自定义View系列教程07--详解ViewGroup分发Touch事件


深刻探讨Android异步精髓Handlerjava


站在源码的肩膀上全解Scroller工做机制android


Android多分辨率适配框架(1)— 核心基础
Android多分辨率适配框架(2)— 原理剖析
Android多分辨率适配框架(3)— 使用指南markdown


自定义View系列教程00–推翻本身和过往,重学自定义View
自定义View系列教程01–经常使用工具介绍
自定义View系列教程02–onMeasure源码详尽分析
自定义View系列教程03–onLayout源码详尽分析
自定义View系列教程04–Draw源码分析及其实践
自定义View系列教程05–示例分析
自定义View系列教程06–详解View的Touch事件处理
自定义View系列教程07–详解ViewGroup分发Touch事件
自定义View系列教程08–滑动冲突的产生及其处理app


PS:若是以为文章太长,那就直接看视频框架


在上一篇中已经分析完了View对于Touch事件的处理,在此基础上分析和理解ViewGroup对于Touch事件的分发就会相对容易些。
当一个Touch事件发生后,事件首先由系统传递给当前Activity而且由其dispatchTouchEvent()派发该Touch事件,源码以下:异步

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

该段代码主要逻辑以下:ide

  1. 处理ACTION_DOWN事件
    调用onUserInteraction()该方法在源码中为一个空方法,可依据业务需求在Activity中覆写该方法。
  2. 利用PhoneWindow的superDispatchTouchEvent()派发事件工具

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

    DecorView的superDispatchTouchEvent()源码以下:源码分析

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

    此处咱们能够看到:在该方法中又将Touch事件交给了DecorView进行派发。
    DecorView继承自FrameLayout它是整个界面的最外层的ViewGroup。
    至此,Touch事件就已经到了顶层的View且由其开始逐级派发。若是superDispatchTouchEvent()方法最终true则表示Touch事件被消费;反之,则进入下一步布局

  3. Activity处理Touch事件
    若是没有子View消费Touch事件,那么Activity会调用自身的onTouchEvent()处理Touch.

在以上步骤中第二步是咱们关注的重点;它是ViewGroup对于Touch事件分发的核心。
关于dispatchTouchEvent(),请看以下源码:

public boolean dispatchTouchEvent(MotionEvent ev) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
        }

        if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
            ev.setTargetAccessibilityFocus(false);
        }

        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;

            if (actionMasked == MotionEvent.ACTION_DOWN) {
                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); 
                } else {
                    intercepted = false;
                }
            } else {
                intercepted = true;
            }


            if (intercepted || mFirstTouchTarget != null) {
                ev.setTargetAccessibilityFocus(false);
            }

            final boolean canceled = resetCancelNextUpFlag(this)|| actionMasked == MotionEvent.ACTION_CANCEL;

            final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
            TouchTarget newTouchTarget = null;
            boolean alreadyDispatchedToNewTouchTarget = false;
            if (!canceled && !intercepted) {
                View childWithAccessibilityFocus= ev.isTargetAccessibilityFocus()? findChildWithAccessibilityFocus() : null;

                if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                    final int actionIndex = ev.getActionIndex(); 
                    final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex): TouchTarget.ALL_POINTER_IDS;

                    removePointersFromTouchTargets(idBitsToAssign);

                    final int childrenCount = mChildrenCount;
                    if (newTouchTarget == null && childrenCount != 0) {
                        final float x = ev.getX(actionIndex);
                        final float y = ev.getY(actionIndex);
                        final ArrayList<View> preorderedList = buildOrderedChildList();
                        final boolean customOrder = preorderedList == null&&isChildrenDrawingOrderEnabled();
                        final View[] children = mChildren;
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            final int childIndex = customOrder?getChildDrawingOrder(childrenCount, i) : i;
                            final View child = (preorderedList == null)?children[childIndex] : preorderedList.get(childIndex);

                            if (childWithAccessibilityFocus != null) {
                                if (childWithAccessibilityFocus != child) {
                                    continue;
                                }
                                childWithAccessibilityFocus = null;
                                i = childrenCount - 1;
                            }

                            if (!canViewReceivePointerEvents(child)||!isTransformedTouchPointInView(x,y,child,null)) {
                                ev.setTargetAccessibilityFocus(false);
                                continue;
                            }

                            newTouchTarget = getTouchTarget(child);
                            if (newTouchTarget != null) {
                                newTouchTarget.pointerIdBits |= idBitsToAssign;
                                break;
                            }

                            resetCancelNextUpFlag(child);
                            if (dispatchTransformedTouchEvent(ev,false,child,idBitsToAssign)) {
                                mLastTouchDownTime = ev.getDownTime();
                                if (preorderedList != null) {
                                    for (int j = 0; j < childrenCount; j++) {
                                        if (children[childIndex] == mChildren[j]) {
                                            mLastTouchDownIndex = j;
                                            break;
                                        }
                                    }
                                } else {
                                    mLastTouchDownIndex = childIndex;
                                }
                                mLastTouchDownX = ev.getX();
                                mLastTouchDownY = ev.getY();
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }

                            ev.setTargetAccessibilityFocus(false);
                        }
                        if (preorderedList != null) preorderedList.clear();
                    }

                    if (newTouchTarget == null && mFirstTouchTarget != null) {
                        newTouchTarget = mFirstTouchTarget;
                        while (newTouchTarget.next != null) {
                            newTouchTarget = newTouchTarget.next;
                        }
                        newTouchTarget.pointerIdBits |= idBitsToAssign;
                    }
                }
            }

            if (mFirstTouchTarget == null) {
                handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);
            } else {
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                while (target != null) {
                    final TouchTarget next = target.next;
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                    } else {
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)|| intercepted;
                        if (dispatchTransformedTouchEvent(ev, cancelChild,target.child,target.pointerIdBits)) {
                            handled = true;
                        }
                        if (cancelChild) {
                            if (predecessor == null) {
                                mFirstTouchTarget = next;
                            } else {
                                predecessor.next = next;
                            }
                            target.recycle();
                            target = next;
                            continue;
                        }
                    }
                    predecessor = target;
                    target = next;
                }
            }

            if (canceled||actionMasked==MotionEvent.ACTION_UP||actionMasked==MotionEvent.ACTION_HOVER_MOVE) {
                resetTouchState();
            } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
                final int actionIndex = ev.getActionIndex();
                final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
                removePointersFromTouchTargets(idBitsToRemove);
            }
        }

        if (!handled && mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
        }
        return handled;
    }

第一步:
清理和还原状态,请参见代码第15-18行
ACTION_DOWN是一系列Touch事件的开端,当Touch为ACTION_DOWN时须要进行一些初始化和还原操做。好比:清除以往的Touch状态(state)和开始新的手势(gesture)。因此在cancelAndClearTouchTargets( )中将mFirstTouchTarget设置为null,且在resetTouchState()中重置Touch状态标识

