使用CompositionLocal简化组合式函数参数

使用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

  1. 用于值频繁变化的场景。
  2. 重组时只有依赖该值的代码重组。

staticCompositionLocalOf

  1. 用于值极少变化的场景。
  2. 重组时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)
        }
    }
}

5. 参考资料

  1. juejin.cn/post/709789...
相关推荐
陈旭金-小金子5 分钟前
发现 Kotlin MultiPlatform 的一点小变化
android·开发语言·kotlin
二流小码农2 小时前
鸿蒙开发:DevEcoStudio中的代码提取
android·ios·harmonyos
江湖有缘2 小时前
使用obsutil工具在OBS上完成基本的数据存取【玩转华为云】
android·java·华为云
移动开发者1号4 小时前
Android 多 BaseUrl 动态切换策略(结合 ServiceManager 实现)
android·kotlin
移动开发者1号4 小时前
Kotlin实现文件上传进度监听:RequestBody封装详解
android·kotlin
AJi7 小时前
Android音视频框架探索(三):系统播放器MediaPlayer的创建流程
android·ffmpeg·音视频开发
柿蒂7 小时前
WorkManager 任务链详解:优雅处理云相册上传队列
android
alexhilton8 小时前
使用用例(Use Case)以让Android代码更简洁
android·kotlin·android jetpack
峥嵘life8 小时前
Android xml的Preference设置visibility=“gone“ 无效分析解决
android·xml
用户2018792831679 小时前
通俗故事:驱动二进制文件在AOSP中的角色
android