Android:當滑動衝突遇上RecyclerView
滑動衝突分析很簡單,兩種方法,外部攔截法,內部攔截法。
但是碰上RecyclerView後,我有點懵了。
主要是邏輯的問題,而不是什麼攔截法的問題,當理順了邏輯後,這才感到大徹大悟。
一開始是實現RecyclerView的item的水平監聽。
這還不簡單,可是做著做著,水平雖然滑動的很順利,但是雖然點著滑鼠不鬆開,試著上下滑動了一下,發現,這串事件被RecyclerView奪走了!重新水平滑動,發現我的水平邏輯全部失效了!
又跑去重新看了看原始碼,還看了看RecyclerView的原始碼,這才意識到,他的onIntercept中,把我這串事件,強行奪走了。
理順了這個邏輯,才可以用得上內部攔截法了。
parent.requestDisallowInterceptTouchEvent(false);
這個方法,如果是true,就是不允許parent打斷。false就是允許打斷。
所以我用了一個boolean去判斷,但是發現boolean根本滿足不了需求。需求是,在這串事件的一開始,如果判定為水平,那麼希望parent.requestDisallowInterceptTouchEvent(false);一直是處於false狀態的。但是一個boolean明顯不行。具體原因自行測試。
後來採用了int來儲存。
move中
if (blockState == 0) { if (Math.abs(deltaX) < Math.abs(deltaY)) { blockState = 1; } else { blockState = 2; } }
這樣一個邏輯才算圓滿。這個邏輯是代表只能進行判定一次。進行判定後,會判定本次是豎直滑動,還是水平滑動。再次進入move時候,由於已經賦值,所以不會再進行判斷,彌補了boolean的缺陷。
這樣一來,終於滿足了需求:當本次判定為豎直的時候,水平的邏輯不可干預;本次判定為水平的時候,豎直的邏輯不可干預;全部程式碼如下
public class HorizontalSlideView extendsFrameLayout { private Context context; private ViewGroup parent; private static final String TAG = "xbh"; public HorizontalSlideView(@NonNull Context context, @Nullable AttributeSet attrs) {//這個被呼叫 super(context, attrs); this.context = this.context == null ? context : this.context; } private int mLastX; private int mLastY; int state; @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); parent = (ViewGroup) getParent(); } @Override public boolean onTouchEvent(MotionEvent event) { int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: parent.requestDisallowInterceptTouchEvent(true); state = 0; break; case MotionEvent.ACTION_MOVE: int deltaX = x - mLastX; int deltaY = y - mLastY; if (state == 0) { if (Math.abs(deltaX) < Math.abs(deltaY)) { state = 1; } else { state = 2; } } if (state == 1) { parent.requestDisallowInterceptTouchEvent(false); } else { if (deltaX < 0) { Log.i(TAG, "左滑"); } else { Log.i(TAG, "右滑"); } } break; case MotionEvent.ACTION_UP : break; } mLastX = x; mLastY = y; return true; } }
最後一點還有一個重要的發現,我這裡繼承的是FrameLayout,他是不可點選的。所以看一看view的onTouchEvent可以知道,他是不會進入move 和 up的。所以需要設定為setClickable為true才能正常工作。但是遇見RecyclerView的時候,RecyclerView會為每一個item,都設定成clickable屬性。一旦view是clickable的話,view的onTouch中返回super和返回true將沒有什麼區別。super(view的onTouchEvent)中會直接返回true。
原始碼證明如下
public boolean onTouchEvent(MotionEvent event) { final float x = event.getX(); final float y = event.getY(); final int viewFlags = mViewFlags; final int action = event.getAction(); final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE; if ((viewFlags & ENABLED_MASK) == DISABLED) { if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) { setPressed(false); } mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN; // A disabled view that is clickable still consumes the touch // events, it just doesn't respond to them. return clickable; }
最後一個挺重要的一點,parent的獲取,可以說是人盡皆知了,不過還是得提一下
只有當新增到檢視樹後才可以獲取寬啊,高啊,parent
所以在activity中在onWindowFocusChanged回撥中獲取才行
自定義view在onAttachedToWindow回撥中獲取才行
@Override protected void onAttachedToWindow() { super.onAttachedToWindow(); parent = (ViewGroup) getParent(); }
其實滑動衝突真的非常簡單,只是我今天短路了,不過還是值得記錄一下的。