Android 解決BottomSheetDialog 拖曳衝突問題

ganshenml發表於2019-03-29

問題

在實現BottomSheetDialog中嵌入Webview的時候,會向下拖動會將整個dialog消失掉而不是滑動webview內容,如下:

整個dialog向下拖動時會消失掉

解決問題

1.提出設想

一般webview的向下滑動/拖動的效果應該是可以正常顯現出來的,而BottomSheetDialog控制元件本身自帶的拖動效果也是將其整個View給消失掉的,這樣兩者在同時向下拖動時就會產生部分衝突。(如果沒有衝突,那麼必定是谷歌在設計時以及做了處理。)因此,可以把問題的解決關鍵假定為:當發生向下動作的,去解決這之間的“衝突”。

2.原始碼分析

翻開BottomSheetDialog的原始碼,發現內容並不多:

關鍵方法
可以看見,所有setContentView的入口都呼叫了這個wrapInBottomSheet方法。

 private View wrapInBottomSheet(int layoutResId, View view, ViewGroup.LayoutParams params) {
        final FrameLayout container = (FrameLayout) View.inflate(getContext(),
                R.layout.design_bottom_sheet_dialog, null);
        final CoordinatorLayout coordinator =
                (CoordinatorLayout) container.findViewById(R.id.coordinator);
        if (layoutResId != 0 && view == null) {
            view = getLayoutInflater().inflate(layoutResId, coordinator, false);
        }
        FrameLayout bottomSheet = (FrameLayout) coordinator.findViewById(R.id.design_bottom_sheet);
        mBehavior = BottomSheetBehavior.from(bottomSheet);
        mBehavior.setBottomSheetCallback(mBottomSheetCallback);
        mBehavior.setHideable(mCancelable);
        if (params == null) {
            bottomSheet.addView(view);
        } else {
            bottomSheet.addView(view, params);
        }
    	//...省略
        return container;
    }
複製程式碼

傳入的View最終會被add進一個FrameLayout的bottomSheet中,然後通過bottomSheet來例項化一個BottomSheetBehavior,之後給這個mBehavior設定了一個mBottomSheetCallback,我們來看看其內容是什麼:

    private BottomSheetBehavior.BottomSheetCallback mBottomSheetCallback
            = new BottomSheetBehavior.BottomSheetCallback() {
        @Override
        public void onStateChanged(@NonNull View bottomSheet,
                @BottomSheetBehavior.State int newState) {
            if (newState == BottomSheetBehavior.STATE_HIDDEN) {
                cancel();
            }
        }

        @Override
        public void onSlide(@NonNull View bottomSheet, float slideOffset) {
        }
    };
複製程式碼

可以看出裡面涉及到了對BottomSheet的狀態監聽,邏輯判斷則是根據BottomSheetBehavior的狀態來進行相應處理,再來看看BottomSheetBehavior中有哪些狀態:

/**
     * The bottom sheet is dragging.
     */
    public static final int STATE_DRAGGING = 1;

    /**
     * The bottom sheet is settling.
     */
    public static final int STATE_SETTLING = 2;

    /**
     * The bottom sheet is expanded.
     */
    public static final int STATE_EXPANDED = 3;

    /**
     * The bottom sheet is collapsed.
     */
    public static final int STATE_COLLAPSED = 4;

    /**
     * The bottom sheet is hidden.
     */
    public static final int STATE_HIDDEN = 5;
複製程式碼

找到了STATE_DRAGGING 狀態,假定我們能在BottomSheetCallback的onStateChanged的監聽方法中能監聽到該對應的動作,那麼我們就能攔截處理一開始出現的問題。 於是,在onStateChanged中輸出日誌,列印各動作的newState對應的狀態碼是多少,最後發現,果然當向下拖動整個view時,其newState為STATE_DRAGGING。因此,只要在這裡將狀態改為不是該狀態可能就會實現要求的效果。根據名稱選擇STATE_EXPANDED 來做相應的處理。

但是如何來做呢? BottomSheetCallback是通過BottomSheetBehavior來set的,而BottomSheetBehavior則有

 public static <V extends View> BottomSheetBehavior<V> from(V view) 
複製程式碼

的靜態方法來例項化,於是到這裡就很清晰了。

解決方案

1.改寫BottomSheetCallback裡的判斷邏輯

private BottomSheetBehavior.BottomSheetCallback mBottomSheetCallback
            = new BottomSheetBehavior.BottomSheetCallback() {
        @Override
        public void onStateChanged(@NonNull View bottomSheet,
                                   @BottomSheetBehavior.State int newState) {
            if (newState == BottomSheetBehavior.STATE_DRAGGING) {//判斷為向下拖動行為時,則強制設定狀態為展開
                bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED );
            }
            LogUtil.e(TAG, "onStateChanged——>" + newState);
        }

        @Override
        public void onSlide(@NonNull View bottomSheet, float slideOffset) {
            LogUtil.e(TAG, "onSlide——>" + slideOffset);
        }
    };
複製程式碼

2.例項化BottomSheetBehavior,給BottomSheetDialog設定回撥

 mBottomSheetDialog.setmBottomSheetCallback((FrameLayout)(view.getParent()));//因為view是被add進去,設定的回撥監聽是其parent view
複製程式碼
 public void setmBottomSheetCallback(View sheetView) {
        if (bottomSheetBehavior == null) {
            bottomSheetBehavior = BottomSheetBehavior.from(sheetView);
        }
        bottomSheetBehavior.setBottomSheetCallback(mBottomSheetCallback);
    }

複製程式碼

3.方案效果

handle

總結

以上只是簡單地結合原始碼分析並提出猜想的一種可解決方案,還有很多問題並未深究。一開始所說的“衝突”其實是已經被谷歌解決了的,只是在依據此的基礎上做了些額外的處理使其能依照其規則正常的執行。其它例如加入RecycleView等也應該是類似的解決方案,一步步分析原始碼和猜想嘗試,總會解決。

相關文章