Android touch 事件傳遞機制


前言:

(1)在自定義view的時候經常會遇到事件攔截處理,比如在側滑菜單的時候,我們希望在側滑菜單里面有listview控件,但是我們希望既能左右滑動又能上下滑動,這個時候就需要對觸摸的touch事件進行攔截。這個時候我們就需要明白android touch 事件傳遞機制,

(2)以前很多時候比較模糊,也許是網上看到也有很多事件傳遞的相關文章,但我看着頭暈,解釋不徹底,有的說得一半,總算不滿足不滿意,於是據我自己的理解來徹底的來整理下具體的是怎么個傳遞方式,以最簡單通俗易懂的方式分享給大家,希望大家看到有什么不對的地方及時提出糾正。謝謝 

測試布局:

8CE09066 2E13 4CCE 81E6 019317E8068C

這是本次理解android touch 事件傳遞機制的布局文件

傳遞機制視圖: 

 Android touch

(1)android touch 事件傳遞機制示意圖,由於網頁原因會被拉伸,請大家單獨將該圖在另一個窗口打開查看。

(2)事件是從Activity觸發事件然后傳遞到布局文件,一層一層的往子容器傳遞到最底層的view,如果每層布局文件未對該事件進行處理或者消費那么該事件會從最底層開始往上傳到Activity進行消費。類似於一個U型。

(3)那么事件的發起是由Activity界面的touch事件發起傳遞到布局視圖,但是該視圖只是描述了布局文件或者view的相關事件傳遞機制,Activity事件沒有進行描述,但是在下面測試中會涉及Activity相關事件傳遞來解釋心中些許疑惑

事件傳遞另一個角度解釋:

android touch 事件傳遞機制示意圖可以總結如下規律

(0)忘掉以前的各種說法解釋,因為各種組合說來說去的,我們需要簡單的,換一個角度,換一個思維,忘記事件傳遞,以最簡單通俗易懂的方式理解事件傳遞,我只需要一張圖就搞定事件傳遞機制。

(1)一個事件由用戶點擊觸發開始順着箭頭的方向進行傳遞,直到任意一個結束點結束事件傳遞。

(2)那么事件傳遞可以由A傳到B,B可以不傳到C,B不進行分發,那么就從B再傳回A進行消費然后結束,也可以由B傳到C然后傳遞D,或者不傳到D進行消費或者傳到父容器進行消費結束。

將圖變成理論印在腦海里

一、touch事件傳遞過程,我們需要搞清楚三種種情況:

  (1)事件從Activity界面開始  事件處理方法有兩個  dispatchTouchEventon 和 TouchEvent 兩種

  (2)繼承ViewGroup的子類事件處理有dispatchTouchEvent、onInterceptTouchEvent 和 onTouchEvent三種

  (3)繼承View的子類事件處理有dispatchTouchEventon 和 TouchEvent 兩種

二、touch事件傳遞形象說明

  (1)touch事件事件傳遞形象的理解可以這么認為:比如我有一個蘋果(touch),我可以自己吃也可以分發(dispatchTouchEventon)給孩子吃(TouchEvent)。如果我不吃那么我就返回給我的父親處理,如果我分發給孩子那么孩子,那么這個蘋果交給我的孩子他有自己獨立的權利進行處理,他可以繼續分給他的孩子就是我的孫子進行處理,也可以自己吃了吃掉,如果我的孫子不處理他也可以返回給他的父親就是我的孩子處理,我的孩子也有相同的權利進行處理。

 (2)接着上面其實這個事件(蘋果)的傳遞是從上往下,然后再由下往上傳遞,中途如果有人消費這個事件(吃掉蘋果),那么這個事件就結束(蘋果沒有了),就結束傳遞。

 (3)事件(蘋果)傳遞,不像我們人一樣要害羞要矜持,推來推去,比如這個蘋果孩子不分發給他的孩子但是他自己又不想消費(吃掉)而是返回給我,那么我就是只有兩個選擇要么消費(吃掉)要么返回給我的父類進行處理,不能推來推去,就是不能孩子給我了事件(蘋果),我又來分發給孩子,這是不行的,這樣就是個死循環。

 (4)總之我拿到這個事件(蘋果)會往我的孩子進行傳遞,我的孩子也可以往他的孩子進行傳遞和消費,這樣轉往下走,如果有一個孩子消費掉這個事件(吃掉蘋果),那么該事件結束。如果孩子都不消費那么就會從最下面的孩子一層層傳上來傳到我手里進行處理。

 (5)記住  分發  攔截  處理。任何孩子拿到該事件第一步就是往下面分發,如果中途有攔截那么久就自己處理,直到分到最底層就輩分最低的孩子,如果該事件就往上給父親處理。

4243F93F C4BE 40DD A12D F5ED8DDF0B9A 

二、事件處理邏輯流程

  (1)事件無論是從開始觸發還是在傳遞到不同的層級布局文件過程中,一定會對事件進行分發,當然在父容器中如果touch事件被攔截了就不會下傳了。就是如果該事件傳遞到某一層那么該層的dispatchTouchEventon會首先被調用進行事件分發,比如示意圖中的事件傳遞到C-ViewGroup層,那么C-ViewGroup中的 dispatchTouchEventon這個方法是一定會被調用的進行事件分發,當然如果事件在B-ViewGroup不分發事件就會往上傳遞給父類的onTouchEvent進行處理,或者在B-ViewGroup被攔截發事件就會往上傳遞給B-ViewGroup的onTouchEvent進行處理,也就不會傳到C-ViewGroup這層。

 (2)在對事件進行分發中,dispatchTouchEventon返回值為false表示對事件進行分發,返回true為不分發,當事件不進行分發的時候,那么該事件在該層就不會往下傳遞,就會返回傳遞給父容器onTouchEvent進行處理,那么如果該事件被攔截事件就傳遞給當前容器onTouchEvent進行處理,如果當前容器onTouchEvent返回false就表示不想消費處理,那么該事件就會往上傳遞給父容器onTouchEvent進行處理,記住onTouchEvent這里不能往下傳,如果這里不消費就只能往上傳遞,只有dispatchTouchEventon才能往下分發。

測試驗證事件傳遞機制:

一、在測試驗證之前需要了解每個View的子類都具有下面三個和TouchEvent處理密切相關的方法:

(1)public boolean dispatchTouchEvent(MotionEvent ev) 分發TouchEvent
(2)public boolean onInterceptTouchEvent(MotionEvent ev) 攔截TouchEvent
(3)public boolean onTouchEvent(MotionEvent ev) 處理TouchEvent

二、界面效果

6C94DDF8 30CD 482E B1A8 D108DF463A9B

三、布局文件代碼

<?xml version="1.0" encoding="utf-8"?>
<boyoi.com.event.transfer.ALinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#4bf3a8">

<boyoi.com.event.transfer.BViewGroup
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="50dp"
android:background="#4a8f8f">

<boyoi.com.event.transfer.CViewGroup

android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="50dp"
android:background="#847834">

<boyoi.com.event.transfer.DView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="50dp"
android:background="#765982">

</boyoi.com.event.transfer.DView>
</boyoi.com.event.transfer.CViewGroup>
</boyoi.com.event.transfer.BViewGroup>
</boyoi.com.event.transfer.ALinearLayout>

 

  布局文件相關代碼

/**
* Created by yishujun on 16/6/12.
*/
public class ALinearLayout extends LinearLayout{
private final String TAG = "ALinearLayout";
public ALinearLayout(Context context) {
super(context);
}

public ALinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}

public ALinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.i(TAG,"dispatchTouchEvent" );
return super.dispatchTouchEvent(ev);
}


@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.i(TAG,"onInterceptTouchEvent" );
return super.onInterceptTouchEvent(ev);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i(TAG,"onTouchEvent" );
return super.onTouchEvent(event);
}
}

 

/**
* Created by yishujun on 16/6/12.
*/
public class BViewGroup extends ViewGroup{
private final String TAG = "BViewGroup";
public BViewGroup(Context context) {
super(context);
}

public BViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
}

public BViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}


@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//該demo主要講解android的事件傳遞,這里不解釋,如有疑問請關注我接下來的自定義view相關博客
measureChildren(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
//該demo主要講解android的事件傳遞,這里不解釋,,如有疑問請關注我接下來的自定義view相關博客
//下面為什么這么處理,因為例子中只有一共孩子,getChildAt(0)只能是只有一個孩子,不然得不到我們要的效果
View childView = getChildAt(0);
childView.layout(l, t, r-2*l, b-2*t);
}

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.i(TAG,"dispatchTouchEvent" );
return super.dispatchTouchEvent(ev);
}


@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.i(TAG,"onInterceptTouchEvent" );
return super.onInterceptTouchEvent(ev);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i(TAG,"onTouchEvent" );
return super.onTouchEvent(event);
}
}

 

/**
* Created by yishujun on 16/6/12.
*/
public class CViewGroup extends ViewGroup {
private final String TAG = "CViewGroup";
public CViewGroup(Context context) {
super(context);
}

public CViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
}

public CViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}


@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//該demo主要講解android的事件傳遞,這里不解釋,如有疑問請關注我接下來的自定義view相關博客
measureChildren(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
}

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
//該demo主要講解android的事件傳遞,這里不解釋,,如有疑問請關注我接下來的自定義view相關博客
//下面為什么這么處理,因為例子中只有一共孩子,getChildAt(0)只能是只有一個孩子,不然得不到我們要的效果
View childView = getChildAt(0);
childView.layout(l, t, r-2*l, b-2*t);
}


