在Android項(xiàng)目開發(fā)中,為了實(shí)現(xiàn)需求和兼并用戶體驗(yàn)邑飒,相信很多人都碰到滑動事件沖突的問題徙缴。在Android系統(tǒng)中事件分發(fā)機(jī)制是一個很重要的組成部分,由于這事件分發(fā)機(jī)制不是本文重點(diǎn),故不在此多述孕蝉,如果有想詳細(xì)了解的可以自己搜下屡律,網(wǎng)上有很多相關(guān)資料詳細(xì)描述了Android事件分發(fā)機(jī)制。
一降淮、問題場景
由于RecyclerView自身的優(yōu)點(diǎn)超埋,使得它已經(jīng)基本取代了GridView、ListView佳鳖,而且ViewPager2也是基于RecyclerView實(shí)現(xiàn)的霍殴,所以現(xiàn)在涉及到列表的基本都離不開RecyclerView。
本文就就基于項(xiàng)目中采用RecyclerView + ViewPager + Fragment + RecyclerView這種嵌套方式出現(xiàn)了滑動沖突系吩。
QQ截圖20200516115700.png
二来庭、三種解決方式
首先講下當(dāng)下的幾種處理方式:
- 在父RecyclerView中的事件攔截事件中處理;
自定義父recyclerView并重寫onInterceptTouchEvent()方法穿挨,代碼如下:public class ParentRecyclerView extends RecyclerView { public ParentRecyclerView(@NonNull Context context) { this(context,null); } public ParentRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) { this(context, attrs,0); } public ParentRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } //不攔截月弛,繼續(xù)分發(fā)下去 @Override public boolean onInterceptTouchEvent(MotionEvent e) { //當(dāng)然這里可能要根據(jù)實(shí)際場景去處理下,不僅僅是返回false就結(jié)束了科盛。 //todo : 實(shí)際場景處理代碼 //--------------------------------------------------------------------------------- return false; } }
- 在子RecyclerView中的事件攔截事件中處理帽衙;
通過requestDisallowInterceptTouchEvent方法干預(yù)事件分發(fā)過程,該方法就是通知父布局要不要攔截事件
自定義子RecyclerView并重寫dispatchTouchEvent贞绵,如下:public class ChildRecyclerView extends RecyclerView { public ChildRecyclerView (@NonNull Context context) { this(context,null); } public ChildRecyclerView (@NonNull Context context, @Nullable AttributeSet attrs) { this(context, attrs,0); } public ChildRecyclerView (@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { //父層ViewGroup不要攔截點(diǎn)擊事件,true不要攔截厉萝,false攔截 getParent().requestDisallowInterceptTouchEvent(true); return super.dispatchTouchEvent(ev); } }
- 采用優(yōu)先級最高的OnTouchListener;
從事件分發(fā)機(jī)制上看,OnTouchListener優(yōu)先級很高榨崩,可以通過這個來告訴父布局谴垫,不要攔截我的事件recyclerView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent motionEvent) { switch (motionEvent.getAction()){ case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_MOVE: //這里有時要根據(jù)自己的場景去寫自己的邏輯 view.getParent().requestDisallowInterceptTouchEvent(true); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: view.getParent().requestDisallowInterceptTouchEvent(false); break; } return true; } });
以上三種方式至于采用哪種要根據(jù)自己的實(shí)際場景。
三母蛛、針對一種方式進(jìn)行詳解
下面就針對第二種方式在自定義子RecyclerView的做事件攔截處理弹渔,因?yàn)檫@種方式正好適合項(xiàng)目解決沖突。
目標(biāo) :觸摸子RecyclerView上下滑動時溯祸,子列表滑動肢专,當(dāng)列表滑動到頂部舞肆、底部或觸摸點(diǎn)超出子RecyclerView上下邊距時繼續(xù)滑動,則父RecyclerView跟著滑動博杖。
- MotionEvent.ACTION_DOWN
按下時記錄按下的x,y值椿胯,并重置標(biāo)記為;float x = ev.getX(); float y = ev.getY(); switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: mDownX = x; mDownY = y; lastY = y; disallowInterceptState = 0; getParent().requestDisallowInterceptTouchEvent(true); break;
- MotionEvent.ACTION_MOVE
手指滑動時剃根,通過計算在x,y軸方向移動的距離哩盲,判斷哪個方向先移動超過給的距離來判斷移動的方向,若是x軸方向則不攔截(因?yàn)閂iewPager橫線滑動)次數(shù)將標(biāo)記位設(shè)置為2(disallowInterceptState = 2
)狈醉,若y方向則告訴父View不要攔截并將標(biāo)記位設(shè)置為1(disallowInterceptState = 1
)廉油;
繼續(xù)move時,不斷檢查是否到View的上下邊緣和列表是否滑動到頂部或底部苗傅,當(dāng)滿足條件時將標(biāo)記位設(shè)置為2,(disallowInterceptState = 2
)告訴父View可以攔截事件了抒线。if (disallowInterceptState == 0) { float absX = Math.abs(x - mDownX); float absY = Math.abs(y - mDownY); if ((absX > 5f || absY > 5f)) { if (absX < absY) { disallowInterceptState = 1; } else { disallowInterceptState = 2; } } } if (getParent() != null && disallowInterceptState != 0) { //y坐標(biāo)邊界檢測 boolean bl = y < 0 || y > getMeasuredHeight(); disallowInterceptState = bl ? 2 : disallowInterceptState; //若滑動到頂部 && 繼續(xù)下滑動,則釋放攔截事件 if((isScrollTop() && lastY < y) || (isScrollBottom() && lastY > y)){ disallowInterceptState = 2; } //檢查滑動到底部或頂部 getParent().requestDisallowInterceptTouchEvent(disallowInterceptState == 1); } lastY = y;
- MotionEvent.ACTION_UP和MotionEvent.ACTION_CANCEL
這兩個事件不需要做其他處理渣慕,恢復(fù)父view可以攔截事件//父層ViewGroup不要攔截點(diǎn)擊事件 getParent().requestDisallowInterceptTouchEvent(false);
完整代碼ChildRecyclerView.java
public class ChildRecyclerView extends RecyclerView {
public ChildRecyclerView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
private float mDownX, mDownY,lastY;
private int disallowInterceptState = 0;
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
float x = ev.getX();
float y = ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mDownX = x;
mDownY = y;
lastY = y;
disallowInterceptState = 0;
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
if (disallowInterceptState == 0) {
float absX = Math.abs(x - mDownX);
float absY = Math.abs(y - mDownY);
if ((absX > 5f || absY > 5f)) {
if (absX < absY) {
disallowInterceptState = 1;
} else {
disallowInterceptState = 2;
}
}
}
if (getParent() != null && disallowInterceptState != 0) {
//y坐標(biāo)邊界檢測
boolean bl = y < 0 || y > getMeasuredHeight();
disallowInterceptState = bl ? 2 : disallowInterceptState;
//若滑動到頂部 && 繼續(xù)下滑動嘶炭,則釋放攔截事件
if((isScrollTop() && lastY < y) || (isScrollBottom() && lastY > y)){
disallowInterceptState = 2;
}
//檢查滑動到底部或頂部
getParent().requestDisallowInterceptTouchEvent(disallowInterceptState == 1);
}
lastY = y;
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
//父層ViewGroup不要攔截點(diǎn)擊事件
getParent().requestDisallowInterceptTouchEvent(false);
break;
}
return super.dispatchTouchEvent(ev);
}
/**
* 滑動到底部檢查
* @return true滑動到底部,false沒有到底
*/
private boolean isScrollBottom(){
return !canScrollVertically(1);
}
/**
* 滑動到頂部檢查
* @return true滑動到頂部逊桦,false沒有到頂
*/
private boolean isScrollTop(){
return !canScrollVertically(-1);
}
}
最后附上效果圖
效果圖.gif
最后附上處理滑動沖突最根本的解決方法:
-
外部攔截法:
指點(diǎn)擊事件都先經(jīng)過父容器的攔截處理眨猎,如果父容器需要此事 件 就 攔 截 , 否 則 就 不 攔 截 强经。 具 體 方 法 : 需 要 重 寫 父 容 器 的onInterceptTouchEvent 方法睡陪,在內(nèi)部做出相應(yīng)的攔截。 -
內(nèi)部攔截法:
指父容器不攔截任何事件匿情,而將所有的事件都傳遞給子容器宝穗,如果子容器需要此事件就直接消耗,否則就交由父容器進(jìn)行處理码秉。具體方法:需要配合 requestDisallowInterceptTouchEvent 方法逮矛。
希望能幫助到大家。
每日一句:要想練就絕世武功 就要忍受常人難忍受的痛转砖。