目 录CONTENT

文章目录

终于懂了系列之Android事件分发机制(全新版本)

小王同学
2024-07-11 / 0 评论 / 1 点赞 / 138 阅读 / 0 字

手把手教你跟踪源码!本篇文章内容较长,建议收藏小王同学网站浏览。

事件分发机制在面试中的重要性

在 Android 开发面试中,事件分发机制是 Android 系统的核心部分,掌握这部分内容是衡量一个开发者是否具备扎实基础的重要标准。所以面试官为什么每次都问???为什么你在开发中滑动冲突处理不了??为什么自己写的自定义View的点击事件有很多BUG?说明你还是没掌握,今天我们就再次来讲解一下Android的事件分发机制!

事件分发的本质是什么?

Android 事件分发的本质是系统如何将触摸事件传递给合适的视图(View)进行处理。

即 事件传递的过程 = 触摸事件的分发过程。

事件在哪些对象之间进行传递?

答:Activity、ViewGroup、ViewAndroidUI界面由ActivityViewGroupView 及其派生类组成

Activity、ViewGroup、View 的区别

特性

Activity

ViewGroup

View

定义

应用程序组件,代表单个屏幕

视图容器,可以包含其他 View 或 ViewGroup

UI 元素的基类,用于绘制和事件处理

生命周期

有完整的生命周期管理方法

无生命周期管理方法

无生命周期管理方法

事件分发机制

通过 Window 传递事件给根视图

分发和拦截事件,传递给子视图

处理具体的触摸事件

典型方法

onCreate(), onStart(), onResume()

addView(), removeView(), dispatchTouchEvent()

onDraw(), onTouchEvent(), dispatchTouchEvent()

事件处理

不直接处理视图事件,通过根视图传递

可以拦截子视图事件

直接处理用户触摸事件

事件分发机制中的关系

类别

事件分发方法

拦截方法

事件处理方法

Activity

dispatchTouchEvent()

ViewGroup

dispatchTouchEvent()

onInterceptTouchEvent()

onTouchEvent()

View

dispatchTouchEvent()

onTouchEvent()

1个点击事件发生后,事件先传到Activity、再传到ViewGroup、最终再传到 View

好了,废话不多说,我们开始研究源码。

Activity的事件分发

public boolean dispatchTouchEvent(MotionEvent ev) {
	// 如果触摸事件是ACTION_DOWN
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
	 // 开始分发事件的起点!!将触摸事件分发给当前窗口的根视图,判断是否已处理事件
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
 	// 如果根视图没有处理事件,调用Activity的onTouchEvent()方法处理
    return onTouchEvent(ev);
}

为什么从Activity 作为事件传递的入口

我们先要知道一个点,就是系统把点击事件传递到Activity就是通过调用Activity的dispatchTouchEvent方法,与其说是传递,也就是调用该方法

Activity 是应用界面管理的核心,它持有 Window 对象。Window 包含了整个视图层次结构的根视图。因此,Activity 自然成为了事件传递的入口。

  • onUserInteraction()

    • 当接收到 ACTION_DOWN 事件时,调用 onUserInteraction() 方法,通知 Activity 有用户交互发生。

  • superDispatchTouchEvent(ev)

    • 调用 getWindow().superDispatchTouchEvent(ev) 将事件传递给 Window 对象的根视图。Window 会将事件传递给视图层次结构中的各个视图。

  • onTouchEvent(ev)

    • 如果事件没有被视图层次结构中的任何视图处理,调用 Activity 自身的 onTouchEvent(ev) 方法进行处理。这通常是作为最终的兜底处理。

我们点进去superDispatchTouchEvent方法,发现是一个抽象方法

//Window类,是抽象类
public abstract boolean superDispatchTouchEvent(MotionEvent event);

按住control点击这个方法,卧槽,根本点不动,想知道谁实现这个方法,此时怎么办呢?别急,我们到Window顶部看一下

注释清晰的写着,翻译过来就是:其唯一实现类 :android.view.PhoneWindow类,好了,那我们就去找PhoneWindow

哈哈哈一下就找到了。这时候内心喊一句:哪里跑~我们继续点击mDecor.superDispatchTouchEvent

继续追进去!!

哈哈终于找到了你了吧,到这里豁然开朗,居然调用到了ViewGroup的分发事件方法,其实这就完成了把点击事件传递到了ViewGroup的过程分发到ViewGroup去了,此时我们点进来看下,ViewGroup的dispatchTouchEvent代码很长很长,别急,我们到这里先缓一缓,我们先回头看看我们走过的路,总结一下再继续。

那回归到Activity的dispatchTouchEvent方法:

//Activity的dispatchTouchEvent方法
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
    //这个判断会耗时,很久才返回
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    return onTouchEvent(ev);
}

也就是说,当我们一点击屏幕,就触发dispatchTouchEvent,然后第一个判断我们不需要看,看第二个if,getWindow().superDispatchTouchEvent(ev)

这一句代码就开始往下分发,如果getWindow().superDispatchTouchEvent(ev) 返回true,那么说明被ViewGroup或者View消费掉了,如果返回false,那么就没有被ViewGroup或者View消费,接着还是调用Activity的onTouchEvent方法。

总结来说就是如果Activity所包含的视图拦截或者消费了该触摸事件的话,就不会再执行Activity的onTouchEvent();
如果Activity所包含的视图没有拦截或者消费该触摸事件的话,则会执行Activity的onTouchEvent()。

Activity中的onTouchEvent是Activity自身对触摸事件的处理。

public boolean onTouchEvent(MotionEvent event) {
        if (mWindow.shouldCloseOnTouch(this, event)) {
            finish();
            return true;
        }

        return false;
    }

此时,如果底层的View没有消费事件,最终会走到Activity的onTouchEvent方法,我们看一下onTouchEvent 方法,它调用了mWindow.shouldCloseOnTouch(this, event),我们再点进去看看。

@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
    // 判断事件是否为 ACTION_UP 并且在边界之外,或者事件为 ACTION_OUTSIDE
    final boolean isOutside =
            event.getAction() == MotionEvent.ACTION_UP && isOutOfBounds(context, event)
            || event.getAction() == MotionEvent.ACTION_OUTSIDE;
    
    // 如果允许点击外部关闭窗口,并且装饰视图不为空,并且事件在外部
    if (mCloseOnTouchOutside && peekDecorView() != null && isOutside) {
        return true; // 返回 true,表示应该关闭
    }
    
    return false; // 否则返回 false,表示不关闭
}

代码比较简单。它会先调用mWindow.shouldCloseOnTouch(),如果shouldCloseOnTouch()返回true,则意味着该触摸事件会触发"结束Activity"的动作。就调用finish()来结束Activity,并返回true,表示Activity消费了这个触摸事件。否则的话,就返回false。

最后我们总结一下:

1. 开始 (用户触碰屏幕)

• 用户在屏幕上触摸,触摸事件被触发。

2. Activity.dispatchTouchEvent()

• 触摸事件首先传递到当前 Activity 的 dispatchTouchEvent() 方法。

3. getWindow().superDispatchTouchEvent(ev)

• 在 Activity.dispatchTouchEvent() 方法内部,调用 Window 的 superDispatchTouchEvent() 方法。

4. PhoneWindow.superDispatchTouchEvent()

• 在 Window 的 superDispatchTouchEvent() 方法内部,调用 PhoneWindow 的 superDispatchTouchEvent() 方法。

5. mDecor.superDispatchTrackballEvent(event)

• 在 PhoneWindow.superDispatchTouchEvent() 方法内部,调用 DecorView 的 superDispatchTrackballEvent() 方法。

6. ViewGroup.dispatchTouchEvent()

• 在 DecorView.superDispatchTrackballEvent() 方法内部,调用 ViewGroup 的 dispatchTouchEvent() 方法。

7. 判断 ViewGroup.dispatchTouchEvent() 返回值

• 如果 ViewGroup.dispatchTouchEvent() 返回 true:

• 表示事件被消费,Activity.dispatchTouchEvent() 返回 true,流程结束。

• 如果 ViewGroup.dispatchTouchEvent() 返回 false:

• 表示事件未被消费,继续向下传递。

8. Activity.onTouchEvent()

• 调用 Activity 的 onTouchEvent() 方法处理未被消费的事件。

9. mWindow.shouldCloseOnTouch(this, event)

• 调用 Window 的 shouldCloseOnTouch() 方法,根据触摸事件决定是否关闭窗口。

10. 结束

• 事件处理完成,整个事件分发过程结束。

那么,ViewGroupdispatchTouchEvent()什么时候返回true / false?请继续往下看ViewGroup事件的分发机制

ViewGroup的事件分发

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
			······代码省略······
            // 判断是否需要拦截事件
        final boolean intercepted;
        if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            intercepted = !disallowIntercept && onInterceptTouchEvent(ev);
            ev.setAction(action); // 恢复action,以防它被改变
        } 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 && ev.getSource() != InputDevice.SOURCE_MOUSE;
        TouchTarget newTouchTarget = null;
        boolean alreadyDispatchedToNewTouchTarget = false;

        if (!canceled && !intercepted) {
            // 分配目标子视图
            if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)) {
                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);
                    final View[] children = mChildren;

                    // 找到可以接收事件的子视图
                    for (int i = childrenCount - 1; i >= 0; i--) {
                        final View child = children[i];
					//2. 判断当前遍历的子View能否接收到事件
                        if (!child.canReceivePointerEvents() || !isTransformedTouchPointInView(x, y, child, null)) {
                            ev.setTargetAccessibilityFocus(false);
                            continue;
                        }

                        newTouchTarget = getTouchTarget(child);
                        if (newTouchTarget != null) {
                            newTouchTarget.pointerIdBits |= idBitsToAssign;
                            break;
                        }

                        resetCancelNextUpFlag(child);
					//就是这里!!!!
					//调用 dispatchTransformedTouchEvent 方法,将触摸事件分发给特定的子视图。
                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                            mLastTouchDownTime = ev.getDownTime();
                            mLastTouchDownIndex = i;
                            mLastTouchDownX = ev.getX();
                            mLastTouchDownY = ev.getY();
                            newTouchTarget = addTouchTarget(child, idBitsToAssign);
                            alreadyDispatchedToNewTouchTarget = true;
                            break;
                        }
                    }
                }

                // 没有找到新的触摸目标,分配给现有触摸目标
                if (newTouchTarget == null && mFirstTouchTarget != null) {
                    newTouchTarget = mFirstTouchTarget;
                    while (newTouchTarget.next != null) {
                        newTouchTarget = newTouchTarget.next;
                    }
                    newTouchTarget.pointerIdBits |= idBitsToAssign;
                }
            }
        }

        // 事件分发
		// 没有子视图被标记为当前触摸事件的目标
        if (mFirstTouchTarget == null) {
		//调用 dispatchTransformedTouchEvent 方法,将触摸事件分发给当前 ViewGroup 自己(而不是子视图)。
            handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);
        } else {
            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;
            }
        }

    }

    return handled;
}

这段代码实现了复杂的事件分发逻辑,确保触摸事件能够在 ViewGroup 和其子视图之间正确地分发和处理。核心逻辑包括:

1. 判断是否拦截事件。

2. 分发事件给 ViewGroup 或其子视图。

分发事件给 ViewGroup 或其子视图的是调用的dispatchTransformedTouchEvent方法,我们下面看下这个方法

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
final boolean handled;

// 获取事件的原始动作类型
final int oldAction = event.getAction();

// 如果当前事件是取消事件或者动作类型是 ACTION_CANCEL
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
    // 将事件的动作类型设置为 ACTION_CANCEL
    event.setAction(MotionEvent.ACTION_CANCEL);

    // 如果 child 为 null
    if (child == null) {
        // 调用父类的 dispatchTouchEvent 方法分发事件
        handled = super.dispatchTouchEvent(event);
    } else {
        // 这里这里!!!
		// 调用子View 视图的 dispatchTouchEvent 方法分发事件
        handled = child.dispatchTouchEvent(event);
    }

    // 将事件的动作类型恢复为原始动作类型
    event.setAction(oldAction);

    // 返回事件是否被处理
    return handled;
}

// 省略的代码
}

调用子View的dispatchTouchEvent后是有返回值的

若该控件可点击,那么点击时dispatchTouchEvent的返回值必定是true,因此会导致条件判断成立

于是给ViewGroup的dispatchTouchEvent()直接返回了true,即直接跳出

