Compose的CompositionLocal(十一)

一、前言

在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 时,通常需要使用 compositionLocalOfstaticCompositionLocalOf 方法。然而这两者的区别是什么呢?

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()
        }
    }
}

https://compose.net.cn/assets/design/theme/understanding_material_theme/dynamic.gif

那么 compositionLocalOfstaticCompositionLocalOf 方法的应用场景是什么?

文档是这样说的

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全部重组会比较好。

四、参考链接

  1. CompositionLocal

    https://developer.android.google.cn/reference/kotlin/androidx/compose/runtime/CompositionLocal

  2. compositionLocalOf 与 staticCompositionLocalOf 区别

    https://compose.net.cn/design/theme/understanding_material_theme/

  3. staticCompositionLocalOf

    https://developer.android.google.cn/reference/kotlin/androidx/compose/runtime/package-summary#staticCompositionLocalOf(kotlin.Function0)

  4. Android Jetpack Compose:让 CompositionLocal 变得简单

    https://medium.com/mobile-app-development-publication/android-jetpack-compose-compositionlocal-made-easy-8632b201bfcd

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 12
    评论
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值