您的位置:首页 > 移动开发 > Android开发

Android——View事件分发机制

2016-05-21 23:16 441 查看
我们经常使用的控件,如: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调用的机制等在下一篇博文中进行分析。


感谢大家的阅读,如有不足之处,还望指出!




                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android 控件 源码