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 的区别和用法
相关推荐
androidwork3 小时前
Android LinearLayout、FrameLayout、RelativeLayout、ConstraintLayout大混战
android·java·kotlin·androidx
每次的天空3 小时前
Android第十三次面试总结基础
android·面试·职场和发展
wu_android3 小时前
Android 相对布局管理器(RelativeLayout)
android
李斯维5 小时前
循序渐进 Android Binder(二):传递自定义对象和 AIDL 回调
android·java·android studio
androidwork5 小时前
OkHttp 3.0源码解析:从设计理念到核心实现
android·java·okhttp·kotlin
莉樱Yurin6 小时前
Kotlin/CLR 让Kotlin走进.NET世界
kotlin
像风一样自由6 小时前
【001】frida API分类 总览
android·frida
casual_clover6 小时前
Android 之 kotlin 语言学习笔记四(Android KTX)
android·学习·kotlin
移动开发者1号8 小时前
Android 大文件分块上传实战:突破表单数据限制的完整方案
android·java·kotlin
移动开发者1号8 小时前
单线程模型中消息机制解析
android·kotlin