博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android 一步一步教你使用ViewDragHelper
阅读量:6175 次
发布时间:2019-06-21

本文共 12181 字,大约阅读时间需要 40 分钟。

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

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

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

 

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

布局文件:

1 
2
6 7
11 12
18 19
25 26 27
28 29

 

然后我们看一下自定义的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     @Override25     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         @Override48         public boolean tryCaptureView(View child, int pointerId) {49             if (child == iv2) {50                 return false;51             }52             return true;53         }54 55         @Override56         public int clampViewPositionHorizontal(View child, int left, int dx) {57             return left;58         }59 60         @Override61         public int clampViewPositionVertical(View child, int top, int dy) {62             return top;63         }64     }65 66 67     @Override68     public boolean onInterceptTouchEvent(MotionEvent ev) {69         //决定是否拦截当前事件70         return mDragger.shouldInterceptTouchEvent(ev);71     }72 73     @Override74     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之间 那么就返回left14             //如果left的值 比 leftbound还要小 那么就说明 超过了左边界 那我们只能返回给他左边界的值15             //如果left的值 比rightbound还要大 那么就说明 超过了右边界,那我们只能返回给他右边界的值16             return Math.min(Math.max(left, leftBound), rightBound);17         }18 19         //纵向的注释就不写了 自己体会20         @Override21         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         }

 

我们看下效果

 

然后我们可以再加上一个回弹的效果,就是你把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     @Override104     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     @Override112     public boolean onInterceptTouchEvent(MotionEvent ev) {113         //决定是否拦截当前事件114         return mDragger.shouldInterceptTouchEvent(ev);115     }116 117     @Override118     public boolean onTouchEvent(MotionEvent event) {119         //处理事件120         mDragger.processTouchEvent(event);121         return true;122     }123 124 125 }

看下效果:

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

滑动事件。

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

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

然后看下效果:

这个地方 如果你学过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's17                         // getView[Horizontal|Vertical]DragRange methods to know18                         // if you can move at all along an axis, then see if it19                         // would clamp to the same value. If you can't move at20                         // 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 > 033                                 && newLeft == oldLeft) && (verticalDragRange == 034                                 || 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 drag41                         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   @Override2         public void onEdgeDragStarted(int edgeFlags, int pointerId) {3             mDragger.captureChildView(iv1, pointerId);4         }
1         mDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_ALL);

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

 

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

看下源码。

 

转载于:https://www.cnblogs.com/punkisnotdead/p/4724825.html

你可能感兴趣的文章
lsyncd搭建测试
查看>>
移动web开发之像素和DPR
查看>>
nginx+tomcat+redis实现session共享
查看>>
UWP VirtualizedVariableSizedGridView 支持可虚拟化可变大小Item的View(二)
查看>>
rsync 介绍
查看>>
做一个合格的Team Leader -- 基本概念
查看>>
leetcode 190 Reverse Bits
查看>>
阿里巴巴发布AliOS品牌 重投汽车及IoT领域
查看>>
OPENCV图像处理(二):模糊
查看>>
glassfish4系统启动脚本
查看>>
VMware 虚拟化编程(13) — VMware 虚拟机的备份方案设计
查看>>
独家 | 一文读懂推荐系统知识体系-下(评估、实战、学习资料)
查看>>
UIEvent&amp;nbsp;UIResponder&amp;nbsp;UI_04
查看>>
从非GP到GP
查看>>
云计算助力CDN加速
查看>>
iphone开发之多线程NSThread和NSInvocationOperation
查看>>
MFMailComposeViewController 发邮件
查看>>
velocity 模板解析类
查看>>
HTTP以及HTTPS协议
查看>>
Browser:浏览器版本判断类
查看>>