## 《代码里的世界》UI篇

用文字札记描绘自己 android学习之路

转载请保留出处 by Qiao

http://blog.csdn.net/qiaoidea/article/details/46417747

【导航】


1.概述

  在讲述了弹出式对话框和对其 源代码分析之后,我们尝试来模仿一下ios中常见的弹出式按钮选项——ActionSheet。事实上样式也比較简单。从底部弹出几个按钮,提供选项菜单,同一时候出现半透明背景蒙版。

详细详情及效果參考IOS设备。这里展示下我们最后实现的各种样式及效果图:

  

  

  


2.分析研究

  动手实现之前,先简单说两句。实现这个样式,详细要怎么做,能做成什么样,究竟该怎么用,究竟好不好用?额。不知道,似乎都看你的编写技巧和封装了。

鄙人愚笨。且写且摸索。

  当然先从熟悉的自己定义View入手。使用一个线性布局LinearLayout嵌套N个Button实现。

黑色半透明背景採用单独一个View,便于包装使用。做出最主要的效果之后。封装对外接口,用String[]数组来存取每一个Button item的文本。并定义一个itemListener,设置监听item的点击事件。

  OK。完毕这个并不难,假设我们想更进一层做好扩展,最好还是尝试使用DialogFragment再做一遍,另外,前面还有提到AlertDialog中使用的Builder模式,我们也来做一下,看看效果怎样。

那么,为什么不更灵活一点儿,使用我们自己定义的样式。能够切换ActionSheet风格。比方IOS6和IOS7?

  详细怎么做。来理下思路。首先继承自Fragment,在OnCreateView中实现自己定义View,当然,在自己定义View中使用我们的自己定义属性,控制风格样式,另外呢。定义一个静态Builder类。负责设置数据与交互逻辑,最后通过Argument绑定到Fragment中去,实现终于效果。


3.详细实现

3.1 自己定义View实现

  第一想法是用LinerLayout包指定个数的button,然后点击从底部弹出。

然后设置背景变暗。后来发现其有用到黑色透明背景的地方貌似非常多。弹窗。弹菜单,消息提示。差点儿都是。

  因此这里先定义一个通用的MaskView,作用就是在窗体最前端弹出一个黑色半透明的遮罩蒙层。

1. MaskView黑色半透明蒙版

  MaskView全局变量

  1. public class MaskView extends RelativeLayout {
  2. protected ViewGroup targetView; //将要加入到的目标view
  3. protected boolean isShowing; //是否显示
  4. protected long durationMillis; //显示动画持续时长
  5. protected boolean canCancel; //能否点击外部隐藏 touchCancelOutside
  6. protected MaskListener maskListener; //点击事件
  7. //...
  8. public interface MaskListener { //点击事件监听接口
  9. void onShow();//显示
  10. void onHide();//隐藏
  11. }
  12. }

  构造方法和初始化。设置MaskView背景黑色,75%透明度,加入到目标view,并绑定点击事件。

  1. public MaskView(Context context, ViewGroup targetView) {
  2. super(context);
  3. this.targetView = targetView;
  4. initialize();
  5. }
  6. protected void initialize() {
  7. setBackgroundColor(0x88000000);
  8. setVisibility(View.GONE);
  9. LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
  10. targetView.addView(this, lp);
  11. /**
  12. * 设置点击事件,假设能够点击空白区域隐藏,则点击隐藏
  13. */
  14. setOnClickListener(new OnClickListener() {
  15. @Override
  16. public void onClick(View v) {
  17. if (canCancel) {
  18. hide();
  19. }
  20. }
  21. });
  22. }

  显示show()和隐藏hide()。

使用透明动画AlphaAnimation。当动画结束运行相应监听事件。

  1. public void show() {
  2. if (isShowing)
  3. return;
  4. isShowing = true;
  5. clearAnimation();
  6. setVisibility(View.VISIBLE);
  7. AlphaAnimation an = new AlphaAnimation(0, 1);
  8. an.setDuration(durationMillis);
  9. startAnimation(an);
  10. if (maskListener != null)
  11. maskListener.onShow();
  12. }
  13. public void hide() {
  14. if (!isShowing)
  15. return;
  16. isShowing = false;
  17. clearAnimation();
  18. AlphaAnimation an = new AlphaAnimation(1, 0);
  19. an.setDuration(durationMillis);
  20. an.setAnimationListener(new AnimationListener() {
  21. @Override
  22. public void onAnimationStart(Animation animation) {
  23. }
  24. @Override
  25. public void onAnimationRepeat(Animation animation) {
  26. }
  27. @Override
  28. public void onAnimationEnd(Animation animation) {
  29. setVisibility(View.GONE);
  30. }
  31. });
  32. startAnimation(an);
  33. if (maskListener != null)
  34. maskListener.onHide();
  35. }

2. ActionSheet底部弹出菜单

  那么这里就打算做成一个RelativeLayout。包裹一个MaskView和一个LinearLayout,这个linearLayout就是前面说的菜单按钮集合了。

  打算做成什么样才干使我们调用方便?我期望是

  1. final ActionSheet actionSheet = new ActionSheet(MainActivity.this);
  2. actionSheet.show("确定要退出么?",new String[]{"退出" },new Action1<Integer>(){
  3. @Override
  4. public void invoke(Integer index) {
  5. actionSheet.hide();
  6. if(index==0){
  7. MainActivity.this.finish();
  8. }
  9. }
  10. });

  构造出来一个ActionSheet对象,然后显示的时候,显示

show(String title , String[] displayStrings, Action1 callback)

title 标题,

displayStrings 即各行item,

callback 回调,它传回的參数int表示第几个item被选中

  详细实现:

  ActionSheet 全局变量:

  1. public class ActionSheet extends RelativeLayout {
  2. protected final static long durationMillis = 200;
  3. protected WindowManager windowManager;
  4. protected GestureDetector gestureDetector; //手势识别
  5. protected MaskView maskView;
  6. protected LinearLayout actionSheetView; //实际展示线性布局
  7. protected Button cancelButton; //取消按钮
  8. //...
  9. }

  构造方法都运行initalize()实现初始化。首先绑定MaskView,加入显示/消失事件监听。

接着初始化线性布局actionSheetView,默认不可见。位于视图底部并设置间距。其次,加入手势监听和按键监听,单点屏幕消失,按返回按钮消失。

  1. protected void initialize() {
  2. //初始化MaskView
  3. maskView = new MaskView(getContext(), this);
  4. maskView.setCanCancel(true);
  5. maskView.setDurationMillis(durationMillis);
  6. maskView.setOnMaskListener(new MaskListener() {
  7. @Override
  8. public void onShow() {
  9. }
  10. @Override
  11. public void onHide() {
  12. hide();
  13. }
  14. });
  15. //初始化线性布局容器actionSheetView
  16. actionSheetView = new LinearLayout(getContext());
  17. actionSheetView.setOrientation(LinearLayout.VERTICAL);
  18. actionSheetView.setVisibility(View.GONE);
  19. RelativeLayout.LayoutParams rlp = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
  20. rlp.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM, RelativeLayout.TRUE); //位于父View底部
  21. rlp.leftMargin = rlp.rightMargin = (int)applyDimension(getContext(), TypedValue.COMPLEX_UNIT_DIP, 8); //左右间距8dip
  22. addView(actionSheetView, rlp); //加入布局
  23. //初始化windowManager
  24. windowManager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
  25. //初始化手势识别事件,单点消失
  26. gestureDetector = new GestureDetector(getContext(), new SimpleOnGestureListener() {
  27. @Override
  28. public boolean onSingleTapUp(MotionEvent e) {
  29. hide();
  30. return super.onSingleTapUp(e);
  31. }
  32. });
  33. //初始化按键监听。按下返回则消失
  34. setOnKeyListener(new OnKeyListener() {
  35. @Override
  36. public boolean onKey(View v, int keyCode, KeyEvent event) {
  37. if (KeyEvent.KEYCODE_BACK == keyCode && KeyEvent.ACTION_DOWN == event.getAction()) {
  38. hide();
  39. return true;
  40. }
  41. return false;
  42. }
  43. });
  44. setFocusable(true);
  45. setFocusableInTouchMode(true);
  46. }

  包装接口。

提供了各种缺省方法。达到不同的显示效果。最后运行的都是

show(String title,String[] displayStrings, boolean[] isShow,final Action1 callback, boolean hasCancelButton)

  先看这些缺省方法:

  1. //仅仅显示各行item以及点击回调
  2. public void show(String[] displayStrings, Action1<Integer> callback) {
  3. show(null,displayStrings, callback, true);
  4. }
  5. //显示各行item和点击回调,添加ishow[]的Boolean控制某行是否显示
  6. public void show(String[] displayStrings,boolean[] isShow,Action1<Integer> callback){
  7. show(null,displayStrings, isShow,callback, true);
  8. }
  9. //显示标题头,各行item以及回调
  10. public void show(String title,String[] displayStrings, Action1<Integer> callback) {
  11. show(title,displayStrings, callback, true);
  12. }
  13. //显示标题头,各行item以及回调,添加ishow[]的Boolean控制某行是否显示
  14. public void show(String title,String[] displayStrings,boolean[] isShow,Action1<Integer> callback){
  15. show(title,displayStrings, isShow,callback, true);
  16. }

  来看终于运行的show方法:

  1. public void show(String title,String[] displayStrings, boolean[] isShow,final Action1<Integer> callback, boolean hasCancelButton) {
  2. /**
  3. * 假设当前view是首次被加入到窗体。则构造WindowManager并加入到根视图
  4. * 初始化WindowManager.LayoutParams,设置背景透明
  5. * 左上角对齐,填充父容器
  6. */
  7. if (getParent() == null) {
  8. WindowManager.LayoutParams wlp = new WindowManager.LayoutParams();
  9. wlp.type = WindowManager.LayoutParams.TYPE_APPLICATION;
  10. wlp.format = PixelFormat.TRANSPARENT; //透明背景
  11. wlp.gravity = Gravity.LEFT | Gravity.TOP; //左上角
  12. wlp.width = LayoutParams.MATCH_PARENT;
  13. wlp.height = LayoutParams.MATCH_PARENT;
  14. windowManager.addView(this, wlp); //加入填充布局
  15. }
  16. maskView.show(); //显示透明蒙版
  17. int mrg = (int) applyDimension(getContext(), TypedValue.COMPLEX_UNIT_DIP, 10);
  18. LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
  19. lp.setMargins(0, mrg, 0, mrg);
  20. actionSheetView.setVisibility(View.VISIBLE); //使actionView显示可见
  21. actionSheetView.removeAllViews(); //清空view
  22. /**
  23. * 假设title不为空而且不为空格,则添加一个标题头
  24. */
  25. if(null!=title&&!title.trim().equals("")){
  26. titleTextView = new TextView(getContext());
  27. titleTextView.setBackgroundColor(Color.TRANSPARENT);
  28. titleTextView.setGravity(Gravity.CENTER);
  29. titleTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 12);
  30. titleTextView.setText(title);
  31. titleTextView.setTextColor(Color.WHITE);
  32. actionSheetView.addView(titleTextView,lp);
  33. }
  34. /**
  35. * 逐行添加displayStrings中的选项到item,
  36. * 为每一行加入点击监听,调用回调
  37. * 依据isShow[]来控制每行显示
  38. */
  39. for (int i = 0, len = displayStrings.length; i < len; i++) {
  40. final int index = i;
  41. Button button = new Button(getContext());
  42. button.setBackgroundResource(R.drawable.actionsheet_red_btn);
  43. button.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18);
  44. button.setText(displayStrings[index]);
  45. button.setTextColor(Color.WHITE);
  46. button.setOnClickListener(new OnClickListener() {
  47. @Override
  48. public void onClick(View v) {
  49. callback.invoke(index);//点击调用回调
  50. }
  51. });
  52. if(isShow[i]) { //控制显示
  53. button.setVisibility(View.GONE);
  54. }
  55. actionSheetView.addView(button, lp);
  56. }
  57. /**
  58. * 依据是否有取消按钮来加入点击取消
  59. */
  60. if (hasCancelButton) {
  61. Button button = new Button(getContext());
  62. button.setBackgroundResource(R.drawable.actionsheet_black_btn);
  63. button.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18);
  64. button.setTextColor(Color.WHITE);
  65. button.setText("取消");
  66. button.setOnClickListener(new OnClickListener() {
  67. @Override
  68. public void onClick(View v) {
  69. hide();
  70. }
  71. });
  72. actionSheetView.addView(button, lp);
  73. }
  74. //清除动画,加入从底部 显示动画效果
  75. actionSheetView.clearAnimation();
  76. TranslateAnimation an = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 1, Animation.RELATIVE_TO_SELF, 0);
  77. an.setDuration(durationMillis);
  78. actionSheetView.startAnimation(an);
  79. }

  隐藏菜单hide()方法:

  1. public void hide() {
  2. maskView.hide(); //隐藏MaskView
  3. actionSheetView.clearAnimation();
  4. TranslateAnimation an = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 1);
  5. an.setDuration(durationMillis);
  6. an.setAnimationListener(new AnimationListener() {
  7. @Override
  8. public void onAnimationStart(Animation animation) {
  9. }
  10. @Override
  11. public void onAnimationRepeat(Animation animation) {
  12. }
  13. @Override
  14. public void onAnimationEnd(Animation animation) {
  15. actionSheetView.setVisibility(View.GONE);
  16. if (getParent() != null)
  17. windowManager.removeView(ActionSheet.this); //动画结束。从窗体移除当前View
  18. }
  19. });
  20. actionSheetView.startAnimation(an);
  21. }

  最后看看用到的几个监听事件接口和像素转换方法:

  1. //像素转换
  2. public static float applyDimension(Context context, int unit, float size) {
  3. Resources r = context.getResources();
  4. return TypedValue.applyDimension(unit, size, r.getDisplayMetrics());
  5. }
  6. //点击监听事件
  7. public interface Action1<T1> {
  8. void invoke(T1 p);
  9. }

  至此我们的自己定义View实现ActionSheet也OK了,基本能胜任日常使用了。

  

  当然,作为一个有逼格的猿,要尝试用更方便的方法,并符合fragmentDialog的要求来做出相同的效果。

