安卓面试题(18/30)事件分发机制全解析

牛客高级系列专栏:

安卓(安卓系统开发也要掌握)


嵌入式


本人是2020年毕业于广东工业大学研究生:许乔丹,有国内大厂CVTE和世界500强企业安卓开发经验,该专栏整理本人对常见安卓高频开发面试题的理解;

网上安卓资料千千万,笔者将继续维护专栏,一杯奶茶价格不止提供答案解析,承诺提供专栏内容免费技术答疑,直接咨询即可。助您提高安卓面试准备效率,为您面试保驾护航!

正文开始⬇

事件分发机制属于安卓基础面试必问题目之一。最关键是理解事件分发三个函数的作用和返回值的含义,就基本足够应付面试了。面试官可能会问:

  1. 请介绍什么是事件分发机制,以及对应流程⭐⭐⭐⭐⭐
  2. 谈谈你对MotionEvent的认识?Cancel事件是什么情况下触发的⭐⭐⭐
  3. OnTouchListener & OnTouchEvent & OnClickListener三者之间的关系⭐⭐⭐⭐

看完以下的解析,一定可以让面试官眼前一亮。

目录

  • 1、触摸事件分发流程
  • 2、触摸事件分发的3个重要方法
  • 3、onTouch()、onTouchEvent()、onClick()傻傻分不清?
  • 4、事件分发总结
    • 4.1 表格总结
    • 4.2 伪代码表示
    • 4.3 事件由上到下的传递规则
    • 4.4 事件由下而上的传递规则

1、触摸事件分发流程

在《Activity、Window、DecorView以及ViewRoot层级关系全解析》一节有下图: alt

我们知道View的结构是树形结构,View可以放在ViewGroup中,ViewGroup也可以放在另一个ViewGroup中,如此就形成了层层嵌套的关系。当我们触摸到屏幕后,就会生成一个Touch事件,常见的Touch事件有:

  • MotionEvent.ACTION_DOWN:按下
  • MotionEvent.ACTION_MOVE:滑动
  • MotionEvent.ACTION_CANCEL:非人为原因结束本次事件
  • MotionEvent.ACTION_UP:抬起 一般来说,一个事件会经过:按下 --》 滑动 --》抬起,这三个阶段,并在这个过程中会有非人为原因结束本次触摸流程。这些事件会在代码里会封装成一个MotionEvent。那么,当MotionEvent产生后,系统就会将其传递给View树,MotionEvent在View的层级传递,并最终得到处理的过程,就是触摸事件分发流程。一个流程的传递顺序是:

Activity/Window --> ViewGroup --> View

其中,View就是各种控件,如Button、TextView等,而ViewGroup是View的子类,因此本质上也是一个View,只不过ViewGroup可以包含多个子View和定义布局参数。

2、触摸事件分发的3个重要方法

有以下3个重要方法是必须掌握的:

  • dispatchTouchEvent(MotionEvent ev):进行事件的分发,在View和ViewGroup类都有该方法,下文会对该方法的源码进行分析,需要区分清楚;
  • onInterceptTouchEvent(MotionEvent ev):进行事件拦截,在dispatchTouchEvent()中调用,在分发的过程中判断是否需要进行拦截,需要注意的是只有ViewGroup有该方法,View是没有提供该方法的。如果返回true代表拦截,返回false代表不拦截;
  • onTouchEvent(MotionEvent ev):触摸事件处理,同样在dispatchTouchEvent()方法中进行调用,如果返回true代表已处理事件,返回false代表不处理事件,事件继续传递。

为了更好的了解三者的关系,我们从源码出发,首先看看ViewGroup的dispatchTouchEvent(),源码是Android9.0.0=》/frameworks/base/core/java/android/view/ViewGroup.java:


public boolean dispatchTouchEvent(MotionEvent ev) {
    ...
    if (!disallowIntercept) {
        intercepted = onInterceptTouchEvent(ev);  //1
        ev.setAction(action); // restore action in case it was changed
    } else {
        intercepted = false;
    }
    ...
    if (actionMasked == MotionEvent.ACTION_DOWN
            || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
            || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
        ...
        if (newTouchTarget == null && childrenCount != 0) {
            ...
            for (int i = childrenCount - 1; i >= 0; i--) { //2:遍历ViewGroup的子View
                final int childIndex = getAndVerifyPreorderedIndex(
                        childrenCount, i, customOrder);
                final View child = getAndVerifyPreorderedView(
                        preorderedList, children, childIndex);

                ...
                resetCancelNextUpFlag(child);
                if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { //3
                    ...
                    break;
                }

                ...
            }
        }
    }
}

在[注释1]调用了onInterceptTouchEvent()方法来判断是否要拦截当前的事件。

  public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
                && ev.getAction() == MotionEvent.ACTION_DOWN
                && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
                && isOnScrollbarThumb(ev.getX(), ev.getY())) {
            return true;
        }
        return false;
    }

可以看出onInterceptTouchEvent()默认返回false,代表不拦截。接着在[注释2]遍历ViewGroup的子View,如果子View可以接收到触摸事件,则会执行[注释3]dispatc

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

Android高频面试题全解析 文章被收录于专栏

#提供免费售后答疑!!花一杯奶茶的钱获得安卓面试答疑服务,稳赚不赔# Android发展已经很多年,安卓资料网上千千万,本专栏免费提供专栏内容技术答疑!!私聊当天必回。在阅读过程或者其他安卓学习过程有疑问,都非常欢迎私聊交流。

全部评论

相关推荐

投递海康威视等公司10个岗位
点赞 评论 收藏
分享
13 26 评论
分享
牛客网
牛客企业服务