可滑动关闭的对话框(一)
2015-08-02 16:41
211 查看
前言
Uber大家都用过,有时候它的对话框是从顶部落下来,你可以把它推上去关闭,或者把它拽下去关闭。我觉得这种交互方式很好。符合认知,也更加便捷。用在一些非关键信息的展示很合适,比如广告。效果图
原理
并没有去继承Dialog,而是直接将dialog视图通过WindowManager.addView方法添加到窗口中。当然,我在dialog视图外层包了一层FrameLayout用来获取并处理触摸事件,并实现dialog视图的移动。至于获取触摸事件,移动dialog视图就是老生长谈了。值得注意的是,在自己处理触摸事件的时候,要注意不影响别的控件的正常工作。比如,如何中途你要夺取手势的处理权,记得给手势本来的处理者发送cancel事件;如果你中途想把一个手势交给别的控件处理记得给该控件发个down事件,让它可以进行一些必要的初始化。剩下的就是移动dialog过程中一些状态的判断。比如如果移动距离大于touchSlop截获触摸事件,这样不会触发dialog视图上面的各种Listener。在抬手的时候,判断速度决定是否关闭对话框。
Demo
Github小点
注意VelocityTracker的用法[code] @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); velocityTracker = VelocityTracker.obtain(); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); velocityTracker.clear(); } private boolean onDown(MotionEvent ev) { velocityTracker.clear(); velocityTracker.addMovement(ev); ... } private boolean onMove(MotionEvent ev) { velocityTracker.addMovement(ev); ... } private boolean onEnd(MotionEvent ev) { velocityTracker.addMovement(ev); ... }
recycle MotionEvent
[code] private void dispatchCancelEvent(MotionEvent ev) { MotionEvent cancelEvent = MotionEvent.obtain(ev); cancelEvent.setAction(MotionEvent.ACTION_CANCEL); super.dispatchTouchEvent(cancelEvent); cancelEvent.recycle(); }
根据要移动的距离计算移动动画的时长
[code]long duration = Math.min(Math.max((long) (Math.abs(translationYDelta) * 0.7F), 400), 600);
源码
[code]public class SwipeDialogManager{ private Context context; private WindowManager windowManager; private EmbedDialogFrameLayout dialogContainer; public SwipeDialogManager(Context context) { this.context = context; this.windowManager = (WindowManager) context.getSystemService("window"); dialogContainer = new EmbedDialogFrameLayout(context); } /** * deprecated * @param layout */ public void addDialogView(int layout) { dialogContainer = new EmbedDialogFrameLayout(context); dialogContainer.addDialogView(layout); dialogContainer.setRemoveDialogListener(new RemoveDialogListener(){ @Override public void removeDialog() { windowManager.removeView(dialogContainer); } }); addContainerToWindowManager(); } public void addDialogView(View view) { addViewToContainer(view); addContainerToWindowManager(); } private void addViewToContainer(View view) { dialogContainer.addDialogView(view); dialogContainer.setRemoveDialogListener(new RemoveDialogListener() { @Override public void removeDialog() { windowManager.removeView(dialogContainer); } }); } private void addContainerToWindowManager() { WindowManager.LayoutParams windowParams = new WindowManager.LayoutParams(); windowParams.x = 0; windowParams.y = 0; windowParams.width = WindowManager.LayoutParams.MATCH_PARENT; windowParams.height = WindowManager.LayoutParams.MATCH_PARENT; windowParams.flags = WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; windowParams.format = PixelFormat.TRANSLUCENT; windowManager.addView(dialogContainer, windowParams); } public void show(){ dialogContainer.show(); } public interface RemoveDialogListener { void removeDialog(); }
[code]public class EmbedDialogFrameLayout extends FrameLayout { private static final float DIM_RATIO = 0.8F; private float currentDim; private View dialogView; private float downY; private int translationYTopBoundary; private int translationYBottomBoundary; private boolean animating; private SwipeDialogManager.RemoveDialogListener removeDialogListener; private int minFlingVelocity; private int touchSlop; private boolean intercept; private VelocityTracker velocityTracker; private int maxFlingVelocity; private int customFlingVelocityThrehold; private int translationYMax; public EmbedDialogFrameLayout(Context context) { super(context); init(); } public EmbedDialogFrameLayout(Context context, AttributeSet attrs) { super(context, attrs); init(); } public EmbedDialogFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { ViewConfiguration viewConfiguration = ViewConfiguration.get(getContext()); minFlingVelocity = viewConfiguration.getScaledMinimumFlingVelocity(); maxFlingVelocity = viewConfiguration.getScaledMaximumFlingVelocity(); customFlingVelocityThrehold = (minFlingVelocity + maxFlingVelocity) / 5; touchSlop = viewConfiguration.getScaledTouchSlop(); } public void addDialogView(int layout) { View dialogView = LayoutInflater.from(getContext()).inflate(layout, this, false); dialogView.findViewById(R.id.girl).setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { Toast.makeText(getContext(), "beautiful girl", Toast.LENGTH_LONG).show(); } }); dialogView.findViewById(R.id.button).setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { Toast.makeText(getContext(), "button clicked", Toast.LENGTH_LONG).show(); } }); addDialogView(dialogView); } public void addDialogView(View dialogView){ this.dialogView = dialogView; dialogView.setVisibility(INVISIBLE); FrameLayout.LayoutParams params = (LayoutParams) dialogView.getLayoutParams(); params.gravity = Gravity.CENTER; addView(dialogView, params); } public void show() { getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { translationYTopBoundary = -(dialogView.getTop() + dialogView.getBottom()) / 2; translationYBottomBoundary = -translationYTopBoundary; translationYMax = dialogView.getBottom(); fallIn(); getViewTreeObserver().removeOnPreDrawListener(this); return true; } }); invalidate(); } private void fallIn() { dialogView.setTranslationY(-dialogView.getBottom()); dialogView.setVisibility(VISIBLE); animate(0, DIM_RATIO, false); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); velocityTracker = VelocityTracker.obtain(); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); velocityTracker.clear(); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { if (animating) { return false; } switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: return onDown(ev); case MotionEvent.ACTION_MOVE: return onMove(ev); case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: return onEnd(ev); } return super.dispatchTouchEvent(ev); } private boolean onDown(MotionEvent ev) { velocityTracker.clear(); velocityTracker.addMovement(ev); downY = ev.getY(); super.dispatchTouchEvent(ev); return true; } private boolean onMove(MotionEvent ev) { velocityTracker.addMovement(ev); if (downY == -1) { downY = ev.getY(); } float dy = ev.getY() - downY; dialogView.setTranslationY(dy); float newDim = DIM_RATIO * (1 - Math.min(1, Math.abs(dy / translationYMax))); setDim(newDim); if (intercept) { return true; } if (Math.abs(dy) > touchSlop) { dispatchCancelEvent(ev); intercept = true; return true; } return super.dispatchTouchEvent(ev); } private void dispatchCancelEvent(MotionEvent ev) { MotionEvent cancelEvent = MotionEvent.obtain(ev); cancelEvent.setAction(MotionEvent.ACTION_CANCEL); super.dispatchTouchEvent(cancelEvent); cancelEvent.recycle(); } private boolean onEnd(MotionEvent ev) { velocityTracker.addMovement(ev); velocityTracker.computeCurrentVelocity(1000); float velocityY = velocityTracker.getYVelocity(); if (Math.abs(velocityY) < customFlingVelocityThrehold) { if (dialogView.getTranslationY() < translationYTopBoundary) {//[-infinite, -dialogTop) riseOut(); } else if (dialogView.getTranslationY() < translationYBottomBoundary) {//[-dialog, bottom) recover(); } else { fallOut(); } } else { if (intercept) { if (velocityY < 0) { riseOut(); } else { fallOut(); } } } return super.dispatchTouchEvent(ev); } private void recover() { animate(0, DIM_RATIO, false); } private void riseOut() { animate(-dialogView.getBottom(), 0, true); } private void fallOut() { animate(getHeight() - dialogView.getTop(), 0, true); } private void animate(float endTranslationY, float endDim, final boolean dismiss) { float translationYDelta = endTranslationY - dialogView.getTranslationY(); long duration = Math.min(Math.max((long) (Math.abs(translationYDelta) * 0.7F), 400), 600); final float startDim = currentDim; final float dimDelta = endDim - startDim; ValueAnimator animator = ValueAnimator.ofFloat(dialogView.getTranslationY(), endTranslationY); animator.setDuration(duration); animator.setInterpolator(new DecelerateInterpolator(1.6F)); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { dialogView.setTranslationY((Float) valueAnimator.getAnimatedValue()); setDim(startDim + dimDelta * valueAnimator.getAnimatedFraction()); } }); animator.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animator) { animating = true; } @Override public void onAnimationEnd(Animator animator) { animating = false; downY = -1; intercept = false; if (dismiss) { if (removeDialogListener != null) { removeDialogListener.removeDialog(); } if (onDismissListener != null) { onDismissListener.onDismiss(null); } } } @Override public void onAnimationCancel(Animator animator) { } @Override public void onAnimationRepeat(Animator animator) { } }); animator.start(); } private void setDim(float dim) { setBackgroundColor(Color.argb((int) (255 * dim), 0, 0, 0)); currentDim = dim; } private Dialog.OnDismissListener onDismissListener; public void setOnDismissListener(Dialog.OnDismissListener onDismissListener) { this.onDismissListener = onDismissListener; } public void setRemoveDialogListener(SwipeDialogManager.RemoveDialogListener removeDialogListener) { this.removeDialogListener = removeDialogListener; } @Override public boolean dispatchKeyEvent(KeyEvent event) { switch (event.getKeyCode()) { case KeyEvent.KEYCODE_BACK: if (!animating){ riseOut(); } break; case KeyEvent.KEYCODE_MENU: break; default: break; } return super.dispatchKeyEvent(event); } }
相关文章推荐
- java学习总结及心得体会
- JQuery隐藏,显示div的方法
- 搜索引擎整体结构图以及描述
- 华为是怎样研发的(12)——FMEA分析
- LightOJ_1422_HalloweenCostumes
- ArcGIS for Service中JavaScript预览在内网环境无法使用
- 九度OJ 题目1012:畅通工程 (并查集,连通子图)
- 客户管理系统案例总结
- 高清屏的背景图片适配
- C++ 常见面试题目 (二)
- cassandra counter 类型使用注意事项
- HDU 2255 — 奔小康赚大钱 KM入门题
- 服务层的必要性
- TCP建立连接和释放的过程,及TCP状态变迁图
- 第一篇博文:PHP函数原型中的可选参数写法为什么这么写?
- 用python加cPAMIE加pyinstaller为我柱哥点赞
- 【leetcode】Implement Queue using Stacks
- leetcode 抢房子House Robber
- C语言编程入门——指针(上)
- Excel数据导入导出