UI--仿IOS控件之ActionSheet样式 and more..

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

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

转载请保留出处 by Qiao
http://blog.csdn.net/qiaoidea/article/details/46417747

【导航】

  • 弹出式对话框各种方案 从仿QQ消息提示框来谈弹出式对话框的实现方式 (Dialog,PopupWind,自定义View,Activity,FragmentDialog)
  • Dialog源码解析 从源码上看Dialog与DialogFragment
  • 仿IOS ActionSheet 两种方式实现ActionSheet底部弹出菜单效果

1.概述

  在讲述了弹出式对话框和对其 源码分析之后,我们尝试来模仿一下ios中常见的弹出式按钮选项——ActionSheet。其实样式也比较简单,从底部弹出几个按钮,提供选项菜单,同时出现半透明背景蒙版。具体详情及效果参考IOS设备。这里展示下我们最后实现的各种样式及效果图:
  
  UI--仿IOS控件之ActionSheet样式 and more.._第1张图片
  


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全局变量

public class MaskView extends RelativeLayout {
    protected ViewGroup targetView; //将要添加到的目标view
    protected boolean isShowing; //是否显示
    protected long durationMillis; //显示动画持续时长
    protected boolean canCancel; //是否能点击外部隐藏 touchCancelOutside
    protected MaskListener maskListener; //点击事件

    //...

    public interface MaskListener { //点击事件监听接口
        void onShow();//显示
        void onHide();//隐藏
    }
 }

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

public MaskView(Context context, ViewGroup targetView) {
        super(context);
        this.targetView = targetView;
        initialize();
    }

    protected void initialize() {
        setBackgroundColor(0x88000000);
        setVisibility(View.GONE);
        LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
        targetView.addView(this, lp);
        /**
        * 设置点击事件,如果可以点击空白区域隐藏,则点击隐藏
        */
        setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                if (canCancel) { 
                    hide();
                }
            }
        });
    }

  显示show()和隐藏hide()。使用透明动画AlphaAnimation,当动画结束执行相应监听事件。

 public void show() {
        if (isShowing)
            return;
        isShowing = true;
        clearAnimation();
        setVisibility(View.VISIBLE);
        AlphaAnimation an = new AlphaAnimation(0, 1);
        an.setDuration(durationMillis);
        startAnimation(an);
        if (maskListener != null)
            maskListener.onShow();
    }

    public void hide() {
        if (!isShowing)
            return;
        isShowing = false;
        clearAnimation();
        AlphaAnimation an = new AlphaAnimation(1, 0);
        an.setDuration(durationMillis);
        an.setAnimationListener(new AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {
            }

            @Override
            public void onAnimationRepeat(Animation animation) {
            }

            @Override
            public void onAnimationEnd(Animation animation) {
                setVisibility(View.GONE);
            }
        });
        startAnimation(an);
        if (maskListener != null)
            maskListener.onHide();
    }

2. ActionSheet底部弹出菜单

  那么这里就打算做成一个RelativeLayout,包裹一个MaskView和一个LinearLayout,这个linearLayout就是前面说的菜单按钮集合了。
  打算做成什么样才能使我们调用方便?我期望是

final ActionSheet actionSheet = new ActionSheet(MainActivity.this);
        actionSheet.show("确定要退出么?",new String[]{"退出" },new Action1(){
            @Override
            public void invoke(Integer index) {
                actionSheet.hide();
                if(index==0){
                    MainActivity.this.finish();
                }
            }
        });

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

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

