防止BottomSheetDialogFragment覆盖导航栏

72

我正在使用非常朴素的代码来展示一个底部弹出式对话框片段:

class LogoutBottomSheetFragment : BottomSheetDialogFragment() {

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        val view = inflater.inflate(R.layout.view_image_source_chooser, container, false)
        return view
    }
}

这是我称呼此对话框的方式:

LogoutBottomSheetFragment().show(supportFragmentManager, "logout")

但是我得到的效果很糟糕,如下图所示。如何保持导航栏为白色(底部带有后退/主页软件按钮的栏)?

我正在使用的应用主题:

 <!-- Base application theme. -->
<style name="BaseAppTheme" parent="Theme.AppCompat.Light.NoActionBar">
    <!-- Customize your theme here. -->
</style

<style name="AppTheme" parent="BaseAppTheme">
    <item name="android:windowNoTitle">true</item>
    <item name="windowActionBar">false</item>

    <!-- Main theme colors -->
    <!--   your app branding color for the app bar -->
    <item name="android:colorPrimary">@color/colorPrimary</item>
    <!--   darker variant for the status bar and contextual app bars -->
    <item name="android:colorPrimaryDark">@android:color/white</item>
    <!--   theme UI controls like checkboxes and text fields -->
    <item name="android:colorAccent">@color/charcoal_grey</item>

    <item name="colorControlNormal">@color/charcoal_grey</item>
    <item name="colorControlActivated">@color/charcoal_grey</item>
    <item name="colorControlHighlight">@color/charcoal_grey</item>

    <item name="android:textColorPrimary">@color/charcoal_grey</item>
    <item name="android:textColor">@color/charcoal_grey</item>

    <item name="android:windowBackground">@color/white</item>
</style>

我还尝试重写 setupDialog 方法而不是 onCreateView,但仍然发生相同的问题:

    @SuppressLint("RestrictedApi")
override fun setupDialog(dialog: Dialog, style: Int) {
    super.setupDialog(dialog, style)
    val view = View.inflate(context, R.layout. view_image_source_chooser,null)
    dialog.setContentView(view)
}

请显示 R.layout.view_image_source_chooser - kalabalik
@azizbekian 是的,我认为这是正确的方向。我编辑了我的回答。 - oferiko
你使用什么设备和操作系统版本?你是否使用任何应用程序来自定义系统导航栏等? - Przemysław Piechota. kibao
@oferiko 我在_Nexus 5X_上测试了一段示例代码,导航栏没有变灰。与您不同的是,我创建了一个扩展BottomSheetDialogFragment类的类,并覆盖了_setupDialog_方法。 - JJ86
mm setupDialog 是支持库的内部函数,请查看 IDE 警告(错误)。 - oferiko
显示剩余9条评论
14个回答

61

我曾经遇到了同样的问题,最终我找到了一种既不需要hacky也不需要大量代码的解决方案。

这种方法用一个LayerDrawable替换了窗口背景,其中包括两个元素:背景模糊和导航栏背景。

@RequiresApi(api = Build.VERSION_CODES.M)
private void setWhiteNavigationBar(@NonNull Dialog dialog) {
    Window window = dialog.getWindow();
    if (window != null) {
        DisplayMetrics metrics = new DisplayMetrics();
        window.getWindowManager().getDefaultDisplay().getMetrics(metrics);

        GradientDrawable dimDrawable = new GradientDrawable();
        // ...customize your dim effect here

        GradientDrawable navigationBarDrawable = new GradientDrawable();
        navigationBarDrawable.setShape(GradientDrawable.RECTANGLE);
        navigationBarDrawable.setColor(Color.WHITE);

        Drawable[] layers = {dimDrawable, navigationBarDrawable};

        LayerDrawable windowBackground = new LayerDrawable(layers);
        windowBackground.setLayerInsetTop(1, metrics.heightPixels);

        window.setBackgroundDrawable(windowBackground);
    }
}

方法“setLayerInsetTop”需要API 23,但这没关系,因为在Android O(API 26)中引入了深色导航栏图标。

所以解决方案的最后一步是从您的底部表单的onCreate方法中调用此方法,就像这样。

@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
    Dialog dialog = super.onCreateDialog(savedInstanceState);

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
        setWhiteNavigationBar(dialog);
    }

    return dialog;
}

我希望这能帮到您,如果您找到一种设备或外壳,该解决方案无法使用,请告诉我。

变化前后对比图


1
嘿,也许这不是上下文,但你能告诉我如何为BottomSheetDialog设置圆角吗? - Hemanth S
2
很简单:您需要在应用程序主题中覆盖bottomSheetTheme,并在bottomSheetTheme中覆盖bottomSheetStyle。在bottomSheetStyle中,您现在可以将背景设置为具有矩形形状和圆角的可绘制对象。就是这样。 - Denis Schura
4
这是最清晰的答案。此外,如果你只想使用活动的导航栏颜色,你可以直接使用navigationBarDrawable.setColor(activity.getWindow().getNavigationBarColor())。 - dknchris
这是最好的答案,它还允许您无缝地显示底部表格并保持导航栏的主题不会变暗 :) - Muhammad Alfaifi
在你的回答中,你说“API 26”,但是在代码中你使用了API 27。为什么会这样? - arekolek

49

我知道这里已经有很多解决方案,但它们对我来说似乎都太过繁琐了,所以我找到了这个非常简单的解决方案(这里),感谢Arthur Nagy

只需在BottomSheetDialogFragment中覆盖getTheme方法即可:

override fun getTheme(): Int  = R.style.Theme_NoWiredStrapInNavigationBar

并在styles.xml中:

<style name="Theme.NoWiredStrapInNavigationBar" parent="@style/Theme.Design.BottomSheetDialog">
    <item name="android:windowIsFloating">false</item>
    <item name="android:navigationBarColor">@color/bottom_sheet_bg</item>
    <item name="android:statusBarColor">@android:color/transparent</item>
</style>

您也可以通过在values-night资产文件夹中更改颜色@color/bottom_sheet_bg来添加对夜间模式的支持。


12
<item name="android:windowIsFloating">false</item> 修复了该问题。 - NickUnuchek
1
谢谢兄弟,你救了我的一天。 - Na Pro
1
你太棒了!找了好几个月才找到这个答案...非常感谢! - Tomas M
1
@oferiko 我认为这个答案应该被接受。这只是一个样式问题。 - Taxist Samael

13

j2esu的回答效果很好。但是,如果您坚持使用“完全白色”的导航栏,则必须省略其中的一部分。

请注意,此解决方案适用于Android O(API 26)及更高版本,因为在此版本中引入了深色导航栏图标。在旧版本上,您将在白色背景上获得白色图标。

您需要:

  1. android:fitsSystemWindows="true"添加到对话框布局的根目录中。
  2. 正确修改您的DialogWindow

将此代码放置在BottomSheetDialogFragment子类的onStart中。如果您正在使用设计库而不是材料库,则使用android.support.design.R.id.container

@Override
public void onStart() {
    super.onStart();
    if (getDialog() != null && getDialog().getWindow() != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        Window window = getDialog().getWindow();
        window.findViewById(com.google.android.material.R.id.container).setFitsSystemWindows(false);
        // dark navigation bar icons
        View decorView = window.getDecorView();
        decorView.setSystemUiVisibility(decorView.getSystemUiVisibility() | View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
    }
}

结果可能看起来像这样:

在 Android P 中对话框中的白色导航栏


12

BottomSheetDialogFragment中,唯一需要做的就是将底层CoordinatorLayout的容器的fitSystemWindows设置为false

override fun onActivityCreated(savedInstanceState: Bundle?) {
    super.onActivityCreated(savedInstanceState)
    (view!!.parent.parent.parent as View).fitsSystemWindows = false
}
  • view是您的布局
  • view.parent是包含您的视图的FrameLayout
  • view.parent.parentCoordinatorLayout
  • view.parent.parent.parentCoordinatorLayout的容器,默认情况下其fitsSystemWindow设置为true

这确保了整个BottomSheetDialogFragment在导航栏下方绘制。然后,您可以相应地将fitsSystemWindows设置为自己的容器。

您不需要从其他答案中获取以下内容:

  • 使用系统ID引用进行hacky findViewById,这些ID可能会更改,
  • 引用getWindow()getDialog()
  • 不需要设置可绘制物来代替导航栏。

此解决方案适用于使用onCreateView创建的BottomSheetDialogFragment,我没有检查onCreateDialog


fitsSystemWindows使对话框固定在屏幕底部,这使得导航栏绘制在其上方,不完全符合预期。 - Wackaloon
你需要为对话框添加边距来解决这个问题(根据答案中提到的,在需要放置在导航栏上方的容器中添加fitssystemwindows)。如果你将导航栏绘制在应用程序顶部,那么你就要负责在其下面绘制的内容。 - Michał Klimczak

10

现在有一种避免在Java/Kotlin代码中进行更改的方法,这个问题可以完全通过XML解决:

<style name="MyTheme" parent="Theme.MaterialComponents">
    <item name="bottomSheetDialogTheme">@style/BottomSheet</item>
</style>

<style name="BottomSheet" parent="Theme.MaterialComponents.Light.BottomSheetDialog">
    <item name="android:windowIsFloating">false</item>
    <item name="android:statusBarColor">@android:color/transparent</item>         
    <item name="android:navigationBarColor">?android:colorBackground</item>
    <item name="android:navigationBarDividerColor">?android:colorBackground</item>
</style>

我也遇到了一个问题,就是我的主题/样式没有应用到 BottomSheetDialogFragment 中的视图中,下面是我在基础的 BottomSheetDialogFragment 中进行修复的方法:

override fun onGetLayoutInflater(savedInstanceState: Bundle?): LayoutInflater {
    val inflater = super.onGetLayoutInflater(savedInstanceState)
    val wrappedContext = ContextThemeWrapper(requireContext(), R.style.My_Theme)
    return inflater.cloneInContext(wrappedContext)
}

谢谢!对于我来说,使用暗色主题,导航栏颜色的 ?colorSurface 是必要的。并且使用 <item name="elevationOverlayEnabled">false</item> 来防止对话框背景看起来比导航栏苍白。 - Tenfour04

9

无需编写代码!使用Material Components:

<style name="Theme.YourApp" parent="Theme.MaterialComponents.DayNight.NoActionBar">
    ...
    <item name="bottomSheetDialogTheme">@style/ThemeOverlay.YourApp.BottomSheetDialog</item>
</style>

<style name="Widget.YourApp.BottomSheet" parent="Widget.MaterialComponents.BottomSheet"/>

<style name="ThemeOverlay.YourApp.BottomSheetDialog" parent="@style/ThemeOverlay.MaterialComponents.BottomSheetDialog">
    <item name="bottomSheetStyle">@style/Widget.YourApp.BottomSheet</item>
    <item name="android:windowIsFloating">false</item>
    <item name="android:statusBarColor">@android:color/transparent</item>
    <item name="android:navigationBarColor">?colorSurface</item>
</style>

3
根据material.io上的文档,这似乎是最正确的答案。然而,我似乎无法使其工作...什么都没有改变:导航栏颜色和底部表格小部件的样式都没有改变。有什么提示可以告诉我哪里做错了吗?谢谢! - necavit

3

为了不覆盖其他样式,如背景、按钮和文本样式,需要使用ThemeOverlay。

<style name="AppTheme" parent="Theme.MaterialComponents.Light.NoActionBar">
    ...
    <item name="bottomSheetDialogTheme">@style/ThemeOverlay.AppTheme.BottomSheetDialog</item>
</style>

<style name="ThemeOverlay.AppTheme.BottomSheetDialog" parent="ThemeOverlay.MaterialComponents.BottomSheetDialog">
    <item name="android:windowIsFloating">false</item>
    <item name="android:windowLightNavigationBar">true</item>
    <item name="android:navigationBarColor">#FFFFFF</item>
</style>

3
我只需在style.xml文件的BottomSheetDialog部分添加代码<item name="android:windowIsFloating">false</item>,就能够使导航栏在BottomSheetDialog打开时不会变暗。请注意保留html标签。

1

BottomSheetDialogFragment 扩展了 DialogFragment。在 BottomSheetDialog 中,它在 onCreateDialog 中创建一个对话框。

public class BottomSheetDialogFragment extends AppCompatDialogFragment {

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        return new BottomSheetDialog(getContext(), getTheme());
    }

}

暗层是对话框的一个属性,应用于整个窗口。这样才能覆盖状态栏。如果您需要没有底部按钮的暗层,则必须手动显示布局内的层,并相应地更改状态栏颜色。

按照下面给出的方式为dialogfragment应用主题

class LogoutBottomSheetFragment : BottomSheetDialogFragment() {
    init {
        setStyle(DialogFragment.STYLE_NORMAL,R.style.dialog);
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        val view = inflater.inflate(R.layout.view_image_source_chooser, container, false)
        return view
    }


}

样式为

 <style name="dialog" parent="Base.Theme.AppCompat.Dialog">
        <item name="android:windowBackground">@android:color/transparent</item>
        <item name="android:backgroundDimEnabled">false</item>
</style>

如果要这样做,您必须手动显示布局内的一个层,并相应地更改状态栏颜色。如何操作?这不是问题吗?您的答案似乎不完整。 - azizbekian
@azizbekian 请使用答案中指定的样式检查您的手机是否已经消失了那个维度。我没有那种手机可以测试。 - Sangeet Suresh

0

只要按照Material Design指南,你可以通过一些改变主题来使你的底部菜单表现出所需的效果。

在res/values/themes.xml中:

<style name="Theme.App" parent="Theme.MaterialComponents.*">
  ...
  <item name="bottomSheetDialogTheme">@style/ThemeOverlay.App.BottomSheetDialog</item>
</style>

<style name="ThemeOverlay.App.BottomSheetDialog" parent="ThemeOverlay.MaterialComponents.BottomSheetDialog">
    <item name="bottomSheetStyle">@style/ModalBottomSheetDialog</item>
</style>

在res/values/styles.xml文件中:
<style name="ModalBottomSheetDialog" parent="Widget.MaterialComponents.BottomSheet.Modal">
    <item name="backgroundTint">@color/shrine_pink_light</item>
    <item name="shapeAppearance">@style/ShapeAppearance.App.LargeComponent</item>
</style>

<style name="ShapeAppearance.App.LargeComponent" parent="ShapeAppearance.MaterialComponents.LargeComponent">
    <item name="cornerFamily">rounded</item>
    <item name="cornerSize">5dp</item>
</style>

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