Android 一步一步教你使用ViewDragHelper
在自定义viewgroup的时候 要重写onInterceptTouchEvent和onTouchEvent 这2个方法 是非常麻烦的事情,好在谷歌后来
推出了ViewDragHelper这个类。可以极大方便我们自定义viewgroup.
先看一个简单效果 一个layout里有2个图片 其中有一个可以滑动 一个不能滑
这个效果其实还蛮简单的
布局文件:
[XML] 纯文本查看 复制代码
?
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | <? xml version = “1.0” encoding = “utf-8” ?> android:layout_width = “match_parent” android:layout_height = “match_parent” android:orientation = “vertical” > < com.example.administrator.viewdragertestapp.DragLayout android:layout_width = “match_parent” android:layout_height = “match_parent” android:orientation = “vertical” > < ImageView android:id = “@+id/iv1” android:layout_width = “wrap_content” android:layout_height = “wrap_content” android:layout_gravity = “center_horizontal” android:src = “@drawable/a1” ></ ImageView > < ImageView android:id = “@+id/iv2” android:layout_width = “wrap_content” android:layout_height = “wrap_content” android:layout_gravity = “center_horizontal” android:src = “@drawable/a2” ></ ImageView > </ com.example.administrator.viewdragertestapp.DragLayout > </ LinearLayout > |
然后我们看一下自定义的layout 如何实现2个子view 一个可以滑动 一个不能滑动的
[Java] 纯文本查看 复制代码
?
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | package com.example.administrator.viewdragertestapp; import android.content.Context; import android.support.v4.widget.ViewDragHelper; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; /*
Created by Administrator on 2015/8/12. */ public class DragLayout extends LinearLayout { private ViewDragHelper mDragger; private ViewDragHelper.Callback callback; private ImageView iv1; private ImageView iv2; @Override protected void onFinishInflate() { iv1 = (ImageView) this .findViewById(R.id.iv1); iv2 = (ImageView) this .findViewById(R.id.iv2); super .onFinishInflate(); } public DragLayout(Context context) { super (context); } public DragLayout(Context context, AttributeSet attrs) { super (context, attrs); callback = new DraggerCallBack(); //第二个参数就是滑动灵敏度的意思 可以随意设置 mDragger = ViewDragHelper.create( this , 1 .0f, callback); } class DraggerCallBack extends ViewDragHelper.Callback { //这个地方实际上函数返回值为true就代表可以滑动 为false 则不能滑动 @Override public boolean tryCaptureView(View child, int pointerId) { if (child == iv2) { return false ; } return true ; } @Override public int clampViewPositionHorizontal(View child, int left, int dx) { return left; } @Override public int clampViewPositionVertical(View child, int top, int dy) { return top; } } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { //决定是否拦截当前事件 return mDragger.shouldInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { //处理事件 mDragger.processTouchEvent(event); return true ; } } |
然后再完善一下这个layout,刚才滑动的时候我们的view 出了屏幕的边界很不美观 现在我们修改2个函数 让滑动的范围
在这个屏幕之内(准确的说是在这个layout之内,因为我们的布局文件layout充满了屏幕 所以看上去是在屏幕内)
[Java] 纯文本查看 复制代码
?
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | <font style= “color:rgb(51, 51, 51)” ><font style= “background-color:rgb(255, 255, 255)” ><font face= “Verdana, Arial, Helvetica, sans-serif” > //这个地方实际上left就代表 你将要移动到的位置的坐标。返回值就是最终确定的移动的位置。 // 我们要让view滑动的范围在我们的layout之内 //实际上就是判断如果这个坐标在layout之内 那我们就返回这个坐标值。 //如果这个坐标在layout的边界处 那我们就只能返回边界的坐标给他。不能让他超出这个范围 //除此之外就是如果你的layout设置了padding的话,也可以让子view的活动范围在padding之内的. @Override public int clampViewPositionHorizontal(View child, int left, int dx) { //取得左边界的坐标 final int leftBound = getPaddingLeft(); //取得右边界的坐标 final int rightBound = getWidth() - child.getWidth() - leftBound; //这个地方的含义就是 如果left的值 在leftbound和rightBound之间 那么就返回left //如果left的值 比 leftbound还要小 那么就说明 超过了左边界 那我们只能返回给他左边界的值 //如果left的值 比rightbound还要大 那么就说明 超过了右边界,那我们只能返回给他右边界的值 return Math.min(Math.max(left, leftBound), rightBound); } //纵向的注释就不写了 自己体会 @Override public int clampViewPositionVertical(View child, int top, int dy) { final int topBound = getPaddingTop(); final int bottomBound = getHeight() - child.getHeight() - topBound; return Math.min(Math.max(top, topBound), bottomBound); }</font></font></font> |
我们看下效果
然后我们可以再加上一个回弹的效果,就是你把babay拉倒一个位置 然后松手他会自动回弹到初始位置
其实思路很简单 就是你松手的时候 回到初始的坐标位置即可。
[Java] 纯文本查看 复制代码
?
001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027 028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050 051 052 053 054 055 056 057 058 059 060 061 062 063 064 065 066 067 068 069 070 071 072 073 074 075 076 077 078 079 080 081 082 083 084 085 086 087 088 089 090 091 092 093 094 095 096 097 098 099 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 | <font style= “color:rgb(51, 51, 51)” ><font style= “background-color:rgb(255, 255, 255)” ><font face= “Verdana, Arial, Helvetica, sans-serif” > package com.example.administrator.viewdragertestapp; import android.content.Context; import android.graphics.Point; import android.support.v4.widget.ViewDragHelper; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; /*
Created by Administrator on 2015/8/12. */ public class DragLayout extends LinearLayout { private ViewDragHelper mDragger; private ViewDragHelper.Callback callback; private ImageView iv1; private ImageView iv2; private Point initPointPosition = new Point(); @Override protected void onFinishInflate() { iv1 = (ImageView) this .findViewById(R.id.iv1); iv2 = (ImageView) this .findViewById(R.id.iv2); super .onFinishInflate(); } public DragLayout(Context context) { super (context); } public DragLayout(Context context, AttributeSet attrs) { super (context, attrs); callback = new DraggerCallBack(); //第二个参数就是滑动灵敏度的意思 可以随意设置 mDragger = ViewDragHelper.create( this , 1 .0f, callback); } class DraggerCallBack extends ViewDragHelper.Callback { //这个地方实际上函数返回值为true就代表可以滑动 为false 则不能滑动 @Override public boolean tryCaptureView(View child, int pointerId) { if (child == iv2) { return false ; } return true ; } //这个地方实际上left就代表 你将要移动到的位置的坐标。返回值就是最终确定的移动的位置。 // 我们要让view滑动的范围在我们的layout之内 //实际上就是判断如果这个坐标在layout之内 那我们就返回这个坐标值。 //如果这个坐标在layout的边界处 那我们就只能返回边界的坐标给他。不能让他超出这个范围 //除此之外就是如果你的layout设置了padding的话,也可以让子view的活动范围在padding之内的. @Override public int clampViewPositionHorizontal(View child, int left, int dx) { //取得左边界的坐标 final int leftBound = getPaddingLeft(); //取得右边界的坐标 final int rightBound = getWidth() - child.getWidth() - leftBound; //这个地方的含义就是 如果left的值 在leftbound和rightBound之间 那么就返回left //如果left的值 比 leftbound还要小 那么就说明 超过了左边界 那我们只能返回给他左边界的值 //如果left的值 比rightbound还要大 那么就说明 超过了右边界,那我们只能返回给他右边界的值 return Math.min(Math.max(left, leftBound), rightBound); } //纵向的注释就不写了 自己体会 @Override public int clampViewPositionVertical(View child, int top, int dy) { final int topBound = getPaddingTop(); final int bottomBound = getHeight() - child.getHeight() - topBound; return Math.min(Math.max(top, topBound), bottomBound); } @Override public void onViewReleased(View releasedChild, float xvel, float yvel) { //松手的时候 判断如果是这个view 就让他回到起始位置 if (releasedChild == iv1) { //这边代码你跟进去去看会发现最终调用的是startScroll这个方法 所以我们就明白还要在computeScroll方法里刷新 mDragger.settleCapturedViewAt(initPointPosition.x, initPointPosition.y); invalidate(); } } } @Override public void computeScroll() { if (mDragger.continueSettling( true )) { invalidate(); } } @Override protected void onLayout( boolean changed, int l, int t, int r, int b) { super .onLayout(changed, l, t, r, b); //布局完成的时候就记录一下位置 initPointPosition.x = iv1.getLeft(); initPointPosition.y = iv1.getTop(); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { //决定是否拦截当前事件 return mDragger.shouldInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { //处理事件 mDragger.processTouchEvent(event); return true ; } }</font></font></font> |
看下效果:
到这里有人会发现 这样做的话imageview就无法响应点击事件了。继续修改这个代码让iv可以响应点击事件并且可以响应
滑动事件。
首先修改xml 把click属性设置为true 这个代码就不上了,然后修改我们的代码 其实就是增加2个函数
[Java] 纯文本查看 复制代码
?
1 2 3 4 5 6 7 8 9 | <font style= “color:rgb(51, 51, 51)” ><font style= “background-color:rgb(255, 255, 255)” ><font face= “Verdana, Arial, Helvetica, sans-serif” > @Override public int getViewHorizontalDragRange(View child) { return getMeasuredWidth() - child.getMeasuredWidth(); } @Override public int getViewVerticalDragRange(View child) { return getMeasuredHeight()-child.getMeasuredHeight(); }</font></font></font> |
然后看下效果:
这个地方 如果你学过android 事件传递的话很好理解,因为如果你子view可以响应点击事件的话,那说明你消费了这个事件。
如果你消费了这个事件话 就会先走dragger的 onInterceptTouchEvent这个方法。我们跟进去看看这个方法
[Java] 纯文本查看 复制代码
?
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | <font style= “color:rgb(51, 51, 51)” ><font style= “background-color:rgb(255, 255, 255)” ><font face= “Verdana, Arial, Helvetica, sans-serif” > case MotionEvent.ACTION_MOVE: { if (mInitialMotionX == null || mInitialMotionY == null ) break ; // First to cross a touch slop over a draggable view wins. Also report edge drags. final int pointerCount = MotionEventCompat.getPointerCount(ev); for ( int i = 0 ; i < pointerCount; i++) { final int pointerId = MotionEventCompat.getPointerId(ev, i); final float x = MotionEventCompat.getX(ev, i); final float y = MotionEventCompat.getY(ev, i); final float dx = x - mInitialMotionX[pointerId]; final float dy = y - mInitialMotionY[pointerId]; final View toCapture = findTopChildUnder(( int ) x, ( int ) y); final boolean pastSlop = toCapture != null && checkTouchSlop(toCapture, dx, dy); if (pastSlop) { // check the callback’s // getView[Horizontal|Vertical]DragRange methods to know // if you can move at all along an axis, then see if it // would clamp to the same value. If you can’t move at // all in every dimension with a nonzero range, bail. final int oldLeft = toCapture.getLeft(); final int targetLeft = oldLeft + ( int ) dx; final int newLeft = mCallback.clampViewPositionHorizontal(toCapture, targetLeft, ( int ) dx); final int oldTop = toCapture.getTop(); final int targetTop = oldTop + ( int ) dy; final int newTop = mCallback.clampViewPositionVertical(toCapture, targetTop, ( int ) dy); final int horizontalDragRange = mCallback.getViewHorizontalDragRange( toCapture); final int verticalDragRange = mCallback.getViewVerticalDragRange(toCapture); if ((horizontalDragRange == 0 || horizontalDragRange > 0 && newLeft == oldLeft) && (verticalDragRange == 0 || verticalDragRange > 0 && newTop == oldTop)) { break ; } } reportNewEdgeDrags(dx, dy, pointerId); if (mDragState == STATE_DRAGGING) { // Callback might have started an edge drag break ; } if (pastSlop && tryCaptureViewForDrag(toCapture, pointerId)) { break ; } } saveLastMotion(ev); break ; }</font></font></font> |
注意看29行到末尾 你会发现 只有当
horizontalDragRange 和verticalDragRange
大于0的时候 对应的move事件才会捕获。否则就是丢弃直接丢给子view自己处理了
另外还有一个效果就是 假如我们的 baby被拉倒了边界处,
我们的手指不需要拖动baby这个iv,手指直接在边界的其他地方拖动此时也能把这个iv拖走。
这个效果其实也可以实现,无非就是捕捉你手指在边界处的动作 然后传给你要拖动的view即可。
代码非常简单 两行即可
再重写一个回调函数 然后加个监听
[Java] 纯文本查看 复制代码
?
1 2 3 4 | @Override public void onEdgeDragStarted( int edgeFlags, int pointerId) { mDragger.captureChildView(iv1, pointerId); } |
[Java] 纯文本查看 复制代码
?
1 | mDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_ALL); |
这个效果在模拟器上不知道为啥 鼠标拖不动,GIF图片我就不上了大家可以自己在手机里跑一下就可以。
上面的那些效果实际上都是DrawerLayout 等类似抽屉效果里经常用到的函数,有兴趣的同学可以
看下源码。
原文:http://www.cnblogs.com/punkisnotdead/p/4724825.html?utm\_source=tuicool
还没有评论,来说两句吧...