Compose 官方说明一直很简洁:CompositionLocal
是通过组合隐式向下传递数据的工具。
我们先来看一段代码:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ComposeBlogTheme {
val name = "Hi, Compose!" // name 是局部变量
ShowMessage(name)
}
}
}
}
@Composable
fun ShowMessage(message: String) {
Text(message)
}
这段代码中局部变量 name
被暴露出来了,这就是我们常见的 State Hoisting
(状态提升)。
🤔 🤔 现在思考两个问题:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ComposeBlogTheme {
val name = "Hi, Compose!"
ShowMessage(name)
}
👉🏻 name = // 思考 1: 这边能不能获取到 name?
}
}
}
@Composable
fun ShowMessage(message: String) {
👉🏻 name = // 思考 2: 这边能不能获取到 name?
Text(message)
}
答案是:肯定不行!因为 name
这个局部变量的 作用域 是限定在 ComposeBlogTheme {}
范围内的。
那么你可能会有疑惑,为什么要思考这么常识的问题?我现在把代码改一下:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ComposeBlogTheme {
val name = "Hi, Compose!"
// ShowMessage(name) // 这段代码我不要了
}
}
}
}
@Composable
fun ShowMessage() {
// 参数我也去掉,不需要外面传参进来
Text(name) // 我就直接把外面的 name 写到这里,直接用
}
这样写行吗?肯定是不行的!但我既然这么写了,肯定是有目的的。如果我们的代码这样写,还希望它可以运行,并且结果正确!那么也就意味着 name
这个局部变量拥有了可以穿出它的 作用域,并且穿透进 showMessage
函数的能力!
其实我们可以实现这样的效果,主角登场:CompositionLocal
,它就提供了这种 「穿透能力」
。
创建 CompositionLocal 实例
首先创建一个具有 「穿透能力」
的变量:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ComposeBlogTheme {
val name = "Hi, Compose!"
}
}
}
}
@Composable
fun ShowMessage() {
Text(name)
}
// 1. 通过 compostionLocalOf 创建 CompositionLocal
val LocalName = compositionLocalOf<String> {
"Compose" }
Compose 中,一般定义这种具有「穿透能力」
的 CompostionLocal
,它的名称有个约定俗成的写法:LocalXXX
。
为 CompositionLocal 提供值
CompostionLocal
实例定义好了,我们要通过 provides
给它绑定一个值。
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ComposeBlogTheme {
val name = "Hi, Compose!"
// 2. CompositionLocalProvider 可组合项可将值绑定到给定层次结构的 CompositionLocal 实例
CompositionLocalProvider(LocalName provides name) {
ShowMessage()
}
}
}
}
}
@Composable
fun ShowMessage() {
Text(name)
}
// 1. 通过 compostionLocalOf 创建 CompositionLocal
val LocalName = compositionLocalOf<String> {
"Compose" }
获取 CompositionLocal 数据
前面两步其实就已经创建好了一个包含 数据 + 可穿透
的 CompositionLocal
了,最后一步就是获取到其中的数据。
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ComposeBlogTheme {
val name = "Hi, Compose!"
// 2. CompositionLocalProvider 可组合项可将值绑定到给定层次结构的 CompositionLocal 实例
CompositionLocalProvider(LocalName provides name) {
ShowMessage()
}
}
}
}
}
@Composable
fun ShowMessage() {
// 3. 获取 name
Text(LocalName.current)
}
// 1. 通过 compostionLocalOf 创建 CompositionLocal
val LocalName = compositionLocalOf<String> {
"Compose" }
👌🏼👌🏼👌🏼,搞定!
CompositionLocal 应用场景
提供上下文
LocalContext.current // 是不是很熟悉?这其实就相当于 getContext()
MaterialTheme
其实官方的 MaterialTheme 就用到了 CompositionLocal
,我们看源码:
@Composable
fun MaterialTheme(
colors: Colors = MaterialTheme.colors,
typography: Typography = MaterialTheme.typography,
shapes: Shapes = MaterialTheme.shapes,
content: @Composable () -> Unit
) {
val rememberedColors = remember {
colors.copy() }.apply {
updateColorsFrom(colors) }
val rippleIndication = rememberRipple()
val selectionColors = rememberTextSelectionColors(rememberedColors)
// 通过 providers 将 rememberedColors 提供给了 LocalColors
CompositionLocalProvider(
LocalColors provides rememberedColors,
LocalContentAlpha provides ContentAlpha.high,
LocalIndication provides rippleIndication,
LocalRippleTheme provides MaterialRippleTheme,
LocalShapes provides shapes,
LocalTextSelectionColors provides selectionColors,
LocalTypography provides typography
) {
ProvideTextStyle(value = typography.body1) {
PlatformMaterialTheme(content)
}
}
}
object MaterialTheme {
val colors: Colors
@Composable
@ReadOnlyComposable
get() = LocalColors.current
val typography: Typography
@Composable
@ReadOnlyComposable
get() = LocalTypography.current
val shapes: Shapes
@Composable
@ReadOnlyComposable
get() = LocalShapes.current
}
compositionLocalOf / staticCompositionLocalOf 的区别
前面的例子我们使用 compositionLocalOf
创建 CompositionLocal
,另外官方还提供了 staticCompositionLocalOf
。
📌 compositionLocalOf
:在重组期间更改提供的值只会使读取其 current 值的内容无效。
📌 staticCompositionLocalOf
:与 compositionLocalOf 不同,Compose 不会跟踪 staticCompositionLocalOf 的读取。更改该值会导致提供 CompositionLocal 的整个 content lambda 被重组,而不仅仅是在组合中读取 current 值的位置。
😵😵😵,很懵???我们来看下面的代码:
compositionLocalOf
class MainActivity : ComponentActivity() {
private val themeBackground by mutableStateOf(Color.Blue)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ComposeBlogTheme {
CompositionLocalProvider(LocalBackground provides Color.Red) {
TextBackground()
CompositionLocalProvider(LocalBackground provides themeBackground) {
/**
* 2. themeBackground 变化后,仅此范围重组
* TextBackground()
*/
CompositionLocalProvider(LocalBackground provides Color.Red) {
TextBackground()
}
}
TextBackground()
}
}
}
}
}
@Composable
fun TextBackground() {
Surface(color = LocalBackground.current) {
Text("Hi, Compose")
}
}
// 1. Compose 会记录跟踪每一个调用 LocalBackground.current 的区域
val LocalBackground = compositionLocalOf {
Color.Blue }
staticCompositionLocalOf
class MainActivity : ComponentActivity() {
private val themeBackground by mutableStateOf(Color.Blue)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ComposeBlogTheme {
CompositionLocalProvider(LocalBackground provides Color.Red) {
TextBackground()
CompositionLocalProvider(LocalBackground provides themeBackground) {
/**
* 2. themeBackground 变化后,内部所有 content 全部重组
* TextBackground()
*
* CompositionLocalProvider(LocalBackground provides Color.Red) {
* TextBackground()
* }
*/
}
TextBackground()
}
}
}
}
}
@Composable
fun TextBackground() {
Surface(color = LocalBackground.current) {
Text("Hi, Compose")
}
}
// Compose 不会记录跟踪每一个调用 LocalBackground.current 的区域
val LocalBackground = staticCompositionLocalOf {
Color.Blue }
区别搞懂了吧?那么随之而来一个疑问:我们定义 CompositionLocal 该用哪个?
官方说明
如果为 CompositionLocal
提供的值发生更改的可能性微乎其微或永远不会更改,使用 staticCompositionLocalOf
可提高性能。
比如,我们看两个系统里面定义好的 CompositionLocal
:
// 上下文固定不变,用 staticCompositionLocalOf
val LocalContext = staticCompositionLocalOf<Context> {
noLocalProvidedFor("LocalContext")
}
// 内部内容颜色常变,用 compositionLocalOf
val LocalContentColor = compositionLocalOf {
Color.Black }