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" ?>
 
< LinearLayout xmlns:android = "http://schemas.android.com/apk/res/android"
 
     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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值