防止点击外部区域关闭 BottomSheetDialogFragment

67
我已经实现了一个BottomSheet对话框,并且希望当用户在peeking状态(未完全展开状态)下触摸底部之外的区域时,防止bottomsheet关闭。
我已经在代码中设置了 dialog.setCanceledOnTouchOutside(false);,但似乎没有任何效果。
这是我的BottomSheetDialogFragment类:
public class ShoppingCartBottomSheetFragment extends BottomSheetDialogFragment  {

    private BottomSheetBehavior.BottomSheetCallback mBottomSheetBehaviorCallback = new BottomSheetBehavior.BottomSheetCallback() {

        @Override
        public void onStateChanged(@NonNull View bottomSheet, int newState) {
            if (newState == BottomSheetBehavior.STATE_HIDDEN) {
                dismiss();
            }

        }

        @Override
        public void onSlide(@NonNull View bottomSheet, float slideOffset) {
        }
    };

    @Override
    public void setupDialog(Dialog dialog, int style) {
        super.setupDialog(dialog, style);

        View contentView = View.inflate(getContext(), R.layout.fragment_shopping_cart_bottom_sheet, null);

        dialog.setCanceledOnTouchOutside(false);

        dialog.setContentView(contentView);

        CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) ((View) contentView.getParent()).getLayoutParams();
        CoordinatorLayout.Behavior behavior = params.getBehavior();

        if( behavior != null && behavior instanceof BottomSheetBehavior ) {
            ((BottomSheetBehavior) behavior).setBottomSheetCallback(mBottomSheetBehaviorCallback);
            ((BottomSheetBehavior) behavior).setPeekHeight(97);
            ((BottomSheetBehavior) behavior).setHideable(false);
        }
    }


    @Override
    public void onStart() {
        super.onStart();
        Window window = getDialog().getWindow();
        WindowManager.LayoutParams windowParams = window.getAttributes();
        windowParams.dimAmount = 0;
        windowParams.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND;
        window.setAttributes(windowParams);
    }
}

根据底部工作表规范,可以通过触摸底部工作表外部来关闭底部工作表,那么我有哪些选项可以覆盖此行为并防止其被关闭?
13个回答

62

创建实例时应该使用#setCancelable(false)

    BottomSheetDialogFragment bottomSheetDialogFragment = new SequenceControlFragmentBottomSheet();
    bottomSheetDialogFragment.setCancelable(false);
    bottomSheetDialogFragment.show(getChildFragmentManager(), bottomSheetDialogFragment.getTag());

谢谢!但是如果您禁用取消,您将无法调用触摸外部侦听器(请参见https://dev59.com/oFkR5IYBdhLWcg3w7xdc)。例如,如果您稍后想在特定情况下取消对话框并尝试覆盖`onCancel()`和`onStateChanged()`事件,它们将不会被调用。 - CoolMind
2
这不起作用,当向下滚动或触摸底部表格之外时,它会被关闭。 - Ajay Chauhan

29

以上所有答案对于简单的底部对话框来说都有些复杂,只需使用“可取消”即可防止对话框外的滚动和点击。

mBottomSheetDialog.setCancelable(false)
mBottomSheetDialog.setCanceledOnTouchOutside(false)

只需将其用于简单的底部对话框即可。对我有用。


请提供英文文本,我将为您翻译成中文。 - John Woo
工作了。如果您需要使底部弹出式对话框片段在屏幕上持久存在,请仅添加mBottomSheetDialog.setCanceledOnTouchOutside(false) - Krusty

26

setCancelable(false)将阻止底部表单在按下返回键时关闭。如果我们查看Android设计支持库中底部表单的布局资源,可以看到一个具有ID touch_outsideView组件,并且在BottomSheetDialogwrapInBottomSheet方法中设置了一个OnClickListener,用于检测点击外部并关闭对话框。因此,为了防止在触摸底部表单以外的区域时自动取消它,我们需要删除OnClickListener

将以下行添加到onActivityCreated方法(或任何其他生命周期方法,例如onCreateView)。

@Override public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    View touchOutsideView = getDialog().getWindow()
        .getDecorView()
        .findViewById(android.support.design.R.id.touch_outside);
    touchOutsideView.setOnClickListener(null);
}

如果你想要防止向下滑动导致底部菜单关闭,可以将底部菜单对话框的行为更改为不可隐藏。要使用setHideable(false)方法,将以下代码添加到onCreateDialog方法中。

@Override public Dialog onCreateDialog(Bundle savedInstanceState) {
    final BottomSheetDialog bottomSheetDialog =
        (BottomSheetDialog) super.onCreateDialog(savedInstanceState);

    bottomSheetDialog.setOnShowListener(new DialogInterface.OnShowListener() {
      @Override public void onShow(DialogInterface dialog) {
        FrameLayout bottomSheet =
        bottomSheetDialog.findViewById(android.support.design.R.id.design_bottom_sheet);

        if (null != bottomSheet) {
          BottomSheetBehavior behavior = BottomSheetBehavior.from(bottomSheet);
        behavior.setHideable(false);
        }
      }
    });
    return bottomSheetDialog;
  }

