转载

Android 一步一步教你使用ViewDragHelper

在自定义viewgroup的时候 要重写 onInterceptTouchEventonTouchEvent 这2个方法 是非常麻烦的事情,好在谷歌后来

推出了ViewDragHelper这个类。可以极大方便我们自定义viewgroup.

先看一个简单效果 一个layout里有2个图片 其中有一个可以滑动 一个不能滑

Android 一步一步教你使用ViewDragHelper

这个效果其实还蛮简单的(原谅我让臭脚不能动 让BABY动)

布局文件:

 1 <?xml version="1.0" encoding="utf-8"?>  2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  3     android:layout_width="match_parent"  4     android:layout_height="match_parent"  5     android:orientation="vertical">  6   7     <com.example.administrator.viewdragertestapp.DragLayout  8         android:layout_width="match_parent"  9         android:layout_height="match_parent" 10         android:orientation="vertical"> 11  12         <ImageView 13             android:id="@+id/iv1" 14             android:layout_width="wrap_content" 15             android:layout_height="wrap_content" 16             android:layout_gravity="center_horizontal" 17             android:src="@drawable/a1"></ImageView> 18  19         <ImageView 20             android:id="@+id/iv2" 21             android:layout_width="wrap_content" 22             android:layout_height="wrap_content" 23             android:layout_gravity="center_horizontal" 24             android:src="@drawable/a2"></ImageView> 25  26  27     </com.example.administrator.viewdragertestapp.DragLayout> 28  29 </LinearLayout>

