Android中神奇的ViewDragHelper
参考文献:
https://blog.csdn.net/briblue/article/details/73730386
https://www.jianshu.com/p/111a7bc76a0e
ViewDragHelper是针对 ViewGroup 中的拖拽和重新定位 views 操作时提供了一系列非常有用的方法和状态追踪。基本上使用在自定义ViewGroup处理拖拽中!
所谓无图无真相。。。
终极目标回弹效果
本文知识点:
- 常用API的说明
- ViewDragHelper的使用案例
- 常见的使用案例
1.常用API说明
这里为什么先介绍API呢?因为上来我贴出来一堆代码你都不知道每个方法什么意思,会很痛苦的,而且还要跳着看。所以这里决定先介绍一下API大家先有个印象,后面说到的时候也不会那么陌生,好了废话不多说了!
ViewDragHelper的API
-
ViewDragHelper create(ViewGroup forParent, Callback cb);一个静态的创建方法,
- 参数1:出入的是相应的ViewGroup
- 参数2:是一个回掉(其实这个回掉你可以自己在外面实现,后面在细说)
-
shouldInterceptTouchEvent(MotionEvent ev) 处理事件分发的(怎么说这个方法呢?主要是将ViewGroup的事件分发,委托给ViewDragHelper进行处理)
- 参数1:MotionEvent ev 主要是ViewGroup的事件
- processTouchEvent(MotionEvent event) 处理相应TouchEvent的方法,这里要注意一个问题,处理相应的TouchEvent的时候要将结果返回为true,消费本次事件!否则将无法使用ViewDragHelper处理相应的拖拽事件!
基本上使用到的就这三个API就能实现相应的拖拽效果了!(有的API我没有查看,好像还有很多API但是有的真的不怎么理解!原谅我的放荡不羁。。。)
ViewDragHelper.Callback的API(也就是创建ViewDragHelper传入的回调方法)
其实这个回掉主要是用来监听一些内容的,其实你可以这样,自己实现一个类,继承这个类,然后在里面写相应的逻辑,这样代码能比较整洁!也便于其他人的观看
-
tryCaptureView(View child, int pointerId) 这是一个抽象类,必须去实现,也只有在这个方法返回true的时候下面的方法才会生效;
- 参数1:捕获的View(也就是你拖动的这个View)
- 参数2:这个参数我也不知道什么意思API中写的一个什么指针,这里没有到也没有注意
-
onViewDragStateChanged(int state) 当状态改变的时候回调,返回相应的状态(这里有三种状态)
- STATE_IDLE 闲置状态
- STATE_DRAGGING 正在拖动
- STATE_SETTLING 放置到某个位置
-
onViewPositionChanged(View changedView, int left, int top, int dx, int dy) 当你拖动的View位置发生改变的时候回调
- 参数1:你当前拖动的这个View
- 参数2:距离左边的距离
- 参数3:距离右边的距离
- 参数4:x轴的变化量
- 参数5:y轴的变化量
-
onViewCaptured(View capturedChild, int activePointerId)捕获View的时候调用的方法
- 参数1:捕获的View(也就是你拖动的这个View)
- 参数2:这个参数我也不知道什么意思API中写的一个什么指针,这里没有到也没有注意
-
onViewReleased(View releasedChild, float xvel, float yvel) 当View停止拖拽的时候调用的方法,一般在这个方法中重置一些参数,比如回弹什么的。。。
- 参数1:你拖拽的这个View
- 参数2:x轴的速率
- 参数3:y轴的速率
-
clampViewPositionVertical(View child, int top, int dy) 竖直拖拽的时候回调的方法
- 参数1:拖拽的View
- 参数2:距离顶部的距离
- 参数3:变化量
-
clampViewPositionHorizontal(View child, int left, int dx) 水平拖拽的时候回调的方法
- 参数1:拖拽的View
- 参数2:距离左边的距离
- 参数3:变化量
基本上常用的API就这么多,也都解释了相应参数代表的意思,可能有些不是那么准确,还请指正。。。
2. ViewDragHelper的使用案例
2.1案例一:
自定义一个LinearLayout使其内部的View可以进行相应的拖动
2.1.1 创建相应的回调CallBack(其实就是创建一个相应的ViewDragHelper.Callback对象)这个主要是用于把代码拆分到外面去。。。代码如下
public class MyDragHelper extends ViewDragHelper.Callback {
@Override
public boolean tryCaptureView(View child, int pointerId) {
//注意这里一定要返回true,否则后续的拖拽回调是不会生效的
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;
}
}
这里主要处理了内部控件的拖拽问题;
2.1.2自定义一个LinearLayout,授权相应的拖拽。代码如下:
public class MyDragLinearLayout extends LinearLayout {
private ViewDragHelper mViewDragHelper;
public MyDragLinearLayout(Context context) {
this(context, null);
}
public MyDragLinearLayout(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public MyDragLinearLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
MyDragHelper dragHelper = new MyDragHelper();
mViewDragHelper = ViewDragHelper.create(this, dragHelper);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mViewDragHelper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
mViewDragHelper.processTouchEvent(event);
return true;
}
}
上面这段代码就是在View初始话的时候,创建了一个回调和一个ViewDragHelper对象,在onInterceptTouchEvent和onTouchEvent处理一下相应的逻辑(这里有一点千万要注意就是在onTouchEvent的时候一定要返回true,切记!!!)
然后直接在布局文件中使用这个自定义的ViewGroup就可以了。
3.常见的使用案例
1.回弹效果
回弹效果是最常见的了,也是最主要的内容就是回弹的效果!!!其实使用方法和上面是类似的!
1.1委托相应的服务是一样的,这里就直接贴上相应的代码了。
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mViewDragHelper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
mViewDragHelper.processTouchEvent(event);
return true;
}
1.2获取捕获的View,并获取捕获时候的位置信息
@Override
public void onViewCaptured(View capturedChild, int activePointerId) {
super.onViewCaptured(capturedChild, activePointerId);
mLeft = capturedChild.getLeft();
mTop = capturedChild.getTop();
}
这里主要是通过相应的捕获的回调获取相应的位置信息,主要是作用是为了之后回弹到原始位置提供相应的位置信息;
1.3在释放的时候重置这个位置信息
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
super.onViewReleased(releasedChild, xvel, yvel);
mViewDragHelper.settleCapturedViewAt(mLeft, mTop);
invalidate();
}
这里主要是通过ViewDragHelper的settleCapturedViewAt(int finalLeft, int finalTop)方法使你拖拽的View回到上面获取的原始位置了。。。但是其实这样是不行的,为什么呢???上面这个确实是设置拖拽View位置的,但是查看API的时候有这样一段描述信息
If this method returns true, the caller should invoke {@link #continueSettling(boolean)}
on each subsequent frame to continue the motion until it returns false. If this method
returns false there is no further work to do to complete the movement.
简单的翻译一下:
如果这个方法返回true,调用者应该调用{ @link #continueSettling(boolean)}在每个后续帧继续运动,直到返回false。如果这个方法返回false,没有进一步的工作来完成运动。
也就是说这里你要通过这个方法进行相应的处理,但是怎么处理呢?请看下面这段代码:
@Override
public void computeScroll() {
if (mViewDragHelper != null && mViewDragHelper.continueSettling(true)) {
invalidate();
}
}
这个是重写的ViewGroup的方法,主要是用于ViewGroup中更新相应View的(通过ScrollX
和ScrollY)通过上面的代码就能实现回弹了。整体代码如下:
public class MyDragLinearLayout extends LinearLayout {
private ViewDragHelper mViewDragHelper;
public MyDragLinearLayout(Context context) {
this(context, null);
}
public MyDragLinearLayout(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public MyDragLinearLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mViewDragHelper = ViewDragHelper.create(this, new ViewDragHelper.Callback() {
private int mLeft;
private int mTop;
@Override
public boolean tryCaptureView(View child, int pointerId) {
return true;
}
@Override
public void onViewCaptured(View capturedChild, int activePointerId) {
super.onViewCaptured(capturedChild, activePointerId);
mLeft = capturedChild.getLeft();
mTop = capturedChild.getTop();
}
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
return top;
}
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
return left;
}
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
super.onViewReleased(releasedChild, xvel, yvel);
mViewDragHelper.settleCapturedViewAt(mLeft, mTop);
invalidate();
}
});
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mViewDragHelper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
mViewDragHelper.processTouchEvent(event);
return true;
}
@Override
public void computeScroll() {
if (mViewDragHelper != null && mViewDragHelper.continueSettling(true)) {
invalidate();
}
}
}
其实对这个API也只是表层的理解,如有什么不对的地方请指出。。。