ViewDragHelper

来源:互联网 发布:wordpress 程序员主题 编辑:程序博客网 时间:2024/05/16 00:30

一、基本介绍

前辈们已经总结的很多了,所以从别人的博客里直接复制的比较多,用到的源码我也会经过修改。用到的博客我会在下方注释。

2013年谷歌i/o大会上介绍了两个新的layout: SlidingPaneLayout和DrawerLayout,现在这俩个类被广泛的运用,其实研究他们的源码你会发现这两个类都运用了ViewDragHelper来处理拖动。ViewDragHelper是framework中不为人知却非常有用的一个工具

ViewDragHelper解决了Android中手势处理过于复杂的问题,在DrawerLayout出现之前,侧滑菜单都是由第三方开源代码实现的,其中著名的当属MenuDrawer(现在用android.support.v4.widget.DrawerLayout)。

其实ViewDragHelper并不是第一个用于分析手势处理的类,gesturedetector也是,但是在和拖动相关的手势分析方面gesturedetector只能说是勉为其难。

关于ViewDragHelper有如下几点:

   1、ViewDragHelper.Callback是连接ViewDragHelper与view之间的桥梁(这个view一般是指拥子view的容器即parentView);

   2、ViewDragHelper的实例是通过静态工厂方法创建的;

   3、你能够指定拖动的方向;

   4、ViewDragHelper可以检测到是否触及到边缘;

   5、ViewDragHelper并不是直接作用于要被拖动的View,而是使其控制的视图容器中的子View可以被拖动,如果要指定某个子view的行为,需要在Callback中想办法;

   6、ViewDragHelper的本质其实是分析onInterceptTouchEventonTouchEvent的MotionEvent参数,然后根据分析的结果去改变一个容器中被拖动子View的位 通过offsetTopAndBottom(int offset)和offsetLeftAndRight(int offset)方法 ),他能在触摸的时候判断当前拖动的是哪个子View;

   7、虽然ViewDragHelper的实例方法 ViewDragHelper create(ViewGroup forParent, Callback cb) 可以指定一个被ViewDragHelper处理拖动事件的对象 ,但ViewDragHelper类的设计决定了其适用于被包含在一个自定义ViewGroup之中,而不是对任意一个布局上的视图容器使用ViewDragHelper

注意:ViewDragHelper是作用在一个ViewGroup上,也就是说他不能直接作用到被拖拽的view, 其实这也很好理解,因为view在布局中的位置是父ViewGroup决定的。

如何使用ViewGroup实现一个可以拖动的view?

1、获取ViewDragHelper的实例,注意,这里不能直接new,而是使用ViewDragHelper的一个静态方法:

ViewDragHelper.create(ViewGroup forParent, float sensitivity, ViewDragHelper.Callback cb);

参数1: 一个ViewGroup, 也就是ViewDragHelper将要用来拖拽谁下面的子view

参数2:灵敏度,一般设置为1.0f就行

参数3:一个回调,用来处理拖动到位置

2、在Callback对象里面,下面几个方法非常重要。

 (1) public int clampViewPositionHorizontal(View child, int left, int dx)

          这个是返回被横向移动的子控件child的左坐标left,和移动距离dx,我们可以根据这些值来返回child的新的left。

          返回值该child现在的位置,  这个方法必须重写,要不然就不能移动了。

(2)public int clampViewPositionVertical(View child, int top, int dy) 

         这个和上面的方法一个意思,就是换成了垂直方向的移动和top坐标。

         如果有垂直移动,这个也必须重写,要不默认返回0,也不能移动了。

(3)public abstract boolean tryCaptureView(View child, int pointerId) 

表示尝试捕获子view,这里一定要返回true, 返回true表示允许。           

这个方法用来返回可以被移动的View对象,我们可以通过判断child与我们想移动的View是的相等来控制谁能移动。

(4)public int getViewVerticalDragRange(View child)

          这个用来控制垂直移动的边界范围,单位是像素。

(5)public int getViewHorizontalDragRange(View child) 

          和上面一样,就是是横向的边界范围。

(6)public void onViewReleased(View releasedChild, float xvel, float yvel) 

           当releasedChild被释放的时候,xvel和yvel是x和y方向的加速度

(7)public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) 

这个是当changedView的位置发生变化时调用,我们可以在这里面控制View的显示位置和移动。

    我们前面虽然获取了ViewDragHelper的对象,但是现在我们还是不能接收到事件的,我们需要在onTouch()和onInterceptTouchEvent()里面,将触摸事件传入到ViewDragHelper里面,才能进行处理,就像下面这样,注意需要在clampViewPositionHorizontal和clampViewPositionHorizontal中做一点工作,以防止view超出了边界。

 

[java] view plain copy
  1. @Override    
  2. public boolean onInterceptTouchEvent(MotionEvent ev) {    
  3.     
  4.     …    
  5.     
  6.     return mDragHelper.shouldInterceptTouchEvent(ev);    
  7. }   

 

[java] view plain copy
  1. public boolean onTouchEvent(MotionEvent event) {    
  2.         
  3.     …    
  4.     mDragHelper.processTouchEvent(event);    
  5.     return true;    
  6. }  


   传递给ViewDragHelper之后,我们就可以在Callback的各个事件里面进行处理了。


二、实战1


例1:

