
你以为你在写UI?不,你在描述"状态到界面的映射函数"。不再是"创建→更新→销毁",而是"状态变,UI自动变"。
Compose最基础的单位是@Composable函数。它不是传统意义上的"组件类",而是一个纯函数——输入状态,输出UI描述。
最基础的Compose UI(对比传统XML):
// 传统XML方式(命令式)
// activity_main.xml:
// <TextView android:id="@+id/tv_name" android:text="Hello" />
// 在Activity中:findViewById<TextView>(R.id.tv_name).text = "World"
// Compose方式(声明式)
@Composable
fun Greeting(name: String) {
// 直接描述UI,不需要findViewById
Text(
text = "Hello $name!",
fontSize = 24.sp,
color = Color.Blue,
modifier = Modifier.padding(16.dp)
)
}
// 使用方式
@Composable
fun MainScreen() {
Greeting(name = "Jetpack Compose")
}工程要点拆解:
@Composable函数就是一个UI片段,可以无限组合findViewById、没有setText,UI是"描述"出来的组合的优势:
@Composable
fun UserCard(user: User) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.background(Color.White, RoundedCornerShape(8.dp))
) {
Row {
Image(
painter = rememberImagePainter(user.avatar),
contentDescription = "Avatar",
modifier = Modifier.size(48.dp).clip(CircleShape)
)
Spacer(modifier = Modifier.width(16.dp))
Column {
Text(text = user.name, fontSize = 18.sp, fontWeight = FontWeight.Bold)
Text(text = user.email, fontSize = 14.sp, color = Color.Gray)
}
}
}
}组合 vs 继承:Compose抛弃了View的继承体系,改用组合方式。你不再需要继承LinearLayout、重写onMeasure,只需要把基础组件拼装起来。
Compose最革命性的设计是状态驱动UI。当状态变化时,受影响的可组合函数自动"重组"(Recompose)——只更新变化的部分,不是整个页面刷新。
计数器示例(展示状态驱动):
@Composable
fun Counter() {
// mutableStateOf:Compose的"响应式变量"
var count by remember { mutableStateOf(0) }
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxSize()
) {
Text(
text = "点击次数: $count",
fontSize = 30.sp
)
Button(
onClick = { count++ } // 状态变化触发重组
) {
Text("点击增加")
}
}
}状态三要素拆解:
mutableStateOf:创建可观察状态,Compose会监听它的变化remember:记住状态值,重组时不重新初始化(否则每次重组count都归零)by委托:Kotlin属性委托,让count像普通变量一样读写(实际调用getValue/setValue)状态的"智能更新":
@Composable
fun SmartUpdate(name: String, age: Int) {
// 只有name变化时,这个Text才重组
Text(text = "Name: $name")
// 只有age变化时,这个Text才重组
Text(text = "Age: $age")
// 如果name和age都没变,整个函数不执行
}Compose编译器会分析每个@Composable函数,只重组依赖变化状态的代码块,不是整个函数。
状态提升(State Hoisting):
// 状态提升到父组件,实现数据单向流动
@Composable
fun ParentScreen() {
var text by remember { mutableStateOf("") }
ChildInput(
value = text,
onValueChange = { text = it } // 子组件通过回调修改父状态
)
}
@Composable
fun ChildInput(value: String, onValueChange: (String) -> Unit) {
TextField(
value = value,
onValueChange = onValueChange,
label = { Text("输入内容") }
)
}Compose函数是纯函数,不能直接做网络请求、数据库操作、Toast弹窗。这些"副作用"需要放到特定作用域中。
LaunchedEffect:在可组合函数中执行协程:
@Composable
fun UserProfile(userId: String) {
var user by remember { mutableStateOf<User?>(null) }
var isLoading by remember { mutableStateOf(true) }
// LaunchedEffect:进入组合时启动协程,离开时自动取消
LaunchedEffect(userId) { // userId变化时重新执行
isLoading = true
try {
user = fetchUserFromApi(userId) // 挂起函数
} catch (e: Exception) {
// 错误处理
} finally {
isLoading = false
}
}
if (isLoading) {
CircularProgressIndicator()
} else {
user?.let { UserCard(it) }
}
}其他副作用API:
@Composable
fun SideEffectDemo() {
// DisposableEffect:需要清理的副作用(注册/注销监听器)
DisposableEffect(Unit) {
val listener = registerListener()
onDispose {
unregisterListener(listener) // 离开时清理
}
}
// SideEffect:每次重组都执行(不常用,用于和外部系统同步)
SideEffect {
Log.d("Compose", "重组了")
}
// rememberCoroutineScope:在非组合上下文中启动协程
val scope = rememberCoroutineScope()
Button(onClick = {
scope.launch {
// 点击时执行异步操作
performHeavyOperation()
}
}) {
Text("执行任务")
}
}Compose抛弃了XML的match_parent、wrap_content,改用Modifier链来定制UI外观和行为。
常用布局对比:
@Composable
fun LayoutDemo() {
// Column:垂直排列(相当于LinearLayout垂直)
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.SpaceEvenly,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("顶部")
Text("中间")
Text("底部")
}
// Row:水平排列
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text("左")
Text("中")
Text("右")
}
// Box:叠加布局(相当于FrameLayout)
Box(
modifier = Modifier.size(200.dp),
contentAlignment = Alignment.Center
) {
Image(...) // 背景
Text("居中文字") // 前景
}
}
// Modifier链式调用(组合多个修饰符)
@Composable
fun StyledButton() {
Text(
text = "点击我",
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.background(Color.Blue, RoundedCornerShape(8.dp))
.clickable { /* 点击事件 */ }
.padding(24.dp), // 内边距
color = Color.White,
textAlign = TextAlign.Center
)
}Modifier顺序很重要:
// 顺序1:padding在外,background在内
Modifier
.padding(16.dp) // 先留外间距
.background(Color.Red) // 再画背景(背景在padding内部)
// 顺序2:background在外,padding在内
Modifier
.background(Color.Red) // 先画背景
.padding(16.dp) // 再留内间距(背景在padding外部)
// 视觉效果完全不同!Compose用LazyColumn(懒加载列表)代替RecyclerView,但API简洁10倍。
@Composable
fun UserList(users: List<User>) {
LazyColumn {
// items:渲染列表
items(users) { user ->
UserCard(user)
}
// item:渲染单个元素
item {
Text("列表头部", style = MaterialTheme.typography.h5)
}
// stickyHeader:吸顶标题
stickyHeader {
Text("A组", modifier = Modifier.background(Color.White))
}
// 分页加载
items(
items = users,
key = { it.id } // 给每个item唯一key,优化重组
) { user ->
UserCard(user)
}
}
}LazyColumn的性能优化:
@Composable
fun OptimizedList(users: List<User>) {
LazyColumn {
items(
items = users,
key = { user -> user.id } // 1. 提供稳定key
) { user ->
// 2. 每个item独立可组合,不影响其他item
UserCard(
user = user,
modifier = Modifier.animateItemPlacement() // 3. 动画自动处理
)
}
}
}Compose内置Material Design 3支持,用MaterialTheme统一管理颜色、字体、形状。
// 自定义主题
@Composable
fun MyAppTheme(content: @Composable () -> Unit) {
val colors = lightColors(
primary = Color(0xFF6200EE),
secondary = Color(0xFF03DAC6),
background = Color(0xFFF5F5F5)
)
MaterialTheme(
colors = colors,
typography = Typography(
h1 = TextStyle(fontSize = 30.sp, fontWeight = FontWeight.Bold),
body1 = TextStyle(fontSize = 16.sp)
),
shapes = Shapes(
small = RoundedCornerShape(4.dp),
medium = RoundedCornerShape(8.dp),
large = RoundedCornerShape(16.dp)
)
) {
content()
}
}
// 使用主题
@Composable
fun ThemedText() {
Text(
text = "Hello World",
style = MaterialTheme.typography.h1,
color = MaterialTheme.colors.primary
)
}
// 支持深色模式
@Composable
fun AutoTheme(content: @Composable () -> Unit) {
val isDark = isSystemInDarkTheme()
val colors = if (isDark) darkColors() else lightColors()
MaterialTheme(colors = colors, content = content)
}传统View体系:
状态变化 → findViewById → setText/setVisibility → 手动更新UICompose体系:
状态变化 → 自动重新执行@Composable函数 → 差异化更新UI核心思想转换:
最后一句忠告:
Compose不是"另一种XML",它是"另一种思考方式"。如果你还在找
findViewById、还在想notifyDataSetChanged,说明你还没放下过去的包袱。真正掌握Compose的标志是——当需求变化时,你最先想到的是"改哪个状态变量",而不是"改哪个UI控件"。
附加:调试技巧(重组计数):
@Composable
@Preview
fun DebugRecomposition() {
// 在Logcat中查看重组次数
val recompositionCount = remember { mutableStateOf(0) }
SideEffect {
recompositionCount.value++
Log.d("Compose", "重组次数: ${recompositionCount.value}")
}
Text("重组了 ${recompositionCount.value} 次")
}看到疯狂重组时,检查是否在@Composable中直接创建了mutableStateOf(没包remember),或者lambda表达式创建了新对象导致重组死循环。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。