DialogFragment 全屏显示适配指南
在开发中,我们可能需要用到Dialog全屏展示的场景,这并没有想象中的那么容易,需要考虑:
- 顶部状态栏
- 底部导航栏
- 刘海屏、挖孔屏
期间也在Google搜过一些方案,但是不够系统,且均有瑕疵,本文将依据Android官方文档和代码,逐步探索出可信赖的全屏展示方案。
1. Full-screen dialog guideline
m3.material.io/components/… 中举了一下日历例子,效果如下:
下一步就是找到相关的代码,底部有一个入口:
最终对应的github链接是:github.com/material-co…
github里面并没有上述例子,但是有类似的case:
我们可以根据material-components的源码实现,来探索全屏展示的方案。
2. 全屏展示Theme配置
1. MaterialDatePicker theme配置
// https://github.com/material-components/material-components-android/blob/master/lib/java/com/google/android/material/datepicker/res/values/themes.xml
<style name="ThemeOverlay.Material3.MaterialCalendar.Fullscreen">
<item name="android:windowIsFloating">false</item>
<item name="android:windowElevation" tools:ignore="NewApi">@dimen/m3_datepicker_elevation</item>
<item name="materialCalendarStyle">@style/Widget.Material3.MaterialCalendar.Fullscreen</item>
<item name="materialCalendarHeaderLayout">@style/Widget.Material3.MaterialCalendar.HeaderLayout.Fullscreen</item>
<item name="materialCalendarHeaderSelection">@style/Widget.Material3.MaterialCalendar.HeaderSelection.Fullscreen</item>
</style>
不难发现,关键设置项为<item name="android:windowIsFloating">false</item>。
2. Demo全屏效果
1. theme设置
<style name="MyFullScreenDialog" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:windowIsFloating">false</item>
</style>
2. dialog 代码
class MyFullScreenDialog : DialogFragment() {
...
override fun getTheme(): Int {
return R.style.MyFullScreenDialog
}
}
3. 内容延伸到状态栏和导航栏
3.1 MaterialDatePicker代码设置
// https://github.com/material-components/material-components-android/blob/master/lib/java/com/google/android/material/datepicker/MaterialDatePicker.java
public final class MaterialDatePicker<S> extends DialogFragment {
...
@Override
public void onStart() {
super.onStart();
Window window = requireDialog().getWindow();
// Dialogs use a background with an InsetDrawable by default, so we have to replace it.
if (fullscreen) {
window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
window.setBackgroundDrawable(background);
enableEdgeToEdgeIfNeeded(window);
}
...
}
private void enableEdgeToEdgeIfNeeded(Window window) {
if (edgeToEdgeEnabled) {
// Avoid enabling edge-to-edge multiple times.
return;
}
final View headerLayout = requireView().findViewById(R.id.fullscreen_header);
// 重点
EdgeToEdgeUtils.applyEdgeToEdge(window, true, ViewUtils.getBackgroundColor(headerLayout), null);
...
}
}
这里面的edge-to-edge是指将内容显示区域延伸至状态栏和导航栏,官方介绍:developer.android.com/develop/ui/…
You can make your app display edge-to-edge—using the entire width and height of the display—by drawing behind the system bars. The system bars are the status bar and the navigation bar.
关于设置状态栏&导航栏颜色,查看edge-to-edge或者EdgeToEdgeUtils.applyEdgeToEdge源码即可。
3.2 Demo全屏效果(二)
WindowCompat.setDecorFitsSystemWindows(window, false) // 设置全屏
val windowInsetsController = WindowCompat.getInsetsController(window, window.decorView)
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars()) // 隐藏system bars
有没有注意到顶部的白条没,这个是因为左上角有个摄像头
4. 适配挖孔屏、刘海屏
参考官方文档:developer.android.com/develop/ui/…
Before attempting to render your app into the cutout area, ensure your app is configured to display content edge to edge.
这个edge to edge.我们前面已经设置了,先在只需要配置:android:windowLayoutInDisplayCutoutMode即可。
5. 完整代码
5.1 完整theme配置
<style name="MyFullScreenDialog" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:windowIsFloating">false</item>
<item name="android:windowLayoutInDisplayCutoutMode" tools:ignore="NewApi">shortEdges</item>
<!--<item name="android:windowFullscreen">true</item>-->
<item name="android:statusBarColor">@android:color/transparent</item>
</style>
5.2 完整dialog 代码
class MyFullScreenDialog : DialogFragment() {
...
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return super.onCreateDialog(savedInstanceState).also {
it.window?.let { window ->
WindowCompat.setDecorFitsSystemWindows(window, false) // 设置全屏
val windowInsetsController = WindowCompat.getInsetsController(window, window.decorView)
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars()) // 隐藏system bars
}
}
}
override fun getTheme(): Int {
return R.style.MyFullScreenDialog
}
}
6. 总结
本文以官方MaterialDatePicker组件作为起点,逐步探索出Dialog全屏展示的方案,能够适配状态栏、导航栏、刘海屏,上述内容均有官方文档作为依据,理论上值得信赖。