@Composable注解是Jetpack Compose的基本元素,它将普通的Kotlin函数转变为声明式的用户界面构建块。没有这个注解,在现代Android开发中无法创建任何屏幕。根据Google Android Developers, 2026的数据,超过80%的Kotlin新项目使用Compose构建UI,而@Composable是生态系统中使用最频繁的注解。
要点
@Composable是Kotlin语言的一种注解,它将函数标记为用于在Jetpack Compose框架中描述用户界面。当Kotlin编译器遇到这个注解时,它会生成额外的代码,使函数能够在组合上下文中工作——UI树管理系统。
@Composable注解由Google于2021年随Jetpack Compose 1.0的第一个稳定版本一起推出。在它出现之前,Android界面的开发完全通过XML标记和View系统进行。@Composable彻底改变了方法:程序员不再在单独的标记文件中描述UI,而是直接在Kotlin中编写界面。
@Composable与普通Kotlin函数的主要区别——读取和响应状态变化的能力。当Composable函数读取的变量改变值时,系统会自动安排该函数的重启(重组)。这使程序员免于通过findViewById和setText手动更新UI。
@Composable的内部机制基于插槽概念——为组合中每个函数分配的特殊内存区域。在这个插槽中存储传递给函数的值,以及后续调用比较所需的辅助信息。
要声明Composable函数,只需在fun关键字前添加@Composable注解。函数必须位于从androidx.compose.runtime导入注解的包中。建议函数名以大写字母开头——这是Compose社区中普遍接受的约定,可以将UI组件与普通函数视觉上区分开来。
import androidx.compose.runtime.Composable
@Composable
fun Greeting(name: String) {
var count by remember { mutableStateOf(0) }
Column {
Text("你好,$name!")
Button(onClick = { count++ }) {
Text("点击了$count次")
}
}
}
Composable函数的参数可以是任何类型——原始类型、字符串、lambda甚至通过Slot API传递的其他Composable函数。建议使参数不可变(val),以避免重组时的副作用。所有可变数据应通过Compose的状态机制进行管理。
Composable函数不能像普通函数那样返回任意值——它们的唯一任务是构建或更新UI树的一部分。然而存在特殊的模式,如State Hoisting,其中Compose函数通过参数接收状态和回调,保持纯净和可复用。
Compose系统对Composable函数的外观和行为施加了几个严格的限制。第一条规则:Composable函数只能调用其他Composable函数或没有副作用的普通函数。这确保了组合的可预测性和Compose优化的正确运行。
第二条规则涉及执行顺序。Compose有权以任何顺序调用Composable函数,因此函数体中的代码不应依赖于相邻函数的调用顺序。每个Composable函数应在UI树中其位置级别上自给自足。
第三条规则——禁止副作用在Composable函数体内部。像写入数据库、发送网络请求或更改外部变量等操作只能在LaunchedEffect、DisposableEffect或SideEffect等特殊效果内部执行。违反此规则会导致重组时的不可预测行为。
第四条规则:Composable函数应该是幂等的。使用相同参数的重复调用应产生相同的UI。这个要求对于跳过优化的正确工作至关重要,在这种优化中,Compose会跳过那些输入数据未变化的函数的重新绘制。
// 正确:无副作用的纯净Composable函数
@Composable
fun UserCard(user: User, onClick: () -> Unit) {
Card(modifier = Modifier.clickable { onClick() }) {
Text(text = user.name)
}
}
// 错误:函数体中的副作用
@Composable
fun WrongCard(userId: String) {
// val result = viewModel.loadUser(userId) // 不允许
Text("加载中...")
}
让我们看一个使用@Composable注解创建个人资料屏幕的实际示例。这里我们演示多个Composable函数的组合、状态管理和修饰符的使用——任何Compose布局的关键元素。
@Composable
fun ProfileScreen(userId: String) {
var isFollowed by remember { mutableStateOf(false) }
Column(modifier = Modifier.fillMaxSize().padding(16.dp)) {
ProfileHeader(userId = userId)
Spacer(modifier = Modifier.height(16.dp))
StatsRow(posts = 42, followers = 1280)
Spacer(modifier = Modifier.height(24.dp))
FollowButton(
isFollowed = isFollowed,
onToggle = { isFollowed = !isFollowed }
)
}
}
@Composable
fun ProfileHeader(userId: String) {
Row(verticalAlignment = Alignment.CenterVertically) {
AsyncImage(model = "https://example.com/avatars/$userId",
contentDescription = "User avatar")
Spacer(modifier = Modifier.width(12.dp))
Text(text = "用户 #$userId", style = MaterialTheme.typography.headlineMedium)
}
}
@Composable
fun StatsRow(posts: Int, followers: Int) {
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceEvenly) {
StatItem("Posts", posts)
StatItem("Followers", followers)
}
}
@Composable
fun StatItem(label: String, value: Int) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(text = "$value", style = MaterialTheme.typography.headlineSmall)
Text(text = label, style = MaterialTheme.typography.bodySmall)
}
}
在示例中,每个Composable函数负责屏幕的其部分:ProfileScreen管理整体状态和子函数的组合,ProfileHeader显示头像和名称,StatsRow显示统计块。这种方法遵循单一职责原则,简化了组件的复用。
在Jetpack Compose中有三种主要类型的Composable函数。第一种类型——容器(Row, Column, Box, LazyColumn),决定子元素的排列。第二种——显示元素(Text, Image, Icon, Button),渲染特定的UI组件。第三种——自定义Composable函数,将内置组件组合成可复用块。
容器与普通元素不同之处在于它们接受content lambda——@Composable () -> Unit类型的最后一个参数。正是这种机制允许构建嵌套的UI树:每个容器创建具有自己上下文和内存区域的子组合。
自定义Composable函数分为两种子类型:智能(smart)和哑(dumb)。智能函数管理状态和逻辑——包含对remember、LaunchedEffect和其他Compose API的调用。哑函数通过参数接收所有数据并仅显示它们。分为智能和哑组件提高了可测试性和代码复用性。
| 类型 | 示例 | 用途 |
|---|---|---|
| 容器 | Column, Row, Box | 管理子元素的排列 |
| 元素 | Text, Image, Button | 显示内容和处理输入 |
| 自定义 | ProfileCard, UserList | 标准组件的组合 |
@Composable注解的主要优势——无需继承和复杂的类层次结构即可创建可复用的UI组件。与View系统不同,其中每个自定义元素需要创建带有构造函数的Java类,Composable组件只是一个带有参数的Kotlin函数。
为了确保复用,应用了Slot API模式,其中Composable函数接受其布局不同区域的content lambda。例如,Card组件可以为标题、正文和底部接受单独的内容,使其适用于应用程序的任何屏幕。
修饰符(Modifier)在复用中扮演关键角色:它们允许调整间距、大小、点击和动画,而无需更改组件本身。建议始终将Modifier作为Composable函数的参数传递,并带有默认值:Modifier = Modifier——这是Google官方库中采纳的标准实践。
@Composable
fun SectionCard(
modifier: Modifier = Modifier,
title: String,
content: @Composable () -> Unit
) {
Card(modifier = modifier) {
Column(modifier = Modifier.padding(16.dp)) {
Text(text = title, style = MaterialTheme.typography.titleMedium)
Spacer(modifier = Modifier.height(8.dp))
content()
}
}
}
得益于Slot API,SectionCard组件可以在不同屏幕上用于不同的内容——表单、列表、文本块。修饰符和Slot API的结合使Compose组件在保持Kotlin提供的类型安全性的同时具有最大的灵活性。
常见问题
@Composable函数在组合上下文中执行,可以读取状态,在状态改变时自动重启。普通Kotlin函数无法访问状态跟踪机制,不参与UI树的构建。
不能,Composable函数只能从其他Composable函数调用,因为需要特殊的组合上下文。要将Compose代码与普通Kotlin集成,使用Activity中的setContent { }方法或View系统中的ComposeView。
这是Compose社区采纳的命名约定。大写字母视觉上区分UI组件与普通函数,遵循类的命名规则。这不是编译器的要求,而是Google文档中推荐的实践。
数量没有限制。实际上,一个大屏幕可以包含50–100个Composable函数,包括内置组件(Text, Button)和自定义组件。Compose优化函数树,只执行那些输入数据已更改的函数。
通常Composable函数返回Unit,因为它们的任务是构建UI。但存在专门的函数如remember和derivedStateOf,它们被@Composable标记并返回值。这是例外,不是规则。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。