Android 使用 Edge-to-Edge 实现沉浸式状态栏详解

在 Android 中实现 Edge-to-Edge 布局(内容延伸到状态栏和导航栏下方)并适配不同版本、刘海屏/挖孔屏设备,需要综合处理系统栏的显示、颜色、安全区域和兼容性问题。以下是分步骤的完整实现方案:

一、基础配置:启用 Edge-to-Edge

1. 设置透明系统栏

styles.xml 或代码中移除系统栏的默认半透明遮罩:

xml 复制代码
<!-- styles.xml -->
<style name="AppTheme" parent="Theme.Material3.DayNight.NoActionBar">
    <item name="android:windowTranslucentStatus">false</item>
    <item name="android:windowTranslucentNavigation">false</item>
    <item name="android:statusBarColor">@android:color/transparent</item>
    <item name="android:navigationBarColor">@android:color/transparent</item>
</style>
kotlin 复制代码
// Activity 代码中设置
window.apply {
    statusBarColor = Color.TRANSPARENT
    navigationBarColor = Color.TRANSPARENT
    // 允许内容绘制到系统栏下方
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        isStatusBarContrastEnforced = false
        //Android 10以后会存在遮罩,false去掉这个遮罩
        isNavigationBarContrastEnforced = false
    }
}

2. 允许内容延伸到系统栏区域

onCreate 中设置 Window 标志:

kotlin 复制代码
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
    window.setDecorFitsSystemWindows(false)
} else {
    @Suppress("DEPRECATION")
    window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
            View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
            View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
}

二、处理系统栏的显示与隐藏

1. 隐藏系统栏(全屏模式)

kotlin 复制代码
private fun hideSystemBars() {
    val decorView = window.decorView
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
        window.insetsController?.hide(WindowInsets.Type.systemBars())
    } else {
        @Suppress("DEPRECATION")
        decorView.systemUiVisibility = (
            View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or
            View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
            View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
            View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
            View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or
            View.SYSTEM_UI_FLAG_FULLSCREEN
        )
    }
}

2. 显示系统栏

kotlin 复制代码
private fun showSystemBars() {
    val decorView = window.decorView
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
        window.insetsController?.show(WindowInsets.Type.systemBars())
    } else {
        @Suppress("DEPRECATION")
        decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
                View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
                View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
    }
}

三、修改系统栏颜色和前景色

1. 设置背景色

kotlin 复制代码
window.statusBarColor = Color.TRANSPARENT
window.navigationBarColor = Color.TRANSPARENT

2. 设置图标/文字颜色(深色或浅色)

kotlin 复制代码
// 状态栏图标颜色(Android 6.0+)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    val isLight = isSystemBarIconLight() // 根据背景色动态判断
    val visibility = if (isLight) View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR else 0
    window.decorView.systemUiVisibility = visibility
}

// 导航栏图标颜色(Android 8.0+)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    val isLight = isSystemBarIconLight() // 根据背景色动态判断
    window.decorView.systemUiVisibility = if (isLight) {
        window.decorView.systemUiVisibility or View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
    } else {
        window.decorView.systemUiVisibility and View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR.inv()
    }
}

// Android 11+ 使用 WindowInsetsControllerCompat
val windowInsetsController = ViewCompat.getWindowInsetsController(window.decorView)
windowInsetsController?.isAppearanceLightStatusBars = isLight
windowInsetsController?.isAppearanceLightNavigationBars = isLight

四、处理安全区域(Insets)

1. 适配刘海屏/挖孔屏

AndroidManifest.xml 中声明支持刘海屏:

xml 复制代码
<application
    ...
    android:resizeableActivity="true">
    <meta-data
        android:name="android.max_aspect"
        android:value="2.1" />
    <meta-data
        android:name="android.notch_support"
        android:value="true" />
</application>

在代码中设置布局模式:

kotlin 复制代码
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
    window.attributes.layoutInDisplayCutoutMode =
        WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
}

2. 处理系统栏遮挡内容

使用 WindowInsetsCompat 动态调整布局:

kotlin 复制代码
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, insets ->
    val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
    view.updatePadding(
        top = systemBars.top, // 状态栏高度
        bottom = systemBars.bottom // 导航栏高度
    )
    insets
}

五、兼容性处理

1. 版本兼容方案

