首页 » FastDev4Android » 正文

神器ViewDragHelper完全解析,妈妈再也不担心我自定义ViewGroup啦~

(一).前言:

这几天正在更新录制实战项目,整体框架是采用仿照QQ5.X侧滑效果的。那么我们一般的做法就是自定义ViewGroup或者采用开源项目MenuDrawer或者Google提供的控件DrawerLayout等方式来实现。这些的控件的很多效果基本上都是采用实现onInterceptTouchEvent和onTouchEvent这两个方法进行实现,而且都是根据要实现的效果做自定义处理例如:多点触控处理,加速度方面检测以及控制等等。一般这样做作于普通开发人员来讲也是需要很强的程序与逻辑开发能力,幸好Android开发框架给我们提供了一个组件ViewDragHelper。那么今天我们来具体实现和分析一下ViewDragHelper

具体代码已经上传到下面的项目中,欢迎各位去star和fork一下。

FastDev4Android框架项目地址:https://github.com/jiangqqlmj/FastDev4Android

(二).ViewDragHelper基本介绍:

1.官方介绍如下:

对于自定义ViewGroup而言这边的ViewDragHelper是一个很不错的实用程序类。它给我们提供一系列的方法和相关状态,让我们可以进行拖拽移动或者重新定位ViewGroup中子视图View。ViewDragHelper是一个简化View的拖拽操作的帮助类,使用起来比较简单与方便,一般我们只需要实现几个方法和一个CallBack类就可以实现拖动的View。

2.在使用过程中我们一般会通过ViewDragHelper.Callback来进行连接ViewDragHelper和View。ViewDragHelper提供了一个静态方法让我们来创建实例,使用ViewDragHelper我们可以控制拖动的方向以及检测是否已经拖动到屏幕的边缘等相关操作。

3.ViewDragHelper.Callback中的相关方法说明:

Callback中的方法如下,其他包括一个抽象方法tryCaptureView()和十二个一般方法组成

tryCaptureView(View,int) 传递当前触摸的子View实例,如果当前的子View需要进行拖拽移动返回true
clampViewPositionHorizontal 决定拖拽的View在水平方向上面移动到的位置
clampViewPositionVertical 决定拖拽的View在垂直方向上面移动到的位置
getViewHorizontalDragRange 返回一个大于0的数,然后才会在水平方向移动
getViewVerticalDragRange 返回一个大于0的数,然后才会在垂直方向移动
onViewDragStateChanged 拖拽状态发生变化回调

onViewPositionChanged

当拖拽的View的位置发生变化的时候回调(特指capturedview)
onViewCaptured 捕获captureview的时候回调
onViewReleased 当拖拽的View手指释放的时候回调

onEdgeTouched

当触摸屏幕边界的时候回调

onEdgeLock

是否锁住边界

onEdgeDrageStarted

在边缘滑动的时候可以设置滑动另一个子View跟着滑动
getOrderedChildIndex

上面简单讲解了其中的一些方法说明,下面我们就需要使用这些方法以及ViewDragHelper本身提供的初始化方法以及设置方法来简要说明使用ViewDragHelper使用流程。

(三).ViewDragHelper流程实例:

下面我们开始具体来使用ViewDragHelper了。

1.获取ViewDragHelper实例:我们通过使用ViewDragHelper的一个静态方法来创建实例:

1
2
3
4
5
public static ViewDragHelper create(ViewGroup forParent, float sensitivity, Callback cb) {
        final ViewDragHelper helper = create(forParent, cb);
        helper.mTouchSlop = (int) (helper.mTouchSlop * (1 / sensitivity));
        return helper;
    }
  • 参数1.一个ViewGroup,也就是拖动子View的父控件(ViewGroup)
  • 参数2.灵敏度一般设置成1.0f,表示灵敏度最敏感
  • 参数3.拖拽回调,用来处理拖动的位置等相关操作

具体使用如下:

1
mDragHelper = ViewDragHelper.create(this, 1.0f, new DragHelperCallback());

2.继承ViewDragHelper.Callback类实现一个抽象方法:tryCaptureView()然后在内部进行处理需要捕获的子View(用于拖拽操作和移动)

1
2
3
4
5
6
7
8
9
10
/**
         * 进行捕获拦截,那些View可以进行drag操作
         * @param child
         * @param pointerId
         * @return  直接返回true,拦截所有的VIEW
         */

        @Override
        public boolean tryCaptureView(View child, int pointerId) {
            return true;
        }

这样表示捕捉所有的子View,表示所有的子View都可以进行拖拽操作。

3.重写onInterceptTouchView和onTouchEvent方法来拦截事件以及让ViewDragHelper来进行处理拦截到得事件。因为ViewDragHelper的内部是根据触摸等相关事件来实现拖拽的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 /**
     * 事件分发
     * @param ev
     * @return
     */

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return mDragHelper.shouldInterceptTouchEvent(ev);
    }
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        mDragHelper.processTouchEvent(ev);
        return true;
    }

4.拖动行为处理,例如我们现在需要处理横向的拖拽的,那么我们需要实现clampViewPositionHorizontal方法,并且返回一个适当的数值表示横向拖拽的效果。 一般返回第二个参数即可,不过需要对边界值做一下判断这样子View不要拖出屏幕。

【注】要实现横向滑动这个方法必须要重写,因为ViewDragHelper内部该方法的时候直接返回了0,看下内部实现代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public int clampViewPositionHorizontal(View child, int left, int dx) {
            return 0;
        }
         我们重写的代码如下:
 /**
         * 水平滑动 控制left
         * @param child
         * @param left
         * @param dx
         * @return
         */

        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            Log.d("DragLayout", "clampViewPositionHorizontal " + left + "," + dx);
            final int leftBound = getPaddingLeft();
            final int rightBound = getWidth() - view_one.getWidth();
            final int newLeft = Math.min(Math.max(left, leftBound), rightBound);
            return newLeft;
        }

同样对于垂直方向拖拽的重写clampViewPositionVertical()基本方法也差不多如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 /**
         * 垂直滑动,控制top
         * @param child
         * @param top
         * @param dy
         * @return
         */

        @Override
        public int clampViewPositionVertical(View child, int top, int dy) {
            Log.d("DragLayout", "clampViewPositionVertical " + top + "," + dy);
            final int topBound = getPaddingTop();
            final int bottomBound = getHeight() - view_one.getHeight();
            final int newTop = Math.min(Math.max(top, topBound), bottomBound);
            return newTop;
        }

5.基本功能实现核心代码已经做完了,下面我们来实现一个布局文件:自定义组件ViewGragOne内部放入了两个TextView子View,到时候我们主要拖拽这两个子View即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
< ?xml version="1.0" encoding="utf-8"?>
<linearlayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">
    <include layout="@layout/common_top_bar_layout"/>
    <com .chinaztt.fda.test.ViewDragHelper.ViewGragOne
        android:layout_width="fill_parent"
        android:layout_height="fill_parent">
        <textview android:id="@+id/tv_one"
            android:layout_width="120dp"
            android:layout_height="120dp"
            android:layout_margin="10dp"
            android:text="TV_ONE"
            android:gravity="center"
            android:background="@color/color_1"/>
        <textview android:id="@+id/tv_two"
            android:layout_width="120dp"
            android:layout_height="120dp"
            android:layout_marginTop="200dp"
            android:layout_marginLeft="50dp"
            android:text="TV_TWO"
            android:gravity="center"
            android:background="@color/color_3"/>
    </com>
</linearlayout>

5.自定义组件ViewGragOnew继承自LinerLayout,然后在内部采用ViewDragHelper做相关操作,具体包括以上核心操作的代码如下:

1
2
3
4
5
6
7
8
9
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
public class ViewGragOne extends LinearLayout{
    private View view_one,view_two;
    private ViewDragHelper mDragHelper;
    public ViewGragOne(Context context) {
        this(context, null);
    }
    public ViewGragOne(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }
    public ViewGragOne(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mDragHelper = ViewDragHelper.create(this, 1.0f, new DragHelperCallback());
     }
    class  DragHelperCallback extends Callback {
        /**
         * 进行捕获拦截,那些View可以进行drag操作
         * @param child
         * @param pointerId
         * @return  直接返回true,拦截所有的VIEW
         */

        @Override
        public boolean tryCaptureView(View child, int pointerId) {
            return true;
        }
        /**
         * 水平滑动 控制left
         * @param child
         * @param left
         * @param dx
         * @return
         */

        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            Log.d("DragLayout", "clampViewPositionHorizontal " + left + "," + dx);
            final int leftBound = getPaddingLeft();
            final int rightBound = getWidth() - view_one.getWidth();
            final int newLeft = Math.min(Math.max(left, leftBound), rightBound);
            return newLeft;
        }
        /**
         * 垂直滑动,控制top
         * @param child
         * @param top
         * @param dy
         * @return
         */

        @Override
        public int clampViewPositionVertical(View child, int top, int dy) {
            Log.d("DragLayout", "clampViewPositionVertical " + top + "," + dy);
            final int topBound = getPaddingTop();
            final int bottomBound = getHeight() - view_one.getHeight();
            final int newTop = Math.min(Math.max(top, topBound), bottomBound);
            return newTop;
        }

    }

    /**
     * 事件分发
     * @param ev
     * @return
     */

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return mDragHelper.shouldInterceptTouchEvent(ev);
    }
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        mDragHelper.processTouchEvent(ev);
        return true;
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        view_one=getChildAt(0);
        view_two=getChildAt(1);
    }
}

6.运行效果如下:

(四).ViewDragHelper进阶讲解:

1.tryCaptureView该方法可以进行选择性拦截可以拖拽的子View,那么我们这边选择只拦截第一个View,那么第二个View就无法进行拖拽啦,具体实现方法如下:

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

实现效果如下:

2.滑动边缘相关处理方法setEdgeTrackingEnabled(),onEdgeTouch()以及onEdgeDragStart()。

我们可以给ViewDragHelper加入滑动的边缘设置,组件给我们提供了如下边缘控制值:EDGE_LEFT,EDGE_RIGHT,EDGE_TOP,EDGE_BOTTOM和EDGE_ALL,分别控制上下左右以及全部边缘控制。我们这边设置左边缘:

1
mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);

那么根据上面我们的方法介绍,我们可以重写onEdgeTouched()方法来拦截触摸到边缘的动作。

1
2
3
4
5
@Override
        public void onEdgeTouched(int edgeFlags, int pointerId) {
            super.onEdgeTouched(edgeFlags, pointerId);
            Toast.makeText(getContext(), "edgeTouched", Toast.LENGTH_SHORT).show();
        }

如果要实现我们的手指在边缘进行滑动的时候,同时根据滑动的距离来滑动另外一个View,我们可以重写onEdgeDragStared()方法,在内部调用caturedChildView()方法实现即可。(这个效果我们就可以联想到侧滑界面效果,滑动时候可以打开主布局界面),具体实现代码如下:

1
2
3
4
5
6
7
8
9
/**
         * 在边界滑动的时候 同时滑动dragView2
         * @param edgeFlags
         * @param pointerId
         */

        @Override
        public void onEdgeDragStarted(int edgeFlags, int pointerId) {
            mDragHelper.captureChildView(view_two, pointerId);
        }

运行效果如下:

3.除了以上的方法之外,我们还有一个手指触摸释放之后回调的方法,onViewReleased()。这个好比侧滑组件中我们点击一个位置,然后界面自动打开或者关闭的效果。

1
2
3
4
5
6
7
8
9
10
11
/**
         * 当手指松开的时候回调方法
         * @param releasedChild  滑动手指松开的View
         * @param xvel
         * @param yvel
         */

        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            super.onViewReleased(releasedChild, xvel, yvel);
            Log.d("zttjiangqq","onViewReleased");
        }

当我们拖拽一个子View,然后手指释放之后运行效果如下:

 

     4.ViewGragOne全部实例代码如下:

1
2
3
4
5
6
7
8
9
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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
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
126
127
128
129
130
package com.chinaztt.fda.test.ViewDragHelper;

import android.content.Context;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.widget.ViewDragHelper;
import android.support.v4.widget.ViewDragHelper.Callback;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.Toast;

import com.chinaztt.fda.utils.Log;

/**
 * 当前类注释:
 * 项目名:FastDev4Android
 * 包名:com.chinaztt.fda.test.ViewDragHelper
 * 作者:江清清 on 15/11/24 20:29
 * 邮箱:jiangqqlmj@163.com
 * QQ: 781931404
 * 公司:江苏中天科技软件技术有限公司
 */

public class ViewGragOne extends LinearLayout{
    private View view_one,view_two;
    private ViewDragHelper mDragHelper;
    public ViewGragOne(Context context) {
        this(context, null);
    }
    public ViewGragOne(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }
    public ViewGragOne(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mDragHelper = ViewDragHelper.create(this, 1.0f, new DragHelperCallback());
        mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
    }
    class  DragHelperCallback extends Callback {
        /**
         * 进行捕获拦截,那些View可以进行drag操作
         * @param child
         * @param pointerId
         * @return  直接返回true,拦截所有的VIEW
         */

        @Override
        public boolean tryCaptureView(View child, int pointerId) {
            return true;
        }
        /**
         * 水平滑动 控制left
         * @param child
         * @param left
         * @param dx
         * @return
         */

        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            Log.d("DragLayout", "clampViewPositionHorizontal " + left + "," + dx);
            final int leftBound = getPaddingLeft();
            final int rightBound = getWidth() - view_one.getWidth();
            final int newLeft = Math.min(Math.max(left, leftBound), rightBound);
            return newLeft;
        }
        /**
         * 垂直滑动,控制top
         * @param child
         * @param top
         * @param dy
         * @return
         */

        @Override
        public int clampViewPositionVertical(View child, int top, int dy) {
            Log.d("DragLayout", "clampViewPositionVertical " + top + "," + dy);
            final int topBound = getPaddingTop();
            final int bottomBound = getHeight() - view_one.getHeight();
            final int newTop = Math.min(Math.max(top, topBound), bottomBound);
            return newTop;
        }

        @Override
        public void onEdgeTouched(int edgeFlags, int pointerId) {
            super.onEdgeTouched(edgeFlags, pointerId);
            Toast.makeText(getContext(), "edgeTouched", Toast.LENGTH_SHORT).show();
        }

        /**
         * 在边界滑动的时候 同时滑动dragView2
         * @param edgeFlags
         * @param pointerId
         */

        @Override
        public void onEdgeDragStarted(int edgeFlags, int pointerId) {
            mDragHelper.captureChildView(view_two, pointerId);
        }

        /**
         * 当手指松开的时候回调方法
         * @param releasedChild  滑动手指松开的View
         * @param xvel
         * @param yvel
         */

        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            super.onViewReleased(releasedChild, xvel, yvel);
            Log.d("zttjiangqq","onViewReleased");
        }
    }

    /**
     * 事件分发
     * @param ev
     * @return
     */

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return mDragHelper.shouldInterceptTouchEvent(ev);
    }
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        mDragHelper.processTouchEvent(ev);
        return true;
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        view_one=getChildAt(0);
        view_two=getChildAt(1);
    }
}

(五).最后总结

今天我们对于ViewDragHelper的基本使用方法和相关流程做了详解,下一篇我们会通过ViewDragHelper来讲解实现一个类似QQ5.x侧滑效果的组件以及ViewDragHelper源代码解析。

本次具体实例注释过的全部代码已经上传到FastDev4Android项目中了。同时欢迎大家去Github站点进行clone或者下载浏览:

https://github.com/jiangqqlmj/FastDev4Android 同时欢迎大家star和fork整个开源快速开发框架项目~

尊重原创,转载请注明:From Sky丶清(http://www.lcode.org/) 侵权必究!

关注我的订阅号(codedev123),每天分享移动开发技术(Android/IOS),项目管理以及博客文章!(欢迎关注,第一时间推送精彩文章)

关注我的微博,可以获得更多精彩内容