Android Compose 使用 CompositionLocal 将数据的作用域限定在局部

文章目录

CompositionLocal 是 Jetpack Compose 中一个非常重要的机制,它允许你在组合树中隐式地向下传递数据,而不需要通过每个 Composable 函数的参数手动传递

为什么需要 CompositionLocal

在 Compose 中,通常通过参数将数据传递给子函数。但在复杂的应用中,某些数据(如主题颜色、字体样式、当前登录用户信息或配置信息)几乎每个 UI 组件都需要访问。

如果通过参数层层传递:

代码冗余:中间层的函数即使不需要这些数据,也必须定义参数并向下转发。

难以维护:一旦需要增加一个全局参数,可能需要修改数十个函数的签名。

CompositionLocal 解决了这个问题:你在高层提供一个值,底层的任何 Composable 都可以直接访问它。


核心概念与创建方式

创建 CompositionLocal 主要有两种方式:

  • A. compositionLocalOf

    特性:当提供的值发生改变时,Compose 只会让读取该值的 Composable 发生重组。

  • B. staticCompositionLocalOf

    特性:当值改变时,Compose 会重新读取并重组整个内容树,而不是只重组读取它的部分。

    优点:存储和读取的性能比 compositionLocalOf 略好(因为它不需要跟踪依赖关系)。

重要属性 current

kotlin 复制代码
class CompositionLocal {
	public inline val current: T
        @ReadOnlyComposable @Composable get() = currentComposer.consume(this)
}

示例

kotlin 复制代码
data class UserInfo(val name: String, val isAdmin: Boolean)

val LocalUserInfo = compositionLocalOf { UserInfo("default", false) }
// val LocalUserInfo = staticCompositionLocalOf { UserInfo("default", false) }
kotlin 复制代码
@Composable
fun CompLocalTest() {
		// state 记忆
    var currentUser by remember { mutableStateOf(UserInfo("Manager", true)) }

    // 通过 CompositionLocalProvider 向下传递,本质是定义了共享它的作用域
    CompositionLocalProvider(LocalUserInfo provides currentUser) {
        // 这里的子组件及其所有后代都可以访问到 currentUser
        Dashboard1()
        Dashboard2() // 内部使用 local.current
    }

    val user = LocalUserInfo.current
    Text(text = "hi, ${user.name}! Admin: ${user.isAdmin}",
        modifier = Modifier.padding(top = 40.dp))

    Button(onClick = {
        currentUser = UserInfo("xx", false)
    }, modifier = Modifier.padding(top = 100.dp)) {
        Text("change user:${currentUser}")
    }
}

@Composable
private fun Dashboard1() {
    SideEffect {
        Log.d("Dashboard1", "Dashboard1: 进入组合")
    }
}

@Composable
private fun Dashboard2() {
    SideEffect {
        Log.d("Dashboard2", "Dashboard2: 进入组合")
    }
    UserInfoLabel()
}

@Composable
private fun UserInfoLabel() {
    SideEffect {
        Log.d("UserInfoLabel", "UserInfoLabel: 进入组合")
    }
    // 获取当前作用域内的值
    val user = LocalUserInfo.current
    Text(text = "Welcome, ${user.name}! Admin: ${user.isAdmin}")
}
  • 通过 compositionLocalOfstaticCompositionLocalOf 创建的类型,它的直接或间接父类 是 ProvidableCompositionLocal,这个父类中提供了一个 中缀函数:
kotlin 复制代码
public infix fun provides(value: T): ProvidedValue<T> = defaultProvidedValue(value)

CompositionLocalProvider 函数接收一个 ProvidedValue<*> 类型的参数

kotlin 复制代码
public fun CompositionLocalProvider(value: ProvidedValue<*>, content: @Composable () -> Unit) {
    currentComposer.startProvider(value)
    content()
    currentComposer.endProvider()
}

这个参数类型就是上面的中缀函数的返回类型

  • SideEffect 每次进入组合阶段时都会调用。重组当然也会。
  • CompLocalTest() 功能:

currentUser 声明定义成 记忆状态,使用了 remember { mutableStateOf(...) }
CompositionLocalProvider 向内部 content 传递了 currentUser

界面中 第一个组件是 text = "Welcome ... 用户name是 "xx"

第二个 padding(top = 40.dp) 的Text显示的是 默认用户name是 "default"

第三个组件按钮 padding(top = 100.dp),显示的是 用户name是 "Manager"
Button 点击修改了 记忆状态对象 currentUser,引起了读取 currentUser 的可组合项发生重组。Button中的 Text("change user:${currentUser}") 、UserInfoLabel 中的 text = "Welcome ... 肯定会发生重组

若使用 ... = compositionLocalOf {},点击后只有这两处会发生重组。此时日志输出:

UserInfoLabel: 进入组合
若使用 ... = staticCompositionLocalOf {},点击后不光这两处会发生重组;CompositionLocalProvider的 content 内的 所有可组合项包括子项都会重组。此时日志输出:

Dashboard1: 进入组合

Dashboard2: 进入组合

UserInfoLabel: 进入组合


适用场景

compositionLocalOf:需要局部灵活变动、且变动频繁的数据。
staticCompositionLocalOf:需要局部灵活变动、但几乎一成不变的数据(如皮肤、样式)

相关推荐
plainGeekDev14 小时前
依赖管理 → Version Catalog
android·java·kotlin
韩曙亮16 小时前
【Flutter】Flutter 进程保活 ③ ( 屏幕常亮设置 | Flutter 禁止息屏、关闭自动锁屏、屏蔽系统屏保 )
android·flutter·ios·屏幕常亮·禁止息屏
爱笑鱼16 小时前
NDK、SDK、APP、Framework、Native 到底怎么区分?
android
私人珍藏库17 小时前
[Android] 发票制作器-内置多行业专业模板
android·人工智能·app·软件·多功能
阿pin17 小时前
Android随笔-PMS
android·pms
悲凉的紫菜18 小时前
dariy
android·ide·android studio
2501_9159090619 小时前
IPA 深度混淆是什么意思?从混淆强度到实际效果的解读
android·ios·小程序·https·uni-app·iphone·webview
LT101579744419 小时前
2026年手机机型兼容性测试选型指南:安卓机型碎片化、APP闪退UI错乱如何解决
android·测试工具·ui·智能手机
阿pin20 小时前
Android随笔-view是如何显示到屏幕上
android·view
恋猫de小郭20 小时前
Flutter material/cupertino 解耦最新进展,已经在做新样式了
android·前端·flutter