事件分发源码解析 2

来源:互联网 发布:淘宝外卖那里送货 编辑:程序博客网 时间:2024/06/05 18:45
#事件分发源码解析


1 .##            **Activity的事件分发过程**
>当一个事件发生时,首先传递给Activity,由Activity的dispathTouchEvent来进行转发,具体的工作是由Activity内部的Window来完成的。Window一般就会将事件传递给decor view ,(decor view 一般就是当前界面的底层容器--即setContentView设置的View的父容器),可以通过**Activity.getWindow.getDecorView()**可以获得。
-  首先从Activity的dispatchTouchEvent开始分析
   
                         ``源码: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()
;
}
//Activity内部的Window进行事件分发,如果是true,整个事件结束.
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
//返回false 意味着事件没人处理,所有View的onToucheEvent都返回的false,那么Activity的onTouchEvent方法就会调用
return onTouchEvent(ev);
}
       
                                `    源码:Window #superDispatchTouchEvent`
/**
* Abstract base class for a top-level window look and behavior policy. An
* instance of this class should be used as the top-level view added to the
* window manager. It provides standard UI policies such as a background, title
* area, default key processing, etc.
* Window类可以控制顶级View的外观和行为策略,它的唯一实现位于android.view.PhoneWindow
* <p>The only existing implementation of this abstract class is
* android.view.PhoneWindow, which you should instantiate when needing a
* Window. 存在唯一的实现类 就是 android.view.PhoeWindow类
*/
public abstract class Window {
/**
* Used by custom windows, such as Dialog, to pass the touch screen event
* further down the view hierarchy. Application developers should
* not need to implement or call this.
*
*/
public abstract boolean superDispatchTouchEvent(MotionEvent event);

>那么Window是如何将事件传递给ViewGroup的。通过源码我们知道,Window是一个抽象类,它的superDispatchTouchEvent是一个抽象方法,因此我们需要找到Window的实现类。

                  ` 源码: Activity#attach重载方法`
final void attach···
mWindow = new PhoneWindow(this); //可以找到Window的实例是一个PhoneWindow ···

> 我们就找到了Window的实现类 ---》,实际上在Window类的描述中,就已经说明了该类是唯一的实现类
2 . ##            **Window的事件分发过程**
>找到了PhoneWindow ,那么 PhoneWindow的superDispatchToucheEvent是具体怎么处理的。

                                    PhoneWindow #superDispatchTouchEvent
public boolean superDispatchTouchEvent(MotionEvent event){                                return mDecor.superDispatchTouchEvent(event);    
}    
private DecorView mDecor;
@Override
public final View
getDecorView(){
if(mDecor == null){
installDecor()
;
}
return mDecor
>这里逻辑就很清晰了:  PhoneWindow 将事件传递个了DecorView,那么这个DecorView是什么?
public final class DecorView extends FrameLayout implements RootViewSurfaceTacker{    
//This is the top-level view of the window ,containing the wiondow decor.                
>我们知道 
         
                                                  Activity
/**
* 获取Activity所设置的View,这个mDecor显然就是getWindow().getDecorView()返回的View.
*/
View view = ((ViewGroup) getWindow().getDecorView().findViewById(android.R.id.content)).getChildAt(0);
view 就是我们setContentView 的View,是mDecor的子View。


>从这里开始 ,事件已经由mDecor传递给顶级View了(就是我们在Activity中setContentView设置的View),另外顶级View也叫根View,一般来说都是ViewGroup

3.##            **顶级View(一般是ViewGroup)的事件分发过程**

1.ViewGroup对点击事件的分发过程。其主要实现在ViewGroup的dispatchTouchEvent方法中。
               
                  `  `ViewGroup # dispatchTouchEvent`
// Check for interception.
final boolean intercepted;//ViewGroup是否拦截事件。
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {//当是DOWN事件 或者 ViewGroup不拦截子,由子类处理时
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;//这个标志为是通过子类调用requestDisallowInterceptTouchEvent方法来设置的。表示父类不要拦截事件。
if (!disallowIntercept) {//当前ViewGroup允许拦截
intercepted = onInterceptTouchEvent(ev)
;//调用拦截方法,判断是否拦截
ev.setAction(action); // restore action in case it was changed
} else {
intercepted =
false; //如果子类请求不许拦截,就交给子类处理,并且给mFirstTouTarget 赋值。
}
}
else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
一旦事件由当前ViewGroup拦截时,那么mFirstTouchTarget 不会赋值。
那么以后的所有事件到来时,Move和up 事件  。down除外。
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
判断都是false 就不会进入,即ViewGroup的onInterceptTouchEvent不会在被调用,同一序列的所有事件都默认交给当前ViewGroup进行处理。

当面对DOWN事件时,ViewGroup会重置FLAG_DISALLOW_INTERCEPT标志位,这就导致View中设置这个标志为无效。ViewGroup总会调用自己的onInterceptTouchEvent方法来询问自己是否拦截事件,所以这样FLAG_DISALLOW_INTERCEPT标志位一旦被子类调用requestDiaallowInterceptTouchEvent设置后,除了DOWN事件,其他事件都可不被拦截。

             `  `ViewGroup # dispatchTouchEvent`
// Handle an initial down.//每一个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();//FLAG_DISALLOW_INTERCEPT标志位被充值。
}

/**
* Resets all touch state in preparation for a new cycle.//重置所有的触摸状态准备一个新的序列。
*/
private void resetTouchState() {
clearTouchTargets()
;
resetCancelNextUpFlag(this);
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
mNestedScrollAxes = SCROLL_AXIS_NONE;
}
两条结论:
  1.  onInterceptTouchEvent方法不是每次都会被调用,如果我们想提前处理所有的点击事件,我们应在选择dispatchTouchEvent方法。只有这个方法每次都会被调用。
  2.FLAG_DISALLOW_INTERCEPT解决滑动冲突。

当ViewGroup不拦截的时候,事件会下发给它的子类进行处理。
        
                      `ViewGroup # dispatchTouchEvent`

final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);//获取当前事件的点击坐标
final float y = ev.getY(actionIndex);
// Find a child that can receive the event.找一个能够接受事件的子类
// Scan children from front to back.遍历所有的子类

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 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;
}
//Returns true if a child view can receive pointer event
if (!canViewReceivePointerEvents(child)//检测child是否能够接受点击事件
Returns true if a child view contains the specified point when transformed
into its coordinate space.
Child must not be null. //检测 (x,y)点是否在child中
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(
false);
continue;
}

newTouchTarget = getTouchTarget(child)
;
if (newTouchTarget != null) {
// Child is already receiving touch within its bounds.
// Give it the new pointer in addition to the ones it is handling.
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}

resetCancelNextUpFlag(child); /
**
 * 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.
dispatchTransformedTouchEvent实际上就是调用的child的dispatchTouchEvent 
*
     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(); //如果子元素的disspatchTouchEvent返回的是ture,那么mFirstTouchTarget就会被赋值,同时跳出for循环。
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget= true;
break; //上面3行代码完成了对mFirstTouchTarget的赋值并终止了定义子元素的遍历。如果子元素的dispatchTouchEvent返回的是false,ViewGroup就会把事件传递给下一个子元素(如果有下一个子元素的话)。
} ···
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS); ···

 

                                ViewGroup # dispatchTransformedTouchEvent`
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) {
//如果传入的child为空,就转给View的分发事件方法。
handled =
super.dispatchTouchEvent(event);
} else {//否则交给child分发事件
handled = child.dispatchTouchEvent(event)
;
}
event.setAction(oldAction)
;
return handled;
}
/**
* 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;
}



  分析:1.首先遍历ViewGroup的所有子元素,然后判断子元素是否可以接受到点击事件。能否接受点击事件主要有两点衡量:子元素是否在播放动画和点击事件的坐标落在子元素的区域内。如果子元素满足,事件就会传递给它处理。dispatchTransformedTouchEvent实际上就是调用的子元素的dispatchTouchEvent
如果子元素的dispatchTouchEvent返回的是true,那么mFirstTouchTarget就会被赋值
其实mFirstTouchTarget真正的赋值过程是在addTouchTarget内部完成的。
mFirstTouchTarget是一种单链表结构,mFristTouchTarget是否被赋值,将之间影响ViewGroup对事件的拦截策略。如果mFristTouchTarget == null,ViewGroup就会默认拦截下来同一序列中所有的点击事件。

   2。如果遍历所有的子元素后事件都没有被适合的处理,这包含两种情况。
        1.ViewGroup没有子元素;
        2.子元素处理了点击事件,但是在dispatchTouchEvent中返回了false,这一般是因为子元素在onTouchEvent中返回了false.
    
    这两种情况下,ViewGroup会自己处理点击事件。
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view. //这里判断mFirstTouchTarget== null ,第三个参数传入了null,根据上面的分析,我们知道,如果传入的为null,那么就会调用View的dispatchTouchEvent方法
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS)

    所以这里就转到了View的dispatchTouchEvent方法,即点击事件开始交给View来处理。


    4.##            **View对点击事件的处理过程**(这里的View不包含ViewGroup)

View的分发方法    dispatchTouchEvent
                           
                                ` View#dispatchTouchEvent`
    
boolean result = false;

if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}

final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
stopNestedScroll();
}

if (onFilterTouchEventForSecurity(event)) {
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null//判断有没有OnTouCHListener
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {//并且返回true
result =
true;
}

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

View对点击事件的处理过程就比较简单,因为View是一个单独的元素,它没有子元素因下次无法向下传递,所以它只能自己处理事件。
    处理过程:首先判断有没有设置OnTouchListener,如果OnTouchListener中的onTouch方法返回true,那么onTouchEvent方法就不会调用。说明OnTouchListener 比  onTouchEvent的优先级高。这样方便在外界处理点击事件。



View的onTouchEvent方法

                                View#onTouchEvent

/**
* Implement this method to handle touch screen motion events.
* <p>
* If this method is used to detect click actions, it is recommended that
* the actions be performed by implementing and calling
* {@link #performClick()}. This will ensure consistent system behavior,
* including:
* <ul>
* <li>obeying click sound preferences
* <li>dispatching OnClickListener calls
* <li>handling {@link AccessibilityNodeInfo#ACTION_CLICK ACTION_CLICK} when
* accessibility features are enabled
* </ul>
*
* @param event The motion event.
* @return True if the event was handled, false otherwise.
*/
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
先看以下View处于不可用状态下点击事件的处理过程,可以看到不可用状态下的View,依然会消耗点击事件,尽管它看起来不可用。

