Prevent dismissal of BottomSheetDialogFragment on touch outside

AndroidBottom Sheet

Android Problem Overview


I have implemented a BottomSheet Dialog and I want to prevent the bottomsheet from dismissing when the user touches outside of the bottomsheet when it's peeking (Not fully expanded state).

I have set dialog.setCanceledOnTouchOutside(false); in the code but it doesn't seem to take any affect.

Here's my BottomSheetDialogFragment class:

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

According to the BottomSheet specification bottom sheets can be dismissed by touching outside of the bottom sheet, therefore what are my options to override this behavior and prevent it being dismissed?

Android Solutions


Solution 1 - Android

You should use #setCancelable(false) when you create an instance of it.

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

Solution 2 - Android

setCancelable(false) will prevent the bottom sheet dismiss on back press also. If we look at the layout resource for the bottom sheet in android design support library, there is a View component with ID touch_outside and there is an OnClickListener set in method wrapInBottomSheet of BottomSheetDialog, which is used for detecting clicks outside and dismiss the dialog. So, to prevent cancel on touch outside the bottom sheet we need to remove the OnClickListener.

Add these line to onActivityCreated method (or any other life cycle method after 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);
}

Also if you want to prevent the bottom sheet dismiss by swiping down, change the bottom sheet dialog behaviour Hideable false. To setHideable(false) add the following code to the onCreateDialog method.

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

Solution 3 - Android

all of the above answers are a little complex in case of simple bottom sheet dialog Just Use cancellable As it prevents the scrolling and clicking outside of the Dialog.

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

just use it for simple Bottom Sheet Dialog. it worked for Me.

Solution 4 - Android

Simplest way is to set setCanceledOnTouchOutside to false on the BottomSheetDialogFragment's dialog. with Kotlin,

class XFragment : BottomSheetDialogFragment() {

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

}

Solution 5 - Android

The answer by M. Erfan Mowlaei is useful but I was looking for a way to enforce this behavior every time an instance of the class is created without having to remember to call setCancelable(false). See below.

class MyBottomSheet : BottomSheetDialogFragment() {

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

Solution 6 - Android

I tried all the answers but here is the best working solution

The only thing that worked for me

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

Solution 7 - Android

I could see a lot of answers but could not get them to work unltil found this one nice bolg post here

Here's one more solution on top of what is proposed by user @shijo above. All I did for my BottomSheetDialogFragment is just following code snippet inside onActivityCreated and was able to achieve these two behaviors.

  • 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)
    

This worked wonders for me. Since my bottomsheet UX demanded it to be non-cancelable and non-draggable, I was able to achieve it correctly with these changes.

Solution 8 - Android

I think all the above answers are a little incomplete, I will explain the reason why.

  1. setCancelable will stop outside click behavior but it will also stop your bottomsheetDialoagFragment from back press.
  2. Few answers are overriding the setCancelable() method to manage click outside, which is little complex as you will need to handle cancelable and hideable.

Solution

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

you will have to call this method in onStart() where instance of dialog will always be there.

Solution 9 - Android

simple and short working solution

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

override onActivityCreated() method of BottomSheetDialogFragment in your customDialogFragment and setCanceledOnTouchOutside(false)

Solution 10 - Android

Try Kotlin way

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

Solution 11 - Android

In kotlin is so simple , you can do it like this ;

class MyBottomSheetDialog : BottomSheetDialogFragment() {
/*********/

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

/*******/

}

Solution 12 - Android

I faced same issue. But I needed to prevent bottom sheet fragment from close by click outside but to leave opportunity to close by swipe down. Just add variable isHideable: Boolean to your BSFragment class and add next snippet

    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
}

and when you call your fragment - set isHideable to true

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

now user cannot close it by click outside but able to swipe down and close dialog

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionRamithDRView Question on Stackoverflow
Solution 1 - AndroidM. Erfan MowlaeiView Answer on Stackoverflow
Solution 2 - AndroidshijoView Answer on Stackoverflow
Solution 3 - AndroidRabiaView Answer on Stackoverflow
Solution 4 - AndroidAll Іѕ VаиітyView Answer on Stackoverflow
Solution 5 - AndroidIvan WoollView Answer on Stackoverflow
Solution 6 - AndroidRonny ShibleyView Answer on Stackoverflow
Solution 7 - Androidsud007View Answer on Stackoverflow
Solution 8 - AndroidaamitaryaView Answer on Stackoverflow
Solution 9 - AndroidKinjal BharsadiyaView Answer on Stackoverflow
Solution 10 - AndroidMujahid KhanView Answer on Stackoverflow
Solution 11 - AndroidAnis MARZOUKView Answer on Stackoverflow
Solution 12 - AndroidKirguduckView Answer on Stackoverflow