如何允许BottomSheetDialog外部触摸?

10
我正在处理BottomSheetDialogFragment,我的需求是创建底部菜单,当我在片段外面单击时,它不应该取消对话框并应该保留状态。

问题:并且在片段之外的事件应该向较低的片段视图/片段传播。

我已经尝试过下面的方法(对于BottomDialogFragment不起作用):允许对DialogFragment的外部触摸

为了停止对话框的取消,我尝试了以下方法(在BottomDialogFragment的onStart()中调用setCancelable(boolean)):

@Override
    public void setCancelable(boolean cancelable) {
        super.setCancelable(cancelable);

        BottomSheetDialog dialog = (BottomSheetDialog) getDialog();
        dialog.setCanceledOnTouchOutside(cancelable);

        View bottomSheetView = dialog.getWindow().getDecorView().findViewById(R.id.design_bottom_sheet);
        BottomSheetBehavior.from(bottomSheetView).setHideable(cancelable);
    }

参考

编辑:我发现除了使用Coordinate layout没有其他选择。 BottomSheetDialog的最佳解决方案在这里

  • 这个解决方案解决了问题,但带来了另一个问题,即所有actionMode事件都无法导航,而所有其他应用程序事件都可以。
  • 这是我对该问题的最佳解决方案

这个链接对你有帮助吗? - coroutineDispatcher
@StavroXhardha 我已经尝试过这个方法,它允许我停止取消对话框,但是下面视图的事件也被阻止了。 - Anmol
我找到了解决方案,并在此演示项目 https://github.com/andor201995/PersistIt 中应用。 - Anmol
5个回答

6
尝试在你的BottomSheetDialog中使用以下代码
 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
     return (super.onCreateDialog(savedInstanceState) as BottomSheetDialog).apply {
         setCanceledOnTouchOutside(false)
     }
 }

或者使用<CoordinatorLayout>包装例如您的<ConstraintLayout>,并实现<layout />并附加到BottomSheetBehavior


你好,我尝试了这个,但是当我点击视图之外的区域时,对话框没有被取消,但同时所有事件都被阻止了,而我想要它们传递到较低的视图。我想知道如何将事件传递到较低的视图。谢谢您的回复。 - Anmol
2
尝试将<layout />附加到BottomSheetBehavior,它会正常工作。 - ardiien
我认为我将被迫使用坐标布局作为根布局,以便将BottomSheetBehavior添加为layout_behaviour。但我只是试图弄清楚是否可以使用对话框并绕过外部触摸区域。 - Anmol
嗯,试试关闭“dim”并在“styles”中应用一些属性。也许会有所帮助。参考链接 - ardiien
我认为你只需要用<CoordinatorLayout>将你的<ConstraintLayout>包装起来,然后实现<layout />并附加到BottomSheetBehavior即可。 - ardiien
@YaroslavOvdiienko 谢谢您的建议,它正是我想要的。谢谢。 - Dharmishtha

5

正如Pankaj Kumar所说,这在默认情况下是不可能的。但是我找到了一个可以实现的解决方法,它允许触摸底部表格之外的视图,并保持底部表格打开状态。

您可以按照以下方式覆盖BottomSheetDialog的布局:

values/refs.xml

<resources xmlns:tools="http://schemas.android.com/tools">
    <item name="design_bottom_sheet_dialog" type="layout" tools:override="true">@layout/custom_design_bottom_sheet_dialog</item>
</resources>

layout/custom_design_bottom_sheet_dialog.xml

<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ This is an override of the design_bottom_sheet_dialog from material library
-->
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

  <androidx.coordinatorlayout.widget.CoordinatorLayout
      android:id="@+id/coordinator"
      android:layout_width="match_parent"
      android:layout_height="wrap_content">

    <View
        android:id="@+id/touch_outside"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:visibility="gone"
        android:importantForAccessibility="no"
        android:soundEffectsEnabled="false"
        tools:ignore="UnusedAttribute"/>

    <FrameLayout
        android:id="@+id/design_bottom_sheet"
        style="?attr/bottomSheetStyle"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal|top"
        app:layout_behavior="@string/bottom_sheet_behavior"/>

  </androidx.coordinatorlayout.widget.CoordinatorLayout>

