Android 事件分发事件分发源码分析 ( ViewGroup 事件传递机制 六 )

Posted 韩曙亮

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 事件分发事件分发源码分析 ( ViewGroup 事件传递机制 六 )相关的知识,希望对你有一定的参考价值。

android 事件分发 系列文章目录


【Android 事件分发】事件分发源码分析 ( 驱动层通过中断传递事件 | WindowManagerService 向 View 层传递事件 )
【Android 事件分发】事件分发源码分析 ( Activity 中各层级的事件传递 | Activity -> PhoneWindow -> DecorView -> ViewGroup )
【Android 事件分发】事件分发源码分析 ( ViewGroup 事件传递机制 一 )
【Android 事件分发】事件分发源码分析 ( ViewGroup 事件传递机制 二 )
【Android 事件分发】事件分发源码分析 ( ViewGroup 事件传递机制 三 )
【Android 事件分发】事件分发源码分析 ( ViewGroup 事件传递机制 四 | View 事件传递机制 )
【Android 事件分发】事件分发源码分析 ( ViewGroup 事件传递机制 五 )
【Android 事件分发】事件分发源码分析 ( ViewGroup 事件传递机制 六 )






一、按下触摸事件记录



在上一篇博客 【Android 事件分发】事件分发源码分析 ( ViewGroup 事件传递机制 五 ) 中 , 着重分析了 ViewGroup 事件分发中 , 触摸事件没有被消费 , 或被父容器拦截的情况 ;

这里再分析下触摸事件被消费之后的 , 触摸事件记录过程 ;

触摸事件如果成功被消费 , 则 dispatchTransformedTouchEvent 方法返回 true ;

对应的会调用 addTouchTarget 方法 , 创建 TouchTarget 对象 , 赋值给 newTouchTarget 成员变量 ;

newTouchTarget = addTouchTarget(child, idBitsToAssign)

TouchTarget 代表了个触摸事件 , 每个 TouchTarget 中都封装了消费该触摸事件的 View 组件 ;

        // The touched child view.
        // 当前 View 对象 
        public View child;

TouchTarget 经过优化后 , 以链表形式存储 , 每个 TouchTarget 都定义了一个 next 成员变量 , 指向下一个 TouchTarget 被消费的触摸事件 ;

        // The next target in the target list.
        // 链表操作 , 该引用指向下一个触摸事件 
        public TouchTarget next;

相关源码 :

@UiThread
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
		...
                            // 正式开始分发触摸事件
                            // 处理以下两种情况 : 
                            // ① 情况一 : 子控件触摸事件返回 true 
                            // ② 情况二 : 子控件触摸事件返回 false 
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                // Child wants to receive touch within its bounds.
                                // 如果返回值为 true , 说明该事件已经被消费了 
                                // 此时记录这个已经被消费的事件 
                                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();
                                // 记录消费事件 
                                // 添加触摸目标 
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }

                            // The accessibility focus didn't handle the event, so clear
                            // the flag and do a normal dispatch to all children.
                            ev.setTargetAccessibilityFocus(false);
                        }
                        if (preorderedList != null) preorderedList.clear();
                    }

					// 如果上述事件分发方法 dispatchTransformedTouchEvent 返回 true 
					// 就会创建 newTouchTarget 值 , 该值不会为空 , 同时 mFirstTouchTarget 不为空
					// 如果上述事件分发方法 dispatchTransformedTouchEvent 返回 false 
					// 此时 newTouchTarget 值 , 就会为空 , 同时 mFirstTouchTarget 为空 
                    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;
                    }
                }
            }

			// 如果事件被消费 , 事件分发方法 dispatchTransformedTouchEvent 返回 true 
			// 就会创建 newTouchTarget 值 , 该值不会为空 , 同时 mFirstTouchTarget 不为空
			// 反之
			// 如果上述事件分发方法 dispatchTransformedTouchEvent 返回 false 
			// 此时 newTouchTarget 值 , 就会为空 , 同时 mFirstTouchTarget 为空 
			// 
			// 还有一个逻辑就是 , 如果该事件被父容器拦截 , mFirstTouchTarget 也是 null 值
			// 调用 dispatchTransformedTouchEvent , 但是传入的子组件时 null 
			// 在 dispatchTransformedTouchEvent 方法中触发调用 if (child == null) 分支的 
			// handled = super.dispatchTouchEvent(event) 方法 , 调用父类的事件分发方法 
            // Dispatch to touch targets.
            if (mFirstTouchTarget == null) {
            	// 事件没有被消费的分支 
                // No touch targets so treat this as an ordinary view.
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
            	// 事件被消费的分支 , 事件消费成功 , 会走这个分支 
                // Dispatch to touch targets, excluding the new touch target if we already
                // dispatched to it.  Cancel touch targets if necessary.
                TouchTarget predecessor = null;

				// 
                TouchTarget target = mFirstTouchTarget;
                while (target != null) {
                	// 链表式操作 
                    final TouchTarget next = target.next;
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                    } else {
                        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;
                }
            }

            // Update list of touch targets for pointer up or cancel, if needed.
            // 移动取消相关逻辑 
            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;
    }

    /**
     * Adds a touch target for specified child to the beginning of the list.
     * Assumes the target child is not already present.
     */
    private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
        final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
        target.next = mFirstTouchTarget;
        mFirstTouchTarget = target;
        return target;
    }
}