title 标题,
displayStrings 即各行item,
callback 回调,它传回的参数int表示第几个item被选中

  具体实现:
  ActionSheet 全局变量:

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

  构造方法都执行initalize()实现初始化。首先绑定MaskView,添加显示/消失事件监听。接着初始化线性布局actionSheetView,默认不可见。位于视图底部并设置间距。其次,添加手势监听和按键监听,单点屏幕消失,按返回按钮消失。

    protected void initialize() {
        //初始化MaskView
        maskView = new MaskView(getContext(), this);
        maskView.setCanCancel(true);
        maskView.setDurationMillis(durationMillis);
        maskView.setOnMaskListener(new MaskListener() {
            @Override
            public void onShow() {
            }

            @Override
            public void onHide() {
                hide();
            }
        });

        //初始化线性布局容器actionSheetView
        actionSheetView = new LinearLayout(getContext());
        actionSheetView.setOrientation(LinearLayout.VERTICAL);
        actionSheetView.setVisibility(View.GONE);
        RelativeLayout.LayoutParams rlp = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
        rlp.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM, RelativeLayout.TRUE); //位于父View底部
        rlp.leftMargin = rlp.rightMargin = (int)applyDimension(getContext(), TypedValue.COMPLEX_UNIT_DIP, 8); //左右间距8dip
        addView(actionSheetView, rlp); //添加布局

        //初始化windowManager
        windowManager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
        //初始化手势识别事件,单点消失
        gestureDetector = new GestureDetector(getContext(), new SimpleOnGestureListener() {
            @Override
            public boolean onSingleTapUp(MotionEvent e) {
                hide();
                return super.onSingleTapUp(e);
            }
        });
        //初始化按键监听,按下返回则消失
        setOnKeyListener(new OnKeyListener() {
            @Override
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                if (KeyEvent.KEYCODE_BACK == keyCode && KeyEvent.ACTION_DOWN == event.getAction()) {
                    hide();
                    return true;
                }
                return false;
            }
        });
        setFocusable(true);
        setFocusableInTouchMode(true);
    }

  包装接口。提供了各种缺省方法,达到不同的显示效果。最后执行的都是

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

  先看这些缺省方法:

    //只显示各行item以及点击回调
     public void show(String[] displayStrings, Action1 callback) {
        show(null,displayStrings, callback, true);
    }

    //显示各行item和点击回调,增加ishow[]的Boolean控制某行是否显示
    public void show(String[] displayStrings,boolean[] isShow,Action1 callback){
         show(null,displayStrings, isShow,callback, true);
    }

    //显示标题头,各行item以及回调
    public void show(String title,String[] displayStrings, Action1 callback) {
        show(title,displayStrings, callback, true);
    }

    //显示标题头,各行item以及回调,增加ishow[]的Boolean控制某行是否显示
    public void show(String title,String[] displayStrings,boolean[] isShow,Action1 callback){
         show(title,displayStrings, isShow,callback, true);
    }

  来看最终执行的show方法:

