Android Touch事件分发

目录

1.Touch事件分发--泛而谈之

2.Touch事件分类

3.参与touch事件分发的视图层级

4.参与touch事件分发的核心类和方法

5.读源码

6.关于touch事件分发的几个问题

6.1 dispatchTouchEvent方法干了啥

6.2 相关核心方法返回true或者false,各有什么含义?

6.3 View的enable和clickable的属性值为true或者false有什么区别?

6.4 什么时候执行onClick方法

鸣谢


注意:本文只讨论Android Framework已经实现的Touch事件分发过程,暂不讨论工程师对相关类的继承、方法的重写。

1.Touch事件分发--泛而谈之

目前,用户与手机的交互方式以touch(手指触摸屏幕)为主。

用面向对象的思维解释“touch事件分发”,“touch事件”是一个对象,在Android中就是一个MotionEvent类型的实例;“分发”是方法的调用过程,MotionEvent对象作为方法的参数传递给所调用的方法。

MotionEvent对象记录了touch事件类型、发生此touch事件的位置坐标等。

举个例子,当用户触摸手机屏幕上的一个button,然后系统给出一个动态的反馈。从触摸到反馈,泛而谈之,处理流程如下:

2.Touch事件分类

类型 描述
ACTION_DOWN 手指按下,以下简称down
ACTION_UP 手指抬起,以下简称up
ACTION_MOVE 手指滑动,,以下简称move
ACTION_POINTER_DOWN 多个手指按下
ACTION_POINTER_UP 多个手指抬起
ACTION_CANCEL 取消事件

手指从按下,再经过移动,最后抬起,走完一个完整的touch事件流程,即down>move>move>move>move>move>move>up。
手指每触碰到一个位置,都会产生一个MotionEvent对象,并分发下去。

3.参与touch事件分发的视图层级

Activity中有一个PhoneWindow实例,PhoneWindow是抽象类Window的唯一子类。

PhoneWindow中有一个DecorVIew实例,DecorVIew是FrameLayout的子类,本质是一个ViewGroup,DecorVIew是界面的根视图。常见地,在Activity#onCreate()中调用setContentView就是设置DecorVIew的子视图。

touch事件分发的入口是Activity的dispatchTouchEvent(MotionEvent event)方法。

视图层级

4.参与touch事件分发的核心类和方法

方法
Activity

dispatchTouchEvent

onTouchEvent

ViewGroup

dispatchTouchEvent

onInterceptTouchEvent

onTouchEvent(继承View的)

View

dispatchTouchEvent

onTouchEvent

View的onTouch方法需要工程师在外部实现,本文对此不做重点讨论。 

5.读源码

了解了touch事件分发的基本概念之后,那就开始读源码吧。

读源码秘诀:注重主干,选择性忽略细微的实现;带着问题去读源码比较有针对性,始终记得主线,防止读源码过程中,陷得太深,跑偏了。
读源码目的:理解原理、弥补盲区

6.关于touch事件分发的几个问题

6.1 dispatchTouchEvent方法干了啥

6.1.1 Activity中的dispatchTouchEvent负责分发touch事件给ViewGroup,源码为证:

    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

getWindow().superDispatchTouchEvent(ev),顺着调用线,一直追踪这个方法,可以发现,该方法调用的是DecorVIew对象的dispatchTouchEvent方法。

6.1.2 ViewGroup中的dispatchTouchEvent大体干了三件事:(源码太长,就不全贴了)

6.1.2.1 判断是否要拦截事件,如果不拦截,则将touch事件传递给其子View处理;如果拦截,则将touch事件传递给自己的onTouch或者onTouchEvent方法处理。

6.1.2.2 在当前ViewGroup中找到用户实际触摸到的View。

6.1.2.3 将touch事件分发给View。

6.1.3 View中的dispatchTouchEvent方法处理touch事件,源码为证:

    public boolean dispatchTouchEvent(MotionEvent event) {
        // If the event should be handled by accessibility focus first.
        if (event.isTargetAccessibilityFocus()) {
            // We don't have focus or no virtual descendant has it, do not handle the event.
            if (!isAccessibilityFocusedViewOrHost()) {
                return false;
            }
            // We have focus and got the event, then use normal event dispatch.
            event.setTargetAccessibilityFocus(false);
        }

        boolean result = false;

        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
        }

        final int actionMasked = event.getActionMasked();
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // Defensive cleanup for new gesture
            stopNestedScroll();
        }

        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;
            }
        }

        if (!result && mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }

        // Clean up after nested scrolls if this is the end of a gesture;
        // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
        // of the gesture.
        if (actionMasked == MotionEvent.ACTION_UP ||
                actionMasked == MotionEvent.ACTION_CANCEL ||
                (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
            stopNestedScroll();
        }

        return result;
    }

6.2 相关核心方法返回true或者false,各有什么含义?

方法 return true return false
Activity # dispatchTouchEvent 我重写了该方法,打了log,发现该方法返回true和false,执行结果都是:整个touch事件流中的每一个touch事件都会被分发。 见左
Activity # onTouchEvent 该方法的返回值就是Activity # dispatchTouchEvent的返回值 见左
ViewGroup # dispatchTouchEvent 该ViewGroup消费了touch事件,其父ViewGroup # dispatchTouchEvent也返回true 该ViewGroup消费不了touch事件,touch事件交由其父ViewGroup来处理,并且不再接收down之后的touch事件
ViewGroup # onInterceptTouchEvent 该ViewGroup拦截了touch事件,该touch事件由自己来处理,不将touch事件分发给子View 该ViewGroup不拦截touch事件
ViewGroup # onTouchEvent 该方法的返回值就是ViewGroup # dispatchTouchEvent的返回值 见左
View # dispatchTouchEvent 该View消费了touch事件,其父ViewGroup # dispatchTouchEvent也返回true 该View消费不了touch事件,touch事件交由其父ViewGroup来处理,并且不再接收down之后的touch事件
View # onTouch 该方法消费了touch事件,不再执行View # onTouchEvent,View # dispatchTouchEvent也返回true 该方法消费不了touch事件,将touch事件传递给View # onTouchEvent处理
View # onTouchEvent 该方法的返回值就是View # dispatchTouchEvent的返回值 见左

可见,touch事件分发过程中,不仅传递MontionEvent对象,还传递返回值。
MontionEvent对象从外往内传
返回值从内往外传

6.3 View的enable和clickable的属性值为true或者false有什么区别?

如果enable为true,且注册了OnTouchListener,才会执行重写后的onTouch方法,这与clickable的值无关

如果执行到了View # onTouchEvent,enable和clickable的值影响onTouchEvent的返回值,关系如下:

enable clickable View # onTouchEvent的返回值

false

true

true

false

false

false

true

true

true

true

false

false

6.4 什么时候执行onClick方法

当 注册了View.OnClickListener
   && enable和clickable均为true
   && 执行到了View#onTouchEvent(onTouchEvent中调用了onClick)
   && 一个完整的touch事件流(down>*>up)都传递到了VIew

,才会在up之后执行外部实现的onClick方法。

鸣谢

Android 9.0源码

简书某blog
Android Touch事件分发超详细解析

工匠若水 的CSDN博客, 非常详细的3篇分析文章
Android触摸屏事件派发机制详解与源码分析一(View篇)
Android触摸屏事件派发机制详解与源码分析二(ViewGroup篇)
Android触摸屏事件派发机制详解与源码分析三(Activity篇)

猜你喜欢

转载自blog.csdn.net/qq_38861828/article/details/107842427
今日推荐