Android:巢狀滑動總結

zmy愛吃炸雞發表於2020-09-22
一.何為滑動巢狀?
就是就有滑動功能的view,再巢狀另一個具有滑動功能的view,例如recyclerview巢狀recyclerview。
二.CoordinatorLayout是什麼?
是協調佈局,是一個超級framelayout,可以控制多個view協調運動。透過behavior來實現。主要原理是,子view獲取到滑動事件後,在滑動前,先詢問一下父view,問下父view要不要先滑,父view如果要滑,那就滑,然後把滑完之後把剩下的距離告訴子view,然後子view再滑。
所以說,如果處理不好,可以滑起來卡卡的,例如父view滑了一點,然後子view又滑了一點,你就會感覺卡卡的,難受。
三.behavior是怎樣的?
public static abstract class Behavior {
    public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child,
            @NonNull View target, int dxConsumed, int dyConsumed,
            int dxUnconsumed, int dyUnconsumed, @NestedScrollType int type) {
        if (type == ViewCompat.TYPE_TOUCH) {
            onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed,
                    dxUnconsumed, dyUnconsumed);
        }
    }
    @Deprecated
    public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout,
            @NonNull V child, @NonNull View target, int dx, int dy, @NonNull int[] consumed) {
        // Do nothing
    }
    public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout,
            @NonNull V child, @NonNull View target, int dx, int dy, @NonNull int[] consumed,
            @NestedScrollType int type) {
        if (type == ViewCompat.TYPE_TOUCH) {
            onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
        }
    }
    public boolean onNestedFling(@NonNull CoordinatorLayout coordinatorLayout,
            @NonNull V child, @NonNull View target, float velocityX, float velocityY,
            boolean consumed) {
        return false;
    }
    public boolean onNestedPreFling(@NonNull CoordinatorLayout coordinatorLayout,
            @NonNull V child, @NonNull View target, float velocityX, float velocityY) {
        return false;
    }