@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.i(TAG,"dispatchTouchEvent" );
return super.dispatchTouchEvent(ev);
}


@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.i(TAG,"onInterceptTouchEvent" );
return super.onInterceptTouchEvent(ev);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i(TAG,"onTouchEvent" );
return super.onTouchEvent(event);
}
}

 


/**
* Created by yishujun on 16/6/12.
*/
public class DView extends View{
private final String TAG = "DView";

public DView(Context context) {
super(context);
}

public DView(Context context, AttributeSet attrs) {
super(context, attrs);
}

public DView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 計算出自己的寬高
setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
}


@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.i(TAG,"dispatchTouchEvent" );
return super.dispatchTouchEvent(ev);
}


@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i(TAG,"onTouchEvent" );
return super.onTouchEvent(event);
}
}

 

public class MainActivity extends AppCompatActivity {
private final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.i(TAG,"dispatchTouchEvent" );
return super.dispatchTouchEvent(ev);
}


@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i(TAG,"onTouchEvent" );
return super.onTouchEvent(event);
}

}

 

 

四:驗證結果

 測試用例一:所以的 touch事件都分發不攔截不消費,所有的dispatchTouchEvent,onInterceptTouchEvent,onTouchEven方法t都返回false,如以上代碼默認代碼,點擊中間的D-View:

06-12 11:09:56.274 31712-31712/boyoi.com.event.transfer I/MainActivity: dispatchTouchEvent

06-12 11:09:56.274 31712-31712/boyoi.com.event.transfer I/ALinearLayout: dispatchTouchEvent

06-12 11:09:56.274 31712-31712/boyoi.com.event.transfer I/ALinearLayout: onInterceptTouchEvent

06-12 11:09:56.274 31712-31712/boyoi.com.event.transfer I/BViewGroup: dispatchTouchEvent

06-12 11:09:56.274 31712-31712/boyoi.com.event.transfer I/BViewGroup: onInterceptTouchEvent

06-12 11:09:56.274 31712-31712/boyoi.com.event.transfer I/CViewGroup: dispatchTouchEvent

06-12 11:09:56.274 31712-31712/boyoi.com.event.transfer I/CViewGroup: onInterceptTouchEvent

06-12 11:09:56.274 31712-31712/boyoi.com.event.transfer I/DView: dispatchTouchEvent

06-12 11:09:56.274 31712-31712/boyoi.com.event.transfer I/DView: onTouchEvent

06-12 11:09:56.274 31712-31712/boyoi.com.event.transfer I/CViewGroup: onTouchEvent

06-12 11:09:56.274 31712-31712/boyoi.com.event.transfer I/BViewGroup: onTouchEvent

06-12 11:09:56.274 31712-31712/boyoi.com.event.transfer I/ALinearLayout: onTouchEvent

06-12 11:09:56.274 31712-31712/boyoi.com.event.transfer I/MainActivity: onTouchEvent

06-12 11:09:56.377 31712-31712/boyoi.com.event.transfer I/MainActivity: dispatchTouchEvent

06-12 11:09:56.377 31712-31712/boyoi.com.event.transfer I/MainActivity: onTouchEvent

由以上日志可以得出如下結論,事件是從Activity觸發事件然后傳遞到布局文件,一層一層的往子容器傳遞到最底層的view,如果每層布局文件未對該事件進行處理或者消費那么該事件會從最底層開始往上傳到Activity進行消費。類似於一個U型。

NewImage 

 

 測試用例二:在mainActivity界面對touch事件不進行分發,mainActivity 里面的dispatchTouchEvent返回true 則結果:

06-12 11:26:24.288 31712-31712/boyoi.com.event.transfer I/MainActivity: dispatchTouchEvent

06-12 11:26:24.390 31712-31712/boyoi.com.event.transfer I/MainActivity: dispatchTouchEvent

由以上日志可知,如果Activity界面不進行事件分發那么事件將無法往下傳遞

 測試用例三:在mainActivity界面對touch事件進行分發,但是在mainActivity 對該事件進行消費,不往下面傳遞,mainActivity 里面的 dispatchTouchEvent返回false,onTouchEvent 返回true 則結果:

06-12 11:32:06.521 31712-31712/boyoi.com.event.transfer I/MainActivity: dispatchTouchEvent

06-12 11:32:06.521 31712-31712/boyoi.com.event.transfer I/ALinearLayout: dispatchTouchEvent

06-12 11:32:06.521 31712-31712/boyoi.com.event.transfer I/ALinearLayout: onInterceptTouchEvent

06-12 11:32:06.521 31712-31712/boyoi.com.event.transfer I/BViewGroup: dispatchTouchEvent

