其實 Android 事件分發機制在早幾年一直都困擾着我,那時候處理事件分發的自定義 View 腦子都是一片白,老感受處理很差。後來本身看了 android 源碼,也閱讀了不少大牛的文章纔算完全明白,總之掌握 Android 事件分發機制是必不可少的,而 Android 事件分發機制絕對不是三言兩語就能說得清的。android
而今天因爲咱們自定義 View 進階的須要,本身也是籌備了好久。目前雖然網上相關的文章也很多,不少也寫得很是詳細,可是多數文章只是講了講理論,而後配合 Log 打印一下結果而已。而我準備不只帶着你們從源碼的角度進行分析,還須要理論結合實踐寫幾個關於這方面的效果,這樣相信咱們會有更深的理解。閱讀源碼講究由淺入深,按部就班,咱們就不像其餘文章同樣搞混合了,先講 View 的 Touch 事件分發,而後再講 ViewGroup 的事件分發,最後再寫個幾回效果。咱們一向的套路都是理論結合實踐,由淺入深面試
先來看幾個效果,如前幾回咱們寫自定義評分控件的 RatingBar 複寫了 onTouchEvent(),這裏只是舉個例子:bash
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);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("TAG", "onTouchEvent execute -> " + event.getAction());
return super.onTouchEvent(event);
}
}
複製代碼
若是想要給這個控件註冊一個點擊事件,只須要調用:ide
mRatingBar.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d("TAG","onClick execute");
}
});
複製代碼
若是想給這個按鈕再添加一個touch事件,只須要調用:ui
mRatingBar.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.d("TAG", "onTouch execute -> " + event.getAction());
return false;
}
});
複製代碼
上面的代碼若是運行起來,哪個會先執行呢? 若是是靠猜,那麼咱們來試一下就知道了,運行程序點擊,打印結果以下:this
能夠看到,onTouch 是優先於優先於 onTouchEvent 優先於 onClick 執行的,而且 onTouch 和 onTouchEvent 執行了兩次,一次是 ACTION_DOWN ,一次是 ACTION_UP (你還可能會有屢次 ACTION_MOVE 的執行,若是你在上面觸摸)。所以事件傳遞的順序是先通過 onTouch ,而後通過 onTouchEvent ,再傳遞到 onClick 。spa
若是留心觀察你會發現 setOnTouchListener 是有返回值的,若是返回 ture ,再次運行一下會怎樣?code
咱們發現,onTouchEvent 和 onClick 方法再也不執行了!爲何會這樣呢?你能夠先理解成 onTouch 方法返回 true 就認爲這個事件被 onTouch 消費掉了,於是不會再繼續 onTouchEvent 和 onClick 。到目前位置若是你清楚了,那麼面試的時候基本靠背,那麼本身寫效果的時候基本靠蒙。咱們確定不能侷限於這個裝態,接下了咱們就帶着疑問從源碼的角度分析一下,爲何會出現上述狀況?orm
首先咱們須要知道,你點擊或者或者觸摸任何一個 View 都會調用 View 的 dispatchTouchEvent() 方法,咱們就從這裏開始分析源碼:視頻
public boolean dispatchTouchEvent(MotionEvent event) {
// 省略部分代碼 ...
boolean result = false;
// 省略部分代碼 ...
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;
}
}
// 返回 result
return 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