CompositionLocal 用法
从前面的章节中我们已经了解到,在 Compose 中构建用户界面时,需要创建可组合函数的层级结构。
Compose 是状态驱动的,并且状态通常应在可组合层级的 “尽可能高的层级” 中声明(这一概念被称为 “状态提升”),再通过层级结构传递给需要该状态的子可组合项。这种方式在大多数场景下都能很好地工作,但如果状态需要通过层级结构中的 “多个层级” 向下传递,操作就会变得繁琐。而 Composition Local 正是解决这一问题的方案,本章将围绕它展开讲解。
理解 CompositionLocal
CompositionLocal 提供了一种方式:让在可组合项层级树中 “高层级” 声明的状态,能被树中 “低层级” 的函数使用,而无需在 “状态声明处” 与 “状态使用处” 之间的每一个可组合项中都传递该状态。

在这个层级中,一个名为 colorState 的状态在 Composable1 中声明,但仅在 Composable8 中使用。尽管 Composable3 和 Composable5 都不需要这个状态,colorState 仍需通过这两个函数传递,才能到达 Composable8。可组合项树的层级越深,状态传递经过的层级就越多。
解决这个问题就是使用 CompositionLocal。CompositionLocal 允许我们在树中 “必要的最高层级节点” 声明数据,之后在更深层次的可组合项中直接访问该数据,无需通过中间的子可组合项传递。

CompositionLocal 另一优势:它只会将数据提供给被赋值节点以下的子树分支。换句话说,若在调用 Composable3 时为该状态赋值,那么只有 Composable3、Composable5、Composable7 和 Composable8 能访问到该状态,而 Composable1、Composable2、Composable4 或 Composable6 无法访问。
这一特性使得状态可以 “局限在可组合项树的特定分支中”,且不同子分支可给同一个 CompositionLocal 状态分配不同的值。例如,Composable5 中为 colorState 分配的颜色,可与调用 Composable7 时设置的颜色不同。
使用 CompositionLocal
使用 CompositionLocal 声明状态,首先要创建一个 ProvidableCompositionLocal 实例,可通过调用 compositionLocalOf() 或 staticCompositionLocalOf() 函数获取该实例。
这两个函数都需要传入一个 lambda 表达式,用于定义在未指定具体值时为状态分配的默认值,例如:
val LocalColor = compositionLocalOf { Color.Red }
val LocalColor = staticCompositionLocalOf { Color.Red }
函数选择建议:
- staticCompositionLocalOf():适用于不常变化的状态。因为状态值变更会导致赋值点以下整个组件树进行重组。
- compositionLocalOf():适用于频繁变化的状态。它仅重组访问了当前状态的组件,性能更优。
接下来,需要为 ProvidableCompositionLocal 实例赋值,并在 CompositionLocalProvider 的调用中包裹对直接子可组合项的调用:
val color = Color.BlueCompositionLocalProvider(LocalColor provides color) {Composable5()
}
此时,Composable5 的所有后代组件都能通过 ProvidableCompositionLocal 实例的 current 属性访问该 CompositionLocal 状态,例如:
val background = LocalColor.current
创建 CompLocalDemo 项目
启动 Android Studio,创建 CompLocalDemo 的 “Empty Activity”项目。指定包名为 com.example.complocaldemo,选择最低 API 级别为 API 26(Android 8.0,Oreo)。
步骤 1:修改 MainActivity.kt 文件,删除并新增可组合函数。在 MainActivity.kt 文件中,删除默认的 Greeting 函数,然后添加 Composable1 的空可组合函数:
@Composable
fun Composable1(modifier: Modifier = Modifier) {}
步骤 2:编辑 onCreate() 方法,将原本调用 Greeting 的代码替换为调用 Composable1:
override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {CompLocalDemoTheme {Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->// 替换为调用 Composable1,并传入内边距参数Composable1(Modifier.padding(innerPadding))}}}
}
步骤 3:编辑 GreetingPreview 预览函数,将原本调用 Greeting 的代码替换为调用 Composable1:
@Preview(showBackground = true)
@Composable
fun GreetingPreview() {CompLocalDemoTheme {// 替换为调用 Composable1Composable1()}
}
布局设计
在 MainActivity.kt 文件中,按如下方式实现可组合项层级结构:
// 导入所需依赖
import androidx.compose.foundation.background
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.graphics.Color// ...(其他已有代码)@Composable
fun Composable1(modifier: Modifier = Modifier) {Column {Composable2()Composable3()}
}@Composable
fun Composable2(modifier: Modifier = Modifier) {Composable4()
}@Composable
fun Composable3(modifier: Modifier = Modifier) {Composable5()
}@Composable
fun Composable4(modifier: Modifier = Modifier) {Composable6()
}@Composable
fun Composable5(modifier: Modifier = Modifier) {Composable7()Composable8()
}@Composable
fun Composable6(modifier: Modifier = Modifier) {Text("Composable 6")
}@Composable
fun Composable7(modifier: Modifier = Modifier) {// 空实现
}@Composable
fun Composable8(modifier: Modifier = Modifier) {Text("Composable 8")
}
层级结构如下所示
Composable1
├─ Composable2 → Composable4 → Composable6
└─ Composable3 → Composable5├─ Composable7└─ Composable8
添加 CompositionLocal 状态
本项目的使用一个 “colorState”:该状态可根据设备处于浅色模式还是深色模式自动切换,并用于控制 Composable8 中文本组件的背景色。由于该状态值不会频繁变化,因此我们可以使用 staticCompositionLocalOf() 函数。
在 MainActivity.kt 文件中,在 Composable1 声明上方添加以下代码:
// 定义 CompositionLocal 状态,默认颜色为 #ffdbcf(浅肤色)
val LocalColor = staticCompositionLocalOf { Color(0xFFffdbcf) }@Composable
fun Composable1(modifier: Modifier = Modifier) {Column {// ...(原有代码)}
}
接下来,需要添加 isSystemInDarkTheme() 函数的调用,根据其返回结果为 LocalColor 状态分配不同颜色;同时,需在 CompositionLocal 提供者的作用域内调用 Composable3。修改后的 Composable1 代码如下:
@Composable
fun Composable1(modifier: Modifier = Modifier) {// 根据系统主题模式分配颜色:深色模式用 #a08d87(深灰色),浅色模式用 #ffdbcf(浅肤色)val color = if (isSystemInDarkTheme()) {Color(0xFFa08d87)} else {Color(0xFFffdbcf)}Column {Composable2()// 在 CompositionLocalProvider 中提供 LocalColor 的值,作用域包含 Composable3 及其后代CompositionLocalProvider(LocalColor provides color) {Composable3()}}
}
访问 CompositionLocal 状态
在测试代码前,最后一项任务是将颜色状态赋值给 Composable8 中的 Text 组件,具体代码如下:
@Composable
fun Composable8(modifier: Modifier = Modifier) {// 通过 LocalColor.current 访问 CompositionLocal 状态,设置文本背景色Text("Composable 8", modifier.background(LocalColor.current))
}
测试
要在浅色模式和深色模式下测试代码,需在 MainActivity.kt 中新增一个预览可组合项,并将 uiMode 设置为 UI_NIGHT_MODE_YES(深色模式),代码如下:
@Preview(showBackground = true,uiMode = android.content.res.Configuration.UI_MODE_NIGHT_YES
)
@Composable
fun DarkPreview() {CompLocalDemoTheme {Composable1()}
}
刷新预览面板后,默认预览(浅色模式)和深色模式预览都会显示。两者中 Composable8 文本组件的背景色会不同,分别对应两种模式下的颜色设置。

为不同可组合项设置不同颜色,修改代码,让 Composable3、Composable5、Composable7 和 Composable8 拥有不同的颜色设置。只需在调用每个可组合项时,用 CompositionLocalProvider 包裹,并为其分配不同颜色即可,具体代码如下:
- 修改 Composable3
@Composable
fun Composable3(modifier: Modifier = Modifier) {// 使用当前 LocalColor 颜色(从 Composable1 传递而来)Text("Composable 3", modifier.background(LocalColor.current))// 为 Composable5 分配红色CompositionLocalProvider(LocalColor provides Color.Red) {Composable5()}
}
- 修改 Composable5
@Composable
fun Composable5(modifier: Modifier = Modifier) {// 使用从 Composable3 传递的红色Text("Composable 5", modifier.background(LocalColor.current))// 为 Composable7 分配绿色CompositionLocalProvider(LocalColor provides Color.Green) {Composable7()}// 为 Composable8 分配黄色CompositionLocalProvider(LocalColor provides Color.Yellow) {Composable8()}
}
- 修改 Composable7
@Composable
fun Composable7(modifier: Modifier = Modifier) {// 使用从 Composable5 传递的绿色Text("Composable 7", modifier.background(LocalColor.current))
}
刷新预览面板后,这四个可组合项会呈现不同的颜色 —— 且它们都基于同一个 LocalColor 状态。

测试 Composable6 对状态的访问权限,尝试从 Composable6 中访问 LocalColor 状态,修改代码如下:
@Composable
fun Composable6(modifier: Modifier = Modifier) {Text("Composable 6", modifier.background(LocalColor.current))
}
刷新预览后会发现,Composable6 的文本组件会使用 LocalColor 的默认颜色(而非 Composable1 中设置的模式颜色)。原因是:Composable6 位于组件树的另一个分支(Composable1→Composable2→Composable4→Composable6),无法访问到 Composable1 中为 LocalColor 设置的当前值。
小结
本章介绍了 CompositionLocal,并演示了如何通过它声明状态 —— 让布局层级中较低层级的可组合项能够访问该状态,而无需在子可组合项之间逐层传递。以这种方式声明的状态,其作用范围仅限于 “为其赋值的层级树分支”。