public void show(String title,String[] displayStrings, boolean[] isShow,final Action1 callback, boolean hasCancelButton) {
        /**
        * 如果当前view是首次被添加到窗口,则构造WindowManager并添加到根视图
        * 初始化WindowManager.LayoutParams,设置背景透明
        * 左上角对齐,填充父容器
        */
        if (getParent() == null) {
            WindowManager.LayoutParams wlp = new WindowManager.LayoutParams();
            wlp.type = WindowManager.LayoutParams.TYPE_APPLICATION;
            wlp.format = PixelFormat.TRANSPARENT; //透明背景
            wlp.gravity = Gravity.LEFT | Gravity.TOP; //左上角
            wlp.width = LayoutParams.MATCH_PARENT;
            wlp.height = LayoutParams.MATCH_PARENT;
            windowManager.addView(this, wlp); //添加填充布局
        }

        maskView.show(); //显示透明蒙版

        int mrg = (int) applyDimension(getContext(), TypedValue.COMPLEX_UNIT_DIP, 10);
        LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
        lp.setMargins(0, mrg, 0, mrg);

        actionSheetView.setVisibility(View.VISIBLE); //使actionView显示可见
        actionSheetView.removeAllViews(); //清空view

        /**
        * 如果title不为空并且不为空格,则增加一个标题头
        */
        if(null!=title&&!title.trim().equals("")){
            titleTextView = new TextView(getContext());
            titleTextView.setBackgroundColor(Color.TRANSPARENT);
            titleTextView.setGravity(Gravity.CENTER);
            titleTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 12);
            titleTextView.setText(title);
            titleTextView.setTextColor(Color.WHITE);
            actionSheetView.addView(titleTextView,lp);
        }

        /**
        * 逐行增加displayStrings中的选项到item,
        * 为每一行添加点击监听,调用回调
        * 根据isShow[]来控制每行显示
        */
        for (int i = 0, len = displayStrings.length; i < len; i++) {
            final int index = i;
            Button button = new Button(getContext());
            button.setBackgroundResource(R.drawable.actionsheet_red_btn);
            button.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18);
            button.setText(displayStrings[index]);
            button.setTextColor(Color.WHITE);
            button.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    callback.invoke(index);//点击调用回调
                }
            });
            if(isShow[i]) { //控制显示
                button.setVisibility(View.GONE);
            }
            actionSheetView.addView(button, lp);
        }

        /**
        * 根据是否有取消按钮来添加点击取消
        */
        if (hasCancelButton) {
            Button button = new Button(getContext());
            button.setBackgroundResource(R.drawable.actionsheet_black_btn);
            button.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18);
            button.setTextColor(Color.WHITE);
            button.setText("取消");
            button.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    hide();
                }
            });
            actionSheetView.addView(button, lp);
        }

        //清除动画,添加从底部 显示动画效果
        actionSheetView.clearAnimation();
        TranslateAnimation an = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 1, Animation.RELATIVE_TO_SELF, 0);
        an.setDuration(durationMillis);
        actionSheetView.startAnimation(an);
    }

  隐藏菜单hide()方法:

 public void hide() {
        maskView.hide(); //隐藏MaskView
        actionSheetView.clearAnimation();
        TranslateAnimation an = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 1);
        an.setDuration(durationMillis);
        an.setAnimationListener(new AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {
            }

            @Override
            public void onAnimationRepeat(Animation animation) {
            }

            @Override
            public void onAnimationEnd(Animation animation) {
                actionSheetView.setVisibility(View.GONE);
                if (getParent() != null)
                    windowManager.removeView(ActionSheet.this); //动画结束,从窗口移除当前View
            }
        });
        actionSheetView.startAnimation(an);
    }

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

    //像素转换
    public static float applyDimension(Context context, int unit, float size) {
        Resources r = context.getResources();
        return TypedValue.applyDimension(unit, size, r.getDisplayMetrics());
    }

    //点击监听事件
    public interface Action1<T1> {
        void invoke(T1 p);
    }

  至此我们的自定义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本身需要配置样式的可选项,(即在界面中允许外部配置的的地方)

 <declare-styleable name="ActionSheet">
        <attr name="actionSheetBackground" format="color|reference" />
        <attr name="cancelButtonBackground" format="color|reference" />
        <attr name="topItemBackground" format="color|reference" />
        <attr name="middleItemBackground" format="color|reference" />
        <attr name="bottomItemBackground" format="color|reference" />
        <attr name="singleItemBackground" format="color|reference" />
        <attr name="cancelButtonTextColor" format="color|reference" />
        <attr name="titleColor" format="color|reference" />
        <attr name="itemTextColor" format="color|reference" />
        <attr name="actionSheetPadding" format="dimension|reference" />
        <attr name="itemSpacing" format="dimension|reference" />
        <attr name="cancelButtonMarginTop" format="dimension|reference" />
        <attr name="actionSheetTextSize" format="dimension|reference" />
    declare-styleable>

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


    <style name="ActionSheetStyleIOS6">
        <item name="actionSheetBackground">#80000000item>
        <item name="cancelButtonBackground">@drawable/as_cancel_bt_bg
        "topItemBackground">@drawable/as_other_bt_bg
        "middleItemBackground">@drawable/as_other_bt_bg
        "bottomItemBackground">@drawable/as_other_bt_bg
        "singleItemBackground">@drawable/as_other_bt_bg
        "cancelButtonTextColor">@android:color/white
        "itemTextColor">@android:color/black
        "titleColor">@android:color/white
        "actionSheetPadding">20dp
        "itemSpacing">5dp
        "cancelButtonMarginTop">20dp
        "actionSheetTextSize">16sp
    style>

    <style name="ActionSheetStyleIOS7">
        <item name="actionSheetBackground">@android:color/transparent
        "cancelButtonBackground">@drawable/slt_as_ios7_cancel_bt
        "topItemBackground">@drawable/slt_as_ios7_other_bt_top
        "middleItemBackground">@drawable/slt_as_ios7_other_bt_middle
        "bottomItemBackground">@drawable/slt_as_ios7_other_bt_bottom
        "singleItemBackground">@drawable/slt_as_ios7_other_bt_single
        "cancelButtonTextColor">#1E82FF
        "itemTextColor">#1E82FF
        "titleColor">@android:color/white
        "actionSheetPadding">10dp
        "itemSpacing">0dp
        "cancelButtonMarginTop">10dp
        "actionSheetTextSize">16sp
    style>

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

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

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

    /**
     * actionsheet各种属性样式
     */
    private static class Attributes {
        Context mContext;
        Drawable background;
        Drawable cancelButtonBackground;
        Drawable topItemBackground;
        Drawable middleItemBackground;
        Drawable bottomItemBackground;
        Drawable singleItemBackground;
        int cancelButtonTextColor;
        int itemTextColor;
        int titleColor;
        int padding;
        int itemSpacing;
        int margin;
        int cancelButtonMarginTop;
        float textSize;

        public Attributes(Context context) {
            mContext = context;
            this.background = new ColorDrawable(Color.TRANSPARENT);
            this.cancelButtonBackground = new ColorDrawable(Color.BLACK);
            ColorDrawable gray = new ColorDrawable(Color.GRAY);
            this.topItemBackground = gray;
            this.middleItemBackground = gray;
            this.bottomItemBackground = gray;
            this.singleItemBackground = gray;
            this.cancelButtonTextColor = Color.WHITE;
            this.titleColor = Color.WHITE;
            this.itemTextColor = Color.BLACK;
            this.padding = dp2px(20);
            this.itemSpacing = dp2px(2);
            this.margin = dp2px(5);
            this.cancelButtonMarginTop = dp2px(10);
            this.textSize = dp2px(16);
        }

        /**
         * dp转像素类
         * @param dp
         * @return
         */
        private int dp2px(int dp){
            return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                    dp, mContext.getResources().getDisplayMetrics());
        }

        /**
         * 获取items按键状态对应背景
         * @return
         */
        public Drawable getMiddleItemBackground() {
            if (middleItemBackground instanceof StateListDrawable) {
                TypedArray a = mContext.getTheme().obtainStyledAttributes(null,
                        R.styleable.ActionSheet, R.attr.actionSheetStyle, 0);
                middleItemBackground = a
                        .getDrawable(R.styleable.ActionSheet_middleItemBackground);
                a.recycle();
            }
            return middleItemBackground;
        }
    }

  2.读取样式并赋值

