'Prevent dismissal of BottomSheetDialogFragment on touch outside
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?
Solution 1:[1]
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:[2]
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:[3]
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:[4]
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:[5]
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:[6]
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:[7]
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:[8]
I think all the above answers are a little incomplete, I will explain the reason why.
- setCancelable will stop outside click behavior but it will also stop your bottomsheetDialoagFragment from back press.
- 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:[9]
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:[10]
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:[11]
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:[12]
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
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow