我们经常使用的控件,如: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
通过getWindow得到当前Activity的window,window是抽象类,下面是Window的注解if (getWindow().superDispatchTouchEvent(ev)) { return true; }
从注解可以看到,Window的实现类是PhoneWindow,下面看看PhoneWindow.superDispathTouchEvent()* <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. */
@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,看下面一段代码:
这段代码是在上端代码之前执行的,当有ACTION_DOWN事件到来时,会调用resetTouchState()// 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(); }
/** * 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(),判断子元素是否在播放动画
这段代码也是在for语句里,先看 dispatchTransformedTouchEvent();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; }
如果child不为null,就调用子View的dispathchTouchEvent()来进行分发,完成一轮事件分发,否则就直接执行View类中的dispathTouchEvent来对事件进行处理,如果dispatchTransformedTouchEvent返回true,说明子View消耗了触摸事件,如果返回false,说明所有的子元素都没有被合适的处理,这种情况有两种: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; } }
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调用的机制等在下一篇博文中进行分析。
感谢大家的阅读,如有不足之处,还望指出!