使用CompositionLocal简化组合式函数参数
目录
1. 组合式函数难以维护状态
和对象相比,组合式函数维护状态的能力比较弱。如果所有状态都通过参数列表显示传递,函数将难以维护。每次改动,调用树上所有的函数全部需要改动。如果使用一个全局对象包含所有的状态,一方面引入了全局依赖,一方面对象会变得非常复杂,难以维护一致性。为了解决这个问题,Jetpack通过CompositionLocal提供了一个带有层级结构的状态树,可以理解为OOP中把继承树中所有对象的行为移除之后的结果。
2. 通过CompositionLocal对象隐式传递状态
代码1 定义和使用CompositionLocal
import androidx.compose.runtime.compositionLocalOf val ActiveUser = compositionLocalOf<User> { User("someone") } @Composable fun SomeScreen() { Text(ActiveUser.current.name) // 通过CompositionLocal的成员current访问具体对象。 }
代码2 提供/更换CompositionLocal
import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.CompositionLocalProvider @Composable fun App() { Screen() // show default user val anotherUser by remember { mutableStateOf(User("another")) } CompositionLocalProvider(ActiveUser provides anotherUser) { Screen() // show another user } Screen() // show default user again }
从上面的例子可以看到,通过CompositionLocal可以隐式传递对象,并且传递给组合式函数对象可以由父函数控制,限制在特定范围内。因为我们可以把哪些与组合式函数自身逻辑无关,同时又会影响函数结果的对象放到CompositionLocal中,简化函数参数列表。
假设组合式函数Screen依赖于某个特性Feature,我们可以通过CompositionLocal隐含的传递Feature对象。
val LocalFeatureNullable = compositionLocalOf<Feature?> { null } val LocalFeatureMustProvide = compositionLocalOf<Feature> { error("No Feature provided.") } @Composable fun Screen() { LocalFeatureNullable.current?.let { DisplayWithFeature() } :? DisplayWithoutFeature() Text(LocalFeatureMustProvide.current.name) }
3. 重组
有两个函数可以创建CompositionLocal对象:
compositionLocalOf
- 用于值频繁变化的场景。
- 重组时只有依赖该值的代码重组。
staticCompositionLocalOf
- 用于值极少变化的场景。
- 重组时CompositionLocalProvider范围内的代码都参与重组。
4. 示例
代码3 使用CompositionLocal简化导航
val LocalNavController = compositionLocalOf<NavHostController> { error("No NavController provided.") } @Composable fun Screen1() { Text(text = "Screen1") val navctl = LocalNavController.current Button( onClick = { navctl.navigate("screen2") }, modifier = Modifier.wrapContentSize() ) { Text("打开屏幕2") } } @Composable fun Screen2() { Text(text = "Screen2") val navctl = LocalNavController.current Button( onClick = { navctl.navigate("screen1") }, modifier = Modifier.wrapContentSize() ) { Text("打开屏幕1") } } @Composable fun MainScreen() { CompositionLocalProvider(LocalNavController provides rememberNavController()) { NavHost(LocalNavController.current, startDestination = "screen1") { composable("screen1") { Screen1() } composable("screen2") { Screen2() } } } }
代码4 部分内置的CompositionLocal对象。文件:AndroidCompositionLocals.android.kt
package androidx.compose.ui.platform ... /** * The Android [Configuration]. The [Configuration] is useful for determining how to organize the * UI. */ val LocalConfiguration = compositionLocalOf<Configuration> { noLocalProvidedFor("LocalConfiguration") } /** * Provides a [Context] that can be used by Android applications. */ val LocalContext = staticCompositionLocalOf<Context> { noLocalProvidedFor("LocalContext") } internal val LocalImageVectorCache = staticCompositionLocalOf<ImageVectorCache> { noLocalProvidedFor("LocalImageVectorCache") } /** * The CompositionLocal containing the current [LifecycleOwner]. */ val LocalLifecycleOwner = staticCompositionLocalOf<LifecycleOwner> { noLocalProvidedFor("LocalLifecycleOwner") } /** * The CompositionLocal containing the current [SavedStateRegistryOwner]. */ val LocalSavedStateRegistryOwner = staticCompositionLocalOf<SavedStateRegistryOwner> { noLocalProvidedFor("LocalSavedStateRegistryOwner") } /** * The CompositionLocal containing the current Compose [View]. */ val LocalView = staticCompositionLocalOf<View> { noLocalProvidedFor("LocalView") } ...
代码5 MaterialTheme使用CompositionLocal控制组件样式
@Composable fun MaterialTheme( colors: Colors = MaterialTheme.colors, typography: Typography = MaterialTheme.typography, shapes: Shapes = MaterialTheme.shapes, content: @Composable () -> Unit ) { ... 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) } } }