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:需要局部灵活变动、但几乎一成不变的数据(如皮肤、样式)

相关推荐
YF02111 小时前
Android 权限系统的演变与深度治理
android·app
左小左2 小时前
🔥🔥🔥 我用AI基于 Tauri + Vue 3 写了个 ADB 桌面工具,把命令行的脏活全干了
android·vue.js·rust
花花鱼2 小时前
android studio 图标的使用及处理
android·ide·android studio
JJay.2 小时前
什么时候该用 BLE,什么时候该用 SPP?很多 Android 项目一开始就做错了
android
山峰哥2 小时前
SQL性能飙升秘籍:从索引策略到查询优化全解析
android
黄林晴2 小时前
稳定性全面升级!Compose Multiplatform 1.11 RC 正式推送
android
恋猫de小郭2 小时前
实用性 Max ,新 Flutter & Dart Agent Skills 深度解读
android·前端·flutter
重生之小比特2 小时前
【MySQL 数据库】表的约束
android·数据库·mysql
Kapaseker3 小时前
Android 中的 MVVM 是如何构建起来的
android·kotlin