淺談onTouch先執(zhí)行,還是onClick執(zhí)行(詳解)
有一個Button 按鈕,要想為該按鈕設(shè)置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; } });
此時,我們現(xiàn)在分析一下,是onTouch先執(zhí)行,還是onClick執(zhí)行,接下來我從FrameWork 源碼去探尋一下整個事件的執(zhí)行流程和原理:
我們知道 Button ,TextView 等基礎(chǔ)控件的基類都是View,只要你觸摸到了任何一個控件,就一定會調(diào)用該控件的dispatchTouchEvent方法。那當(dāng)我們?nèi)c擊按鈕的時候,就會去調(diào)用Button類(實際上是基類View)里的dispatchTouchEvent方法,所以接下來看View源碼中dispatchTouchEvent()方法的具體實現(xiàn):
public boolean dispatchTouchEvent(MotionEvent event) { if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener.onTouch(this, event)) { return true; } return onTouchEvent(event); }
分析上述代碼,第2行 如果三個條件都為真的話,就返回true,否則執(zhí)行onTouchEvent,先看第一個條件mOnTouchListener!=null,這個條件就是如果設(shè)置了OnTouchListener就會為true,否則是false; 第二個條件(mViewFlags & ENABLED_MASK) == ENABLED是判斷當(dāng)前點擊的控件是否是enable的,按鈕默認(rèn)都是enable的,因此這個條件恒定為true;第三個條件就比較復(fù)雜了,mOnTouchListener.onTouch(this, event),這個其實就是去回調(diào)控件注冊touch事件時的onTouch方法。也就是說如果我們在onTouch方法里返回true,就會讓這三個條件全部成立,從而整個方法直接返回true。如果我們在onTouch方法里返回false,就會再去執(zhí)行onTouchEvent(event)方法。onTouchEvent(MotionEvent event)方法同樣也是在view中定義的一個方法,主要是處理傳遞到view 的手勢事件,包括ACTION_DOWN,ACTION_MOVE,ACTION_UP,ACTION_CANCEL四種事件。
接下來我們結(jié)合上面的具體例子,來分析一下這個過程,首先會執(zhí)行dispatchTouchEvent(MotionEvent event) ,所以onTouch方法肯定是早于onClick方法的,如果在onTouch里返回false,就會出現(xiàn)下面的現(xiàn)象:
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
即先執(zhí)行了onTouch,再執(zhí)行了onClick事件,而且onTouch執(zhí)行了兩次,一個是action_down,一個是action_up事件;
如果onTouch里返回true,則出現(xiàn)下面的現(xiàn)象:
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
結(jié)果是onClick事件沒有執(zhí)行了,原因是如果onTouch返回true的話,則dispatchEvent(MotionEvent event)方法直接返回true了,相當(dāng)于不往下傳遞事件了,所以onClick不會執(zhí)行,相反如果onTouch返回false的話(此時會執(zhí)行onClick方法),則會執(zhí)行 onTouchEvent(MotionEvent event)方法,由此可以得出這樣一個結(jié)論,onClick事件的具體調(diào)用執(zhí)行肯定是在onTouchEvent(MotionEvent event)方法源碼中,接下來分析一下該函數(shù)的源碼:
public boolean onTouchEvent(MotionEvent event) { final int viewFlags = mViewFlags; if ((viewFlags & ENABLED_MASK) == DISABLED) { // A disabled view that is clickable still consumes the touch // events, it just doesn't respond to them. return (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)); } if (mTouchDelegate != null) { if (mTouchDelegate.onTouchEvent(event)) { return true; } } if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) { switch (event.getAction()) { case MotionEvent.ACTION_UP: boolean prepressed = (mPrivateFlags & PREPRESSED) != 0; if ((mPrivateFlags & 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 (!mHasPerformedLongPress) { // 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(); } } } if (mUnsetPressedState == null) { mUnsetPressedState = new UnsetPressedState(); } if (prepressed) { mPrivateFlags |= PRESSED; refreshDrawableState(); postDelayed(mUnsetPressedState, ViewConfiguration.getPressedStateDuration()); } else if (!post(mUnsetPressedState)) { // If the post failed, unpress right now mUnsetPressedState.run(); } removeTapCallback(); } break; case MotionEvent.ACTION_DOWN: if (mPendingCheckForTap == null) { mPendingCheckForTap = new CheckForTap(); } mPrivateFlags |= PREPRESSED; mHasPerformedLongPress = false; postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); break; case MotionEvent.ACTION_CANCEL: mPrivateFlags &= ~PRESSED; refreshDrawableState(); removeTapCallback(); break; case MotionEvent.ACTION_MOVE: final int x = (int) event.getX(); final int y = (int) event.getY(); // Be lenient about moving outside of buttons int slop = mTouchSlop; if ((x < 0 - slop) || (x >= getWidth() + slop) || (y < 0 - slop) || (y >= getHeight() + slop)) { // Outside button removeTapCallback(); if ((mPrivateFlags & PRESSED) != 0) { // Remove any future long press/tap checks removeLongPressCallback(); // Need to switch from pressed to not pressed mPrivateFlags &= ~PRESSED; refreshDrawableState(); } } break; } return true; } return false; }
雖然源碼有點多,但是我們只重點關(guān)注關(guān)鍵代碼,在38行我們看到了代碼:performClick();這個方法從名字表義來看就是OnClick方法的調(diào)用,我們進(jìn)入到該方法中去看一探究竟,是否執(zhí)行了OnClick方法呢?
public boolean performClick() { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); if (mOnClickListener != null) { playSoundEffect(SoundEffectConstants.CLICK); mOnClickListener.onClick(this); return true; } return false; }
從上述代碼可以看到,只要mOnClickListener不是null,就會去調(diào)用它的onClick方法,那mOnClickListener又是在哪里賦值的呢?經(jīng)過分析后找到如下方法:
public void setOnClickListener(OnClickListener l) { if (!isClickable()) { setClickable(true); } mOnClickListener = l; }
而上述這個方法就是我們在Application層經(jīng)常使用的方法,即我們給button 設(shè)置點擊事件的時候就會調(diào)用該方法了,分析到這了,我們知道了OnClick方法確實是在OnTouchEvent方法中,那么除了要設(shè)置 OnClickListener,調(diào)用onClick的條件又是什么呢?我們從38行代碼往前推,從第14行可以分析出:
只要該控件是可點擊的或者是長按類型的,則會進(jìn)入到MotionEvent.ACTION_UP這個分支當(dāng)中 ,然后經(jīng)過各種條件判斷,則會進(jìn)入到38行的performClick()方法中。
至此,一切都清晰明白了!當(dāng)我們通過調(diào)用setOnClickListener方法來給控件注冊一個點擊事件時,就會給mOnClickListener賦值。然后每當(dāng)控件被點擊時或者長按時,都會在performClick()方法里回調(diào)被點擊控件的onClick方法。
經(jīng)驗之談:
關(guān)于OnTouchEvent(MotionEvent事件)事件的層級傳遞。我們都知道如果給一個控件注冊了touch事件,每次點擊它的時候都會觸發(fā)一系列的ACTION_DOWN,ACTION_MOVE,ACTION_UP等事件。這里需要注意,如果你在執(zhí)行ACTION_DOWN的時候返回了false,后面一系列其它的action就不會再得到執(zhí)行了。簡單的說,就是當(dāng)dispatchTouchEvent在進(jìn)行事件分發(fā)的時候,只有前一個action返回true,才會觸發(fā)后一個action。
那我們可以換一個控件,將按鈕替換成ImageView,然后給它也注冊一個touch事件,并返回false。
以上這篇淺談onTouch先執(zhí)行,還是onClick執(zhí)行(詳解)就是小編分享給大家的全部內(nèi)容了,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Android自定義Drawable實現(xiàn)圓形和圓角
這篇文章主要為大家詳細(xì)介紹了Android自定義Drawable實現(xiàn)圓形和圓角,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-09-09Android kotlin RecyclerView遍歷json實現(xiàn)列表數(shù)據(jù)的案例
這篇文章主要介紹了Android kotlin RecyclerView遍歷json實現(xiàn)列表數(shù)據(jù)的案例,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧2024-08-08Kotlin實現(xiàn)多函數(shù)接口的簡化調(diào)用
這篇文章主要為大家詳細(xì)介紹了Kotlin實現(xiàn)多函數(shù)接口的簡化調(diào)用,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-06-06Anroid四大組件service之本地服務(wù)的示例代碼
本篇文章主要介紹了Anroid四大組件service之本地服務(wù)的示例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-10-10Android數(shù)據(jù)加密之SHA安全散列算法
這篇文章主要為大家詳細(xì)介紹了Android數(shù)據(jù)加密之SHA安全散列算法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-09-09Android實現(xiàn)自定義的衛(wèi)星式菜單(弧形菜單)詳解
相信大家經(jīng)常在應(yīng)用中會看到衛(wèi)星菜單,那么這篇文章就來介紹在Android中如何實現(xiàn)自定義的衛(wèi)星式菜單(弧形菜單),有需要的可以參考學(xué)習(xí)。2016-08-08Android Studio和Gradle使用不同位置JDK的問題解決
這篇文章主要介紹了Android Studio和Gradle使用不同位置JDK的問題解決,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03Android開發(fā) -- 狀態(tài)欄通知Notification、NotificationManager詳解
本文主要講解狀態(tài)欄通知Notification、NotificationManager,小編覺得非常詳細(xì),給大家一個參考,希望對大家學(xué)習(xí)有所幫助。2016-06-06Android Recyclerview實現(xiàn)上拉加載更多功能
在項目中使用列表的下拉刷新和上拉加載更多是很常見的功能。下文給大家?guī)砹薃ndroid Recyclerview上拉加載更多功能,需要的朋友參考下吧2017-05-05