D3b结构与容器

页面壳与容器:Scaffold、顶栏、Card、对话框、底栏壳、@Preview。路由细节留给导航章

📌 插槽:Scaffold 的 topBar / bottomBar 等只在对应参数里填;内容区记得吃 innerPadding

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()
    }
}

🔗 预览官方说明

积木段告一段落。下一章进入状态与数据流——把这些组件真正「驱动」起来。
Android Compose 现代开发知识体系 · 主线必读 / 扩展选读。
术语表常驻;组件与属性先看分讲,再查两张总表。案例默认收起。