Android-触摸事件分发

一、什么是触摸事件分发,分发的是什么?

  • 触摸事件分发,顾名思义就是将触摸事件(MotionEvent)进行传递的过程。
  • MotionEvent也就是触摸事件,主要分为几种类型:
类型介绍
MotionEvent.ACTION_DOWN按下:一个已开始按下的手势,动作包含初始开始位置。
MotionEvent.ACTION_MOVE移动:在DOWN和UP之间,包含最近的点以及自上次下移或移动事件以来的任何中间点。 产生多个move事件
MotionEvent. ACTION_UP抬起:按下手势已完成,该动作包含最终的释放位置以及自上次下移或移动事件以来的任何中间点。
MotionEvent.ACTION_CANCEL取消(中止):非人为。应该视为UP事件

一个完整的事件序列:从手指接触屏幕(ACTION_DOWN)开始,中间经历大于等于0个移动事件(ACTION_MOVE),最后以手指离开屏幕(ACTION_UP)结束。
如:点击操作(ACTION_DOWN→ACTION_UP),滑动操作(ACTION_DOWN→ACTION_MOVE→ACTION_UP/ACTION_CANCEL)。

二、如何进行分发?

1. Activity

触摸事件的发生往往是Activity可以和用户进行交互以后,那么先从Activity来看。
从源码开始:

  • Activity.dispatchTouchEvent(MotionEvent ev)
public boolean dispatchTouchEvent(MotionEvent ev) {
		// 触摸事件链的开始,ACTION_DOWN事件发生
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        // 该方法为空实现,可在Activity自行重写,注释说明只有Touch Down 即Touch事件的开始会触发该回调,不会在move 和 up 分发时触发
            onUserInteraction();
        }
        // 调用Window(只有唯一子类PhoneWindow)的superDispatchTouchEvent方法,事件向window分发
        if (getWindow().superDispatchTouchEvent(ev)) {
        // 如果被window内的view消费,则返回true
            return true;
        }
        // 如果没有被window内的view消费,则调用activity的onTouchEvent方法
        return onTouchEvent(ev);
    }

首先判断是否有ACTION_DOWN事件发生,有就走空实现方法onUserInteraction(),接着调用Window的superDispatchTouchEvent方法,事件向window分发。如果window消费了事件则返回true,否则调用activity的onTouchEvent方法。
接下来看传递的第二层Window,Window只有唯一子类PhoneWindow:

  • PhoneWindow.superDispatchTouchEvent(MotionEvent event)
	// This is the top-level view of the window, containing the window decor.
    private DecorView mDecor;

 	@Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
    // 这里直接调用了DecorView 的superDispatchTouchEvent方法并返回
        return mDecor.superDispatchTouchEvent(event);
    }
  • 再看DecorView:
public boolean superDispatchTouchEvent(MotionEvent event) {
		// 直接返回调用父类(ViewGroup)的dispatchTouchEvent
        return super.dispatchTouchEvent(event);
    }

DecorView 继承自FrameLayout,而FrameLayout中没有
dispatchTouchEvent方法,故直接调用其父类ViewGroup的dispatchTouchEvent方法。

  • 最后回过去看View没有被消费时调用的Activity.onTouchEvent()方法
/**
     * Called when a touch screen event was not handled by any of the views
     * under it.  This is most useful to process touch events that happen
     * outside of your window bounds, where there is no view to receive it.
     *
     * @param event The touch screen event being processed.
     *
     * @return Return true if you have consumed the event, false if you haven't.
     * The default implementation always returns false.
     */
public boolean onTouchEvent(MotionEvent event) {
        if (mWindow.shouldCloseOnTouch(this, event)) {
            finish();
            return true;
        }

        return false;
    }

源码给出注释:只要不重写消费事件,该方法默认实现始终返回false。
但是这里这里简单做了个判断,如果window的shouldCloseOnTouch()方法返回true,则结束当前Activity并返回true。还是看看吧:
在这里插入图片描述
大概意思就是说:触摸事件发生在屏幕外就返回true。(这里看不懂咯,只知道有个变量mCloseOnTouchOutside默认为false,而能修改它的地方都是hide方法,所以就默认一直返回false了?)

  • 总结
    Activity事件分发机制
    到这里,Activity中一个事件的分发就结束了。以上流程图能清晰地说明其分发走向。

2. ViewGroup

从上面的流程图中,Activity最终返回值应该是由ViewGroup的dispatchTouchEvent方法决定的。接下来继续探索ViewGroup的事件分发。

@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
    	...
    	 boolean handled = false;
    	 // 当视图或window被隐藏、遮挡时返回false
        if (onFilterTouchEventForSecurity(ev)) {
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;

            // 处理第一个DOWN事件
            if (actionMasked == MotionEvent.ACTION_DOWN{
            // 这里是
                cancelAndClearTouchTargets(ev);
               	// 对FLAG_DISALLOW_INTERCEPT标记位进行重置
                resetTouchState();
            }
            ...
    }

首先是在处理第一次触摸事件DOWN,如果是,则取消和清除触摸对象并且重置触摸状态(完全按照方法名来理解,哈哈)。先不纠结这里了。往下看

			...
			// 检查拦截
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                    // 根据“不允许拦截标志位”定义不允许拦截变量disallowIntercept 
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                // 如果没有设置不允许拦截,则disallowIntercept返回onInterceptTouchEvent方法
                //此方法默认返回false,返回true的情况是:鼠标按住左键拖动滚动条,所以要拦截事件
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
                // 如果子View设置了不允许拦截,则不拦截
                    intercepted = false;
                }
            } else {
                // 没有触摸目标,并且此操作并非初始的DOWN操作。
				//	因此该视图组继续拦截触摸。
                intercepted = true;
            }
            ...

检查拦截部分主要根据FLAG_DISALLOW_INTERCEPT这个“不允许拦截标记”设置来确定是否进行拦截。
下面的我有点跟不下去了,整体的步骤我先给翻译出来,后面来补充细节:
1.检查窗口被遮挡,从DOWN操作开始,清空FLAG_DISALLOW_INTERCEPT标记位。
2.检查拦截:根据标记位设置拦截变量,如果被拦截,开始正常事件分发。 如果已经有一个正在处理手势的View,则进行常规事件调度。
3.接着是检查CANCEL操作
偷张图来用:
在这里插入图片描述

3. View

继续盗图:
在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android触摸事件分发是指在屏幕上发生触摸事件时,Android系统将该事件分发给适当的视图处理。触摸事件分发的过程涉及多个层级,包括Activity、ViewGroup和View。 当用户触摸屏幕时,Android系统首先将触摸事件发送给当前活动的Window。然后,Window将事件传递给顶级ViewGroup(通常是根布局),该ViewGroup负责协调子视图的事件处理。 在ViewGroup中,触摸事件会按照一定的规则进行分发。常见的分发方式有以下几种: 1. 捕获阶段(Capture Phase):从根布局向下遍历,让父级ViewGroup有机会拦截事件。可以通过重写`onInterceptTouchEvent()`方法来实现事件的拦截。 2. 目标阶段(Target Phase):如果没有被拦截,触摸事件将传递给目标View,即最终接收事件的视图。目标View将调用`onTouchEvent()`方法处理事件。 3. 冒泡阶段(Bubble Phase):如果目标View没有消耗事件事件将向上传递给父级ViewGroup,直到根布局。在这个阶段,可以通过返回值来控制是否继续向上传递。 除了上述的默认分发方式外,还可以通过重写`dispatchTouchEvent()`方法来自定义事件分发逻辑。通过调用`super.dispatchTouchEvent()`来保持默认行为,或者根据需求进行处理。 总结来说,Android触摸事件分发涉及捕获阶段、目标阶段和冒泡阶段,通过重写相关方法或自定义分发逻辑,可以实现对触摸事件的处理和控制。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值