kotlin 复制代码
fun handleEdgeToEdge(window: Window, rootView: View) {
    // 设置透明系统栏
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        window.statusBarColor = Color.TRANSPARENT
        window.navigationBarColor = Color.TRANSPARENT
    }

    // 处理系统栏 Insets(处理安全边衬区)
    ViewCompat.setOnApplyWindowInsetsListener(rootView) { view, insets ->
        val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
        view.updatePadding(
            top = systemBars.top,
            bottom = systemBars.bottom
        )
        insets
    }

    // 适配刘海屏(API 28+)
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
        window.attributes.layoutInDisplayCutoutMode =
            WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
    }
}

2. 厂商特定适配

  • 华为刘海屏 :通过 HwNotchSizeUtil 获取刘海尺寸。
  • 小米挖孔屏 :读取 ro.miui.notch 属性判断是否支持。

六、完整示例

kotlin 复制代码
class EdgeToEdgeActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_edge_to_edge)

        // 1. 启用 Edge-to-Edge
        enableEdgeToEdge()

        // 2. 处理系统栏 Insets
        handleSystemInsets()

        // 3. 动态修改系统栏颜色
        updateSystemBarColors(isDarkTheme = true)
    }

    private fun enableEdgeToEdge() {
        window.apply {
            statusBarColor = Color.TRANSPARENT
            navigationBarColor = Color.TRANSPARENT
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
                setDecorFitsSystemWindows(false)
            } else {
                @Suppress("DEPRECATION")
                decorView.systemUiFlags = (
                    View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
                    View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
                    View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                )
            }
        }
    }

    private fun handleSystemInsets() {
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.root)) { view, insets ->
            val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
            view.updatePadding(
                top = systemBars.top,
                bottom = systemBars.bottom
            )
            insets
        }
    }

    private fun updateSystemBarColors(isDarkTheme: Boolean) {
        val windowInsetsController = ViewCompat.getWindowInsetsController(window.decorView)
        windowInsetsController?.isAppearanceLightStatusBars = !isDarkTheme
        windowInsetsController?.isAppearanceLightNavigationBars = !isDarkTheme
    }
}

七、注意事项

  1. 测试不同设备:尤其针对华为、小米、三星等厂商的刘海屏和挖孔屏设备。
  2. 键盘弹出处理 :使用 WindowInsetsCompat.Type.ime() 调整布局。
  3. 手势导航兼容:在 Android 10+ 中,避免隐藏导航栏导致手势冲突。
  4. 性能优化 :避免频繁修改 systemUiVisibilityWindowInsetsController

通过上述步骤,可以实现在不同 Android 版本和设备上兼容的 Edge-to-Edge 沉浸式布局,同时确保系统栏颜色、安全区域和交互逻辑的正确性。

更多分享

  1. 一文吃透Kotlin中冷流(Clod Flow)和热流(Hot Flow)
  2. 一文带你吃透Kotlin协程的launch()和async()的区别
  3. Kotlin 作用域函数(let、run、with、apply、also)的使用指南
  4. Android 详解:高频使用的 8 种设计模式的核心思想和代码实现
  5. 一文带你吃透Kotlin中 lateinit 和 by lazy 的区别和用法
相关推荐
二流小码农1 小时前
鸿蒙开发:如何实现文本跑马灯效果
android·ios·harmonyos
二流小码农1 小时前
鸿蒙开发:单一手势实现长按事件
android·ios·harmonyos
二流小码农2 小时前
鸿蒙开发:信息标记组件
android·ios·harmonyos
利维亚的杰洛特3 小时前
【Android15 ShellTransitions】(九)结束动画+Android原生ANR问题分析
android
二流小码农3 小时前
鸿蒙开发:单一手势实现多次点击事件
android·ios·harmonyos
louisgeek4 小时前
Kotlin inline、noinline 和 crossinline 的区别
kotlin
江上清风山间明月4 小时前
一周掌握Flutter开发--8. 调试与性能优化(下)
android·flutter·性能优化
Mr_万能胶5 小时前
要失业了!写在 Android “不再开源”之后
android·android studio·android jetpack
Renounce5 小时前
【Android】ViewModel和AndroidViewModel区别
android
珹洺6 小时前
Java-servlet(九)前端会话,会话管理与Cookie和HttpSession全解析
android·java·服务器·开发语言·前端·数据库·servlet