即该子View把ViewGroup的点击事件消费掉了

下面我们画图总结一下ViewGroup的流程:

开始 (用户触碰屏幕)

• 用户在屏幕上触摸,触摸事件被触发。

2. ViewGroup.dispatchTouchEvent()

• 触摸事件首先传递到 ViewGroup 的 dispatchTouchEvent() 方法。

3. ViewGroup.onInterceptTouchEvent()

• 在 dispatchTouchEvent() 方法内部,调用 ViewGroup 的 onInterceptTouchEvent() 方法判断是否拦截事件。

4. 判断是否拦截事件

• 根据 onInterceptTouchEvent() 的返回值进行判断:

• false: 不拦截事件,允许事件继续向子 View 传递。

• true: 拦截事件,不允许事件继续向子 View 传递。

5. 事件未被拦截 (false 分支)

寻找可处理事件的子 View

• 遍历子 View,寻找可以处理当前触摸事件的子 View。

找到处理事件的子 View

• 调用找到的子 View 的 dispatchTouchEvent() 方法,将事件传递给该子 View。

• 事件传递给子 View 后,由子 View 处理。

6. 事件被拦截 (true 分支)

不允许事件继续向子 View 传递

• 调用当前 ViewGroup 的 dispatchTouchEvent() 方法来处理事件,而不是向子 View 传递。

自己处理该事件

• 当前 ViewGroup 自己处理事件,处理完成后结束。

7. 结束

• 事件处理完成,整个事件分发过程结束。

View的事件分发

从上面ViewGroup事件分发机制知道,View事件分发机制从dispatchTouchEvent()开始

源码分析

public boolean dispatchTouchEvent(MotionEvent event) {
    ······省略的代码·······
    // 安全性过滤触摸事件
    if (onFilterTouchEventForSecurity(event)) {
        // 如果视图已启用并且处理滚动条拖动事件
        if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
            result = true;
        }



	// (mViewFlags & ENABLED_MASK) == ENABLED:这是为了检查视图是否处于启用状态。
    // mViewFlags:通常包含视图的各种标志位,包括启用状态等。
    // ENABLED_MASK:这是一个掩码,用于提取启用状态位。
    // ENABLED:表示视图处于启用状态的常量值。
    // (mViewFlags & ENABLED_MASK) == ENABLED:这个条件用于判断视图是否处于启用状态。
    // handleScrollBarDragging(event):这是一个方法,检查和处理滚动条拖动事件。
    // 如果 handleScrollBarDragging(event) 返回 true,则表示触摸事件被处理为滚动条拖动事件。
    // result = true;:如果上述条件都满足,表示事件已经被处理,设置 result 为 true。








        //noinspection SimplifiableIfStatement
        ListenerInfo li = mListenerInfo;
        // 如果有触摸监听器并且视图已启用,处理触摸事件
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }



	//  ListenerInfo li = mListenerInfo;:获取视图的监听器信息。
    //  mListenerInfo:包含视图的监听器信息,包括触摸监听器等。
    // 	li:局部变量,指向视图的监听器信息。
    // 	li != null && li.mOnTouchListener != null:检查是否有触摸监听器。
    // 	li != null:确认视图有监听器信息。
    //  li.mOnTouchListener != null:确认监听器信息中有触摸监听器。
    // 	(mViewFlags & ENABLED_MASK) == ENABLED:再次检查视图是否处于启用状态。
    //  li.mOnTouchListener.onTouch(this, event):调用触摸监听器的 onTouch 方法处理事件。
    //  如果 onTouch 返回 true,表示事件已被处理。
    //  result = true;:如果上述条件都满足,表示事件已经被触摸监听器处理,设置 result 为 true。





        // 如果没有被触摸监听器处理,则调用 onTouchEvent 处理
        if (!result && onTouchEvent(event)) {
            result = true;
        }


	// !result:检查前面是否已经处理了事件。如果 result 为 false,表示事件还没有被处理。
    // onTouchEvent(event):调用视图的 onTouchEvent 方法处理事件。
    // 如果 onTouchEvent 返回 true,表示事件已被处理。
    // result = true;:如果上述条件都满足,表示事件已经被 onTouchEvent 处理,设置 result 为 true。




    }
    ······省略的代码·······

    return result;
}

public boolean onTouchEvent(MotionEvent event) {
    final float x = event.getX(); // 获取触摸点的X坐标
    final float y = event.getY(); // 获取触摸点的Y坐标
    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 ((viewFlags & ENABLED_MASK) == DISABLED
            && (mPrivateFlags4 & PFLAG4_ALLOW_CLICK_WHEN_DISABLED) == 0) {
        // 如果触摸事件是ACTION_UP并且视图当前是按下状态,取消按下状态
        if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
            setPressed(false);
        }
        // 清除手指按下标志位
        mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
        
        // 返回是否可点击
        return clickable;
    }

    // 如果有TouchDelegate,优先由TouchDelegate处理触摸事件
    if (mTouchDelegate != null) {
        if (mTouchDelegate.onTouchEvent(event)) {
            return true;
        }
    }

    // 如果视图是可点击或者有工具提示
    if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
        switch (action) {
            case MotionEvent.ACTION_UP:
                // 如果视图没有获取到焦点,执行点击操作
                if (!focusTaken) {
                    if (mPerformClick == null) {
                        mPerformClick = new PerformClick(); // 创建PerformClick对象
                    }
						// 尝试延迟执行点击操作
                    if (!post(mPerformClick)) { 
						******************
						* 直接执行点击操作  *
						******************
                        performClickInternal(); 
                    }
                }
                break;
            case MotionEvent.ACTION_DOWN:
				// 如果在滚动容器中
                if (isInScrollingContainer) { 
					// 设置预按下标志位
                    mPrivateFlags |= PFLAG_PREPRESSED; 
                    if (mPendingCheckForTap == null) {
						// 创建CheckForTap对象
                        mPendingCheckForTap = new CheckForTap(); 
                    }
					// 记录触摸点的X坐标
                    mPendingCheckForTap.x = event.getX(); 
					// 记录触摸点的Y坐标
                    mPendingCheckForTap.y = event.getY(); 
					// 延迟检查是否是点击操作
                    postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); 
                } else {
                    // 不在滚动容器中,立即显示按下反馈
                    setPressed(true, x, y); 
					// 检查长按
                    checkForLongClick(
                            ViewConfiguration.getLongPressTimeout(), 
                            x,
                            y,
                            TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
                }
                break;

            case MotionEvent.ACTION_CANCEL:
                if (clickable) {
                    setPressed(false); // 取消按下状态
                }
                removeTapCallback(); // 移除点击回调
                removeLongPressCallback(); // 移除长按回调
                mInContextButtonPress = false; // 重置上下文按钮按下状态
                mHasPerformedLongPress = false; // 重置长按已执行标志位
                mIgnoreNextUpEvent = false; // 重置忽略下次抬起事件标志位
                mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN; // 清除手指按下标志位
                break;

            case MotionEvent.ACTION_MOVE:
                // 判断触摸点是否还在视图范围内
                if (!pointInView(x, y, touchSlop)) {
                    // 触摸点移出视图范围
                    removeTapCallback(); // 移除点击回调
                    removeLongPressCallback(); // 移除长按回调
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                        setPressed(false); // 取消按下状态
                    }
                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN; // 清除手指按下标志位
                }

                // 检查是否是深按
                final boolean deepPress =
                        motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS;
                if (deepPress && hasPendingLongPressCallback()) {
                    // 立即处理长按操作
                    removeLongPressCallback(); // 移除长按回调
                    checkForLongClick(
                            0, // 立即发送
                            x,
                            y,
                            TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS);
                }

                break;
        }

        return true; // 触摸事件已处理
    }

    return false; // 触摸事件未处理
}

这个大家再熟悉不过了吧,我们在实现按钮的点击的时候就会实现这个回调方法,终于找到是哪里出发的了。只要通过setOnClickListener()为控件View注册1个点击事件,那么就会给mOnClickListener变量赋值(即不为空),则会往下回调onClick()返回true

1. 开始:当用户触摸屏幕时,这个过程开始。

2. View.dispatchTouchEvent():首先调用View类的dispatchTouchEvent()方法来分发触摸事件。

3.

如果开发者注册了onTouch方法:

• 如果开发者实现的onTouch方法返回false(表示没有消费该事件),则继续往下传递。

