外观
TopTitleScaffold — 页面骨架
带标题栏的页面骨架:标题栏、三边 inset(状态栏 / 导航栏 / 输入法)、内容区底色,以及 overlay(内容铺到标题栏下面)两种模式。
这是可选的。 界面结构简单、或者要自己控制整屏布局(全屏看图、完全自定义头部、纯 Box 叠层)时,直接用 Modifier.statusBarsPadding() 这类原生手段也行 —— 前提是你清楚 inset 得自己处理。凡是要标题栏 + 返回键 + 动作区的页面,用它比手拼省事,而且不会漏 inset。
基本用法
常规页面:
kotlin
TopTitleScaffold(
title = "设置",
onBack = { finish() },
actions = {
ActionImageButton(icon = Phosphor.Gear, onAction = { openSettings() })
},
) {
SettingsContent()
}根页面(不画返回键):
kotlin
TopTitleScaffold(title = "首页") {
HomeContent()
}带副标题:
kotlin
TopTitleScaffold(
title = "播放历史",
subtitle = "最近 30 天",
onBack = { finish() },
) { HistoryList() }整屏大图 / 视频页(标题栏浮在内容上):
kotlin
TopTitleScaffold(
title = "",
onBack = { finish() },
overlay = true,
colors = TopTitleScaffoldDefaults.overlayColors(),
) {
AsyncImage(model = url, contentScale = ContentScale.Crop, modifier = Modifier.fillMaxSize())
}底部固定条:
kotlin
TopTitleScaffold(
title = "作品详情",
onBack = { finish() },
overlay = true,
colors = TopTitleScaffoldDefaults.overlayColors(),
bottomBar = {
Row(modifier = Modifier.fillMaxWidth().navigationBarsPadding()) { /* 操作栏 */ }
},
) { /* 大图 */ }完全自定义头部(连状态栏内缩一起接管):
kotlin
TopTitleScaffold(
title = "",
topBar = { MyCustomHeader() },
) { /* content */ }参数
TopTitleScaffold
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
title | String | — | 标题 |
modifier | Modifier | Modifier | 修饰符 |
subtitle | String? | null | 标题下方那行小字,不传则不占高度 |
onBack | (() -> Unit)? | null | 返回回调。传 null 就不显示返回键 |
actions | @Composable RowScope.() -> Unit | {} | 右侧动作区,用 ActionImageButton 填 |
topBar | (@Composable () -> Unit)? | null | 自定义顶部栏。传了就不再用 title / subtitle / onBack / actions |
bottomBar | @Composable () -> Unit | {} | 底部固定条,内容区会自动避开它 |
overlay | Boolean | false | true 时内容铺满整屏、标题栏浮在其上 |
colors | TopTitleScaffoldColors | TopTitleScaffoldDefaults.colors() | 配色 |
content | @Composable () -> Unit | — | 页面内容。两个模式下都不用自己算 inset |
TopTitleBar
只要标题栏、不要 Scaffold 那一层时用(比如塞进别的容器、或者标题行作列表头):
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
title | String | — | 标题,headlineMedium |
modifier | Modifier | Modifier | 修饰符 |
subtitle | String? | null | 副标题,bodySmall,空白等同于不传 |
onBack | (() -> Unit)? | null | 返回回调。传 null 就不显示返回键 |
actions | @Composable RowScope.() -> Unit | {} | 右侧动作区 |
colors | TopTitleScaffoldColors | TopTitleScaffoldDefaults.colors() | 配色 |
windowInsets | WindowInsets | TopAppBarDefaults.windowInsets | 系统装饰内缩。已自己处理状态栏时传 WindowInsets(0.dp, 0.dp, 0.dp, 0.dp) |
TopTitleScaffoldColors
| 字段 | colors() 默认值 | overlayColors() 默认值 |
|---|---|---|
containerColor | background | Color.Transparent |
contentContainerColor | background | Color.Transparent |
titleColor | onSurface | Color.White |
subtitleColor | onSurfaceVariant | Color.White @ 70% |
navigationIconTint | onSurface | Color.White |
actionIconTint | onSurface | Color.White |
要点
- 不要自己补
statusBarsPadding()/navigationBarsPadding()。 三边 inset 由TopTitleScaffold统一处理,叠着加会出现双倍留白。自己手拼布局时才需要补。 - 返回键只在
onBack != null时出现。 别靠传空 lambda 去「隐藏」它 —— 那样会画出一个点了没反应的返回键。根页面直接不传。 overlay = true时content不吃 Scaffold 的 padding,contentContainerColor那一项也不生效(容器色直接设成透明)—— 背景图自己铺满。- 传了
topBar之后,状态栏内缩也归调用方管。 外层那个statusBarsPadding()的Box仍然在,但它只在不传topBar时才把内缩归零后交给内置标题行;自定义topBar会被塞进那个已经有statusBarsPadding的Box里,所以别再加一次。 bottomBar自己要不要避让导航栏由调用方决定。 内容区会自动避开它,但底部条内部(如果你是自定义Row)要自己加Modifier.navigationBarsPadding()。subtitle不占高度:不传时标题栏就是一行。传了之后TopAppBar的 64dp 也放得下,不会挤成一团。- 右侧动作按钮用
ActionImageButton(定义在按钮那一页),它的默认图标色会通过LocalContentColor跟着actionIconTint走。
说明
- 为什么整个页面骨架都由它负责。 早期只有标题栏那一行(旧的
TopTitleBar),调用方还得自己写Column/Box拼「标题栏 + 内容」,Insets 一漏就是「返回键被状态栏压住」。现在归Scaffold管:标题栏、内容区、导航栏与输入法的内缩都在这一个组件里。 overlay为什么不用另起一套Box布局:Scaffold的摆放顺序是先 content、后 topBar,本来就是为了让标题栏盖在内容之上(M3 源码注释里写着这是控制绘制顺序)。所以「浮层与否」只差content吃不吃 padding,两个模式共用同一个Scaffold。TopTitleBar的顶部内缩默认避让状态栏,所以单独用它时也能用;只有「自己已经处理过状态栏」或「要浮在全屏视频 / 大图上」时才传零WindowInsets。- 配色没有复用 M3 的
TopAppBarColors。 后者把「容器色 / 标题色 / 图标色」绑在TopAppBar那套槽位上,而实际需要的是「容器透明 + 标题与返回键按图片明暗一起换色」这种组合 —— 用TopAppBarColors得先造一个实例再copy三个字段,绕。这里直接把用到的六个颜色摊平,调用方要改哪个改哪个。 overlayColors()的标题与图标默认白色(看图页通常压在深色渐变上);遇到亮图时调用方把按亮度算出来的深色(例如Color(0xFF111318))传进去即可。- 和
CollapsingTitleScaffold的分工:那个是「滚动折叠」的大标题页(列表页用);这个是不折叠的静态标题栏,标题那一行高度固定。