然后我们看一下自定义的layout 如何实现2个子view 一个可以滑动 一个不能滑动的

 1 package com.example.administrator.viewdragertestapp;  2   3 import android.content.Context;  4 import android.support.v4.widget.ViewDragHelper;  5 import android.util.AttributeSet;  6 import android.view.MotionEvent;  7 import android.view.View;  8 import android.widget.ImageView;  9 import android.widget.LinearLayout; 10 import android.widget.TextView; 11  12 /** 13  * Created by Administrator on 2015/8/12. 14  */ 15 public class DragLayout extends LinearLayout { 16  17     private ViewDragHelper mDragger; 18  19     private ViewDragHelper.Callback callback; 20  21     private ImageView iv1; 22     private ImageView iv2; 23  24     @Override 25     protected void onFinishInflate() { 26         iv1 = (ImageView) this.findViewById(R.id.iv1); 27         iv2 = (ImageView) this.findViewById(R.id.iv2); 28         super.onFinishInflate(); 29  30     } 31  32     public DragLayout(Context context) { 33         super(context); 34  35     } 36  37     public DragLayout(Context context, AttributeSet attrs) { 38         super(context, attrs); 39         callback = new DraggerCallBack(); 40         //第二个参数就是滑动灵敏度的意思 可以随意设置 41         mDragger = ViewDragHelper.create(this, 1.0f, callback); 42     } 43  44     class DraggerCallBack extends ViewDragHelper.Callback { 45  46         //这个地方实际上函数返回值为true就代表可以滑动 为false 则不能滑动 47         @Override 48         public boolean tryCaptureView(View child, int pointerId) { 49             if (child == iv2) { 50                 return false; 51             } 52             return true; 53         } 54  55         @Override 56         public int clampViewPositionHorizontal(View child, int left, int dx) { 57             return left; 58         } 59  60         @Override 61         public int clampViewPositionVertical(View child, int top, int dy) { 62             return top; 63         } 64     } 65  66  67     @Override 68     public boolean onInterceptTouchEvent(MotionEvent ev) { 69         //决定是否拦截当前事件 70         return mDragger.shouldInterceptTouchEvent(ev); 71     } 72  73     @Override 74     public boolean onTouchEvent(MotionEvent event) { 75         //处理事件 76         mDragger.processTouchEvent(event); 77         return true; 78     } 79  80  81 }

然后再完善一下这个layout,刚才滑动的时候我们的view 出了屏幕的边界很不美观 现在我们修改2个函数 让滑动的范围

在这个屏幕之内(准确的说是在这个layout之内,因为我们的布局文件layout充满了屏幕 所以看上去是在屏幕内)

 1  //这个地方实际上left就代表 你将要移动到的位置的坐标。返回值就是最终确定的移动的位置。  2         // 我们要让view滑动的范围在我们的layout之内  3         //实际上就是判断如果这个坐标在layout之内 那我们就返回这个坐标值。  4         //如果这个坐标在layout的边界处 那我们就只能返回边界的坐标给他。不能让他超出这个范围  5         //除此之外就是如果你的layout设置了padding的话,也可以让子view的活动范围在padding之内的.  6   7         @Override  8         public int clampViewPositionHorizontal(View child, int left, int dx) {  9             //取得左边界的坐标 10             final int leftBound = getPaddingLeft(); 11             //取得右边界的坐标 12             final int rightBound = getWidth() - child.getWidth() - leftBound; 13             //这个地方的含义就是 如果left的值 在leftbound和rightBound之间 那么就返回left 14             //如果left的值 比 leftbound还要小 那么就说明 超过了左边界 那我们只能返回给他左边界的值 15             //如果left的值 比rightbound还要大 那么就说明 超过了右边界,那我们只能返回给他右边界的值 16             return Math.min(Math.max(left, leftBound), rightBound); 17         } 18  19         //纵向的注释就不写了 自己体会 20         @Override 21         public int clampViewPositionVertical(View child, int top, int dy) { 22             final int topBound = getPaddingTop(); 23             final int bottomBound = getHeight() - child.getHeight() - topBound; 24             return Math.min(Math.max(top, topBound), bottomBound); 25         }

我们看下效果

Android 一步一步教你使用ViewDragHelper

然后我们可以再加上一个回弹的效果,就是你把babay拉倒一个位置 然后松手他会自动回弹到初始位置

其实思路很简单 就是你松手的时候 回到初始的坐标位置即可。

  1 package com.example.administrator.viewdragertestapp;   2    3 import android.content.Context;   4 import android.graphics.Point;   5 import android.support.v4.widget.ViewDragHelper;   6 import android.util.AttributeSet;   7 import android.view.MotionEvent;   8 import android.view.View;   9 import android.widget.ImageView;  10 import android.widget.LinearLayout;  11 import android.widget.TextView;  12   13 /**  14  * Created by Administrator on 2015/8/12.  15  */  16 public class DragLayout extends LinearLayout {  17   18     private ViewDragHelper mDragger;  19   20     private ViewDragHelper.Callback callback;  21   22     private ImageView iv1;  23     private ImageView iv2;  24   25     private Point initPointPosition = new Point();  26   27     @Override  28     protected void onFinishInflate() {  29         iv1 = (ImageView) this.findViewById(R.id.iv1);  30         iv2 = (ImageView) this.findViewById(R.id.iv2);  31         super.onFinishInflate();  32   33     }  34   35     public DragLayout(Context context) {  36         super(context);  37   38     }  39   40     public DragLayout(Context context, AttributeSet attrs) {  41         super(context, attrs);  42         callback = new DraggerCallBack();  43         //第二个参数就是滑动灵敏度的意思 可以随意设置  44         mDragger = ViewDragHelper.create(this, 1.0f, callback);  45     }  46   47     class DraggerCallBack extends ViewDragHelper.Callback {  48   49         //这个地方实际上函数返回值为true就代表可以滑动 为false 则不能滑动  50         @Override  51         public boolean tryCaptureView(View child, int pointerId) {  52             if (child == iv2) {  53                 return false;  54             }  55             return true;  56         }  57   58   59         //这个地方实际上left就代表 你将要移动到的位置的坐标。返回值就是最终确定的移动的位置。  60         // 我们要让view滑动的范围在我们的layout之内  61         //实际上就是判断如果这个坐标在layout之内 那我们就返回这个坐标值。  62         //如果这个坐标在layout的边界处 那我们就只能返回边界的坐标给他。不能让他超出这个范围  63         //除此之外就是如果你的layout设置了padding的话,也可以让子view的活动范围在padding之内的.  64   65         @Override  66         public int clampViewPositionHorizontal(View child, int left, int dx) {  67             //取得左边界的坐标  68             final int leftBound = getPaddingLeft();  69             //取得右边界的坐标  70             final int rightBound = getWidth() - child.getWidth() - leftBound;  71             //这个地方的含义就是 如果left的值 在leftbound和rightBound之间 那么就返回left  72             //如果left的值 比 leftbound还要小 那么就说明 超过了左边界 那我们只能返回给他左边界的值  73             //如果left的值 比rightbound还要大 那么就说明 超过了右边界,那我们只能返回给他右边界的值  74             return Math.min(Math.max(left, leftBound), rightBound);  75         }  76   77         //纵向的注释就不写了 自己体会  78         @Override  79         public int clampViewPositionVertical(View child, int top, int dy) {  80             final int topBound = getPaddingTop();  81             final int bottomBound = getHeight() - child.getHeight() - topBound;  82             return Math.min(Math.max(top, topBound), bottomBound);  83         }  84   85         @Override  86         public void onViewReleased(View releasedChild, float xvel, float yvel) {  87             //松手的时候 判断如果是这个view 就让他回到起始位置  88             if (releasedChild == iv1) {  89                 //这边代码你跟进去去看会发现最终调用的是startScroll这个方法 所以我们就明白还要在computeScroll方法里刷新  90                 mDragger.settleCapturedViewAt(initPointPosition.x, initPointPosition.y);  91                 invalidate();  92             }  93         }  94     }  95   96     @Override  97     public void computeScroll() {  98         if (mDragger.continueSettling(true)) {  99             invalidate(); 100         } 101     } 102  103     @Override 104     protected void onLayout(boolean changed, int l, int t, int r, int b) { 105         super.onLayout(changed, l, t, r, b); 106         //布局完成的时候就记录一下位置 107         initPointPosition.x = iv1.getLeft(); 108         initPointPosition.y = iv1.getTop(); 109     } 110  111     @Override 112     public boolean onInterceptTouchEvent(MotionEvent ev) { 113         //决定是否拦截当前事件 114         return mDragger.shouldInterceptTouchEvent(ev); 115     } 116  117     @Override 118     public boolean onTouchEvent(MotionEvent event) { 119         //处理事件 120         mDragger.processTouchEvent(event); 121         return true; 122     } 123  124  125 }

看下效果:

Android 一步一步教你使用ViewDragHelper

到这里有人会发现 这样做的话imageview就无法响应点击事件了。继续修改这个代码让iv可以响应点击事件并且可以响应

滑动事件。

首先修改xml 把click属性设置为true 这个代码就不上了,然后修改我们的代码 其实就是增加2个函数

1  @Override 2         public int getViewHorizontalDragRange(View child) { 3             return getMeasuredWidth() - child.getMeasuredWidth(); 4         } 5  6         @Override 7         public int getViewVerticalDragRange(View child) { 8             return getMeasuredHeight()-child.getMeasuredHeight(); 9         }

然后看下效果:

Android 一步一步教你使用ViewDragHelper

这个地方 如果你学过android 事件传递的话很好理解,因为如果你子view可以响应点击事件的话,那说明你消费了这个事件。

如果你消费了这个事件话 就会先走dragger的 onInterceptTouchEvent这个方法。我们跟进去看看这个方法

 1   case MotionEvent.ACTION_MOVE: {  2                 if (mInitialMotionX == null || mInitialMotionY == null) break;  3   4                 // First to cross a touch slop over a draggable view wins. Also report edge drags.  5                 final int pointerCount = MotionEventCompat.getPointerCount(ev);  6                 for (int i = 0; i < pointerCount; i++) {  7                     final int pointerId = MotionEventCompat.getPointerId(ev, i);  8                     final float x = MotionEventCompat.getX(ev, i);  9                     final float y = MotionEventCompat.getY(ev, i); 10                     final float dx = x - mInitialMotionX[pointerId]; 11                     final float dy = y - mInitialMotionY[pointerId]; 12  13                     final View toCapture = findTopChildUnder((int) x, (int) y); 14                     final boolean pastSlop = toCapture != null && checkTouchSlop(toCapture, dx, dy); 15                     if (pastSlop) { 16                         // check the callback's 17                         // getView[Horizontal|Vertical]DragRange methods to know 18                         // if you can move at all along an axis, then see if it 19                         // would clamp to the same value. If you can't move at 20                         // all in every dimension with a nonzero range, bail. 21                         final int oldLeft = toCapture.getLeft(); 22                         final int targetLeft = oldLeft + (int) dx; 23                         final int newLeft = mCallback.clampViewPositionHorizontal(toCapture, 24                                 targetLeft, (int) dx); 25                         final int oldTop = toCapture.getTop(); 26                         final int targetTop = oldTop + (int) dy; 27                         final int newTop = mCallback.clampViewPositionVertical(toCapture, targetTop, 28                                 (int) dy); 29                         final int horizontalDragRange = mCallback.getViewHorizontalDragRange( 30                                 toCapture); 31                         final int verticalDragRange = mCallback.getViewVerticalDragRange(toCapture); 32                         if ((horizontalDragRange == 0 || horizontalDragRange > 0 33                                 && newLeft == oldLeft) && (verticalDragRange == 0 34                                 || verticalDragRange > 0 && newTop == oldTop)) { 35                             break; 36                         } 37                     } 38                     reportNewEdgeDrags(dx, dy, pointerId); 39                     if (mDragState == STATE_DRAGGING) { 40                         // Callback might have started an edge drag 41                         break; 42                     } 43  44                     if (pastSlop && tryCaptureViewForDrag(toCapture, pointerId)) { 45                         break; 46                     } 47                 } 48                 saveLastMotion(ev); 49                 break; 50             }

注意看29行到末尾 你会发现 只有当

horizontalDragRange 和verticalDragRange

大于0的时候 对应的move事件才会捕获。否则就是丢弃直接丢给子view自己处理了

另外还有一个效果就是 假如我们的 baby被拉倒了边界处,

我们的手指不需要拖动baby这个iv,手指直接在边界的其他地方拖动此时也能把这个iv拖走。

这个效果其实也可以实现,无非就是捕捉你手指在边界处的动作 然后传给你要拖动的view即可。

代码非常简单 两行即可

再重写一个回调函数 然后加个监听

1   @Override 2         public void onEdgeDragStarted(int edgeFlags, int pointerId) { 3             mDragger.captureChildView(iv1, pointerId); 4         }
1         mDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_ALL);

这个效果在模拟器上不知道为啥 鼠标拖不动,GIF图片我就不上了大家可以自己在手机里跑一下就可以。

上面的那些效果实际上都是DrawerLayout 等类似抽屉效果里经常用到的函数,有兴趣的同学可以

看下源码。

正文到此结束
Loading...