Android事件分发机制浅谈(三)--源码分析(View篇)
写事件分发源码分析的时候很纠结,网上的许多博文都是先分析的View,后分析ViewGroup。因为我一开始理解的时候是按我的流程图往下走的,感觉方向很对,单是具体分析的时候总是磕磕绊绊的,老要跳到View中去分析,很多方法理解不了,但毕竟流程清楚了许多,算是有得有失吧,不多说,开始分析。
继续根据流程,先分析View的dispatchTouchEvent()方法,看源码。
public boolean dispatchTouchEvent(MotionEvent event) { // If the event should be handled by accessibility focus first. if (event.isTargetAccessibilityFocus()) { // We don't have focus or no virtual descendant has it, do not handle the event. if (!isAccessibilityFocusedViewOrHost()) { return false; } // We have focus and got the event, then use normal event dispatch. event.setTargetAccessibilityFocus(false); } 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 && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; } if (!result && onTouchEvent(event)) { result = true; } } if (!result && mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(event, 0); } // Clean up after nested scrolls if this is the end of a gesture; // also cancel it if we tried an ACTION_DOWN but we didn't want the rest // of the gesture. if (actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_CANCEL || (actionMasked == MotionEvent.ACTION_DOWN && !result)) { stopNestedScroll(); } return result; }
不得不说,比起ViewGroup这个方法真的很短。
if (onFilterTouchEventForSecurity(event))
略过不必要的看这个判断中的方法判断当前View是否没被遮住等,接下来的就是我们曾在上一节中讲解过的
1 ListenerInfo li = mListenerInfo; 2 if (li != null && li.mOnTouchListener != null 3 && (mViewFlags & ENABLED_MASK) == ENABLED 4 && li.mOnTouchListener.onTouch(this, event)) { 5 result = true; 6 } 7 8 if (!result && onTouchEvent(event)) { 9 result = true; 10 }
这里不分析情景了,这里主要看下上一次没分析的onTouch方法和onTouchEvent方法
首先进入onTouch方法的源码
boolean onTouch(View v, MotionEvent event);
才发现这只是个接口方法,看来是要重写的,那没什么好看的了,看onTouchEvent()方法
1 public boolean onTouchEvent(MotionEvent event) { 2 final float x = event.getX(); 3 final float y = event.getY(); 4 final int viewFlags = mViewFlags; 5 final int action = event.getAction(); 6 7 if ((viewFlags & ENABLED_MASK) == DISABLED) { 8 if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) { 9 setPressed(false); 10 } 11 // A disabled view that is clickable still consumes the touch 12 // events, it just doesn't respond to them. 13 return (((viewFlags & CLICKABLE) == CLICKABLE 14 || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) 15 || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE); 16 } 17 18 if (mTouchDelegate != null) { 19 if (mTouchDelegate.onTouchEvent(event)) { 20 return true; 21 } 22 } 23 24 if (((viewFlags & CLICKABLE) == CLICKABLE || 25 (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || 26 (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) { 27 switch (action) { 28 case MotionEvent.ACTION_UP: 29 boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0; 30 if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) { 31 // take focus if we don't have it already and we should in 32 // touch mode. 33 boolean focusTaken = false; 34 if (isFocusable() && isFocusableInTouchMode() && !isFocused()) { 35 focusTaken = requestFocus(); 36 } 37 38 if (prepressed) { 39 // The button is being released before we actually 40 // showed it as pressed. Make it show the pressed 41 // state now (before scheduling the click) to ensure 42 // the user sees it. 43 setPressed(true, x, y); 44 } 45 46 if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) { 47 // This is a tap, so remove the longpress check 48 removeLongPressCallback(); 49 50 // Only perform take click actions if we were in the pressed state 51 if (!focusTaken) { 52 // Use a Runnable and post this rather than calling 53 // performClick directly. This lets other visual state 54 // of the view update before click actions start. 55 if (mPerformClick == null) { 56 mPerformClick = new PerformClick(); 57 } 58 if (!post(mPerformClick)) { 59 performClick(); 60 } 61 } 62 } 63 64 if (mUnsetPressedState == null) { 65 mUnsetPressedState = new UnsetPressedState(); 66 } 67 68 if (prepressed) { 69 postDelayed(mUnsetPressedState, 70 ViewConfiguration.getPressedStateDuration()); 71 } else if (!post(mUnsetPressedState)) { 72 // If the post failed, unpress right now 73 mUnsetPressedState.run(); 74 } 75 76 removeTapCallback(); 77 } 78 mIgnoreNextUpEvent = false; 79 break; 80 81 case MotionEvent.ACTION_DOWN: 82 mHasPerformedLongPress = false; 83 84 if (performButtonActionOnTouchDown(event)) { 85 break; 86 } 87 88 // Walk up the hierarchy to determine if we're inside a scrolling container. 89 boolean isInScrollingContainer = isInScrollingContainer(); 90 91 // For views inside a scrolling container, delay the pressed feedback for 92 // a short period in case this is a scroll. 93 if (isInScrollingContainer) { 94 mPrivateFlags |= PFLAG_PREPRESSED; 95 if (mPendingCheckForTap == null) { 96 mPendingCheckForTap = new CheckForTap(); 97 } 98 mPendingCheckForTap.x = event.getX(); 99 mPendingCheckForTap.y = event.getY(); 100 postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); 101 } else { 102 // Not inside a scrolling container, so show the feedback right away 103 setPressed(true, x, y); 104 checkForLongClick(0); 105 } 106 break; 107 108 case MotionEvent.ACTION_CANCEL: 109 setPressed(false); 110 removeTapCallback(); 111 removeLongPressCallback(); 112 mInContextButtonPress = false; 113 mHasPerformedLongPress = false; 114 mIgnoreNextUpEvent = false; 115 break; 116 117 case MotionEvent.ACTION_MOVE: 118 drawableHotspotChanged(x, y); 119 120 // Be lenient about moving outside of buttons 121 if (!pointInView(x, y, mTouchSlop)) { 122 // Outside button 123 removeTapCallback(); 124 if ((mPrivateFlags & PFLAG_PRESSED) != 0) { 125 // Remove any future long press/tap checks 126 removeLongPressCallback(); 127 128 setPressed(false); 129 } 130 } 131 break; 132 } 133 134 return true; 135 } 136 137 return false; 138 }
突然发现好长,那么咱们分开看
7-16 我们可以看到如果控件是disabled的同时是可以clickable的则onTouchEvent直接消费事件返回true,反之如果控件(View)是disenable状态,同时是disclickable的则onTouchEvent直接false。
24、137看到如果是disclickable的那么返回false
27-134是一个switch判断event的类型,对每个类型进行了适当的分析,有兴趣的可以自己了解一下
我们可以看到当ACTION_UP执行时,执行了一个perforClick方法
1 public boolean performClick() { 2 final boolean result; 3 final ListenerInfo li = mListenerInfo; 4 if (li != null && li.mOnClickListener != null) { 5 playSoundEffect(SoundEffectConstants.CLICK); 6 li.mOnClickListener.onClick(this); 7 result = true; 8 } else { 9 result = false; 10 } 11 12 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); 13 return result; 14 }
这下我们可以看到if判断句中熟悉的方法了onCLick,原来onClick()方法是在这里调用的,同样的li.mOnClickListener是通过setOnClickListener方法设置的,现在是知道了为什么onClik方法的优先级较低了吧
好了,现在android的事件分析就告一段落了,写的时候因为很多,所以有些地方可能写的比较糊涂,有错误的话请指出。