</FrameLayout>

您自定义的BottomSheetDialogFragment

override fun onStart() {
        super.onStart()

        // Set layout for custom bottom sheet by allowing background touches
        dialog?.window?.apply {
            setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
            setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL, WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL)
            
            attributes = attributes.apply {
                gravity = Gravity.BOTTOM
            }
            setDimAmount(0.0f)
        }
    }

通过这样做,对话框会有wrap_content的高度,并且标志允许触摸在该对话框外的视图上进行处理。


1
终于!有个干净简单易用的东西了。这对我来说完美地运作了,我太高兴了,可以亲你一下 :D!谢谢!! - Abushawish
2
非常好,但它会影响所有底部弹出窗口。有没有办法只为一个特定的底部弹出窗口进行此更改? - shko
@shko,我现在遇到这个问题,你能解决吗? - BertKing

3
这是不可能的,除非您使用“BottomSheetDialogFragment”。 “BottomSheetDialogFragment”是一个对话框,就像每个对话框的行为一样,它不允许用户拦截对话框后面任何可见的视图。
所以要实现这一点,您需要使用“Fragment”而不是“BottomSheetDialogFragment”。是的,这将需要很多代码更改,并且如果您想要拦截后面的视图,则必须不使用“BottomSheetDialogFragment”。

主要问题是(使用 Fragment)我的应用程序/Activity 没有使用 Adjust resize,但根据要求,如果键盘显示,则底部菜单应向上移动(示例)。 BottomSheetDialog 本身可以智能地根据系统调整调用进行调整。我无法在 MyCustomFragment 中实现相同的行为。因此,我倾向于使用 BottomSheetDialogFragment。 - Anmol
1
我认为如果这是唯一剩下的任务,那是可行的。你只需要设计容器布局来处理键盘出现的情况。你可以参考https://code.luasoftware.com/tutorials/android/move-layout-when-keyboard-shown/或类似的主题。 - Pankaj Kumar

2

您应该使用android.support.design.widget.BottomSheetBehavior

但是如果您想在其他类中拥有一个bottomSheet,我建议您使用Fragment,并在此fragment中打开您的bottomSheet

这种方式打开您的片段。

并且在您的片段中,在以下代码中打开您的bottomSheet

onInitViews

var mBottomSheetBehavior = BottomSheetBehavior.from(bottomSheetCoordinatorLayout)
mBottomSheetBehavior!!.state = BottomSheetBehavior.STATE_HIDDEN

mBottomSheetBehavior!!.setBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
     override fun onStateChanged(bottomSheet: View, newState: Int) {
          when (newState) {
             BottomSheetBehavior.STATE_HIDDEN -> {
                  fragmentManager?.popBackStack()
             }
             //BottomSheetBehavior.STATE_COLLAPSED -> "Collapsed"
             //BottomSheetBehavior.STATE_DRAGGING -> "Dragging..."
             //BottomSheetBehavior.STATE_EXPANDED -> "Expanded"
             //BottomSheetBehavior.STATE_SETTLING -> "Settling..."
           }
      }

      override fun onSlide(bottomSheet: View, slideOffset: Float) {
          //text_view_state!!.text = "Sliding..."
      }
})

您的 布局 应该像这样:

<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layoutDirection="ltr">

    <android.support.design.widget.CoordinatorLayout
        android:id="@+id/bottomSheetCoordinatorLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:behavior_hideable="true"
        app:behavior_peekHeight="55dp"
        app:layout_behavior="@string/bottom_sheet_behavior">

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@drawable/top_radius_primary_color"
            android:paddingStart="@dimen/material_size_32"
            android:paddingEnd="@dimen/material_size_32">

        </RelativeLayout>
    </android.support.design.widget.CoordinatorLayout>
</android.support.design.widget.CoordinatorLayout>

我希望这可以帮助你


1
https://github.com/andor201995/PersistIt 如果您正在使用导航组件,则会遇到其他一些挑战。我无法使用片段事务。请尝试此演示项目以进行进一步的增强。但是,就这个问题的范围而言,这是正确的答案。 - Anmol

