我們在寫自定義ViewGroup或者自定義View的時候經常要處理用戶的點擊事件,如果我們的View在最底層,他在很多ViewGroup裡面,我們如何讓我們的點擊事件准確傳遞到View裡面,這就涉及到一個View很重要的知識點,View的事件分發。事件分發,分開來講就是事件+分發,所謂事件指的就是View的被手機觸摸後產生的事件MotionEvent
,而分發指的就是MotionEvent
的傳遞和處理。
下面,我們說一下單手指觸摸事件有哪些
ACTION_DOWN
——手指剛觸摸屏幕ACTION_MOVE
——手指在屏幕上移動ACTION_UP
———手指從屏幕上松開的一瞬間
事件講完了,我們接下來說一下分發過程中涉及到的方法
dispatchTouchEvent(MotionEvent ev)
onInterceptTouchEvent(MotionEvent ev)
onTouchEvent(MotionEvent event)
所以事件分發,結合這些代碼就是每一個ACTION皆會觸發那些方法。我們在要做就是根據需求來決定那個事件分發到那層,以及搞清楚為什麼會這樣分發。
接下來,我們通過一個例子來仔細講講這三個方法以及上述三個事件。
測試的例子如上,我們編寫三個自定義view來演示這個效果,第一個是ViewGroupA,也就是最外層的綠的,第二個是ViewGroupB,也就是中間的藍的,然後是最裡面的黑色的View。XML布局如下:
<com.byhieg.viewdipatch.custormview.ViewGroupA
Android:layout_width="300dp"
android:layout_height="300dp"
android:background="@android:color/holo_green_light">
<com.byhieg.viewdipatch.custormview.ViewGroupB
android:layout_width="200dp"
android:layout_height="200dp"
android:background="@android:color/holo_blue_light">
<com.byhieg.viewdipatch.custormview.ViewTest
android:layout_width="100dp"
android:layout_height="100dp" />
</com.byhieg.viewdipatch.custormview.ViewGroupB>
</com.byhieg.viewdipatch.custormview.ViewGroupA>
ViewGroupA 裡面放入子View ——ViewGroupB 然後ViewGroupB放入子View-ViewTest
三者的代碼如下:ViewGroupA
:
public class ViewGroupA extends ViewGroup{
public ViewGroupA(Context context) {
super(context);
}
public ViewGroupA(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ViewGroupA(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
measureChildren(widthMeasureSpec,heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childCount = getChildCount();
for(int i = 0;i < childCount;i++) {
View child = getChildAt(i);
child.layout(0,0, Change.dip2px(getContext(),200),Change.dip2px(getContext(),200));
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.e("ViewGroupA","ViewGroupA dispatchTouchEvent" + ev.getAction());
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.e("ViewGroupA","ViewGroupA onInterceptTouchEvent" + ev.getAction());
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e("ViewGroupA","ViewGroupA onTouchEvent" + event.getAction());
return super.onTouchEvent(event);
}
}
ViewGroupB
:
public class ViewGroupB extends ViewGroup{
public ViewGroupB(Context context) {
super(context);
}
public ViewGroupB(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ViewGroupB(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
measureChildren(widthMeasureSpec,heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childCount = getChildCount();
for(int i = 0;i < childCount;i++) {
View child = getChildAt(i);
child.layout(0,0,getMeasuredWidth(),getMeasuredHeight());
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.e("ViewGroupB","ViewGroupB dispatchTouchEvent" + ev.getAction());
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.e("ViewGroupB","ViewGroupB onInterceptTouchEvent" + ev.getAction());
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e("ViewGroupB","ViewGroupB onTouchEvent" + event.getAction());
return super.onTouchEvent(event);
}
}
ViewTest
:
public class ViewTest extends View{
private Paint paint;
public ViewTest(Context context) {
super(context);
init();
}
public ViewTest(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public ViewTest(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.BLACK);
paint.setStrokeWidth(10);
paint.setStyle(Paint.Style.FILL);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawRect(0,0, Change.dip2px(getContext(),100), Change.dip2px(getContext(),100),paint);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.e("ViewTest","View dispatchTouchEvent" + event.getAction());
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e("ViewTest","View onTouchEvent" + event.getAction());
return super.onTouchEvent(event);
}
}
根據我們寫入的Log,當我們點擊最裡面的ViewTest的時候,我們會看到如下的Log輸出
08-30 07:47:13.741 7574-7574/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA dispatchTouchEvent0
08-30 07:47:13.741 7574-7574/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA onInterceptTouchEvent0
08-30 07:47:13.741 7574-7574/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB dispatchTouchEvent0
08-30 07:47:13.741 7574-7574/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB onInterceptTouchEvent0
08-30 07:47:13.741 7574-7574/com.byhieg.viewdipatch E/ViewTest: View dispatchTouchEvent0
08-30 07:47:13.741 7574-7574/com.byhieg.viewdipatch E/ViewTest: View onTouchEvent0
08-30 07:47:13.741 7574-7574/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB onTouchEvent0
08-30 07:47:13.741 7574-7574/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA onTouchEvent0
這些方法默認返回時false
然後按照如下傳遞原則:
在事件分發的時候,最外層的ViewGroup首先調用
dispatchTouchEvent()
方法,然後再執行onInterceptTouchEvent()
方法,而View是沒有onInterceptTouchEvent()
的方法的,在傳遞的時候,如果這些方法返回的是false
,則表示不攔截,事件會下面傳遞。我們在覆寫這些方法的時候,不作處理,默認返回時false
,所以我們可以看到事件傳遞到了ViewTest的dispatchTouchEvent()
,但注意dispatchTouchEvent()
與onInterceptTouchEvent()的區別,如果事件傳遞到了這個View,則dispatchTouchEvent()
方法一定會調用,而onInterceptTouchEvent()
方法則在dispatchTouchEvent()
內部調用,表示是否攔截事件,所以當我們需要攔截的時候一般改寫onInterceptTouchEvent()
在事件處理的時候,則是從分發到了最底層的View開始向上處理,在onTouchEvent(),返回了true,則表示這個View已經處理了 ,不必在向上傳遞,但我們覆寫這些方法的時候,不作處理,默認返回時false
,所以繼續向上傳遞,到了最上層的ViewGroup中。這就是我們驗證的結果。
這種結果我們可以用現實中的例子來解釋,這個例子是網上看到了,很生動形象。我們把整個事件傳遞的View看成是一個公司,ViewGroupA是一個總經理,ViewGroupB是一個部長,ViewTest是一個員工。現在來了一個任務,總經理覺得不需要自己處理,返回了false
,就傳遞給了下一層部長,部長看了也覺得不需要處理,繼續返回false
,傳遞給了底層員工,員工做了做,發現太難了,處理不了,就返回了false
,然後上層部長接手,發現確實很難,也處理不了,繼續返回false
,傳遞給了總經理。整個事件分發處理就結束了。
現在,又來了一個新任務,我們總經理有了前車之鑒,決定自己處理這件事,不在交給下面的人做,因為給了他們,他們也處理不好,所以他決定自己攔截這個事件,於是在ViewGroupA中的onInterceptTouchEvent()
中返回true,查看效果
08-30 08:53:31.125 23810-23810/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA dispatchTouchEvent0
08-30 08:53:31.126 23810-23810/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA onInterceptTouchEvent0
08-30 08:53:31.126 23810-23810/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA onTouchEvent0
確實如我們之前所說的,是這樣一個處理流程,ViewGroupA自己弄完了所有事情,隨著事件的變多,總經理終於累倒了,於是他決定把事情分給部長,自己只處理部長處理不了的,於是我們總經理不攔截事件,而是部長攔截事件,我們看看效果
08-30 08:59:27.702 23810-23810/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA dispatchTouchEvent0
08-30 08:59:27.702 23810-23810/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA onInterceptTouchEvent0
08-30 08:59:27.702 23810-23810/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB dispatchTouchEvent0
08-30 08:59:27.702 23810-23810/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB onInterceptTouchEvent0
08-30 08:59:27.702 23810-23810/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB onTouchEvent0
08-30 08:59:27.702 23810-23810/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA onTouchEvent0
確實,事件分發到了部長,而部長也如願的沒處理好事件,傳遞給了總經理,如此以往,因為處理如此多的事件,總經理的病再也沒好。於是,部長,和底層員工決定好好提高自身水平,不在把事件傳遞給總經理,於是,我們在底層員工ViewTest的onTouchEvent()
返回true,表示他已經處理好事情,我們看看效果
08-30 09:10:27.550 23810-23810/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA dispatchTouchEvent0
08-30 09:10:27.550 23810-23810/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA onInterceptTouchEvent0
08-30 09:10:27.550 23810-23810/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB dispatchTouchEvent0
08-30 09:10:27.550 23810-23810/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB onInterceptTouchEvent0
08-30 09:10:27.550 23810-23810/com.byhieg.viewdipatch E/ViewTest: View dispatchTouchEvent0
08-30 09:10:27.550 23810-23810/com.byhieg.viewdipatch E/ViewTest: View onTouchEvent0
這是一部分的Log,後面還有,不過我們先講解上半部分,在這裡,我們看到事件處理到ViewTest就為止了,所以事件處理沒有傳遞到ViewGroupB,當然有些問題,確實底層員工處理不了,於是,我們在ViewGroupB的onTouchEvent()
返回true。
08-30 09:15:28.998 23810-23810/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA dispatchTouchEvent0
08-30 09:15:28.998 23810-23810/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA onInterceptTouchEvent0
08-30 09:15:28.998 23810-23810/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB dispatchTouchEvent0
08-30 09:15:28.998 23810-23810/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB onInterceptTouchEvent0
08-30 09:15:28.998 23810-23810/com.byhieg.viewdipatch E/ViewTest: View dispatchTouchEvent0
08-30 09:15:28.998 23810-23810/com.byhieg.viewdipatch E/ViewTest: View onTouchEvent0
這樣,事件確實沒有在傳遞給總經理。底層員工,部長已經足夠勝任工作,總經理因為沒有雜事壓身,身體也逐漸康復,這家公司正常運轉。Happy Ending
在view進行事件處理的時候,如果他設置了onTouchListener
,那麼onTouchListener
中的onTouch()
方法將被調用,這時,我們需要根據onTouch()
的返回值來決定onTouchEvent
會不會調用,如果返回的是false
,則表示會繼續調用onTouchEvent
,返回的是true
,則不會。下面我們驗證一下:返回為true
的時候。
在MainActivity中,設置viewTest的事件,代碼如下:
ViewTest view = (ViewTest)findViewById(R.id.viewTest);
view.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
Log.e("onTouch","我中彈了");
return true;
}
});
08-30 10:40:38.209 32359-32359/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA dispatchTouchEvent0
08-30 10:40:38.209 32359-32359/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA onInterceptTouchEvent0
08-30 10:40:38.209 32359-32359/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB dispatchTouchEvent0
08-30 10:40:38.209 32359-32359/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB onInterceptTouchEvent0
08-30 10:40:38.209 32359-32359/com.byhieg.viewdipatch E/ViewTest: View dispatchTouchEvent0
08-30 10:40:38.209 32359-32359/com.byhieg.viewdipatch E/onTouch: 我中彈了
可以看出沒有打印出viewTest的onTouchEvent
日志,證明確實沒有調用,現在設置返回為false
, onTouchEvent
被調用的日志會出現,受限於篇幅,結果就不放出來了。
接下來,我們在ViewTest的onTouchEvent
中設置一個點擊監聽,查看一些好玩的東西。首先,我先設置我們的ViewTest為可點擊,viewTest的onTouch返回值也是false,然後我們看一下onTouchEvent代碼:
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e("ViewTest","View onTouchEvent" + event.getAction());
Log.e("More","我被調用");
setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
Log.e("ViewTest","原來中彈的不是我");
}
});
return false;
}
這時點擊我們的viewTest,我們發現他竟然沒有點擊事件的日志。這是因為我們直接返回的false,截斷了後面的點擊事件的觸發,所以決定什麼都不做,采用默認的實現,即
return super.onTouchEvent(event);
神奇的事情發生了,出現了該有的點擊效果。我們查看Log
08-30 13:07:04.834 16083-16083/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA dispatchTouchEvent0
08-30 13:07:04.834 16083-16083/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA onInterceptTouchEvent0
08-30 13:07:04.834 16083-16083/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB dispatchTouchEvent0
08-30 13:07:04.834 16083-16083/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB onInterceptTouchEvent0
08-30 13:07:04.834 16083-16083/com.byhieg.viewdipatch E/ViewTest: View dispatchTouchEvent0
08-30 13:07:04.834 16083-16083/com.byhieg.viewdipatch E/onTouch: 我中彈了
08-30 13:07:04.834 16083-16083/com.byhieg.viewdipatch E/ViewTest: View onTouchEvent0
08-30 13:07:04.834 16083-16083/com.byhieg.viewdipatch E/More: 我被調用
08-30 13:07:04.931 16083-16083/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA dispatchTouchEvent1
08-30 13:07:04.931 16083-16083/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA onInterceptTouchEvent1
08-30 13:07:04.931 16083-16083/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB dispatchTouchEvent1
08-30 13:07:04.931 16083-16083/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB onInterceptTouchEvent1
08-30 13:07:04.931 16083-16083/com.byhieg.viewdipatch E/ViewTest: View dispatchTouchEvent1
08-30 13:07:04.931 16083-16083/com.byhieg.viewdipatch E/onTouch: 我中彈了
08-30 13:07:04.931 16083-16083/com.byhieg.viewdipatch E/ViewTest: View onTouchEvent1
08-30 13:07:04.931 16083-16083/com.byhieg.viewdipatch E/More: 我被調用
08-30 13:07:04.932 16083-16083/com.byhieg.viewdipatch E/ViewTest: 原來中彈的不是我
這是完整的調用日志,我們可以仔細看一下,會發現出現點擊事件的日志在最後一行,而之前重���出現了ViewGroupA和ViewGroupB的事件分發。是時候揭露出完整的觸發過程了。
當我們手指按下時候,會觸發ACTION_DOWN的事件,這時會進行一系列的事件分發,分發規則如上面所講,當我們手指松開的時候,會觸發ACTION_UP的事件,這同樣會進行一系列的事件分發,最後當這些都弄完之後,會觸發
onClick()
事件,也就是說onClick()
是事件觸發的尾端。一旦前面決定不繼續傳遞事件,則onClick()
事件就不會觸發。
所以這裡面有一個概念叫做同一事件序列:指從手指接觸屏幕的那一刻起,到手指離開屏幕的那一刻結束,在這個過程中所產生的一系列事件,這個事件序列以down事件開始,中間含有數個move事件,最終以up事件結束。
簡而言之就是down-> move-> move-> move-> move......->up
而上面我們提到的onclick()
如果大家看源碼的話,會發現他是ACTION_UP
之後通過performClick()
調用的。所以如果view設置了點擊事件並且能向後傳遞,則會最後調用onClick()。
如果仔細看上面的日志,會發現我們的打印日志的代碼是這樣寫的Log.e("ViewTest","View onTouchEvent" + event.getAction());
日志最後會出現事件的名字,在日志裡面,由於getAction
返回的int型的靜態常量,所以是用0, 1表示,0代表ACTION_DOWN
,1代表ACTION_UP
,所以通過這個日志,我們可以驗證這個同一事件序列這個過程。前面我放出的日志實際也是不全 我也只是把DOWN的會就放出來,日志後面還有UP的事件,處於篇幅就不全放了而已。
而針對同一事件序列 上面的例子還會說明一個特性
注意,一開始,我們直接將
onTouchEvent(MotionEvent event)
返回值設置為false,這樣他會截斷後面ACTION_UP事件的觸發,通俗來講,就是傳遞到VIew的DOWN事件,他都沒有消耗他,沒處理好他,返回了false,讓他上層處理,則同一事件序列的MOVE,UP也不會給他。結合我們之前公司的例子,就是這個DOWN事件都辦不好,後面也不會給他其他任務了。
我們驗證一下:
08-31 07:18:40.791 16083-16083/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA dispatchTouchEvent0
08-31 07:18:40.791 16083-16083/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA onInterceptTouchEvent0
08-31 07:18:40.791 16083-16083/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB dispatchTouchEvent0
08-31 07:18:40.791 16083-16083/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB onInterceptTouchEvent0
08-31 07:18:40.791 16083-16083/com.byhieg.viewdipatch E/ViewTest: View dispatchTouchEvent0
08-31 07:18:40.791 16083-16083/com.byhieg.viewdipatch E/onTouch: 我中彈了
08-31 07:18:40.791 16083-16083/com.byhieg.viewdipatch E/ViewTest: View onTouchEvent0
08-31 07:18:40.791 16083-16083/com.byhieg.viewdipatch E/More: 我被調用
08-31 07:18:40.791 16083-16083/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB onTouchEvent0
08-31 07:18:40.791 16083-16083/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA onTouchEvent0
這是全部的日志,確實沒有UP事件。
下面的規則是出自《Android開發藝術探索》,我們對這些規則進行講解,驗證
onInterceptTouchEvent
不會被調用onTouchEvent
會被調用onTouchEvent
並不會被調用,並且當前View可以持續受到後續的事件,最終這些消失的點擊事件會傳遞給Activity處理onInterceptTouchEvent
方法,一旦有點擊事件傳遞給他,那麼他的onTouchEvent方法會被調用onTouchEvent
默認會消耗事件,除非他是不可點擊的。onTouchEvent
的默認返回值。哪怕一個View是disable
狀態,只要他的clickable或者longClickable有一個true,那麼他的onTouchEvent
返回true。onClick
會發生的前提是當前View是可點擊的,並且他收到了down和up事件。我們分別講解和驗證一下大神總結這些結論。
第一條,之前已經講過了,就不說了
第二條,一旦View決定攔截,則表明這個View要自己上手處理這個事件,那他勢必不會再將事件傳遞下去,自然也不會調用onInterceptTouchEvent
第三條,一般,一個事件序列:down-> move-> move-> move-> move......->up
只會交給一個View處理,因為第二條,一旦決定攔截,則表明他要自己上手處理這個。特殊情況會通過onTouchEvent強行傳遞給其他View,這也是書中原話,但至今不清楚怎麼強行傳遞給其他View。
第四條,這句話的意思就是如果DOWN事件返回的是false,則UP事件和MOVE事件也不會用來處理。而且會把事件交給父元素去處理,剛才的日志,已經驗證了這點。
第五條,如果DOWN事件返回的是true,而MOVE事件返回的false,則這個事件會消失,父元素的onTouchEvent
並不會調用,我們修改下我們的代碼驗證下:
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.e("ViewTest","View onTouchEvent" + event.getAction());
return true;
case MotionEvent.ACTION_MOVE:
Log.e("ViewTest","View onTouchEvent" + event.getAction());
return false;
case MotionEvent.ACTION_UP:
Log.e("ViewTest","View onTouchEvent" + event.getAction());
return true;
}
return super.onTouchEvent(event);
}
我們將代碼改成這個樣子,然後運行看日志:
08-31 08:09:13.076 16083-16083/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA dispatchTouchEvent0
08-31 08:09:13.076 16083-16083/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA onInterceptTouchEvent0
08-31 08:09:13.076 16083-16083/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB dispatchTouchEvent0
08-31 08:09:13.076 16083-16083/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB onInterceptTouchEvent0
08-31 08:09:13.076 16083-16083/com.byhieg.viewdipatch E/ViewTest: View dispatchTouchEvent0
08-31 08:09:13.077 16083-16083/com.byhieg.viewdipatch E/ViewTest: View onTouchEvent0
08-31 08:09:13.148 16083-16083/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA dispatchTouchEvent2
08-31 08:09:13.148 16083-16083/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA onInterceptTouchEvent2
08-31 08:09:13.148 16083-16083/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB dispatchTouchEvent2
08-31 08:09:13.148 16083-16083/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB onInterceptTouchEvent2
08-31 08:09:13.149 16083-16083/com.byhieg.viewdipatch E/ViewTest: View dispatchTouchEvent2
08-31 08:09:13.149 16083-16083/com.byhieg.viewdipatch E/ViewTest: View onTouchEvent2
08-31 08:09:13.166 16083-16083/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA dispatchTouchEvent2
08-31 08:09:13.166 16083-16083/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA onInterceptTouchEvent2
08-31 08:09:13.166 16083-16083/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB dispatchTouchEvent2
08-31 08:09:13.166 16083-16083/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB onInterceptTouchEvent2
08-31 08:09:13.166 16083-16083/com.byhieg.viewdipatch E/ViewTest: View dispatchTouchEvent2
08-31 08:09:13.166 16083-16083/com.byhieg.viewdipatch E/ViewTest: View onTouchEvent2
08-31 08:09:13.180 16083-16083/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA dispatchTouchEvent2
08-31 08:09:13.181 16083-16083/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA onInterceptTouchEvent2
08-31 08:09:13.181 16083-16083/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB dispatchTouchEvent2
08-31 08:09:13.181 16083-16083/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB onInterceptTouchEvent2
08-31 08:09:13.181 16083-16083/com.byhieg.viewdipatch E/ViewTest: View dispatchTouchEvent2
08-31 08:09:13.181 16083-16083/com.byhieg.viewdipatch E/ViewTest: View onTouchEvent2
08-31 08:09:13.181 16083-16083/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA dispatchTouchEvent1
08-31 08:09:13.181 16083-16083/com.byhieg.viewdipatch E/ViewGroupA: ViewGroupA onInterceptTouchEvent1
08-31 08:09:13.181 16083-16083/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB dispatchTouchEvent1
08-31 08:09:13.181 16083-16083/com.byhieg.viewdipatch E/ViewGroupB: ViewGroupB onInterceptTouchEvent1
08-31 08:09:13.181 16083-16083/com.byhieg.viewdipatch E/ViewTest: View dispatchTouchEvent1
08-31 08:09:13.181 16083-16083/com.byhieg.viewdipatch E/ViewTest: View onTouchEvent1
日志很長,因為不小心移動多了,觸發了多個移動事件,我們會發現,滿足了上述的條件,然後我們的父View確實沒有觸發onTouchEvent
。斯國一,確實是這個樣子的
第六條和第七條,上文提到過,這個不說了 ,因為ViewGroup的這個方法默認返回是false,默認不攔截。而View沒有攔截方法,所以必須要處理。
第八條和第九條,處理事件的時候,view的onTouchEvent
默認返回true,表示自己消耗這件事情,這個和我們上面說的默認返回時false是沖突的,這個是因為他後面補了一個條件,這它默認是不點擊的,在我們上面的例子中,我們用的自定義的ViewTest,這個在沒有引入討論onClick的時候,他就是默認不可點擊的,所以是false,當他可點擊之後,我們在xml中設立了clickable屬性,他的onTouchEvent
則表示會消耗事件,返回是true,也可以理解嘛,設立了他可點擊,就是想讓他處理onclick
事件。所以,一旦有一個Clickable或者longClickable為true,那麼他的onTouchEvent就返回true。
第十條,onClick觸發的前提是view點擊,並且收到了down和up事件,這個上面也驗證過了。
這十條說完,發現很多東西,我們上文已經提到過,只是歸納的沒有他好。他這十條,總體說來,就是
默認的事件傳遞會到底層的view,根據view的onTouchEvent
方法中對DOWN事件的處理不同,會有不同的結果,如果返回了true,表示處理好了,則後續的MOVE,UP事件還會交給他來做,如果此外還設置了Clickable,則表示這個view也會響應onClick,而如果MOVE和UP返回了false,則會調用父View的onTouchEvent,如果返回了false,表示,這個view不堪重任,後續的MOVE和UP也不會給他做。
如果我們什麼都不做,不去主動的更改onTouchEvent的返回結果,則會因為這個View是否能被點擊來決定他的事件處理是否向上傳遞,如果能點擊,則不傳遞,他自己來處理,如果不能點擊,則返回false,向上傳遞。
至此,View事件的分發就到此結束了。後續可能會有根據源碼去分析View分發的過程,這個就看事件啦。