源码角度把Android事件处理与分发理透

本文详细探讨了Android事件处理与分发机制,从DOWN事件开始,讲解了事件的传递顺序,分析了View与ViewGroup在事件处理和分发上的区别。通过实例和源码解析,阐述了事件冲突的处理,包括内部拦截法和外部拦截法,以及MOVE事件的特殊处理流程。文章总结了整个事件分发和处理的完整流程,帮助读者深入理解Android触摸事件系统。
摘要由CSDN通过智能技术生成

总结

事件分发是DOWN事件进行的,只能在MOVE中处理事件冲突。
在正常情况下,事件从ActivitydispatchTouchEvent传到PhoneWindowdispatchTouchEvent再传到DecorViewdispatchTouchEvent然后再传到ViewGroupdispatchTouchEvent,在ViewGroupdispatchTouchEvent中,有两个子方法,一是onInterceptTouchEvent ,还有一个是onTouchEvent。当onInterceptTouchEvent 返回true的时候,代表拦截事件,则执行onTouchEvent方法,否则不管返回啥,都传到下一层。ACTION_DOWN事件在哪个控件消费了(return true), 那么ACTION_MOVEACTION_UP就会从上往下(通过dispatchTouchEvent)做事件分发往下传,就只会传到这个控件,不会继续往下传,如果ACTION_DOWN事件是在dispatchTouchEvent消费,那么事件到此为止停止传递,如果ACTION_DOWN事件是在onTouchEvent消费的,那么会把ACTION_MOVEACTION_UP事件传给该控件的onTouchEvent处理并结束传递。

针对单点触摸

一.首先得知道有什么事件

MotionEvent

在这里插入图片描述

注意:

ACTION_MOVE会多次触发(体现为源码中同一块代码会调用多次)。
ACTION_CANCLE先记住,是事件被上层拦截时触发,至于具体的,后面就知道了。

二.DOWN事件处理与分发

知道了有什么事件,下面就得知道谁能怎么处理事件

具体来说,就是继承自View的只能处理事件,继承自ViewGroup的才能分发事件。ViewGroup先要走分发流程,再走处理流程。而View只能走处理流程。看下图

在这里插入图片描述

1.事件传递的顺序:当一个事件发生的时候,它传递的顺序,可以用这么一张图来表示

在这里插入图片描述
触摸屏幕后,首先接触到事件的是Activity,下面让我们从源码角度理解此流程
进入Activity类的dispatchTouchEvent方法

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

第一个if不需要我们管,看第二个if,它调用了PhoneWindowsuperDispatchTouchEvent方法,让我们继续追踪

@Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
   
        return mDecor.superDispatchTouchEvent(event);
    }

发现它调用了DecorViewsuperDispatchTouchEvent方法,我们继续追踪

public boolean superDispatchTouchEvent(MotionEvent event) {
   
        return super.dispatchTouchEvent(event);
    }

DecorViewsuperFrameLayout
在这里插入图片描述
但是由于FrameLayout没有实现dispatchTouchEvent方法,所以此方法最最终在ViewGroup里面执行。我们追踪它,就进入了ViewGroup的此方法
在这里插入图片描述
然后就开始事件分发了。事件分发在ViewGroup.java中。事件处理在View.java中,有两个方法,即dispatchTouchEvent和onTouchEvent。

那么就有人会问了,ViewdispatchTouchEventViewGroupdispatchTouchEvent有什么区别呢?其实是这样的,我们知道,ViewGroup是继承View的,所以它重写了ViewdispatchTouchEvent方法,使得它具有事件分发功能。而具体如何分发,后面来分析。

2.一个小案例,理解View处理事件

插播一个小知识:什么是冲突?事件只有一个,但是有多个人想要处理,如果处理的人不是我们想要的那个人,我们就说发生了冲突

来看这样一个非常典型的例子:
在这里插入图片描述
比如这个例子,我们想让onTouch处理,结果onClick处理了,那么我们就说发生了冲突。经过测试我发现,当我在onTouch方法中返回true的时候,onClick方法不执行,当返回false的时候,onClick方法执行。此时问题来了,什么时候onTouch处理,什么时候onClick处理呢?我们能否自己控制呢?
刚刚说了,事件处理在ViewdispatchTouchEvent中,所以我们直接进入源码找答案

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;
            }
        
			....
        return result;
}

可以通过源码发现,在第一个if的最后一个条件之前,所有的条件都为true。而我们的onTouch方法就是重写了最后一个条件的那个语句,即li.mOnTouchListener.onTouch(this, event),当我们onTouch返回true的时候,result赋值为true,当返回false的时候,result也赋值为false。然后我们看下一个if。当是第一种情况的时候,resulttrue之后为false,所以onTouchEvent方法不会执行。而当是第二种情况的时候,resultfalse之后为true,所以onTouchEvent就会执行。这个和前面的测试结果好像很像,所以我们不妨做个大胆的推测,即onClick方法就在onTouchEvent方法中调用。口说无凭,我们追踪进入

public boolean onTouchEvent(MotionEvent event) {
   
        ...
        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
   
            switch (action) {
   
                case MotionEvent.ACTION_UP:
                 				 ...
                                if (!post(mPerformClick)) {
   
                                    performClickInternal();
                                }
                       			 ...
      

我们知道onClick方法是在ACTION_UP的时候执行的,所以我们进入相应的case语句,找到performClickInternal方法(略去大量无关代码),追踪

    private boolean performClickInternal() {
   
        // Must notify autofill manager before performing the click actions to avoid scenarios where
        // the app has a click listener that changes the state of views the autofill service might
        // be interested on.
        notifyAutofillManagerOnClick();

        return performClick();
    }

追踪performClick

	public boolean performClick() {
   
        ...
        if (li != null && li.mOnClickListener != null) {
   
            playSoundEffect(SoundEffectConstants.CLICK);
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
   
            result = false;
        }
		...
    }

终于在if中找到了onClick的调用。同时resulttrue,意为告诉父容器,此事件我已处理了。

刚刚我们通过一个小案例,一个小事件冲突,体会到了View事件处理的机制以及流程,也就是说View的事件处理就是onTouchonClick。除此之外,我们也体会到了一个小的事件冲突的处理是怎样的。也就是说,在这里插入图片描述这张图的红框部分我们已经解决了,接下来我们看ViewGroup的分发事件,是如何处理的

3.ViewGroup的事件分发源码解析

我们不妨类比这么一张图

在这里插入图片描述
DecorView其实就是个ViewGroup。下面这些总监也是各种ViewGroup。我们的事件分发其实就是在总经理这里开始分发,一层一层地来。
第一个层级:总经理,即ViewGroup
第二个层级:总监,也是ViewGroup
第三个层级:比如清洁工,有可能是View,也可能是ViewGroup
在这里插入图片描述比如这张图,如果清洁是ImageButton那就是View,如果它是清洁工的头,比如LinearLayout,那么他就是ViewGroup。但是我们这里为了方便理解清楚,默认规定为普通清洁工,即View(其实都一样)。

我们首先看总经理,也就是ViewGroup的事件分发。源码进入ViewGroup.java,找到dispatchTouchEvent

下面是拦截的情况(也就是不分发,或者是分发了,但是都不处理。如果子View都不处理,也只能自己处理)
@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
   
     	...
        if (onFilterTouchEventForSecurity(ev)) {
   
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;

            // Handle an initial down.
            if (actionMasked == MotionEvent.ACTION_DOWN) {
   
 				//-----------------------------------
 				//-----------------------------------
 				//如果是DOWN事件,则进行一些清0操作
 				//-----------------------------------
 				//-----------------------------------
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }

            //-----------------------------------
            //-----------------------------------
			//下面办的事就是判断事件是否需要拦截
			//-----------------------------------
			//-----------------------------------
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
   
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
   
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
   
                    intercepted = false;
                }
            } else {
   
                // There are no touch targets and this action is not an initial down
                // so this view group continues to intercept touches.
                intercepted = true;
            }
			...

            //-----------------------------------
            //-----------------------------------
			//如果拦截了,就会走到这个if里面
			//-----------------------------------
			//-----------------------------------
            if (mFirstTouchTarget == null) {
   
                // No touch targets so treat this as an ordinary view.
       
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值