[java] view plain copy
  1. public class CustomView extends LinearLayout {  
  2.     private ViewDragHelper mDragHelper;  
  3.   
  4.     public CustomView(Context context) {  
  5.         super(context);  
  6.         init();  
  7.     }  
  8.   
  9.     public CustomView(Context context, AttributeSet attrs) {  
  10.         super(context, attrs);  
  11.         init();  
  12.     }  
  13.   
  14.     public CustomView(Context context, AttributeSet attrs, int defStyle) {  
  15.         super(context, attrs, defStyle);  
  16.         init();  
  17.     }  
  18.   
  19.     private void init() {  
  20.         /** 
  21.          * @params ViewGroup forParent 必须是一个ViewGroup 
  22.          * @params float sensitivity 灵敏度 
  23.          * @params Callback cb 回调 
  24.          */  
  25.         mDragHelper = ViewDragHelper.create(this1.0f, new ViewDragCallback());  
  26.     }  
  27.       
  28.     private class ViewDragCallback extends ViewDragHelper.Callback {  
  29.         /** 
  30.          * 尝试捕获子view,一定要返回true 
  31.          * @param View child 尝试捕获的view 
  32.          * @param int pointerId 指示器id?  
  33.          * 这里可以决定哪个子view可以拖动 
  34.          */  
  35.         @Override  
  36.         public boolean tryCaptureView(View view, int pointerId) {  
  37. //          return mCanDragView == view;  
  38.             return true;  
  39.         }  
  40.           
  41.         /** 
  42.          * 处理水平方向上的拖动 
  43.          * @param View child 被拖动到view 
  44.          * @param int left 移动到达的x轴的距离 
  45.          * @param int dx 建议的移动的x距离 
  46.          */  
  47.         @Override  
  48.         public int clampViewPositionHorizontal(View child, int left, int dx) {  
  49.             System.out.println("left = " + left + ", dx = " + dx);  
  50.               
  51.             // 两个if主要是为了让viewViewGroup里  
  52.             if(getPaddingLeft() > left) {  
  53.                 return getPaddingLeft();  
  54.             }  
  55.               
  56.             if(getWidth() - child.getWidth() < left) {  
  57.                 return getWidth() - child.getWidth();  
  58.             }  
  59.               
  60.             return left;  
  61.         }  
  62.           
  63.         /** 
  64.          *  处理竖直方向上的拖动 
  65.          * @param View child 被拖动到view 
  66.          * @param int top 移动到达的y轴的距离 
  67.          * @param int dy 建议的移动的y距离 
  68.          */  
  69.         @Override  
  70.         public int clampViewPositionVertical(View child, int top, int dy) {  
  71.             // 两个if主要是为了让viewViewGroup里  
  72.             if(getPaddingTop() > top) {  
  73.                 return getPaddingTop();  
  74.             }  
  75.               
  76.             if(getHeight() - child.getHeight() < top) {  
  77.                 return getHeight() - child.getHeight();  
  78.             }  
  79.               
  80.             return top;  
  81.         }  
  82.           
  83.         /** 
  84.          * 当拖拽到状态改变时回调 
  85.          * @params 新的状态 
  86.          */  
  87.         @Override  
  88.         public void onViewDragStateChanged(int state) {  
  89.             switch (state) {  
  90.             case ViewDragHelper.STATE_DRAGGING:  // 正在被拖动  
  91.                 break;  
  92.             case ViewDragHelper.STATE_IDLE:  // view没有被拖拽或者 正在进行fling/snap  
  93.                 break;  
  94.             case ViewDragHelper.STATE_SETTLING: // fling完毕后被放置到一个位置  
  95.                 break;  
  96.             }  
  97.             super.onViewDragStateChanged(state);  
  98.         }  
  99.     }  
  100.       
  101.     @Override  
  102.     public boolean onInterceptTouchEvent(MotionEvent ev) {  
  103.         switch (ev.getAction()) {  
  104.         case MotionEvent.ACTION_CANCEL:  
  105.         case MotionEvent.ACTION_DOWN:  
  106.             mDragHelper.cancel(); // 相当于调用 processTouchEvent收到ACTION_CANCEL  
  107.             break;  
  108.         }  
  109.           
  110.         /** 
  111.          * 检查是否可以拦截touch事件 
  112.          * 如果onInterceptTouchEvent可以return true 则这里return true 
  113.          */  
  114.         return mDragHelper.shouldInterceptTouchEvent(ev);  
  115.     }  
  116.       
  117.     @Override  
  118.     public boolean onTouchEvent(MotionEvent event) {  
  119.         /** 
  120.          * 处理拦截到的事件 
  121.          * 这个方法会在返回前分发事件 
  122.          */  
  123.         mDragHelper.processTouchEvent(event);  
  124.         return true;  
  125.     }  
  126. }  

layout:

[java] view plain copy
  1. <span style="color:#999999;"><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     android:paddingBottom="@dimen/activity_vertical_margin"  
  6.     android:paddingLeft="@dimen/activity_horizontal_margin"  
  7.     android:paddingRight="@dimen/activity_horizontal_margin"  
  8.     android:paddingTop="@dimen/activity_vertical_margin"  
  9.     tools:context=".MainActivity" >  
  10.   
  11.     <com.bluemor.reddotface.view.CustomView  
  12.         android:layout_width="match_parent"  
  13.         android:layout_height="match_parent">  
  14.         <View  
  15.             android:layout_width="100dp"  
  16.             android:layout_height="100dp"  
  17.             android:background="#FFFF0000" />  
  18.     </com.bluemor.reddotface.view.CustomView>  
  19.   
  20. </RelativeLayout></span>  


三、实战2

 

DragLayout.java

