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}")
}
  • 通过 compositionLocalOf 或 staticCompositionLocalOf 创建的类型,它的直接或间接父类 是 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:需要局部灵活变动、但几乎一成不变的数据(如皮肤、样式)

相关推荐
墨天梦3 小时前
B17_LifecycleLiveData与Flow
android·kotlin
_祝你今天愉快4 小时前
UI核心 - 绘制流程(测量、布局、绘制)
android
冬奇Lab5 小时前
LLM 驱动的自动化测试系列(07):移动端自动化(三)——Mobile-Agent-v3 与自研 GUI-Owl 模型路线
android·人工智能·测试
leobertlan8 小时前
痛苦系列 | DSP-02 频域切片:DTFT与DFT的探索
android·后端
leobertlan9 小时前
痛苦系列 | DSP-01 从连续到离散:DSP基础与采样
android·后端
事圆则缓11 小时前
Android 视频压缩上传实战:从 content Uri、转码到断点续传
android
冉冉同学14 小时前
AI Agent 开始操作真实手机:移动端自动化的 3 个新考点
android·ai编程
帅得不敢出门15 小时前
Android Framework关闭软件低电关机功能测试PMIC低电保护
android
恋猫de小郭16 小时前
Flutter + EmbeddingGemma 2,谷歌发布完全端侧的 AI Edge Foresight
android·前端·flutter
墨天梦17 小时前
B16_Material主题与可访问交互
android·kotlin·交互