声明:本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布
本篇为该系列的第二篇,侧重讲解ViewDragHelper 的实现原理和源码逻辑,以及它所提供的Callback。
目录
ViewDragHelper 的介绍以及初步使用请阅读这篇:
ViewDragHelper (一)- 介绍及简单用例(入门篇)
ViewDragHelper 的源码以及Callback的详情介绍请阅读这篇:
ViewDragHelper (二)- 源码及原理解读(进阶篇)
利用DrageHelper 打造仿陌陌APP视频播放页的demo请阅读这篇:
ViewDragHelper (三)- 打造仿陌陌视频播放页(深入篇)
一、 UML 类图及流程图
1.1 ViewDragHelper的UML类图如下所示:
在使用ViewDragHelper过程中,主要涉及到如下四个类:
- MyDraggableView
我们自定义的ViewGroup类。
- ViewDragHelper
帮助类,是我们本篇文章主要分析的对象。
- Callback
ViewDragHelper的内部抽象静态类,主要用于事件处理结果的回调及事件监听。
- DraggableViewCallback
继承于Callback,是它的实现类,ViewDragHelper里面处理的事件,我们可以通过该实现类进行监听回调。
1.2 ViewDragHelper的事件流程图如下所示:
MotionEvent事件是从上往下传递的,如果其中的一个onInterceptTouchEvent返回了true,则表示该View拦截此事件系列,此后的MOVE,UP都不会再调用onInterceptTouchEvent,而是会直接调用自己的onTouchEvent方法。
第一篇文章里面提及的,我们自定义的ViewGroup控件的 onInterceptTouchEvent 方法,是通过 viewDragHelper.shouldInterceptTouchEvent(ev) 方法的返回值来决定是否拦截,当它返回 true 时,会直接触发该类自己的onTouchEvent方法;在onTouchEvent事件里面通过viewDragHelper 的 processTouchEvent(ev) 方法,将MotionEvent传递给viewDragHelper 内部,让viewDragHelper 对事件进行分析处理。以上就是在使用viewDragHelper时,事件分发的大概流程以及它的处理过程了,接下来将分析我们在onTouch 方法里将事件传递给viewDragHelper之后 ,它内部是如何对事件进行分析处理的。
本文由于篇幅关系,重点讲解的是以下几个部分:
1. 抽象内部静态类 ViewDrageHelper .Callback。
2. ViewDrageHelper 内部部分源码逻辑。
3. VelocityTracker。
4. ScrollerCompat。
二、ViewDragHelper源码
由UML类图我们不难看出,ViewDragHelper 是在我们自定义ViewGroup类的构造方法中初始化的,而Callback 是一个ViewDrageHelper 的内部静态抽象类。在创建ViewDragHelper 对象时,我们需要传入一个继承自Callback 的实现类实例对象进去。下面我们一步一步来剖析它的内部逻辑。
2.1 构造器
/**
* Apps should use ViewDragHelper.create() to get a new instance.
* This will allow VDH to use internal compatibility implementations for different
* platform versions.
*
* @param context Context to initialize config-dependent params from
* @param forParent Parent view to monitor
*/
private ViewDragHelper(Context context, ViewGroup forParent, Callback cb)
由以上源码我们看到,它的构造器是私有的,也就是说我们并不能直接在外部通过new ViewDragHelper()的方式来创建对象。那么我们需要如何创建一个新的ViewDragHelper对象呢?不急,我们接着往下看。
2.2 创建对象
我们贴上关于创建对象以及初始化相关的完整源代码,其实,通过构造方法上面的英文注释可以知道,Google提供了两个工厂方法,让开发者去创建一个新的ViewDragHelper对象。如下所示:
create(ViewGroup forParent, Callback cb)
该方法在return 时,利用构造器创建了一个新的ViewDragHelper实例。create(ViewGroup forParent, float sensitivity, Callback cb)
该方法内部,先调用了第一个工厂方法,得到新ViewDragHelper实例,之后又初始化了 mTouchSlop、mMaxVelocity 、mMinVelocity 、mScroller 等数据和对象。
不难看出含有sensitivity 这个参数的create方法,内部也是调用了create(forParent, cb)方法,只是它对mTouchSlop做了一下处理,传入的灵敏度(sensitivity值)越大,mTouchSlop的值越小。假设当前手机的系统mTouchSlop 大小为24dp, 若我们传入的sensitivity = 3.0f ,则mTouchSlop = 8 dp,即单次滑动距离超过8dp,就会触发系统的 MOVE事件。它的源码如下:
/**
* Factory method to create a new ViewDragHelper.
*
* @param forParent Parent view to monitor
* @param cb Callback to provide information and receive events
* @return a new ViewDragHelper instance
*/
public static ViewDragHelper create(ViewGroup forParent, Callback cb) {
return new ViewDragHelper(forParent.getContext(), forParent, cb);
}
/**
* Factory method to create a new ViewDragHelper.
*
* @param forParent Parent view to monitor
* @param sensitivity Multiplier for how sensitive the helper should be about detecting
* the start of a drag. Larger values are more sensitive. 1.0f is normal.
* @param cb Callback to provide information and receive events
* @return a new ViewDragHelper instance
*/
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;
}
/**
* Apps should use ViewDragHelper.create() to get a new instance.
* This will allow VDH to use internal compatibility implementations for different
* platform versions.
*
* @param context Context to initialize config-dependent params from
* @param forParent Parent view to monitor
*/
private ViewDragHelper(Context context, ViewGroup forParent, Callback cb) {
if (forParent == null) {
throw new IllegalArgumentException("Parent view may not be null");
}
if (cb == null) {
throw new IllegalArgumentException("Callback may not be null");
}
mParentView = forParent;
mCallback = cb;
final ViewConfiguration vc = ViewConfiguration.get(context);
final float density = context.getResources().getDisplayMetrics().density;
mEdgeSize = (int) (EDGE_SIZE * density + 0.5f);
mTouchSlop = vc.getScaledTouchSlop();
mMaxVelocity = vc.getScaledMaximumFlingVelocity();
mMinVelocity = vc.getScaledMinimumFlingVelocity();
mScroller = ScrollerCompat.create(context, sInterpolator);
}
2.3 滑动相关
smoothSlideViewTo方法
该方法用于平顺地滑动控件到指定位置。 child代表子控件对象, finalLeft代表滑动结束时,子控件左边所处的位置, finalTop 代表子控件顶部的位置。
那么,smoothSlideViewTo方法内部做了哪些操作呢?下面我们来看一看源代码:
public boolean smoothSlideViewTo(View child, int finalLeft, int finalTop) {
mCapturedView = child;
mActivePointerId = INVALID_POINTER;
boolean continueSliding = forceSettleCapturedViewAt(finalLeft, finalTop, 0, 0);
if (!continueSliding && mDragState == STATE_IDLE && mCapturedView != null) {
mCapturedView = null;
}
return continueSliding;
}
我们可以看到,它是一个布尔型的方法,如果此方法返回 true,则我们应该调用continueSettling方法让它继续滑动,直到返回false,这次滑动才算完成。
settleCapturedViewAt方法
该方法是以松手前的滑动速度为初值,让捕获到的子View自动滑动到指定位置,它只能在Callback的onViewReleased()中使用,若mReleaseInProgress不为True,则会抛出IllegalStateException异常。传递的两个参数分别是结束时子控件的位置,其内部最终调用的是forceSettleCapturedViewAt 方法。
public boolean settleCapturedViewAt(int finalLeft, int finalTop) {
if (!mReleaseInProgress) {
throw