Запретить BottomSheetDialogFragment, закрывающий панель навигации

Я использую действительно наивный код, чтобы показать фрагмент диалога нижнего листа:

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

14 ответов

У меня была та же проблема, и я наконец нашел решение, которое не является хакерским или требует большого количества кода.

Этот метод заменил фон окна на 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;
}

Я надеюсь, что это поможет, и, пожалуйста, дайте мне знать, если вы найдете устройство или случай, в котором это решение не работает.

до и после

Я знаю, что есть много решений здесь уже, но все они, кажется, слишком много для меня, так что я нашел это очень простое решение здесь, заслуга Артура Надь:

просто переопределите метод getTheme в BottomSheetDialogFragment:

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>

Кабум, вот и все.

вы также можете добавить поддержку ночного режима, изменив цвет @color/bottom_sheet_bg в values-night папка с ресурсами

Ответ от j2esu работает довольно хорошо. Однако, если вы настаиваете на "полностью белой" панели навигации, вы должны пропустить ее часть.

Обратите внимание, что это решение применимо для Android O (API 26), поскольку в этой версии были представлены значки темной панели навигации. В старых версиях вы получите белые иконки на белом фоне.

Вам нужно:

  1. добавлять android:fitsSystemWindows="true" в корне вашего диалогового макета.
  2. изменять Window вашей Dialog должным образом.

Поместите этот код в onStart вашего ребенка BottomSheetDialogFragment, Если вы используете библиотеку дизайна вместо библиотеки материалов, используйте 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 в диалоге

В BottomSheetDialogFragmentединственное, что нужно сделать, это установить контейнер CoordinatorLayoutfitSystemWindows в false,

override fun onActivityCreated(savedInstanceState: Bundle?) {
    super.onActivityCreated(savedInstanceState)
    (view!!.parent.parent.parent as View).fitsSystemWindows = false
}
  • view это ваш макет
  • view.parent такое FrameLayout, содержащий ваше представление
  • view.parent.parent это CoordinatorLayout
  • view.parent.parent.parent это контейнер для CoordinatorLayout который имеет свой fitsSystemWindow установлен в true по умолчанию.

Это гарантирует, что весь BottomSheetDialogFragment рисуется под панелью навигации. Тогда вы можете установить fitsSystemWindows к вашим собственным контейнерам соответственно.

Что вам не нужно из других ответов, в частности:

  • хакерский findViewById со ссылкой на системные идентификаторы, которые могут быть изменены,
  • ссылка на getWindow() или же getDialog(),
  • на панели навигации не должно быть никаких рисунков.

Это решение работает с BottomSheetDialogFragment создан с onCreateViewЯ не проверял onCreateDialog,

Есть способ избежать изменений в коде 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)
}

Код не требуется! Использование компонентов материала:

<style name="Theme.YourApp" parent="Theme.MaterialComponents.DayNight.NoActionBar">
    ...
    <item name="bottomSheetDialogTheme">@style/ThemeOverlay.Planner.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.Planner.BottomSheet</item>
    <item name="android:windowIsFloating">false</item>
    <item name="android:statusBarColor">@android:color/transparent</item>
    <item name="android:navigationBarColor">?colorSurface</item>
</style>

Чтобы не переопределять другие стили, такие как фон, стили кнопок и стили текста, необходимо использовать 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>

Я просто добавляю <item name="android:windowIsFloating">false</item> в разделе style.xml, а затем панель навигации не будет тускнеть, когда BottomSheetDialog открывается.

BottomSheetDialogFragment продолжается DialogFragment, Внутри BottomSheetDialog он создает диалог внутри onCreateDialog

public class BottomSheetDialogFragment extends AppCompatDialogFragment {

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

}

Слой dim - это свойство диалога, которое применяется ко всему окну. Только тогда он будет покрывать строку состояния. Если вам нужен тусклый слой без нижних кнопок, то вам нужно сделать это вручную, показав слой внутри макета и изменив соответствующим образом цвет строки состояния.

Примените тему для 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>

Используйте следующий API для setContentView вместо переопределения onCreateView.

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

BottomSheetDialog.setContentView настроит правильное поведение для BottomSheetDialog. Вы можете увидеть исходный код:

       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
       }

Просто следуйте руководству по дизайну материалов , вы можете создать тему для нижнего листа и получить желаемый результат с некоторыми изменениями.

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

Потратив некоторое время на исследования, вот мое решение.

      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
}

}

У меня такая же проблема. Изучив источники, я нашел обходной путь (немного странный, но не нашел альтернативы).

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

И, наконец, не забудьте добавить android:fitsSystemWindows="true" в корень вашего диалогового окна.

Надеюсь, поможет.

Не использовать BottomSheetDialogFragment.Я бы предпочел использовать добавление нижнего листа путем обёртывания макета в макет координатора и присоединения BottomSheetBehaiviour к этому макету.

Вы можете последовать этому примеру

Другие вопросы по тегам