如何在 Kotlin Multiplatform 中强制让 Samsung 状态栏透明?
通常不需要为 Samsung 单独添加一套“强制透明”代码。现有代码在 Android 14 上已经请求透明状态栏,并关闭了状态栏对比度保护。仅凭这些片段,还无法确定黑条的原因。
建议先检查 Samsung 对该应用的“相机开孔”设置,再确认页面背景是否绘制到了状态栏后方。代码上可以让 enableEdgeToEdge() 管理窗口配置,SystemAppearance() 只调整系统栏图标颜色。
先检查 Samsung 的“相机开孔”设置
在出现问题的手机上,打开:
设置 → 显示 → 相机开孔(Camera cutout)→ 选择你的应用
如果当前选中“隐藏相机开孔”,改为允许显示开孔的选项,然后重新打开应用。具体选项名称可能因 One UI 版本和机型而异。
Samsung 官方说明,“隐藏相机开孔”会用黑条覆盖开孔区域,且支持按应用设置。因此,即使其他应用的状态栏透明,你的应用也可能启用了这个选项。可以优先检查这一项,但目前还不能确定它就是这台设备出现黑条的原因。Samsung 官方说明
LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES 用于请求窗口在相应开孔区域布局,不能保证覆盖 Samsung 的用户设置。
现有代码中有哪些问题?
Android 14 的透明配置已经存在
在 Android 14 上,以下两个设置都会执行:
window.statusBarColor = Color.Transparent.toArgb()
window.isStatusBarContrastEnforced = false
前者请求透明背景,后者关闭系统为状态栏自动添加的对比度保护。如果后续没有代码覆盖这两个设置,重复设置相同的值通常无法解释或解决黑条问题。Window API 文档
Android 9 及以下的分支则把状态栏颜色设为 primaryContainer,没有请求透明状态栏。这个颜色可能恰好与页面顶部一致,所以看起来像透明。
图标颜色和背景透明度是两个设置
controller.isAppearanceLightStatusBars = false
这行代码会使用浅色状态栏图标,不会把状态栏背景设成黑色。设为 true 时使用深色图标,适合浅色背景。Android 官方示例
SystemBarStyle.dark(Color.TRANSPARENT) 同样表示使用适合深色背景的系统栏样式,传入的背景色仍是透明色。
简化窗口配置
保留 MainActivity 中的 enableEdgeToEdge(),让 AndroidX 处理不同 Android 版本的窗口配置:
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
SongBookTheme {
// 应用页面
}
}
}
}
enableEdgeToEdge() 默认会配置透明状态栏。在三键导航模式下,底部导航栏默认可能带有半透明遮罩,这与顶部状态栏是否透明是两件事。Android edge-to-edge 文档
再将 Android 源集中的 SystemAppearance() 简化为只设置图标颜色:
import android.app.Activity
import android.content.Context
import android.content.ContextWrapper
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.ui.platform.LocalView
import androidx.core.view.WindowCompat
private tailrec fun Context.findActivity(): Activity? =
when (this) {
is Activity -> this
is ContextWrapper -> baseContext.findActivity()
else -> null
}
@Composable
actual fun SystemAppearance(isDark: Boolean) {
val view = LocalView.current
if (view.isInEditMode) return
val activity = view.context.findActivity() ?: return
SideEffect {
val controller = WindowCompat.getInsetsController(
activity.window,
activity.window.decorView
)
controller.isAppearanceLightStatusBars = !isDark
controller.isAppearanceLightNavigationBars = !isDark
}
}
原有的 expect/actual 结构保持不变,SongBookTheme() 中调用 SystemAppearance(darkTheme) 的位置也可以保留。
使用 !isDark 的前提是状态栏后方的背景跟随应用的明暗主题。如果顶部显示照片,或工具栏始终为深色,应根据实际背景选择图标颜色。
确保背景确实延伸到状态栏后方
透明状态栏会显示后面的内容。如果页面背景从状态栏下方才开始绘制,上方仍可能露出黑色的窗口背景。
可以在 SongBookTheme() 的 MaterialTheme 内容块中,让背景铺满根布局,再给内部内容添加安全区域留白:
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.material3.MaterialTheme
import androidx.compose.ui.Modifier
// 放在现有 MaterialTheme(...) { ... } 内
SystemAppearance(darkTheme)
Box(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.background)
) {
Box(
modifier = Modifier
.fillMaxSize()
.safeDrawingPadding()
) {
content()
}
}
外层背景可以绘制到系统栏后方,内部内容则避开系统栏和开孔区域。Insets 需要根据页面结构处理。如果 Scaffold 或顶部栏已经提供了相应留白,就不要重复叠加。Compose Insets 文档
排查时,可以临时把外层背景改成醒目的颜色:
.background(androidx.compose.ui.graphics.Color.Magenta)
如果状态栏区域随之变色,说明透明已经生效,可以继续检查原来的背景和 Insets 布局。如果仍然显示黑条,再检查 Samsung 的开孔设置,以及后续是否有页面、主题或第三方库重新修改了窗口配置。这一步只能缩小排查范围,不能单独确定具体原因。
版本与验证注意事项
这些改动属于 Kotlin Multiplatform 的 Android 平台实现,放在 Android 源集即可。
Window.setStatusBarColor() 从 API 35 起已弃用。后续维护应使用 edge-to-edge,并处理好状态栏后方的背景绘制,避免继续叠加旧的系统栏配置。API 文档
上述代码用于整理配置和排查问题,尚未在题述 Galaxy S23 Ultra 上验证。如果关闭“隐藏相机开孔”后黑条消失,就无需再增加 Samsung 专用代码;如果仍然存在,需要结合实际页面布局、One UI 版本和窗口配置继续排查。
备注:内容仅供参考。