if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
一个可点击但不可用的View依然会消费触摸事件,它只是不能响应他们
return (((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags &
CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
} //如果View设置的代理,那么还会执行TouchDelegate的onTouchEvent方法,这个onTouceEvent的工作机制看起来和OnTouchListener类似。
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
    下面就是onTouchEvent对点击事件的具体处理
//只要View的CLICKABLE和LONG_CLICKABLE有一个为true,那么它就会消耗这个事件。即                onTouchEvent返回的就是true,而不管它是不是DISABLE.
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags &
LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags &
CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
switch (action) {
case MotionEvent.ACTION_UP://在ACTION_UP事件发生时,会触发performClick方法
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus()
;
}

if (prepressed) {
// The button is being released before we actually
// showed it as pressed. Make it show the pressed
// state now (before scheduling the click) to ensure
// the user sees it.
setPressed(true, x, y);
}

if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// This is a tap, so remove the longpress check
removeLongPressCallback();

// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick();//如果View设置了OnTouchListener,那么performClick()方法内部会调用它的onClick方法
}
}
}

···
如果View设置了OnTouchListener,那么performClick()方法内部会调用它的onClick方法
                       
                            `View#performClick`
/**
* Call this view's OnClickListener, if it is defined. Performs all normal
* actions associated with clicking: reporting accessibility event, playing
* a sound, etc.
*
* @return True there was an assigned OnClickListener that was called, false
* otherwise is returned.
*/
public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {//点击监听器不为null
playSoundEffect(SoundEffectConstants.
CLICK);
li.mOnClickListener.onClick(this);//执行点击事件
result = true;
} else {
result =
false;
}

sendAccessibilityEvent(AccessibilityEvent.
TYPE_VIEW_CLICKED);
return result;
}
NOTE:
View的LONG_CLICKABLE (长点击)属性默认为false, 而 CLICKABLE属性是否为false和具体的View有关。确切的说,可点击的View其CLICKABLE = true; 不可点击的View的CLICKABLE = false;
通过setCLickable setLongCLickable 可以分别改变VIew的 CLICKABLE 和 LONG_CLICKABLE 属性。
另外,setOnClickListener会自动将View的CLICKABLE设为true,setOnLongClickListener则会将LONG_CLICKABLE 设为true

/**
* Register a callback to be invoked when this view is clicked. If this view is not
* clickable, it becomes clickable.
*
* @param l The callback that will run
*
* @see #setClickable(boolean)
*/
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(
true);//设为可点击
}
getListenerInfo().
mOnClickListener = l;
}
/**
* Register a callback to be invoked when this view is clicked and held. If this view is not
* long clickable, it becomes long clickable.
*
* @param l The callback that will run
*
* @see #setLongClickable(boolean)
*/
public void setOnLongClickListener(@Nullable OnLongClickListener l) {
if (!isLongClickable()) {
setLongClickable(
true);//设为可长按
}
getListenerInfo().
mOnLongClickListener = l;
}



ListenerInfo getListenerInfo() {
if (mListenerInfo != null) {
return mListenerInfo;
}
mListenerInfo = new ListenerInfo();
return mListenerInfo;
}
ListenerInfo是View的一个静态内部类,里面包含可用的监听声明,

static class ListenerInfo {  /*  * Listener used to dispatch focus change events.
     * This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
protected OnFocusChangeListener mOnFocusChangeListener;

/**
* Listeners for layout change events.
*/
private ArrayList<OnLayoutChangeListener> mOnLayoutChangeListeners;

protected OnScrollChangeListener mOnScrollChangeListener;

/**
* Listeners for attach events.
*/
private CopyOnWriteArrayList<OnAttachStateChangeListener> mOnAttachStateChangeListeners;

/**
* Listener used to dispatch click events.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
public OnClickListener mOnClickListener;

/**
* Listener used to dispatch long click events.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
protected OnLongClickListener mOnLongClickListener;

/**
* Listener used to dispatch context click events. This field should be made private, so it
* is hidden from the SDK.
* {@hide}
*/
protected OnContextClickListener mOnContextClickListener;

/**
* Listener used to build the context menu.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
protected OnCreateContextMenuListener mOnCreateContextMenuListener;

private OnKeyListener mOnKeyListener;

private OnTouchListener mOnTouchListener;

private OnHoverListener mOnHoverListener;

private OnGenericMotionListener mOnGenericMotionListener;

private OnDragListener mOnDragListener;

private OnSystemUiVisibilityChangeListener mOnSystemUiVisibilityChangeListener;

OnApplyWindowInsetsListener mOnApplyWindowInsetsListener;
}




















































0 0
原创粉丝点击