[java] view plain copy
  1. public class DragLayout extends FrameLayout {  
  2.     private Context context;  
  3.     private GestureDetectorCompat gestureDetector;  
  4.     private ViewDragHelper dragHelper;  
  5.     private DragListener dragListener;  
  6.   
  7.     /** 水平可以滚动的范围 */  
  8.     private int horizontalRange;  
  9.     /** 垂直可以滚动的范围 */  
  10.     private int verticalRange;  
  11.     /** 默认滚动式水平的 */  
  12.     private Orientation orientation = Orientation.Horizontal;  
  13.   
  14.     private int viewWidth;  
  15.     private int viewHeight;  
  16.     private int distanceLeft;  
  17.     private int distanceTop;  
  18.   
  19.     private ViewGroup layoutMenu;  
  20.     private ViewGroup layoutContent;  
  21.   
  22.     private Status status = Status.Close;  
  23.   
  24.     public DragLayout(Context context) {  
  25.         this(context, null);  
  26.     }  
  27.   
  28.     public DragLayout(Context context, AttributeSet attrs) {  
  29.         this(context, attrs, 0);  
  30.         this.context = context;  
  31.     }  
  32.   
  33.     public DragLayout(Context context, AttributeSet attrs, int defStyle) {  
  34.         super(context, attrs, defStyle);  
  35.         gestureDetector = new GestureDetectorCompat(context,  
  36.                 new XYScrollDetector());  
  37.         dragHelper = ViewDragHelper.create(this, dragHelperCallback);  
  38.     }  
  39.   
  40.     class XYScrollDetector extends SimpleOnGestureListener {  
  41.         @Override  
  42.         public boolean onScroll(MotionEvent e1, MotionEvent e2, float dx,  
  43.                 float dy) {  
  44.             if (orientation == Orientation.Vertical) {  
  45.                 return Math.abs(dy) >= Math.abs(dx);  
  46.             }  
  47.             return Math.abs(dy) <= Math.abs(dx);  
  48.         }  
  49.     }  
  50.   
  51.     private ViewDragHelper.Callback dragHelperCallback = new ViewDragHelper.Callback() {  
  52.   
  53.         // 这个是返回被横向移动的子控件child的左坐标left,和移动距离dx,我们可以根据这些值来返回child的新的left。  
  54.         // 这个方法必须重写,要不然就不能移动了。  
  55.         // 返回横向坐标左右边界值  
  56.         @Override  
  57.         public int clampViewPositionHorizontal(View child, int left, int dx) {  
  58.   
  59.             if (orientation == Orientation.Vertical)  
  60.                 return 0;  
  61.             if (distanceLeft + dx < 0) {  
  62.                 // 右边界  
  63.                 return 0;  
  64.             } else if (distanceLeft + dx > horizontalRange) {  
  65.                 // 左边界  
  66.                 return horizontalRange;  
  67.             } else {  
  68.                 // 左右边界范围内  
  69.                 return left;  
  70.             }  
  71.         }  
  72.   
  73.         // 这个方法用来返回可以被移动的View对象,我们可以通过判断child与我们想移动的View是的相等来控制谁能移动。  
  74.         @Override  
  75.         public boolean tryCaptureView(View child, int pointerId) {  
  76.             return true;  
  77.         }  
  78.   
  79.         // 横向的边界范围  
  80.         @Override  
  81.         public int getViewHorizontalDragRange(View child) {  
  82.             return horizontalRange;  
  83.         }  
  84.   
  85.         public int clampViewPositionVertical(View child, int top, int dy) {  
  86.   
  87.             if (orientation == Orientation.Horizontal)  
  88.                 return 0;  
  89.   
  90.             if (distanceTop + dy < 0) {  
  91.                 return 0;  
  92.             } else if (distanceTop + dy > verticalRange) {  
  93.                 return verticalRange;  
  94.             } else {  
  95.                 return top;  
  96.             }  
  97.         }  
  98.   
  99.         public int getViewVerticalDragRange(View child) {  
  100.   
  101.             return verticalRange;  
  102.   
  103.         };  
  104.   
  105.         // ACTION_UP事件后调用其方法  
  106.         // 当releasedChild被释放的时候,xvel和yvel是x和y方向的加速度  
  107.         @Override  
  108.         public void onViewReleased(View releasedChild, float xvel, float yvel) {  
  109.             super.onViewReleased(releasedChild, xvel, yvel);  
  110.             if (orientation == Orientation.Vertical) {  
  111.                 if (releasedChild == layoutMenu)  
  112.                     return;  
  113.                 if (yvel > 0) {  
  114.                     // 加速度向下  
  115.                     open();  
  116.                 } else if (yvel < 0) {  
  117.                     // 加速度向上  
  118.                     close();  
  119.                 } else if (releasedChild == layoutContent  
  120.                         && distanceTop > verticalRange * 0.3) {  
  121.                     // 如果释放时,手指在内容区且内容区离左边的距离是range * 0.3  
  122.                     open();  
  123.                 } else {  
  124.                     close();  
  125.                 }  
  126.   
  127.             } else {  
  128.                 if (xvel > 0) {  
  129.                     // 加速度向  
  130.                     open();  
  131.                 } else if (xvel < 0) {  
  132.                     // 加速度向左  
  133.                     close();  
  134.                 } else if (releasedChild == layoutContent  
  135.                         && distanceLeft > horizontalRange * 0.3) {  
  136.                     // 如果释放时,手指在内容区且内容区离左边的距离是range * 0.3  
  137.                     open();  
  138.                 } else if (releasedChild == layoutMenu  
  139.                         && distanceLeft > horizontalRange * 0.7) {  
  140.                     // 如果释放时,手指在菜单区且内容区离左边的距离是range * 0.7  
  141.                     open();  
  142.                 } else {  
  143.                     close();  
  144.                 }  
  145.             }  
  146.   
  147.         }  
  148.   
  149.         // view在拖动过程坐标发生变化时会调用此方法,包括两个时间段:手动拖动和自动滚动  
  150.         @Override  
  151.         public void onViewPositionChanged(View changedView, int left, int top,  
  152.                 int dx, int dy) {  
  153.   
  154.             if (orientation == Orientation.Horizontal) {  
  155.                 if (changedView == layoutContent) {  
  156.                     distanceLeft = left;  
  157.                 } else {  
  158.                     distanceLeft = distanceLeft + left;  
  159.                 }  
  160.                 if (distanceLeft < 0) {  
  161.                     distanceLeft = 0;  
  162.                 } else if (distanceLeft > horizontalRange) {  
  163.                     distanceLeft = horizontalRange;  
  164.                 }  
  165.                 layoutMenu.layout(00, viewWidth, viewHeight);  
  166.                 layoutContent.layout(distanceLeft, 0, distanceLeft + viewWidth,  
  167.                         viewHeight);  
  168.                 dispatchDragEvent(distanceLeft);  
  169.             } else {  
  170.                 distanceTop = top;  
  171.                 if (distanceTop < 0) {  
  172.                     distanceTop = 0;  
  173.                 } else if (distanceTop > verticalRange) {  
  174.                     distanceTop = verticalRange;  
  175.                 }  
  176.                 layoutMenu.layout(00, viewWidth, viewHeight);  
  177.                 layoutContent.layout(0, distanceTop, viewWidth, distanceTop  
  178.                         + viewHeight);  
  179.                 dispatchDragEvent(distanceTop);  
  180.             }  
  181.         }  
  182.     };  
  183.   
  184.     public interface DragListener {  
  185.         /** 已经打开 */  
  186.         public void onOpen();  
  187.   
  188.         /** 已经关闭 */  
  189.         public void onClose();  
  190.   
  191.         /** 真在拖拽 */  
  192.         public void onDrag(float percent);  
  193.     }  
  194.   
  195.     public void setDragListener(DragListener dragListener) {  
  196.         this.dragListener = dragListener;  
  197.     }  
  198.   
  199.     @Override  
  200.     protected void onFinishInflate() {  
  201.         super.onFinishInflate();  
  202.         layoutMenu = (ViewGroup) getChildAt(0);  
  203.         layoutContent = (ViewGroup) getChildAt(1);  
  204.         layoutMenu.setClickable(true);  
  205.         layoutContent.setClickable(true);  
  206.     }  
  207.   
  208.     @Override  
  209.     protected void onSizeChanged(int w, int h, int oldw, int oldh) {  
  210.         super.onSizeChanged(w, h, oldw, oldh);  
  211.         viewWidth = layoutMenu.getMeasuredWidth();  
  212.         viewHeight = layoutMenu.getMeasuredHeight();  
  213.         horizontalRange = (int) (viewWidth * 0.7);  
  214.         verticalRange = (int) (viewHeight * 0.9);  
  215.     }  
  216.   
  217.     @Override  
  218.     protected void onLayout(boolean changed, int left, int top, int right,  
  219.             int bottom) {  
  220.         layoutMenu.layout(00, viewWidth, viewHeight);  
  221.         if (orientation == Orientation.Horizontal) {  
  222.             layoutContent.layout(distanceLeft, 0, distanceLeft + viewWidth,  
  223.                     viewHeight);  
  224.         } else {  
  225.             layoutContent.layout(0, distanceTop, viewWidth, distanceTop  
  226.                     + viewHeight);  
  227.         }  
  228.   
  229.     }  
  230.   
  231.     @Override  
  232.     public boolean onInterceptTouchEvent(MotionEvent ev) {  
  233.   
  234.         if (orientation == Orientation.Vertical) {  
  235.             if ((Status.Open == getStatus() && ev.getY() < verticalRange)) {  
  236.                 return false;  
  237.             }  
  238.         }  
  239.         return dragHelper.shouldInterceptTouchEvent(ev)  
  240.                 && gestureDetector.onTouchEvent(ev);  
  241.   
  242.     }  
  243.   
  244.     @Override  
  245.     public boolean onTouchEvent(MotionEvent e) {  
  246.         try {  
  247.   
  248.             // 在processTouchEvent中对ACTION_DOWN、ACTION_MOVE和ACTION_UP事件进行了处理:  
  249.             // 1.在ACTION_DOWN中调用回调接口中的tryCaptureView方法,看当前touch的view是否允许拖动  
  250.             // 在此项目中的是直接return true,两个view都是允许拖动的  
  251.             // 2.在ACTION_MOVE中,view的坐标发生改变,调用回调接口中的onViewPositionChanged方法,  
  252.             // 根据坐标信息对view进行layout,通过ViewHelper这个类中的setScaleX、setScaleY方法,实现在  
  253.             // 拖动的过程中view在XY坐标上进行相应比例的缩放;  
  254.             // 3.在ACTION_UP后调用回调接口中的onViewReleased方法,此方法中一个重要的任务是在ACTION_UP事件  
  255.             dragHelper.processTouchEvent(e);  
  256.         } catch (Exception ex) {  
  257.             ex.printStackTrace();  
  258.         }  
  259.         return false;  
  260.     }  
  261.   
  262.     private void dispatchDragEvent(int mainLeft) {  
  263.         float percent;  
  264.         if (orientation == Orientation.Horizontal) {  
  265.             percent = mainLeft / (float) horizontalRange;  
  266.             animateView(percent);  
  267.         } else {  
  268.             percent = mainLeft / (float) verticalRange;  
  269.         }  
  270.   
  271.         Status lastStatus = status;  
  272.         if (dragListener == null)  
  273.             return;  
  274.         dragListener.onDrag(percent);  
  275.         if (lastStatus != getStatus() && status == Status.Close) {  
  276.             dragListener.onClose();  
  277.         } else if (lastStatus != getStatus() && status == Status.Open) {  
  278.             dragListener.onOpen();  
  279.         }  
  280.     }  
  281.   
  282.     private void animateView(float percent) {  
  283.         float f1 = 1 - percent * 0.3f;  
  284.   
  285.         ViewHelper.setScaleX(layoutContent, f1);  
  286.         ViewHelper.setScaleY(layoutContent, f1);  
  287.   
  288.         ViewHelper.setTranslationX(  
  289.                 layoutMenu,  
  290.                 -layoutMenu.getWidth() / 2.3f  
  291.                         + layoutMenu.getWidth() / 2.3f * percent);  
  292.         ViewHelper.setScaleX(layoutMenu, 0.5f + 0.5f * percent);  
  293.         ViewHelper.setScaleY(layoutMenu, 0.5f + 0.5f * percent);  
  294.         ViewHelper.setAlpha(layoutMenu, percent);  
  295.   
  296.         getBackground().setColorFilter(  
  297.                 evaluate(percent, Color.BLACK, Color.TRANSPARENT),  
  298.                 Mode.SRC_OVER);  
  299.     }  
  300.   
  301.     private Integer evaluate(float fraction, Object startValue, Integer endValue) {  
  302.         int startInt = (Integer) startValue;  
  303.         int startA = (startInt >> 24) & 0xff;  
  304.         int startR = (startInt >> 16) & 0xff;  
  305.         int startG = (startInt >> 8) & 0xff;  
  306.         int startB = startInt & 0xff;  
  307.         int endInt = (Integer) endValue;  
  308.         int endA = (endInt >> 24) & 0xff;  
  309.         int endR = (endInt >> 16) & 0xff;  
  310.         int endG = (endInt >> 8) & 0xff;  
  311.         int endB = endInt & 0xff;  
  312.         return (int) ((startA + (int) (fraction * (endA - startA))) << 24)  
  313.                 | (int) ((startR + (int) (fraction * (endR - startR))) << 16)  
  314.                 | (int) ((startG + (int) (fraction * (endG - startG))) << 8)  
  315.                 | (int) ((startB + (int) (fraction * (endB - startB))));  
  316.     }  
  317.   
  318.     @Override  
  319.     public void computeScroll() {  
  320.         if (dragHelper.continueSettling(true)) {  
  321.             ViewCompat.postInvalidateOnAnimation(this);  
  322.         }  
  323.     }  
  324.   
  325.     public enum Status {  
  326.         Drag, Open, Close  
  327.     }  
  328.   
  329.     public enum Orientation {  
  330.         Horizontal, Vertical;  
  331.     }  
  332.   
  333.     public Status getStatus() {  
  334.         if (orientation == Orientation.Horizontal) {  
  335.             if (distanceLeft == 0) {  
  336.                 status = Status.Close;  
  337.             } else if (distanceLeft == horizontalRange) {  
  338.                 status = Status.Open;  
  339.             } else {  
  340.                 status = Status.Drag;  
  341.             }  
  342.         } else {  
  343.             if (distanceTop == 0) {  
  344.                 status = Status.Close;  
  345.             } else if (distanceTop == verticalRange) {  
  346.                 status = Status.Open;  
  347.             } else {  
  348.                 status = Status.Drag;  
  349.             }  
  350.         }  
  351.   
  352.         return status;  
  353.     }  
  354.   
  355.     public ViewGroup getlayoutContent() {  
  356.         return layoutContent;  
  357.     }  
  358.   
  359.     public ViewGroup getlayoutMenu() {  
  360.         return layoutMenu;  
  361.     }  
  362.   
  363.     public void setOrientation(Orientation orientation) {  
  364.         this.orientation = orientation;  
  365.     }  
  366.   
  367.     public void open() {  
  368.         open(true);  
  369.     }  
  370.   
  371.     public void open(boolean animate) {  
  372.         if (animate) {  
  373.             if (orientation == Orientation.Horizontal) {  
  374.                 if (dragHelper.smoothSlideViewTo(layoutContent,  
  375.                         horizontalRange, 0)) {  
  376.                     ViewCompat.postInvalidateOnAnimation(this);  
  377.                 }  
  378.             } else {  
  379.                 if (dragHelper.smoothSlideViewTo(layoutContent, 0,  
  380.                         verticalRange)) {  
  381.                     ViewCompat.postInvalidateOnAnimation(this);  
  382.                 }  
  383.             }  
  384.   
  385.         } else {  
  386.             layoutContent.layout(horizontalRange, 0, horizontalRange  
  387.                     + viewWidth, viewHeight);  
  388.             dispatchDragEvent(horizontalRange);  
  389.         }  
  390.     }  
  391.   
  392.     public void close() {  
  393.         close(true);  
  394.     }  
  395.   
  396.     public void close(boolean animate) {  
  397.         if (animate) {  
  398.   
  399.             if (dragHelper.smoothSlideViewTo(layoutContent, 00)) {  
  400.                 ViewCompat.postInvalidateOnAnimation(this);  
  401.             }  
  402.   
  403.         } else {  
  404.             layoutContent.layout(00, viewWidth, viewHeight);  
  405.             dispatchDragEvent(0);  
  406.         }  
  407.     }  
  408.   
  409. }  
layout.xml

[java] view plain copy
  1. <com.bluemor.reddotface.view.DragLayout   
  2.     xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:id="@+id/dl"  
  4.     android:layout_width="match_parent"  
  5.     android:layout_height="match_parent"  
  6.     android:background="@drawable/bg" >  
  7.   
  8.     <RelativeLayout  
  9.         android:layout_width="match_parent"  
  10.         android:layout_height="match_parent"  
  11.         android:paddingBottom="30dp"  
  12.         android:paddingLeft="30dp"  
  13.         android:onClick="true"  
  14.         android:paddingTop="50dp" >  
  15.   
  16.         <LinearLayout  
  17.             android:id="@+id/ll1"  
  18.             android:layout_width="match_parent"  
  19.             android:layout_height="wrap_content"  
  20.             android:orientation="horizontal" >  
  21.   
  22.             <ImageView  
  23.                 android:id="@+id/iv_bottom"  
  24.                 android:layout_width="70dp"  
  25.                 android:layout_height="70dp"  
  26.                 android:src="@drawable/ic_launcher" />  
  27.   
  28.             <TextView  
  29.                 android:layout_width="wrap_content"  
  30.                 android:layout_height="wrap_content"  
  31.                 android:layout_gravity="center_vertical"  
  32.                 android:layout_marginLeft="20dp"  
  33.                 android:text="BlueMor"  
  34.                 android:textColor="#ffffff"  
  35.                 android:textSize="25sp" />  
  36.         </LinearLayout>  
  37.   
  38.         <ListView  
  39.             android:id="@+id/lv"  
  40.             android:layout_width="match_parent"  
  41.             android:layout_height="wrap_content"  
  42.             android:layout_below="@id/ll1"  
  43.             android:layout_marginBottom="30dp"  
  44.             android:layout_marginTop="20dp"  
  45.             android:cacheColorHint="#00000000"  
  46.             android:divider="@null"  
  47.             android:textColor="#ffffff" />  
  48.     </RelativeLayout>  
  49.   
  50.     <RelativeLayout  
  51.         android:layout_width="match_parent"  
  52.         android:layout_height="match_parent"  
  53.         android:background="#eeeeee" >  
  54.   
  55.         <RelativeLayout  
  56.             android:id="@+id/rl_title"  
  57.             android:layout_width="match_parent"  
  58.             android:layout_height="55dp"  
  59.             android:background="#009990" >  
  60.   
  61.             <ImageView  
  62.                 android:id="@+id/iv_icon"  
  63.                 android:layout_width="42dp"  
  64.                 android:layout_height="42dp"  
  65.                 android:layout_centerVertical="true"  
  66.                 android:layout_marginLeft="10dp"  
  67.                 android:scaleType="centerCrop"  
  68.                 android:src="@drawable/ic_launcher" />  
  69.   
  70.             <TextView  
  71.                 android:layout_width="wrap_content"  
  72.                 android:layout_height="wrap_content"  
  73.                 android:layout_centerInParent="true"  
  74.                 android:text="系统相册"  
  75.                 android:textColor="#ffffff"  
  76.                 android:textSize="20sp" />  
  77.         </RelativeLayout>  
  78.   
  79.         <GridView  
  80.             android:id="@+id/gv_img"  
  81.             android:layout_width="match_parent"  
  82.             android:layout_height="wrap_content"  
  83.             android:layout_below="@id/rl_title"  
  84.             android:cacheColorHint="#00000000"  
  85.             android:numColumns="4"  
  86.             android:verticalSpacing="20dp" >  
  87.         </GridView>  
  88.   
  89.     </RelativeLayout>  
  90.   
  91. </com.bluemor.reddotface.view.DragLayout>  

