安卓之事件分发机制

安卓之事件分发机制

简介

  • 事件分发的"事件"是指什么?
    答:点击事件(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 {
     	//...省略代码
     }
相关推荐
蓝天星空几秒前
spring cloud gateway 3
java·spring cloud
罗政6 分钟前
PDF书籍《手写调用链监控APM系统-Java版》第9章 插件与链路的结合:Mysql插件实现
java·mysql·pdf
从以前8 分钟前
【算法题解】Bindian 山丘信号问题(E. Bindian Signaling)
开发语言·python·算法
一根稻草君12 分钟前
利用poi写一个工具类导出逐级合并的单元格的Excel(通用)
java·excel
kirito学长-Java14 分钟前
springboot/ssm网上宠物店系统Java代码编写web宠物用品商城项目
java·spring boot·后端
木头没有瓜28 分钟前
ruoyi 请求参数类型不匹配,参数[giftId]要求类型为:‘java.lang.Long‘,但输入值为:‘orderGiftUnionList
android·java·okhttp
奋斗的老史29 分钟前
Spring Retry + Redis Watch实现高并发乐观锁
java·redis·spring
high201131 分钟前
【Java 基础】-- ArrayList 和 Linkedlist
java·开发语言
键盘侠00731 分钟前
springboot 上传图片 转存成webp
android·spring boot·okhttp