防止在外部触摸时解除 BottomSheetDialogFragment
Posted
技术标签:
【中文标题】防止在外部触摸时解除 BottomSheetDialogFragment【英文标题】:Prevent dismissal of BottomSheetDialogFragment on touch outside 【发布时间】: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 底部工作表可以通过触摸底部工作表的外部来关闭,因此我有哪些选项可以覆盖此行为并防止它被关闭?
【问题讨论】:
【参考方案1】:创建实例时应使用#setCancelable(false)
。
BottomSheetDialogFragment bottomSheetDialogFragment = new SequenceControlFragmentBottomSheet();
bottomSheetDialogFragment.setCancelable(false);
bottomSheetDialogFragment.show(getChildFragmentManager(), bottomSheetDialogFragment.getTag());
【讨论】:
谢谢!但是如果你禁用取消,你将无法调用 touch 外部监听器(参见***.com/questions/40616833/…)。例如,如果您稍后想在特定情况下取消对话框并尝试覆盖onCancel()
和 onStateChanged()
事件,它们不会调用。
这不起作用,它在向下滚动或触摸底页外时被关闭【参考方案2】:
setCancelable(false)
也将防止底部纸张在背压时消失。如果我们查看 android 设计支持库中底部工作表的布局资源,有一个 ID 为 touch_outside
的 View 组件,并且在 @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
,请参见 ***.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】:我认为以上所有的答案都有点不完整,我会解释原因。
-
setCancelable 将停止外部点击行为,但它也会阻止您的 bottomsheetDialoagFragment 进行后按。
很少有答案会覆盖 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")
现在用户无法通过点击外部关闭它,但可以向下滑动并关闭对话框
【讨论】:
以上是关于防止在外部触摸时解除 BottomSheetDialogFragment的主要内容,如果未能解决你的问题,请参考以下文章
Axios - 防止在外部 api 调用上发送 JWT 令牌