activity

[java] view plain copy
  1.         dl = (DragLayout) findViewById(R.id.dl);  
  2.         dl.setOrientation(Orientation.Vertical);  
  3. //      dl.setDragListener(new DragListener() {  
  4. //          @Override  
  5. //          public void onOpen() {  
  6. //              lv.smoothScrollToPosition(new Random().nextInt(30));  
  7. //          }  
  8. //  
  9. //          @Override  
  10. //          public void onClose() {  
  11. //                
  12. //          }  
  13. //  
  14. //          @Override  
  15. //          public void onDrag(float percent) {  
  16. //                
  17. //          }  
  18. //      });  




四、实战3

例3:

ViewDragHelper一般用在一个自定义ViewGroup的内部,比如下面自定义了一个继承于LinearLayout的DragLayout,DragLayout内部有一个子viewmDragView作为成员变量:

[html] view plaincopy在CODE上查看代码片派生到我的代码片
  1. public class DragLayout extends LinearLayout {  
  2. private final ViewDragHelper mDragHelper;  
  3. private View mDragView;  
  4. public DragLayout(Context context) {  
  5.   this(context, null);  
  6. }  
  7. public DragLayout(Context context, AttributeSet attrs) {  
  8.   this(context, attrs, 0);  
  9. }  
  10. public DragLayout(Context context, AttributeSet attrs, int defStyle) {  
  11.   super(context, attrs, defStyle);  
  12. }  

创建一个带有回调接口的ViewDragHelper

 

[html] view plaincopy在CODE上查看代码片派生到我的代码片
  1. public DragLayout(Context context, AttributeSet attrs, int defStyle) {  
  2.   super(context, attrs, defStyle);  
  3.   mDragHelper = ViewDragHelper.create(this, 1.0f, new DragHelperCallback());  
  4. }  

其中1.0f是敏感度参数参数越大越敏感。第一个参数为this,表示该类生成的对象,他是ViewDragHelper的拖动处理对象,必须为ViewGroup

要让ViewDragHelper能够处理拖动需要将触摸事件传递给ViewDragHelper,这点和gesturedetector是一样的:

[html] view plaincopy在CODE上查看代码片派生到我的代码片
  1. @Override  
  2. public boolean onInterceptTouchEvent(MotionEvent ev) {  
  3.   final int action = MotionEventCompat.getActionMasked(ev);  
  4.   if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {  
  5.       mDragHelper.cancel();  
  6.       return false;  
  7.   }  
  8.   return mDragHelper.shouldInterceptTouchEvent(ev);  
  9. }  
  10. @Override  
  11. public boolean onTouchEvent(MotionEvent ev) {  
  12.   mDragHelper.processTouchEvent(ev);  
  13.   return true;  
  14. }  

接下来,你就可以在回调中处理各种拖动行为了。


2.拖动行为的处理

处理横向的拖动:

DragHelperCallback中实现clampViewPositionHorizontal方法, 并且返回一个适当的数值就能实现横向拖动效果,clampViewPositionHorizontal的第二个参数是指当前拖动子view应该到达的x坐标。所以按照常理这个方法原封返回第二个参数就可以了,但为了让被拖动的view遇到边界之后就不在拖动,对返回的值做了更多的考虑。

[html] view plaincopy在CODE上查看代码片派生到我的代码片
  1. @Override  
  2. public int clampViewPositionHorizontal(View child, int left, int dx) {  
  3.   Log.d("DragLayout", "clampViewPositionHorizontal " + left + "," + dx);  
  4.   final int leftBound = getPaddingLeft();  
  5.   final int rightBound = getWidth() - mDragView.getWidth();  
  6.   final int newLeft = Math.min(Math.max(left, leftBound), rightBound);  
  7.   return newLeft;  
  8. }  

同上,处理纵向的拖动:

DragHelperCallback中实现clampViewPositionVertical方法,实现过程同clampViewPositionHorizontal

[html] view plaincopy在CODE上查看代码片派生到我的代码片
  1. @Override  
  2. public int clampViewPositionVertical(View child, int top, int dy) {  
  3.   final int topBound = getPaddingTop();  
  4.   final int bottomBound = getHeight() - mDragView.getHeight();  
  5.   final int newTop = Math.min(Math.max(top, topBound), bottomBound);  
  6.   return newTop;  
  7. }  

clampViewPositionHorizontal 和 clampViewPositionVertical必须要重写,因为默认它返回的是0。事实上我们在这两个方法中所能做的事情很有限。 个人觉得这两个方法的作用就是给了我们重新定义目的坐标的机会。

通过DragHelperCallback的tryCaptureView方法的返回值可以决定一个parentview中哪个子view可以拖动,现在假设有两个子views (mDragView1和mDragView2)  ,如下实现tryCaptureView之后,则只有mDragView1是可以拖动的。

1
2
3
4
@Override
public boolean tryCaptureView(View child, int pointerId) {
  returnchild == mDragView1;
}

滑动边缘:

分为滑动左边缘还是右边缘:EDGE_LEFT和EDGE_RIGHT,下面的代码设置了可以处理滑动左边缘:

[html] view plaincopy在CODE上查看代码片派生到我的代码片
  1. mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);  

假如如上设置,onEdgeTouched方法会在左边缘滑动的时候被调用,这种情况下一般都是没有和子view接触的情况。

[html] view plaincopy在CODE上查看代码片派生到我的代码片
  1. @Override  
  2. public void onEdgeTouched(int edgeFlags, int pointerId) {  
  3.     super.onEdgeTouched(edgeFlags, pointerId);  
  4.     Toast.makeText(getContext(), "edgeTouched", Toast.LENGTH_SHORT).show();  
  5. }  

如果你想在边缘滑动的时候根据滑动距离移动一个子view,可以通过实现onEdgeDragStarted方法,并在onEdgeDragStarted方法中手动指定要移动的子View

[html] view plaincopy在CODE上查看代码片派生到我的代码片
  1. @Override  
  2. public void onEdgeDragStarted(int edgeFlags, int pointerId) {  
  3.     mDragHelper.captureChildView(mDragView2, pointerId);  
  4. }  

ViewDragHelper让我们很容易实现一个类似于YouTube视频浏览效果的控件,效果如下:


代码中的关键点:

1.tryCaptureView返回了唯一可以被拖动的header view;

2.拖动范围drag range的计算是在onLayout中完成的;

3.注意在onInterceptTouchEvent和onTouchEvent中使用的ViewDragHelper的若干方法;