二、完整的触摸事件处理机制



完整触摸事件 :

一个完整的触摸动作 , 由 1 1 1 次 按下触摸事件 , 若干次 移动触摸事件 , 1 1 1 次 抬起触摸事件 组成 ,

1 1 1 个触摸动作只有 1 1 1 次按下操作 , 并且是整个触摸动作的起始 触摸事件 ;

一个完整的动作 , 只有第一次按下 , 才执行 子组件的 排序 , 遍历 , 事件分发 等操作 ; 第一次按下后 , 手指按着移动 , 属于第2次以及之后的第n次动作 , 不再走该分支 , 直接执行该分支后面的代码 ;

这里在第 1 1 1 次按下时 , 创建触摸事件记录 TouchTarget ; 假如当前动作时按下以后的移动/抬起动作 , 则跳过上面的分支 , 直接执行后面的代码逻辑 , 按下之后 mFirstTouchTarget 肯定不为空 ;


TouchTarget 事件记录封装 :

TouchTarget 对象对应着一个完整的动作 , 该动作包含 1 个按下事件 , 若干 移动 事件 , 1 个抬起事件 ;

第一次按下 , 负责构建 TouchTarget 链表 , 将消费事件的 View 组件封装到 TouchTarget 中 ;

然后的移动/抬起操作 , 不再重复的创建 TouchTarget 对象了 ; 直接使用第一次按下的 TouchTarget 对象作为当前动作的标识 , 直接向该 TouchTarget 对象中的 View 组件分发事件 ;

这也是我们按下按钮时 , 即使将手指按着移出边界 , 按钮也处于按下状态 ;


相关源码 :

public abstract class ViewGroup extends View implements ViewParent, ViewManager {

    // First touch target in the linked list of touch targets.
    private TouchTarget mFirstTouchTarget;

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
				// 判断是否是按下操作 
				// 一个完整的动作 , 只有第一次按下 , 才执行下面的逻辑 
				// 第一次按下后 , 手指按着移动 , 属于第2次以及之后的第n次动作 , 不再走该分支 
				// 直接执行该分支后面的代码 
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                    // 获取触摸索引值 
                    final int actionIndex = ev.getActionIndex(); // always 0 for down
                    final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                            : TouchTarget.ALL_POINTER_IDS;

