## (一).前言:
这几天正在更新录制实战项目,整体框架是采用仿照QQ5.X侧滑效果的。那么我们一般的做法就是自定义ViewGroup或者采用开源项目MenuDrawer或者Google提供的控件DrawerLayout等方式来实现。这些的控件的很多效果基本上都是采用实现onInterceptTouchEvent和onTouchEvent这两个方法进行实现,而且都是根据要实现的效果做自定义处理例如:多点触控处理,加速度方面检测以及控制等等。一般这样做作于普通开发人员来讲也是需要很强的程序与逻辑开发能力,幸好Android开发框架给我们提供了一个组件ViewDragHelper。那么今天我们来具体实现和分析一下ViewDragHelper
具体代码已经上传到下面的项目中,欢迎各位去star和fork一下。
FastDev4Android框架项目地址:[https://github.com/jiangqqlmj/FastDev4Android](https://github.com/jiangqqlmj/FastDev4Android)
## (二).ViewDragHelper基本介绍:
1.官方介绍如下:
![](https://box.kancloud.cn/2016-01-18_569c8ec037d15.jpg) 对于自定义ViewGroup而言这边的ViewDragHelper是一个很不错的实用程序类。它给我们提供一系列的方法和相关状态,让我们可以进行拖拽移动或者重新定位ViewGroup中子视图View。ViewDragHelper是一个简化View的拖拽操作的帮助类,使用起来比较简单与方便,一般我们只需要实现几个方法和一个CallBack类就可以实现拖动的View。
2.在使用过程中我们一般会通过ViewDragHelper.Callback来进行连接ViewDragHelper和View。ViewDragHelper提供了一个静态方法让我们来创建实例,使用ViewDragHelper我们可以控制拖动的方向以及检测是否已经拖动到屏幕的边缘等相关操作。
3.ViewGragHelper.Callback中的相关方法说明:
![](https://box.kancloud.cn/2016-01-18_569c8ec04bd7e.jpg)
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.获取ViewGragHelper实例:我们通过使用ViewDragHelper的一个静态方法来创建实例:
~~~
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.拖拽回调,用来处理拖动的位置等相关操作
具体使用如下:
~~~
mDragHelper =ViewDragHelper.create(this, 1.0f, new DragHelperCallback());
~~~
2.继承ViewGragHelper.Callback类实现一个抽象方法:tryCaptureView()然后在内部进行处理需要捕获的子View(用于拖拽操作和移动)
~~~
/**
* 进行捕获拦截,那些View可以进行drag操作
* @param child
* @param pointerId
* @return 直接返回true,拦截所有的VIEW
*/
@Override
public boolean tryCaptureView(Viewchild, int pointerId) {
return true;
}
~~~
这样表示捕捉所有的子View,表示所有的子View都可以进行拖拽操作。
3.重写onInterceptTouchView和onTouchEvent方法来拦截事件以及让ViewDragHelper来进行处理拦截到得事件。因为ViewDragHelper的内部是根据触摸等相关事件来实现拖拽的。
~~~
/**
* 事件分发
* @param ev
* @return
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
returnmDragHelper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent ev){
mDragHelper.processTouchEvent(ev);
return true;
}
~~~
4.拖动行为处理,例如我们现在需要处理横向的拖拽的,那么我们需要实现clampViewPositionHorizontal方法,并且返回一个适当的数值表示横向拖拽的效果。 一般返回第二个参数即可,不过需要对边界值做一下判断这样子View不要拖出屏幕。
【注】要实现横向滑动这个方法必须要重写,因为ViewGragHelper内部该方法的时候直接返回了0,看下内部实现代码:
~~~
public int clampViewPositionHorizontal(Viewchild, 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()基本方法也差不多如下:
~~~
/**
* 垂直滑动,控制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即可。
~~~
<?xmlversion="1.0" encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"android:layout_width="match_parent"
android:layout_height="match_parent">
<includelayout="@layout/common_top_bar_layout"/>
<com.chinaztt.fda.test.ViewGragHelper.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.chinaztt.fda.test.ViewGragHelper.ViewGragOne>
</LinearLayout>
~~~
5.自定义组件ViewGragOnew继承自LinerLayout,然后在内部采用ViewDragHelper做相关操作,具体包括以上核心操作的代码如下:
~~~
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(Viewchild, 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) {
returnmDragHelper.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.运行效果如下:
![](https://box.kancloud.cn/2016-01-18_569c8ec06d9c0.jpg)
## (四).ViewDragHelper进阶讲解:
1.tryCaptureView该方法可以进行选择性拦截可以拖拽的子View,那么我们这边选择只拦截第一个View,那么第二个View就无法进行拖拽啦,具体实现方法如下:
~~~
@Override
public boolean tryCaptureView(Viewchild, int pointerId) {
return child==view_one;
}
~~~
实现效果如下:
![](https://box.kancloud.cn/2016-01-18_569c8ec0d047a.jpg)
2.滑动边缘相关处理方法setEdgeTrackingEnabled(),onEdgeTouch()以及onEdgeDragStart()。
我们可以给ViewDragHelper加入滑动的边缘设置,组件给我们提供了如下边缘控制值:EDGE_LEFT,EDGE_RIGHT,EDGE_TOP,EDGE_BOTTOM和EDGE_ALL,分别控制上下左右以及全部边缘控制。我们这边设置左边缘:
mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
那么根据上面我们的方法介绍,我们可以重写onEdgeTouched()方法来拦截触摸到边缘的动作。
~~~
@Override
public void onEdgeTouched(intedgeFlags, int pointerId) {
super.onEdgeTouched(edgeFlags,pointerId);
Toast.makeText(getContext(),"edgeTouched", Toast.LENGTH_SHORT).show();
}
~~~
如果要实现我们的手指在边缘进行滑动的时候,同时根据滑动的距离来滑动另外一个View,我们可以重写onEdgeDragStared()方法,在内部调用caturedChildView()方法实现即可。(这个效果我们就可以联想到侧滑界面效果,滑动时候可以打开主布局界面),具体实现代码如下:
~~~
/**
* 在边界滑动的时候 同时滑动dragView2
* @param edgeFlags
* @param pointerId
*/
@Override
public void onEdgeDragStarted(intedgeFlags, int pointerId) {
mDragHelper.captureChildView(view_two, pointerId);
}
~~~
运行效果如下:
![](https://box.kancloud.cn/2016-01-18_569c8ec131435.jpg)
3.除了以上的方法之外,我们还有一个手指触摸释放之后回调的方法,onViewReleased()。这个好比侧滑组件中我们点击一个位置,然后界面自动打开或者关闭的效果。
~~~
/**
* 当手指松开的时候回调方法
* @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,然后手指释放之后运行效果如下:
![](https://box.kancloud.cn/2016-01-18_569c8ec1b7fe9.jpg)
4.ViewGragOne全部实例代码如下:
~~~
package com.chinaztt.fda.test.ViewGragHelper;
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.ViewGragHelper
* 作者:江清清 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(Viewchild, 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);
}
/**
* 当手指松开的时候回调方法
* @paramreleasedChild 滑动手指松开的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](https://github.com/jiangqqlmj/FastDev4Android) 同时欢迎大家star和fork整个开源快速开发框架项目~
- 前言
- Android快速开发框架介绍(一)
- Android首页图片自动无限循环轮播Gallery+FlowIndicator(二)
- Android 列表下拉刷新组件PullToRefreshListView使用(三)
- Android 数据缓存器ACache的详解和使用(四)
- Android崩溃异常捕捉CustomCrash,提升用户体验(五)
- Android实现沉浸式状态栏(六)
- AndroidAnnnotations注入框架介绍和Android Studios基本配置(七)
- AndroidAnnnotations注入框架的工作原理(八)
- AndroidAnnnotations注入框架使用之注入组件Components(九)
- AndroidAnnnotations注入框架使用之Injection标签详解(十)
- AndroidAnnnotations注入框架使用之事件绑定Event Binding(十一)
- AndroidAnnnotations注入框架使用之线程处理Threading(十二)
- AndroidAnnnotations注入框架使用之第三方框架集成RoboGuice(十三)
- AndroidAnnnotations注入框架使用之第三方框架集成Otto事件总线(十四)
- AndroidAnnnotations注入框架使用之第三方框架集成OrmLite(十五)
- AndroidAnnnotations注入框架使用之最佳实践之Adapters和lists(十六)
- AndroidAnnnotations注入框架使用之最佳实践SharedPreferences(十七)
- Android MVP开发模式详解(十九)
- 消息总线EventBus的基本使用(二十)
- 消息总线EventBus源码分析以及与Otto框架对比(二十一)
- 列表头生成带文本或者字母的图片开源库TextDrawable使用和详解(二十二)
- 重写WebView网页加载以及JavaScript注入详解(二十三)
- BaseAdapterHelper的基本使用介绍,让你摆脱狂写一堆Adapter烦恼(二十四)
- BaseAdapterHelper详解源码分析,让你摆脱狂写一堆Adapter烦恼(二十五)
- Volley完全解析之基础使用(二十六)
- Volley完全解析之进阶最佳实践与二次封装(二十七)
- RecyclerView完全解析,让你从此爱上它(二十八)
- RecyclerView完全解析之打造新版类Gallery效果(二十九)
- RecyclerView完全解析之结合AA(Android Annotations)注入框架实例(三十)
- RecyclerView完全解析之下拉刷新与上拉加载SwipeRefreshLayout(三十一)
- CardView完全解析与RecyclerView结合使用(三十二)
- 神器ViewDragHelper完全解析,妈妈再也不担心我自定义ViewGroup滑动View操作啦~(三十三)
- 神器ViewDragHelper完全解析之详解实现QQ5.X侧滑酷炫效果(三十四)
- 实例解析之SwipeRefreshLayout+RecyclerView+CardView(三十五)
- HorizontalScrollView,Fragment,FragmentStatePagerAdapter打造网易新闻Tab及滑动页面效果(三十六)
- Android Design支持库TabLayout打造仿网易新闻Tab标签效果(三十七)
- 打造QQ6.X最新版本侧滑界面效果(三十八)