4.在computeScroll中使用continueSettling方法(因为ViewDragHelper使用了scroller)

5.smoothSlideViewTo方法来完成拖动结束后的惯性操作。

需要注意的是代码仍然有很大改进空间。

activity_main.xml

[html] view plaincopy在CODE上查看代码片派生到我的代码片
  1. <FrameLayout  
  2.         xmlns:android="http://schemas.android.com/apk/res/android"  
  3.         android:layout_width="match_parent"  
  4.         android:layout_height="match_parent">  
  5.     <ListView  
  6.             android:id="@+id/listView"  
  7.             android:layout_width="match_parent"  
  8.             android:layout_height="match_parent"  
  9.             android:tag="list"  
  10.             />  
  11.     <com.example.vdh.YoutubeLayout  
  12.             android:layout_width="match_parent"  
  13.             android:layout_height="match_parent"  
  14.             android:id="@+id/youtubeLayout"  
  15.             android:orientation="vertical"  
  16.             android:visibility="visible">  
  17.         <TextView  
  18.                 android:id="@+id/viewHeader"  
  19.                 android:layout_width="match_parent"  
  20.                 android:layout_height="128dp"  
  21.                 android:fontFamily="sans-serif-thin"  
  22.                 android:textSize="25sp"  
  23.                 android:tag="text"  
  24.                 android:gravity="center"  
  25.                 android:textColor="@android:color/white"  
  26.                 android:background="#AD78CC"/>  
  27.         <TextView  
  28.                 android:id="@+id/viewDesc"  
  29.                 android:tag="desc"  
  30.                 android:textSize="35sp"  
  31.                 android:gravity="center"  
  32.                 android:text="Loreum Loreum"  
  33.                 android:textColor="@android:color/white"  
  34.                 android:layout_width="match_parent"  
  35.                 android:layout_height="match_parent"  
  36.                 android:background="#FF00FF"/>  
  37.     </com.example.vdh.YoutubeLayout>  
  38. </FrameLayout>  