06-12 11:32:06.521 31712-31712/boyoi.com.event.transfer I/BViewGroup: onInterceptTouchEvent

06-12 11:32:06.521 31712-31712/boyoi.com.event.transfer I/CViewGroup: dispatchTouchEvent

06-12 11:32:06.521 31712-31712/boyoi.com.event.transfer I/CViewGroup: onInterceptTouchEvent

06-12 11:32:06.521 31712-31712/boyoi.com.event.transfer I/DView: dispatchTouchEvent

06-12 11:32:06.521 31712-31712/boyoi.com.event.transfer I/DView: onTouchEvent

06-12 11:32:06.521 31712-31712/boyoi.com.event.transfer I/CViewGroup: onTouchEvent

06-12 11:32:06.521 31712-31712/boyoi.com.event.transfer I/BViewGroup: onTouchEvent

06-12 11:32:06.522 31712-31712/boyoi.com.event.transfer I/ALinearLayout: onTouchEvent

06-12 11:32:06.522 31712-31712/boyoi.com.event.transfer I/MainActivity: onTouchEvent

06-12 11:32:06.634 31712-31712/boyoi.com.event.transfer I/MainActivity: dispatchTouchEvent

06-12 11:32:06.634 31712-31712/boyoi.com.event.transfer I/MainActivity: onTouchEvent

結果與測試用例一樣的效果

測試用例四:在CViewGroup界面對touch事件進行攔截, 並自己消費該事件,CViewGroup 里面的 onInterceptTouchEvent返回true,onTouchEvent 返回true 則結果:

06-12 11:38:28.949 31712-31712/boyoi.com.event.transfer I/MainActivity: dispatchTouchEvent

06-12 11:38:28.949 31712-31712/boyoi.com.event.transfer I/ALinearLayout: dispatchTouchEvent

06-12 11:38:28.949 31712-31712/boyoi.com.event.transfer I/ALinearLayout: onInterceptTouchEvent

06-12 11:38:28.949 31712-31712/boyoi.com.event.transfer I/BViewGroup: dispatchTouchEvent

06-12 11:38:28.949 31712-31712/boyoi.com.event.transfer I/BViewGroup: onInterceptTouchEvent

06-12 11:38:28.949 31712-31712/boyoi.com.event.transfer I/CViewGroup: dispatchTouchEvent

06-12 11:38:28.949 31712-31712/boyoi.com.event.transfer I/CViewGroup: onTouchEvent

由以上日志可以看到,事件傳遞到CViewGroup就不在往下傳遞了,也不往上傳遞了,自己消費了

測試用例五:在CViewGroup界面對touch事件進行攔截, 自己不消費該事件,CViewGroup 里面的 onInterceptTouchEvent返回true,onTouchEvent 返回false 則結果:

06-12 11:41:23.857 31712-31712/boyoi.com.event.transfer I/MainActivity: dispatchTouchEvent

06-12 11:41:23.857 31712-31712/boyoi.com.event.transfer I/ALinearLayout: dispatchTouchEvent

06-12 11:41:23.857 31712-31712/boyoi.com.event.transfer I/ALinearLayout: onInterceptTouchEvent

06-12 11:41:23.857 31712-31712/boyoi.com.event.transfer I/BViewGroup: dispatchTouchEvent

06-12 11:41:23.857 31712-31712/boyoi.com.event.transfer I/BViewGroup: onInterceptTouchEvent

06-12 11:41:23.857 31712-31712/boyoi.com.event.transfer I/CViewGroup: dispatchTouchEvent

06-12 11:41:23.857 31712-31712/boyoi.com.event.transfer I/CViewGroup: onInterceptTouchEvent

06-12 11:41:23.857 31712-31712/boyoi.com.event.transfer I/CViewGroup: onTouchEvent

06-12 11:41:23.857 31712-31712/boyoi.com.event.transfer I/BViewGroup: onTouchEvent

06-12 11:41:23.857 31712-31712/boyoi.com.event.transfer I/ALinearLayout: onTouchEvent

06-12 11:41:23.857 31712-31712/boyoi.com.event.transfer I/MainActivity: onTouchEvent

06-12 11:41:23.936 31712-31712/boyoi.com.event.transfer I/MainActivity: dispatchTouchEvent

06-12 11:41:23.936 31712-31712/boyoi.com.event.transfer I/MainActivity: onTouchEvent

由以上日志可以看到,事件傳遞到CViewGroup就不在往下傳遞了,但是自己也並未消費,而是將該事件傳遞給父容器了

好了其他的測試例子自己可以進行嘗試,我就不一一嘗試了,如果有疑問或者錯誤需要糾正請留言,我一般看到會及時回答,謝謝交流溝通。后面接下來我還會寫一些自定義控件來闡述事件傳遞在什么時候用和怎么用,希望支持鼓勵。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM