源码阅读分析 View的Touch事件分发
其實 Android 事件分發機制在早幾年一直都困擾著我,那時候處理事件分發的自定義 View 腦子都是一片白,老感覺處理不好。后來自己看了 android 源碼,也閱讀了很多大牛的文章才算徹底明白,總之掌握 Android 事件分發機制是必不可少的,而 Android 事件分發機制絕對不是三言兩語就能說得清的。
而今天由于我們自定義 View 進階的需要,自己也是籌備了很久。目前雖然網上相關的文章也不少,很多也寫得非常詳細,但是多數文章只是講了講理論,然后配合 Log 打印一下結果而已。而我準備不僅帶著大家從源碼的角度進行分析,還需要理論結合實踐寫幾個關于這方面的效果,這樣相信我們會有更深的理解。閱讀源碼講究由淺入深,循序漸進,我們就不像其他文章一樣搞混合了,先講 View 的 Touch 事件分發,然后再講 ViewGroup 的事件分發,最后再寫個幾次效果。我們一貫的套路都是理論結合實踐,由淺入深
先來看幾個效果,如前幾次我們寫自定義評分控件的 RatingBar 復寫了 onTouchEvent(),這里只是舉個例子:
public class RatingBar extends View {public RatingBar(Context context) {super(context);}public RatingBar(Context context, @Nullable AttributeSet attrs) {super(context, attrs);}public RatingBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}@Overridepublic boolean onTouchEvent(MotionEvent event) {Log.d("TAG", "onTouchEvent execute -> " + event.getAction());return super.onTouchEvent(event);} } 復制代碼如果想要給這個控件注冊一個點擊事件,只需要調用:
mRatingBar.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Log.d("TAG","onClick execute");} }); 復制代碼如果想給這個按鈕再添加一個touch事件,只需要調用:
mRatingBar.setOnTouchListener(new View.OnTouchListener() {@Overridepublic boolean onTouch(View v, MotionEvent event) {Log.d("TAG", "onTouch execute -> " + event.getAction());return false;} }); 復制代碼上面的代碼如果運行起來,哪一個會先執行呢? 如果是靠猜,那么我們來試一下就知道了,運行程序點擊,打印結果如下:
可以看到,onTouch 是優先于優先于 onTouchEvent 優先于 onClick 執行的,并且 onTouch 和 onTouchEvent 執行了兩次,一次是 ACTION_DOWN ,一次是 ACTION_UP (你還可能會有多次 ACTION_MOVE 的執行,如果你在上面觸摸)。因此事件傳遞的順序是先經過 onTouch ,然后經過 onTouchEvent ,再傳遞到 onClick 。
如果留心觀察你會發現 setOnTouchListener 是有返回值的,如果返回 ture ,再次運行一下會怎樣?
我們發現,onTouchEvent 和 onClick 方法不再執行了!為什么會這樣呢?你可以先理解成 onTouch 方法返回 true 就認為這個事件被 onTouch 消費掉了,因而不會再繼續 onTouchEvent 和 onClick 。到目前位置如果你清楚了,那么面試的時候基本靠背,那么自己寫效果的時候基本靠蒙。我們肯定不能局限于這個裝態,接下了我們就帶著疑問從源碼的角度分析一下,為什么會出現上述情況?
首先我們需要知道,你點擊或者或者觸摸任何一個 View 都會調用 View 的 dispatchTouchEvent() 方法,我們就從這里開始分析源碼:
public boolean dispatchTouchEvent(MotionEvent event) {// 省略部分代碼 ...boolean result = false;// 省略部分代碼 ...if (onFilterTouchEventForSecurity(event)) {//noinspection SimplifiableIfStatementListenerInfo 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;}}// 返回 resultreturn result;} 復制代碼省略掉部分代碼之后,這個方法就變得非常的簡潔了,只有短短幾行代碼!我們可以看到,在這個方法內,首先是進行了一個判斷,如果li != null,mOnTouchListener != null,(mViewFlags & ENABLED_MASK) == ENABLED 和 mOnTouchListener.onTouch(this, event) 這三個條件都為真,result 就是 true,否則就去執行 onTouchEvent(event) 方法并返回。 那么 ListenerInfo 到底是什么?我們可以看下源碼,這其實就是有關 View 所有事件的一個集合類,如 OnFocusChangeListener , OnScrollChangeListener , OnClickListener 、、、
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;} 復制代碼先看一下條件 li.mOnTouchListener 這個變量是在哪里賦值的呢?我們尋找之后在View里發現了如下方法:
/*** Register a callback to be invoked when a touch event is sent to this view.* @param l the touch listener to attach to this view*/public void setOnTouchListener(OnTouchListener l) {getListenerInfo().mOnTouchListener = l;}ListenerInfo getListenerInfo() {if (mListenerInfo != null) {return mListenerInfo;}mListenerInfo = new ListenerInfo();return mListenerInfo;} 復制代碼第二個條件 mOnTouchListener 正是在 setOnTouchListener 方法里賦值的,也就是說只要我們給控件注冊了 touch 事件,mListenerInfo 和 mListenerInfo.mOnTouchListener 就一定被賦值了。
第三個條件(mViewFlags & ENABLED_MASK) == ENABLED是判斷當前點擊的控件是否是enable的,默認都是enable的,因此這個條件恒定為 true 。
第四個條件就比較關鍵了,mOnTouchListener.onTouch(this, event),其實也就是去回調控件注冊 touch 事件時的 onTouch 方法。也就是說如果我們在 onTouch 方法里返回true,就會讓這三個條件全部成立,從而 result 是 true , 那么 onTouchEvent 就不會被執行 。如果我們在 onTouch 方法里返回 false,就會去執行 onTouchEvent() 方法。
現在我們可以結合前面的例子來分析一下了,首先在 dispatchTouchEvent 中最先執行的就是 onTouch 方法,因此 onTouch 肯定是要優先于 onTouchEvent 方法,也是印證了剛剛的打印結果。而如果在 onTouch 方法里返回了 true,不會再執行 onTouchEvent 。但是到目前位置我們還沒有看到 onClick 執行,但是我們可以猜到,onClick的調用肯定是在onTouchEvent(event)方法中的!那我們馬上來看下onTouchEvent的源碼,如下所示:
public boolean onTouchEvent(MotionEvent event) {// 省略部分代碼if (((viewFlags & CLICKABLE) == CLICKABLE ||(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {switch (action) {case MotionEvent.ACTION_UP:boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {performClick();}mIgnoreNextUpEvent = false;break;// 省略部分代碼}return true;}return false;}public boolean performClick() {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);return result;} 復制代碼相較于剛才的 dispatchTouchEvent 方法,onTouchEvent 方法復雜了很多,不過沒關系,我們只挑重點看就可以了。switch 中如果當前的事件是抬起手指,則會進入到 MotionEvent.ACTION_UP 這個 case 當中。在經過種種判斷之后,會執行到 performClick() 方法,可以看到,只要 mListenerInfo.mOnClickListener 不是 null,就會去調用它的 onClick 方法,那 mListenerInfo.mOnClickListener 又是在哪里賦值的呢?我們大概能猜到肯定在 setOnclickLstener 方法中:
public void setOnClickListener(@Nullable OnClickListener l) {if (!isClickable()) {setClickable(true);}getListenerInfo().mOnClickListener = l;}ListenerInfo getListenerInfo() {if (mListenerInfo != null) {return mListenerInfo;}mListenerInfo = new ListenerInfo();return mListenerInfo;} 復制代碼View 的 Touch 事件分發我們就講到這里了,下一節我將帶著大家一起了解 ViewGroup 的事件分發和事件攔截,在源碼的基礎上寫幾個效果,我想應該可以說就堪稱完美了。
所有分享大綱:Android進階之旅 - 自定義View篇
視頻講解地址:pan.baidu.com/s/1hr6ql72
總結
以上是生活随笔為你收集整理的源码阅读分析 View的Touch事件分发的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 23 duplicate symbols
- 下一篇: Java并发工具类(闭锁CountDow