使用CompositionLocal简化组合式函数参数 (tommwq.work)
========================================================================================================================================================================================================================
目录
1. 组合式函数难以维护状态
和对象相比,组合式函数维护状态的能力比较弱。如果所有状态都通过参数列表显示传递,函数将难以维护。每次改动,调用树上所有的函数全部需要改动。如果使用一个全局对象包含所有的状态,一方面引入了全局依赖,一方面对象会变得非常复杂,难以维护一致性。为了解决这个问题,Jetpack通过CompositionLocal提供了一个带有层级结构的状态树,可以理解为OOP中把继承树中所有对象的行为移除之后的结果。
2. 通过CompositionLocal对象隐式传递状态
代码1 定义和使用CompositionLocal
kotlin
import androidx.compose.runtime.compositionLocalOf
val ActiveUser = compositionLocalOf<User> { User("someone") }
@Composable
fun SomeScreen() {
Text(ActiveUser.current.name) // 通过CompositionLocal的成员current访问具体对象。
}
代码2 提供/更换CompositionLocal
kotlin
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对象。
kotlin
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简化导航
kotlin
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
scala
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控制组件样式
less
@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)
}
}
}