最近實際應用中遇到了滑動衝突的相關問題,在解決過程中,有些需要注意的問題,特別記錄一下。
在解決具體問題之前,先介紹下實際應用場景及問題狀況。
從圖中可以看出,一個ScrollView內部嵌套三個RecyclerView,其中兩個RecyclerView是橫向,一個RecyclerView是縱向。
在這個場景下,出現了滑動衝突問題,主要表現為橫向RecyclerView滑動不靈敏,縱向RecyclerView滑動卡頓。
針對該問題,解決的方案是根據當前滑動方向,水平還是垂直來判斷這個事件到底該交給誰來處理。
一般情況下根據滑動路徑形成的夾角(或者說是斜率如下圖)、水平和豎直方向滑動速度差來判斷。
該問題所產生的滑動衝突如上圖所示。
針對該問題,一般情況下必需通過業務邏輯來進行判斷,決定到底誰來處理該事件。
針對滑動衝突,一般有兩個解決方法。
事件都先經過父容器的攔截處理,如果不需要此事件就不攔截,這樣就可以解決滑動衝突的問題。外部攔截法需要重寫父容器的onInterceptTouchEvent()方法,在內部完成相應的攔截即可
@Override public boolean onInterceptTouchEvent(MotionEvent ev) { boolean intercepted = false; int x = (int) ev.getX(); int y = (int) ev.getY();
switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: intercepted = false; break; case MotionEvent.ACTION_MOVE: { if (父容器需要事件) { intercepted = true; } else { intercepted = false; } break; } case MotionEvent.ACTION_UP: { intercepted = false; break; } } return intercepted; }
父容器不攔截任何事件,所有的事件都傳遞給子元素,如果子元素需要此事件就直接消耗掉,否則就交由父容器進行處理。這種方法需要配合requestDisallowInterceptTouchEvent()方法才能正常工作。
主要是修改子view的dispatchTouchEvent()方法
@Override public boolean dispatchTouchEvent(MotionEvent ev) { int x = (int) ev.getX(); int y = (int) ev.getY(); switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: { getParent().requestDisallowInterceptTouchEvent(true); break; } case MotionEvent.ACTION_MOVE: { if (父容器需要此類事件) { getParent().requestDisallowInterceptTouchEvent(false); } break; } case MotionEvent.ACTION_UP: { break; } } return super.dispatchTouchEvent(ev); }
父容器需要重寫onInterceptTouchEvent()方法
@Override public boolean onInterceptTouchEvent(MotionEvent ev) { int action = ev.getAction(); if(action == MotionEvent.ACTION_DOWN){ return false; }else { return true; } }
父容器攔截ACTION_DOWN以外的其他事件,因為ACTION_DOWN 事件不受 FLAG_DISALLOW_INTERCEPT這個標記的控制,所以一旦父容器攔截了ACTION_DOWN 事件那麼所有的事件都無法傳到子view中去了,這樣內部攔截法就不起作用了。
下面就來實際解決本文中遇到的滑動衝突問題。通過上述分析可知,本文所遇到的問題通過外部攔截法,重寫ScrollView的onInterceptTouchEvent()方法即可快速簡單的解決。
public class FScrollView extends ScrollView { private float mLastXIntercept = 0f; private float mLastYIntercept = 0f;
public FScrollView(Context context, AttributeSet attrs) { super(context, attrs); }
@Override public boolean onInterceptTouchEvent(MotionEvent ev) { boolean intercepted = false; float x = ev.getX(); float y = ev.getY(); int action = ev.getAction() & MotionEvent.ACTION_MASK; switch (action) { case MotionEvent.ACTION_DOWN: { intercepted = false; //初始化mActivePointerId super.onInterceptTouchEvent(ev); break; } case MotionEvent.ACTION_MOVE: { //橫坐標位移增量 float deltaX = x - mLastXIntercept; //縱坐標位移增量 float deltaY = y - mLastYIntercept; if (Math.abs(deltaX) < Math.abs(deltaY)) { intercepted = true; } else { intercepted = false; } break; } case MotionEvent.ACTION_UP: { intercepted = false; break; } } mLastXIntercept = x; mLastYIntercept = y; return intercepted; } }
沒錯,年初我花了一個多月的時間整理出來的學習資料,希望能幫助那些想進階提升Android開發,卻又不知道怎麼進階學習的朋友。【包括高級UI、性能優化、架構師課程、NDK、Kotlin、混合式開發(ReactNative+Weex)、Flutter等架構技術資料】,希望能幫助到您面試前的複習且找到一個好的工作,也節省大家在網上搜索資料的時間來學習。
喜歡我的文章可以點贊+關注我的【個人主頁】獲取免費資料,後續我將繼續分享更多Android技術乾貨,感謝支持!