安卓之事件分发机制

安卓之事件分发机制

简介

  • 事件分发的"事件"是指什么?
    答:点击事件(Touch事件)。
  • 当用户触摸屏幕(VIew或ViewGroup)时,将产生点击事件,即Touch事件。Touch事件的细节(如:触摸位置、事件等)会被封装成MotionEvent对象。
  • 常见的事件:
    • MotionEvent.ACTION_DOWN:按下。
    • MotionEvent.ACTION_UP:抬起。
    • MotionEvent.ACTION_MOVE:滑动。
    • MotionEvent.ACTION_CANCEL:结束事件(非正常情况)。
  • 事件列:指从手指接触屏幕至手指离开屏幕这个过程产生的一系列事件。
    • 一般情况下,事件列都是以DOWN事件开始、UP事件结束,中间有无数的MOVE事件。

本质

  • 事件分发的本质是将点击事件(MotionEvent)传递到某个具体的View进行处理的整个过程。即:事件传递的过程 = 分发过程

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

  • 答:Activity、ViewGroup、View。

事件分发的顺序是?

  • 答:Activity -> ViewGroup -> View。

事件分发过程由哪些方法协作完成?

  • 答:dispatchTouchEvent() 、onInterceptTouchEvent()和onTouchEvent()。

Activity的事件分发机制

当用户触摸屏幕时,底层的 InputManager 会将触摸事件封装为 MotionEvent 对象,然后将该对象传递给当前 Activity 的 PhoneWindow 对象。PhoneWindow 对象会将该 MotionEvent 传递给 DecorView 对象,DecorView 再传递给Atcivity的 dispatchTouchEvent() 方法进行事件分发或处理。Atcivity的 dispatchTouchEvent() 方法如下:

java 复制代码
 public boolean dispatchTouchEvent(MotionEvent ev) {
     ...
     if (getWindow().superDispatchTouchEvent(ev)) {
         return true;
     }
     return onTouchEvent(ev);
 }

View的事件分发机制

  • 先看源码:
    *

    java 复制代码
     public boolean dispatchTouchEvent(MotionEvent event) {
     	//...省略代码
     	//以下为核心代码:
     	//这里就是View在收到事件后的具体处理逻辑了,也就是ViewGroup调用了View的dispatchTouchEvent()方法把事件传递给了View
     	//先判断有没有给View设置OnTouchListener,如果有就执行OnTouchListener的onTouch()方法
     	//同时该方法有返回值表示事件有没有被消费,如果被消费了,result设置为true
     	//接着是判断如果没有被消费,则执行onTouchEvent()方法,这个方法中会沿着MotionEvent.ACTION_UP->performClickInternal()->performClick()->OnClickListener的onClick(),最后走到了点击监听的onClick()中。
     	//同时,onClick()也是用返回值表示事件有没有被消费。
     	if (onFilterTouchEventForSecurity(event)) {
             if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                 result = true;
             }
             //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;
             }
         }
         //...省略代码
     }
  • 简单总结:

    • View中按onTouch()->onTouckEvent()->onClick()的顺序执行。
    • 同理,你的自定义View如果需要处理事件分发,那就在dispatchTouchEvent()方法中。
  • 以上就是View中最核心的事件分发处理流程了,同时延伸出几个可以提问的问题:

    • ①问:onTouch()和onClick()谁先执行?
      答:源码的顺序就是先走onTouch(),再走onClick()。
    • ②问:onTouch()方法返回值的作用?
      答:不仅仅是onTouch()方法,几乎所有的方法返回true表示事件被消费,返回false表示事件未被消费,可以继续分发。
    • ③问:onTouchEvent()一定执行吗?
      答:不一定,如果设置了OnTouchListener,则先看onTouch()拦不拦截,不拦截才会走onTouchEvent()方法。同时如果是自定义View,还要看有没有重写dispatchTouchEvent()方法。
    • ④问:onTouch()和onTouchEvent一定会执行吗?
      答:不一定,首先事件得先传递到dispatchTouchEvent()中,其次看各种条件是否能通过。
    • ⑤问:如何分析事件分发冲突?
      答:就是查看对应的控件的dispatchTouchEvent()是怎么么处理的,看事件有没有传递进去,传递进去后又是在哪消费的。
    • ⑥问:
    • 答:

Activity的事件分发机制

  • 先看源码:
    *

    java 复制代码
     //事件是怎么传递到Activity的dispatchTouchEvent()方法中的?
     //		
     //Activity中进行分发:
     //		这里调用沿着getWindow().superDispatchTouchEvent(ev)
     //					->Window的唯一实现类PhoneWindow的mDecor.superDispatchTouchEvent(event)
     //					->DecorView的super.dispatchTouchEvent(event),
     //		最后传递到ViewGroup中,ViewGroup又进行事件分发。
     //
     //如果事件在Activity->ViewGroup->View整个传递过程中没有被处理,
     //最后又交回Activity的onTouchEvent()进行处理,
     //再如果Activity也不处理,那只能交给系统处理了。
     public boolean dispatchTouchEvent(MotionEvent ev) {
         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
             onUserInteraction();
         }
         if (getWindow().superDispatchTouchEvent(ev)) {
             return true;
         }
         return onTouchEvent(ev);
     }

ViewGroup的事件分发机制

  • 先看源码:
    *

    java 复制代码
     //第一块:ViewGroup每次事件分发时,都需调用onInterceptTouchEvent()询问是否拦截事件。
     //这里两个关键的地方:disallowIntercept和onInterceptTouchEvent()。
     //		disallowIntercept:是否禁用事件拦截的功能(默认是false),可通过调用requestDisallowInterceptTouchEvent()修改。
     //		onInterceptTouchEvent():
     //			a.若在onInterceptTouchEvent()中返回false,即当前ViewGroup不拦截事件。
     // 		b. 若在onInterceptTouchEvent()中返回true,即拦截事件。
     //			这个方法中一般就是解决事件分发冲突的,本质就是事件拦不拦截,交由谁处理。
     final boolean intercepted;
     if (actionMasked == MotionEvent.ACTION_DOWN
             || mFirstTouchTarget != null) {
         final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
         if (!disallowIntercept) {
             intercepted = onInterceptTouchEvent(ev);
             ev.setAction(action);
         } else {
             intercepted = false;
         }
     } else {
         intercepted = true;
     }
     
     //第二块:分发事件,看哪个子View处理事件
     //通过for循环,遍历当前ViewGroup下的所有子View,根据点击的位置判断当前遍历的View是不是正在点击的View,从而找到当前被点击的View。
     //dispatchTransformedTouchEvent()方法:内部根据传入的childView是否为空来决定调用当前View还是子View的dispatchTouchEvent()方法。
     if (!canceled && !intercepted) {
     	//..省略代码
        for (int i = childrenCount - 1; i >= 0; i--) {
            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                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;
            }
            ev.setTargetAccessibilityFocus(false);
        }
     }
     
     //第三块:
     //执行事件。
     if (mFirstTouchTarget == null) {
         // No touch targets so treat this as an ordinary view.
         handled = dispatchTransformedTouchEvent(ev, canceled, null,
                 TouchTarget.ALL_POINTER_IDS);
     } else {
     	//...省略代码
     }
相关推荐
Ray Liang1 小时前
用六边形架构与整洁架构对比是伪命题?
java·python·c#·架构设计
Java水解1 小时前
Java 中间件:Dubbo 服务降级(Mock 机制)
java·后端
砖厂小工3 小时前
用 GLM + OpenClaw 打造你的 AI PR Review Agent — 让龙虾帮你审代码
android·github
张拭心4 小时前
春节后,有些公司明确要求 AI 经验了
android·前端·人工智能
张拭心4 小时前
Android 17 来了!新特性介绍与适配建议
android·前端
SimonKing6 小时前
OpenCode AI辅助编程,不一样的编程思路,不写一行代码
java·后端·程序员
FastBean6 小时前
Jackson View Extension Spring Boot Starter
java·后端
Kapaseker6 小时前
Compose 进阶—巧用 GraphicsLayer
android·kotlin
黄林晴7 小时前
Android17 为什么重写 MessageQueue
android
Seven977 小时前
剑指offer-79、最⻓不含重复字符的⼦字符串
java