“View 层级的事件分发,只是应用整体的事件分发的一小部分”
参考:
《Android 开发艺术探索》
反思 | Android 事件分发机制的设计与实现 【掘金】
ConstraintLayout详解【简书】(本文没参考,列一下,方便后续复习)
1. Scroller
scroller 本身无法实现滑动,它实际是一个保存滑动配置数据的类。
1.5 OverScroller(Scroller太旧了)
OverScroller 和 Scroller 没有继承或者组合关系,它们是独立的两个类,只是OverScroller 实现更加优化,同时它增加了一个Over的能力,就是回弹。
之前这篇文章是系统分析了一下Scroller的,但是感觉没有必要,而且讲的也不明白,所以先删掉吧。
2. View的事件分发机制
事件分发相关的三个主要方法:
public boolean dispatchTouchEvent(MotionEvent ev):
这个方法用来进行事件的分发。如果事件传递给当前view,则调用此方法。返回结果表示是否消耗此事件,受onTouchEvent和下级View的dispatchTouchEvent方法影响。
public boolean onInterceptTouchEvent(MotionEvent ev):
这个方法用来判断是否拦截事件。在dispatchTouchEvent方法中调用。返回结果表示是否拦截。
public boolean onTouchEvent(MotionEvent ev):
这个方法用来处理点击事件。在dispatchTouchEvent方法中调用,返回结果表示是否消耗事件。如果不消耗,则在同一个事件序列中,当前View无法再次接收到事件。
三个主要方法之间的关系
伪代码描述
// 伪代码描述,只代表三者间的关系,实际这是一个递归的调用
public boolean dispatchTouchEvent(MotionEvent event) {
boolean isConsume = false;
if (onInterceptTouchEvent(event)) {
isConsume = onTouchEvent(event);
} else {
isConsume = child.dispatchTouchEvent(event);
}
return isConsume;
}
实质概括(大意):
所谓的事件分发,在view这个层级(View的事件分发实际只是Android事件机制的一部分,前置的事件处理可以看另一篇文章:《Android 应用整体的事件分发体系》),就是一个递归调用的过程。
dispatchTouchEvent :这个方法,就是递归方法体;
onInterceptTouchEvent :控制的是否要拦截不进入下一层次的递归,直接在本层结束递归;
onTouchEvent:则是递归的出口,终点,从最底层的View的 onTouchEvent 会一路判断然后直到最外层的 ViewGroup 的 onTouchEvent,如果没有 View 的 onTouchEvent 选择处理这个点击事件,那么事件最终会交给 Activity 处理;
3. View事件分发源码分析
ViewGroup.dispatchTouchEvent()
FLAG_DISALLOW_INTERCEPT:
这个标记是由 View 通过 requestDisallowDispatchTouchEvent() 这个方法来控制的,子View可以通过这个方法来控制父View的拦截,一旦设置了这个变量,那么父View将不能拦截除DOWN事件之外的其他事件(DOWN事件会重置这个标志位)。
// Handle an initial down.
// down 事件触发重置标志位
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
cancelAndClearTouchTargets(ev);
resetTouchState();
}
判断当前ViewGroup是否需要拦截此次的事件。其中,如果ViewGroup确定了由哪个View来消费事件,mFirstTouchTarget 就会指向目标 View,它的值就不会为空了。
// Check for interception.
final boolean intercepted;
// down 事件必然触发拦截的判断,
// 如果不是自己消费事件,而是继续传递,那么也可能触发拦截判断,
// 具体还要看 FLAG_DISALLOW_INTERCEPT 这个标志位的值
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
判断完拦截事件相关的逻辑之后,到了具体分发事件的地方。根据坐标找到有触摸事件发生的View,然后判断动画等状态之后,注意 dispatchTransformTouchEvent() 这个方法,它里面会调用 View 的 dispatchTouchEvent() 从而实现事件从ViewGroup 到 View 的传递。
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY