Android 解决BottomSheetDialog 拖曳冲突问题

7,187 阅读3分钟

问题

在实现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等也应该是类似的解决方案,一步步分析源码和猜想尝试,总会解决。