Android——View事件分发机制

我们经常使用的控件,如:TextView,Button,ListVIew等控件,他们都是继承自View的,当我们点击一个View控件后,会触发控件的一些行为,那这些行为是如何通过Activity传递到View中的,下面我们通过源码来进行了解:

首先,当我们在一个Activity上点击一个控件的时候,会触发Activity.dispatchTouchEvent(),从方法的字面意思就可以看出此方法就是对触摸行为进行分发

   /**
     * Called to process touch screen events.  You can override this to
     * intercept all touch screen events before they are dispatched to the
     * window.  Be sure to call this implementation for touch screen events
     * that should be handled normally.
     *
     * @param ev The touch screen event.
     *
     * @return boolean Return true if this event was consumed.
     */
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }
注解说,这个方法是用来处理在屏幕上的触摸事件的,可以重写这个方法来让事件不能分发到window,如果这个事件被消耗掉的话,应该返回true。

当有ACTION_DOWN事件来的时候,会调用onUserInteraction

   /**
     * Called whenever a key, touch, or trackball event is dispatched to the
     * activity.  Implement this method if you wish to know that the user has
     * interacted with the device in some way while your activity is running.
     * This callback and {@link #onUserLeaveHint} are intended to help
     * activities manage status bar notifications intelligently; specifically,
     * for helping activities determine the proper time to cancel a notfication.
     *
     * <p>All calls to your activity's {@link #onUserLeaveHint} callback will
     * be accompanied by calls to {@link #onUserInteraction}.  This
     * ensures that your activity will be told of relevant user activity such
     * as pulling down the notification pane and touching an item there.
     *
     * <p>Note that this callback will be invoked for the touch down action
     * that begins a touch gesture, but may not be invoked for the touch-moved
     * and touch-up actions that follow.
     *
     * @see #onUserLeaveHint()
     */
    public void onUserInteraction() {
    }

这个方法是空实现,回调的时机是当按键、触屏或者轨迹球事件初始化时

然后再看onUserLeaveHint,他们两个是相铺相成的

  /**
     * Called as part of the activity lifecycle when an activity is about to go
     * into the background as the result of user choice.  For example, when the
     * user presses the Home key, {@link #onUserLeaveHint} will be called, but
     * when an incoming phone call causes the in-call Activity to be automatically
     * brought to the foreground, {@link #onUserLeaveHint} will not be called on
     * the activity being interrupted.  In cases when it is invoked, this method
     * is called right before the activity's {@link #onPause} callback.
     *
     * <p>This callback and {@link #onUserInteraction} are intended to help
     * activities manage status bar notifications intelligently; specifically,
     * for helping activities determine the proper time to cancel a notfication.
     *
     * @see #onUserInteraction()
     */
    protected void onUserLeaveHint() {
    }

注解上说,这个方法调用的时机是activity有意图去进入后台中,比如:当用户按下“home”或者后退键,它会在onPause方法调用之前进行调用的,如果是因为一个Activity进入而使其进入到后台中,这种情况onUserLeaveHint是不会调用的。

onUserInteraction 与 onUserLeaveHint方法一般是用于对通知栏进行管理。
 
好了,回过来继续看dispatchTouchEvent
if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
通过getWindow得到当前Activity的window,window是抽象类,下面是Window的注解
 * <p>The only existing implementation of this abstract class is
 * android.policy.PhoneWindow, which you should instantiate when needing a
 * Window.  Eventually that class will be refactored and a factory method
 * added for creating Window instances without knowing about a particular
 * implementation.
 */
从注解可以看到,Window的实现类是PhoneWindow,下面看看PhoneWindow.superDispathTouchEvent()
 @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }

mDecor就是DecorView的一个对象:
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {}

DecorView就是Activity中的顶级View,可以通过getWindow().getDecoreView()来获得
然后看DecorView中的superDispatchTouchEvent():
public boolean superDispatchTouchEvent(MotionEvent event) {
            return super.dispatchTouchEvent(event);
        }

可以看到它是回到父类的方法,FrameLayout又是继承自GroupView的,我们直接看GroupView中的dispatchTouchEvent():
这个方法中的代码比较多,但我们只是分析View事件的分发机制,所有我就一部分一部分的截:
 // Check for interception.
            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;
            }

这一段逻辑是检查是否要中断,有两种情况会判断是否要进行中断检查,第一种是当触发MotionEvent.ACTION_DOWN,第二种是判断mFristTouchTarget是否为null,第一种好理解,第二种mFristTouchTarget这个字段,从后面源码我们会了解到,先在这里说一下,这个字段会在当有View消了MotionEvent.Action_DOWN事件,mFristTouchTarget就会不为null,如果没有View消耗Motion.ACTION_DOWN事件,那么mFristTouchTarget == null,在ACTION_MOVE、ACTION_UP事件中,就会执行else语句,也就是把intercepted设为ture,这样ACTION_DOWN之后的事件都会交由它处理,而不会向下进行分发。
要想执行onIntercepTouchEvent(),disallowIntercept得为false,disallowIntercept是通过FLAG_DISALLOW_INTERCEPT标记位进行控制的,这个标记位可以通过requestDisallowInterceptTouchEvent来进行设置,这个方法是ViewParent中的方法,在ViewParent中实现了这个方法,在子view中使用。当dispatchIntercept被设置了,viewGroup就中断不了除了ACTION_DOWN的其他事件,所以在这里给我们提供一个处理滑动冲突的解决方法。
为什么说除了ACTION_DOWN,看下面一段代码:
// Handle an initial down.
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                // Throw away all previous state when starting a new touch gesture.
                // The framework may have dropped the up or cancel event for the previous gesture
                // due to an app switch, ANR, or some other state change.
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }
这段代码是在上端代码之前执行的,当有ACTION_DOWN事件到来时,会调用resetTouchState()
/**
     * Resets all touch state in preparation for a new cycle.
     */
    private void resetTouchState() {
        clearTouchTargets();
        resetCancelNextUpFlag(this);
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        mNestedScrollAxes = SCROLL_AXIS_NONE;
    }

这里面,就是重置触摸的一些状态,首先在clearTouchTargets()会把mFristTouchTarget制为null,然后会对FLAG_DISALLOW_INTERCEPT进行重置,这样就不会对ACTION_DOWN事件就行中断,而是调用onInterceptTouchEvent(),让ViewGroup来决定是否要中断。
接下来看一段代码:
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 there is a view that has accessibility focus we want it
                            // to get the event first and if not handled we will perform a
                            // normal dispatch. We may do a double iteration but this is
                            // safer given the timeframe.
                            if (childWithAccessibilityFocus != null) {
                                if (childWithAccessibilityFocus != child) {
                                    continue;
                                }
                                childWithAccessibilityFocus = null;
                                i = childrenCount - 1;
                            }

                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                ev.setTargetAccessibilityFocus(false);
                                continue;
                            }
}
这一段代码,是当ViewGroup不中断事件,就会对ViewGroup中的子View进行遍历,要得到能够接受事件的子元素,必须要满足两个条件:
1,canViewReceivePointerEvents(child),判断子元素是否在用户点击的区域内
2,isTransformedTouchPointInView(),判断子元素是否在播放动画
 resetCancelNextUpFlag(child);
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                // Child wants to receive touch within its bounds.
                                mLastTouchDownTime = ev.getDownTime();
                                if (preorderedList != null) {
                                    // childIndex points into presorted list, find original index
                                    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;
                            }
这段代码也是在for语句里,先看 dispatchTransformedTouchEvent();
 private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;

        // Canceling motions is a special case.  We don't need to perform any transformations
        // or filtering.  The important part is the action, not the contents.
        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;
        }
}
如果child不为null,就调用子View的dispathchTouchEvent()来进行分发,完成一轮事件分发,否则就直接执行View类中的dispathTouchEvent来对事件进行处理,如果dispatchTransformedTouchEvent返回true,说明子View消耗了触摸事件,如果返回false,说明所有的子元素都没有被合适的处理,这种情况有两种:
1)ViewGroup中没有子元素
2)子元素处理了事件,但是在dispathTouchEvent中返回可false,一般都是在onTouchEvent中返回了false。
还记得上面我们说的,当子View消耗了触摸事件,就会对mFristTouchTarget进行赋值,这个过程就是在
addTouchTarget()方法中进行的
    /**
     * Adds a touch target for specified child to the beginning of the list.
     * Assumes the target child is not already present.
     */
    private TouchTarget addTouchTarget(View child, int pointerIdBits) {
        TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
        target.next = mFirstTouchTarget;
        mFirstTouchTarget = target;
        return target;
    }

可以看到,把target赋给了mFirstTouchTarget
 
可以看到,view事件分发是从Activity-->Window-->View
 
到此,view事件的分发机制差不多就分析完了,至于view对点击事件的处理过程以及view的onTouch、onTouchEvent、onClick调用的机制等在下一篇博文中进行分析。
 
感谢大家的阅读,如有不足之处,还望指出!


 
 
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值