跳到正文Lab学后总结 · Jimmy Huang's BlogsetContent { ... }
里面调用了哪个 Composable;Android Studio 预览看的是
@Preview
里面调用了哪个 Composable。
如果你只改了 @Preview,模拟器里可能不会变;如果你只改了 setContent,Preview 面板也可能不会变。
2.2 Task 1:用 Column 纵向显示四季
这一部分创建了一个新的 Composable,例如:
Column 的意思是“列”,也就是从上到下排列组件。
最开始只是把文字直接放进 Column 里。后面为了让 UI 更好看,又加入了:
UI 不是随便堆文字,而是要通过布局、背景、间距,让界面变得可读、可控。
2.3 Task 2:用 Row 横向显示四季
2.4 Task 3:用 Box、Column、ElevatedButton 和 Toast 做点击反馈
用 Box 占满屏幕。
让内容居中。
在中间放一个 Surface。
Surface 里面放一个 Column。
Column 里显示四季文字。
下面加一个 ElevatedButton。
点击按钮后弹出 Toast,例如显示:This is a Toast。
ElevatedButton 是用户可以点的按钮。
onClick = { ... } 是按钮被点击后执行的代码。
Toast.makeText(...).show() 是弹出一个短消息。
LocalContext.current 是为了让 Compose 代码拿到 Android 运行环境,Toast 需要它才能显示。
初学者最容易忽略的是:布局组件本身不会自动产生交互,点击之后发生什么,必须写在 onClick 里面。
2.5 Part 3:Problem Solving,完成 Seasons and Months 小应用
最后的 problem-solving 任务要求你们分组做一个更完整的小应用。
显示四个代表季节的按钮。
四个按钮要在屏幕上均匀分布。
点击每个季节按钮时,用 Toast 显示澳大利亚该季节对应的三个月。
页面顶部要有大标题:Seasons and Months。
要加入一张好看的、无版权问题的图片;材料给了 Pexels 图片作为参考。
要使用 nested layouts,也就是布局嵌套。
需要尽量匹配材料中的示例截图;但原截图图片内容在本 md 中不复制。
Spring:September, October, November
Summer:December, January, February
Autumn:March, April, May
Winter:June, July, August
注意:这里不是北半球季节月份。澳大利亚在南半球,所以季节月份和中国、欧洲、北美常见印象相反。
3. 每个核心组件有什么用
3.1 MainActivity.kt
Android App 的主入口文件之一,这个 Lab 主要在这里写 UI。
你在里面写 setContent { ... },决定运行 App 时显示哪个 Compose 界面。
你不知道 UI 应该从哪里启动,App 也没有明确的页面入口。
它像一个“总入口”,里面调用 ColumnLayoutCompose、RowLayoutCompose、ButtonCompose 或最终的 SeasonsMonthsApp。
3.2 setContent { ... }
Compose UI 的运行入口。它告诉 Android:“这个屏幕的内容从这里开始画。”
决定模拟器或真机运行时看到的是哪个 Composable。
你写了很多 Composable,也可能不会显示在真正运行的 App 里。
setContent 会调用你的 @Composable 函数,例如:
ColumnLayoutCompose()
RowLayoutCompose()
ButtonCompose()
SeasonsMonthsApp()
3.3 @Composable
你写的每一个 UI 块,例如四季文字列表、按钮区域、最终页面,都可以是一个 @Composable 函数。
普通 Kotlin 函数不能直接当作 Compose UI 使用;你也不能在里面正常调用 Text()、Column()、Row() 这些 Composable。
Column、Row、Text、Button 等都要在 Composable 环境里使用。
3.4 @Preview
你仍然可以运行 App,但调整 UI 会更慢,因为每次都要启动或刷新模拟器。
它通常会调用一个 Composable 来展示预览,但它不等于真正运行 App。
3.5 Column
把 Spring、Summer、Autumn、Winter 从上到下排列。
里面可以放 Text、Spacer、Button,也可以被放进 Box 或 Surface 里。
3.6 Row
把四季从左到右排列,或者在最终 App 中让四个季节按钮横向均匀分布。
常配合 fillMaxWidth() 和 Arrangement.SpaceBetween / SpaceEvenly 来实现横向均匀分布。
3.7 Box
材料中用它把内容放到屏幕中间,否则内容容易出现在左上角。
你可能只能看到内容默认从左上角开始,不容易做“整屏居中”。
3.8 Surface
Material Design 里的一个 UI 容器,可以提供背景颜色、形状、阴影等视觉层。
给四季文字区域加背景色,让界面不像普通黑白文字那么单调。
Surface 通常包住 Column 或其他内容,像给一组组件铺了一块背景板。
3.9 Modifier
Compose 里控制组件外观和行为的“配置链”。
控制大小、间距、填满屏幕、居中、padding 等。
几乎所有 Compose UI 组件都能接收 Modifier。
3.10 Text
显示季节名,例如 Spring、Summer、Autumn、Winter,以及最终标题 Seasons and Months。
Text 通常放在 Column、Row、Button 里面。
3.11 Spacer
在四季文字和按钮之间留出距离,例如 Spacer(modifier = Modifier.height(20.dp))。
经常放在 Column 或 Row 中间,用来调节视觉间距。
3.12 ElevatedButton
Material Design 风格的按钮,比普通按钮更有层次感。
按钮内部一般放 Text,点击逻辑写在 onClick 里。
3.13 Toast
Toast 不是 Compose 布局组件,它是 Android 系统层的提示功能,所以需要 LocalContext.current 获取上下文。
3.14 LocalContext.current
Compose 中获取当前 Android Context 的方式。
给 Toast.makeText(...) 提供运行环境。
Toast 不知道应该在哪个 Android 环境里显示。
它连接了 Compose UI 世界和 Android 系统功能。
3.15 Image
最终 Seasons and Months App 要加入一张好看的图片。
最终页面可能能完成基本功能,但不符合材料里“加图片”的要求,视觉效果也会更弱。
图片通常放在 Column 中,位于标题和按钮之间或页面主体区域。具体图片下载、命名和资源放置细节,材料只给了参考图片和 Android codelab 提示,完整步骤在当前可见材料中没有完全展开。
4. 用初学者类比解释
你可以把这个 Lab 想象成:你在布置一个小型展览摊位。
MainActivity.kt:展览馆入口。
setContent { ... }:告诉工作人员今天这个摊位要摆什么。
@Composable:每一个可以摆出来的小展台设计图。
Column:竖着摆的货架。
Row:横着摆的柜台。
Box:整个摊位空间,可以决定东西放中间、左边、右边。
Surface:摊位背景板或桌布,让内容更有视觉层次。
Modifier:卷尺和胶带,用来决定宽度、高度、间距、位置。
Text:贴在摊位上的文字标签。
Spacer:两个物品之间留出来的空位。
ElevatedButton:摊位上的按钮或门铃,顾客可以按。
Toast:按下按钮后,旁边小喇叭播报一句短消息。
LocalContext.current:告诉小喇叭“你现在在哪个展馆里”,不然它不知道要在哪里响。
@Preview:展览开始前,在设计室里看模型;能看样子,但不能完全代替真实开门营业。
Image:摊位上的装饰海报,让页面更像一个完整应用。
最终的 Seasons and Months App 就像一个“四季介绍小展台”:
顶部有招牌:Seasons and Months
中间有图片装饰。
下方有四个季节按钮。
用户点击某个季节按钮。
小喇叭 Toast 告诉用户:澳大利亚这个季节是哪三个月。
5. 重点区分容易混淆的概念
5.1 Column 和 Row 的区别
5.2 Box 和 Column / Row 的区别
Box 不是用来替代 Column 和 Row 的。
在这个 Lab 里,Box 更像一个大舞台,负责把内容放在屏幕中央;Column 负责舞台中间的内容从上到下摆放。
5.3 @Preview 和真正运行 App 的区别
Preview 是“看界面草图”,不是“完整运行 App”。
5.4 @Preview 和 setContent 的区别
想让模拟器变,要改 setContent 调用的 Composable;想让 Preview 变,要改 @Preview 调用的 Composable。
5.5 Surface 和 Modifier 的区别
Surface 像桌布或背景板。
Modifier 像卷尺、胶带和摆放规则。
5.6 Button 和 Toast 的区别
Button 不会自动显示 Toast;必须在 onClick 里写 Toast.makeText(...).show()。
5.7 Text 和 Toast 的区别
5.8 LocalContext.current 为什么需要
“我只是弹个 Toast,为什么还要 context?”
因为 Toast 是 Android 系统功能,它需要知道:
这个提示属于哪个 App?
应该在哪个 Activity 或运行环境里显示?
5.9 最终 App 里谁是入口、谁是布局、谁是反馈
6. 这个 Lab 有什么实际用途
6.1 对 Android 开发的用途
这个 Lab 是 Android Compose UI 的入门核心。以后你做任何 Android App,都会不断遇到这些问题:
文字怎么摆?
按钮怎么放?
页面怎么居中?
组件之间怎么留空?
点击按钮后怎么给用户反馈?
怎么快速预览 UI?
怎么把多个小组件组合成一个完整页面?
这次 Lab 就是在训练这些最基础但最常用的能力。
6.2 对真实项目的用途
在真实项目里,一个页面通常不是一次写完的,而是拆成很多小块:
标题区域
图片区域
按钮区域
内容区域
提示反馈
输入表单
列表区域
这个 Lab 的 Column、Row、Box、Modifier 就是后面搭建复杂页面的基础。
登录页面
个人资料页面
商品详情页
食物分享 App
聊天页面
Dashboard
设置页面
6.3 对后端 / DevOps / 云架构学习的间接用途
虽然这个 Lab 本身是 Android UI,不是后端或云服务,但它仍然很有用:
你会理解“前端界面如何接收用户操作”。
以后连接 API 时,按钮点击可能不只是弹 Toast,而是发送网络请求。
Toast 这种反馈机制以后可以换成 Snackbar、Dialog、页面状态提示。
你会逐渐建立“UI → 用户事件 → 业务逻辑 → 反馈”的基本链路。
用户点击 “Login” 按钮。
App 发送请求到后端 API。
后端验证账号密码。
App 根据结果显示成功页面或错误 Toast。
这次 Lab 只是把第 1 步和第 4 步先用最简单方式练熟。
6.4 对安全设计的间接用途
这个 Lab 没有直接做权限、安全组、登录认证等内容。
但它让你明白:用户能点什么、看到什么、收到什么反馈,都是由 UI 设计控制的。
登录失败提示
权限不足提示
表单校验提示
删除确认
隐私授权提醒
7. 最后画一张生动形象的示意图
下面这张图不是原始截图,而是帮助初学者理解这个 Lab 的结构图。
红色叉号表示“这条路不能这样理解 / 不能这样验证 / 被阻止”。
最后复习:这个 Lab 最应该掌握什么
你学完这次 Lab 后,最重要的是能说清楚下面这几句话:
Jetpack Compose 是用 Kotlin 函数描述 UI 的现代 Android UI 工具。
@Composable 函数负责描述界面,setContent 决定真实运行时显示哪个界面。
@Preview 只是预览,不等于真实运行。
Column 负责竖排,Row 负责横排,Box 常用来居中或叠放。
Modifier 控制大小、间距、填充和对齐。
Surface 给一组 UI 加背景和视觉层次。
ElevatedButton 是点击入口,Toast 是点击后的短暂反馈。
LocalContext.current 是 Compose 连接 Android 系统功能的桥梁。
最终的 Seasons and Months App 是把布局、图片、按钮、Toast 组合起来的综合练习。
自测问题
如果我想让四个文字竖着排,应该用 Column 还是 Row?
如果我想让四个按钮横向平均分布,应该用什么布局?
为什么内容默认可能出现在左上角?怎样让它居中?
@Preview 和 setContent 有什么区别?
为什么 Toast 需要 LocalContext.current?
Surface 和 Modifier 分别负责什么?
点击按钮后为什么不会自动弹 Toast?
澳大利亚的 Spring、Summer、Autumn、Winter 分别是哪几个月?
如果模拟器运行结果没变,但 Preview 变了,最可能是哪里没改?
最终 App 为什么需要 nested layouts?
flowchart TB
Title["Lab Week 2:Jetpack Compose 基础布局与点击反馈"]
subgraph Project["Android 项目:LayoutLab"]
Main["MainActivity.kt<br/>App 主入口文件"]
SetContent["setContent { ... }<br/>真实运行入口"]
Preview["@Preview<br/>Android Studio 预览入口"]
end
subgraph ComposeTree["Composable UI 树:用函数描述界面"]
Screen["SeasonsMonthsApp / ButtonCompose<br/>一个完整页面"]
Box["Box<br/>像大舞台:负责居中/叠放"]
Surface["Surface<br/>像背景板:提供颜色/视觉层"]
Column["Column<br/>竖向货架:从上到下排"]
Row["Row<br/>横向柜台:从左到右排"]
Text["Text<br/>标题和季节文字"]
Image["Image<br/>页面装饰图片"]
Spacer["Spacer<br/>留白间距"]
Buttons["4 个 ElevatedButton<br/>Spring / Summer / Autumn / Winter"]
end
subgraph Runtime["点击交互:真实运行时才完整验证"]
Click["用户点击季节按钮"]
Context["LocalContext.current<br/>获取 Android 运行环境"]
Toast["Toast<br/>短暂显示对应三个月"]
end
subgraph Blocked["容易误解的阻止路径"]
X1["❌ 只改 @Preview<br/>不会自动改变真机运行画面"]
X2["❌ Preview 不能完整验证<br/>Toast 这类运行时交互"]
X3["❌ Button 不会自动弹 Toast<br/>必须写 onClick"]
X4["❌ Column / Row / Box<br/>只是布局,不是点击反馈"]
end
Remember["你要记住的核心区别:<br/>1. Column 竖排,Row 横排,Box 管居中/叠放。<br/>2. setContent 管真实运行,@Preview 管设计时预览。<br/>3. Button 是点击入口,Toast 是点击后的短消息。<br/>4. Modifier 像卷尺,Surface 像背景板。"]
Title --> Project
Main --> SetContent
Main --> Preview
SetContent --> Screen
Preview -. "只看布局外观" .-> Screen
Screen --> Box
Box --> Surface
Surface --> Column
Column --> Text
Column --> Image
Column --> Spacer
Column --> Row
Row --> Buttons
Buttons --> Click
Click --> Context
Context --> Toast
Preview -.-> X2
Preview -.-> X1
Buttons -.-> X3
Column -.-> X4
Row -.-> X4
Box -.-> X4
Toast --> Remember
X1 --> Remember
X2 --> Remember
X3 --> Remember
X4 --> Remember
classDef main fill:#EAF3FF,stroke:#2F73D8,stroke-width:2px,color:#111;
classDef ui fill:#EFFFF4,stroke:#2E8B57,stroke-width:2px,color:#111;
classDef action fill:#FFF7E6,stroke:#D9822B,stroke-width:2px,color:#111;
classDef danger fill:#FFECEC,stroke:#D00000,stroke-width:3px,color:#9B0000;
classDef remember fill:#F3E8FF,stroke:#7B2CBF,stroke-width:2px,color:#111;
class Title,Project,Main,SetContent,Preview main;
class ComposeTree,Screen,Box,Surface,Column,Row,Text,Image,Spacer,Buttons ui;
class Runtime,Click,Context,Toast action;
class X1,X2,X3,X4,Blocked danger;
class Remember remember;
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Column { ... }
}
Modifier.fillMaxWidth()
Modifier.fillMaxSize()
Modifier.padding(24.dp)
Modifier.height(20.dp)
val context = LocalContext.current