0

如上所述,我们无法直接使用BottomSheetDialogFragment或BottomSheetDialog实现允许在外部触碰的效果。

但是,我们可以通过BottomSheetDialogFragment中的Dialog和BottomSheetCallback来实现它。(言而有信,代码如下)

class BottomPanelFragment : BottomSheetDialogFragment() {

 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
    
      val dialog = Dialog(requireActivity(), R.style.ChargeMapDialog)
        val view = requireActivity().layoutInflater.inflate(
            R.layout.fragment_around_charge_station_info,
            null
        )
        dialog.setContentView(view)

        dialog.window?.setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)
        dialog.window?.setFlags(WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH, WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH)


        dialog.window?.setDimAmount(0F)
        dialog.setCancelable(false)
        dialog.setCanceledOnTouchOutside(false)

  }   

   override fun onStart() {
        super.onStart()
        // Set layout for custom bottom sheet by allowing background touches
        dialog?.window?.apply {
            setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
            setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL, WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL)

            attributes = attributes.apply {
                gravity = Gravity.BOTTOM
            }
            setDimAmount(0.0f)
        }
    }    
}


对应的 XML 文件:

 <androidx.coordinatorlayout.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    // notes:app:layout_behavior="@string/bottom_sheet_behavior"
    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/fill_100"
        app:behavior_hideable="false"
        app:behavior_peekHeight="@dimen/dp_80"
        app:layout_behavior="@string/bottom_sheet_behavior">
        >

        <ImageView
            android:id="@+id/iv_charge_station"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="@dimen/dp_24"
            android:layout_marginTop="@dimen/dp_20"
            android:src="@drawable/ic_charge_station_flag"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:id="@+id/tv_charge_station_info"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="@dimen/dp_24"
            android:layout_marginTop="@dimen/dp_8"
            android:text="Welcome driving the Tesla model-S"
            android:textColor="@color/gray_400"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/iv_charge_station" />

        <TextView
            android:id="@+id/tv_select_area"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="@dimen/dp_24"
            android:layout_marginEnd="@dimen/dp_20"
            android:drawablePadding="@dimen/dp_4"
            android:gravity="center"
            android:text="Los Angeles"
            android:textColor="@color/text_700"
            android:textSize="@dimen/sp_12"
            app:drawableEndCompat="@drawable/ic_arrow_down"
            app:layout_constraintBottom_toBottomOf="@id/iv_charge_station"
            app:layout_constraintEnd_toStartOf="@id/tv_select_charge_operator"
            app:layout_constraintTop_toTopOf="@id/iv_charge_station" />

        <TextView
            android:id="@+id/tv_select_charge_operator"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="16dp"
            android:layout_marginEnd="@dimen/dp_24"
            android:drawablePadding="@dimen/dp_4"
            android:gravity="center"
            android:text="select"
            android:textColor="@color/text_700"
            android:textSize="@dimen/sp_12"
            app:drawableEndCompat="@drawable/ic_arrow_down"
            app:layout_constraintBottom_toBottomOf="@id/iv_charge_station"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toTopOf="@id/iv_charge_station" />


        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/rv_charge_station"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginHorizontal="@dimen/dp_24"
            android:layout_marginVertical="@dimen/dp_20"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintTop_toBottomOf="@id/tv_charge_station_info"
            app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
            tools:itemCount="3"
            tools:listitem="@layout/item_charge_station" />
    </androidx.constraintlayout.widget.ConstraintLayout>

    </androidx.coordinatorlayout.widget.CoordinatorLayout>

还有style.xml文件

 <style name="ChargeMapDialog" parent="android:Theme.Dialog">
        <item name="android:windowFrame">@null</item>
        <item name="android:windowIsFloating">true</item>
        <item name="android:windowBackground">@color/color_transparent</item>
        <item name="android:windowIsTranslucent">true</item>
        <item name="android:windowNoTitle">true</item>
        <item name="android:backgroundDimEnabled">false</item>
        <item name="android:windowCloseOnTouchOutside">false</item>
    </style>

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