概述
我们先看下源代码中针对该工具类的注释:
ViewDragHelper is a utility class for writing custom ViewGroups. It offers a number of useful operations and state tracking for allowing a user to drag and reposition views within their parent ViewGroup.
所以:ViewDragHelper 是 Android 提供的一个辅助类,用于实现拖拽、滑动等手势操作。它主要用于处理用户触摸操作,例如实现拖拽布局、滑动删除等交互效果。
ViewDragHelper 能够大大简化开发者处理拖拽手势的工作,使得实现复杂的交互效果变得更加简单和高效。不过,需要注意的是,ViewDragHelper 在一些复杂场景下可能会有一定的学习曲线,并且需要注意处理好各种边界情况,以确保用户体验的流畅性。
主要功能:
ViewDragHelper 提供了以下主要功能:
1、拖拽控制:可以轻松地将 ViewDragHelper 与 ViewGroup 结合使用,实现拖拽指定的 View,这些 View 可以是任何可绘制的对象,例如图片、文本等。
2、边界检测:可以设置边界,限制用户拖拽的范围,防止对象移出屏幕范围或者不可见。
3、速度计算:ViewDragHelper 能够计算拖拽过程中的速度,以便根据用户的操作进行调整。
4、辅助动画:支持拖拽时的动画效果,例如拖拽到一定位置释放时自动回到指定位置。
5、多手势支持:支持多点触控,可以同时拖拽多个对象。
使用步骤
1、初始化:在自定义的 ViewGroup 中创建 ViewDragHelper 实例,并重写相应的回调方法。
2、处理触摸事件:在 ViewGroup 的 onTouchEvent() 方法中将触摸事件传递给 ViewDragHelper 处理。
3、实现回调方法:重写 ViewDragHelper.Callback 中的相关方法,用于处理拖拽、释放等事件。
4、设置拖拽对象:在 ViewGroup 中设置需要拖拽的 View 对象。
举个栗子🌰
比如我们要实现一个简单布局的拖拽,最常用的拖拽方式是直接在onTouch事件中处理拖拽实现,然后通过layout函数来更新View在父布局中的相对位置
以前我们会怎么做
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.RelativeLayout;
public class DraggableRelativeLayout extends RelativeLayout implements View.OnTouchListener {
private int lastX;
private int lastY;
public DraggableRelativeLayout(Context context) {
super(context);
init();
}
public DraggableRelativeLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public DraggableRelativeLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
setOnTouchListener(this);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
int action = event.getAction();
int x = (int) event.getRawX();
int y = (int) event.getRawY();
switch (action) {
case MotionEvent.ACTION_DOWN:
lastX = x;
lastY = y;
break;
case MotionEvent.ACTION_MOVE:
int deltaX = x - lastX;
int deltaY = y - lastY;
// 获取布局的当前位置
int left = v.getLeft();
int top = v.getTop();
int right = v.getRight();
int bottom = v.getBottom();
// 根据手指移动的距离更新布局的位置
v.layout(left + deltaX, top + deltaY, right + deltaX, bottom + deltaY);
lastX = x;
lastY = y;
break;
}
return true;
}
}
如果使用ViewDragHelper会怎么写
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.RelativeLayout;
import androidx.customview.widget.ViewDragHelper;
public class DraggableRelativeLayout extends RelativeLayout {
private ViewDragHelper viewDragHelper;
public DraggableRelativeLayout(Context context) {
super(context);
init();
}
public DraggableRelativeLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public DraggableRelativeLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
viewDragHelper = ViewDragHelper.create(this, 1.0f, new ViewDragHelper.Callback() {
@Override
public boolean tryCaptureView(View child, int pointerId) {
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 viewDragHelper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
viewDragHelper.processTouchEvent(event);
return true;
}
}
所以ViewDragHelper帮我们做了什么呢?
ViewDragHelper 封装了常见的拖动操作,可以简化代码,并提供了更好的性能和稳定性。
比如在以上的代码对比中。
方案一中:我们需要处理Down、Move、Up事件记录坐标进行计算,并更新布局
方案二中:ViewDragHelper 工具类,帮助我们封装了拖动手势的处理逻辑,只需简单地设置拖动的边界和拖动对象,ViewDragHelper 就会帮助你完成剩余的工作。
还有哪些我们常用的成员函数?
create(ViewGroup forParent, float sensitivity, Callback cb)
创建一个 ViewDragHelper 实例。
参数 forParent 是指定要与 ViewDragHelper 关联的父布局。
参数 sensitivity 是灵敏度,表示拖动的灵敏程度,一般取 1.0f。
参数 cb 是 ViewDragHelper.Callback 对象,用于定义拖动行为。
shouldInterceptTouchEvent(MotionEvent ev)
判断是否拦截触摸事件。
在父布局的 onInterceptTouchEvent() 方法中调用,用于决定是否拦截触摸事件交给 ViewDragHelper 处理。
processTouchEvent(MotionEvent event):
处理触摸事件。
在父布局的 onTouchEvent() 方法中调用,用于将触摸事件传递给 ViewDragHelper 处理。
captureChildView(View child, int activePointerId)
捕获指定的子视图。
当需要开始拖动某个视图时调用此方法。
cancel()
取消当前正在进行的拖动操作。
当需要取消拖动操作时调用此方法。
flingCapturedView(int minLeft, int minTop, int maxLeft, int maxTop)
在当前位置释放被捕获的视图并执行惯性动画。
参数 minLeft、minTop、maxLeft、maxTop 为释放后视图的位置范围。
settleCapturedViewAt(int finalLeft, int finalTop)
将被捕获的视图移动到指定位置。
参数 finalLeft、finalTop 为目标位置。
smoothSlideViewTo(View child, int finalLeft, int finalTop)
平滑地将指定的子视图移动到指定位置。
参数 child 是要移动的子视图,finalLeft、finalTop 为目标位置。
getCapturedView()
获取当前被捕获的子视图。
getViewDragState()
获取当前的拖动状态,返回值为 STATE_IDLE、STATE_DRAGGING 或 STATE_SETTLING。
所以我们的处理重点是什么呢?
ViewDragHelper的Create函数中,ViewDragHelper.Callback对象是一个回调接口,用于处理拖拽手势的各个阶段,我们有很多针对手势的“干预”、“探测”工作都是在这里做的。
比如我们上面的例子,只是在clampViewPositionHorizontal回调函数中限制了View在水平方面的移动范围,就可以不让View移出我们限制的移动区域,所以当我们需要缩小或者扩大水平方向的可移动范围时,我们只需要修改return返回的值即可。
再比如,如果我们想要让View只在Y轴移动(仅竖直移动)。
我们可以通过实现
ViewDragHelper.Callback接口,并且复写clampViewPositionHorizontal、clampViewPositionVertical函数即可
完成代码如下:
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.RelativeLayout;
import androidx.customview.widget.ViewDragHelper;
public class LimitedDraggableRelativeLayout extends RelativeLayout {
private ViewDragHelper viewDragHelper;
public LimitedDraggableRelativeLayout(Context context) {
super(context);
init();
}
public LimitedDraggableRelativeLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public LimitedDraggableRelativeLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
viewDragHelper = ViewDragHelper.create(this, 1.0f, new ViewDragHelper.Callback() {
@Override
public boolean tryCaptureView(View child, int pointerId) {
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 Math.max(0, Math.min(getHeight() - child.getHeight(), top));
}
});
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return viewDragHelper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
viewDragHelper.processTouchEvent(event);
return true;
}
}