安卓之事件分发机制

安卓之事件分发机制

简介

  • 事件分发的"事件"是指什么?
    答:点击事件(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 {
     	//...省略代码
     }
相关推荐
CYRUS STUDIO9 分钟前
ARM64汇编寻址、汇编指令、指令编码方式
android·汇编·arm开发·arm·arm64
起名字真南15 分钟前
【OJ题解】C++实现字符串大数相乘:无BigInteger库的字符串乘积解决方案
开发语言·c++·leetcode
爬山算法20 分钟前
Maven(28)如何使用Maven进行依赖解析?
java·maven
tyler_download26 分钟前
golang 实现比特币内核:实现基于椭圆曲线的数字签名和验证
开发语言·数据库·golang
小小小~27 分钟前
qt5将程序打包并使用
开发语言·qt
hlsd#27 分钟前
go mod 依赖管理
开发语言·后端·golang
小春学渗透29 分钟前
Day107:代码审计-PHP模型开发篇&MVC层&RCE执行&文件对比法&1day分析&0day验证
开发语言·安全·web安全·php·mvc
杜杜的man31 分钟前
【go从零单排】迭代器(Iterators)
开发语言·算法·golang
亦世凡华、32 分钟前
【启程Golang之旅】从零开始构建可扩展的微服务架构
开发语言·经验分享·后端·golang
2401_8574396944 分钟前
SpringBoot框架在资产管理中的应用
java·spring boot·后端