3.2 Fragment实现

  使用Fragment的优点这里不再赘述,仅仅简述下要会而且能够秀的关键点:

  • styleable 自己定义主题样式。可选IOS6/IOS7风格以及相应的详细配置
  • Argument数据交互 通过Argument实现fragment交互数据。减少数据耦合度
  • Builder模式 利用Builder构造Fragment,达到简约使用和高速构建

      明白上述三点后。我们来理一下实现思路。

    首先定义xml样式和相应读取Attributes,接着利用在Fragment中初始化和创建界面及动画,提供展示数据和关闭(消除销毁)的接口,然后使用builder构建高速模板。最后呢。就是验收測试和移植到应用了。

1. declare-styleable和 使用Attributes读取

 (1) 定义样式

  先定义Actionsheet详细各个用于界面展示的可配置项,再定义两种相应配置项的两种不同实现样式,最后定义一个可选择项。用来选择使用哪种样式(即内置两种样式之中的一个)。

1.定义ActionSheet本身须要配置样式的可选项。(即在界面中同意外部配置的的地方)

  1. <declare-styleable name="ActionSheet">
  2. <attr name="actionSheetBackground" format="color|reference" />
  3. <attr name="cancelButtonBackground" format="color|reference" />
  4. <attr name="topItemBackground" format="color|reference" />
  5. <attr name="middleItemBackground" format="color|reference" />
  6. <attr name="bottomItemBackground" format="color|reference" />
  7. <attr name="singleItemBackground" format="color|reference" />
  8. <attr name="cancelButtonTextColor" format="color|reference" />
  9. <attr name="titleColor" format="color|reference" />
  10. <attr name="itemTextColor" format="color|reference" />
  11. <attr name="actionSheetPadding" format="dimension|reference" />
  12. <attr name="itemSpacing" format="dimension|reference" />
  13. <attr name="cancelButtonMarginTop" format="dimension|reference" />
  14. <attr name="actionSheetTextSize" format="dimension|reference" />
  15. </declare-styleable>

2.基于ActionSheet配置项来定义两种样式IOS6/IOS7


  1. <style name="ActionSheetStyleIOS6">
  2. <item name="actionSheetBackground">#80000000</item>
  3. <item name="cancelButtonBackground">@drawable/as_cancel_bt_bg</item>
  4. <item name="topItemBackground">@drawable/as_other_bt_bg</item>
  5. <item name="middleItemBackground">@drawable/as_other_bt_bg</item>
  6. <item name="bottomItemBackground">@drawable/as_other_bt_bg</item>
  7. <item name="singleItemBackground">@drawable/as_other_bt_bg</item>
  8. <item name="cancelButtonTextColor">@android:color/white</item>
  9. <item name="itemTextColor">@android:color/black</item>
  10. <item name="titleColor">@android:color/white</item>
  11. <item name="actionSheetPadding">20dp</item>
  12. <item name="itemSpacing">5dp</item>
  13. <item name="cancelButtonMarginTop">20dp</item>
  14. <item name="actionSheetTextSize">16sp</item>
  15. </style>
  16. <style name="ActionSheetStyleIOS7">
  17. <item name="actionSheetBackground">@android:color/transparent</item>
  18. <item name="cancelButtonBackground">@drawable/slt_as_ios7_cancel_bt</item>
  19. <item name="topItemBackground">@drawable/slt_as_ios7_other_bt_top</item>
  20. <item name="middleItemBackground">@drawable/slt_as_ios7_other_bt_middle</item>
  21. <item name="bottomItemBackground">@drawable/slt_as_ios7_other_bt_bottom</item>
  22. <item name="singleItemBackground">@drawable/slt_as_ios7_other_bt_single</item>
  23. <item name="cancelButtonTextColor">#1E82FF</item>
  24. <item name="itemTextColor">#1E82FF</item>
  25. <item name="titleColor">@android:color/white</item>
  26. <item name="actionSheetPadding">10dp</item>
  27. <item name="itemSpacing">0dp</item>
  28. <item name="cancelButtonMarginTop">10dp</item>
  29. <item name="actionSheetTextSize">16sp</item>
  30. </style>

3.在styleable中定义一个选择样式的选项Actionsheets。其类型为reference,可选择的样式(可选ios6/ios7)将实现ActionSheet的详细配置项;

  1. <declare-styleable name="ActionSheets">
  2. <attr name="actionSheetStyle" format="reference" />
  3. </declare-styleable>
 (2) 样式读取

  首先相同定义一个java类,保存各种配置项和相应的值,然后我们现依据配置的样式选项来确定详细使用的样式方案,最后读取该方案下的详细配置并赋值。

  1.定义详细属性样式类

  1. /**
  2. * actionsheet各种属性样式
  3. */
  4. private static class Attributes {
  5. Context mContext;
  6. Drawable background;
  7. Drawable cancelButtonBackground;
  8. Drawable topItemBackground;
  9. Drawable middleItemBackground;
  10. Drawable bottomItemBackground;
  11. Drawable singleItemBackground;
  12. int cancelButtonTextColor;
  13. int itemTextColor;
  14. int titleColor;
  15. int padding;
  16. int itemSpacing;
  17. int margin;
  18. int cancelButtonMarginTop;
  19. float textSize;
  20. public Attributes(Context context) {
  21. mContext = context;
  22. this.background = new ColorDrawable(Color.TRANSPARENT);
  23. this.cancelButtonBackground = new ColorDrawable(Color.BLACK);
  24. ColorDrawable gray = new ColorDrawable(Color.GRAY);
  25. this.topItemBackground = gray;
  26. this.middleItemBackground = gray;
  27. this.bottomItemBackground = gray;
  28. this.singleItemBackground = gray;
  29. this.cancelButtonTextColor = Color.WHITE;
  30. this.titleColor = Color.WHITE;
  31. this.itemTextColor = Color.BLACK;
  32. this.padding = dp2px(20);
  33. this.itemSpacing = dp2px(2);
  34. this.margin = dp2px(5);
  35. this.cancelButtonMarginTop = dp2px(10);
  36. this.textSize = dp2px(16);
  37. }
  38. /**
  39. * dp转像素类
  40. * @param dp
  41. * @return
  42. */
  43. private int dp2px(int dp){
  44. return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
  45. dp, mContext.getResources().getDisplayMetrics());
  46. }
  47. /**
  48. * 获取items按键状态相应背景
  49. * @return
  50. */
  51. public Drawable getMiddleItemBackground() {
  52. if (middleItemBackground instanceof StateListDrawable) {
  53. TypedArray a = mContext.getTheme().obtainStyledAttributes(null,
  54. R.styleable.ActionSheet, R.attr.actionSheetStyle, 0);
  55. middleItemBackground = a
  56. .getDrawable(R.styleable.ActionSheet_middleItemBackground);
  57. a.recycle();
  58. }
  59. return middleItemBackground;
  60. }
  61. }

  2.读取样式并赋值

  1. protected Attributes initAttrs(){
  2. Attributes attrs = new Attributes(getActivity());
  3. TypedArray a = getActivity().getTheme().obtainStyledAttributes(null,
  4. R.styleable.ActionSheet, R.attr.actionSheetStyle, 0);
  5. Drawable background = a
  6. .getDrawable(R.styleable.ActionSheet_actionSheetBackground);
  7. if (background != null) {
  8. attrs.background = background;
  9. }
  10. Drawable cancelButtonBackground = a
  11. .getDrawable(R.styleable.ActionSheet_cancelButtonBackground);
  12. if (cancelButtonBackground != null) {
  13. attrs.cancelButtonBackground = cancelButtonBackground;
  14. }
  15. Drawable itemTopBackground = a
  16. .getDrawable(R.styleable.ActionSheet_topItemBackground);
  17. if (itemTopBackground != null) {
  18. attrs.topItemBackground = itemTopBackground;
  19. }
  20. Drawable itemMiddleBackground = a
  21. .getDrawable(R.styleable.ActionSheet_middleItemBackground);
  22. if (itemMiddleBackground != null) {
  23. attrs.middleItemBackground = itemMiddleBackground;
  24. }
  25. Drawable itemBottomBackground = a
  26. .getDrawable(R.styleable.ActionSheet_bottomItemBackground);
  27. if (itemBottomBackground != null) {
  28. attrs.bottomItemBackground = itemBottomBackground;
  29. }
  30. Drawable itemSingleBackground = a
  31. .getDrawable(R.styleable.ActionSheet_singleItemBackground);
  32. if (itemSingleBackground != null) {
  33. attrs.singleItemBackground = itemSingleBackground;
  34. }
  35. attrs.titleColor = a.getColor(
  36. R.styleable.ActionSheet_titleColor,
  37. attrs.titleColor);
  38. attrs.cancelButtonTextColor = a.getColor(
  39. R.styleable.ActionSheet_cancelButtonTextColor,
  40. attrs.cancelButtonTextColor);
  41. attrs.itemTextColor = a.getColor(
  42. R.styleable.ActionSheet_itemTextColor,
  43. attrs.itemTextColor);
  44. attrs.padding = (int) a.getDimension(
  45. R.styleable.ActionSheet_actionSheetPadding, attrs.padding);
  46. attrs.itemSpacing = (int) a.getDimension(
  47. R.styleable.ActionSheet_itemSpacing,
  48. attrs.itemSpacing);
  49. attrs.cancelButtonMarginTop = (int) a.getDimension(
  50. R.styleable.ActionSheet_cancelButtonMarginTop,
  51. attrs.cancelButtonMarginTop);
  52. attrs.textSize = a.getDimensionPixelSize(R.styleable.ActionSheet_actionSheetTextSize, (int) attrs.textSize);
  53. a.recycle();
  54. return attrs;
  55. }

2. 实现Fragment界面和数据绑定

  Fragment全局变量和參数:

  1. public class ActionSheet extends Fragment implements OnClickListener{
  2. protected WindowManager windowManager;
  3. protected View mBackgroundView;
  4. protected ViewGroup decorView;
  5. protected View parent;
  6. protected LinearLayout container; //各个item选项容器
  7. protected Attributes mAttrs; //读取的样式參数配置
  8. private ItemClikListener mItemClikListener; //item点击监听事件
  9. private CancelListener mCancelListener; //取消按键监听事件
  10. protected boolean isDismissed = true;
  11. protected boolean isCancel = true;
  12. /**
  13. * 几个item的Id
  14. */
  15. public static final int BG_VIEW_ID = 100;
  16. public static final int CANCEL_BUTTON_ID = 101;
  17. public static final int ITEM_ID = 102;
  18. //两种样式相应key。便于外部设置
  19. public static final int ActionSheetThemeIOS6 = R.style.ActionSheetStyleIOS6;
  20. public static final int ActionSheetThemeIOS7 = R.style.ActionSheetStyleIOS7;
  21. protected final static long durationMillis = 200; //显示消失动画时长
  22. //。
  23. 。。
  24. }

  Fragment构造和初始化做了三件事:

  1. @Override
  2. public View onCreateView(LayoutInflater inflater, ViewGroup container,
  3. Bundle savedInstanceState) {
  4. initalize();
  5. startSlideInAnim();
  6. return super.onCreateView(inflater, container, savedInstanceState);
  7. }
  8. protected void initalize() {
  9. tryHideSoftInput(); //尝试隐藏输入法
  10. mAttrs = initAttrs(); //读取样式
  11. initViews(); //初始化绑定view
  12. }
  13. /**
  14. * 假设输入法键盘没有隐藏,则隐藏软键盘
  15. */
  16. protected void tryHideSoftInput(){
  17. InputMethodManager imm = (InputMethodManager) getActivity()
  18. .getSystemService(Context.INPUT_METHOD_SERVICE);
  19. if (imm.isActive()) {
  20. View focusView = getActivity().getCurrentFocus();
  21. if (focusView != null) {
  22. imm.hideSoftInputFromWindow(focusView.getWindowToken(), 0);
  23. }
  24. }
  25. }
  26. //读取样式前边已贴。。。
  27. /**
  28. * 初始化背景view 和 底部items
  29. */
  30. protected void initViews(){
  31. windowManager = (WindowManager) getActivity().getSystemService(Context.WINDOW_SERVICE);
  32. FrameLayout parent = new FrameLayout(getActivity());
  33. /**
  34. *初始化背景view
  35. */
  36. mBackgroundView = new View(getActivity());
  37. mBackgroundView.setBackgroundColor(0x88000000);
  38. mBackgroundView.setId(BG_VIEW_ID);
  39. mBackgroundView.setOnClickListener(this);
  40. FrameLayout.LayoutParams bgLayoutParams = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,
  41. FrameLayout.LayoutParams.MATCH_PARENT);
  42. parent.addView(mBackgroundView);
  43. /**
  44. *初始化包括item选项的view
  45. */
  46. container = new LinearLayout(getActivity());
  47. FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,
  48. FrameLayout.LayoutParams.WRAP_CONTENT);
  49. params.gravity = Gravity.BOTTOM;
  50. container.setLayoutParams(params);
  51. container.setOrientation(LinearLayout.VERTICAL);
  52. createItems();
  53. parent.addView(container);
  54. this.parent = parent;
  55. //获取跟试图并加入view
  56. decorView = (ViewGroup) getActivity().getWindow().getDecorView();
  57. decorView.addView(parent);
  58. }

  创建选项并绑定数据和事件

  1. /**
  2. * 初始化底部items
  3. */
  4. protected void createItems(){
  5. //创建title
  6. String mTitle = getArguments().getString(Builder.ARG_TITLE);
  7. if(mTitle !=null){
  8. TextView title = new TextView(getActivity());
  9. title.getPaint().setFakeBoldText(true);
  10. title.setTextSize(TypedValue.COMPLEX_UNIT_PX, mAttrs.textSize);
  11. title.setText(mTitle);
  12. title.setTextColor(mAttrs.titleColor);
  13. title.setGravity(Gravity.CENTER);
  14. LinearLayout.LayoutParams params = createItemLayoutParams();
  15. int margin = mAttrs.itemSpacing > 0 ?
  16. mAttrs.itemSpacing:mAttrs.margin;
  17. params.setMargins(margin, 0, margin, margin);
  18. container.addView(title, params);
  19. }
  20. //创建items
  21. String[] titles = getArguments().getStringArray(Builder.ARG_ITEM_TITLES);
  22. if (titles != null) {
  23. for (int i = 0; i < titles.length; i++) {
  24. Button bt = new Button(getActivity());
  25. bt.setId(ITEM_ID + i);
  26. bt.setOnClickListener(this);
  27. bt.setBackgroundDrawable(getItemBg(titles, i));
  28. bt.setText(titles[i]);
  29. bt.setTextColor(mAttrs.itemTextColor);
  30. bt.setTextSize(TypedValue.COMPLEX_UNIT_PX, mAttrs.textSize);
  31. if (i > 0) {
  32. LinearLayout.LayoutParams params = createItemLayoutParams();
  33. params.topMargin = mAttrs.itemSpacing;
  34. container.addView(bt, params);
  35. } else {
  36. container.addView(bt);
  37. }
  38. }
  39. }
  40. //创建cancelbutton
  41. String cancelText = getArguments().getString(Builder.ARG_CANCEL_TEXT);
  42. if(cancelText != null){
  43. Button bt = new Button(getActivity());
  44. bt.getPaint().setFakeBoldText(true);
  45. bt.setTextSize(TypedValue.COMPLEX_UNIT_PX, mAttrs.textSize);
  46. bt.setId(CANCEL_BUTTON_ID);
  47. bt.setBackgroundDrawable(mAttrs.cancelButtonBackground);
  48. bt.setText(cancelText);
  49. bt.setTextColor(mAttrs.cancelButtonTextColor);
  50. bt.setOnClickListener(this);
  51. LinearLayout.LayoutParams params = createItemLayoutParams();
  52. params.topMargin = mAttrs.cancelButtonMarginTop;
  53. container.addView(bt, params);
  54. }
  55. container.setBackgroundDrawable(mAttrs.background);
  56. container.setPadding(mAttrs.padding, mAttrs.padding, mAttrs.padding,
  57. mAttrs.padding);
  58. }

  Fragment展示和消失方法接口

  1. public void show(FragmentManager manager, String tag) {
  2. if (!isDismissed) {
  3. return;
  4. }
  5. isDismissed = false;
  6. FragmentTransaction ft = manager.beginTransaction();
  7. ft.add(this, tag);
  8. ft.addToBackStack(null); //同意回退栈
  9. ft.commit();
  10. }
  11. public void dismiss(){
  12. if (isDismissed) {
  13. return;
  14. }
  15. isDismissed = true;
  16. getFragmentManager().popBackStack();
  17. FragmentTransaction ft = getFragmentManager().beginTransaction();
  18. ft.remove(this);
  19. ft.commit();
  20. }

  显示消失相应的动画:

  1. private void startSlideInAnim() {
  2. container.clearAnimation();
  3. mBackgroundView.clearAnimation();
  4. TranslateAnimation translateAnimation = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 1, Animation.RELATIVE_TO_SELF, 0);
  5. translateAnimation.setDuration(durationMillis);
  6. AlphaAnimation alphaAnimation = new AlphaAnimation(0, 1);
  7. alphaAnimation.setDuration(durationMillis);
  8. container.startAnimation(translateAnimation);
  9. mBackgroundView.startAnimation(alphaAnimation);
  10. }
  11. private void startSlideOutAnim() {
  12. container.clearAnimation();
  13. TranslateAnimation translateAnimation = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 1);
  14. translateAnimation.setDuration(durationMillis);
  15. mBackgroundView.clearAnimation();
  16. AlphaAnimation alphaAnimation = new AlphaAnimation(1, 0);
  17. alphaAnimation.setDuration(durationMillis);
  18. container.startAnimation(translateAnimation);
  19. mBackgroundView.startAnimation(alphaAnimation);
  20. }

  点击事件的处理:

  1. @Override
  2. public void onClick(View v) {
  3. if (v.getId() == BG_VIEW_ID && !getArguments().getBoolean(Builder.ARG_CANCELABLE_ONTOUCHOUTSIDE)) {
  4. return;
  5. }
  6. if (v.getId() != CANCEL_BUTTON_ID && v.getId() != BG_VIEW_ID) {
  7. if (mItemClikListener != null) {
  8. mItemClikListener.onitemClick(this, v.getId() - CANCEL_BUTTON_ID
  9. - 1);
  10. }
  11. isCancel = false;
  12. }
  13. dismiss();
  14. }

  Fragment销毁时处理逻辑:

  1. @Override
  2. public void onDestroyView() {
  3. startSlideOutAnim();
  4. container.postDelayed(new Runnable() {
  5. @Override
  6. public void run() {
  7. decorView.removeView(parent);
  8. }
  9. }, durationMillis);
  10. if (mCancelListener != null) {
  11. mCancelListener.onCancelClick(this);
  12. }
  13. super.onDestroyView();
  14. }

  Fragment事件接口定义:

(注: show()差别于display()方法,show仅仅是负责展示。display负责显示/消除的切换)

  1. protected void setActionSheetListener(ItemClikListener itemClikListener,CancelListener cancelListener){
  2. this.mItemClikListener = itemClikListener;
  3. this.mCancelListener = cancelListener;
  4. }
  5. public static interface ItemClikListener{
  6. void onitemClick(ActionSheet actionSheet, int index);
  7. }
  8. public static interface CancelListener{
  9. void onCancelClick(ActionSheet actionSheet);
  10. }

3. Builder模式封装和高速构建

  通过Builder静态高速构建Fragment对象

  1. public static Builder createBuilder(Context context,
  2. FragmentManager fragmentManager) {
  3. return init(context, fragmentManager);
  4. }

  Builder类以及对外封装接口:


  1. public static class Builder {
  2. private String mTag = "ActionSheet";
  3. private static final String ARG_TITLE = "title";
  4. private static final String ARG_CANCEL_TEXT = "cancel_text";
  5. private static final String ARG_ITEM_TITLES = "items_text";
  6. private static final String ARG_CANCELABLE_ONTOUCHOUTSIDE = "cancelable_ontouchoutside";
  7. private FragmentManager mFragmentManager;
  8. private Context mContext;
  9. private String mTitle;
  10. private String mCancelButtonText;
  11. private String[] mItemsText;
  12. private boolean mCancelableOnTouchOutside = true;
  13. private ItemClikListener itemClikListener;
  14. private CancelListener cancelListener;
  15. private boolean isThemed = false;
  16. public Builder(Context context, FragmentManager fragmentManager) {
  17. mContext = context;
  18. mFragmentManager = fragmentManager;
  19. }
  20. public Builder setTitle(String title) {
  21. mTitle = title;
  22. return this;
  23. }
  24. public Builder setCancelText(String title) {
  25. mCancelButtonText = title;
  26. return this;
  27. }
  28. public Builder setCancelText(String title,CancelListener cancelListener) {
  29. mCancelButtonText = title;
  30. this.cancelListener = cancelListener;
  31. return this;
  32. }
  33. public Builder setItemTexts(String... titles) {
  34. mItemsText = titles;
  35. return this;
  36. }
  37. public Builder setTheme(int resid){
  38. isThemed = true;
  39. mContext.setTheme(resid);
  40. return this;
  41. }
  42. public Builder setTag(String tag) {
  43. mTag = tag;
  44. return this;
  45. }
  46. public Builder setItemClickListener(ItemClikListener listener) {
  47. this.itemClikListener = listener;
  48. return this;
  49. }
  50. public Builder setCancelableOnTouchOutside(boolean cancelable) {
  51. mCancelableOnTouchOutside = cancelable;
  52. return this;
  53. }
  54. public Bundle initArguments() {
  55. Bundle bundle = new Bundle();
  56. bundle.putString(ARG_TITLE, mTitle);
  57. bundle.putString(ARG_CANCEL_TEXT, mCancelButtonText);
  58. bundle.putStringArray(ARG_ITEM_TITLES, mItemsText);
  59. bundle.putBoolean(ARG_CANCELABLE_ONTOUCHOUTSIDE,
  60. mCancelableOnTouchOutside);
  61. return bundle;
  62. }
  63. public static ActionSheet actionSheet;
  64. public ActionSheet show() {
  65. if(!isThemed)
  66. setTheme(R.style.ActionSheetStyleIOS7);
  67. if(actionSheet == null) {
  68. actionSheet = (ActionSheet) Fragment.instantiate(
  69. mContext, ActionSheet.class.getName(), initArguments());
  70. actionSheet.setActionSheetListener(itemClikListener, cancelListener);
  71. }
  72. actionSheet.show(mFragmentManager, mTag);
  73. return actionSheet;
  74. }
  75. public ActionSheet display(){
  76. if(actionSheet == null){
  77. if(!isThemed)
  78. setTheme(R.style.ActionSheetStyleIOS7);
  79. actionSheet = (ActionSheet) Fragment.instantiate(
  80. mContext, ActionSheet.class.getName(), initArguments());
  81. actionSheet.setActionSheetListener(itemClikListener, cancelListener);
  82. }
  83. if(actionSheet.isDismissed){
  84. actionSheet.show(mFragmentManager, mTag);
  85. }else{
  86. actionSheet.dismiss();
  87. }
  88. return actionSheet;
  89. }
  90. }

  以上便是Fragment实现ActionSheet的方法,我们使用的时候,也是各种灵活:(不调用方法即不配置该项)

  1. ActionSheet.init(this)
  2. .setTitle("This is test title ,do you want do something?")
  3. .setTheme(resid)
  4. .setItemTexts("Item1", "Item2", "Item3", "Item4")
  5. .setItemClickListener(new ItemClikListener() {
  6. @Override
  7. public void onitemClick(
  8. baoyz.qiao.actionsheet.ActionSheet actionSheet,
  9. int index) {
  10. Toast.makeText(getApplicationContext(), "click item index = " + index,0).show();
  11. }
  12. })
  13. .setCancelText("Cancel")
  14. // .setCancelText("Cancel",new CancelListener() {
  15. // @Override
  16. // public void onCancelClick(ActionSheet actionSheet) {
  17. // Toast.makeText(getApplicationContext(), "dismissed ", 0).show();
  18. // }
  19. // })
  20. .show();

