ITmob-Ly
发布于 2022-11-01 / 687 阅读
0

Compose:CompositionLocal 提供一个限定在局部的数据作用域

简介

Compose 以静态和可观察的方式提供数据:

  1. compositionLocalOf 维护所提供对象的可变状态,更改该对象的值会导致提供 CompositionLocal 的整个 content lambda 被重组。
  2. staticCompositionalLocalOf 创建的 CompositionLocal 对象不会观察值的变化,也不会在其值更改时触发重组。另一方面,staticCompositionalLocalOf 不会观察对象的变化,也不会在读取提供的值时触发重组。

注意CompositionLocal 对象或常量通常带有 Local 前缀,以便在 IDE 中利用自动填充功能提高可检测性。

建议使用 CompositionLocal 的情况为:其可能会被任何(而非少数几个)后代使用。

CompositionLocalProvider 可组合项可将值绑定到给定层次结构的 CompositionLocal 实例,为 CompositionLocal 提供值。在 Composable 之间共享对象而不将它们作为函数参数传递。共享对象存储在 Composer 中,并且在 CompositionLocalProvider 包装的当前 ComposeScope 树中可用

staticCompositionalLocalOf

如果为 CompositionLocal 提供的值发生更改的可能很低或永远不会更改,使用 staticCompositionLocalOf 可提高性能。

val localIntent = staticCompositionLocalOf<Intent> { error("CompositionLocal intent not present") }

@AndroidEntryPoint
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            CompositionLocalProvider(LocalIntent provides intent) {
                // TODO ...
            }
        }
    }
}

通过 staticCompositionLocalOf<String> { error("CompositionLocal intent not present") } 限制它在 Provider 之外使用。通过上面这样给定默认值,如果在 Provider 之外访问则会抛出异常。

比如 Compose Material 中的 MaterialTheme 中的 colors, typography, shapes 都是通过 staticCompositionLocalOf 实现的。

如下是 androidx 源码中的实现:

// androidx 源码:MaterialTheme.kt
object MaterialTheme {
    /**
     * Retrieves the current [Colors] at the call site's position in the hierarchy.
     *
     * @sample androidx.compose.material.samples.ThemeColorSample
     */
    val colors: Colors
        @Composable
        @ReadOnlyComposable
        get() = LocalColors.current

    /**
     * Retrieves the current [Typography] at the call site's position in the hierarchy.
     *
     * @sample androidx.compose.material.samples.ThemeTextStyleSample
     */
    val typography: Typography
        @Composable
        @ReadOnlyComposable
        get() = LocalTypography.current

    /**
     * Retrieves the current [Shapes] at the call site's position in the hierarchy.
     */
    val shapes: Shapes
        @Composable
        @ReadOnlyComposable
        get() = LocalShapes.current
}  
// androidx 源码:Colors.kt
internal val LocalColors = staticCompositionLocalOf { lightColors() }
// 自定义 Theme
val lightColors = lightColors(
    //...
)

val darkColors = darkColors(
    //...
)

@Composable
fun AppTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    content: @Composable () -> Unit
) {
    MaterialTheme(
        colors = if (darkTheme) darkColors else lightColors,
        typography = typography,
        shapes = shapes,
        content = content
    )
}

compositionalLocalOf

compositionLocalOf() 适合值可能会经常改变的场景。

比如 LocalContentColor 的实现

// androidx 源码:ContentColor.kt
package androidx.compose.material

import androidx.compose.runtime.compositionLocalOf
import androidx.compose.ui.graphics.Color

val LocalContentColor = compositionLocalOf { Color.Black }

Text 的文字颜色, Icon 的 tint 颜色, Tab/TabRow 的 Divider 和 Indicator, 的使用了 LocalContentColor.current 的值作为默认值。

// androidx 源码:Text.kt
@Composable
fun Text(
    text: AnnotatedString,
    modifier: Modifier = Modifier,
    ...
) {
    val textColor = color.takeOrElse {
        style.color.takeOrElse {
            LocalContentColor.current.copy(alpha = LocalContentAlpha.current)
        }
    }
}

// androidx 源码:Icon.kt
@Composable
@NonRestartableComposable
fun Icon(
    bitmap: ImageBitmap,
    contentDescription: String?,
    modifier: Modifier = Modifier,
    tint: Color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current)
) {
    val painter = remember(bitmap) { BitmapPainter(bitmap) }
    Icon(
        painter = painter,
        contentDescription = contentDescription,
        modifier = modifier,
        tint = tint
    )
}

Surface 等 Composable 中使用 CompositionLocalProvider 保证其 ComposeScopeLocalContentColor 的值可用并在其值发送改变时重组。

// androidx 源码:Surface.kt
@Composable
fun Surface(
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
    ...
) {
    val absoluteElevation = LocalAbsoluteElevation.current + elevation
    CompositionLocalProvider(
        LocalContentColor provides contentColor,
        LocalAbsoluteElevation provides absoluteElevation
    ) {
        ...
    }
    ...
}

更多内容参见官方文档:

https://developer.android.com/jetpack/compose/compositionlocal