第二步:
检查是否须要ViewGroup拦截Touch事件,请参见代码第20-31行
在此详细分析该段代码:

  1. 请注意变量intercepted,请参见代码第20行
    该值用来标记ViewGroup是否拦截Touch事件的传递,它在后续代码中起着重要的做用.
  2. 事件为ACTION_DOWN或者mFirstTouchTarget不为null时检查是否须要ViewGroup拦截Touch事件,请参见代码第21行

    if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget !=null)

    ACTION_DOWN表示Touch事件是手指按下的事件,那么mFirstTouchTarget又是什么意思呢?mFirstTouchTarget是TouchTarget类的对象,而TouchTarget是ViewGroup中的一个内部类,它封装了被触摸的View及此次触摸所对应的ID,该类主要用于多点触控。好比:三个指头依次按到了同一个Button上。
    咱们没必要过多的理会TouchTarget,可是要重点关注mFirstTouchTarget。
    mFirstTouchTarget贯穿dispatchTouchEvent(),对于流程的走向发挥着相当重要的做用。
    (1) mFirstTouchTarget不为null
    表示ViewGroup没有拦截Touch事件而且子View消费了Touch
    (2) mFirstTouchTarget为null
    表示ViewGroup拦截了Touch事件或者虽然ViewGroup没有拦截Touch事件可是子View也没有消费Touch。总之,此时须要ViewGroup自身处理Touch事件

    若是ACTION_DOWN事件被子View消费(即mFirstTouchTarget!=null),当处理后续到来的ACTION_MOVE和ACTION_UP时仍会调用该代码判断是否须要拦截Touch事件。

    2.1 判断disallowIntercept(禁止拦截)标志位,请参见代码第22行
    ViewGroup能够拦截Touch事件,可是它的子View可调用 getParent().requestDisallowInterceptTouchEvent(true)禁止其父View的拦截。其实,从这个较长的方法名也能够看出来它的用途——禁止事件拦截;在该方法内部会改变FLAG_DISALLOW_INTERCEPT的值。

    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;

    因此利用此行代码判断是否禁止拦截(disallowIntercept)。

    在此请注意:
    ViewGroup中的requestDisallowInterceptTouchEvent( )方法能够用来禁止或容许ViewGroup拦截Touch事件,可是它对于ACTION_DOWN是无效的。
    也就是说子View能够禁止父View拦截ACTION_MOVE和ACTION_UP可是没法禁止父View拦截ACTION_DOWN。由于在ACTION_DOWN时会调用resetTouchState()重置了FLAG_DISALLOW_INTERCEPT的值致使子View对该值设置失效。因此,对于ACTION_DOWN事件ViewGroup总会调用onInterceptTouchEvent()判读是否要拦截Touch事件

    2.2 处理disallowIntercept的值为false的状况,请参见代码第23-25行
    若disallowIntercept(禁止拦截)的值为false,因此调用onInterceptTouchEvent()拦截Touch并将结果赋值给intercepted。
    常说ViewGroup的事件传递中的流程是:

    dispatchTouchEvent->onInterceptTouchEvent->onTouchEvent

    其实在这就是一个体现:dispatchTouchEvent()中调用了onInterceptTouchEvent()。

    2.3 处理disallowIntercept的值为true的状况,请参见代码第27行
    若disallowIntercept(禁止拦截)的值为true,表示不拦截Touch事件。
    因此将intercepted设置为false

  3. 将intercepted设置为true,请参见代码第30行
    若是不是ACTION_DOWN事件而且mFirstTouchTarget为null,那么直接将intercepted设置为true,表示ViewGroup拦截Touch事件。
    更加直白地说:若是ACTION_DOWN没有被子View消费(mFirstTouchTarget为null)那么当ACTION_MOVE和ACTION_UP到来时ViewGroup再也不去调用onInterceptTouchEvent()判断是否须要拦截而是直接的将intercepted设置为true表示由其自身处理Touch事件

第三步:
检查cancel,请参见代码第38行

第四步:
分发ACTION_DOWN事件,请参见代码第43-117行

if (!canceled && !intercepted)

若是Touch事件没有被取消也没有被拦截,那么ViewGroup将类型为ACTION_DOWN的Touch事件分发给子View。
在此梳理该阶段的主要逻辑。

  1. 计算Touch事件的坐标,请参见代码第56-57行
    在后续的判断中会依据坐标来判断触摸到了ViewGroup中的哪一个子View。
  2. 依据坐标,判断哪一个子View接收Touch事件,请参见代码第61-105行
    这部分代码的主要操做为:
    在找到能够接收Touch事件的子View后调用dispatchTransformedTouchEvent()方法将Touch事件派发给该子View。
    第一种状况:
    子View没有消费Touch事件则该方法的返回值为false,此时mFirstTouchTarget仍为null
    第二种状况:
    子View消费掉了Touch事件那么该方法的返回值为true,而后执行

    newTouchTarget = addTouchTarget(child, idBitsToAssign);

    在addTouchTarget()方法内将该子View添加到mFirstTouchTarget链表的表头,而且为mFirstTouchTarget设值使其不为null。随后将alreadyDispatchedToNewTouchTarget置为true,表示已经将Touch事件分发到了子View,或者说子View消费掉了Touch事件

小总结:
在这个步骤中只有找到了能够消费Touch事件的子View时mFirstTouchTarget才不为null;其他状况好比未找到能够接收Touch事件的子View或者子View不能消费Touch事件时mFirstTouchTarget仍为null

小疑惑:
请参见代码第78-82行:
为何newTouchTarget!=null就会执行break跳出for循环了呢?
还记得这个for循环的做用是什么吗?——寻找一个能够接受Touch事件的子View。
若是先有个指头按在了子View上(即ACTION_DOWN),而后另外一根指头又按在相同的子View上(即ACTION_POINTER_DOWN)。这种多点触摸的状况下两个指头按在了同一个View上,当第一指头按下的时候一个TouchTarget就已经记录了该子View,因此当第二个指头再按下的时候固然仍是由这个子View来处理Touch事件,也就是说没有再继续寻找的必要了。

第五步:
继续事件分发,请参见代码第119-147行
在第四步对于ACTION_DOWN事件作了一些特有处理,在此继续进行事件的分发。不管是ACTION_DOWN仍是ACTION_MOVE和ACTION_UP均会进入该步骤。
第一种状况:
mFirstTouchTarget==null
它表示Touch事件被ViewGroup拦截了根本就没有派发给子view或者虽然派发了可是在第四步中没有找到可以消费Touch事件的子View。
此时,直接调用dispatchTransformedTouchEvent()方法处理事件
第二种状况:
mFirstTouchTarget != null,表示找到了可以消费Touch事件的子View。
在该处亦有两种不一样的状况:

  1. 处理ACTION_DOWN,请参见代码第126-127行
    若是mFirstTouchTarget!=null则说明在第四步中Touch事件已经被消费,因此再也不作其余处理
  2. 处理ACTION_MOVE和ACTION_UP,请参见代码第129-143行
    调用dispatchTransformedTouchEvent()将事件分发给子View处理,请参见代码第130行

结合第四步和第五步,在此思考一个问题:
ViewGroup将ACTION_DOWN分发给子View,若是子View没有消费该事件,那么当ACTION_MOVE和ACTION_UP到来的时候系统还会将Touch事件派发给该子View么?
答案是否认的——若是子View没有处理ACTION_DOWN那么它就失去了处理ACTION_MOVE和ACTION_UP的资格。
在第四步中若是子View处理了ACTION事件那么mFirstTouchTarget不为null,当ACTION_MOVE和ACTION_UP到来时会跳过第四步进入到第五步。在第五步中就会判断mFirstTouchTarget是否为null,若是为空那么ViewGroup自身会处理Touch;若是不为空那么继续由mFirstTouchTarget处理Touch事件。

第六步:
清理数据和状态还原,请参见代码第149-155行
在手指抬起或者取消Touch分发时清除原有的相关数据


在分析dispatchTouchEvent()源码时屡次调用dispatchTransformedTouchEvent(),在次对其源码作一个简略的分析

