Android源码解读——事件分发原理

首先我们需要了解,当屏幕被点击之后,事件会经理哪些流程?

那么ViewGroup的dispatchTouchEvent与View的dispatchTouchEvent有什么区别呢?

  • ViewGroup---》dispatchTouchEvent---》事件分发
  • View          ---》dispatchTouchEvent---》事件处理
  • ViewGroup继承自View,重写了View的dispatchTouchEvent方法实现事件分发功能,因为ViewGroup是容器而View不是容器,所以view里面的dispatchTouchEvent是不会进行事件分发的,只做事件处理。

 

有事件就会有冲突,那么什么是事件冲突呢?

通俗来说,事件只有一个,而多个人想要处理该事件,如果处理该事件的对象不是我们希望的对象,那么久可以说发生了事件冲突。

 

先了解view的事件处理

按照图1的时序图,我们先了解view的事件处理,之后再看事件分发的时候,事件处理我们就可以不用再管了,先看如下代码:

     tv_test = findViewById(R.id.tv_test);
        tv_test.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.e("test", "onClick");
            }
        });

        tv_test.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                Log.e("test", "onTouch");
                return false;//注释111
            }
        });

在“注释111”处,如果return false;则onclick和onTouch都会打印,如果return true;那么只会打印onTouch不会打印onClick,这点我们都知道,但是是什么原因导致这种情况的发生呢?

进入到源码,看view的dispatchTouchEvent是如何处理的,我们会发现在dispatchTouchEvent方法里面有这样一段代码

if (onFilterTouchEventForSecurity(event)) {
  //if 1
  if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {     result = true;   }   //noinspection SimplifiableIfStatement   ListenerInfo li = mListenerInfo;
  // if 2
  if (li != null && li.mOnTouchListener != null     && (mViewFlags & ENABLED_MASK) == ENABLED     && li.mOnTouchListener.onTouch(this, event)) {       result = true;   }   //if 3   if (!result && onTouchEvent(event)) {     result = true;   } }

可以看到,if2里面执行了li.mOnTouchListener.onTouch,这个onTouch就是执行了我们代码里面onTouchListener里面的onTouch方法

在这个if判断里面用的是三个&&判断,我们都知道&&判断是有短路的,当前面的某个条件等于false,后面的判断就不会执行了,所以看onTouch是否执行首先要看前面的三个条件是否都返回true

在view的setOnTouchListener方法里面执行了这样一行代码

public void setOnTouchListener(OnTouchListener l) {
  getListenerInfo().mOnTouchListener = l;
}

我们的tv_text在.setOnTouchListener的时候就会直接进入view的这个方法,该方法里面的getListenerInfo()方法会直接初始化mListenerInfo

因此li != null和li.mOnTouchListener != null返回的都是true

第三个条件(mViewFlags & ENABLED_MASK) == ENABLED意思是使能,那么我们点击按钮的时候使能肯定是为true的

因此正常情况下,只要我们控件实现了setOnTouchListener监听,那么onTouch是必然会执行的。

终上所述可得,当我们onTouch返回为true的时候,源码里面的result=true,当我们onTouch返回为false的时候,源码里面的result=false,所以我们的onTouch返回值直接会影响到result。

当我们代码里面的onTouch返回为false的时候,我们紧跟着看if3的判断。

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

这也是一个短路语,如果result为false则会执行onTouchEvent(event)条件。进入该方法,可以看到源码里面有对若干action的switch判断

我们都知道onClick执行是在ACTION_UP的时候,那么在这个case里面有一段非常关键的代码

if (!post(mPerformClick)) {
  performClickInternal();
}

我们进入performClickInternal()方法

private boolean performClickInternal() {
  // Must notify autofill manager before performing the click actions to avoid scenarios where
  // the app has a click listener that changes the state of views the autofill service might
  // be interested on.
  notifyAutofillManagerOnClick();

  return performClick();
}

再进入它返回的performClick()方法

public boolean performClick() {
        // We still need to call this method to handle the cases where performClick() was called
        // externally, instead of through performClickInternal()
        notifyAutofillManagerOnClick();

        final boolean result;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }

        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);

        notifyEnterOrExitForAutoFillIfNeeded(true);

        return result;
    }

里面有一个if判断,这个if判断是否感觉非常熟悉呢?这个if在我们onTouch的时候就已经得知li != null和li.mOnTouchListener != null返回的都是true,因此会执行if里面的语句

在if里面执行完onClick之后,result会直接返回true,这也是为什么onClick事件没有返回值而onTouch事件有返回值的原因。在我们执行完onClick之后,系统会默认直接返回true,表示该事件已经处理完毕。

playSoundEffect(SoundEffectConstants.CLICK);表示我们点击的时候所发出的声音,感兴趣的可以去看一下。

至此,我们的view事件处理流程就分析完毕了。也知道了为什么onTouch返回true时会影响到onClick。

 

源码解读ViewGroup的事件分发流程

对照上图来说能够更加直观的了解,下面的部分我们全部都是站在“总经理”的角度来分析,也就是DecorView的角度,贴上ViewGroup的dispatchTouchEvent方法代码,一步一步分析

  1   @Override
  2     public boolean dispatchTouchEvent(MotionEvent ev) {
  3         if (mInputEventConsistencyVerifier != null) {
  4             mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
  5         }
  6 
  7         // If the event targets the accessibility focused view and this is it, start
  8         // normal event dispatch. Maybe a descendant is what will handle the click.
  9         if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
 10             ev.setTargetAccessibilityFocus(false);
 11         }
 12 
 13         boolean handled = false;
 14         if (onFilterTouchEventForSecurity(ev)) {
 15             final int action = ev.getAction();
 16             final int actionMasked = action & MotionEvent.ACTION_MASK;
 17 
 18             // Handle an initial down.
 19             if (actionMasked == MotionEvent.ACTION_DOWN) {
 20                 // Throw away all previous state when starting a new touch gesture.
 21                 // The framework may have dropped the up or cancel event for the previous gesture
 22                 // due to an app switch, ANR, or some other state change.
 23                 cancelAndClearTouchTargets(ev);
 24                 resetTouchState();
 25             }
 26 
 27             // Check for interception.
 28             final boolean intercepted;
 29             if (actionMasked == MotionEvent.ACTION_DOWN
 30                     || mFirstTouchTarget != null) {
 31                 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
 32                 if (!disallowIntercept) {
 33                     intercepted = onInterceptTouchEvent(ev);
 34                     ev.setAction(action); // restore action in case it was changed
 35                 } else {
 36                     intercepted = false;
 37                 }
 38             } else {
 39                 // There are no touch targets and this action is not an initial down
 40                 // so this view group continues to intercept touches.
 41                 intercepted = true;
 42             }
 43 
 44             // If intercepted, start normal event dispatch. Also if there is already
 45             // a view that is handling the gesture, do normal event dispatch.
 46             if (intercepted || mFirstTouchTarget != null) {
 47                 ev.setTargetAccessibilityFocus(false);
 48             }
 49 
 50             // Check for cancelation.
 51             final boolean canceled = resetCancelNextUpFlag(this)
 52                     || actionMasked == MotionEvent.ACTION_CANCEL;
 53 
 54             // Update list of touch targets for pointer down, if needed.
 55             final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
 56             TouchTarget newTouchTarget = null;
 57             boolean alreadyDispatchedToNewTouchTarget = false;
 58             if (!canceled && !intercepted) {
 59 
 60                 // If the event is targeting accessibility focus we give it to the
 61                 // view that has accessibility focus and if it does not handle it
 62                 // we clear the flag and dispatch the event to all children as usual.
 63                 // We are looking up the accessibility focused host to avoid keeping
 64                 // state since these events are very rare.
 65                 View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
 66                         ? findChildWithAccessibilityFocus() : null;
 67 
 68                 if (actionMasked == MotionEvent.ACTION_DOWN
 69                         || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
 70                         || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
 71                     final int actionIndex = ev.getActionIndex(); // always 0 for down
 72                     final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
 73                             : TouchTarget.ALL_POINTER_IDS;
 74 
 75                     // Clean up earlier touch targets for this pointer id in case they
 76                     // have become out of sync.
 77                     removePointersFromTouchTargets(idBitsToAssign);
 78 
 79                     final int childrenCount = mChildrenCount;
 80                     if (newTouchTarget == null && childrenCount != 0) {
 81                         final float x = ev.getX(actionIndex);
 82                         final float y = ev.getY(actionIndex);
 83                         // Find a child that can receive the event.
 84                         // Scan children from front to back.
 85                         final ArrayList<View> preorderedList = buildTouchDispatchChildList();
 86                         final boolean customOrder = preorderedList == null
 87                                 && isChildrenDrawingOrderEnabled();
 88                         final View[] children = mChildren;
 89                         for (int i = childrenCount - 1; i >= 0; i--) {
 90                             final int childIndex = getAndVerifyPreorderedIndex(
 91                                     childrenCount, i, customOrder);
 92                             final View child = getAndVerifyPreorderedView(
 93                                     preorderedList, children, childIndex);
 94 
 95                             // If there is a view that has accessibility focus we want it
 96                             // to get the event first and if not handled we will perform a
 97                             // normal dispatch. We may do a double iteration but this is
 98                             // safer given the timeframe.
 99                             if (childWithAccessibilityFocus != null) {
100                                 if (childWithAccessibilityFocus != child) {
101                                     continue;
102                                 }
103                                 childWithAccessibilityFocus = null;
104                                 i = childrenCount - 1;
105                             }
106 
107                             if (!child.canReceivePointerEvents()
108                                     || !isTransformedTouchPointInView(x, y, child, null)) {
109                                 ev.setTargetAccessibilityFocus(false);
110                                 continue;
111                             }
112 
113                             newTouchTarget = getTouchTarget(child);
114                             if (newTouchTarget != null) {
115                                 // Child is already receiving touch within its bounds.
116                                 // Give it the new pointer in addition to the ones it is handling.
117                                 newTouchTarget.pointerIdBits |= idBitsToAssign;
118                                 break;
119                             }
120 
121                             resetCancelNextUpFlag(child);
122                             if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
123                                 // Child wants to receive touch within its bounds.
124                                 mLastTouchDownTime = ev.getDownTime();
125                                 if (preorderedList != null) {
126                                     // childIndex points into presorted list, find original index
127                                     for (int j = 0; j < childrenCount; j++) {
128                                         if (children[childIndex] == mChildren[j]) {
129                                             mLastTouchDownIndex = j;
130                                             break;
131                                         }
132                                     }
133                                 } else {
134                                     mLastTouchDownIndex = childIndex;
135                                 }
136                                 mLastTouchDownX = ev.getX();
137                                 mLastTouchDownY = ev.getY();
138                                 newTouchTarget = addTouchTarget(child, idBitsToAssign);
139                                 alreadyDispatchedToNewTouchTarget = true;
140                                 break;
141                             }
142 
143                             // The accessibility focus didn't handle the event, so clear
144                             // the flag and do a normal dispatch to all children.
145                             ev.setTargetAccessibilityFocus(false);
146                         }
147                         if (preorderedList != null) preorderedList.clear();
148                     }
149 
150                     if (newTouchTarget == null && mFirstTouchTarget != null) {
151                         // Did not find a child to receive the event.
152                         // Assign the pointer to the least recently added target.
153                         newTouchTarget = mFirstTouchTarget;
154                         while (newTouchTarget.next != null) {
155                             newTouchTarget = newTouchTarget.next;
156                         }
157                         newTouchTarget.pointerIdBits |= idBitsToAssign;
158                     }
159                 }
160             }
161 
162             // Dispatch to touch targets.
163             if (mFirstTouchTarget == null) {
164                 // No touch targets so treat this as an ordinary view.
165                 handled = dispatchTransformedTouchEvent(ev, canceled, null,
166                         TouchTarget.ALL_POINTER_IDS);
167             } else {
168                 // Dispatch to touch targets, excluding the new touch target if we already
169                 // dispatched to it.  Cancel touch targets if necessary.
170                 TouchTarget predecessor = null;
171                 TouchTarget target = mFirstTouchTarget;
172                 while (target != null) {
173                     final TouchTarget next = target.next;
174                     if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
175                         handled = true;
176                     } else {
177                         final boolean cancelChild = resetCancelNextUpFlag(target.child)
178                                 || intercepted;
179                         if (dispatchTransformedTouchEvent(ev, cancelChild,
180                                 target.child, target.pointerIdBits)) {
181                             handled = true;
182                         }
183                         if (cancelChild) {
184                             if (predecessor == null) {
185                                 mFirstTouchTarget = next;
186                             } else {
187                                 predecessor.next = next;
188                             }
189                             target.recycle();
190                             target = next;
191                             continue;
192                         }
193                     }
194                     predecessor = target;
195                     target = next;
196                 }
197             }
198 
199             // Update list of touch targets for pointer up or cancel, if needed.
200             if (canceled
201                     || actionMasked == MotionEvent.ACTION_UP
202                     || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
203                 resetTouchState();
204             } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
205                 final int actionIndex = ev.getActionIndex();
206                 final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
207                 removePointersFromTouchTargets(idBitsToRemove);
208             }
209         }
210 
211         if (!handled && mInputEventConsistencyVerifier != null) {
212             mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
213         }
214         return handled;
215     }

前两个if不用管,首先正常流程我们会进入第14行代码的if语句块,我们只按照正常流程来分析,其他异常流程不管。

接下来到19行代码,如果是down事件,那么会执行cancelAndClearTouchTargets(ev);、resetTouchState();这是把之前的状态清零

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();
}

接下来到29行代码,如果为down事件或者mFirstTouchTarget != null那么就会进入if,这是或判断,显然我们是down事件,那么我们进入if,整个if就是用来判断事件是否被拦截

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;
}

这个if里面首先会对disallowIntercept进行赋值,这个值初始为false,所以在为down事件的时候,32行if语句是会进入。也就是进入

if (!disallowIntercept) {
  intercepted = onInterceptTouchEvent(ev);
  ev.setAction(action); // restore action in case it was changed
}

onInterceptTouchEvent(ev)为true就表示拦截,为false表示不拦截

先分析拦截

 

我们以重写的viewpager为例

onInterceptTouchEvent(ev)==true 则 intercepted==true,此时上面的代码58行的 if (!canceled && !intercepted) 就进不去了

直接跑到163行代码,这里的if和else表示分发或者拦截(拦截:相当于最后一个,事件到底处不处理)

那么此时,到底是进行分发还是直接处理事件呢?

我们知道刚开始的时候 mFirstTouchTarget 肯定是为空的,他的初始化和赋值,都是后面才会执行的,因此进入该if语句。执行代码

handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);

 也就是如果拦截了,就会执行上面的代码。

注意,dispatchTransformedTouchEvent这个方法的第三个参数为null,进入dispatchTransformedTouchEvent方法看下,这个方法非常关键,这个方法意思就是:是否处理

 1 private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
 2             View child, int desiredPointerIdBits) {
 3         final boolean handled;
 4 
 5         // Canceling motions is a special case.  We don't need to perform any transformations
 6         // or filtering.  The important part is the action, not the contents.
 7         final int oldAction = event.getAction();
 8         if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
 9             event.setAction(MotionEvent.ACTION_CANCEL);
10             if (child == null) {
11                 handled = super.dispatchTouchEvent(event);
12             } else {
13                 handled = child.dispatchTouchEvent(event);
14             }
15             event.setAction(oldAction);
16             return handled;
17         }
18 
19         // Calculate the number of pointers to deliver.
20         final int oldPointerIdBits = event.getPointerIdBits();
21         final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
22 
23         // If for some reason we ended up in an inconsistent state where it looks like we
24         // might produce a motion event with no pointers in it, then drop the event.
25         if (newPointerIdBits == 0) {
26             return false;
27         }
28 
29         // If the number of pointers is the same and we don't need to perform any fancy
30         // irreversible transformations, then we can reuse the motion event for this
31         // dispatch as long as we are careful to revert any changes we make.
32         // Otherwise we need to make a copy.
33         final MotionEvent transformedEvent;
34         if (newPointerIdBits == oldPointerIdBits) {
35             if (child == null || child.hasIdentityMatrix()) {
36                 if (child == null) {
37                     handled = super.dispatchTouchEvent(event);
38                 } else {
39                     final float offsetX = mScrollX - child.mLeft;
40                     final float offsetY = mScrollY - child.mTop;
41                     event.offsetLocation(offsetX, offsetY);
42 
43                     handled = child.dispatchTouchEvent(event);
44 
45                     event.offsetLocation(-offsetX, -offsetY);
46                 }
47                 return handled;
48             }
49             transformedEvent = MotionEvent.obtain(event);
50         } else {
51             transformedEvent = event.split(newPointerIdBits);
52         }
53 
54         // Perform any necessary transformations and dispatch.
55         if (child == null) {
56             handled = super.dispatchTouchEvent(transformedEvent);
57         } else {
58             final float offsetX = mScrollX - child.mLeft;
59             final float offsetY = mScrollY - child.mTop;
60             transformedEvent.offsetLocation(offsetX, offsetY);
61             if (! child.hasIdentityMatrix()) {
62                 transformedEvent.transform(child.getInverseMatrix());
63             }
64 
65             handled = child.dispatchTouchEvent(transformedEvent);
66         }
67 
68         // Done.
69         transformedEvent.recycle();
70         return handled;
71     }

当进入dispatchTransformedTouchEvent方法,第三个参数是View child,而我们传进来的是null,这表示该方法并不是给他的child处理,而是给自己处理,因为自己已经拦截了。

因为child==null ,那么到55行代码进入if 执行 handled = super.dispatchTouchEvent(transformedEvent);这里的super指向的就是view,因为viewgroup继承自view,所以执行view的dispatchTouchEvent方法,就到了前面分析的view的事件处理了。

整个onInterceptTouchEvent(ev)为true的拦截分析到此结束。

******注意:我们前面分析的view的dispatchTouchEvent是有返回值result的,该返回值实际上就是返回给ViewGroup的handled的,也就是在执行代码handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);时对handled进行的赋值。

那么这个handled的值为false或者true对接下来代码有什么影响呢?

 

接下来我们分析不拦截(分发)

onInterceptTouchEvent(ev)为false则 intercepted == false

那么就会进入viewgroup的dispatchTouchEvent方法的58行代码 if语句块

  1       if (!canceled && !intercepted) {
  2 
  3                 // If the event is targeting accessibility focus we give it to the
  4                 // view that has accessibility focus and if it does not handle it
  5                 // we clear the flag and dispatch the event to all children as usual.
  6                 // We are looking up the accessibility focused host to avoid keeping
  7                 // state since these events are very rare.
  8                 View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
  9                         ? findChildWithAccessibilityFocus() : null;
 10 
 11                 if (actionMasked == MotionEvent.ACTION_DOWN
 12                         || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
 13                         || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
 14                     final int actionIndex = ev.getActionIndex(); // always 0 for down
 15                     final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
 16                             : TouchTarget.ALL_POINTER_IDS;
 17 
 18                     // Clean up earlier touch targets for this pointer id in case they
 19                     // have become out of sync.
 20                     removePointersFromTouchTargets(idBitsToAssign);
 21 
 22                     final int childrenCount = mChildrenCount;
 23                     if (newTouchTarget == null && childrenCount != 0) {
 24                         final float x = ev.getX(actionIndex);
 25                         final float y = ev.getY(actionIndex);
 26                         // Find a child that can receive the event.
 27                         // Scan children from front to back.
 28                         final ArrayList<View> preorderedList = buildTouchDispatchChildList();
 29                         final boolean customOrder = preorderedList == null
 30                                 && isChildrenDrawingOrderEnabled();
 31                         final View[] children = mChildren;
 32                         for (int i = childrenCount - 1; i >= 0; i--) {
 33                             final int childIndex = getAndVerifyPreorderedIndex(
 34                                     childrenCount, i, customOrder);
 35                             final View child = getAndVerifyPreorderedView(
 36                                     preorderedList, children, childIndex);
 37 
 38                             // If there is a view that has accessibility focus we want it
 39                             // to get the event first and if not handled we will perform a
 40                             // normal dispatch. We may do a double iteration but this is
 41                             // safer given the timeframe.
 42                             if (childWithAccessibilityFocus != null) {
 43                                 if (childWithAccessibilityFocus != child) {
 44                                     continue;
 45                                 }
 46                                 childWithAccessibilityFocus = null;
 47                                 i = childrenCount - 1;
 48                             }
 49 
 50                             if (!child.canReceivePointerEvents()
 51                                     || !isTransformedTouchPointInView(x, y, child, null)) {
 52                                 ev.setTargetAccessibilityFocus(false);
 53                                 continue;
 54                             }
 55 
 56                             newTouchTarget = getTouchTarget(child);
 57                             if (newTouchTarget != null) {
 58                                 // Child is already receiving touch within its bounds.
 59                                 // Give it the new pointer in addition to the ones it is handling.
 60                                 newTouchTarget.pointerIdBits |= idBitsToAssign;
 61                                 break;
 62                             }
 63 
 64                             resetCancelNextUpFlag(child);
 65                             if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
 66                                 // Child wants to receive touch within its bounds.
 67                                 mLastTouchDownTime = ev.getDownTime();
 68                                 if (preorderedList != null) {
 69                                     // childIndex points into presorted list, find original index
 70                                     for (int j = 0; j < childrenCount; j++) {
 71                                         if (children[childIndex] == mChildren[j]) {
 72                                             mLastTouchDownIndex = j;
 73                                             break;
 74                                         }
 75                                     }
 76                                 } else {
 77                                     mLastTouchDownIndex = childIndex;
 78                                 }
 79                                 mLastTouchDownX = ev.getX();
 80                                 mLastTouchDownY = ev.getY();
 81                                 newTouchTarget = addTouchTarget(child, idBitsToAssign);
 82                                 alreadyDispatchedToNewTouchTarget = true;
 83                                 break;
 84                             }
 85 
 86                             // The accessibility focus didn't handle the event, so clear
 87                             // the flag and do a normal dispatch to all children.
 88                             ev.setTargetAccessibilityFocus(false);
 89                         }
 90                         if (preorderedList != null) preorderedList.clear();
 91                     }
 92 
 93                     if (newTouchTarget == null && mFirstTouchTarget != null) {
 94                         // Did not find a child to receive the event.
 95                         // Assign the pointer to the least recently added target.
 96                         newTouchTarget = mFirstTouchTarget;
 97                         while (newTouchTarget.next != null) {
 98                             newTouchTarget = newTouchTarget.next;
 99                         }
100                         newTouchTarget.pointerIdBits |= idBitsToAssign;
101                     }
102                 }
103             }

这个if语句里面,就是对事件进行分发。

首先我们进入的时候,会判断如果为down事件的时候才会分发

我们开发时记得一个结论:如果down事件没有处理权,那么move事件也处理不了。那么这个结论是针对叶节点的。也就是红色框框里面的view。

 

我们继续分析分发流程,这里为什么是针对叶节点的,后面会有分析。

OK,进入该代码块之后会执行到第23行代码 if (newTouchTarget == null && childrenCount != 0) 

首先newTouchTarget 是等于null的,因为在if代码块之前设置了,TouchTarget newTouchTarget = null; 那么childrenCount 是什么玩意呢?

childrenCount 就表示该ViewGroup下面的子view的数量。如果这个ViewGroup下面有子view,那么就可以进行事件分发。

在分发的过程中,子view会依次放进一个数组进行排序,排序的规则是按照Z-index的方式

例如:FrameLayout里面有三个TextView,分别为text1、text2、text3,那么FrameLayout的布局会让三个textview全部重叠在页面的左上角,我们在进行将view插入数组的时候,那么顺序就是text3、text2、text1。

了解了排序规则之后,接下来进入32行的for循环代码,这个for循环进行的是倒序取出。也就是先判断text3,再判断text2,再判断text1,是这样的顺序进行判断的,这是他的一种设计模式。我们主要看for循环里面做了什么。

final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);

这两行代码就是将view取出来,取出child之后,那么这个child表示什么呢?

分析之前就说了,我们是站在DecorView的角度进行分析的,所以这个child的实际上就是DecorView下面的各个ViewGroup。

在拿到child之后首先会进行判断该viewgroup是否有能力处理此事件

if (!child.canReceivePointerEvents()|| !isTransformedTouchPointInView(x, y, child, null)) {
  ev.setTargetAccessibilityFocus(false);
  continue;
}

进入canReceivePointerEvents() 方法看下代码是什么样的!

protected boolean canReceivePointerEvents() {
  return (mViewFlags & VISIBILITY_MASK) == VISIBLE || getAnimation() != null;
}

该方法会进行两个判断

  • 判断该view是否是显示状态,这个view至少是要visible状态
  • 判断该view是否是有Animation属性。(animation这个动画,当我们将view进行平移之后,平移之前的位置是没有view了的。但是点击平移之前的位置依然有用)

即首先这个view我要看得到,如果看不到,是不是因为你有动画。

接下来进入if语句的第二个条件isTransformedTouchPointInView

  protected boolean isTransformedTouchPointInView(float x, float y, View child,
            PointF outLocalPoint) {
        final float[] point = getTempPoint();
        point[0] = x;
        point[1] = y;
        transformPointToViewLocal(point, child);
        final boolean isInView = child.pointInView(point[0], point[1]);
        if (isInView && outLocalPoint != null) {
            outLocalPoint.set(point[0], point[1]);
        }
        return isInView;
    }

这个方法主要就是判断我们点击的位置是否在该view的point 范围内。比如该控件在屏幕右下角,那我点击左上角,肯定是无效的,主要就是这个判断。

 

那么这个child如果有能力处理的话,我们会执行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;
}

这段代码其实我们可以不用管的,因为getTouchTarget方法里面返回的其实就是 mFirstTouchTarget 而此时的mFirstTouchTarget 依旧为空(前面提到了为什么是空,忘记了可以上去看一下。)

因此这个if直接跳过,接下来会执行关键代码,需要记住的是下面的分析是基于dispatchTransformedTouchEvent返回的是true所执行的代码

*************关键代码**********************

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;
}

*************关键代码**********************

这个if判断 dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)  这个代码已经非常熟悉了,因为又到了前面所说到的,分发处理事件

前面我们说过如果ViewGroup对事件进行拦截,那么会执行

handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);

但是拦截时进入dispatchTransformedTouchEvent方法第三个参数为null,而这里第三个参数是有值的,我们再次回到dispatchTransformedTouchEvent方法代码:

 1 private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
 2             View child, int desiredPointerIdBits) {
 3         final boolean handled;
 4 
 5         // Canceling motions is a special case.  We don't need to perform any transformations
 6         // or filtering.  The important part is the action, not the contents.
 7         final int oldAction = event.getAction();
 8         if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
 9             event.setAction(MotionEvent.ACTION_CANCEL);
10             if (child == null) {
11                 handled = super.dispatchTouchEvent(event);
12             } else {
13                 handled = child.dispatchTouchEvent(event);
14             }
15             event.setAction(oldAction);
16             return handled;
17         }
18 
19         // Calculate the number of pointers to deliver.
20         final int oldPointerIdBits = event.getPointerIdBits();
21         final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
22 
23         // If for some reason we ended up in an inconsistent state where it looks like we
24         // might produce a motion event with no pointers in it, then drop the event.
25         if (newPointerIdBits == 0) {
26             return false;
27         }
28 
29         // If the number of pointers is the same and we don't need to perform any fancy
30         // irreversible transformations, then we can reuse the motion event for this
31         // dispatch as long as we are careful to revert any changes we make.
32         // Otherwise we need to make a copy.
33         final MotionEvent transformedEvent;
34         if (newPointerIdBits == oldPointerIdBits) {
35             if (child == null || child.hasIdentityMatrix()) {
36                 if (child == null) {
37                     handled = super.dispatchTouchEvent(event);
38                 } else {
39                     final float offsetX = mScrollX - child.mLeft;
40                     final float offsetY = mScrollY - child.mTop;
41                     event.offsetLocation(offsetX, offsetY);
42 
43                     handled = child.dispatchTouchEvent(event);
44 
45                     event.offsetLocation(-offsetX, -offsetY);
46                 }
47                 return handled;
48             }
49             transformedEvent = MotionEvent.obtain(event);
50         } else {
51             transformedEvent = event.split(newPointerIdBits);
52         }
53 
54         // Perform any necessary transformations and dispatch.
55         if (child == null) {
56             handled = super.dispatchTouchEvent(transformedEvent);
57         } else {
58             final float offsetX = mScrollX - child.mLeft;
59             final float offsetY = mScrollY - child.mTop;
60             transformedEvent.offsetLocation(offsetX, offsetY);
61             if (! child.hasIdentityMatrix()) {
62                 transformedEvent.transform(child.getInverseMatrix());
63             }
64 
65             handled = child.dispatchTouchEvent(transformedEvent);
66         }
67 
68         // Done.
69         transformedEvent.recycle();
70         return handled;
71     }

当child不为null的时候,会执行57行代码,进入else代码块,那么在else方法里面能看到调用了child.dispatchTouchEvent方法

那么这个dispatchTouchEvent是指向谁呢?我们一直在强调,我们是站在DecorView的角度来分析的,那么此时的child就是DecorView下面的各个ViewGroup,在董事长那个图里面,这里的child指的就是市场总监、工程总监等

因此这个child是ViewGroup,所以此时的child.dispatchTouchEvent指向的就是ViewGroup的child.dispatchTouchEvent方法。所以基于上面的分析会再执行一遍。如此反复,直到事件被消费。这相当于就是一个递归了。

那么ViewGroup又会进行分发处理,那么当child.dispatchTouchEvent的child指向的是view的时候,就会直接进入view的dispatchTouchEvent进行事件处理了,而不是继续分发。

需要注意的是上面的分析是基于dispatchTransformedTouchEvent返回的是true所执行的代码,那么如果是false会怎么样呢?

当是false的时候,也就是ViewGroup不处理该事件的时候,这个取child的for循环就会继续循环下去,去询问下一个子view是否符合条件,是否处理该事件,也就是市场总监不处理该事件,那么总经理会询问工程总监是否处理

直到循环结束如果找不到执行者,就会执行for循环下面的语句,也就是直接到了下面的拦截处理的代码块。

// 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);
}

所以如果child全部不处理,就相当于是DecorView拦截了。(我分发给你你不要,和我不分发直接拦截,效果是一样的)

以上分析的仅仅只是关键代码的if条件判断,那么如果child处理的话,会进入if语句,我们再次贴上关键代码

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;
}

注意最后两行代码

newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;

我们进入addTouchTarget方法记录一些数据,待会儿会有用

private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
  final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
  target.next = mFirstTouchTarget;
  mFirstTouchTarget = target;
  return target;
}
  • 1、target进行了初始化
  • 2、target.next = mFirstTouchTarget = null
  • 3、mFirstTouchTarget = target(mFirstTouchTarget 在这里才会进行赋值)

********注意:步骤2和步骤3按顺序执行,因此当执行target.next = mFirstTouchTarget时,mFirstTouchTarget此时还是为null,所以target.next = null;

再次结合关键代码我们整理得出以下结论,后面我们会用到。

  • newTouchTarget = mFirstTouchTarget = target  != null
  • target.next = null
  • alreadyDispatchedToNewTouchTarget = true

接下来直接break;结束我们的for循环,接下来执行下面的代码

if (newTouchTarget == null && mFirstTouchTarget != null) {
  // Did not find a child to receive the event.
  // Assign the pointer to the least recently added target.
  newTouchTarget = mFirstTouchTarget;
  while (newTouchTarget.next != null) {
    newTouchTarget = newTouchTarget.next;
  }
  newTouchTarget.pointerIdBits |= idBitsToAssign;
}
newTouchTarget 肯定不为空,所以我们不用管这段代码,继续往下走

// 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);
            } else {
                // Dispatch to touch targets, excluding the new touch target if we already
                // dispatched to it.  Cancel touch targets if necessary.
                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;
                        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;
                }
            }

最开始我们判断的是进入了拦截if语句块,因为当时mFirstTouchTarget==null,而现在我们的 mFirstTouchTarget已经被赋值了,因此会走else语句块

在else语句块里面有一个while循环,上面我们得到结论

  • newTouchTarget = mFirstTouchTarget = target  != null
  • target.next = null
  • alreadyDispatchedToNewTouchTarget = true

因此while循环里面next = target.next = null

在循环末尾有一行代码:target = next; 因此我们知道,循环结束之后target == null,所以这个while循环只会执行一次(多点触控就会执行多次,本文只做单点解析)。

OK,知道了这个以后,我们看看while循环里面做了什么事!

里面有这个if判断   if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget)

通过结论我们得知,该if条件是满足的,因此进入if语句块执行  handled = true;

因为在之前,我们已经把这个事件交给了他的子view去处理(关键代码的if判断条件里面交给了子view去处理if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign))),所以这里直接返回true表示不处理该事件了。

*************注意:我们的角色一直是DecorView,也就是总经理,因为前面交给了DecorView下面的ViewGroup处理(市场总监),所以总经理就不处理该事件了。

 

以上就是down事件发生时,view以及ViewGroup进行的事件处理和分发的流程,后续补充move时的解析流程。

posted @ 2020-12-20 21:34  金大人的梦  阅读(138)  评论(0编辑  收藏  举报