当中,

+ setTheme() 參数可选 R.style.ActionSheetStyleIOS6 或 R.style.ActionSheetStyleIOS6 。不设置则默觉得 R.style.ActionSheetStyleIOS7。注:

+ setTitle不设置(不调用该方法) 则为无title样式

+ setCanTouchOutside(false) 点击选项外无响应,默觉得true,点击隐藏

+ setCancelText() 不设置 则不显示取消按钮。

其參数有两个,第一个string为显示内容,第二个listener为相应事件

+ show() 最后。别忘记调用show让其显示。


3.综述总结

  至此,我们的ActionSheet两种样式即实现已经ok了。

日常使用也能够通过前面两种封装接口加以调用。方案github挺火。效果不好用我会乱说?!

当然感谢baoyongzhang大士的贡献及灵感。

  最后额外赠送了一个线性式ActionSheetLayout布局,即演示界面的头部布局。

事实上就是一个普通的linearLayout使用。只是同意相似ActionSheet的显示隐藏效果。作为补充呢。能够自己私下尝试使用很多其它的展示样式,比方从左側或右側现实和消失。

有兴趣最好还是做一下。

  关门,放代码:

  演示样例demo源代码下载地址 (资源上传较慢,假设不可用。试试这里


UI--仿IOS控件之ActionSheet样式 and more..的更多相关文章

  1. iOS开发UI篇—UITableview控件简单介绍

    iOS开发UI篇—UITableview控件简单介绍 一.基本介绍 在众多移动应⽤用中,能看到各式各样的表格数据 . 在iOS中,要实现表格数据展示,最常用的做法就是使用UITableView,UIT ...

  2. iOS开发UI篇—UITableview控件基本使用

    iOS开发UI篇—UITableview控件基本使用 一.一个简单的英雄展示程序 NJHero.h文件代码(字典转模型) #import <Foundation/Foundation.h> ...

  3. iOS开发UI篇—UIScrollView控件实现图片缩放功能

    iOS开发UI篇—UIScrollView控件实现图片缩放功能 一.缩放 1.简单说明: 有些时候,我们可能要对某些内容进行手势缩放,如下图所示 UIScrollView不仅能滚动显示大量内容,还能对 ...

  4. iOS开发UI篇—UIScrollView控件介绍

    iOS开发UI篇—UIScrollView控件介绍 一.知识点简单介绍 1.UIScrollView控件是什么? (1)移动设备的屏幕⼤大⼩小是极其有限的,因此直接展⽰示在⽤用户眼前的内容也相当有限 ...

  5. iOS开发UI篇—UITableview控件使用小结

    iOS开发UI篇—UITableview控件使用小结 一.UITableview的使用步骤 UITableview的使用就只有简单的三个步骤: 1.告诉一共有多少组数据 方法:- (NSInteger ...

  6. iOS开发UI篇—UIScrollView控件实现图片轮播

    iOS开发UI篇—UIScrollView控件实现图片轮播 一.实现效果 实现图片的自动轮播            二.实现代码 storyboard中布局 代码: #import "YYV ...

  7. 【转】 iOS开发UI篇—UIScrollView控件实现图片轮播

    原文:http://www.cnblogs.com/wendingding/p/3763527.html iOS开发UI篇—UIScrollView控件实现图片轮播 一.实现效果 实现图片的自动轮播 ...

  8. Android UI 统一修改Button控件的样式,以及其它系统控件的默认样式

    先介绍下修改原理:首先打开位于android.widget包下面的Button.java文件,这里有一句关键的代码如下: public Button(Context context, Attribut ...

  9. [转]设置控件全局显示样式appearance proxy

    转自:huifeidexin_1的专栏 appearance是apple在iOS5.0上加的一个协议,它让程序员可以很轻松地改变某控件的全局样式(背景) @selector(appearance) 支 ...

随机推荐

  1. EDA: Event-Driven Architecture事件驱动架构

    EDA: Event-Driven Architecture事件驱动架构 2009-09-24 17:28 5 赞  异步编程      软件架构      EDA事件驱动        SOA的核心 ...

  2. Class.isAssignableFrom(Class clz)方法 与 instanceof 关键字的区别

    Class.isAssignableFrom()是用来判断一个类Class1和另一个类Class2是否相同或是另一个类的子类或接口.   格式为:        Class1.isAssignable ...

  3. iOS编程(双语版) - 视图 - Transform(转换)

    视图有一个transform属性,它描述了应该如何绘制该视图. 该属性是CGAffineTransform结构体,它代表了3 x 3的变换矩阵(线性代数). 下面的代码让两个矩形视图旋转45度 (Ob ...

  4. TFS 之 彻底删除团队项目

    方式一 通过选择“齿轮图标”打开团队项目集合的管理上下文. 打开要删除的团队项目的 上下文菜单. 如果未看到上下文图标 (),则你不是在访问 Visual Studio Online,或不是项目集合管 ...

  5. 使用windbg抓取崩溃文件和分析的过程

    在软件编程中,崩溃的场景比较常见的.且说微软技术再牛X,也是会出现崩溃的场景.网上有一段Win98当着比尔盖茨蓝屏的视频非常有意思. (转载请指明出于breaksoftware的csdn博客)     ...

  6. Excel 2007 若干技巧。

    1.自定义序列 office按钮→excel选项→常用→编辑自定义列表 2.无法清空剪贴板错误的处理办法: 取消"显示粘贴选项"选项 3.每次选定同一单元格 输入后按ctrl+En ...

  7. Sqlite和Mysql和SqlServer中insert … select … where not exist的用法

    下面介绍Mysql和Sqlite和Sqlserver中,根据select的条件判断是否插入.例如: 一.Mysql中: INSERT INTO books (name) SELECT 'SongXin ...

  8. HTTP长连接与短链接

    想要充分了解HTTP长连接,需要首先知道一些基本概念: TCP连接 当网络通信时采用TCP协议时,在真正的读写操作之前,server与client之间必须建立一个连接,当读写操作完成后,双方不再需要这 ...

  9. 转:初探nginx架构(一)

    来源:http://tengine.taobao.org/book/chapter_02.html 众所周知,nginx性能高,而nginx的高性能与其架构是分不开的.那么nginx究竟是怎么样的呢? ...

  10. 算法笔记_219:泊松分酒(Java)

    目录 1 问题描述 2 解决方案   1 问题描述 泊松是法国数学家.物理学家和力学家.他一生致力科学事业,成果颇多.有许多著名的公式定理以他的名字命名,比如概率论中著名的泊松分布. 有一次闲暇时,他 ...