簡書上有一篇寫的蠻不錯的...Android View的事件分發(fā)及攔截機制分析,不過我們也自己打印下看看流程吧! 然后琢磨下哪些場景需要處理這個事件分發(fā),需要解決這個事件沖突。小白印象中,ViewPaper與橫向滑動的RecycleView/ListView事件沖突的問題比較經(jīng)??吹剑。ㄟ@個小白會看sdk文檔里面的一些方法介紹).
小白也打印下看看妮?
package me.heyclock.hl.customcopy;
import android.content.Context;
import android.support.constraint.ConstraintLayout;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
public class CustomConstraintLayout extends ConstraintLayout {
public CustomConstraintLayout(Context context) {
this(context, null);
}
public CustomConstraintLayout(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public CustomConstraintLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d("event", "CustomConstraintLayout dispatchTouchEvent");
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.d("event", "CustomConstraintLayout onInterceptTouchEvent" );
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("event", "CustomConstraintLayout onTouchEvent" );
return super.onTouchEvent(event);
}
}
package me.heyclock.hl.customcopy;
import android.annotation.SuppressLint;
import android.content.Context;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.TextView;
@SuppressLint("AppCompatCustomView")
public class CustomTextView extends TextView {
public CustomTextView(Context context) {
this(context, null);
}
public CustomTextView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0, 0);
}
public CustomTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr,0);
}
public CustomTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d("event", "CustomTextView dispatchTouchEvent");
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("event", "CustomTextView onTouchEvent" );
return super.onTouchEvent(event);
}
}
setContentView(R.layout.custom_viewgroup_event);
<?xml version="1.0" encoding="utf-8"?>
<me.heyclock.hl.customcopy.CustomConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffff00ee">
<me.heyclock.hl.customcopy.CustomTextView
android:layout_width="200dp"
android:layout_height="200dp"
android:background="#ffaa00ee"
android:text="大大的中間"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<me.heyclock.hl.customcopy.CustomTextView
android:layout_width="100dp"
android:layout_height="100dp"
android:background="#ffddaaee"
android:text="小小的中間"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</me.heyclock.hl.customcopy.CustomConstraintLayout>
當(dāng)點擊大大的中間以及小小的中間,結(jié)果都是類似:
10-30 03:47:28.676 8046-8046/me.heyclock.hl.customcopy D/event: CustomConstraintLayout dispatchTouchEvent
CustomConstraintLayout onInterceptTouchEvent
CustomTextView dispatchTouchEvent
CustomTextView onTouchEvent
CustomConstraintLayout onTouchEvent
10-30 03:47:43.932 8046-8046/me.heyclock.hl.customcopy D/event: CustomConstraintLayout dispatchTouchEvent
CustomConstraintLayout onInterceptTouchEvent
CustomTextView dispatchTouchEvent
CustomTextView onTouchEvent
CustomTextView dispatchTouchEvent
CustomTextView onTouchEvent
CustomConstraintLayout onTouchEvent
如碼友分析的一樣。事件的傳遞順序是上冊控件依次傳遞到下層控件,直到View. 而事件的處理順序,則由底層的View依次返給上級,直到最頂層進行處理。
事件傳遞的返回值:true,攔截,不繼續(xù);false,不攔截,繼續(xù)流程。
事件處理的返回值:true,處理了,不用審核了;false,給上級處理。
初始情況下,返回值都是false。
為true就表示處理了,不用在往上傳遞了。So,我們就可以選擇某個傳遞的環(huán)節(jié)消費掉該事件,或者攔截掉事件傳遞,讓其不往下層傳遞;比如在CustomConstraintLayout 的onInterceptTouchEvent中返回true進行攔截,這樣中間的空間就不會響應(yīng)了。
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.d("event", "CustomConstraintLayout onInterceptTouchEvent" );
//return super.onInterceptTouchEvent(ev);
return true;
}
10-30 04:04:30.397 8337-8337/me.heyclock.hl.customcopy D/event: CustomConstraintLayout dispatchTouchEvent
CustomConstraintLayout onInterceptTouchEvent
CustomConstraintLayout onTouchEvent
10-30 04:04:32.916 8337-8337/me.heyclock.hl.customcopy D/event: CustomConstraintLayout dispatchTouchEvent
CustomConstraintLayout onInterceptTouchEvent
CustomConstraintLayout onTouchEvent
再比如僅僅把CustomTextView的
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("event", "CustomTextView onTouchEvent" );
//return super.onTouchEvent(event);
return true;
}
事件傳遞繼續(xù)到底,onTouch不再往上到頂
10-30 04:11:57.038 8840-8840/me.heyclock.hl.customcopy D/event: CustomConstraintLayout dispatchTouchEvent
CustomConstraintLayout onInterceptTouchEvent
CustomTextView dispatchTouchEvent
CustomTextView onTouchEvent
10-30 04:11:57.132 8840-8840/me.heyclock.hl.customcopy D/event: CustomConstraintLayout dispatchTouchEvent
CustomConstraintLayout onInterceptTouchEvent
CustomTextView dispatchTouchEvent
CustomTextView onTouchEvent
目前小白也只是知道這樣的一個效果?,F(xiàn)在有兩個問題要考慮下:
1. 運行兩遍?
2. 有什么場景我們可以考慮實踐一下?
第一個問題,因為down和up都會走.....所以會進行兩次事件傳遞。也就是down先傳遞到底層,然后up再次傳遞到底層(如果你進行了滑動,還會有move事件傳遞...)。到底層后我們在View里面return true - 消費掉了這個事件,所以上層不再收到相關(guān)事件處理。
第二個問題,有什么場景需要做特殊處理呢?
比如我們要實現(xiàn)這樣的處理(子View可以進行左右滑動, 同時, 當(dāng)在子View上面進行上下滑動時,依然是上層ViewGroup進行上下滑動.)
思路一下:
1. 子View控件onTouchEvent返回true,保證響應(yīng)處理相關(guān)touch事件(down,move,up)
2. 父ViewGroup在onInterceptTouchEvent中進行處理和攔截move事件(由于左右滑動時,子View需要做響應(yīng),所以只能攔截上下滑動的情況 - 這種情況返回true,表示父ViewGroup自己處理move事件,不再交給子View)
So,我們簡單定義一下ViewGroup和View
package me.heyclock.hl.customcopy;
import android.annotation.SuppressLint;
import android.content.Context;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.TextView;
@SuppressLint("AppCompatCustomView")
public class CustomTextView extends TextView {
public CustomTextView(Context context) {
this(context, null);
}
public CustomTextView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0, 0);
}
public CustomTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public CustomTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d("event", "CustomTextView dispatchTouchEvent" + ev.getAction());
return super.dispatchTouchEvent(ev);
}
private float x1, x2;
private float y1, y2;
private float swing = 0;
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("event", "CustomTextView onTouchEvent" + event.getAction());
//return super.onTouchEvent(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
x1 = event.getX();
break;
case MotionEvent.ACTION_MOVE: ///< 上下滑動已經(jīng)被上層攔截了,所以這里肯定就是左右滑動了
x2 = event.getX();
///< 5作為閥值就可以了,可以根據(jù)效果調(diào)整
if(y1 - y2 > 5) { ///< 上
} else if(y2 - y1 > 5) { ///< 下
} else if(x1 - x2 > 5) { ///< 左
swing = x1 - x2;
///< 采用系統(tǒng)View類的滾動方法
scrollBy((int) swing, 0);
invalidate();
} else if(x2 - x1 > 5) { ///< 右
swing = -(x2 - x1);
///< 采用系統(tǒng)View類的滾動方法
scrollBy((int) swing, 0);
invalidate();
}
x1 = x2;
y1 = y2;
break;
case MotionEvent.ACTION_UP:
break;
}
return true;
}
}
package me.heyclock.hl.customcopy;
import android.content.Context;
import android.support.constraint.ConstraintLayout;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
public class CustomConstraintLayout extends ConstraintLayout {
public CustomConstraintLayout(Context context) {
this(context, null);
}
public CustomConstraintLayout(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public CustomConstraintLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d("event", "CustomConstraintLayout dispatchTouchEvent"+ ev.getAction());
return super.dispatchTouchEvent(ev);
}
private float x1, x2;
private float y1, y2;
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.d("event", "CustomConstraintLayout onInterceptTouchEvent" + ev.getAction());
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
x1 = ev.getX();
y1 = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
x2 = ev.getX();
y2 = ev.getY();
///< 5作為閥值就可以了,可以根據(jù)效果調(diào)整
if(y1 - y2 > 15) { ///< 上
return true;
} else if(y2 - y1 > 15) { ///< 下
return true;
} else if(x1 - x2 > 15) { ///< 左
} else if(x2 - x1 > 15) { ///< 右
}
x1 = x2;
y1 = y2;
break;
case MotionEvent.ACTION_UP:
break;
}
return super.onInterceptTouchEvent(ev);
}
/**
* 滾動相關(guān)(上下滑動)
*/
private float wx2;
private float wy2;
private float swing = 0;
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("event", "CustomConstraintLayout onTouchEvent" + event.getAction());
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
wx2 = event.getX();
wy2 = event.getY();
///< 5作為閥值就可以了,可以根據(jù)效果調(diào)整
if(y1 - wy2 > 5) { ///< 上
swing = y1 - wy2;
///< 采用系統(tǒng)View類的滾動方法
scrollBy(0, (int) swing);
invalidate();
Log.e("test", "上滑動");
} else if(wy2 - y1 > 5) { ///< 下
swing = -(wy2 - y1);
///< 采用系統(tǒng)View類的滾動方法
scrollBy(0, (int) swing);
invalidate();
Log.e("test", "下滑動");
} else if(x1 - wx2 > 50) { ///< 左
} else if(wx2 - x1 > 50) { ///< 右
}
x1 = wx2;
y1 = wy2;
break;
case MotionEvent.ACTION_UP:
break;
}
return super.onTouchEvent(event);
}
}
布局 custom_viewgroup_event.xml
<?xml version="1.0" encoding="utf-8"?>
<me.heyclock.hl.customcopy.CustomConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffff00ee">
<me.heyclock.hl.customcopy.CustomTextView
android:layout_width="200dp"
android:layout_height="200dp"
android:background="#ffaa00ee"
android:text="大大的中間"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</me.heyclock.hl.customcopy.CustomConstraintLayout>
效果...

還有問題,就是上下滑動子View的外部還不行?
這個也很簡單了,有時候一直盯著子View,所以忘記了onTouch事件處理順序了!只需要把父ViewGroup的onTouchEvent返回true不就可以接收后續(xù)的move,up事件了么....
/**
* 滾動相關(guān)(上下滑動)
*/
private float wx2;
private float wy2;
private float swing = 0;
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("event", "CustomConstraintLayout onTouchEvent" + event.getAction());
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
x1 = event.getX();
y1 = event.getY();
break;
case MotionEvent.ACTION_MOVE:
wx2 = event.getX();
wy2 = event.getY();
///< 5作為閥值就可以了,可以根據(jù)效果調(diào)整
if(y1 - wy2 > 5) { ///< 上
swing = y1 - wy2;
///< 采用系統(tǒng)View類的滾動方法
scrollBy(0, (int) swing);
invalidate();
Log.e("test", "上滑動");
} else if(wy2 - y1 > 5) { ///< 下
swing = -(wy2 - y1);
///< 采用系統(tǒng)View類的滾動方法
scrollBy(0, (int) swing);
invalidate();
Log.e("test", "下滑動");
} else if(x1 - wx2 > 50) { ///< 左
} else if(wx2 - x1 > 50) { ///< 右
}
x1 = wx2;
y1 = wy2;
break;
case MotionEvent.ACTION_UP:
break;
}
return true;//super.onTouchEvent(event);
}
這樣我們就大體實現(xiàn)了這個目標(biāo)...

這個的理解我們先這樣,回去再清醒清醒。下一篇打算去看sdk的官方文檔ViewGroup | Android Developers 以及一些個常用的方法,比如類似這種getParent().requestDisallowInterceptTouchEvent(false);
下班之前妮看兩篇文章吧...
事件分發(fā):onTouchEvent返回false一定不執(zhí)行ACTION_MOVE嗎?
(轉(zhuǎn))淺談onInterceptTouchEvent、onTouchEvent與onTouch - oZuiJiaoWeiYang的專欄 - CSDN博客