文章目录
- 一、沉浸式状态栏
- 1.1 Android 4.4(API 19)- Android 5.0( API 21)
- 1.2 Android 5.0(API 21)以上版本
- 1.3 Android 6.0(API 23)以上版本
- 二、全屏模式
- 2.1 向后倾斜
- 2.2 沉浸模式
- 2.3 粘性沉浸模式
- 2.4 状态栏和导航栏的隐藏
- 2.5 view.setFitsSystemWindows()方法
- 2.6 API 30 及以后的方式
- 三、刘海屏
- 3.1 刘海屏的要求
- 3.2 刘海模式
- 3.2.1 默认行为
- 3.2.2 将内容呈现在短边刘海屏区域中
- 3.2.3 从不将内容呈现在刘海区域中
- 四、总结
在做项目的时候图片全屏预览时发现使用全面屏后,图片会闪动一下(
总结部分有答案),带着问题来梳理一下Android 全面屏、沉浸式状态栏和刘海屏的概念和方法。
普通的手机:
刘海屏的手机:
一、沉浸式状态栏
沉浸式状态栏就是ContentView中的内容占用系统状态栏的空间,状态栏和导航栏依然可见。
沉浸式状态栏对 Android 版本要求不一样,所以我们需要通过不同版本来进行判定区分,在 Android 4.4 一下,可以对 StatusBar 和 NavigationBar 进行显示和隐藏操作。直到 Android 4.4 ,才真正实现沉浸式状态栏。从 Android 4.4 (API 19)到 Android 12 (API 31),可以分为3个阶段:
1.1 Android 4.4(API 19)- Android 5.0( API 21)
这个阶段实现沉浸式是通过 WindowManager 的 FLAG_TRANSLUCENT_STATUS
,这个窗口标志:允许窗口内容扩展到屏幕的顶部区域。
这种情况也是分两种情况:
- 其一: 允许窗口内容扩展到屏幕的顶部区域,只需要设置
FLAG_TRANSLUCENT_STATUS
标志; - 其二: 如果不想让内容窗口扩展到系统状态栏上,也就是说不让系统状态栏遮挡住内容。就设置
FLAG_TRANSLUCENT_STATUS
标志而且添加一个与状态栏一样大小的 View ,将 View 的 background 设置我们需要的颜色;从而来实现沉浸式。
/**
* 窗口标志:请求具有最少系统提供的背景保护的半透明状态栏。
*
* 可以通过 {@link android.R.attrwindowTranslucentStatus} 属性在您的主题中控制此标志;
* 此属性会在标准的半透明装饰主题中为您自动设置,例如
* {@link android.R.style#Theme_Holo_NoActionBar_TranslucentDecor},
* {@link android.R.style#Theme_Holo_Light_NoActionBar_TranslucentDecor},
* {@link android.R.style#Theme_DeviceDefault_NoActionBar_TranslucentDecor}, and
* {@link android.R.style#Theme_DeviceDefault_Light_NoActionBar_TranslucentDecor}.
*
* 当为窗口启用此标志时,它会自动设置系统 UI 可见性标志 view.setSystemUiVisibility(int visibility)
* {@link View.SYSTEM_UI_FLAG_LAYOUT_STABLE} 和 {@link View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN}。
*
* 使用带有半透明颜色的 {@link WindowsetStatusBarColor(int)} 代替
*/
@Deprecated
public static final int FLAG_TRANSLUCENT_STATUS = 0x04000000;
上面的注释的比较清楚,当在在 theme 中设置属性 windowTranslucentStatus 为 true 的时候生效:
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- API 19 以上生效-->
<item name="android:windowTranslucentStatus" tools:targetApi="kitkat">true</item>
</style>
当我们在代码里设置
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
还有一点值得注意的是:设置这个标志会自动调用系统的 systemUiVisibility 来设置全屏模式
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE)
效果如下:
当我们不希望ContentView 中的内容被遮挡,我们可以添加一个和状态栏高度一样的 View,来控制这个 View 的颜色,以此来实现沉浸式。
private fun addStatusViewWithColor19(activity: Activity, color: Int) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// 设置 paddingTop
val rootView = activity.window.decorView.findViewById<View>(android.R.id.content) as ViewGroup
rootView.setPadding(0, "状态栏高度", 0, 0)
// 增加占位状态栏
val decorView = activity.window.decorView as ViewGroup
val statusBarView = View(activity)
val lp = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, "状态栏高度")
statusBarView.setBackgroundColor(color)
decorView.addView(statusBarView, lp)
}
}
效果如下:
沉浸式在Android4.4 - Android5.0 之间 的状态栏顶部有个渐变,会显示黑色阴影,效果不是很好。
1.2 Android 5.0(API 21)以上版本
在Android 5.0 的时候加入了一个重要的属性和方法,android:statusBarColor (对应方法为 setStatusBarColor)
,通过设置这个属性或方法,可以实现我们想要的任何状态栏的颜色。
/**
* Sets the color of the status bar to {@code color}.
*
* For this to take effect,
* the window must be drawing the system bar backgrounds with
* {@link android.view.WindowManager.LayoutParams#FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS} and
* {@link android.view.WindowManager.LayoutParams#FLAG_TRANSLUCENT_STATUS} must not be set.
*
* If {@code color} is not opaque, consider setting
* {@link android.view.View#SYSTEM_UI_FLAG_LAYOUT_STABLE} and
* {@link android.view.View#SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN}.
* <p>
* The transitionName for the view background will be "android:status:background".
* </p>
*/
public abstract void setStatusBarColor(@ColorInt int color);
注释的意思是:想要这个方法生效,必须还要配合设置FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS
,并且不能设置FLAG_TRANSLUCENT_STATUS
(Android 4.4才用这个)
这个比 Android 4.4(API 19)- Android 5.0( API 21) 的阶段省去了自己创建 View 并给该 View 上颜色的步骤,并且去掉了黑色的渐变阴影。当需要让 ContentView 的内容占据状态栏的时候是和原来一样的直接设置 WindowManager 的 FLAG_TRANSLUCENT_STATUS
标志
也可以配合 systemUiVisibility(int) 一起使用
1.3 Android 6.0(API 23)以上版本
Android 6.0 以上的的实现方式是和 Android 5.0 一样的,但是从 6.0 开始,提供了状态栏的绘制模式,可以显示白色或黑色的内容和图标,除了部分族开放平台-状态栏变色和小米开放平台-MIUI 9 & 10“状态栏黑色字符”实现方法变更通知,Android 6.0 新添加了一个属性SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
/**
* Flag for {@link #setSystemUiVisibility(int)}: Requests the status bar to draw in a mode that
* is compatible with light status bar backgrounds.
*
* <p>For this to take effect, the window must request
* {@link android.view.WindowManager.LayoutParams#FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS
* FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS} but not
* {@link android.view.WindowManager.LayoutParams#FLAG_TRANSLUCENT_STATUS
* FLAG_TRANSLUCENT_STATUS}.
*
* @see android.R.attr#windowLightStatusBar
* @deprecated Use {@link WindowInsetsController#APPEARANCE_LIGHT_STATUS_BARS} instead.
*/
@Deprecated
public static final int SYSTEM_UI_FLAG_LIGHT_STATUS_BAR = 0x00002000;
注释的意思:可以设置 setSystemUiVisibility(int) 进行状态栏绘制模式,可以兼容亮色模式的状态栏。想要这个方法生效,必须还要配合设置FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS
,并且不能设置FLAG_TRANSLUCENT_STATUS
private fun transparentStatusBar23(activity: Activity) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
activity.window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
activity.window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
activity.window.statusBarColor = Color.TRANSPARENT
val decorView = window.decorView
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// 状态栏图标为浅色
val option = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
decorView.systemUiVisibility = option
}
}
}
设置状态栏字色和图标前:
设置状态栏字色和图标后:
综上所述沉浸式状态栏的两种模式,方法如下:
/**
* 使 contentView 中的内容占据到状态栏的沉浸式
*
*
* @param activity
*/
fun setContentAdjustToStatusBar(activity: Activity, isLight: Boolean) {
val window = activity.window
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
window.statusBarColor = Color.TRANSPARENT
// 部分手机上设置此标识无效
// window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
// or View.SYSTEM_UI_FLAG_LAYOUT_STABLE)
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
if (isLight) {
setStatusLight(activity)
}
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
}
}
/**
* 设置状态栏颜色,contentView 的内容在状态栏的下方的沉浸式
*
* @param activity
* @param color
* @param isLight
*/
fun setStatusColor(activity: Activity, color: Int, isLight: Boolean) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val window = activity.window
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
window.statusBarColor = color
if (isLight) {
setStatusLight(activity)
}
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// 设置 paddingTop
val rootView = activity.window.decorView.findViewById<View>(android.R.id.content) as ViewGroup
rootView.setPadding(0, getStatusBarHeight(activity), 0, 0)
// 增加占位状态栏
val decorView = activity.window.decorView as ViewGroup
val statusBarView = View(activity)
val lp = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getStatusBarHeight(activity))
statusBarView.setBackgroundColor(color)
statusBarView.layoutParams = lp
decorView.addView(statusBarView)
}
}
/**
* 状态栏的图标和文字颜色为暗色
*/
fun setStatusLight(activity: Activity) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val option = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
activity.window.decorView.systemUiVisibility = option
}
}
/**
* 获取状态栏高度
*
* @param activity
* @return
*/
fun getStatusBarHeight(activity: Activity): Int {
var result = 0
//获取状态栏高度的资源id
val resourceId = activity.resources.getIdentifier("status_bar_height", "dimen", "android")
if (resourceId > 0) {
result = activity.resources.getDimensionPixelSize(resourceId)
}
Log.d("getStatusBarHeight", result.toString() + "")
return result
}
二、全屏模式
Android 手机屏幕一般是有状态栏和导航栏的,当需要全屏模式的时候,需要把状态栏和导航栏都隐藏起来。
Android 提供了三个用于将应用设为全屏模式选项:向后倾斜模式、沉浸模式和粘性沉浸模式。在所有三种方法中,系统栏都是隐藏的,您的 Activity 会持续收到所有轻触事件。 它们之间的区别在于用户让系统栏重新显示出来的方式。
2.1 向后倾斜
向后倾斜模式适用于用户不会于屏幕进行大量互动的全屏体验,例如,在观看视频的时候。
当用户希望调出系统栏时,只需要点击屏幕上的任意位置即可调出系统栏。
开启向后倾斜模式,代码如下:
val window = activity.window
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_FULLSCREEN)
2.2 沉浸模式
沉浸模式使用于用户将与屏幕有大量互动的应用,例如,游戏、查看图库中的图片或分页阅读。
当用户需要调出系统栏时,可以从状态栏或者导航栏的侧边来滑动调起系统栏,其他方式不会退出全面屏。
val window = activity.window
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_IMMERSIVE
or View.SYSTEM_UI_FLAG_FULLSCREEN
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION)
2.3 粘性沉浸模式
在粘连沉浸模式下,可以从状态栏或者导航栏的侧边来滑动调起系统栏,但他们是半透明的效果浮在应用视图(ContentView内容)之上,当用户点击应用实体后无互动几秒之后,系统栏自动消失,恢复全面屏。这种模式比较适合游戏、绘图类应用。
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
or View.SYSTEM_UI_FLAG_FULLSCREEN
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION)
2.4 状态栏和导航栏的隐藏
/**
* 状态栏隐藏
*
* @param activity
*/
fun setStatusBarHide(activity: Activity) {
// 允许窗口延伸到屏幕短边上的刘海区域
// supportDisplayCutouts(activity)
val window = activity.window
window.decorView.systemUiVisibility = (
// 预留控制内容到状态栏的距离
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
// 隐藏状态栏
or View.SYSTEM_UI_FLAG_FULLSCREEN
// 将 contentView的内容延伸到状态栏。
// 设置此属性时,要考虑是否使用 fitSystemWindows 来填充状态栏的距离;
// 还要考虑刘海屏的情况,使用 layoutInDisplayCutoutMode 设置
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
)
}
/**
* 导航栏隐藏
*
* @param activity
*/
fun setNavigationBarHide(activity: Activity) {
val window = activity.window
window.decorView.systemUiVisibility = (
// 预留控制内容到导航栏的距离
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
// 隐藏导航栏
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
// 将 contentView的内容延伸到导航栏。
// 设置此属性时,要考虑是否使用 fitSystemWindows 来填充导航栏的距离;
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
)
}
2.5 view.setFitsSystemWindows()方法
- 1.设置系统是否需要考虑System bar(status bar和Navigation bar的统称)占据的区域来显示。如果需要的话就会执行
fitSystemWindows(Rect)
方法。即设置为true的时候系统会适应System bar 的区域,不内容不被遮住。fitSystemWindows(Rect)(api level 14)
:用来调整自身的内容来适应System Bar(不让被System Bar遮住)。 这里其实不止Status Bar和Navigation Bar,只是目前只考虑Status Bar、Navigation Bar、IME。 - 2.
onApplyWindowInsets(WindowInsets)(api level 20)
:同fitSystemWindows(Rect)的作用是一样的,更加方便扩展,对以后增加新的系统控件便于扩展。 - 3.使用
android:fitsSystemWindows="true"
,系统会自动的调整显示区域来实现详情的控件不会被遮住。
2.6 API 30 及以后的方式
在Android 30 API 上 SystemUIvisibility已弃用,推荐使用 WindowInsetsController类来进行状态栏的显示和隐藏以及其他操作。这种方式更简单、易操作。也可以使用 WindowInsetsControllerCompat 进行兼容。
private fun hideSystemBars30s(activity: Activity) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val controller = activity.window.decorView.windowInsetsController
// 隐藏状态栏
controller?.hide(WindowInsets.Type.statusBars())
// 显示状态栏
controller?.show(WindowInsets.Type.statusBars())
// 隐藏导航栏
controller?.hide(WindowInsets.Type.navigationBars())
// 显示导航栏
controller?.show(WindowInsets.Type.navigationBars())
// 同时隐藏状态栏和导航栏
controller?.hide(WindowInsets.Type.systemBars())
// 同时显示状态栏和导航栏
controller?.show(WindowInsets.Type.systemBars())
// 向后模式
val behavior1 = WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_TOUCH
// 沉浸模式
val behavior2 = WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_SWIPE
// 粘性沉浸模式
val behavior3 = WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
controller?.systemBarsBehavior = behavior1
}
}
三、刘海屏
3.1 刘海屏的要求
在Android 9(API 28)及更高版本的设备上正式支持刘海屏。部分制造商也有在低版本上支持刘海屏。
在带刘海屏的设备上为确保一致性和兼容性,有五点强制要求:
- 一条边缘最多只能包含一个刘海
- 一台设备不能有两个以上的刘海
- 设备的两条较长边缘上不能有刘海
- 在未设置特殊标志的竖屏模式下,状态栏的高度必须至少与刘海屏的高度一致
- 在默认情况下,在全屏模式或横屏模式下,整个刘海屏必须显示黑边
第五条比较重要,这里设置全面屏时必须要考虑是否有刘海屏的这个性质
3.2 刘海模式
Android 允许控制是否在刘海区域显示内容。窗口布局属性 layoutInDisplayCutoutMode
控制内容将如何呈现在刘海区域中。共分为3种模式,可以通过编程或设置 Activity 的样式来控制刘海屏模式:
<style name="ActivityTheme">
<item name="android:windowLayoutInDisplayCutoutMode">
shortEdges <!-- default, shortEdges, never -->
</item>
</style>
fun supportDisplayCutouts(activity: Activity) {
val window = activity.window
val lp = window.attributes
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
// 仅当缺口区域完全包含在状态栏之中时,才允许窗口延伸到刘海区域显示
// lp.layoutInDisplayCutoutMode =
// WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
// 永远不允许窗口延伸到刘海区域
// lp.layoutInDisplayCutoutMode =
// WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
// 始终允许窗口延伸到屏幕短边上的刘海区域
lp.layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
window.attributes = lp
}
}
下面就更详细的介绍不同的刘海模式。
3.2.1 默认行为
默认情况下,在未设置特殊标志的竖屏模式下,在带刘海屏的设备上,状态栏的大小会调整为至少与刘海一样高,而您的内容会显示在下方区域。在横屏模式或全屏模式下,您的应用窗口会显示黑边,因此您的任何内容都不会显示在刘海区域中。
3.2.2 将内容呈现在短边刘海屏区域中
在竖屏模式和横屏模式下,内容都会呈现到刘海区域中,而不管系统栏处于隐藏还是可见状态栏。需要注意的是:Android 一般是不允许内容视图与系统栏重叠,如果要强制视图内容延伸到刘海区域,需要和设置上述介绍的沉浸式状态栏或全面屏的方式结合使用。
3.2.3 从不将内容呈现在刘海区域中
内容从不呈现到刘海区域中。此模式应该用于暂时设置 View.SYSTEM_UI_FLAG_FULLSCREEN 或 View.SYSTEM_UI_FLAG_HIDE_NAVIGATION 的窗口,以避免在设置或清除了该标志时执行另一种窗口布局。
四、总结
开头说的全屏预览时发现使用全面屏后,图片会闪动一下,这是因为,我当时使用的刘海屏的手机,当使用全屏模式时,刘海屏的手机默认会显示黑边,我们还要设置刘海屏的模式,上面提到的刘海屏的3中模式,可以设置**将内容呈现在短边刘海屏区域中**从而在刘海屏中达到全屏的效果。
沉浸式状态栏主要是分两个模式,一种是控制内容呈现在状态栏中,一种是控制状态栏的颜色。而且沉浸式状态栏还要根据 Android API 的不同使用的方式也不同,这块相对来说在日常开发中用的比较多,概念理清后就比较简单。
全面屏在 Android API 30 之前和之后也有所不同,在 Android API 30 及以后方式简单明了。
刘海屏是在 Android API 28 及以后支持的,要和全面屏配合使用,不然只考虑全面屏,在刘海屏上的效果是不进人不如意的。