Android View 事件分发源码分析

一. 概述

Android的事件分发主要有这几个角色:Activity、Window、ViewGroup和View。当Activity接收到事件时,会将事件传递给Window,然后Window将事件传递给顶层容器DecorView(继承自FrameLayout),事件分发由此开始。

这边我将对DOWN、MOVE和UP事件结合源码单独分析。

二. 源码分析

2.1 前言

首先先明确几个概念:

  1. 同一事件序列: 由一个DOWN事件,若干个MOVE事件,一个UP事件组成
  2. 新的一个事件序列开始前会重置所有的点击状态

当Activity接收到事件时,Activity的dispatchTouchEvent方法会被调用。

Activity.java

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

从代码中可以看到,Activity收到事件后将事件交由Windows处理

PhoneWindow.java

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

Window会将事件交由DecorView处理

private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
	 ...

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

}

由此可以看到事件传递给了ViewGroup,View事件分发由此开始。

2.2 DOWN事件

首先分析DOWN事件,当我们触摸手机屏幕的一瞬间,Activity接收到DOWN事件,事件由Activity传递到Window,再到DecorView。当DecorView接收到事件,会调用ViewGroup的dispatchTouchEvent方法。

由于是DOWN事件传递到ViewGroup,在dispatchTouchEvent方法中首先会重置触摸状态,包括清除保存处理事件View的单链表,因此每次DOWN事件代表一个新的事件序列的开始,这点之后会具体分析。

if (actionMasked == MotionEvent.ACTION_DOWN) {
    //重置所有的触摸状态
    cancelAndClearTouchTargets(ev);
    resetTouchState();
}

由于是DOWN事件,则先会去判断当前容器是否禁止拦截事件。默认情况下,父容器可以拦截事件,此时会调用onInterceptTouchEvent方法,该方法默认返回false;若父容器被禁止拦截事件,则不会调用onInterceptTouchEvent方法

ViewGroup#dispatchTouchEvent

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 {
    //拦截事件
    intercepted = true;
}

2.2.1 父容器不拦截事件

这里先看默认情况,事件没有被父容器拦截,即intercepted为false。此时会去遍历该ViewGroup的子View,寻找发生DOWN事件的View。若找到发生DOWN事件的View,将事件分发给对应的子View,若View能够处理事件,也就是子View的dispatchTouchEvent方法返回true,则将该处理事件的View加入mFirstTouchTarget这个链表中,并标记当前DOWN事件已被处理。

ViewGroup#dispatchTouchEvent

 TouchTarget newTouchTarget = null;
        boolean alreadyDispatchedToNewTouchTarget = false;
        if (!canceled && !intercepted) {
            //未取消未拦截

            if (actionMasked == MotionEvent.ACTION_DOWN
                    || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {


                //遍历所有的子View,寻找处理事件的View
                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 (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {

                        //找到处理事件的子View,保存该子View
                        newTouchTarget = addTouchTarget(child, idBitsToAssign);
                        //事件已经被子View处理
                        alreadyDispatchedToNewTouchTarget = true;
                        break;
                    }

                }
            }
        }

父ViewGroup通过调用dispatchTransformedTouchEvent 将事件分发给对应的子View。子View处理了DOWN事件,也就是子View的dispatchTouchEvent方法返回true,从而会使得dispatchTransformedTouchEvent方法返回true。

ViewGroup.java

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
        View child, int desiredPointerIdBits) {
  ...
    
    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);
    }

    // Done.
    transformedEvent.recycle();
    return handled;
}

若没有找到能够处理事件的子View,此时事件会交给当前ViewGroup来处理。没有找到处理DOWN事件的子View,也就是mFirstTouchTarget这个链表没有被赋值,此时为null。此时通过dispatchTransformedTouchEvent将事件传递给当前ViewGroup的父类,调用View的dispatchTouchEvent方法进行事件处理。

ViewGroup#dispatchTouchEvent

if (mFirstTouchTarget == null) {
    // No touch targets so treat this as an ordinary view.
    handled = dispatchTransformedTouchEvent(ev, canceled, null,
            TouchTarget.ALL_POINTER_IDS);
}

接下来事件传递到了View,下方是View的dispatchTouchEvent方法。

View.java

public boolean dispatchTouchEvent(MotionEvent ev){

        ...

        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }

        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }

事件到了View的dispatchTouchEvent方法,先会去判断事件是否由OnTouchListener消费掉并且View是否可用,若OnTouchListener返回true且View处于可用状态,则表示该DOWN事件被消费掉,该DOWN事件处理结束;若事件未被OnTouchListener消费掉或者View处于不可用状态,则将事件交由View的onTouchEvent方法

View.java

public boolean onTouchEvent(MotionEvent event) {
        final float x = event.getX();
        final float y = event.getY();
        final int viewFlags = mViewFlags;

        if ((viewFlags & ENABLED_MASK) == DISABLED) {
        	//当前View不可用
            if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
            //若View可点击或者可长按,则事件被消费
            return (((viewFlags & CLICKABLE) == CLICKABLE ||
                    (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
        }

    	...
        
        if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
                //若View可点击或者长按,则事件被消费
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
   					...
                    break;

            }

            return true;
        }

        return false;
    }
}
  • 若当前View处于不可用状态,但是View可以被点击或者长按,则该事件被消费,反之事件未被当前View消费,则将事件交由父容器处理。
  • 若当前View处于可用状态,并且View可以被长按或者点击,事件被消费,返回事件交由父容器处理。

2.2.2 父容器拦截了事件

上述2.1.1的前提是父容器没有拦截事件,也就是intercepted的值为false。若此时intercepted值未true(当onInterceptToucnEvent方法返回true)。此时不会去寻找处理事件的子View,也就是mFirstTouchTarget为null,同样事件会交由ViewGroup父类的dispatchTouchEvent方法处理。

2.2.3 DOWN事件总结

 

DOWN事件的分发流程如图所示,总的来说可以归纳一下几点:

  1. Activity接收DOWN事件后,将事件传递给Window,Window将事件分发给ViewGroup
  2. ViewGroup接收到DOWN事件,默认情况下ViewGroup会遍历所有子View,寻找发生触摸事件的子View,若找到子View且子View消费了DOWN事件,则ViewGroup会保存该处理事件的子View。
  3. 若ViewGroup拦截了事件,事件交由ViewGroup自己去处理,此时会调用ViewGroup父类的dispatchTouchEvent方法。在View处理事件时,若View可用并且OnTouchListener处理了事件,则DOWN事件被消费,DOWN事件分发结束;反之,则交由onTouchEvent方法处理DOWN事件
  4. 事件传递到了View的onTouchEvent方法中,只要View可点击或者长按,则事件一定被消费,反之,ViewGroup没有处理事件,事件交由父容器处理。

2.3 UP事件

上述2.2分析了DOWN事件的分发,接下来先分析UP事件的分发。 当手机抬起屏幕的一瞬间,Activity会接收到UP事件,Activity将UP事件传递给Window,Window将事件传递给DecorView,DecorView父类ViewGroup的dispatchTouchEvent方法被调用。

由于dispatchTouchEvent方法接收到的是UP事件,若mFirstTouchTarget不为空,此时代表存在处理DOWN和MOVE事件的子View。mFirstTouchTarget是一个链表,用于保存处理事件的子View,在DOWN事件被子View处理后在子View的父容器内被赋值,至于要用一个链表的原因是存在多点触控的情况,这里只考虑单点触控的事件分发。

2.3.1 存在处理DOWN和MOVE事件的子View

存在处理DOWN和MOVE事件的子View,也就是mFirstTouchTarget不为空。在mFirstTouchTarget不为空的情况下,会去判断当前容器是否禁止拦截事件。默认情况下为不拦截事件,此时会调用onInterceptTouchEvent方法,该方法默认返回false;

ViewGroup#dispatchTouchEvent

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 {
    //拦截事件
    intercepted = true;
}

2.3.1.1 当前容器不拦截UP事件

当intercepted为false时,此时为默认情况,代表当前容器不拦截UP事件,事件被分发给保存在mFirstTouchTarget的子View。

ViewGroup#dispatchToucnEvent

if (mFirstTouchTarget == null) {
    // No touch targets so treat this as an ordinary view.
    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;
            //将事件分发给子View
            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;
    }
}

可以看到事件传递到了dispatchTransformedTouchEvent内部,由于child不为null,则会调用child的dispatchTouchEvent方法将事件分发给子View。

ViewGroup.java

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
        View child, int desiredPointerIdBits) {
  ...
    
    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);
    }

    // Done.
    transformedEvent.recycle();
    return handled;
}

2.3.1.2 当前容器拦截UP事件

