使用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...
相关推荐
HLC++9 小时前
Linux的进程间通信
android·linux·服务器
爱笑鱼13 小时前
Binder(二):AIDL 生成的 Proxy、Stub 和 Parcel 到底在做什么?
android
爱笑鱼13 小时前
Binder(一):一次方法调用,究竟怎样跨进程执行?
android
壮哥_icon14 小时前
【Android 系统开发】使用 BAT 脚本高效自动化管理 /system/priv-app/ 系统应用(安装与卸载)
android·运维·自动化
用户693717500138415 小时前
Claude Code终端日志Token占用实测:一个过滤器砍掉60%-90%
android·前端·后端
蝉蜕日记15 小时前
AccumuPDF高级版 v2.57 | 视图文转换PDF,合并分割压缩
android·智能手机·pdf·生活·软件需求
吐了啊取名字太难16 小时前
美颜系统AI修图本地跑并支持Mac、win、安卓、iOS不卡顿
android·人工智能·windows·数码相机·mac·ai编程
阿pin17 小时前
Android随笔-Retrofit
android·retrofit
想取一个与众不同的名字好难17 小时前
安卓自定义颜色选择器
android
我命由我1234518 小时前
Android 开发问题:java.lang.NoSuchMethodError:No virtual method readAllBytes()
android·java·java-ee·kotlin·android studio·android-studio·android runtime