第三篇:可组合函数(Composable)——Compose 的基石

3.1 什么是可组合函数

可组合函数是带有 @Composable 注解的 Kotlin 函数,它是 Compose 构建 UI 的最小单元。

kotlin 复制代码
@Composable
fun TextMessage(text: String) {
    Text(text = text)
}

但这段看似普通的函数背后,蕴藏着大量编译器魔法。

3.2 @Composable 注解的编译器魔法

编译器插件的插入

当你给函数加上 @Composable 注解,Kotlin Compose 编译器插件会做三件事:

① 添加额外的参数

编译器会为每个 Composable 函数隐式添加 ComposerApplier 参数:

kotlin 复制代码
// 你的代码
@Composable
fun TextMessage(text: String) {
    Text(text = text)
}

// 编译器转换后的等效代码(概念简化)
fun TextMessage(text: String, $composer: Composer, $applier: Applier<Any?>) {
    // 1. 开始组合作用域
    $composer.startRestartGroup()
    // 2. 状态追踪
    $composer.track(text)
    // 3. 调用子 Composable
    Text(text = text, composer = $composer, applier = $applier)
    // 4. 结束作用域
    $composer.endRestartGroup()
}

② 插入重组代码

每次状态变化时,Compose 运行时通过 Composer 对象跟踪哪些 Composable 函数读取了哪些状态。当状态改变时,只重新执行对应的函数。

③ 生成位置信息

编译器为每个组合单元分配 groupKey 和位置信息,用于 Compose 在重组时准确定位和复用。

@Composable 的传染性

kotlin 复制代码
@Composable
fun Outer() {
    Inner()  // ✅ OK,在 Composable 上下文中
}

@Composable
fun Inner() {
    Text("Hello")
}

fun NonComposable() {
    // Text("Hello")  ❌ 编译错误!
    // 不能在非 Composable 函数中调用 Composable 函数
}

@Composable传染性的------被它标记的函数只能在同一上下文中被调用。这意味着你的 UI 代码天然地被限制在正确的组合作用域内。

3.3 Composable 函数的三大规则

规则一:无副作用(Side Effect Free)

Composable 函数可能因重组被频繁调用,因此它必须幂等------相同的输入产生相同的输出。

kotlin 复制代码
// ❌ 违反:Composable 中直接执行副作用
@Composable
fun BadCounter() {
    val count = mutableStateOf(0)
    // 每次重组都会创建新的协程!
    CoroutineScope(Dispatchers.IO).launch {
        saveToDatabase(count.value)
    }
    Text("Count: ${count.value}")
}

// ✅ 正确:使用 LaunchedEffect 管理副作用
@Composable
fun GoodCounter() {
    var count by remember { mutableStateOf(0) }
    // 仅在首次组合时执行
    LaunchedEffect(Unit) {
        saveToDatabase(count)
    }
    Text("Count: $count")
}

规则二:不依赖外部条件执行

kotlin 复制代码
// ❌ 违反:依赖函数调用顺序
var globalFlag = false

@Composable
fun A() {
    if (globalFlag) Text("A")  // 外部条件不可控
}

@Composable
fun B() {
    globalFlag = true
    Text("B")
}

Composable 函数的执行顺序不应影响结果,否则重组时会出现难以追踪的 bug。

规则三:重组应尽可能快速

Composable 函数可能因动画或高频状态变化而被频繁调用。不要在 Composable 中进行:

kotlin 复制代码
// ❌ 避免:阻塞性操作
@Composable
fun SlowComponent(data: List<Item>) {
    // 每次重组都进行大量排序计算
    val sorted = data.sortedBy { it.complexCalculation() }
    LazyColumn { items(sorted) { Item(it) } }
}

// ✅ 优化:用 derivedStateOf 或 remember 缓存
@Composable
fun FastComponent(data: List<Item>) {
    val sorted by remember(data) {
        derivedStateOf { data.sortedBy { it.complexCalculation() } }
    }
    LazyColumn { items(sorted) { Item(it) } }
}

3.4 Composable 函数命名规范与最佳实践

命名规范

  • 名词大写开头UserProfileMessageListLoadingIndicator
  • 动词短语加 by 后缀Button 而不是 DisplayButton
  • 有语义,无歧义:宁长勿短

参数惯例

kotlin 复制代码
// 惯例 1:modifier 作为第一个可选参数
@Composable
fun UserAvatar(
    url: String,
    modifier: Modifier = Modifier,  // 第一个可选参数
    size: Int = 48,
    onClick: (() -> Unit)? = null
)

// 惯例 2:内容 lambda 作为最后一个参数
@Composable
fun Card(
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit  // 最后一个参数
)

这是遵循 Material Design 组件的设计惯例,让组件调用方式统一。

单一职责

kotlin 复制代码
// ❌ 一个 Composable 包含太多逻辑
@Composable
fun ComplexScreen() {
    var text by remember { mutableStateOf("") }
    var items by remember { mutableStateOf(listOf<Item>()) }
    var isLoading by remember { mutableStateOf(false) }

    Column {
        // 搜索栏
        TextField(value = text, onValueChange = { text = it })
        // 列表
        if (isLoading) CircularProgressIndicator()
        else LazyColumn { items(items) { ... } }
        // 底部按钮
        Button(onClick = {}) { Text("Submit") }
    }
}

