Android Compose 01_基础显示组件
基础显示组件 - Text、Button、TextField、FAB
前言
欢迎来到 Compose 学习的第一站!
如果说 XML 布局像砖块盖房子,每一块都要精心打磨;那么 Compose 就像乐高积木,拿起就能拼,拼完就能用。
今天要讲的四个组件——Text、Button、TextField、FloatingActionButton(FAB),就是 Compose 中最基础的那几块积木。无论你要盖什么"房子",这几块积木都少不了。

它们就像你手机里的App一样无处不在:
- Text:显示文字,就像朋友圈的文字内容
- Button:用户点击的按钮,就像"发布"按钮
- TextField:输入框,就像评论框、搜索框
- FAB:浮动操作按钮,就像新建邮件的"+"按钮
让我们一个一个来认识它们!
1. Text 组件 - 文字显示的万花筒
Text 是 Compose 中最简单的组件,也是最常用的组件。毕竟,没有文字的 App 就像没有文字的书,很难传达信息。
1.1 实战示例一:基础文本样式
让我们先看看 Text 组件的各种样式:
// 基础文本样式
Card(modifier = Modifier.fillMaxWidth()) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text = "基础文本样式",
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold
)
// 使用预设样式
Text(text = "默认文本样式 (bodyLarge)")
Text(
text = "标题样式 (headlineSmall)",
style = MaterialTheme.typography.headlineSmall
)
Text(
text = "副标题样式 (titleMedium)",
style = MaterialTheme.typography.titleMedium
)
Text(
text = "标签样式 (labelMedium)",
style = MaterialTheme.typography.labelMedium
)
}
}
代码讲解:
- Card 容器:用 Card 把相关内容包在一起,Card 会自动添加圆角和阴影,让内容有层次感
(先忽略下章介绍) - Column 布局:Column 让多个 Text 垂直排列,
spacedBy(8.dp)表示每个 Text 之间间隔 8dp(先忽略下章介绍) - 预设样式:可以看到,我们使用了
MaterialTheme.typography.xxx来获取预设样式,这样可以保证整个 App 的文字风格统一 - fontWeight.Bold:给标题加粗,让层级更清晰
运行效果:
你注意到没有?第一个 Text 没有设置 style,它使用的是默认的 bodyLarge 样式。而后面几个 Text 分别使用了不同的预设样式,大小和粗细都有明显区别。
1.2 Text 常用 API 详解
| API 参数 | 作用 | 使用场景 | 注意事项 |
|---|---|---|---|
text: String |
要显示的文本内容 | 显示任何文字 | 必需参数,支持字符串模板如 "计数: $count" |
style: TextStyle |
文本样式 | 统一文字风格 | 推荐使用 MaterialTheme.typography.xxx 而非硬编码 |
color: Color |
文字颜色 | 强调或区分文字 | 优先使用主题颜色,如 MaterialTheme.colorScheme.primary |
fontSize: TextUnit |
字体大小 | 自定义文字尺寸 | 使用 sp 单位以支持系统字体缩放,如 18.sp |
fontWeight: FontWeight |
字体粗细 | 强调重要内容 | 常用值:Bold(粗体)、Medium(中等)、Normal(正常) |
textAlign: TextAlign |
文本对齐 | 居中/两端对齐等 | 需配合 Modifier.fillMaxWidth() 使用才生效 |
maxLines: Int |
最大行数 | 限制显示行数 | 配合 overflow 使用,超出则截断 |
overflow: TextOverflow |
溢出处理 | 文本过长时的处理 | Clip(直接裁剪)、Ellipsis(显示省略号...) |
lineHeight: TextUnit |
行高 | 多行文本的行间距 | 提升长文本可读性,如 24.sp |
modifier: Modifier |
修饰符 | 控制尺寸、位置等 | 如 Modifier.padding(16.dp) |
1.3 Material Theme 预设样式
Material 3 预定义了一套字体样式,强烈推荐使用它们而不是硬编码:
| 样式 | 用途 | 示例场景 |
|---|---|---|
displayLarge/Medium/Small |
超大标题 | 启动页欢迎语 |
headlineLarge/Medium/Small |
大标题 | 页面主标题 |
titleLarge/Medium/Small |
中等标题 | 卡片标题、列表项标题 |
bodyLarge/Medium/Small |
正文 | 文章内容、描述文字 |
labelLarge/Medium/Small |
标签 | 按钮文字、输入框标签 |
使用预设样式的好处:
- 自动适配深色模式
- 统一的设计风格
- 支持无障碍功能
- 维护更方便
1.4 实战示例二:文本溢出处理
当文本内容过长时,我们需要控制它的显示方式:
// 文本溢出处理
Card(modifier = Modifier.fillMaxWidth()) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text = "文本溢出处理",
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold
)
// 方式一:直接裁剪
Text(
text = "这是一段很长的文本,如果超出容器宽度会如何显示?",
modifier = Modifier.width(200.dp),
maxLines = 1,
overflow = TextOverflow.Clip
)
// 方式二:显示省略号
Text(
text = "这是一段很长的文本,使用省略号处理溢出...",
modifier = Modifier.width(200.dp),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
// 方式三:两行后省略
Text(
text = "这是一段很长的文本,如果超出容器宽度会如何显示?",
modifier = Modifier.width(200.dp),
maxLines = 2,
overflow = TextOverflow.Ellipsis
)
}
}
代码讲解:
这里演示了三种处理长文本的方式:
TextOverflow.Clip:直接裁断超出的部分,文字会突然中断TextOverflow.Ellipsis+maxLines = 1:单行显示,超出部分用省略号(...)代替,最常用TextOverflow.Ellipsis+maxLines = 2:最多显示两行,第二行超出时用省略号
小技巧:Modifier.width(200.dp) 限制容器宽度是为了演示溢出效果,实际使用时可以根据需求调整。
运行效果:
你可以清楚地看到三种处理方式的区别:第一种直接截断很突兀,第二种用省略号最友好,第三种可以显示更多内容。
1.5 实战示例三:动态文本
Text 也可以显示动态内容,比如计数器:
// 动态文本
Card(modifier = Modifier.fillMaxWidth()) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text = "动态文本",
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold
)
var counter by remember { mutableIntStateOf(0) }
Text(
text = "点击次数: $counter",
fontSize = 18.sp,
fontWeight = FontWeight.Medium
)
Button(
onClick = { counter++ },
modifier = Modifier.align(Alignment.CenterHorizontally)
) {
Text("增加计数")
}
}
}
代码讲解:
remember { mutableIntStateOf(0) }:这是 Compose 的状态管理,counter会记住当前的计数值$counter:Kotlin 的字符串模板,直接把变量的值嵌入到字符串中onClick = { counter++ }:每次点击按钮,counter 就会加 1,Text 会自动更新显示
重点理解:在 Compose 中,你不需要手动去更新 UI,只需要改变状态,UI 会自动重组(Recompose)来反映新的状态。这就是 Compose 的核心思想——数据驱动 UI。
运行效果:

每次点击按钮,你会看到数字立即更新,这就是 Compose 响应式编程的魅力!
2. Button 组件 - 用户交互的主角
Button 是用户最常点击的组件,Material 3 提供了多种按钮样式,让你可以根据重要程度选择合适的按钮。
2.1 实战示例一:按钮家族大合影
让我们看看各种按钮的样式:
@Composable
fun ButtonDemo() {
var clickCount by remember { mutableIntStateOf(0) }
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
Text(
text = "Button 组件演示",
style = MaterialTheme.typography.headlineMedium,
fontWeight = FontWeight.Bold
)
Text(
text = "点击次数: $clickCount",
style = MaterialTheme.typography.bodyLarge,
modifier = Modifier.align(Alignment.CenterHorizontally)
)
// 基础按钮
Card(modifier = Modifier.fillMaxWidth()) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
Text(
text = "基础按钮",
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Button(
onClick = { clickCount++ },
modifier = Modifier.weight(1f)
) {
Text("主要按钮")
}
OutlinedButton(
onClick = { clickCount++ },
modifier = Modifier.weight(1f)
) {
Text("轮廓按钮")
}
}
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
TextButton(
onClick = { clickCount++ },
modifier = Modifier.weight(1f)
) {
Text("文本按钮")
}
ElevatedButton(
onClick = { clickCount++ },
modifier = Modifier.weight(1f)
) {
Text("凸起按钮")
}
}
}
}
}
}
代码讲解:
clickCount状态:用一个变量记录点击次数,所有按钮都共享这个状态verticalScroll:让整个 Column 可以滚动,防止内容超出屏幕Modifier.weight(1f):让两个按钮平分宽度,各占 50%Arrangement.spacedBy(8.dp):Row 中两个按钮之间间隔 8dp
运行效果:
你可以清楚地看到四种按钮的区别:
- Button:蓝色填充背景,最显眼
- OutlinedButton:有边框但透明,次一级
- TextButton:只有文字,最低调
- ElevatedButton:有阴影的填充按钮,介于 Button 和 OutlinedButton 之间
2.2 实战示例二:带图标的按钮
按钮不仅可以放文字,还可以放图标:
// 带图标的按钮
Card(modifier = Modifier.fillMaxWidth()) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
Text(
text = "带图标的按钮",
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Button(
onClick = { clickCount++ },
modifier = Modifier.weight(1f)
) {
Icon(
imageVector = Icons.Default.Add,
contentDescription = "添加",
modifier = Modifier.size(18.dp)
)
Spacer(modifier = Modifier.width(4.dp))
Text("添加")
}
OutlinedButton(
onClick = { clickCount++ },
modifier = Modifier.weight(1f)
) {
Icon(
imageVector = Icons.Default.Edit,
contentDescription = "编辑",
modifier = Modifier.size(18.dp)
)
Spacer(modifier = Modifier.width(4.dp))
Text("编辑")
}
}
}
}
代码讲解:
Icons.Default.Add:Material Design 提供的内置图标,无需自己画contentDescription:图标的内容描述,用于无障碍访问,比如屏幕阅读器会读出"添加按钮"Modifier.size(18.dp):控制图标大小,18dp 是按钮内图标的推荐尺寸Spacer:在图标和文字之间加一点间距,避免太挤
运行效果:
带图标的按钮更直观,用户一眼就知道这个按钮是干什么的。比如"添加"按钮带一个"+"图标,比纯文字"添加"更容易理解。
2.3 实战示例三:按钮状态控制
有时候我们需要根据条件控制按钮是否可用:
// 不同状态的按钮
Card(modifier = Modifier.fillMaxWidth()) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
Text(
text = "不同状态的按钮",
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold
)
Button(
onClick = { clickCount++ },
enabled = true,
modifier = Modifier.fillMaxWidth()
) {
Text("启用状态")
}
Button(
onClick = { /* 禁用状态下无法点击 */ },
enabled = false,
modifier = Modifier.fillMaxWidth()
) {
Text("禁用状态")
}
}
}
代码讲解:
enabled = true:按钮正常可用,点击会触发 onClickenabled = false:按钮禁用状态,颜色变灰,点击无效
常见使用场景:
- 表单未填写完成时,提交按钮禁用
- 正在加载数据时,防止重复点击
- 用户权限不足时,某些操作不可用
运行效果:
你可以看到,禁用状态的按钮颜色变灰,给用户明确的反馈——这个按钮现在不能点。
2.4 Button 常用 API 详解
| API 参数 | 作用 | 使用场景 | 注意事项 |
|---|---|---|---|
onClick: () -> Unit |
点击回调 | 处理用户点击事件 | 必需参数,点击时触发 |
enabled: Boolean |
是否启用 | 控制按钮可点击性 | false 时按钮变灰且无法点击 |
colors: ButtonColors |
按钮颜色 | 自定义按钮颜色 | 使用 ButtonDefaults.buttonColors() 创建 |
contentPadding: PaddingValues |
内容内边距 | 调整按钮内部间距 | 影响最小触摸区域,建议保留默认值 |
modifier: Modifier |
修饰符 | 控制尺寸、位置等 | 如 Modifier.fillMaxWidth() |
content: @Composable () -> Unit |
按钮内容 | 图标、文字等 | 必需参数,通常放 Text 和 Icon |
2.5 Button 家族成员
Material 3 提供了 5 种按钮,各有各的适用场景:
| 按钮类型 | 视觉特点 | 重要程度 | 使用场景 |
|---|---|---|---|
Button |
填充背景色 | ⭐⭐⭐⭐⭐ | 主要操作:提交、确认、购买 |
OutlinedButton |
有边框、透明背景 | ⭐⭐⭐ | 次要操作:取消、上一步 |
TextButton |
只有文字、无背景 | ⭐⭐ | 低优先级:"了解更多"、"跳过" |
ElevatedButton |
凸起的填充按钮 | ⭐⭐⭐⭐ | 需要突出的操作 |
IconButton |
只有图标 | ⭐⭐⭐ | 图标操作:收藏、分享、设置 |
按钮选择指南:
- 一个页面只有一个主操作 → 用
Button - 取消、返回等次要操作 → 用
OutlinedButton - "了解更多"类低优先级操作 → 用
TextButton - 只需要图标的操作 → 用
IconButton
3. TextField 组件 - 文本输入的百宝箱
TextField 是用户输入文本的地方,从搜索框到密码框,从评论框到邮箱输入,都离不开它。
3.1 TextField 常用 API 详解
| API 参数 | 作用 | 使用场景 | 注意事项 |
|---|---|---|---|
value: String |
输入框的当前内容 | 显示用户输入的文本 | 必需参数,需配合状态管理使用 |
onValueChange: (String) -> Unit |
值变化回调 | 更新输入框状态 | 必需参数,每次输入都会触发 |
label: @Composable () -> Unit |
浮动标签 | 说明输入框用途 | 聚焦时浮动到顶部,如 Text("邮箱") |
placeholder: @Composable () -> Unit |
占位符 | 提示用户输入内容 | 聚焦且有内容时消失,如 Text("请输入邮箱") |
leadingIcon: @Composable () -> Unit |
前置图标 | 增强输入框可识别性 | 如搜索图标、邮箱图标 |
trailingIcon: @Composable () -> Unit |
后置图标 | 附加操作按钮 | 如清除按钮、密码显示切换 |
visualTransformation: VisualTransformation |
内容转换 | 改变显示内容 | PasswordVisualTransformation() 用于密码隐藏 |
keyboardOptions: KeyboardOptions |
键盘选项 | 优化输入体验 | KeyboardType.Email、ImeAction.Next 等 |
isError: Boolean |
错误状态 | 表单验证提示 | true 时显示红色边框和错误样式 |
supportingText: @Composable () -> Unit |
辅助文本 | 错误提示/字符计数 | 显示在输入框下方 |
TextField 类型选择:
| 类型 | 外观 | 使用场景 | 推荐程度 |
|---|---|---|---|
TextField |
填充背景 | 表单中唯一的输入框 | ⭐⭐⭐ |
OutlinedTextField |
有边框 | 表单中有多个输入框 | ⭐⭐⭐⭐⭐ |
推荐使用 OutlinedTextField,因为边框能让用户清楚看到输入区域。
3.2 实战示例一:基础输入框
让我们从最简单的输入框开始:
@Composable
fun TextFieldDemo() {
var text by remember { mutableStateOf("") }
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
Text(
text = "TextField 组件演示",
style = MaterialTheme.typography.headlineMedium,
fontWeight = FontWeight.Bold
)
// 基础输入框
Card(modifier = Modifier.fillMaxWidth()) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
Text(
text = "基础输入框",
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold
)
OutlinedTextField(
value = text,
onValueChange = { text = it },
label = { Text("基础输入框") },
placeholder = { Text("请输入文本") },
modifier = Modifier.fillMaxWidth()
)
}
}
}
}
代码讲解:
value = text:显示输入框的当前内容,这个 text 是一个状态变量onValueChange = { text = it }:用户每次输入,都会触发这个回调,更新 text 状态label:浮动标签,当输入框获得焦点或有内容时,标签会浮动到顶部placeholder:占位符,提示用户该输入什么,内容为空时显示
运行效果:
当你点击输入框时,label 会自动浮动到顶部,这就是 Material Design 的浮动标签效果,非常优雅。
3.3 实战示例二:密码输入框
密码输入框需要隐藏输入内容,通常还有一个"显示/隐藏"按钮:
// 密码输入框
var password by remember { mutableStateOf("") }
var passwordVisible by remember { mutableStateOf(false) }
Card(modifier = Modifier.fillMaxWidth()) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
Text(
text = "密码输入框",
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold
)
OutlinedTextField(
value = password,
onValueChange = { password = it },
label = { Text("密码") },
visualTransformation = if (passwordVisible)
VisualTransformation.None
else
PasswordVisualTransformation(),
trailingIcon = {
IconButton(onClick = { passwordVisible = !passwordVisible }) {
Icon(
imageVector = if (passwordVisible) Icons.Default.Visibility
else Icons.Default.VisibilityOff,
contentDescription = if (passwordVisible) "隐藏" else "显示"
)
}
},
modifier = Modifier.fillMaxWidth()
)
}
}
代码讲解:
passwordVisible状态:记录密码是否可见visualTransformation:控制密码的显示方式PasswordVisualTransformation():密码显示为 ••••VisualTransformation.None:正常显示密码
trailingIcon:在输入框右侧添加一个按钮- 眼睛图标切换:根据
passwordVisible状态切换显示Visibility或VisibilityOff图标
运行效果:
点击眼睛图标,密码会在明文和密文之间切换。这是一个非常常见且实用的功能,提升了用户体验。
3.4 实战示例三:带验证的邮箱输入框
表单验证是输入框的重要功能:
// 邮箱输入框(带图标)
var email by remember { mutableStateOf("") }
var isError by remember { mutableStateOf(false) }
Card(modifier = Modifier.fillMaxWidth()) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
Text(
text = "邮箱",
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold
)
// 简单的邮箱验证
val isValidEmail = email.contains("@") && email.contains(".")
OutlinedTextField(
value = email,
onValueChange = {
email = it
isError = email.isNotEmpty() && !isValidEmail
},
label = { Text("邮箱") },
leadingIcon = {
Icon(Icons.Default.Email, contentDescription = "邮箱")
},
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Email,
imeAction = ImeAction.Next
),
modifier = Modifier.fillMaxWidth()
)
}
}
代码讲解:
- 邮箱验证逻辑:简单检查是否包含 "@" 和 "."(实际项目中应该用更严格的正则表达式)
leadingIcon:在输入框左侧添加邮箱图标,让用户一眼就知道这是邮箱输入框keyboardOptions:KeyboardType.Email:自动弹出邮箱键盘(带有 @ 符号)ImeAction.Next:键盘的回车键显示为"下一步"
运行效果:
4. FloatingActionButton 组件 - 浮动的魔法按钮
FAB(FloatingActionButton)是 Material Design 的标志性组件,它是一个浮在界面上的圆形按钮,通常用于最重要的操作。
4.1 FAB 常用 API 详解
| API 参数 | 作用 | 使用场景 | 注意事项 |
|---|---|---|---|
onClick: () -> Unit |
点击回调 | 处理点击事件 | 必需参数 |
containerColor: Color |
背景颜色 | 区分操作类型 | 默认使用主题的 primary 颜色 |
modifier: Modifier |
修饰符 | 控制尺寸、位置 | 通常配合 Scaffold 使用 |
content: @Composable () -> Unit |
按钮内容 | 通常是 Icon | 必需参数 |
FAB 尺寸选择:
| 类型 | 尺寸 | 使用场景 |
|---|---|---|
SmallFloatingActionButton |
小 | 紧凑界面 |
FloatingActionButton |
标准 | 通用场景 |
LargeFloatingActionButton |
大 | 需要强调时 |
ExtendedFloatingActionButton |
扩展 | 带文字的按钮 |
4.2 实战示例:FAB
FAB 通常是显示添加某个东西,如视频、图片等等。
@Composable
fun FABDemo() {
var fabCount by remember { mutableStateOf(0) }
Text(
text = "FBA点击次数 $fabCount",
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold
)
Card(
modifier = Modifier.fillMaxWidth()
) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
Text(
text = "基础FAB",
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.CenterVertically
) {
// 标准 FAB (Primary)
FloatingActionButton(
onClick = { fabCount++ }
) {
Icon(
imageVector = Icons.Default.Add,
contentDescription = "添加"
)
}
// 次要颜色 FAB (Secondary)
FloatingActionButton(
onClick = { fabCount++ },
containerColor = MaterialTheme.colorScheme.secondary
) {
Icon(
imageVector = Icons.Default.Edit,
contentDescription = "编辑"
)
}
// 错误色/警告色 FAB (Error)
FloatingActionButton(
onClick = { fabCount++ },
containerColor = MaterialTheme.colorScheme.error
) {
Icon(
imageVector = Icons.Default.Delete,
contentDescription = "删除"
)
}
}
}
}
}
运行效果:
每次点击右下角的 "+" 按钮,就会添加一个新项目。FAB 浮在内容之上,阴影让它看起来真的"浮"起来了。
FAB 使用场景推荐:
| 场景 | FAB 图标 | 操作 |
|---|---|---|
| 邮件列表 | Icons.Default.Email | 写邮件 |
| 聊天列表 | Icons.Default.Add | 新建聊天 |
| 待办事项 | Icons.Default.Check | 添加任务 |
| 相册 | Icons.Default.Camera | 拍照 |
| 编辑器 | Icons.Default.Save | 保存 |
5. 综合实战 - 完整的登录表单
学了这么多,让我们来个综合实战:做一个漂亮的登录表单!
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun LoginForm() {
var email by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
var passwordVisible by remember { mutableStateOf(false) }
var agreedToTerms by remember { mutableStateOf(false) }
var showError by remember { mutableStateOf(false) }
var isLoading by remember { mutableStateOf(false) }
val isFormValid = email.isNotBlank() && password.length >= 6 && agreedToTerms
Box(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
contentAlignment = Alignment.Center
) {
Card(
modifier = Modifier.fillMaxWidth(),
elevation = CardDefaults.cardElevation(defaultElevation = 8.dp)
) {
Column(
modifier = Modifier.padding(24.dp),
verticalArrangement = Arrangement.spacedBy(20.dp)
) {
// 标题
Text(
text = "欢迎回来",
style = MaterialTheme.typography.headlineMedium,
fontWeight = FontWeight.Bold,
modifier = Modifier.align(Alignment.CenterHorizontally)
)
// 邮箱输入
OutlinedTextField(
value = email,
onValueChange = {
email = it
showError = false
},
label = { Text("邮箱地址") },
leadingIcon = {
Icon(Icons.Default.Email, contentDescription = "邮箱")
},
isError = showError && email.isBlank(),
supportingText = {
if (showError && email.isBlank()) {
Text(
text = "请输入邮箱地址",
color = MaterialTheme.colorScheme.error
)
}
},
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Email,
imeAction = ImeAction.Next
),
modifier = Modifier.fillMaxWidth()
)
// 密码输入
OutlinedTextField(
value = password,
onValueChange = {
password = it
showError = false
},
label = { Text("密码") },
leadingIcon = {
Icon(Icons.Default.Lock, contentDescription = "密码")
},
trailingIcon = {
IconButton(onClick = { passwordVisible = !passwordVisible }) {
Icon(
imageVector = if (passwordVisible) Icons.Default.Visibility
else Icons.Default.VisibilityOff,
contentDescription = if (passwordVisible) "隐藏" else "显示"
)
}
},
visualTransformation = if (passwordVisible) VisualTransformation.None
else PasswordVisualTransformation(),
isError = showError && password.length < 6,
supportingText = {
if (showError && password.length < 6) {
Text(
text = "密码至少需要6位字符",
color = MaterialTheme.colorScheme.error
)
}
},
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Password,
imeAction = ImeAction.Done
),
modifier = Modifier.fillMaxWidth()
)
// 同意条款
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
Checkbox(
checked = agreedToTerms,
onCheckedChange = { agreedToTerms = it }
)
Spacer(modifier = Modifier.width(8.dp))
Text("我同意用户协议")
}
// 登录按钮
Button(
onClick = {
if (isFormValid) {
isLoading = true
// 模拟登录请求
} else {
showError = true
}
},
enabled = !isLoading && isFormValid,
modifier = Modifier.fillMaxWidth()
) {
if (isLoading) {
CircularProgressIndicator(
modifier = Modifier.size(20.dp),
strokeWidth = 2.dp
)
} else {
Text("登录")
}
}
// 注册链接
Row(
modifier = Modifier.align(Alignment.CenterHorizontally)
) {
Text("还没有账号?")
TextButton(onClick = { }) {
Text("立即注册")
}
}
}
}
}
}
代码讲解:
这个登录表单综合运用了今天学的所有组件:
-
状态管理:用 6 个状态变量管理表单的各个部分
email、password:输入内容passwordVisible:密码显示状态agreedToTerms:是否同意条款showError:是否显示错误isLoading:是否正在加载
-
表单验证:
isFormValid综合判断表单是否有效- 邮箱不能为空
- 密码至少 6 位
- 必须同意条款
-
用户体验优化:
- 输入框有前置图标,清晰标识用途
- 密码框有显示/隐藏切换
- 错误时实时显示错误提示
- 登录按钮只在表单有效时可点击
- 登录中显示加载动画
-
布局技巧:
- 用 Box +
contentAlignment = Alignment.Center让卡片居中 - Card 添加阴影让它有层次感
verticalArrangement = Arrangement.spacedBy(20.dp)统一间距
- 用 Box +
运行效果:

这是一个完整可用的登录表单!你可以尝试:
- 输入错误的邮箱格式,会看到错误提示
- 密码少于 6 位,会提示密码长度不足
- 不勾选同意条款,登录按钮不可点击
- 填写完整后点击登录,按钮会显示加载动画
6. 最佳实践 & 常见坑
6.1 Text 最佳实践
| ✅ 推荐 | ❌ 不推荐 |
|---|---|
使用 MaterialTheme.typography.xxx |
硬编码 fontSize = 18.sp |
设置 maxLines 防止溢出 |
让文字无限延伸 |
| 提取字符串到资源文件 | 直接写死中文 |
用 buildAnnotatedString 做富文本 |
用多个 Text 拼接 |
6.2 Button 最佳实践
| 建议 | 说明 |
|---|---|
| 一屏一个主按钮 | 不要放太多主要按钮,用户会困惑 |
| 按钮文字用动词 | "登录"、"保存",不要用 "登录按钮" |
| 及时反馈 | 点击后立即给反馈(加载动画、Toast 等) |
| 保持最小触摸区域 | 至少 48x48 dp,确保可点击 |
6.3 TextField 最佳实践
| 建议 | 说明 |
|---|---|
| 提供清晰的 label | 让用户知道该输什么 |
| 设置正确的键盘类型 | 电话号用 Phone,邮箱用 Email |
| 实时验证 | 不要等提交才报错,输入时就可以提示 |
| 添加 contentDescription | 方便无障碍访问 |
| 使用 OutlinedTextField | 多个输入框时更清晰 |
6.4 FAB 最佳实践
| ✅ 正确使用 | ❌ 错误使用 |
|---|---|
| 主要操作(新建、保存) | 次要操作(取消、返回) |
| 放在屏幕右下角 | 放在其他位置 |
| 每屏最多一个 FAB | 放多个 FAB |
6.5 常见坑
坑 1:Modifier 顺序很重要
// ❌ 错误:背景会被 padding 裁剪
Modifier.background(Color.Red).padding(16.dp)
// ✅ 正确:padding 在背景之前
Modifier.padding(16.dp).background(Color.Red)
记住:Modifier 链是从右到左应用的!先应用 padding,再应用 background。
坑 2:记得处理状态
// ❌ 错误:没有更新状态
OutlinedTextField(
value = text, // text 永远不变
onValueChange = { } // 空实现
)
// ✅ 正确:更新状态
var text by remember { mutableStateOf("") }
OutlinedTextField(
value = text,
onValueChange = { text = it } // 更新状态
)
坑 3:忘记设置键盘类型
// ❌ 忘记设置键盘类型,默认键盘不好用
OutlinedTextField(
value = phone,
onValueChange = { phone = it },
label = { Text("手机号") }
)
// ✅ 设置键盘类型,体验更好
OutlinedTextField(
value = phone,
onValueChange = { phone = it },
label = { Text("手机号") },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Phone)
)
7. 总结
今天我们学习了四个最基础的显示组件:
| 组件 | 用途 | 关键点 |
|---|---|---|
| Text | 显示文字 | 用 Material Theme 样式,注意溢出处理 |
| Button | 用户交互 | 根据重要程度选择类型,一屏一个主按钮 |
| TextField | 文本输入 | 用 OutlinedTextField,记得验证和设置键盘类型 |
| FAB | 主要操作 | 悬浮在右下角,一屏一个,用于最重要的操作 |
这些组件就像盖房子的砖块,虽然简单,但无处不在。掌握它们,你就踏出了 Compose 开发的第一步!
你学到了什么?
- Text 可以显示各种样式的文字,记得用 Material Theme 预设样式
- Button 有多种类型,根据操作的重要程度选择
- TextField 需要配合状态管理使用,记得做表单验证
- FAB 用于最重要的操作,通常配合 Scaffold 使用
下篇预告
基础组件学会了,但怎么把它们组织起来呢?
下一篇,我们将学习布局和容器组件:Card、Surface、Scaffold...它们会把你的组件排列得整整齐齐,让界面既美观又实用。
敬请期待!
本文代码来自 Compose Material 3 组件演示项目,欢迎 Star 收藏!

浙公网安备 33010602011771号