Prevent BottomSheetDialogFragment covering navigation bar
Asked Answered
Y

14

73

I'm using really naive code to show a bottom sheet dialog fragment:

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

This is how I called this dialog:

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

But I get this horrible shown in the image below. How can I keep the navigation bar white (the bottom bar where the back/home software buttons are)?

App Theme I'm using:

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

I've also tried to override the setupDialog instead of the onCreateView, but still happens:

    @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)
}
Yurev answered 29/11, 2017 at 13:25 Comment(14)
Are you sure that it is attached to your CoordinatorLayout ?Zena
How do you show the fragment?Maladapted
@Zena yes the activity root is Coordinator layout, though i doubt if it has any effect on this issue as this is a dialog fragment.Yurev
@KalaBalik updated my questionYurev
Can you share styles.xml, specifically the theme of the Activity?Medius
Please show R.layout.view_image_source_chooser.Maladapted
@KalaBalik change it, doesn't matter for this issueYurev
@Medius Yes, I think this is the road to take. edited my answerYurev
What device, OS version do you use ? Are you using any app for system nav bar customization, etc. ?Ethmoid
@PrzemysławPiechota.kibao It happens on plenty of devices, Lollipop+. Nothing other than what i've shared hereYurev
@Yurev I've tested a sample code on Nexus 5X and the navigation bar is not greyed. Differently from you, I created a class which extends BottomSheetDialogFragment, and overrided setupDialog method.Zena
mm setupDialog is internal to support lib, see the IDE warning (error).Yurev
I know that's this could be outrageous, but I added a suppress lint warning. Maybe you can also open an issue on GitHub page for more info.Zena
@Zena I tried the setupDialog method, still the same (checked on samsung s8). I added the attempt's code to the questionYurev
C
61

I had the same problem and I finally found a solution which is not hacky or needs an exorbitant amount of code.

This Method replaced the window background with a LayerDrawable which consists of two elements: the background dim and the navigation bar background.

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

The method "setLayerInsetTop" requires the API 23 but that's fine because dark navigation bar icons were introduced in Android O (API 26).

So the last part of the solution is to call this method from your bottom sheets onCreate method like this.

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

I hope it helps and please let me know if you find a device or case in which this solution does not work.

before and after

Crevasse answered 13/7, 2018 at 16:3 Comment(5)
Hey may be its not context but can you tell me how do round corners for BottomSheetDialogChader
It is pretty simple: you have to override the bottomSheetTheme in your App theme and in your bottomSheetTheme the bottomSheetStyle. In your bottomSheetStyle you can now set the background to a drawable with a rectangle shape and corners. That's it.Crevasse
This is the most clean answer. In addition, if you just want to use the activity's navigation bar color, you can just use navigationBarDrawable.setColor(activity.getWindow().getNavigationBarColor()).Hornswoggle
This is the best answer, it also allows you to seamlessly show a bottom sheet and maintain your theme for the navigation bar not dimming it :)Feculent
In your answer you say "API 26" but then in code you use API 27. Why is that?Crenel
A
53

I know there is many solutions here already but all of them seems too much to me, so I found this very simple solution here, credit goes to Arthur Nagy:

just override the getTheme method in the BottomSheetDialogFragment:

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

and in 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>

You can also add support for night mode by changing the color @color/bottom_sheet_bg in the values-night assets folder

Absalom answered 4/9, 2019 at 14:39 Comment(4)
<item name="android:windowIsFloating">false</item> fixed the issueIllhumored
Thank bro, you saved my day.Nalor
You are amazing! Months looking for this answer... thanks a lot!Aegina
@Yurev I suppose this answer should be accepted. This is an only a styling issueLoos
T
13

Answer from j2esu works pretty well. However if you insist on 'completely white' navigation bar you have to omit part of it.

Please note that this solution is applicable from Android O (API 26) since dark navigation bar icons were introduced in this version. On older versions you would get white icons on white background.

You need to:

  1. Add android:fitsSystemWindows="true" to root of your dialog layout.
  2. Modify Window of your Dialog properly.

Place this code to onStart of your child of BottomSheetDialogFragment. If you are using design library instead of material library use 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);
    }
}

Result might look like this:

White navigation bar on Android P in dialog

Tlingit answered 30/6, 2018 at 22:30 Comment(0)
N
12

In BottomSheetDialogFragment, the only thing that needs to be done is to set the container of the underlying CoordinatorLayout fitSystemWindows to false.

override fun onActivityCreated(savedInstanceState: Bundle?) {
    super.onActivityCreated(savedInstanceState)
    (view!!.parent.parent.parent as View).fitsSystemWindows = false
}
  • view is your layout
  • view.parent is a FrameLayout containing your view
  • view.parent.parent is the CoordinatorLayout
  • view.parent.parent.parent is the container for CoordinatorLayout which has its fitsSystemWindow set to true by default.

This ensures that the whole BottomSheetDialogFragment is drawn underneath the navigation bar. Then you can set the fitsSystemWindows to your own containers accordingly.

What you don't need from the other answers in particular is:

  • hacky findViewById with reference to system ids, which are subject to change,
  • reference to getWindow() or getDialog(),
  • no drawables to be set in the place of navigation bar.

This solution works with BottomSheetDialogFragment created with onCreateView, I did not check onCreateDialog.

Neville answered 11/11, 2018 at 14:48 Comment(2)
fitsSystemWindows makes dialog stick to the bottom of the screen, which makes navbar to be drawn on top of it, not quite what was expectedConglomeration
You need to add the margin to the dialog to account for that (adding fitssystemwindows to the container that needs to go above the navbar as mentioned in the answer). If you are drawing the navbar on top of your app, you become responsible for what is drawn underneath.Bonds
A
10

There is a way to avoid changes in Java/Kotlin code, the issue can be fully resolved in XML nowadays:

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

I also had an issue with my theme/style not being applied to the views inside BottomSheetDialogFragment, here's what I did to fix that in my base BottomSheetDialogFragment:

override fun onGetLayoutInflater(savedInstanceState: Bundle?): LayoutInflater {
    val inflater = super.onGetLayoutInflater(savedInstanceState)
    val wrappedContext = ContextThemeWrapper(requireContext(), R.style.My_Theme)
    return inflater.cloneInContext(wrappedContext)
}
Accommodative answered 29/4, 2020 at 11:31 Comment(1)
Thank you! Using a dark theme, for me ?colorSurface for the navigation bar color was necessary. And also using <item name="elevationOverlayEnabled">false</item> to prevent the dialog background from looking paler than the navigation bar.Aerobatics
F
9

No code required! Using 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>
Farah answered 9/10, 2020 at 22:1 Comment(1)
It seems to be the most correct answer, according to the documentation on material.io. However, I can't seem to make it work... Nothing changes: neither the navigation bar color nor the style of the bottom sheet widget. Any hints of what might I be doing wrong? Thanks!Sunnysunproof
T
3

I just add <item name="android:windowIsFloating">false</item> in the BottomSheetDialog section of style.xml, and then the Navigation bar won't dim when BottomSheetDialog opens.

Tref answered 24/12, 2020 at 18:9 Comment(0)
C
3

For don't override other styles like background, button styles and text styles, need to use 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>
Cardholder answered 1/6, 2021 at 8:45 Comment(0)
S
1

BottomSheetDialogFragment extends DialogFragment. Inside BottomSheetDialog it's creating a Dialog inside onCreateDialog

public class BottomSheetDialogFragment extends AppCompatDialogFragment {

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

}

The dim layer is a property of dialog which is applying to whole window. Then only it will cover the status bar. If you need dim layer without bottom buttons, then you have to do manually by showing a layer inside layout and changing status bar colour accordingly.

Apply theme for dialogfragment as given below

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
    }


}

With styles as

 <style name="dialog" parent="Base.Theme.AppCompat.Dialog">
        <item name="android:windowBackground">@android:color/transparent</item>
        <item name="android:backgroundDimEnabled">false</item>
</style>
Saddlery answered 10/12, 2017 at 8:4 Comment(2)
...then you have to do manually by showing a layer inside layout and changing status bar colour accordingly How? Isn't that the question? Your answer looks incomplete.Medius
@Medius Check whether that dim is gone in your phone by using style specified in answer. I don't have that kind of phone to test withSaddlery
S
0

Use follow API to setContentView instead of overriding onCreateView.

        val dialog = BottomSheetDialog(context)
        dialog.setContentView(R.layout.your_layout)

BottomSheetDialog.setContentView will setup the correct behavior for BottomSheetDialog. You can see the source code:

       public void setContentView(@LayoutRes int layoutResId) {
              super.setContentView(this.wrapInBottomSheet(layoutResId, (View)null, (LayoutParams)null));
       }

       private View wrapInBottomSheet(int layoutResId, View view, LayoutParams params) {
               FrameLayout container = (FrameLayout)View.inflate(this.getContext(), layout.design_bottom_sheet_dialog, (ViewGroup)null);
               CoordinatorLayout coordinator = (CoordinatorLayout)container.findViewById(id.coordinator);
               if (layoutResId != 0 && view == null) {
                  view = this.getLayoutInflater().inflate(layoutResId, coordinator, false);
               }
               // ... more stuff
       }
Samadhi answered 23/8, 2019 at 1:45 Comment(0)
W
0

Just follow the Material Design guide, you could theme your bottom sheet and get the desired result with some changes.

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

In 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>
Winnifredwinning answered 30/3, 2022 at 14:44 Comment(0)
S
0

After spending some time researching, here is my solution.

class AboutDialog : BottomSheetDialogFragment() {

private var _binding: DialogAboutBinding? = null
private val binding get() = _binding

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
    _binding = DialogAboutBinding.inflate(inflater, container, false)
    return binding!!.root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    val color = Color.WHITE
    val isLite = true
    dialog?.window?.run {
        navigationBarColor = color
        WindowCompat.getInsetsController(this, this.decorView).isAppearanceLightNavigationBars = isLite
    }
}

override fun onDestroyView() {
    super.onDestroyView()
    _binding = null
}

}

enter image description here

enter image description here

Soybean answered 8/2, 2023 at 8:50 Comment(0)
C
-2

I had the same problem. After looking into sources I found a workaround (a little bit hacky, but I found no alternatives).

public class YourDialog extends BottomSheetDialogFragment {

    //your code

    @NonNull
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        return new FitSystemWindowsBottomSheetDialog(getContext());
    }
}

public class FitSystemWindowsBottomSheetDialog extends BottomSheetDialog {

    public FitSystemWindowsBottomSheetDialog(Context context) {
        super(context);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getWindow() != null && Build.VERSION.SDK_INT >= 21) {
            findViewById(android.support.design.R.id.coordinator).setFitsSystemWindows(false);
            findViewById(android.support.design.R.id.container).setFitsSystemWindows(false);
            getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS |
                    WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
            getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
        }
    }
}

And, finally, don't forget to add android:fitsSystemWindows="true" at the root of your dialog layout.

Hope it helps.

Crosspiece answered 9/4, 2018 at 14:26 Comment(0)
R
-4

Don't use BottomSheetDialogFragment.I would prefer use adding bottom sheet by wrapping the layout in the coordinator layout and attaching BottomSheetBehaiviour to that layout

You can follow this as an example

Rheum answered 9/12, 2017 at 10:36 Comment(2)
I would like to have the dim effect without writing this code myself. This will be my last resort if I will not find a fix for this issueYurev
It is not possible because opening a dialog pauses the current activity so you won't be able have any interaction with the activityRheum

© 2022 - 2024 — McMap. All rights reserved.