ViewGroup与View
ViewGroup和View事件的分发,拦截,消费的相关方法如下表:
| 类型 | 相关方法 | ViewGroup | View |
|---|---|---|---|
| 事件分发 | dispatchTouchEvent | 有 | 有 |
| 事件拦截 | onInterceptTouchEvent | 有 | 无 |
| 事件消费 | onTouchEvent | 有 | 有 |
这个三个方法的返回值均是Boolean类型,通过true和false来控制事件的传递和消费流程。我们先通过实际例子来看看事件传递,消费的流程。
xml:
<?xml version="1.0" encoding="utf-8"?>
<com.aoy.touch.TouchViewGroup xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.aoy.touch.MainActivity">
<com.aoy.touch.TouchChildView
android:layout_width="150dp"
android:layout_height="150dp"
android:background="@color/colorPrimary"
android:text="TouchChild"
android:gravity="center"
android:textSize="16sp"
android:textColor="#ffffff"
android:layout_centerInParent="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</com.aoy.touch.TouchViewGroup>
TouchViewGroup:
class TouchViewGroup : RelativeLayout {
val TAG : String = "TouchStudy"
constructor(ctx: Context):super(ctx)
constructor(ctx: Context,attrs: AttributeSet):super(ctx,attrs)
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
when(ev!!.action){
MotionEvent.ACTION_DOWN -> {
Log.i(TAG,"ParentView dispatchTouchEvent ACTION_DOWN")
}
MotionEvent.ACTION_MOVE -> {
Log.i(TAG,"ParentView dispatchTouchEvent ACTION_MOVE")
}
MotionEvent.ACTION_UP,MotionEvent.ACTION_CANCEL ->{
Log.i(TAG,"ParentView dispatchTouchEvent ACTION_CANCEL")
}
}
var ret = super.dispatchTouchEvent(ev)
Log.i(TAG,"ParentView dispatchTouchEvent return :" + ret)
return ret
}
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
when(ev!!.action){
MotionEvent.ACTION_DOWN -> {
Log.i(TAG,"ParentView onInterceptTouchEvent ACTION_DOWN")
}
MotionEvent.ACTION_MOVE -> {
Log.i(TAG,"ParentView onInterceptTouchEvent ACTION_MOVE")
// return true
}
MotionEvent.ACTION_UP,MotionEvent.ACTION_CANCEL ->{
Log.i(TAG,"ParentView onInterceptTouchEvent ACTION_CANCEL")
}
}
var ret = super.onInterceptTouchEvent(ev)
Log.i(TAG,"ParentView onInterceptTouchEvent return :" + ret)
return ret
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
when (event!!.action) {
MotionEvent.ACTION_DOWN -> {
Log.i(TAG, "ParentView onTouchEvent ACTION_DOWN")
}
MotionEvent.ACTION_MOVE -> {
Log.i(TAG, "ParentView onTouchEvent ACTION_MOVE")
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
Log.i(TAG, "ParentView onTouchEvent ACTION_CANCEL")
}
}
var ret = super.onTouchEvent(event)
Log.i(TAG,"ParentView onTouchEvent return :" + ret)
return super.onTouchEvent(event)
}
}
TouchChildView:
class TouchChildView : TextView {
val TAG : String = "TouchStudy"
constructor(ctx: Context): super(ctx)
constructor(ctx: Context,attrs: AttributeSet): super(ctx,attrs)
override fun dispatchTouchEvent(event: MotionEvent?): Boolean {
when(event!!.action){
MotionEvent.ACTION_DOWN -> {
Log.i(TAG,"ChildView dispatchTouchEvent ACTION_DOWN")
}
MotionEvent.ACTION_MOVE -> {
Log.i(TAG,"ChildView dispatchTouchEvent ACTION_MOVE")
}
MotionEvent.ACTION_UP,MotionEvent.ACTION_CANCEL ->{
Log.i(TAG,"ChildView dispatchTouchEvent ACTION_CANCEL")
}
}
var ret : Boolean = super.dispatchTouchEvent(event)
Log.i(TAG,"ChildView dispatchTouchEvent return :" + ret)
return ret
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
when (event!!.action) {
MotionEvent.ACTION_DOWN -> {
Log.i(TAG, "ChildView onTouchEvent ACTION_DOWN")
}
MotionEvent.ACTION_MOVE -> {
Log.i(TAG, "ChildView onTouchEvent ACTION_MOVE")
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
Log.i(TAG, "ChildView onTouchEvent ACTION_CANCEL")
}
}
var ret : Boolean = super.onTouchEvent(event)
Log.i(TAG,"ChildView onTouchEvent return :" + ret)
return ret
}
}
TouchViewGroup与TouchChildView主要是将接收到的事件打印出来。
TouchStudy: ParentView dispatchTouchEvent ACTION_DOWN
TouchStudy: ParentView onInterceptTouchEvent ACTION_DOWN
TouchStudy: ParentView onInterceptTouchEvent return :false
TouchStudy: ChildView dispatchTouchEvent ACTION_DOWN
TouchStudy: ChildView onTouchEvent ACTION_DOWN
TouchStudy: ChildView onTouchEvent return :false
TouchStudy: ChildView dispatchTouchEvent return :false
TouchStudy: ParentView onTouchEvent ACTION_DOWN
TouchStudy: ParentView onTouchEvent return :false
TouchStudy: ParentView dispatchTouchEvent return :false
其事件的分发消费流程如下图 :
由于childView在onTouchEvent()中没有消耗down事件导致childView dispatchTouchEvent返回false,这会让ParentView不会把后续的ACTION_MOVE和ACTION_UP分发给childView。同理由于childView和parentView的onTouchEvent()都没有消耗down事件,所以parentView也没有收到后续的ACTION_DOWN和ACTION_UP事件。
② 现在我们让childView的onTouchEvent()消耗ACTION_DOWN事件,但ViewGroup中不拦截ACTION_DOWN事件,改动代码如下:
/**
* childView onTouchEvent
*/
override fun onTouchEvent(event: MotionEvent?): Boolean {
when (event!!.action) {
MotionEvent.ACTION_DOWN -> {
Log.i(TAG, "ChildView onTouchEvent ACTION_DOWN")
return true
}
MotionEvent.ACTION_MOVE -> {
Log.i(TAG, "ChildView onTouchEvent ACTION_MOVE")
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
Log.i(TAG, "ChildView onTouchEvent ACTION_CANCEL")
}
}
var ret : Boolean = super.onTouchEvent(event)
Log.i(TAG,"ChildView onTouchEvent return :" + ret)
return ret
}
在childView中滑动之后,其log为:
TouchStudy: ParentView dispatchTouchEvent ACTION_DOWN
TouchStudy: ParentView onInterceptTouchEvent ACTION_DOWN
TouchStudy: ParentView onInterceptTouchEvent return :false
TouchStudy: ChildView dispatchTouchEvent ACTION_DOWN
TouchStudy: ChildView onTouchEvent ACTION_DOWN
TouchStudy: ChildView onTouchEvent return :true
TouchStudy: ChildView dispatchTouchEvent return :true
TouchStudy: ParentView dispatchTouchEvent return :true
TouchStudy: ParentView dispatchTouchEvent ACTION_MOVE
TouchStudy: ParentView onInterceptTouchEvent ACTION_MOVE
TouchStudy: ParentView onInterceptTouchEvent return :false
TouchStudy: ChildView dispatchTouchEvent ACTION_MOVE
TouchStudy: ChildView onTouchEvent ACTION_MOVE
TouchStudy: ChildView onTouchEvent return :false
TouchStudy: ChildView dispatchTouchEvent return :false
TouchStudy: ParentView dispatchTouchEvent return :false
TouchStudy: ParentView dispatchTouchEvent ACTION_MOVE
TouchStudy: ParentView onInterceptTouchEvent ACTION_MOVE
TouchStudy: ParentView onInterceptTouchEvent return :false
TouchStudy: ChildView dispatchTouchEvent ACTION_MOVE
TouchStudy: ChildView onTouchEvent ACTION_MOVE
TouchStudy: ChildView onTouchEvent return :false
TouchStudy: ChildView dispatchTouchEvent return :false
TouchStudy: ParentView dispatchTouchEvent return :false
TouchStudy: ParentView dispatchTouchEvent ACTION_CANCEL
TouchStudy: ParentView onInterceptTouchEvent ACTION_CANCEL
TouchStudy: ParentView onInterceptTouchEvent return :false
TouchStudy: ChildView dispatchTouchEvent ACTION_CANCEL
TouchStudy: ChildView onTouchEvent ACTION_CANCEL
TouchStudy: ChildView onTouchEvent return :false
TouchStudy: ChildView dispatchTouchEvent return :false
TouchStudy: ParentView dispatchTouchEvent return :false
其ACTION_DOWN事件的分发,消费流程图如下:
其ACTION_MOVE和ACITON_UP(ACTION_CANCEL)事件的分发,消费入下图:
③ 让ViewGroup中拦截ACTION_DOWN事件,改动代码如下:
/**
* parentView onInterceptTouchEvent
*/
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
when(ev!!.action){
MotionEvent.ACTION_DOWN -> {
Log.i(TAG,"ParentView onInterceptTouchEvent ACTION_DOWN")
return true;
}
MotionEvent.ACTION_MOVE -> {
Log.i(TAG,"ParentView onInterceptTouchEvent ACTION_MOVE")
// return true
}
MotionEvent.ACTION_UP,MotionEvent.ACTION_CANCEL ->{
Log.i(TAG,"ParentView onInterceptTouchEvent ACTION_CANCEL")
}
}
var ret = super.onInterceptTouchEvent(ev)
Log.i(TAG,"ParentView onInterceptTouchEvent return :" + ret)
return ret
}
/**
* childView onTouchEvent
*/
override fun onTouchEvent(event: MotionEvent?): Boolean {
when (event!!.action) {
MotionEvent.ACTION_DOWN -> {
Log.i(TAG, "ChildView onTouchEvent ACTION_DOWN")
Log.i(TAG,"ChildView onTouchEvent return :" + true)
return true
}
MotionEvent.ACTION_MOVE -> {
Log.i(TAG, "ChildView onTouchEvent ACTION_MOVE")
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
Log.i(TAG, "ChildView onTouchEvent ACTION_CANCEL")
}
}
var ret : Boolean = super.onTouchEvent(event)
Log.i(TAG,"ChildView onTouchEvent return :" + ret)
return ret
}
这种情景下的运行log如下:
TouchStudy: ParentView dispatchTouchEvent ACTION_DOWN
TouchStudy: ParentView onInterceptTouchEvent ACTION_DOWN
TouchStudy: ParentView onTouchEvent ACTION_DOWN
TouchStudy: ParentView onTouchEvent return :false
TouchStudy: ParentView dispatchTouchEvent return :false
ACTION_DOWN事件的分发消费流程图:
这种情况下,parentView不会将任何事件分发给childView,同时parentView也没有消费ACTION_DOWN事件,导致parentView不会收到ACTION_MOVE,ACTION_UP等事件。
④ 让childView消耗ACTION_DOWN事件,同时让ViewGroup中拦截ACTION_MOVE事件,改动代码如下:
/**
* parentView onInterceptTouchEvent
*/
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
when(ev!!.action){
MotionEvent.ACTION_DOWN -> {
Log.i(TAG,"ParentView onInterceptTouchEvent ACTION_DOWN")
}
MotionEvent.ACTION_MOVE -> {
Log.i(TAG,"ParentView onInterceptTouchEvent ACTION_MOVE")
return true
}
MotionEvent.ACTION_UP,MotionEvent.ACTION_CANCEL ->{
Log.i(TAG,"ParentView onInterceptTouchEvent ACTION_CANCEL")
}
}
var ret = super.onInterceptTouchEvent(ev)
Log.i(TAG,"ParentView onInterceptTouchEvent return :" + ret)
return ret
}
/**
* childView onTouchEvent
*/
override fun onTouchEvent(event: MotionEvent?): Boolean {
when (event!!.action) {
MotionEvent.ACTION_DOWN -> {
Log.i(TAG, "ChildView onTouchEvent ACTION_DOWN")
Log.i(TAG,"ChildView onTouchEvent return :" + true)
return true
}
MotionEvent.ACTION_MOVE -> {
Log.i(TAG, "ChildView onTouchEvent ACTION_MOVE")
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
Log.i(TAG, "ChildView onTouchEvent ACTION_CANCEL")
}
}
var ret : Boolean = super.onTouchEvent(event)
Log.i(TAG,"ChildView onTouchEvent return :" + ret)
return ret
}
其运行log如下:
TouchStudy: ParentView dispatchTouchEvent ACTION_DOWN
TouchStudy: ParentView onInterceptTouchEvent ACTION_DOWN
TouchStudy: ParentView onInterceptTouchEvent return :false
TouchStudy: ChildView dispatchTouchEvent ACTION_DOWN
TouchStudy: ChildView onTouchEvent ACTION_DOWN
TouchStudy: ChildView onTouchEvent return :true
TouchStudy: ChildView dispatchTouchEvent return :true
TouchStudy: ParentView dispatchTouchEvent return :true
TouchStudy: ParentView dispatchTouchEvent ACTION_MOVE
TouchStudy: ParentView onInterceptTouchEvent ACTION_MOVE
TouchStudy: ChildView dispatchTouchEvent ACTION_CANCEL
TouchStudy: ChildView onTouchEvent ACTION_CANCEL
TouchStudy: ChildView onTouchEvent return :false
TouchStudy: ChildView dispatchTouchEvent return :false
TouchStudy: ParentView dispatchTouchEvent return :false
TouchStudy: ParentView dispatchTouchEvent ACTION_MOVE
TouchStudy: ParentView onTouchEvent ACTION_MOVE
TouchStudy: ParentView dispatchTouchEvent ACTION_CANCEL
TouchStudy: ParentView onTouchEvent ACTION_CANCEL
TouchStudy: ParentView onTouchEvent return :false
TouchStudy: ParentView dispatchTouchEvent return :false
ACTION_DOWN事件的分发消费过程图:
ACTION_MOVE的分发消费过程图:
从③和④两种情况可以看出当parentView拦截ACTION_DOWN事件后,childView将不会收到ACTION_DOWN事和后续的ACTION_MOVE与ACTION_UP事件,而parentView能否收到后续的事件,取决于parentView的onTouchEvent是否消费ACTION_DOWN事件。当childView消费了ACTION_DOWN事件后,如果parentView拦截ACTION_MOVE事件,则后续的一系列事件都将交由parentView的ontouchEvent()来处理,不会再走onInterceptionEvent()方法,childView将不会收到后续的任何事件。
结论:
(1)不管是GroupView还是View,消费事件指的是消费ACTION_DOWN事件,只有消费了ACTION_DOWN事件,才有可能接收分发后续的ACTION_MOVE和ACTION_UP等事件。
(2)对于View而言,消耗ACTION_DOWN事件只有一种途径:在onTouchEvent()中接受到ACTION_DOWN时返回true。 而GroupVie消耗ACTION_DOWN事件有两种途径:①其child view消费ACTION_DOWN;②其自身消耗ACTION_DOWN事件,即在其自身的onTouchEvent()中接受到ACTION_DOWN事件时返回true,这两种消耗ACTION_DOWN事件的关系为:parentView会先view是否消耗ACTION_DOWN分发给child view,让child view决定是否消耗ACTION_DOWN,只有当child view不消耗时,才将ACTION_DONW事件传递给其本身的onTouchEvent()方法让其判断是否消费ACTION_DOWN方法。
(3)当GroupView拦截某一事件时,GroupView 不会将这一事件和后续的所有事件分发给child view,拦截的这一事件将会交给GroupView的onTouchEvent()处理。对于后续GroupView接收到的所有事件不会走拦截过程,而是直接交由GroupView的onTouchEvent()处理。
源码分析
以下所有的源码都是基于API26(android 8.0)
View 的dispatchTouchEvent源码:
public boolean dispatchTouchEvent(MotionEvent event) {
...
if (onFilterTouchEventForSecurity(event)) {
//关注点一:当View为可点击并且在拖动scrollBar时直接消费事件
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//关注点二:当View设置为可点击并且并且设置OnTouchListener处理时,直接消费事件,并将所有的一系列事件交由OnTouchListener处理
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//关注点三:如果不是拖动scrollBar并且没有设置OnTouchListener则事件交由onTouchEvent()处理
if (!result && onTouchEvent(event)) {
result = true;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}
从“关注点二”和“关注点三”中可以看出如果View设置了OnTouchListener则view直接消费事件,并将所有的事件都交由OnTouchListener处理,就onTouchEvent()什么事了,如果没有设置OnTouchListener,则是否消费事件由onTouchEvent()决定,下面看看View的onTouchEvent()源码。onTouchEvent源码较长,挑重点来看
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
...
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
...
break;
case MotionEvent.ACTION_DOWN:
...
break;
case MotionEvent.ACTION_CANCEL:
...
case MotionEvent.ACTION_MOVE:
...
break;
}
return true;
}
return false;
}
View的onTouchEvent()比较好理解,如果View设置为可点击或设置OnLongClickListener,OnClickListenerView就消费了事件。switch里的代码是具体处理长按监听和点击事件等事情。
GroupView的dispatchTouchEvent()就比较复杂了,下面通过关键源码来看看GroupView是如何分发消费事件的。
public boolean dispatchTouchEvent(MotionEvent ev) {
...
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
if (actionMasked == MotionEvent.ACTION_DOWN) {
//关注点一:接收到ACTION_DOWN事件时,将mFirstTouchTarget设置为 null
cancelAndClearTouchTargets(ev);
resetTouchState();
}
final boolean intercepted;
//关注点二:当处理ACTION_DOWN或mFirstTouchTarget不为空是走拦截流程
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget不为空是走拦截流程 != null) {
/*关注点三:是否不允许拦截事件,当child View调用
parent.requestDisallowInterceptTouchEvent()时,disallowIntercept = true*/
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 {
intercepted = true;
}
if (intercepted || mFirstTouchTarget != null) {
ev.setTargetAccessibilityFocus(false);
}
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {
...
/*关注点四:只有当事件为ACTION_DOWN或者为多点触控的ACTION_POINTER_DOWN或者鼠标悬浮事件时,
才会去在child view中寻找消费事件的view*/
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex();
//识别手指的标志位
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
/*关注点五:先根据Z轴方法z坐标值大小排序,(Z轴是三维坐标系里的Z方向,cardView设置阴影是就是设置Z轴的值),
再根据child view的绘制顺序排序*/
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
...
//关注点六:如果child view 不可见,并且事件不是发生在child view的范围内直接跳出查找下一个child view
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
//关注点七:如果找到处理这一事件的child view,给mFirstTouchTarget赋值。
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
// childIndex points into presorted list, find original index
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
//关注点八:给mFirstTouchTarget赋值
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
...
}
if (preorderedList != null) preorderedList.clear();
}
if (newTouchTarget == null && mFirstTouchTarget != null) {
// Did not find a child to receive the event.
// Assign the pointer to the least recently added target.
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
// 关注点九:如果没有找到child view去消费事件,将事件分发给自己,判断是否消费事件
if (mFirstTouchTarget == null) {
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
//遍历mFirstTouchTarget这个链表
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
/* 关注点十:如果child view消耗事件,判断是否拦截事件,如果拦截事件则child view 将不会收到这次事件,而是收到
ACTION_CANCEL事件,而mFirstTouchTarget最终将会赋值为null;如果不拦截事件,则按正常流程将事件分发给child view */
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
/*关注点十一:当接受到的事件时ACTION_UP,ACTION_CANCEL事件时,将
mFirstTouchTarget设置为null,重置FLAG_DISALLOW_INTERCEPT位*/
if (canceled
|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
resetTouchState();
} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
final int actionIndex = ev.getActionIndex();
final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
removePointersFromTouchTargets(idBitsToRemove);
}
}
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
return handled;
}
- 重点关注下mFirstTouchTarget这个对象,mFirstTouchTarget是一个链表结构的对象,正常情况下这个链表只有一个元素,当GroupView允许处理多点触控事件时,并且多点触控发生时mFirstTouchTarget可能会有多个元素。多点触控事件的分发消费流程与单点触控的流程相似,下面的说明都是以单点触控为例说明。
- ‘关注点五’注释的代码说明了ACTION_DOWN事件是按照child view绘制顺序来分发事件的,先绘制的child view会优先收到ACTION_DOWN事件。
- ‘关注点四’到‘关注点九’之间的代码就是ACTION_DOWN事件的分发消费过程。如果child view消费了ACTION_DOWN事件,在‘关注点八’的代码就给mFirstTouchTarget赋值,否则mFirstTouchTarget为null。
- ‘关注点九’说明如果child view没有消费ACTION_DOWN事件,其是绝对不会接受到后续的ACTION_MOVE,ACTION_UP等事件,换句话说view消费事件指的是是否消费ACTION_DOWN事件。至于消费了ACTION_DOWN事件之后,能否接受到后续的事件,就要看paren view是否拦截事件了。
- ‘关注点十’代码是mFirstTouchTarget的遍历过程,如果parentView拦截了事件,则child view会收到ACITON_CANCEL事件,同时mFirstTouchTarget会通过链表的遍历会最终赋值为null,这回导致后续的事件不会分发给child view而是执行‘关注点九’的代码。
下面是dispatchTransformedTouchEvent()的代码分析。
//当事件由GroupView自己处理则child = null
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
final int oldAction = event.getAction();
//当GroupView拦截事件时cancel = true
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
//GroupView自己处理事件。 GroupView继承制View,super.dispatchTouchEvent就是调用View.dispatchTouchEvent
handled = super.dispatchTouchEvent(event);
} else {
//将事件分发给child view处理
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
// 获取此次事件的所有手指标识位|运算值{@link MotionEvent#getPointerIdBits()}
final int oldPointerIdBits = event.getPointerIdBits();
final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
//如果此次事件不合法,不处理
if (newPointerIdBits == 0) {
return false;
}
final MotionEvent transformedEvent;
//单点触控处理
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
handled = child.dispatchTouchEvent(event);
event.offsetLocation(-offsetX, -offsetY);
}
return handled;
}
transformedEvent = MotionEvent.obtain(event);
} else {
//多点触控处理
transformedEvent = event.split(newPointerIdBits);
}
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
handled = child.dispatchTouchEvent(transformedEvent);
}
transformedEvent.recycle();
return handled;
}
事件从activity到child view的完整分发消费图:
