有一個Button 按鈕,要想為該按鈕設置onClick事件和OnTouch事件 此時,我們現在分析一下,是onTouch先執行,還是onClick執行,接下來我從FrameWork 源碼去探尋一下整個事件的執行流程和原理: 我們知道 Button ,TextView 等基礎控制項的基類都是View, ...
有一個Button 按鈕,要想為該按鈕設置onClick事件和OnTouch事件
mTestButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Log.d(TAG, "onClick execute"); } }); mTestButton.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent motionEvent) { Log.d(TAG, "onTouch execute, action event " + motionEvent.getAction()); return false; } });
此時,我們現在分析一下,是onTouch先執行,還是onClick執行,接下來我從FrameWork 源碼去探尋一下整個事件的執行流程和原理:
我們知道 Button ,TextView 等基礎控制項的基類都是View,只要你觸摸到了任何一個控制項,就一定會調用該控制項的dispatchTouchEvent方法。那當我們去點擊按鈕的時候,就會去調用Button類(實際上是基類View)里的dispatchTouchEvent方法,所以接下來看View源碼中dispatchTouchEvent()方法的具體實現:
public boolean dispatchTouchEvent(MotionEvent event) { if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener.onTouch(this, event)) { return true; } return onTouchEvent(event); }
分析上述代碼,第2行 如果三個條件都為真的話,就返回true,否則執行onTouchEvent,先看第一個條件mOnTouchListener!=null,這個條件就是如果設置了OnTouchListener就會為true,否則是false; 第二個條件(mViewFlags & ENABLED_MASK) == ENABLED是判斷當前點擊的控制項是否是enable的,按鈕預設都是enable的,因此這個條件恆定為true;第三個條件就比較複雜了,mOnTouchListener.onTouch(this, event),這個其實就是去回調控制項註冊touch事件時的onTouch方法。也就是說如果我們在onTouch方法里返回true,就會讓這三個條件全部成立,從而整個方法直接返回true。如果我們在onTouch方法里返回false,就會再去執行onTouchEvent(event)方法。onTouchEvent(MotionEvent event)方法同樣也是在view中定義的一個方法,主要是處理傳遞到view 的手勢事件,包括ACTION_DOWN,ACTION_MOVE,ACTION_UP,ACTION_CANCEL四種事件。
接下來我們結合上面的具體例子,來分析一下這個過程,首先會執行dispatchTouchEvent(MotionEvent event) ,所以onTouch方法肯定是早於onClick方法的,如果在onTouch里返回false,就會出現下麵的現象:
10-20 18:57:49.670: DEBUG/MainActivity(20153): onTouch execute, action event 0
10-20 18:57:49.715: DEBUG/MainActivity(20153): onTouch execute, action event 1
10-20 18:57:49.715: DEBUG/MainActivity(20153): onClick execute
即先執行了onTouch,再執行了onClick事件,而且onTouch執行了兩次,一個是action_down,一個是action_up事件;
如果onTouch里返回true,則出現下麵的現象:
10-20 19:01:59.795: DEBUG/MainActivity(21010): onTouch execute, action event 0
10-20 19:01:59.860: DEBUG/MainActivity(21010): onTouch execute, action event 1
結果是onClick事件沒有執行了,原因是如果onTouch返回true的話,則dispatchEvent(MotionEvent event)方法直接返回true了,相當於不往下傳遞事件了,所以onClick不會執行,相反如果onTouch返回false的話(此時會執行onClick方法),則會執行 onTouchEvent(MotionEvent event)方法,由此可以得出這樣一個結論,onClick事件的具體調用執行肯定是在onTouchEvent(MotionEvent event)方法源碼中,接下來分析一下該函數的源碼:
1 public boolean onTouchEvent(MotionEvent event) { 2 final int viewFlags = mViewFlags; 3 if ((viewFlags & ENABLED_MASK) == DISABLED) { 4 // A disabled view that is clickable still consumes the touch 5 // events, it just doesn't respond to them. 6 return (((viewFlags & CLICKABLE) == CLICKABLE || 7 (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)); 8 } 9 if (mTouchDelegate != null) { 10 if (mTouchDelegate.onTouchEvent(event)) { 11 return true; 12 } 13 } 14 if (((viewFlags & CLICKABLE) == CLICKABLE || 15 (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) { 16 switch (event.getAction()) { 17 case MotionEvent.ACTION_UP: 18 boolean prepressed = (mPrivateFlags & PREPRESSED) != 0; 19 if ((mPrivateFlags & PRESSED) != 0 || prepressed) { 20 // take focus if we don't have it already and we should in 21 // touch mode. 22 boolean focusTaken = false; 23 if (isFocusable() && isFocusableInTouchMode() && !isFocused()) { 24 focusTaken = requestFocus(); 25 } 26 if (!mHasPerformedLongPress) { 27 // This is a tap, so remove the longpress check 28 removeLongPressCallback(); 29 // Only perform take click actions if we were in the pressed state 30 if (!focusTaken) { 31 // Use a Runnable and post this rather than calling 32 // performClick directly. This lets other visual state 33 // of the view update before click actions start. 34 if (mPerformClick == null) { 35 mPerformClick = new PerformClick(); 36 } 37 if (!post(mPerformClick)) { 38 performClick(); 39 } 40 } 41 } 42 if (mUnsetPressedState == null) { 43 mUnsetPressedState = new UnsetPressedState(); 44 } 45 if (prepressed) { 46 mPrivateFlags |= PRESSED; 47 refreshDrawableState(); 48 postDelayed(mUnsetPressedState, 49 ViewConfiguration.getPressedStateDuration()); 50 } else if (!post(mUnsetPressedState)) { 51 // If the post failed, unpress right now 52 mUnsetPressedState.run(); 53 } 54 removeTapCallback(); 55 } 56 break; 57 case MotionEvent.ACTION_DOWN: 58 if (mPendingCheckForTap == null) { 59 mPendingCheckForTap = new CheckForTap(); 60 } 61 mPrivateFlags |= PREPRESSED; 62 mHasPerformedLongPress = false; 63 postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); 64 break; 65 case MotionEvent.ACTION_CANCEL: 66 mPrivateFlags &= ~PRESSED; 67 refreshDrawableState(); 68 removeTapCallback(); 69 break; 70 case MotionEvent.ACTION_MOVE: 71 final int x = (int) event.getX(); 72 final int y = (int) event.getY(); 73 // Be lenient about moving outside of buttons 74 int slop = mTouchSlop; 75 if ((x < 0 - slop) || (x >= getWidth() + slop) || 76 (y < 0 - slop) || (y >= getHeight() + slop)) { 77 // Outside button 78 removeTapCallback(); 79 if ((mPrivateFlags & PRESSED) != 0) { 80 // Remove any future long press/tap checks 81 removeLongPressCallback(); 82 // Need to switch from pressed to not pressed 83 mPrivateFlags &= ~PRESSED; 84 refreshDrawableState(); 85 } 86 } 87 break; 88 } 89 return true; 90 } 91 return false; 92 }View Code
雖然源碼有點多,但是我們只重點關註關鍵代碼,在38行我們看到了代碼:performClick();這個方法從名字表義來看就是OnClick方法的調用,我們進入到該方法中去看一探究竟,是否執行了OnClick方法呢?
public boolean performClick() { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); if (mOnClickListener != null) { playSoundEffect(SoundEffectConstants.CLICK); mOnClickListener.onClick(this); return true; } return false; }
從上述代碼可以看到,只要mOnClickListener不是null,就會去調用它的onClick方法,那mOnClickListener又是在哪裡賦值的呢?經過分析後找到如下方法:
public void setOnClickListener(OnClickListener l) { if (!isClickable()) { setClickable(true); } mOnClickListener = l; }
而上述這個方法就是我們在Application層經常使用的方法,即我們給button 設置點擊事件的時候就會調用該方法了,分析到這了,我們知道了OnClick方法確實是在OnTouchEvent方法中,那麼除了要設置 OnClickListener,調用onClick的條件又是什麼呢?我們從38行代碼往前推,從第14行可以分析出:
只要該控制項是可點擊的或者是長按類型的,則會進入到MotionEvent.ACTION_UP這個分支當中 ,然後經過各種條件判斷,則會進入到38行的performClick()方法中。
至此,一切都清晰明白了!當我們通過調用setOnClickListener方法來給控制項註冊一個點擊事件時,就會給mOnClickListener賦值。然後每當控制項被點擊時或者長按時,都會在performClick()方法里回調被點擊控制項的onClick方法。
經驗之談:
關於OnTouchEvent(MotionEvent事件)事件的層級傳遞。我們都知道如果給一個控制項註冊了touch事件,每次點擊它的時候都會觸發一系列的ACTION_DOWN,ACTION_MOVE,ACTION_UP等事件。這裡需要註意,如果你在執行ACTION_DOWN的時候返回了false,後面一系列其它的action就不會再得到執行了。簡單的說,就是當dispatchTouchEvent在進行事件分發的時候,只有前一個action返回true,才會觸發後一個action。
那我們可以換一個控制項,將按鈕替換成ImageView,然後給它也註冊一個touch事件,並返回false。如下所示: