1. 程式人生 > >Android:當滑動衝突遇上RecyclerView

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 extends 
FrameLayout { 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();
}

其實滑動衝突真的非常簡單,只是我今天短路了,不過還是值得記錄一下的。