.......
是個抽象類,看上去有點像NestedScrollingParent的方法。
例如,我們看看onDependentViewChanged是怎麼呼叫的把
image.png
可以看到,CoordinatorLayout註冊了PreDraw的監聽器,就是view在繪製前,會有回撥函式回撥到CoordinatorLayout的onChildViewsChanged,
if (b != null && b.layoutDependsOn(this, checkChild, child)) { //首先檢查是不是依賴的view發生了改變
..... //依賴的view發生改變了,回撥給需要協調運動的view
handled = b.onDependentViewChanged(this, checkChild, child);
.....
}
我們再看一種場景,就是使用onNestedPreScroll來實現的協調運動。
例如
的第二個test。
看看他的backtrace
image.png
可以看到事件是從recyclerview傳過來的,所以說move事件到達recyclerview後,recyclerview作為NestedScrollingChild2,會先迴圈一下作為NestedScrollingParent2的CoordinatorLayout,你CoordinatorLayout有沒有需要滑動的,有沒有需要處理move事件的,等CoordinatorLayou處理完之後,recyclerview再繼續跑。這樣看,像是子view和父view都消費了move事件,挺有意思。
recyclerview呼叫dispatchNestedPreScroll後,實際用使用NestedScrollingChildHelper來處理,
public boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed,
        @Nullable int[] offsetInWindow, @NestedScrollType int type) {
    if (isNestedScrollingEnabled()) { //判斷是否支援巢狀滑動,我們常常可以在應用中關閉巢狀滑動
            ViewParentCompat.onNestedPreScroll(parent, mView, dx, 
}
然後,最後就要回撥到CoordinatorLayout,然後CoordinatorLayout會遍歷每個子view,子view可以根據需要來確定是否處理。
四.分析一下BottomSheetBehavior的繪製流程
layout流程
@Override
@SuppressWarnings("unchecked")
protected void onLayout(boolean changed, int l, int t, int r, int b) {
        final Behavior behavior = lp.getBehavior();
       //獲取childview的behavior,然後呼叫behavior的onLayoutChild去佈局。如果沒有behavior就走CoordinatorLayout自身的onLayoutChild
        if (behavior == null || !behavior.onLayoutChild(this, child, layoutDirection)) {
            onLayoutChild(child, layoutDirection);
        }
    }
}
可以看到,有配置behavior的view,就走behavior的onLayoutChild方法。所以layout方法被behavior接管了。
viewDragHelper = ViewDragHelper.create(parent, dragCallback);
最後其實就是用viewdragger來實現拖拉位置的
@Override
public boolean onLayoutChild(
@NonNull CoordinatorLayout parent, @NonNull V child, int layoutDirection) {
if (state == STATE_EXPANDED) {
ViewCompat.offsetTopAndBottom(child, getExpandedOffset());
} else if (state == STATE_HALF_EXPANDED) {
ViewCompat.offsetTopAndBottom(child, halfExpandedOffset);
} else if (hideable && state == STATE_HIDDEN) {
ViewCompat.offsetTopAndBottom(child, parentHeight);
} else if (state == STATE_COLLAPSED) {
ViewCompat.offsetTopAndBottom(child, collapsedOffset);
} else if (state == STATE_DRAGGING || state == STATE_SETTLING) {
ViewCompat.offsetTopAndBottom(child, savedTop - child.getTop());
}
}
五.bottomsheet巢狀recyclerview後,recyclerview關閉巢狀滑動之後,滑recyclerview,bottomsheet會動嗎?
會動,別以為關閉巢狀滑動,父view就不攔截事件了,實際上,父view會會直接把在slopmove的時候,直接開始攔截move事件了。
image.png
那攔截之後,是怎麼拖到整個bottomsheet的呢?
image.png
其實就是攔截之後,事件去了CoordinatorLayout,然後再呼叫behavior,behavior使用ViewDragHelper去拖動配置了behavior的view網上挪。
關鍵是下面的函式,來確認是否攔截,返回true就攔截,因為touchingScrollingChild為false,所以最後返回了true。如果recyclerview設定了NestedScrollingEnabled,則touchingScrollingChild為true,最後這個函式返回false,父view就不攔截,move事件直接到達recyclerview。
    @Override
    public boolean tryCaptureView(@NonNull View child, int pointerId) {
      Log.i("fengfeng", "tryCaptureView start:" + "child=" + child + " pointId=" + pointerId + " state=" + state);
      if (state == STATE_DRAGGING) {
        Log.i("fengfeng", "tryCaptureView false 1");
        return false;
      }
      if (touchingScrollingChild) {
        Log.i("fengfeng", "tryCaptureView false 2");
        return false;
      }
      if (state == STATE_EXPANDED && activePointerId == pointerId) {
        View scroll = nestedScrollingChildRef != null ? nestedScrollingChildRef.get() : null;
        if (scroll != null && scroll.canScrollVertically(-1)) {
          // Let the content scroll up
          Log.i("fengfeng", "tryCaptureView false 3");
          return false;
        }
      }
      Log.i("fengfeng", "tryCaptureView true 4");
      return viewRef != null && viewRef.get() == child;
    }        @Override
    public boolean tryCaptureView(@NonNull View child, int pointerId) {
      Log.i("fengfeng", "tryCaptureView start:" + "child=" + child + " pointId=" + pointerId + " state=" + state);
      if (state == STATE_DRAGGING) {
        Log.i("fengfeng", "tryCaptureView false 1");
        return false;
      }
      if (touchingScrollingChild) {
        Log.i("fengfeng", "tryCaptureView false 2");
        return false;
      }
      if (state == STATE_EXPANDED && activePointerId == pointerId) {
        View scroll = nestedScrollingChildRef != null ? nestedScrollingChildRef.get() : null;
        if (scroll != null && scroll.canScrollVertically(-1)) {
          // Let the content scroll up
          Log.i("fengfeng", "tryCaptureView false 3");
          return false;
        }
      }
      Log.i("fengfeng", "tryCaptureView true 4");
      return viewRef != null && viewRef.get() == child;
    }
touchingScrollingChild在down事件賦值,代表點中了一個可以scroll的view,例如recyclerview就是可以scroll的。
  case MotionEvent.ACTION_DOWN:
    if (state != STATE_SETTLING) {
      View scroll = nestedScrollingChildRef != null ? nestedScrollingChildRef.get() : null;
      if (scroll != null && parent.isPointInChildBounds(scroll, initialX, initialY)) {
        touchingScrollingChild = true;
      }
    }
作者:九九叔
連結:
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69983768/viewspace-2723155/,如需轉載,請註明出處,否則將追究法律責任。

相關文章