当intercepted的值未true时,代表当前UP事件被当前容器拦截。UP事件被当前容器拦截,但是之前的DOWN和MOVE事件都被子View处理了,此时mFirstTouchTarget不为空,所以此时走else分支,取消当前的UP事件,变为CANCEL事件,往下分发或者交由自己处理,并且此时会在遍历时清空保存在mFirstTouchTarget中处理事件的子View,最终mFirstTouchTarget的值为空。

ViewGroup#dispatchTouchEvent

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 {
                //由于事件被拦截,intercepted为true,cancelChild为true,代表取消事件
                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;
        }
}

由于是UP事件,最终会清除View的触摸状态

ViewGroup#dispatchTouchEvent

if (canceled
        || actionMasked == MotionEvent.ACTION_UP
        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
    resetTouchState();
}

2.3.2 不存在处理UP事件的子View

当mFirstTouchTarget为空时,不存在处理事件的子View,此时容器父类View的dispatchTouchEvent方法会接收到事件。

if (mFirstTouchTarget == null) {
        handled = dispatchTransformedTouchEvent(ev, canceled, null,
                TouchTarget.ALL_POINTER_IDS);
    } else {
    ...
}

事件传递到View的dispatchTouchEvent方法,同样先会去判断事件是否由OnTouchListener消费掉,若事件被消费且View可用,则该UP事件处理结束。若事件未被OnTouchListener消费掉或者View不可用,则将事件交由View的onTouchEvent方法。

View.java

public boolean dispatchTouchEvent(MotionEvent ev){

        ...

        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }

        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }

事件传递到View的onTouchEvent中,可以看到在UP的时候在条件满足的情况下会执行单击事件,同时事件被消费。

View.java

public boolean onTouchEvent(MotionEvent event) {
    final float x = event.getX();
    final float y = event.getY();
    final int viewFlags = mViewFlags;

    if ((viewFlags & ENABLED_MASK) == DISABLED) {
        //View不可用,但View可单击或者长按,同样可以消费事件
        return (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
    }

    if (((viewFlags & CLICKABLE) == CLICKABLE ||
            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
        //View 可点击或者长按
        switch (event.getAction()) {
            case MotionEvent.ACTION_UP:
                boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                    .. 

                    if (!mHasPerformedLongPress) {
                        removeLongPressCallback();
                        if (!focusTaken) {
                            if (mPerformClick == null) {
                                mPerformClick = new PerformClick();
                            }
                            //执行单击事件
                            if (!post(mPerformClick)) {
                                performClick();
                            }
                        }
                    }

                }
                break;

        }

        return true;
    }

    return false;
}

UP事件交给ViewGroup自己处理时,除了在UP事件会在条件满足下触发单击事件和事件未被消费时会交给Activity处理,其余的流程和ViewGroup处理DOWN事件类似。

2.3.3 UP事件总结

 

UP事件的分发流程如图所示,可以归纳为以下几点:

  1. Activity收到UP事件后,会将UP事件传递给Window,然后Window会将事件分发给ViewGroup。
  2. ViewGroup接收到UP事件后,若存在处理DOWN和MOVE事件的子View,则会去判断当前ViewGroup是否要拦截UP事件,默认情况下为不拦截;若不存在处理事件的子View,则表示该事件由ViewGroup自己处理。
  3. 当存在处理事件的View时,也就是mFirstTouchTarget这个链表不为空,则从中取出保存的View,将事件分发给该子View。
  4. 当不存在处理事件的View时,事件会由ViewGroup自己处理。当父类View处理事件时,若View可用且OnTouchListener消费了事件,则UP事件被消费;反之将UP事件交由onTouchEvent方法处理。
  5. 在onTouchEvent方法中,只要当前View是可单击或者可长按,则UP事件一定会被消费;反之,虽然事件往上传递,但父容器不会去处理事件,事件会交由Activity处理。
  6. 在onTouchEvent消费UP事件之前,在条件满足的情况下会触发单击事件。

2.4 MOVE事件

 

MOVE事件和UP事件流程类似,略微有些小差别,这边不再过多阐述,流程如图。好了,View事件分发机制的源码分析到这里就结束了,下一篇将介绍滑动冲突解决方式以及原理。

【附】相关架构及资料

资料领取

点赞+加群免费获取 Android IOC架构设计

加群领取获取往期Android高级架构资料、源码、笔记、视频。高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter全方面的Android进阶实践技术,群内还有技术大牛一起讨论交流解决问题。

猜你喜欢

转载自blog.csdn.net/weixin_44109213/article/details/88826017
今日推荐