一、前言
在Compose中定义函数的话如果在行参中使用业务变量的话,会使整个业务慢慢变得复杂,难以维护,也不利于解耦的目的。举个例子,就是有一个布局,嵌套了七八层,然后在低层有一个变量,然后会影响最顶层的组合。但是中间的组合却并不使用这个变量。那么通常来说应该怎么做呢。无非是两种方式,定义全局变量或者是通过函数将值一层层的发送过去。但是这两种哪一种都不是很好,因此官方提供了CompositionLocal
来处理这种组合分层的问题。
官方解释如下:
Compose 将数据通过组合树显式地通过参数传递给可组合函数。这通常是让数据流过树的最简单和最好的方法。
有时,对于许多组件需要的数据,或者当组件需要在彼此之间传递数据但保持该实现细节私有时,此模型可能很麻烦或分解。对于这些情况,CompositionLocal
可以用作让数据流过组合的隐式方式。
CompositionLocal
本质上是分层的。当CompositionLocal
需要将的值限定为组合的特定子层次结构时,它们是有意义的。
必须创建一个CompositionLocal
实例,该实例可以被消费者静态引用。CompositionLocal
实例本身不持有任何数据,可以将其视为传递到树中的数据的类型安全标识符。CompositionLocal
工厂函数采用单个参数:在CompositionLocal
没有提供程序的情况下使用a 的情况下创建默认值的工厂。如果这是您不想处理的情况,则可以在此工厂中引发错误。
在树上的某个地方,CompositionLocalProvider
可以使用一个组件,它为CompositionLocal
. 这通常位于树的“根”,但也可以在任何地方,也可以在多个位置使用以覆盖子树的提供值。
中间组件不需要知道该CompositionLocal
值,并且可以对其具有零依赖关系
二、示例演示
val ActiveUser = compositionLocalOf<String> { error("No active user found!") }//这个需要被定义为可被静态引用的实例或者被@Compose修饰的公共组合中,如果没有提供值的话会报错误
//用来演示组合分层的问题
class CompositionLocalActivity: AppCompatActivity(){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val text = "你好啊!!!"
setContent {
body(text)
}
}
}
@Composable
private fun body(text: String){
val text = staticCompositionLocalOf<String> { "" }//比如在这里定义也是可以的,这样就避免了全局静态变量的那种很糟糕的编码体验,同使也保留了组合隔离的特性
CompositionLocalProvider(ActiveUser provides text) {
Column {
content()
}
}
}
@Composable
private fun content(){
child()
}
@Composable
private fun child(){//在这里并不需要知道传进来的值,所以可以很方便的保持组件的独立性,当然这个也可以隔好几层,只要在使用前定义就好
val text = ActiveUser.current
Text(text)
}
三、compositionLocalOf 与 staticCompositionLocalOf 区别
这一段的解释源自以下链接:
https://compose.net.cn/design/theme/understanding_material_theme/
当我们创建 CompositionLocal 时,通常需要使用 compositionLocalOf
或 staticCompositionLocalOf
方法。然而这两者的区别是什么呢?
Unlike compositionLocalOf, reads of a staticCompositionLocalOf are not tracked by the composer and changing the value provided in the CompositionLocalProvider call will cause the entirety of the content to be recomposed instead of just the places where in the composition the local value is used.
简单概括就是,当我们选择使用 staticCompositionLocalOf
时,实际上创建了个StaticProvidableCompositionLocal
实例,当其所提供的值改变时,会导致 CompositionLocalProvide 内部所有 composable 触发重组(recompose)。
如果我们选择使用 compositionLocalOf
,实际上创建了个 DynamicProvidableCompositionLocal
实例,当其所提供的值改变时,仅会导致 CompositionLocalProvide 内部依赖当前 CompositionLocal 的 composable 触发重组(recompose)。
var isStatic = false
var compositionLocalName = ""
val currentLocalColor = if (isStatic) {
compositionLocalName = "StaticCompositionLocal 场景"
staticCompositionLocalOf { Color.Black }
} else {
compositionLocalName = "DynamicCompositionLocal 场景"
compositionLocalOf { Color.Black }
}
var recomposeFlag = "Init"
@Preview
@Composable
fun CompositionLocalDemo(isStatic: Boolean = false) {
var color by remember{ mutableStateOf(Color.Green) }
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Column(horizontalAlignment = Alignment.CenterHorizontally){
Text(text = "${compositionLocalName}")
Spacer (Modifier.height(20.dp))
CompositionLocalProvider(
currentLocalColor provides color
) {
TaggedBox("Wrapper: ${recomposeFlag}", 400.dp,Color.Red) {
TaggedBox("Middle: ${recomposeFlag}", 300.dp, currentLocalColor.current) {
TaggedBox("Inner: ${recomposeFlag}", 200.dp, Color.Yellow)
}
}
}
Spacer (Modifier.height(20.dp))
Button(
onClick = {
color = Color.Blue
}
) {
Text(text = "Change Theme")
}
}
}
recomposeFlag = "Recompose"
}
@Composable
fun TaggedBox(tag:String, size: Dp, background: Color, content: @Composable () -> Unit = {}) {
Column(
modifier = Modifier
.size(size)
.background(background),
horizontalAlignment = Alignment.CenterHorizontally) {
Text(text = tag)
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
content()
}
}
}
那么 compositionLocalOf
或 staticCompositionLocalOf
方法的应用场景是什么?
文档是这样说的
This lack of tracking, however, makes a staticCompositionLocalOf
more efficient when the value provided is highly unlikely to or will never change. For example, the android context, font loaders, or similar shared values, are unlikely to change for the components in the content of a the CompositionLocalProvider
and should consider using a staticCompositionLocalOf
. A color, or other theme like value, might change or even be animated therefore a compositionLocalOf
should be used.
(英语不好,理解可能有错误)大致意思是当数据不大可能会改变时候或者永远不会改变时候使用staticCompositionLocalOf
会更有效。例如Andorid上下文对象,字体加载器,或者共享元素等。如果是哪些容易改变的,则使用compositionLocalOf
会更好一点,如动画、主题等等。大致原因应该是,类似于字体加载器这些源文件发生改变后,难以监测到,所以使用staticCompositionLocalOf
进行UI全部重组会比较好。
四、参考链接
-
CompositionLocal
https://developer.android.google.cn/reference/kotlin/androidx/compose/runtime/CompositionLocal
-
compositionLocalOf 与 staticCompositionLocalOf 区别
https://compose.net.cn/design/theme/understanding_material_theme/
-
staticCompositionLocalOf
-
Android Jetpack Compose:让 CompositionLocal 变得简单