					// 计算 ViewGroup 父容器下面有多少个子 View 组件 ; 
                    final int childrenCount = mChildrenCount;
                    // TouchTarget newTouchTarget = null; 在上面声明为空 , 此处肯定为 null ; 
                    // childrenCount 子组件个数不为 0 
                    // 如果子组件个数为 0 , 则不走下一段代码 , 如果子组件个数大于 0 , 则执行下一段代码 ; 
                    // 说明下面的代码块中处理的是 ViewGroup 中子组件的事件分发功能 ; 
                    if (newTouchTarget == null && childrenCount != 0) {
                    	// 获取单个手指的 x,y 坐标 
                        final float x = ev.getX(actionIndex);
                        final float y = ev.getY(actionIndex);
                        // Find a child that can receive the event.
                        // Scan children from front to back.
                        // 子组件排序 , 按照 Z 轴排列的层级 , 从上到下进行排序 , 
                        // 控件会相互重叠 , Z 轴的排列次序上 , 
                        // 顶层的组件优先获取到触摸事件 
                        final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                        final View[] children = mChildren;
	
						// 倒序遍历 按照 Z 轴的上下顺序 , 排列好的组件 
						// 先遍历的 Z 轴方向上 , 放在最上面的组件 , 也就是顶层组件 
                        for (int i = childrenCount - 1; i >= 0; i--) {
                        	// 获取索引
                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
                            // 获取索引对应组件 
                            final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, childIndex);

                            // If there is a view that has accessibility focus we want it
                            // to get the event first and if not handled we will perform a
                            // normal dispatch. We may do a double iteration but this is
                            // safer given the timeframe.
                            // 无障碍 辅助功能 
                            if (childWithAccessibilityFocus != null) {
                                if (childWithAccessibilityFocus != child) {
                                    continue;
                                }
                                childWithAccessibilityFocus = null;
                                i = childrenCount - 1;
                            }

							// X 控件范围 A , 如果手指按在 B 范围 , 不会触发 X 控件的事件 
							// 判定当前的组件是否可见 , 是否处于动画过程中 
							// ① canViewReceivePointerEvents 判定组件是否可见 , 会否处于动画 
							// ② isTransformedTouchPointInView 判定手指是否在控件上面 ; 
							// 上述两种情况 , 不触发事件 
                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                ev.setTargetAccessibilityFocus(false);
                                // 不触发事件 
                                continue;
                            }
							
							// 截止到此处 , 可以获取子组件进行操作   

							// 提取当前的子组件 
							// 第一次执行 getTouchTarget 代码时 , 是没有 mFirstTouchTarget 的
							// 此时第一次返回 null 
                            newTouchTarget = getTouchTarget(child);
                            // 该分支操作第一次不执行 
                            if (newTouchTarget != null) {
                                // Child is already receiving touch within its bounds.
                                // Give it the new pointer in addition to the ones it is handling.
                                newTouchTarget.pointerIdBits |= idBitsToAssign;
                                break;
                            }

                            resetCancelNextUpFlag(child);
                            // 正式开始分发触摸事件
                            // 处理以下两种情况 : 
                            // ① 情况一 : 子控件触摸事件返回 true 
                            // ② 情况二 : 子控件触摸事件返回 false 
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                // Child wants to receive touch within its bounds.
                                // 如果返回值为 true , 说明该事件已经被消费了 
                                // 此时记录这个已经被消费的事件 
                                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();
                                // 记录消费事件 
                                // 添加触摸目标 
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }

                            // The accessibility focus didn't handle the event, so clear
                            // the flag and do a normal dispatch to all children.
                            ev.setTargetAccessibilityFocus(false);
                        }
                        if (preorderedList != null) preorderedList.clear();
                    }


			// 上面的分支是只有第一次按下时才执行的 
			// 假如当前动作时按下以后的移动/抬起动作 
			// 则跳过上面的分支 , 直接执行后面的代码逻辑 
			// 按下之后 mFirstTouchTarget 肯定不为空 
		

			// 如果事件被消费 , 事件分发方法 dispatchTransformedTouchEvent 返回 true 
			// 就会创建 newTouchTarget 值 , 该值不会为空 , 同时 mFirstTouchTarget 不为空
			// 反之
			// 如果上述事件分发方法 dispatchTransformedTouchEvent 返回 false 
			// 此时 newTouchTarget 值 , 就会为空 , 同时 mFirstTouchTarget 为空 
			// 
			// 还有一个逻辑就是 , 如果该事件被父容器拦截 , mFirstTouchTarget 也是 null 值
			// 调用 dispatchTransformedTouchEvent , 但是传入的子组件时 null 
			// 在 dispatchTransformedTouchEvent 方法中触发调用 if (child == null) 分支的 
			// handled = super.dispatchTouchEvent(event) 方法 , 调用父类的事件分发方法 
            // Dispatch to touch targets.
            if (mFirstTouchTarget == null) {
            	// 事件没有被消费的分支 
                // No touch targets so treat this as an ordinary view.
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
				// TouchTarget 对象对应着一个完整的动作 , 该动作包含 1 个按下事件 , 若干 移动 事件 , 1 个抬起事件 ; 

            	// 第一次按下 , 负责构建 TouchTarget 链表 , 将消费事件的 View 组件封装到 TouchTarget  中
            	// 然后的移动/抬起操作 , 不再重复的创建 TouchTarget 对象了 
            	// 直接使用第一次按下的 TouchTarget 对象作为当前动作的标识 
            	// 直接向该 TouchTarget 对象中的 View 组件分发事件 
            	// 这也是我们按下按钮时 , 即使将手指按着移出边界 , 按钮也处于按下状态 ; 



            	// 事件被消费的分支 , 事件消费成功 , 会走这个分支 
                // Dispatch to touch targets, excluding the new touch target if we already
                // dispatched to it.  Cancel touch targets if necessary.
                TouchTarget predecessor = null;

				// 将当前所有的消费的事件以及消费的 View 组件做成了一个链表 
                TouchTarget target = mFirstTouchTarget;
                while (target != null) {
                	// 链表式操作 , 检索是哪个组件 , 然后开始分发 
                    final TouchTarget next = target.next;
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                    } else {
                    
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;

						// 找到了 View , 开始分发触摸事件 
                        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;
                }
            }
	}
}




三、ViewGroup | dispatchTouchEvent 方法返回



【Android 事件分发】事件分发源码分析 ( Activity 中各层级的事件传递 | Activity -> PhoneWindow -> DecorView -> ViewGroup ) 博客中分析了从 Activity -> PhoneWindow -> DecorView -> ViewGroup 的调用链 ;


Activity | dispatchTouchEvent 方法调用 PhoneWindow | superDispatchTouchEvent 方法 ,
PhoneWindow | superDispatchTouchEvent 方法调用 DecorView | superDispatchTouchEvent 方法 ,
DecorView | superDispatchTouchEvent 方法调用 ViewGroup | dispatchTouchEvent 方法 ;


ViewGroup 的 dispatchTouchEvent 事件分发方法执行完毕后 ,
先返回到 DecorView | superDispatchTouchEvent 方法 ,
然后返回到 PhoneWindow | superDispatchTouchEvent 方法 .
最终返回到 Activity 的 dispatchTouchEvent ;


如果在 Activity 的 dispatchTouchEvent 方法中 , PhoneWindow 的 superDispatchTouchEvent 方法的返回值是 true , 即 ViewGroup 的 dispatchTouchEvent 方法返回 true ;

则直接返回 , 不再向后执行 ;

		// getWindow() 获取的是 PhoneWindow 窗口 
		// 调用 PhoneWindow 的 superDispatchTouchEvent ; 
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }

相关源码 :

public class Activity extends ContextThemeWrapper
        implements LayoutInflater.Factory2,
        Window.Callback, KeyEvent.Callback,
        OnCreateContextMenuListener, ComponentCallbacks2,
        Window.OnWindowDismissedCallback, WindowControllerCallback,
        AutofillManager.AutofillClient {
    /**
     * Called to process touch screen events.  You can override this to
     * intercept all touch screen events before they are dispatched to the
     * window.  Be sure to call this implementation for touch screen events
     * that should be handled normally.
     *
     * @param ev The touch screen event.
     *
     * @return boolean Return true if this event was consumed.
     */
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        	// 判定是否是第一次按下 
        	// 该方法与事件传递机制流程无关 
        	// 提供给用户按下时可以执行的业务逻辑 
            onUserInteraction();
        }
		
		// getWindow() 获取的是 PhoneWindow 窗口 
		// 调用 PhoneWindow 的 superDispatchTouchEvent ; 
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }
    
    public void Android 事件分发事件分发源码分析 ( ViewGroup 事件传递机制 二 )

Android 事件分发事件分发源码分析 ( ViewGroup 事件传递机制 一 )

Android 事件分发事件分发源码分析 ( ViewGroup 事件传递机制 七 )

Android 事件分发事件分发源码分析 ( ViewGroup 事件传递机制 六 )

Android 事件分发ItemTouchHelper 事件分发源码分析 ( 绑定 RecyclerView )

Android 事件分发ItemTouchHelper 源码分析 ( OnItemTouchListener 事件监听器源码分析 )