View事件分發(fā)(四) - View事件分發(fā)(源碼分析)

1. 概述


前兩篇文章記錄了View事件分發(fā)的一些理論基礎(chǔ),這篇文章主要 從 View的 dispatchTouchEvent 源碼角度 分析下 View事件分發(fā)流程;

下邊通過(guò)一個(gè)示例代碼來(lái)分析

2. 示例如下


創(chuàng)建 activity_main 布局

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/btn1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"
        android:layout_centerInParent="true"
        />

</RelativeLayout>

給Button設(shè)置 setOnClickListener、setOnTouchListener事件

public class TextViewActivity extends AppCompatActivity {

    private Button btn1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_scroll);

        btn1 = (Button) findViewById(R.id.btn1);

        btn1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.e("TAG" , "onClick") ;
            }
        });

        btn1.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                Log.e("TAG" , "onTouch , action: "+event.getAction()) ;
                return false;
            }
        });
    }

}

0代表down、1代表up,2代表move,一般可能有多個(gè)move:

onTouch返回true,log如下:

onTouch , action: 0
onTouch , action: 2
onTouch , action: 2
...
onTouch , action: 1

onTouch返回false,log如下:

onTouch , action: 0
onTouch , action: 2
onTouch , action: 2
...
onTouch , action: 1
onClick

現(xiàn)象是:
如果 onTouch 返回true,表示消費(fèi)事件,就不會(huì)向下傳遞,就不會(huì)執(zhí)行 onClick,只會(huì)執(zhí)行自己的 down、move(多個(gè)move)、up事件;
如果 onTouch 返回false,執(zhí)行 down、move(多個(gè)move)、up事件,最后執(zhí)行 onClick;

下邊通過(guò) View的 dispatchTouchEvent 源碼 進(jìn)行分析 View的事件分發(fā);

3. dispatchTouchEvent源碼分析


前提知識(shí):

只要觸摸任何一個(gè)控件,就一定會(huì)調(diào)用該控件的 dispatchTouchEvent,如果該控件沒(méi)有,就一路向上查找,直到找到它父類的 dispatchTouchEvent方法然后調(diào)用,比如Button如下:


圖片.png
1>:首先看 View 的 dispatchTouchEvent方法如下:
public boolean dispatchTouchEvent(MotionEvent event) {  
    // 存放所有的 listener信息
    ListenerInfo li = mListenerInfo;

    // mOnTouchListener :只要設(shè)置 setOnTouchListener()之后,就不會(huì) null;
    // mViewFlags & ENABLED_MASK:只要 該控件是可點(diǎn)擊的,這個(gè)就是 true;
    // 主要是mOnTouchListener.onTouch(this, event),這個(gè)調(diào)用 的是  setOnTouchListener 
    // 的 onTouche() 方法,只要 onTouch() 返回 false,就會(huì)執(zhí)行下邊的 onTouchEvent(event)
    if (li != null && mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&  
            mOnTouchListener.onTouch(this, event)) {  
        return true;  
    }  
    return onTouchEvent(event);  
}


static class ListenerInfo {
        protected OnFocusChangeListener mOnFocusChangeListener;

        private ArrayList<OnLayoutChangeListener> mOnLayoutChangeListeners;

        protected OnScrollChangeListener mOnScrollChangeListener;

        private CopyOnWriteArrayList<OnAttachStateChangeListener> mOnAttachStateChangeListeners;

        public OnClickListener mOnClickListener;
        protected OnLongClickListener mOnLongClickListener;

        protected OnContextClickListener mOnContextClickListener;
        protected OnCreateContextMenuListener mOnCreateContextMenuListener;

        private OnKeyListener mOnKeyListener;

        private OnTouchListener mOnTouchListener;

        private OnHoverListener mOnHoverListener;

        private OnGenericMotionListener mOnGenericMotionListener;

        private OnDragListener mOnDragListener;

        private OnSystemUiVisibilityChangeListener mOnSystemUiVisibilityChangeListener;

        OnApplyWindowInsetsListener mOnApplyWindowInsetsListener;
    }

從 dispatchTouchEvent 方法中可知:首先是if判斷,如果 mOnTouchListener != null、mViewFlags & ENABLED_MASK== ENABLED、mOnTouchListener.onTouch(this, event) 這3個(gè) 條件 都為真,就 返回 true,否則 執(zhí)行 onTouchEvent(event)方法并返回;

第一個(gè)條件:mOnTouchListener != null:

public void setOnTouchListener(OnTouchListener l) {  
    mOnTouchListener = l;  
} 

可以看到 mOnTouchListener 在 setOnTouchListener() 方法中被賦值,也就是說(shuō) 只要給控件 設(shè)置 setOnTouchListener后, mOnTouchListener 就 會(huì)被賦值;

第二個(gè)條件:mViewFlags & ENABLED_MASK== ENABLED:判斷當(dāng)前點(diǎn)擊 控件是否是 enable的,Button 默認(rèn)是 enable的,ImageView 、TextView不是,所以這個(gè) 條件是 true;

第三個(gè)條件:mOnTouchListener.onTouch(this, event):

    public interface OnTouchListener {
        boolean onTouch(View v, MotionEvent event);
    }

調(diào)用的就是 setOnTouchListener中的 onTouch() 方法,可以看到:
如果 onTouch 返回 true,那么 這3個(gè)條件 都為 true,從而整個(gè)方法返回 true;
如果 onTouch 返回 false,就 執(zhí)行下邊的 onTouchEvent() 方法;

