## 《代码里的世界》 —UI篇
用文字札记描绘自己 android学习之路
转载请保留出处 by Qiao
http://blog.youkuaiyun.com/qiaoidea/article/details/46417747
【导航】
- 弹出式对话框各种方案 从仿QQ消息提示框来谈弹出式对话框的实现方式 (Dialog,PopupWind,自定义View,Activity,FragmentDialog)
- Dialog源码解析 从源码上看Dialog与DialogFragment
- 仿IOS ActionSheet 两种方式实现ActionSheet底部弹出菜单效果
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全局变量
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<Integer>(){
@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);
setFoc