protected Attributes initAttrs(){
        Attributes attrs = new Attributes(getActivity());
        TypedArray a = getActivity().getTheme().obtainStyledAttributes(null,
                R.styleable.ActionSheet, R.attr.actionSheetStyle, 0);
        Drawable background = a
                .getDrawable(R.styleable.ActionSheet_actionSheetBackground);
        if (background != null) {
            attrs.background = background;
        }
        Drawable cancelButtonBackground = a
                .getDrawable(R.styleable.ActionSheet_cancelButtonBackground);
        if (cancelButtonBackground != null) {
            attrs.cancelButtonBackground = cancelButtonBackground;
        }
        Drawable itemTopBackground = a
                .getDrawable(R.styleable.ActionSheet_topItemBackground);
        if (itemTopBackground != null) {
            attrs.topItemBackground = itemTopBackground;
        }
        Drawable itemMiddleBackground = a
                .getDrawable(R.styleable.ActionSheet_middleItemBackground);
        if (itemMiddleBackground != null) {
            attrs.middleItemBackground = itemMiddleBackground;
        }
        Drawable itemBottomBackground = a
                .getDrawable(R.styleable.ActionSheet_bottomItemBackground);
        if (itemBottomBackground != null) {
            attrs.bottomItemBackground = itemBottomBackground;
        }
        Drawable itemSingleBackground = a
                .getDrawable(R.styleable.ActionSheet_singleItemBackground);
        if (itemSingleBackground != null) {
            attrs.singleItemBackground = itemSingleBackground;
        }
        attrs.titleColor = a.getColor(
                R.styleable.ActionSheet_titleColor,
                attrs.titleColor);
        attrs.cancelButtonTextColor = a.getColor(
                R.styleable.ActionSheet_cancelButtonTextColor,
                attrs.cancelButtonTextColor);
        attrs.itemTextColor = a.getColor(
                R.styleable.ActionSheet_itemTextColor,
                attrs.itemTextColor);
        attrs.padding = (int) a.getDimension(
                R.styleable.ActionSheet_actionSheetPadding, attrs.padding);
        attrs.itemSpacing = (int) a.getDimension(
                R.styleable.ActionSheet_itemSpacing,
                attrs.itemSpacing);
        attrs.cancelButtonMarginTop = (int) a.getDimension(
                R.styleable.ActionSheet_cancelButtonMarginTop,
                attrs.cancelButtonMarginTop);
        attrs.textSize = a.getDimensionPixelSize(R.styleable.ActionSheet_actionSheetTextSize, (int) attrs.textSize);

        a.recycle();
        return attrs;
    }

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

  Fragment全局变量和参数:

public class ActionSheet extends Fragment implements OnClickListener{
    protected WindowManager windowManager;
    protected View mBackgroundView;
    protected ViewGroup decorView;
    protected View parent; 
    protected LinearLayout container; //各个item选项容器
    protected Attributes mAttrs; //读取的样式参数配置

    private ItemClikListener mItemClikListener; //item点击监听事件
    private CancelListener mCancelListener; //取消按键监听事件

    protected boolean isDismissed = true;
    protected boolean isCancel = true;

    /**
    * 几个item的Id
    */
    public static final int BG_VIEW_ID = 100;
    public static final int CANCEL_BUTTON_ID = 101;
    public static final int ITEM_ID = 102;

    //两种样式对应key,便于外部设置
    public static final int ActionSheetThemeIOS6 = R.style.ActionSheetStyleIOS6;
    public static final int ActionSheetThemeIOS7 = R.style.ActionSheetStyleIOS7;

    protected final static long durationMillis = 200; //显示消失动画时长

    //。。。
}

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

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        initalize();
        startSlideInAnim();
        return super.onCreateView(inflater, container, savedInstanceState);
    }

    protected void initalize() {
        tryHideSoftInput(); //尝试隐藏输入法
        mAttrs = initAttrs(); //读取样式
        initViews(); //初始化绑定view
    }


    /**
     * 如果输入法键盘没有隐藏,则隐藏软键盘
     */
    protected void tryHideSoftInput(){
        InputMethodManager imm = (InputMethodManager) getActivity()
                .getSystemService(Context.INPUT_METHOD_SERVICE);
        if (imm.isActive()) {
            View focusView = getActivity().getCurrentFocus();
            if (focusView != null) {
                imm.hideSoftInputFromWindow(focusView.getWindowToken(), 0);
            }
        }
    }

    //读取样式前边已贴。。。

    /**
     * 初始化背景view 和 底部items
     */
    protected void initViews(){
        windowManager = (WindowManager) getActivity().getSystemService(Context.WINDOW_SERVICE);

        FrameLayout parent = new FrameLayout(getActivity());

        /**
        *初始化背景view
        */
        mBackgroundView = new View(getActivity());
        mBackgroundView.setBackgroundColor(0x88000000);
        mBackgroundView.setId(BG_VIEW_ID);
        mBackgroundView.setOnClickListener(this);
        FrameLayout.LayoutParams bgLayoutParams = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT);
        parent.addView(mBackgroundView);

        /**
        *初始化包含item选项的view
        */
        container = new LinearLayout(getActivity());
        FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.WRAP_CONTENT);
        params.gravity = Gravity.BOTTOM;
        container.setLayoutParams(params);
        container.setOrientation(LinearLayout.VERTICAL);
        createItems();
        parent.addView(container);
        this.parent = parent;

        //获取跟试图并添加view
        decorView = (ViewGroup) getActivity().getWindow().getDecorView();
        decorView.addView(parent);
    }

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

    /**
     * 初始化底部items
     */
    protected void createItems(){ 
        //创建title
        String mTitle = getArguments().getString(Builder.ARG_TITLE);
        if(mTitle !=null){
            TextView title = new TextView(getActivity());
            title.getPaint().setFakeBoldText(true);
            title.setTextSize(TypedValue.COMPLEX_UNIT_PX, mAttrs.textSize);
            title.setText(mTitle);
            title.setTextColor(mAttrs.titleColor);
            title.setGravity(Gravity.CENTER);
            LinearLayout.LayoutParams params = createItemLayoutParams();
            int margin = mAttrs.itemSpacing > 0 ? mAttrs.itemSpacing:mAttrs.margin;
            params.setMargins(margin, 0, margin, margin);           
            container.addView(title, params);
        }

        //创建items
        String[] titles = getArguments().getStringArray(Builder.ARG_ITEM_TITLES);
        if (titles != null) {
            for (int i = 0; i < titles.length; i++) {
                Button bt = new Button(getActivity());
                bt.setId(ITEM_ID + i);
                bt.setOnClickListener(this);
                bt.setBackgroundDrawable(getItemBg(titles, i));
                bt.setText(titles[i]);
                bt.setTextColor(mAttrs.itemTextColor);
                bt.setTextSize(TypedValue.COMPLEX_UNIT_PX, mAttrs.textSize);
                if (i > 0) {
                    LinearLayout.LayoutParams params = createItemLayoutParams();
                    params.topMargin = mAttrs.itemSpacing;
                    container.addView(bt, params);
                } else {
                    container.addView(bt);
                }
            }
        }
        //创建cancelbutton
        String cancelText = getArguments().getString(Builder.ARG_CANCEL_TEXT);
        if(cancelText != null){
            Button bt = new Button(getActivity());
            bt.getPaint().setFakeBoldText(true);
            bt.setTextSize(TypedValue.COMPLEX_UNIT_PX, mAttrs.textSize);
            bt.setId(CANCEL_BUTTON_ID);
            bt.setBackgroundDrawable(mAttrs.cancelButtonBackground);
            bt.setText(cancelText);
            bt.setTextColor(mAttrs.cancelButtonTextColor);
            bt.setOnClickListener(this);
            LinearLayout.LayoutParams params = createItemLayoutParams();
            params.topMargin = mAttrs.cancelButtonMarginTop;
            container.addView(bt, params);
        }

        container.setBackgroundDrawable(mAttrs.background);
        container.setPadding(mAttrs.padding, mAttrs.padding, mAttrs.padding,
                mAttrs.padding);
    }

  Fragment展示和消失方法接口

    public void show(FragmentManager manager, String tag) {
        if (!isDismissed) {
            return;
        }
        isDismissed = false;
        FragmentTransaction ft = manager.beginTransaction();
        ft.add(this, tag);
        ft.addToBackStack(null); //允许回退栈
        ft.commit();
    }

    public void dismiss(){
        if (isDismissed) {
            return;
        }
        isDismissed = true;
        getFragmentManager().popBackStack();
        FragmentTransaction ft = getFragmentManager().beginTransaction();
        ft.remove(this);
        ft.commit();
    }

  显示消失对应的动画:

private void startSlideInAnim() {
        container.clearAnimation();
        mBackgroundView.clearAnimation();

        TranslateAnimation translateAnimation = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 1, Animation.RELATIVE_TO_SELF, 0);
        translateAnimation.setDuration(durationMillis);
        AlphaAnimation alphaAnimation = new AlphaAnimation(0, 1);
        alphaAnimation.setDuration(durationMillis);


        container.startAnimation(translateAnimation);
        mBackgroundView.startAnimation(alphaAnimation);
    }

    private void startSlideOutAnim() {
        container.clearAnimation();
        TranslateAnimation translateAnimation = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 1);
        translateAnimation.setDuration(durationMillis);

        mBackgroundView.clearAnimation();
        AlphaAnimation alphaAnimation = new AlphaAnimation(1, 0);
        alphaAnimation.setDuration(durationMillis);
        container.startAnimation(translateAnimation);
        mBackgroundView.startAnimation(alphaAnimation);
    }

  点击事件的处理:

    @Override
    public void onClick(View v) {
        if (v.getId() == BG_VIEW_ID && !getArguments().getBoolean(Builder.ARG_CANCELABLE_ONTOUCHOUTSIDE)) {
            return;
        }
        if (v.getId() != CANCEL_BUTTON_ID && v.getId() != BG_VIEW_ID) {
            if (mItemClikListener != null) {
                mItemClikListener.onitemClick(this, v.getId() - CANCEL_BUTTON_ID
                        - 1);
            }
            isCancel = false;
        }
        dismiss();
    }

  Fragment销毁时处理逻辑:

    @Override
    public void onDestroyView() {
        startSlideOutAnim();
        container.postDelayed(new Runnable() {
            @Override
            public void run() {
                decorView.removeView(parent);
            }
        }, durationMillis);
        if (mCancelListener != null) {
            mCancelListener.onCancelClick(this);
        }
        super.onDestroyView();
    }

  Fragment事件接口定义:
(注: show()区别于display()方法,show只是负责展示,display负责显示/消除的切换)

    protected void setActionSheetListener(ItemClikListener itemClikListener,CancelListener cancelListener){
        this.mItemClikListener = itemClikListener;
        this.mCancelListener = cancelListener;
    }

    public static interface ItemClikListener{
        void onitemClick(ActionSheet actionSheet, int index);
    }

    public static interface CancelListener{
        void onCancelClick(ActionSheet actionSheet);
    }

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

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

    public static Builder createBuilder(Context context,
                                        FragmentManager fragmentManager) {
        return init(context, fragmentManager);
    }

  Builder类以及对外封装接口:


    public static class Builder {
        private String mTag = "ActionSheet";
        private static final String ARG_TITLE = "title";
        private static final String ARG_CANCEL_TEXT = "cancel_text";
        private static final String ARG_ITEM_TITLES = "items_text";
        private static final String ARG_CANCELABLE_ONTOUCHOUTSIDE = "cancelable_ontouchoutside";

        private FragmentManager mFragmentManager;
        private Context mContext;
        private String mTitle;
        private String mCancelButtonText;
        private String[] mItemsText;
        private boolean mCancelableOnTouchOutside = true;
        private ItemClikListener itemClikListener;
        private CancelListener cancelListener;
        private boolean isThemed = false;

        public Builder(Context context, FragmentManager fragmentManager) {
            mContext = context;
            mFragmentManager = fragmentManager;
        }

        public Builder setTitle(String title) {
            mTitle = title;
            return this;
        }

        public Builder setCancelText(String title) {
            mCancelButtonText = title;
            return this;
        }

        public Builder setCancelText(String title,CancelListener cancelListener) {
            mCancelButtonText = title;
            this.cancelListener = cancelListener;
            return this;
        }

        public Builder setItemTexts(String... titles) {
            mItemsText = titles;
            return this;
        }

        public Builder setTheme(int resid){
            isThemed = true;
            mContext.setTheme(resid);
            return this;
        }

        public Builder setTag(String tag) {
            mTag = tag;
            return this;
        }

        public Builder setItemClickListener(ItemClikListener listener) {
            this.itemClikListener = listener;
            return this;
        }

        public Builder setCancelableOnTouchOutside(boolean cancelable) {
            mCancelableOnTouchOutside = cancelable;
            return this;
        }

        public Bundle initArguments() {
            Bundle bundle = new Bundle();
            bundle.putString(ARG_TITLE, mTitle);
            bundle.putString(ARG_CANCEL_TEXT, mCancelButtonText);
            bundle.putStringArray(ARG_ITEM_TITLES, mItemsText);
            bundle.putBoolean(ARG_CANCELABLE_ONTOUCHOUTSIDE,
                    mCancelableOnTouchOutside);
            return bundle;
        }

        public static ActionSheet actionSheet;
        public ActionSheet show() {
            if(!isThemed)
                setTheme(R.style.ActionSheetStyleIOS7);
            if(actionSheet == null) {
                actionSheet = (ActionSheet) Fragment.instantiate(
                        mContext, ActionSheet.class.getName(), initArguments());
                actionSheet.setActionSheetListener(itemClikListener, cancelListener);
            }
            actionSheet.show(mFragmentManager, mTag);
            return actionSheet;
        }

        public ActionSheet display(){
            if(actionSheet == null){
                if(!isThemed)
                    setTheme(R.style.ActionSheetStyleIOS7);
                actionSheet = (ActionSheet) Fragment.instantiate(
                        mContext, ActionSheet.class.getName(), initArguments());
                actionSheet.setActionSheetListener(itemClikListener, cancelListener);
            }
            if(actionSheet.isDismissed){
                actionSheet.show(mFragmentManager, mTag);
            }else{
                actionSheet.dismiss();
            }
            return actionSheet;
        }
    }

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

ActionSheet.init(this)
                .setTitle("This is test title ,do you want do something?")
                .setTheme(resid)
                .setItemTexts("Item1", "Item2", "Item3", "Item4")
                .setItemClickListener(new ItemClikListener() {
                    @Override
                    public void onitemClick(
                            baoyz.qiao.actionsheet.ActionSheet actionSheet,
                            int index) {
                        Toast.makeText(getApplicationContext(), "click item index = " + index,0).show();
                    }
                })
                .setCancelText("Cancel")
//              .setCancelText("Cancel",new CancelListener() {
//                  @Override
//                  public void onCancelClick(ActionSheet actionSheet) {
//                      Toast.makeText(getApplicationContext(), "dismissed ", 0).show();
//                  }
//              })
                .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积累,Android,进阶)