【问题标题】:Prevent dismissal of BottomSheetDialogFragment on touch outside防止在外部触摸时解除 BottomSheetDialogFragment
【发布时间】:2017-06-28 12:04:05
【问题描述】:

我已经实现了一个 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);
    }
}

根据 BottomSheet specification 底部工作表可以通过触摸底部工作表的外部来关闭,因此我有哪些选项可以覆盖此行为并防止它被关闭?

【问题讨论】:

    标签: android bottom-sheet


    【解决方案1】:

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

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

    【讨论】:

    • 谢谢!但是如果你禁用取消,你将无法调用 touch 外部监听器(参见stackoverflow.com/questions/40616833/…)。例如,如果您稍后想在特定情况下取消对话框并尝试覆盖 onCancel()onStateChanged() 事件,它们不会调用。
    • 这不起作用,它在向下滚动或触摸底页外时被关闭
    【解决方案2】:

    setCancelable(false) 也将防止底部纸张在背压时消失。如果我们查看 android 设计支持库中底部工作表的布局资源,有一个 ID 为 touch_outsideView 组件,并且在 @987654327 的方法 wrapInBottomSheet 中设置了一个 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);
    }
    

    此外,如果您想通过向下滑动来防止底部工作表消失,请将底部工作表对话框行为更改为 可隐藏 false。在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;
      }
    

    【讨论】:

    • 注意:这在 onActivityCreated 中有效,但在 onViewCreated 中无效,尽管它在 onCreateView 之后。
    • 感谢onActivityCreated。 @AdamHurwitz,是的,对于 onViewCreated,请参见 stackoverflow.com/questions/45614271/…,在那里您可以使用 addOnGlobalLayoutListener
    • 另见medium.com/@betakuang/…。对于 AndroidX 使用 com.google.android.material.R.id.design_bottom_sheet(我没有测试)。
    • 对于AndroidX,你需要得到的是com.google.android.material.R.id.touch_outside,而不是design_bottom_sheet
    • 这很好用。还要感谢@Pierre-OlivierDybman 更新了id 的正确参考。我在下面添加了一个带有插件的解决方案。
    【解决方案3】:

    对于简单的底部工作表对话框,上述所有答案都有些复杂,请使用可取消的,因为它可以防止在对话框之外滚动和单击。

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

    只需将它用于简单的底页对话框。 它对我有用。

    【讨论】:

    • 简单明了的回答! :)
    • 工作。如果您需要使底部工作表对话框片段在屏幕上持久化,请仅添加 mBottomSheetDialog.setCanceledOnTouchOutside(false)
    【解决方案4】:

    最简单的方法是在 BottomSheetDialogFragment 的对话框中将 setCanceledOnTouchOutside 设置为 false。使用 Kotlin,

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

    【讨论】:

      【解决方案5】:

      M 的回答。 Erfan Mowlaei 很有用,但我一直在寻找一种方法来在每次创建类的实例时强制执行此行为,而不必记住调用 setCancelable(false)。见下文。

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

      【讨论】:

        【解决方案6】:

        我认为以上所有的答案都有点不完整,我会解释原因。

        1. setCancelable 将停止外部点击行为,但它也会阻止您的 bottomsheetDialoagFragment 进行后按。
        2. 很少有答案会覆盖 setCancelable() 方法来管理外部点击,这并不复杂,因为您需要处理可取消和可隐藏。

        解决方案

        override fun onStart() {
               super.onStart()
               stopOutsideClick()
        }
        
        private fun stopOutsideClick() {
               val touchOutsideView = dialog?.window?.decorView?.findViewById<View>(com.google.android.material.R.id.touch_outside)
               touchOutsideView?.setOnClickListener(null)
        }
        

        您必须在 onStart() 中调用此方法,其中对话框实例将始终存在。

        【讨论】:

        • 如果你想验证一些数据或者询问用户是否真的想在关闭前退出,这是一个很好的拦截BottomSheet关闭的方法。
        【解决方案7】:

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

        唯一对我有用的东西

        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
            }
        }
        

        【讨论】:

          【解决方案8】:

          我可以看到很多答案,但无法让它们发挥作用,直到找到这篇不错的博尔格帖子 here

          这是在上面用户@shijo 提出的解决方案之上的另一种解决方案。我为我的BottomSheetDialogFragment 所做的只是在onActivityCreated 中遵循代码 sn-p 并且能够实现这两种行为。

          • 禁用可拖动行为。

          • 禁用外部触摸取消。

              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 要求它是不可取消和不可拖动的,因此我能够通过这些更改正确地实现它。

          【讨论】:

            【解决方案9】:

            简单而简短的工作解决方案

                @Override
                public void onActivityCreated(@Nullable Bundle savedInstanceState) {
                    super.onActivityCreated(savedInstanceState);
                    getDialog().setCanceledOnTouchOutside(false);
                }
            

            在您的 customDialogFragment 和 setCanceledOnTouchOutside(false) 中覆盖 BottomSheetDialogFragment 的 onActivityCreated() 方法

            【讨论】:

              【解决方案10】:

              尝试 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
                          }
                  }
              

              【讨论】:

                【解决方案11】:

                我遇到了同样的问题。但是我需要防止底部的片断通过点击外部关闭,但留下机会通过向下滑动关闭。只需将变量 isHideable: Boolean 添加到您的 BSFragment 类并添加下一个 sn-p

                    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
                    val dialog = super.onCreateDialog(savedInstanceState)
                    dialog.setOnShowListener {
                        val d = dialog as BottomSheetDialog
                        val bottomSheet =
                            d.findViewById<FrameLayout>(com.google.android.material.R.id.design_bottom_sheet)
                        BottomSheetBehavior.from(bottomSheet!!).state = initialState()
                        BottomSheetBehavior.from(bottomSheet).setHideable(isHideable)
                    }
                    return dialog
                }
                

                当您调用片段时 - 将 isHideable 设置为 true

                private fun showBSFragmentDialog() {
                  val bSFDialog = BSFragment.newInstance()
                  bSFDialog.isCancelable = false
                  bSFDialog.isHideable = true
                  bSFDialog.show(supportFragmentManager, "bSFDialog")  }
                

                现在用户无法通过点击外部关闭它,但可以向下滑动并关闭对话框

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 2020-04-11
                  • 2019-12-27
                  • 2012-08-19
                  • 1970-01-01
                  • 2023-03-31
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多