Android事件分发机制

最近在学习安卓的事件分发机制,实际上到目前为止还没有写过自定义View,在自定义View中如果需要能够响应手指的点击或者滑动事件就需要处理事件的分发,否则很容易出现冲突。写这篇博客是为了让自己能够更好的消化吸收,同时以后忘了能够很快的捡回来。此篇主要参考《图解Android事件分发机制》

1. Android事件分类

我们知道Android中的触摸事件可以分为:

ACTION_DOWN: 也就是手指按压屏幕

ACTION_MOVE: 手指按压屏幕后未松开,在屏幕上移动

ACTION_UP: 手机从屏幕上离开

2. Android事件分发流程图

图1

首先引入参考文章中的Android事件分发图即图1,该图主要讲的是ACTION_DOWN的分发流程。从图中可以看出事件分发设计的方法主要有三个:

dispatchTouchEvent:  从名字可以看出是分发触摸事件

onInterceptTouchEvent:  拦截触摸事件

onTouchEvent:  处理触摸事件

图中的true和false表示方法的返回值,super表示返回父类的super方法。

3. 结论

(1)事件的分发从图1中可以看出是从Activity -> ViewGroup -> View的,如果未重写前面说的三个方法,就会从

Activity.dispatchTouchEvent -> ViewGroup.dispatchTouchEvent -> ViewGroup.onInterceptTouchEvent -> View.dispatchTouchEvent -> View.onTouchEvent -> ViewGroup.onTouchEvent -> Activity.onTouchEvent

形成一个U型的回路,最后事件在Activity的onTouchEvent中处理。上述每个方法中的默认实现都是调用父类的实现即super.xxxx

(2) dispatchTouchEvent 返回值有三种

true: 表示事件被消费,终止传递在当前方法

false:如果是Activity的,也是被消费了。如果是ViewGroup或者View,都会回传给父控件的onTouchEvent处理。

super:对于Activity,向下传递给ViewGroup的dispatchTouchEvent; 对于ViewGroup,传递给其        onInterceptTouchEvent; 对于View,无法往下传递,其super的默认实现就是交由自己的onTouchEvent来处理。

(3)onInterceptTouchEvent  

首先这个方法只在ViewGroup中存在, Activity和View中是没有这个方法的。

一旦某个view拦截了一个事件,那么这一个事件序列都只能由它来处理,并且它的onInterceotTouchEvent不会再被调用。

true: 表示事件被拦截,交由自己的onTouchEvent处理

false:表示拦截失败,继续向子控件传递给子控件的dispatchTouchEvent

super:默认实现是不拦截的,所以和返回false的结果一样是向子控件传递

(4)onTouchEvent 返回值也是三种

true:表示事件被消费

false:表示不处理事件,把事件回传给父控件的onTouchEvent处理

super:表示不处理事件,把事件回传给父控件的onTouchEvent处理

(5)ACTION_MOVE和ACTION_UP

这两种事件的分发和ACTION_DOWN不太一致,分两种情况,详细看下面的图例,取自《图解Android事件分发机制》

从下面的图示中可以得出结论,如果在down事件在某一个控件中被消费,那么move和up只会分发到这个控件。同理,如果一个view一旦不消耗down事件(onTouchEvent返回了false),那么后续的同一事件序列都不会给他处理。

其中黑色表示ACTION_DOWN的分发路径,红色表示ACTION_MOVE和ACTION_UP的分发路径

1. 在某一级控件的dispatchTouchEvent中被消费(即dispatchTouchEvent返回true)

如图2, 那么ACTION_MOVE和ACTION_UP会从Activity中一直向下传递到消费的控件的dispatchTouchEvent方法

图2

2.如果在某一控件的onTouchEvent中被消费

如图3,4,那么会从Activity的dispatchTouchEvent传递到消费这个事件的控件的dispatchTouchEvent然后在传到这个控件的onTouchEvent中

                                                                        图3

图4

(6)ACTION_CANCEL 触发时机

1. 如果在父View中拦截ACTION_UP或者ACTION_MOVE,在第一次父View拦截事件的瞬间,父View指定子View不接收后续事件了,同时子View会收到ACTION_CANCEL事件

2. 如果触摸某个控件,但是又不是在这个控件的区域上抬起(移动到别的地方),就会出现ACTION_CANCEL

(7)onTouchEvent、setOnTouchListener的onTouch的优先级

1. dispatchTouchEvent -> onTouch -> onInterceptTouchEvent -> onTouchEvent

2. 从下面的Android13的View的dispatchTouchEvent方法源码可以看到,会先调用mOnTouchListener.onTouch(),所以onTouch的优先级更高,如果onTouch返回false,onTouch会被调用,如果返回true,那么onTOuch不会被调用

3. onClick事件是在onTouchEvent的MotionEvent.ACTION_UP事件通过performClick()触发的。        

public boolean dispatchTouchEvent(MotionEvent event) {
    ...//省略部分代码
    if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
        result = true;
    }
    if (!result && onTouchEvent(event)) {
        result = true;
    }
    ...//省略部分代码
}

(8)从native分发事件到Activity流程

当屏幕被触摸input系统事件从Native层分发到Framework层的inputEventReceiver.dispatchInputEvent -> ViewRootImpl.WindowInputEventReceiver.dispatchInputEvent -> ViewRootImpl.DecorView.dispatchInputEvent -> Activity.dispatchInputEvent -> window.superDispatchTouchEvent -> DecorView.superDispatchTouchEvent -> ViewGroup.superDispatchTouchEvent()

(9) View的onTouchEvent默认会消费事件(返回true),除非这个view是不可点击的(clickable和longClickable同时为false)。View的longClickable默认都为false,clickable属性要分开看,Button的clickable默认为true,TextView默认为false。但是对一个view设置setOnClickListener会设置这个view的clickable为true。同理设置一个view的setOnLongClickListenter会设置这个view的longClickable为true。View的enable属性不影响onTouchEvent的默认返回值

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值