14.1 CompositionLocal

479 阅读2分钟

自定义 Banner 已经开始 Compose UI 部分的开发了,但 UI 部分用的都是官方提供的依赖。

UI 部分自然要从主题和屏幕适配开始。为什么先说 CompositionLocal 呢?MaterialTheme 就是使用它来实现的,而且 CompositionLocal 在我们编码中也经常会用到。

CompositionLocal

Compose 中用来 隐式 向 composable 函数提供数据的工具,一般用于底层架构。它提供的数据具有树级作用域,即数据可以在 CompositionLocalProvider 包裹的所有子节点中直接使用。

下面来看一下初始组合时 Compose 添加的默认 CompositionLocal :

    CompositionLocalProvider(
        LocalConfiguration provides configuration,
        LocalContext provides context,
        LocalLifecycleOwner provides viewTreeOwners.lifecycleOwner,
        LocalSavedStateRegistryOwner provides viewTreeOwners.savedStateRegistryOwner,
        LocalSaveableStateRegistry provides saveableStateRegistry,
        LocalView provides owner.view,
        LocalImageVectorCache provides imageVectorCache
    ) {
        ProvideCommonCompositionLocals(
            owner = owner,
            uriHandler = uriHandler,
            content = content
        )
    }
@ExperimentalComposeUiApi
@Composable
internal fun ProvideCommonCompositionLocals(
    owner: Owner,
    uriHandler: UriHandler,
    content: @Composable () -> Unit
) {
    CompositionLocalProvider(
        LocalAccessibilityManager provides owner.accessibilityManager,
        LocalAutofill provides owner.autofill,
        LocalAutofillTree provides owner.autofillTree,
        LocalClipboardManager provides owner.clipboardManager,
        LocalDensity provides owner.density,
        LocalFocusManager provides owner.focusManager,
        @Suppress("DEPRECATION") LocalFontLoader
            providesDefault @Suppress("DEPRECATION") owner.fontLoader,
        LocalFontFamilyResolver providesDefault owner.fontFamilyResolver,
        LocalHapticFeedback provides owner.hapticFeedBack,
        LocalInputModeManager provides owner.inputModeManager,
        LocalLayoutDirection provides owner.layoutDirection,
        LocalTextInputService provides owner.textInputService,
        LocalTextToolbar provides owner.textToolbar,
        LocalUriHandler provides uriHandler,
        LocalViewConfiguration provides owner.viewConfiguration,
        LocalWindowInfo provides owner.windowInfo,
        LocalPointerIconService provides owner.pointerIconService,
        content = content
    )
}

这些 CompositionLocal 都是我们在 Composeable 函数中可以直接使用的,比如 LocalContext.current 前面的代码中就经常使用到。

自定义 CompositionLocal

声明 CompositionLocal

  • staticCompositionLocalOf<T> 适用于值不经常发生变化的情况,一旦其值改变整个 Compose 树都会重组
  • compositionLocalOf<T> 适用于值经常发生变化的情况,其值变化时只有调用 getValue 的 Composeable 函数会重组,类似 State

这两种方式都可以在声明时提供一个默认值 ,不提供默认值的情况需要确保在使用前已经设置过值了,不然会引发异常。

val LocalTestColor = compositionLocalOf<Color> { error("No Color provided") }
var outerCounter = 0
var innerCounter = 0
var centerCounter = 0

@Composable
fun CompositionLocalTest() {

    var color by remember { mutableStateOf(Color.Red) }

    Column {
        Spacer(modifier = Modifier.height(30.dp))
        Text("compositionLocalOf")
        Spacer(modifier = Modifier.height(10.dp))
        CompositionLocalProvider(LocalTestColor provides color) {
            outerCounter++
            TestBox(Color.Yellow, outerCounter, centerCounter, innerCounter) {
                centerCounter++
                TestBox(LocalTestColor.current, outerCounter, centerCounter, innerCounter) {
                    innerCounter++
                    TestBox(Color.Magenta, outerCounter, centerCounter, innerCounter) {
                    }
                }
            }
        }

        Button(onClick = {
            color = if (color == Color.Green) Color.Red else Color.Green
        }, modifier = Modifier.fillMaxWidth()) {
            Text("Change Color")
        }
    }
}

@Composable
fun TestBox(color: Color, outside: Int, center: Int,inside: Int,
    content: @Composable BoxScope.() -> Unit
) {
    Column(Modifier.background(color)) {
        Text(text = "Compose $outside $center $inside")
        Box(modifier = Modifier.fillMaxWidth().padding(16.dp),
            content = content
        )
    }
}

Untitled.gif

将 compositionLocalOf 改成 staticCompositionLocalOf 后 , color 值变化会触发所有 TestBox 重组。

Untitled.gif

为什么是 CompositionLocal.current

前面说到过它是 CompositionLocalProvider 包裹的树级作用域,我们看下 CompositionLocalProvider 的源码。

@Composable
@OptIn(InternalComposeApi::class)
fun CompositionLocalProvider(vararg values: ProvidedValue<*>, content: @Composable () -> Unit) {
    currentComposer.startProviders(values)
    content()
    currentComposer.endProviders()
}

CompositionLocal 提供的值是可在 CompositionLocalProvider 中更改的,current 的意思就是取当前的值。

@Composable
fun CompositionLocalTest() {

    CompositionLocalProvider(LocalTestColor provides Color.Red) {
        Column(modifier = Modifier.padding(20.dp)) {
            Text(text = "LocalTestColor1", color = LocalTestColor.current)
            CompositionLocalProvider(LocalTestColor provides Color.Green) {
                Text(text = "LocalTestColor2", color = LocalTestColor.current)
            }
            Text(text = "LocalTestColor3", color = LocalTestColor.current)
        }
    }
}

7F53C639-4BCC-4618-88CD-7CFCEAE6F8BE.png

最后,千万不要滥用!!!

从上面两个例子就可以看出,CompositionLocal 的值可以在任意位置改变,改变 current 的值还会触发重组,而且这种隐式传递的值出了问题还不好定位。

所以该传参的还是传参,它的适用场景是底层框架设计。