YoutubeLayout.java
[html] view plaincopy在CODE上查看代码片派生到我的代码片
  1. public class YoutubeLayout extends ViewGroup {  
  2. private final ViewDragHelper mDragHelper;  
  3. private View mHeaderView;  
  4. private View mDescView;  
  5. private float mInitialMotionX;  
  6. private float mInitialMotionY;  
  7. private int mDragRange;  
  8. private int mTop;  
  9. private float mDragOffset;  
  10. public YoutubeLayout(Context context) {  
  11.   this(context, null);  
  12. }  
  13. public YoutubeLayout(Context context, AttributeSet attrs) {  
  14.   this(context, attrs, 0);  
  15. }  
  16. @Override  
  17. protected void onFinishInflate() {  
  18.     mHeaderView = findViewById(R.id.viewHeader);  
  19.     mDescView = findViewById(R.id.viewDesc);  
  20. }  
  21. public YoutubeLayout(Context context, AttributeSet attrs, int defStyle) {  
  22.   super(context, attrs, defStyle);  
  23.   mDragHelper = ViewDragHelper.create(this, 1f, new DragHelperCallback());  
  24. }  
  25. public void maximize() {  
  26.     smoothSlideTo(0f);  
  27. }  
  28. boolean smoothSlideTo(float slideOffset) {  
  29.     final int topBound = getPaddingTop();  
  30.     int y = (int) (topBound + slideOffset * mDragRange);  
  31.     if (mDragHelper.smoothSlideViewTo(mHeaderView, mHeaderView.getLeft(), y)) {  
  32.         ViewCompat.postInvalidateOnAnimation(this);  
  33.         return true;  
  34.     }  
  35.     return false;  
  36. }  
  37. private class DragHelperCallback extends ViewDragHelper.Callback {  
  38.   @Override  
  39.   public boolean tryCaptureView(View child, int pointerId) {  
  40.         return child == mHeaderView;  
  41.   }  
  42.     @Override  
  43.   public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {  
  44.       mTop = top;  
  45.       mDragOffset = (float) top / mDragRange;  
  46.         mHeaderView.setPivotX(mHeaderView.getWidth());  
  47.         mHeaderView.setPivotY(mHeaderView.getHeight());  
  48.         mHeaderView.setScaleX(1 - mDragOffset / 2);  
  49.         mHeaderView.setScaleY(1 - mDragOffset / 2);  
  50.         mDescView.setAlpha(1 - mDragOffset);  
  51.         requestLayout();  
  52.   }  
  53.   @Override  
  54.   public void onViewReleased(View releasedChild, float xvel, float yvel) {  
  55.       int top = getPaddingTop();  
  56.       if (yvel > 0 || (yvel == 0 && mDragOffset > 0.5f)) {  
  57.           top += mDragRange;  
  58.       }  
  59.       mDragHelper.settleCapturedViewAt(releasedChild.getLeft(), top);  
  60.   }  
  61.   @Override  
  62.   public int getViewVerticalDragRange(View child) {  
  63.       return mDragRange;  
  64.   }  
  65.   @Override  
  66.   public int clampViewPositionVertical(View child, int top, int dy) {  
  67.       final int topBound = getPaddingTop();  
  68.       final int bottomBound = getHeight() - mHeaderView.getHeight() - mHeaderView.getPaddingBottom();  
  69.       final int newTop = Math.min(Math.max(top, topBound), bottomBound);  
  70.       return newTop;  
  71.   }  
  72. }  
  73. @Override  
  74. public void computeScroll() {  
  75.   if (mDragHelper.continueSettling(true)) {  
  76.       ViewCompat.postInvalidateOnAnimation(this);  
  77.   }  
  78. }  
  79. @Override  
  80. public boolean onInterceptTouchEvent(MotionEvent ev) {  
  81.   final int action = MotionEventCompat.getActionMasked(ev);  
  82.   if (( action != MotionEvent.ACTION_DOWN)) {  
  83.       mDragHelper.cancel();  
  84.       return super.onInterceptTouchEvent(ev);  
  85.   }  
  86.   if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {  
  87.       mDragHelper.cancel();  
  88.       return false;  
  89.   }  
  90.   final float x = ev.getX();  
  91.   final float y = ev.getY();  
  92.   boolean interceptTap = false;  
  93.   switch (action) {  
  94.       case MotionEvent.ACTION_DOWN: {  
  95.           mInitialMotionX = x;  
  96.           mInitialMotionY = y;  
  97.             interceptTap = mDragHelper.isViewUnder(mHeaderView, (int) x, (int) y);  
  98.           break;  
  99.       }  
  100.       case MotionEvent.ACTION_MOVE: {  
  101.           final float adx = Math.abs(x - mInitialMotionX);  
  102.           final float ady = Math.abs(y - mInitialMotionY);  
  103.           final int slop = mDragHelper.getTouchSlop();  
  104.           if (ady > slop && adx > ady) {  
  105.               mDragHelper.cancel();  
  106.               return false;  
  107.           }  
  108.       }  
  109.   }  
  110.   return mDragHelper.shouldInterceptTouchEvent(ev) || interceptTap;  
  111. }  
  112. @Override  
  113. public boolean onTouchEvent(MotionEvent ev) {  
  114.   mDragHelper.processTouchEvent(ev);  
  115.   final int action = ev.getAction();  
  116.     final float x = ev.getX();  
  117.     final float y = ev.getY();  
  118.     boolean isHeaderViewUnder = mDragHelper.isViewUnder(mHeaderView, (int) x, (int) y);  
  119.     switch (action & MotionEventCompat.ACTION_MASK) {  
  120.       case MotionEvent.ACTION_DOWN: {  
  121.           mInitialMotionX = x;  
  122.           mInitialMotionY = y;  
  123.           break;  
  124.       }  
  125.       case MotionEvent.ACTION_UP: {  
  126.           final float dx = x - mInitialMotionX;  
  127.           final float dy = y - mInitialMotionY;  
  128.           final int slop = mDragHelper.getTouchSlop();  
  129.           if (dx * dx + dy * dy < slop * slop && isHeaderViewUnder) {  
  130.               if (mDragOffset == 0) {  
  131.                   smoothSlideTo(1f);  
  132.               } else {  
  133.                   smoothSlideTo(0f);  
  134.               }  
  135.           }  
  136.           break;  
  137.       }  
  138.   }  
  139.   return isHeaderViewUnder && isViewHit(mHeaderView, (int) x, (int) y) || isViewHit(mDescView, (int) x, (int) y);  
  140. }  
  141. private boolean isViewHit(View view, int x, int y) {  
  142.     int[] viewLocation = new int[2];  
  143.     view.getLocationOnScreen(viewLocation);  
  144.     int[] parentLocation = new int[2];  
  145.     this.getLocationOnScreen(parentLocation);  
  146.     int screenX = parentLocation[0] + x;  
  147.     int screenY = parentLocation[1] + y;  
  148.     return screenX >= viewLocation[0] && screenX < viewLocation[0] + view.getWidth() &&  
  149.             screenY >= viewLocation[1] && screenY < viewLocation[1] + view.getHeight();  
  150. }  
  151. @Override  
  152. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  153.     measureChildren(widthMeasureSpec, heightMeasureSpec);  
  154.     int maxWidth = MeasureSpec.getSize(widthMeasureSpec);  
  155.     int maxHeight = MeasureSpec.getSize(heightMeasureSpec);  
  156.     setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, 0),  
  157.             resolveSizeAndState(maxHeight, heightMeasureSpec, 0));  
  158. }  
  159. @Override  
  160. protected void onLayout(boolean changed, int l, int t, int r, int b) {  
  161.   mDragRange = getHeight() - mHeaderView.getHeight();  
  162.     mHeaderView.layout(  
  163.             0,  
  164.             mTop,  
  165.             r,  
  166.             mTop + mHeaderView.getMeasuredHeight());  
  167.     mDescView.layout(  
  168.             0,  
  169.             mTop + mHeaderView.getMeasuredHeight(),  
  170.             r,  
  171.             mTop  + b);  
  172. }  

代码下载地址:https://github.com/flavienlaurent/flavienlaurent.com


不管是menudrawer 还是本文实现的DragLayout都体现了一种设计哲学,即可拖动的控件都是封装在一个自定义的Layout中的,为什么这样做?为什么不直接将ViewDragHelper.create(this, 1f, new DragHelperCallback())中的this替换成任何已经布局好的容器,这样这个容器中的子View就能被拖动了,而往往是单独定义一个Layout来处理?个人认为如果在一般的布局中去拖动子view并不会出现什么问题,只是原本规则的世界被打乱了,而单独一个Layout来完成拖动,无非是说,他本来就没有什么规则可言,拖动一下也无妨。


参考:http://www.xuebuyuan.com/2225442.html

           http://blog.csdn.net/pi9nc/article/details/39583377

           https://software.intel.com/zh-cn/blogs/2015/03/05/android-zlistview-listview-0


0 0
原创粉丝点击