從這3個(gè)條件可知:
前兩個(gè)條件肯定都為 true,所以 在 dispatchTouchEvent方法中最先執(zhí)行 setOnTouchListener的onTouch() 方法, 優(yōu)先級(jí)順序: onTouch > onClick;
如果 onTouch 返回 true,dispatchTouchEvent() 整個(gè)方法就 返回 true,不會(huì)往下執(zhí)行,onClick 就不會(huì)執(zhí)行;

onTouchEvent源碼如下:

public boolean onTouchEvent(MotionEvent event) {

        // 此處省略一些代碼
        ......

        // 從這里可知:
        // 如果該 控件是可點(diǎn)擊的,比如 Button ,就會(huì)進(jìn)入 switch 判斷,在 Action_Up的 case 語(yǔ)句中,
        // 在 經(jīng)過(guò)一系列判斷后,會(huì)進(jìn)入到  performClick()方法;
        // 不管當(dāng)前 的 action 是什么,在 switch 語(yǔ)句外層,都會(huì) 返回 true,

        // 如果該 控件 不是可點(diǎn)擊的,比如 ImageView、TextView,就不會(huì)進(jìn)入 if判斷,更不會(huì)進(jìn)入 switch 語(yǔ)句,
        // 直接在  最外層的  if語(yǔ)句 返回 false

        if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
                (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {

            switch (action) {
                case MotionEvent.ACTION_UP:

                            if (!focusTaken) {
                                // 此處省略一些判斷條件
                                if (!post(mPerformClick)) {
                                    performClick();
                                }
                            }
                        }
                    break;

                case MotionEvent.ACTION_DOWN:
                // 此處省略 down 的一些代碼
                    break;

                case MotionEvent.ACTION_CANCEL:
                // 此處省略 cancel 的一些代碼
                    break;

                case MotionEvent.ACTION_MOVE:
                // 此處省略 move 的一些代碼
                break;
          }
            return true;
        }
        return false;
    }

performClick()源碼如下:

    public boolean performClick() {
        final boolean result;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }
        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
        return result;
    }

如果 mOnClickListener 不為 null,就會(huì) 調(diào)用 它的 onClick() 方法,mOnClickListener在 setOnClickListener中賦值的:

    public void setOnClickListener(@Nullable OnClickListener l) {
        if (!isClickable()) {
            setClickable(true);
        }
        getListenerInfo().mOnClickListener = l;
    }

也就是說(shuō) 只要調(diào)用 setOnClickListener時(shí),就會(huì)給 mOnClickListener 賦值,只要 Button被點(diǎn)擊,就會(huì)調(diào)用 performClick() 中的 onClick() 方法;

分析Button:
在 View的 dispatchTouchEvent()方法中,對(duì)于 那3個(gè)條件,第三個(gè)條件的 setOnTouchListener中的 onTouch()如果返回 false,此時(shí)進(jìn)入 onTouchEvent()方法,這個(gè)方法中,因?yàn)?Button 是可以點(diǎn)擊的,所以就會(huì) 進(jìn)入到 onTouchEvent()的 if (((viewFlags & CLICKABLE) == CLICKABLE () 語(yǔ)句,會(huì)發(fā)現(xiàn) 不管當(dāng)前 action 是什么,都會(huì)返回true,這個(gè)是系統(tǒng)幫我們返回的;
分析ImageView:
給 ImageView 設(shè)置 setOnTouchListener(),然后給 它的 onTouch()返回 false,此時(shí)進(jìn)入 onTouchEvent()方法,因?yàn)?ImageView 是不可點(diǎn)擊的,所以就不會(huì)進(jìn)入 onTouchEvent()的 if (((viewFlags & CLICKABLE) == CLICKABLE () 語(yǔ)句,直接 在 這個(gè) if() 語(yǔ)句 最外層就返回 false

4. 結(jié)論


1. touch事件層級(jí)傳遞,就是給控件設(shè)置 setOnTouchListener():
如果給 控件 設(shè)置 setOnTouchListener(),就會(huì) 觸發(fā)一系列的 down、move、up事件,如果 down 中返回false,后邊的 move、up等一系列事件均不會(huì)執(zhí)行,意思就是 要想觸發(fā)后邊的 某個(gè) move 或者 up事件執(zhí)行,前邊的 down事件就要返回true;

2. onTouch() 與 onTouchEvent() 區(qū)別,如何使用?:
這兩個(gè)方法 都是 在 View 的 dispatchTouchEvent() 方法中調(diào)用的 , 這里的 onTouch() 其實(shí)就是 dispatchTouchEvent() 中的 第三個(gè)條件;
優(yōu)先級(jí): setOnTouchListener 的onTouch > onTouchEvent();

如果 onTouch() 返回 true,表示消費(fèi)事件,那 3個(gè) 條件 都為 true,就不會(huì)執(zhí)行下邊的 onTouchEvent();

onTouch 要執(zhí)行的條件有2個(gè):
第一:mOnTouchListener不為null,意思就是 給 該 控件 設(shè)置了 setOnTouchListener();
第二:該控件要是 可點(diǎn)擊的,就是 enable的;

如果 點(diǎn)擊控件是 非enable的,setOnTouchListener的 onTouch()不會(huì)執(zhí)行,比如ImageView、TextView等, 對(duì)于 這類控件,如果想監(jiān)聽它的 onTouch事件,就 需要在 該控件中重寫 onTouchEvent方法 來(lái)實(shí)現(xiàn);

比如 ImageView,想監(jiān)聽它的 onTouch事件,有2種方式:

  1. onTouch方法返回 true;
  2. 在布局中給 ImageView 增加 android:clickable="true"的屬性;
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容