D3b结构与容器
页面壳与容器:Scaffold、顶栏、Card、对话框、底栏壳、@Preview。路由细节留给导航章。
Scaffold ★
概念
标准 Material 页面骨架:顶栏、底栏、FAB、Snackbar 宿主、正文槽位一次配齐,并自动处理内容避让。
和 XML 对照
XML 常手拼 AppBar + 根布局 + BottomNav。Compose 用 Scaffold 把槽位声明出来。
怎么用(★)
@Composable
fun ScaffoldBasics() {
Scaffold(
topBar = {
TopAppBar(title = { Text("首页") })
},
bottomBar = { /* NavigationBar 见下 */ },
snackbarHost = { /* SnackbarHost */ }
) { innerPadding ->
Column(
modifier = Modifier
.padding(innerPadding)
.fillMaxSize()
) {
Text("正文")
}
}
}
易错点
- 忽略
innerPadding→ 内容钻进顶栏/底栏下面。 - 把整页滚动写在 Scaffold 外层,和槽位 padding 打架。
▶ 标准页面壳:顶栏 + 正文
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SettingsShell() {
Scaffold(
topBar = {
TopAppBar(title = { Text("设置") })
}
) { innerPadding ->
Column(
modifier = Modifier
.padding(innerPadding)
.padding(16.dp)
) {
Text("账号")
Text("通知")
}
}
}
💡 加 @Preview 即可看壳。@Preview
TopAppBar ★
概念
页面顶栏:标题、导航图标(返回)、动作区。折叠/大顶栏细节见列表进阶。
和 XML 对照
≈ Toolbar / Material AppBarLayout + Toolbar。
怎么用(★)
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TopBarBasics(onBack: () -> Unit) {
TopAppBar(
title = { Text("详情") },
navigationIcon = {
IconButton(onClick = onBack) {
Icon(Icons.Default.ArrowBack, contentDescription = "返回")
}
},
actions = {
IconButton(onClick = { }) {
Icon(Icons.Default.Share, contentDescription = "分享")
}
}
)
}
易错点
- 返回图标
contentDescription漏写 → 读屏不友好。 - 单独用 TopAppBar 却不放进 Scaffold → 自己处理状态栏/ insets 更麻烦。
▶ 带返回与标题的顶栏
把上一节 TopBarBasics 塞进 Scaffold(topBar = { … }) 即完整壳。
Card / Surface ★
概念
Card:带圆角/海拔的内容容器;Surface 更底层,可自定义颜色与 elevation。列表项、概要信息常用 Card。
和 XML 对照
≈ CardView / MaterialCardView;Surface ≈ 可着色的容器 View。
怎么用(★)
@Composable
fun CardBasics() {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp),
shape = RoundedCornerShape(12.dp)
) {
Column(modifier = Modifier.padding(16.dp)) {
Text("卡片标题", style = MaterialTheme.typography.titleMedium)
Text("摘要内容……")
}
}
}
易错点
- 整页套 Card 套娃 → 视觉噪点;卡片用于「一块可区分内容」。
- 可点击 Card 忘记
onClick(Material3 Card 重载)或外层clickable。
▶ 信息卡片
@Composable
fun SummaryCard() {
Card(modifier = Modifier.fillMaxWidth()) {
Column(modifier = Modifier.padding(16.dp)) {
Text("本周摘要", style = MaterialTheme.typography.titleMedium)
Spacer(modifier = Modifier.height(8.dp))
Text("完成 12 项 · 待办 3 项")
}
}
}
AlertDialog ★
概念
对话框显隐由状态声明:showDialog == true 时组合进树,否则不调用。别再用「拿到 Dialog 实例 show()」的命令式思维。
和 XML 对照
≈ AlertDialog.Builder。XML:dialog.show();Compose:改布尔状态。
怎么用(★)
@Composable
fun DialogBasics() {
var open by remember { mutableStateOf(false) }
Button(onClick = { open = true }) {
Text("删除")
}
if (open) {
AlertDialog(
onDismissRequest = { open = false },
title = { Text("确认删除?") },
text = { Text("删除后不可恢复") },
confirmButton = {
TextButton(onClick = { open = false }) {
Text("删除")
}
},
dismissButton = {
TextButton(onClick = { open = false }) {
Text("取消")
}
}
)
}
}
易错点
- 关闭时不把状态设回
false→ 对话框「关不掉」或重组后又弹出。 - 在非 Composable 里直接
AlertDialog(...)—— 必须在组合里、且通常用if (open)。
▶ 删除确认框
要点:和登录按钮一样——open 是状态,对话框是 f(open)。
○ Snackbar · BottomSheet · BadgedBox
概念
用得到再查:轻提示、半屏面板、角标数字。主线混眼熟即可。
和 XML 对照
Snackbar ≈ Snackbar;BottomSheet ≈ BottomSheetDialog;Badge ≈ 角标 View。
怎么用(点到)
@Composable
fun OccasionalStructure() {
// Snackbar:Scaffold 提供 snackbarHost + SnackbarHostState.showSnackbar
// ModalBottomSheet:expanded 状态控制显隐(同 Dialog)
BadgedBox(badge = { Badge { Text("3") } }) {
Icon(Icons.Default.Notifications, contentDescription = "通知")
}
}
易错点
Snackbar 要挂在 Scaffold 的 host 上,别当普通 Text 随处弹。
@Preview ★
概念
不装包,在 IDE 里预览 Composable。可配多设备、深浅色、字体缩放。手册案例优先靠示意 + Preview 验证。
和 XML 对照
≈ 布局编辑器 Design 预览;Compose 预览的是函数,不是 XML 文件。
怎么用(★)
@Preview(showBackground = true, name = "登录表单")
@Composable
fun LoginFormPreview() {
MaterialTheme {
LoginForm() // 你的无参或提供默认参数的入口
}
}
易错点
- Preview 函数需要参数却没默认值 / 没包一层无参包装 → 预览起不来。
- Preview 里直接碰 ViewModel / 网络 → 用假数据或再包一层。
▶ 给登录表单加 Preview
@Composable
fun LoginForm() {
Column(modifier = Modifier.padding(16.dp)) {
OutlinedTextField(value = "", onValueChange = { }, label = { Text("账号") })
Spacer(modifier = Modifier.height(8.dp))
OutlinedTextField(value = "", onValueChange = { }, label = { Text("密码") })
Spacer(modifier = Modifier.height(12.dp))
Button(onClick = { }, modifier = Modifier.fillMaxWidth()) {
Text("登录")
}
}
}
@Preview(showBackground = true)
@Composable
private fun LoginFormPreview() {
MaterialTheme {
LoginForm()
}
}
🔗 预览官方说明