private boolean dispatchTransformedTouchEvent(MotionEvent event,boolean cancel,View child,int desiredPointerIdBits) {
        final boolean handled;

        final int oldAction = event.getAction();
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }

        final int oldPointerIdBits = event.getPointerIdBits();
        final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;

        if (newPointerIdBits == 0) {
            return false;
        }


        final MotionEvent transformedEvent;
        if (newPointerIdBits == oldPointerIdBits) {
            if (child == null || child.hasIdentityMatrix()) {
                if (child == null) {
                    handled = super.dispatchTouchEvent(event);
                } else {
                    final float offsetX = mScrollX - child.mLeft;
                    final float offsetY = mScrollY - child.mTop;
                    event.offsetLocation(offsetX, offsetY);

                    handled = child.dispatchTouchEvent(event);

                    event.offsetLocation(-offsetX, -offsetY);
                }
                return handled;
            }
            transformedEvent = MotionEvent.obtain(event);
        } else {
            transformedEvent = event.split(newPointerIdBits);
        }


        if (child == null) {
            handled = super.dispatchTouchEvent(transformedEvent);
        } else {
            final float offsetX = mScrollX - child.mLeft;
            final float offsetY = mScrollY - child.mTop;
            transformedEvent.offsetLocation(offsetX, offsetY);
            if (! child.hasIdentityMatrix()) {
                transformedEvent.transform(child.getInverseMatrix());
            }

            handled = child.dispatchTouchEvent(transformedEvent);
        }

        transformedEvent.recycle();
        return handled;
    }

这段代码不算很复杂,先来瞅瞅官方文档的介绍

Transforms a motion event into the coordinate space of a particular child view,filters out irrelevant pointer ids, and overrides its action if necessary.If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.

该方法的主要做用是将Touch事件传递给特定的子View(即该方法的第三个输入参数child),由子Viwe继续分发处理Touch事件。
但咱们发现:在系统调用dispatchTransformedTouchEvent()时该方法的第三个参数有时候是一个子View(好比dispatchTouchEvent()源码中第85和130行),有时候又是null(好比dispatchTouchEvent()源码中第120行).
那么该方法第三个参数child是否为null对于Touch事件的分发有什么影响呢?
在dispatchTransformedTouchEvent()源码中可见屡次对于child是否为null的判断且均作出以下相似的操做:

if (child == null) { 
    handled = super.dispatchTouchEvent(event); 
 } else { 
     handled = child.dispatchTouchEvent(event); 
}
  1. child == null
    若是子View没有消费掉Touch事件,那么ViewGroup就将本身动手处理Touch事件,即super.dispatchTouchEvent(event)。此时,ViewGroup就化身为了普通的View,它会在本身的onTouch(),onTouchEvent()中处理Touch;这个过程以前已经分析过了,再也不赘述。

  2. child != null
    此时会调用该子View(固然该view多是一个View也多是一个ViewGroup)的dispatchTouchEvent()继续处理Touch,即child.dispatchTouchEvent(event)。

    小结:
    若是ViewGroup拦截了Touch事件或者子View不能消耗掉Touch事件,那么ViewGroup会在其自身的onTouch(),onTouchEvent()中处理Touch
    若是子View消耗了Touch事件父View就不能再处理Touch.

至此咱们就明白了:
Touch事件的传递顺序为
Activity–>外层ViewGroup–>内层ViewGroup–>View
Touch事件的消费顺序为
View–>内层ViewGroup–>外层ViewGroup–>Activity
其实,在咱们日常的工做中也能够见到相似的场景。
开发任务的派发顺序为
CEO–>CTO–>manager–>developer
开发任务的反馈顺序为
developer–>manager–>CTO–>CEO
公司要作一个APP,CEO会将该任务交给CTO;CTO又找到了项目经理,最后项目经理将该任务分配给了开发人员。
在开发人员分析完项目后发现本身能力没法胜任因而就将该问题抛给了项目经理,项目经理以为本身时间有限也完成不了又抛给了CTO,CTO一样由于某些因素没法按时完成该任务因而又将该任务抛给了CEO。
通过这么一圈折腾公司就以为这个开发人员技术不是特别好,因而与该项目有关的后续工做也就不会再让这个开发人员参与了。这就像刚才提到的同样:若是子View没有消费ACTION_DOWN那么ACTION_MOVE和ACTION_UP也就不会再派发给它。
这个过程与ViewGroup对于Touch事件的分发是很是相似的。


至此,ViewGroup对于Touch事件的分发处理的主要流程就分析完了。
为了梳理整个dispatchTouchEvent()的脉络,我又画了两个流程图。

这里写图片描述

该流程图描述Touch事件的传递和消费顺序。

在Touch事件的传递过程当中,若是上一级拦截了Touch那么其下一级就没法在收到Touch事件。
在Touch事件的消费过程当中,若是下一级消费Touch事件那么其上一级就没法处理Touch事件。

这里写图片描述

该流程描述了dispatchTouchEvent( )中对于Touch分发。
这部分源码稍微有些复杂。结合此图,能够将整个流程分为三个阶段:

  1. 判断是否须要拦截(intercepted)
  2. 处理ACTION_DOWN事件
  3. 利用mFirstTouchTarget是否为null继续处理Touch(ACTION_DOWN,ACTION_MOVE,ACTION_UP)

嗯哼,源码分析完了,流程图也有了,咱们再经过示例来验证和理解ViewGroup对于Touch事件的分发。

这里写图片描述

先来瞅瞅布局文件

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.stay4it.testtouch1.MainActivity">

    <com.stay4it.testtouch1.LinearLayoutSubclass  android:layout_width="match_parent" android:layout_height="match_parent">

        <com.stay4it.testtouch1.ButtonSubclass  android:id="@+id/button" android:layout_centerInParent="true" android:layout_width="500px" android:layout_height="500px" android:background="#FF3A9120" android:textSize="50px" android:text="Touch Me" />

    </com.stay4it.testtouch1.LinearLayoutSubclass>

</RelativeLayout>

此处,咱们在示例的布局文件中放入了一个自定义的LinearLayout和Button。
先来看看这个自定义的线性布局LinearLayoutSubclass

package com.stay4it.testtouch1;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.LinearLayout;

/** * 原创做者 * 谷哥的小弟 * * 博客地址 * http://blog.csdn.net/lfdfhl */
public class LinearLayoutSubclass extends LinearLayout {
    public LinearLayoutSubclass(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                System.out.println("---> LinearLayoutSubclass中调用dispatchTouchEvent()--->ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                System.out.println("---> LinearLayoutSubclass中调用dispatchTouchEvent()--->ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                System.out.println("---> LinearLayoutSubclass中调用dispatchTouchEvent()--->ACTION_UP");
            default:
                break;
        }
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                System.out.println("---> LinearLayoutSubclass中调用onInterceptTouchEvent()--->ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                System.out.println("---> LinearLayoutSubclass中调用onInterceptTouchEvent()--->ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                System.out.println("---> LinearLayoutSubclass中调用onInterceptTouchEvent()--->ACTION_UP");
            default:
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                System.out.println("---> LinearLayoutSubclass中调用onTouchEvent()--->ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                System.out.println("---> LinearLayoutSubclass中调用onTouchEvent()--->ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                System.out.println("---> LinearLayoutSubclass中调用onTouchEvent()--->ACTION_UP");
            default:
                break;
        }
        return super.onTouchEvent(ev);
    }
}

在这个自定义的线性布局中主要是输出一些便于验证的日志信息,好比在dispatchTouchEvent()和onInterceptTouchEvent()以及onTouchEvent()中对于ACTION_DOWN和ACTION_MOVE以及ACTION_UP均输出对于信息。

再来看看自定义的按钮ButtonSubclass

package com.stay4it.testtouch1;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.Button;

/** * 原创做者 * 谷哥的小弟 * * 博客地址 * http://blog.csdn.net/lfdfhl */
public class ButtonSubclass extends Button{
    public ButtonSubclass(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                System.out.println("---> ButtonSubclass中调用dispatchTouchEvent()--->ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                System.out.println("---> ButtonSubclass中调用dispatchTouchEvent()--->ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                System.out.println("---> ButtonSubclass中调用dispatchTouchEvent()--->ACTION_UP");
            default:
                break;
        }
        return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                System.out.println("---> ButtonSubclass中调用onTouchEvent()--->ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                System.out.println("---> ButtonSubclass中调用onTouchEvent()--->ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                System.out.println("---> ButtonSubclass中调用onTouchEvent()--->ACTION_UP");
            default:
                break;
        }
        return super.onTouchEvent(event);
    }
}

在这个自定义Button中的处理和LinearLayoutSubclass很是相似只不过View是没有onInterceptTouchEvent()罢了,故此,再也不赘述。

最后请看Activity的代码实现

package com.stay4it.testtouch1;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.MotionEvent;
/** * 原创做者 * 谷哥的小弟 * * 博客地址 * http://blog.csdn.net/lfdfhl */
public class MainActivity extends AppCompatActivity {
   private ButtonSubclass mButton;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init();
    }

    private void init(){
        mButton= (ButtonSubclass) findViewById(R.id.button);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                System.out.println("---> MainActivity中调用dispatchTouchEvent()--->ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                System.out.println("---> MainActivity中调用dispatchTouchEvent()--->ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                System.out.println("---> MainActivity中调用dispatchTouchEvent()--->ACTION_UP");
            default:
                break;
        }
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                System.out.println("---> MainActivity中调用onTouchEvent()--->ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                System.out.println("---> MainActivity中调用onTouchEvent()--->ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                System.out.println("---> MainActivity中调用onTouchEvent()--->ACTION_UP");
            default:
                break;
        }
        return super.onTouchEvent(ev);
    }
}

在该Activity中除了日志输出亦无其他操做。

当手指轻触Button再抬起后,咱们瞅瞅输出日志。
这里写图片描述
看到这些Log一切都是那么清晰明了

  1. ACTION_DOWN事件由外及里从Activity传递到Button
  2. Button处理了ACTION_DOWN事件
  3. ACTION_UP事件由外及里从Activity传递到Button
  4. Button处理了ACTION_UP事件

在这个过程当中把Touch事件从Activity传递到最里层某个子View的过程体现得很清楚和完整。可是对于Touch事件的消费过程怎么没有体现出来呢?不是说Touch事件的消费顺序和传递过程是反过来的么?在这里怎么没有体现呢?
嗯哼,这是由于Button在onTouchEvent()中执行

return super.onTouchEvent(event);

消耗了Touch事件,因此Touch事件就没有回传给LinearLayoutSubclass和Activity。
如今对刚才的代码作一点小小的修改:

  1. 在ButtonSubclass的onTouchEvent()中返回false
  2. 在LinearLayoutSubclass的onTouchEvent()中返回false

如今再次运行代码而且轻触Button后抬起手指,观察一下输出日志:
这里写图片描述

  1. ACTION_DOWN事件由外及里从Activity传递到Button。
  2. Button没有处理ACTION_DOWN事件,将其回传至LinearLayoutSubclass
  3. LinearLayoutSubclass也没有处理ACTION_DOWN事件,将其回传至Activty
  4. Activity处理ACTION_DOWN
  5. Activity处理ACTION_UP,再也不分发

嗯哼,在这里就看明白了若是子View没有处理Touch事件就会回传给父View,一层一层地往上回溯。在刚才这个过程当中没有一个子View处理ACTION_DOWN事件形成mFirstTouchTarget为null,因此当ACTION_UP事件发生时Activity再也不将其派发给子View而是本身处理了。这个过程在以前分析源码的时候也着重提到过。


至此,关于ViewGroup对于Touch事件的分发就所有分析完了。

PS:若是以为文章太长,那就直接看视频


who is the next one? ——> demo

相关文章
相关标签/搜索