// ✅ 拆分为多个小函数
@Composable
fun SearchScreen() {
    var query by remember { mutableStateOf("") }
    var state by remember { mutableStateOf<UiState>(Loading) }

    Column {
        SearchBar(query = query, onQueryChange = { query = it })
        when (val s = state) {
            is Loading -> LoadingIndicator()
            is Success -> ResultList(s.items)
            is Error -> ErrorView(s.message)
        }
        SubmitButton()
    }
}

原则:一个 Composable 只做一件事。既是代码组织的良好实践,也是重组优化的关键------更小的函数意味着更精确的重组范围。

3.5 @Preview 进阶用法

kotlin 复制代码
// 基础预览
@Preview(showBackground = true)
@Composable
fun SimplePreview() { ... }

// 多设备预览
@Preview(
    showBackground = true,
    showSystemUi = true,
    device = "id:pixel_6_pro"
)
@Preview(
    showBackground = true,
    device = "id:pixel_tablet"
)
@Composable
fun MultiDevicePreview() { ... }

// 深色模式
@Preview(
    uiMode = Configuration.UI_MODE_NIGHT_YES,
    name = "Dark Mode"
)
@Composable
fun DarkPreview() { ... }

// 字体缩放
@Preview(fontScale = 1.5f)
@Composable
fun LargeFontPreview() { ... }

// 带参数预览 ------ 用 @PreviewParameter
class MessageParameterProvider : PreviewParameterProvider<Message> {
    override val values = sequenceOf(
        Message("Hello", "John"),
        Message("Long text...", "Alice")
    )
}

@Preview
@Composable
fun MessagePreview(
    @PreviewParameter(MessageParameterProvider::class) msg: Message
) {
    MessageCard(msg)
}

多预览技巧 :对同一个 Composable 添加多个 @Preview 注解(如上例的多设备),Android Studio Preview 面板会并排显示所有配置的渲染效果。

3.6 方法引用与函数式思想

在 Compose 中,Composable 函数是一等公民。你可以像传递数据一样传递 UI 函数:

kotlin 复制代码
// 将 Composable 作为参数传递
@Composable
fun ScreenLayout(
    header: @Composable () -> Unit,
    content: @Composable () -> Unit,
    footer: @Composable () -> Unit = {}
) {
    Column {
        Box(modifier = Modifier.height(56.dp)) { header() }
        Box(modifier = Modifier.weight(1f)) { content() }
        footer()
    }
}

// 使用时
ScreenLayout(
    header = { TopAppBar(title = { Text("Home") }) },
    content = { Greeting("Android") },
    footer = { BottomNavigationBar() }
)

这种函数式的组合方式,使 UI 复用达到前所未有的灵活度------不需要继承、不需要接口回调。

3.7 本章练习

  1. 写一个 UserCard Composable,包含头像、昵称、简介三个展示元素
  2. UserCard 添加多设备 Preview(手机 + 平板 + 深色模式)
  3. 创建一个 ProfileScreen,组合 SearchBar + UserCard + PostList 三个子 Composable
  4. 尝试将 Composable 函数作为参数传递,实现一个通用的 LoadingWrapper 组件
kotlin 复制代码
// 💡 LoadingWrapper 示例
@Composable
fun <T> LoadingWrapper(
    state: UiState<T>,
    onRetry: () -> Unit,
    content: @Composable (data: T) -> Unit
) {
    when (state) {
        is Loading -> CircularProgressIndicator()
        is Success -> content(state.data)
        is Error -> ErrorView(state.message, onRetry)
    }
}

3.8 本章小结

内容 要点
本质 @Composable 注解的 Kotlin 函数,编译器为之生成重组追踪代码
三大规则 无副作用、不依赖外部顺序、快速执行
命名规范 名词大写开头、modifier 惯例、content lambda 惯例
单一职责 一个 Composable 做一件事,利于复用和优化
Preview 多设备、深色模式、字体缩放、PreviewParameter 多种姿势
函数式复用 Composable 可作为参数传递,实现高阶 UI 模式

下一篇:布局系统总览------从 Row、Column 到 Box 的声明式布局思维。

相关推荐
前端环境观察室1 小时前
别只看 task success:AI Agent 浏览器自动化真正要补的是环境证据链
前端·后端
huakoh1 小时前
LangChain 实战:用混合检索啃下 1000 页 PDF,搭一个长文档问答 Agent
前端
Dazer0071 小时前
Edge 浏览器绕过 HTTPS 证书错误
前端·https·edge
元让_vincent1 小时前
Spark 2.0:面向 Web 的 3DGS 可视化与大场景渲染平台详解
前端·3d·spark·渲染·轻量化·3dgs·lod
KaMeidebaby2 小时前
卡梅德生物技术快报|酵母双杂交 cDNA 文库构建与蛋白互作筛选流程
服务器·前端·数据库·人工智能·算法
独隅2 小时前
Android Studio 接入多种不同 AI 大模型进行开发的全面详细指南(Android Studio+AI)
android·人工智能·android studio
沐风___2 小时前
App 上架之后:如何看数据、获取用户与持续迭代产品
服务器·前端·数据库
夜微凉42 小时前
三、MySQL
android·数据库·mysql
我命由我123452 小时前
Android 开发问题:项目同时引入了两个包含相同类文件的库(AndroidX 库、旧版本支持库),导致了重复类错误
android·java·java-ee·android studio·android-studio·androidx·android runtime