1
注意:这个方法在 onActivityCreated 中可以工作,但是在 onViewCreated 中却不能工作,尽管它在 onCreateView 之后调用。 - AdamHurwitz
1
感谢使用 onActivityCreated。@AdamHurwitz,是的,对于 onViewCreated 请参见 https://dev59.com/KVcO5IYBdhLWcg3wkiVN,在那里您可以使用 addOnGlobalLayoutListener - CoolMind
4
请参考 https://medium.com/@betakuang/make-your-bottomsheetdialog-noncancelable-e50a070cdf07。对于AndroidX,请使用`com.google.android.material.R.id.design_bottom_sheet`(我没有测试过)。 - CoolMind
3
要获取AndroidX中的com.google.android.material.R.id.touch_outside,而不是design_bottom_sheet。请注意,这是一条指示,没有其他解释或上下文。 - Pierre-Olivier Dybman
1
这个很好用。同时感谢@Pierre-OlivierDybman更新了id的正确引用。我已经在下面添加了一个附加方案。 - sud007
显示剩余3条评论

17

最简单的方法是在BottomSheetDialogFragment的对话框上将setCanceledOnTouchOutside设置为false,使用Kotlin代码示例如下:

class XFragment : BottomSheetDialogFragment() {

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        dialog?.setCanceledOnTouchOutside(false)
    }

}

3
尝试使用Kotlin的方式。
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            dialog?.setCancelable(false)
            dialog?.setCanceledOnTouchOutside(false)
            view.viewTreeObserver.addOnGlobalLayoutListener {
                val dialog = dialog as BottomSheetDialog?
                val bottomSheet =
                    dialog!!.findViewById<View>(com.google.android.material.R.id.design_bottom_sheet) as FrameLayout
                val behavior = BottomSheetBehavior.from(bottomSheet)
                behavior.state = BottomSheetBehavior.STATE_EXPANDED
                behavior.peekHeight = 0
                behavior.isDraggable = false
            }
    }

3
M. Erfan Mowlaei 的答案很有用,但我正在寻找一种方法,在创建类的实例时每次强制执行此行为,而无需记住调用 setCancelable(false)。请见下文。
class MyBottomSheet : BottomSheetDialogFragment() {

companion object {
    fun newInstance() = MyBottomSheet().apply {
        isCancelable = false
      }
   }
}

2

对我来说,我正在寻找相反的效果,即在我点击外部时关闭BottomSheetDialogFragment

class MyBottomSheetDialogFragment : BottomSheetDialogFragment() {
    ...
    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog =
        super.onCreateDialog(savedInstanceState).apply {
            setCanceledOnTouchOutside(true)
        }
}

要禁用在外部点击时的取消操作,只需将值设置为false


2
在 Kotlin 中非常简单,您可以像这样做:
class MyBottomSheetDialog : BottomSheetDialogFragment() {
/*********/

    override fun onCreateDialog(savedInstanceState: Bundle?) =
        super.onCreateDialog(savedInstanceState).apply {
            setCanceledOnTouchOutside(false)
            setOnShowListener { expand() } /**to Expand your bottomSheet according to the content**/
        }

/*******/

}

2

我尝试了所有的答案,但以下是最好的解决方案

唯一适用于我的方法

Style.xml

<style name="BottomSheetDialogTheme" parent="@style/Theme.Design.Light.BottomSheetDialog">
        <item name="android:statusBarColor">@android:color/transparent</item>
        <item name="android:windowIsFloating">false</item>
        <item name="android:windowSoftInputMode">adjustResize|stateAlwaysVisible</item>
        <item name="android:navigationBarColor">@color/white</item>
        <item name="bottomSheetStyle">@style/BottomSheet</item>
</style>

<!-- set the rounded drawable as background to your bottom sheet -->
<style name="BottomSheet" parent="@style/Widget.Design.BottomSheet.Modal">
    <item name="android:background">@drawable/bg_bottom_sheet_dialog_fragment</item>
</style>

RoundedBottomSheetDialogFragment

open class RoundedBottomSheetDialogFragment : BottomSheetDialogFragment() {

    override fun getTheme(): Int = R.style.BottomSheetDialogTheme

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        return BottomSheetDialog(requireContext(), theme)
    }

}

class UserDetailsSheet : RoundedBottomSheetDialogFragment() {

    init {
        isCancelable = false
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.user_info_fragment, container, false)
    }

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        val dialog = super.onCreateDialog(savedInstanceState)
        dialog.setCancelable(false)
        dialog.setCanceledOnTouchOutside(false)
        return dialog
    }
}

2
我看到了很多答案,但直到找到这篇不错的博客文章,我才能让它们起作用。链接在这里:https://medium.com/@betakuang/make-your-bottomsheetdialog-noncancelable-e50a070cdf07 除了用户@shijo提出的解决方案之外,这里还有一个解决方案。对于我的BottomSheetDialogFragment,我只需要在onActivityCreated中添加以下代码片段,就能实现这两种行为。
  • disable the draggable behavior.

  • disable the cancel on touch outside.

      override fun onActivityCreated(savedInstanceState: Bundle?) {
      super.onActivityCreated(savedInstanceState)
      val dialog = dialog as BottomSheetDialog?
    
      val bottomSheet =
          dialog?.findViewById<FrameLayout>(com.google.android.material.R.id.design_bottom_sheet)
      val behavior: BottomSheetBehavior<View> = BottomSheetBehavior.from(bottomSheet as View)
      behavior.state = BottomSheetBehavior.STATE_EXPANDED
      behavior.peekHeight = 0
    
      // Disable Draggable behavior
      behavior.isDraggable = false
    
      // Disable cancel on touch outside
      val touchOutsideView =
          getDialog()?.window?.decorView?.findViewById<View>(com.google.android.material.R.id.touch_outside)
      touchOutsideView?.setOnClickListener(null)
    

这对我非常有帮助。由于我的底部工作表 UX 要求它不可取消和不可拖动,我能够通过这些更改正确地实现它。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接