• 如果开发者实现的onTouch方法返回true,则停止事件的传播,并且不会触发onClick事件。

如果开发者没有注册onTouch方法:

• onTouchEvent():如果开发者没有注册onTouch()方法,那么这个方法会被调用。在这个方法中,开发者可以根据触摸事件的具体情况来决定是否消耗掉这个事件。

4.

• performClick():如果onTouchEvent()方法返回true,表示事件被消费了,那么系统会自动调用performClick()方法来触发点击事件。

• onClick():如果事件被消费,不再往下传递,那么就会触发onClick()方法。

• 结束:整个触摸事件处理流程结束。

onTouch,onTouchEvent,onClick优先级,关系(字节面试)

onTouch:指的是View设置的OnTouchListener接口的onTouch()方法

onTouchEvent:指的是事件分发中的重要方法(dispatchTouchEvent,onInterceptTouchEvent,onTouchEvent)

onClick:指的是View设置的OnClickListener接口的onClick()方法

这里需要特别注意的是,onTouch()的执行 先于 onClick()

public boolean dispatchTouchEvent(MotionEvent event) {
    boolean result = false;
    
    if (onFilterTouchEventForSecurity(event)) {
        //noinspection SimplifiableIfStatement
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }

        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }

    return result;
}

其中mOnTouchListener指的就是我们通过 setOnTouchListener()设置的接口,可以知道如果onTouch()返回true的话,事件就被消化,onTouchEvent()就不会收到事件了,所以onTouch比onTouchEvent优先级高,从中可以看出在onTouchEvent()中,调用了我们设置的OnClickListener()接口中的onClick(),所以onTouchEvent优先级高于onClick

优先级从高到低:onTouch>onTouchEvent>onClick

dispatchTouchEvent,onInterceptTouchEvent,onTouchEvent三者关系

下面,我用一段伪代码来阐述上述3个方法的关系 & 事件传递规则

// 点击事件产生后
// 步骤1:调用dispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent ev) {

    boolean consume = false; //代表 是否会消费事件

    // 步骤2:判断是否拦截事件
    if (onInterceptTouchEvent(ev)) {
      // a. 若拦截,则将该事件交给当前View进行处理
      // 即调用onTouchEvent()去处理点击事件
      consume = onTouchEvent (ev) ;

    } else {

      // b. 若不拦截,则将该事件传递到下层
      // 即 下层元素的dispatchTouchEvent()就会被调用,重复上述过程
      // 直到点击事件被最终处理为止
      consume = child.dispatchTouchEvent (ev) ;
    }

    // 步骤3:最终返回通知 该事件是否被消费(接收 & 处理)
    return consume;

}

总结

到现在为止,Activity中关于触摸事件的代码就全部分析完了。总结来说:

事件分发的顺序为:

  1. 事件产生

    • 当你在屏幕上进行触摸操作,产生一个 MotionEvent 对象。

  2. Activity 接收

    • 事件从 WindowManager 传递给当前的 ActivityActivity 调用 dispatchTouchEvent() 方法开始分发事件。

  3. 根视图分发

    • Activity 将事件传递给根视图(通常是一个 ViewGroup),根视图的 dispatchTouchEvent() 方法接收事件。

  4. ViewGroup 分发

    • ViewGroup 调用 dispatchTouchEvent() 方法,将事件传递给子视图或调用 onInterceptTouchEvent() 方法决定是否拦截事件。

  5. 拦截事件

    • 如果 ViewGrouponInterceptTouchEvent() 返回 true,表示拦截事件,ViewGroup 自己处理事件;

    • 如果返回 false,事件继续传递给子视图。

  6. 子视图处理

    • 子视图(可能是另一个 ViewGroupView)接收事件并调用自己的 dispatchTouchEvent() 方法。

  7. 具体视图处理

    • 最终由具体的 View 调用 onTouchEvent() 方法处理事件。

  8. 事件消费

    • 视图通过返回 truefalse 表示是否消费了该事件。如果返回 true,表示事件已被处理,不会继续向上传递;

    • 如果返回 false,事件将返回到上一级视图的 onTouchEvent() 方法进行处理,直到最终被处理或丢弃。

1

评论区