Android事件分发机制与滑动冲突

Android事件分发机制

一、概述

1. 事件

事件通常指触摸或点击事件,用户触摸屏幕时产生 Touch 事件。Touch 事件的相关细节封装于 MotionEvent 对象中。

事件类型 具体动作
MotionEvent.ACTION_DOWN 按下事件(开始)
MotionEvent.ACTION_UP 抬起事件(结束)
MotionEvent.ACTION_MOVE 滑动事件
MotionEvent.ACTION_CANCEL 取消事件
2. 分发流程

如上图所示,onTouch事件产生后,先传给Activity,再传给View Group,最后传给View。

事件分发流程的目的就是要找到第一个要处理事件的对象。一旦有一个对象消费了该事件,事件分发结束。反之,如果事件没有被消费,则会被废弃。

3. 重要方法
方法 作用
dispatchTouchEvent(event: MotionEvent?): Boolean 进行事件分发
onInterceptTouchEvent(event: MotionEvent?): Boolean 进行事件拦截
onTouchEvent(event: MotionEvent?): Boolean 进行事件消耗

三个方法之间的关系可以使用如下伪代码表示:

fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
   
    val consume = false
    if (onInterceptTouchEvent(ev)) {
   
        consume = onTouchEvent(ev)
    } else {
   
        consume = child.dispatchTouchEvent(ev)
    }
    return consume
}

事件的传递规则:对于ViewGroup,点击事件传递过来后,首先调用 dispatchTouchEvent 方法。如果其 onInterceptTouchEvent 方法返回true,表示拦截该事件,随后它的 onTouch 方法被调用;如果 onInterceptTouchEvent 方法返回false,表示不拦截事件,该事件会继续传递给子View,接着子View的 dispatchTouchEvent 方法被调用。重复该过程直至事件被消耗。

二、Activity的事件分发

1. Demo演示

(1) 重写Activity的 dispatchTouchEventonTouchEvent 方法。

class MainActivity : AppCompatActivity() {
   
    companion object {
   
        const val TAG = "Activity"
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
   
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

    override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
   
        val eventName = EventUtil.getActionName(ev)
        LogUtil.i(TAG, "dispatchTouchEvent $eventName Start", LogUtil.Depth.ACTIVITY)
        val result = super.dispatchTouchEvent(ev)
        LogUtil.i(TAG, "dispatchTouchEvent $eventName End with $result", LogUtil.Depth.ACTIVITY)
        return result
    }

    override fun onTouchEvent(event: MotionEvent?): Boolean {
   
        val eventName = EventUtil.getActionName(event)
        LogUtil.i(TAG, "onTouchEvent $eventName", LogUtil.Depth.ACTIVITY)
        return super.onTouchEvent(event)
    }
}

(2) 自定义MyLayout (继承自FrameLayout) 并重写 dispatchTouchEvent方法

class MyLayout : FrameLayout {
   
    companion object {
   
        const val TAG = "MyLayout"
    }

    constructor(context: Context) : super(context)
    constructor(context: Context, attrs: AttributeSet) : super(context, attrs)

    override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
   
        val eventName = EventUtil.getActionName(ev)
        val result = false  // false or true
        LogUtil.i(TAG, "dispatchTouchEvent $eventName End with $result", LogUtil.Depth.VIEW_GROUP)
        return result
    }
}

(3) activity_main.xml

<com.example.eventdispatch.ui.MyLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="200dp"
    android:layout_height="200dp"
    android:layout_gravity="center"
    android:background="@color/colorPrimary"
    android:gravity="center">
</com.example.eventdispatch.ui.MyLayout>

当MyLayout dispatchTouchEvent 返回false时,表示其不对事件进行分发。ACTION_DOWN事件传递到MyLayout时,dispatchTouchEvent 被调用,返回false,事件返回给Activity,Activity的 onTouchEvent 被调用。当ACTION_MOVE或ACTION_UP事件到来时,由于上一个事件由Activity处理,因此该事件不再向下传递,直接交给Activity处理。点击MyLayout,打印的Log如下:

I/Activity:     dispatchTouchEvent ACTION_DOWN Start
I/  MyLayout:   dispatchTouchEvent ACTION_DOWN End with false
I/Activity:     onTouchEvent ACTION_DOWN
I/Activity:     dispatchTouchEvent ACTION_DOWN End with false
I/Activity:     dispatchTouchEvent ACTION_UP Start
I/Activity:     onTouchEvent ACTION_UP
I/Activity:     dispatchTouchEvent ACTION_UP End with false

当MyLayout dispatchTouchEvent 返回true时,事件被MyLayout消耗,Activity的 onTouchEvent 不会被调用。点击MyLayout,打印的Log如下:

I/Activity:     dispatchTouchEvent ACTION_DOWN Start
I/  MyLayout:   dispatchTouchEvent ACTION_DOWN End with true
I/Activity:     dispatchTouchEvent ACTION_DOWN End with true
I/Activity:     dispatchTouchEvent ACTION_UP Start
I/  MyLayout:   dispatchTouchEvent ACTION_UP End with true
I/Activity:     dispatchTouchEvent ACTION_UP End with true
2. 源码分析

注: 本文所有源码为API Level 29

点击事件产生后,最先传递给当前Activity,Activity的 dispatchTouchEvent 方法被调用。

Activity的 dispatchTouchEvent 方法如下:

/**
 * Acticity.java
 * Line 3989-3997
 */
public boolean dispatchTouchEvent(MotionEvent ev) {
   
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
   
        // 空方法
        onUserInteraction();
    }
    if (getWindow().superDispatchTouchEvent(ev)) {
   
        return true;
    }
    return onTouchEvent(ev);
}

可以看到,事件首先交给Activity所属的Window进行分发,如果返回true,则事件分发结束;如果返回false,意味着事件没有被处理,Activity的 onTouchEvent 被调用。

getWindow 返回Window对象,Window是一个抽象类,PhoneWindow是其唯一的实现类。因此 getWindow().superDispatchTouchEvent(ev) 就是调用PhoneWindow的 superDispatchTouchEvent(ev) 方法。

PhoneWindow的 superDispatchTouchEvent 方法如下:

/**
 * PhoneWindow.java
 * Line 1847-1850
 */
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
   
    return mDecor.superDispatchTouchEvent(event);
}

PhoneWindow将事件传递给了DecorView对象mDecor,mDecor是 getWindow().getDecorView() 返回的View,Activity中通过 setContentView 设置的View是它的一个子View。

DecorView的 superDispatchTouchEvent 方法如下:

/**
 * DecorView.java
 */
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks{
   
    // ...
    // Line464-466
    public boolean superDispatchTouchEvent(MotionEvent event) {
   
        return super.dispatchTouchEvent(event);
    }
}

DecorView继承自FramgLayout,FrameLayout又继承自ViewGroup,所以 mDecor.superDispatchTouchEvent(event) 其实就是调用ViewGroup的 dispatchTouchEvent 方法。至此,事件已经分发给ViewGroup了。

3. 分发流程图

三、ViewGroup的事件分发

1. Demo演示

(1) 自定义MyLayout (继承自FrameLayout) 并重写 dispatchTouchEvent 方法、onInterceptTouchEvent 方法、onTouchEvent 方法。

class MyLayout : FrameLayout {
   
    companion object {
   
        const val TAG = "MyLayout"
    }

    constructor(context: Context) : super(context)
    constructor(context: Context, attrs: AttributeSet) : super(context, attrs)

    override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
   
        val eventName = EventUtil.getActionName(ev)
        LogUtil.i(TAG, "dispatchTouchEvent $eventName Start", LogUtil.Depth.VIEW_GROUP)
        val result = super.dispatchTouchEvent(ev)
        LogUtil.i(TAG
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值