[Android]使用CompositionLocal隐式传值

news/2024/5/5 17:12:17

1.相关概念

CompositionLocal 是定义数据的方式,而 CompositionLocalProvider 是在 Compose UI 树中传递这些数据的工具。二者合作,为 Compose 应用提供了一个强大的状态和数据流管理机制,使得数据可以在组件间按需传递,而无需通过复杂的层级传递或全局状态。

这种模式非常适合于主题、语言偏好、UI配置等的全局管理,极大地简化了复杂应用中的数据传递和状态管理问题。

(1).CompositionLocal

CompositionLocal 是一个特殊的类型,用于定义和存储可以在 Compose UI 树中访问的数据。你可以将其视为一个可在组件间传递的容器,它携带数据。

compositionLocalOf 和 staticCompositionLocalOf 都用于创建 CompositionLocal 对象,这些对象允许你在组件树中定义可访问的数据,子组件可以通过这些数据进行通信而无需将数据通过参数显式传递。尽管它们的目的相似,但在使用和行为上有一些关键的区别:

compositionLocalOf

compositionLocalOf 用于创建一个 CompositionLocal,它需要一个默认值。这意味着当在组件树的任何位置访问这个 CompositionLocal 的值时,如果没有明确的提供者(CompositionLocalProvider),它将回退到这个默认值。

使用场景:

  • 当你希望确保无论组件树的哪个部分访问该 CompositionLocal 时,都有一个合理的默认值可以使用。
  • 适合那些在大多数情况下具有通用默认值的数据,比如应用的文本大小、颜色主题等。
import androidx.compose.runtime.compositionLocalOf// 使用默认值
val LocalExampleData = compositionLocalOf { "默认值" }@Composable
fun App() {// 以下组件将使用"默认值"ShowData()CompositionLocalProvider(LocalExampleData provides "特定值") {// 以下组件将使用"特定值"ShowData()}
}@Composable
fun ShowData() {val data = LocalExampleData.currentText(text = "数据: $data")
}
staticCompositionLocalOf 

staticCompositionLocalOf 创建的 CompositionLocal 不需要默认值。如果你尝试在没有提供者的情况下访问它的值,则会抛出异常。这强制开发者必须在使用这些数据之前明确地提供它们,增加了类型安全性。

使用场景:

  • 适用于那些没有合理默认值的数据,或者你希望确保每次使用时都显式设置其值的场景。
  • 常用于那些依赖于特定环境或配置的数据,如用户设置、语言偏好等。
// 没有默认值,使用时需要确保提供
val LocalRequiredData = staticCompositionLocalOf<String>()@Composable
fun App() {// 若下面的提供者被注释,则会抛出异常,因为LocalRequiredData没有默认值CompositionLocalProvider(LocalRequiredData provides "必需的值") {ShowRequiredData()}
}@Composable
fun ShowRequiredData() {val data = LocalRequiredData.currentText(text = "必需数据: $data")
}

 

(2).CompositionLocalProvider

CompositionLocalProvider 是一个 Composable 函数,用来在 Compose 的 UI 树中的某个点提供 CompositionLocal 的值。这个函数允许你在其作用域内覆盖 CompositionLocal 的值,从而所有在此作用域内的 Composable 函数都可以访问到这个新值。

例如,如果你想在特定的 UI 部分中使用不同的数据,你可以这样做:

CompositionLocalProvider(LocalExampleData provides "Special Value") {// 这里的 Composable 函数可以使用 "Special Value"Text(text = LocalExampleData.current,)
}

在这个作用域内,任何访问 LocalExampleData 的 Composable 都将获得 "Special Value" 而不是默认值。

2.设置CompositionLocal的默认值

为什么需要默认值?

默认值的主要目的是提供一个后备值,这样当数据没有在上游通过 CompositionLocalProvider 显式提供时,组件仍然可以正常访问一个有效的值。这是一种防止应用在运行时因为缺少所需数据而崩溃的安全措施。

如何定义 CompositionLocal

如果您想创建一个 CompositionLocal,您必须在声明时提供一个默认值。

val LocalExampleData = compositionLocalOf { "Default Value" }

如何让创建时不给定默认值?

如果您的设计中需要在某个点后才确定 CompositionLocal 的值,而又不想在一开始就给出一个具体的默认值,您可以考虑以下几种方法:

(1).使用可空类型

您可以将 CompositionLocal 的默认值设置为 null,这表示在没有提供值的情况下,默认值是 null。然后,您可以在适当的时候通过 CompositionLocalProvider 提供具体的值。

val LocalExampleData = compositionLocalOf<String?> { null }

(2).使用哨兵值或逻辑检查

如果 null 对于您的应用场景不合适,您也可以使用一个特殊的值作为默认值,或者在使用时添加逻辑检查。

val LocalExampleData = compositionLocalOf { "UNINITIALIZED" }

然后,在使用时检查这个值:

@Composable
fun ExampleComponent() {val data = LocalExampleData.currentif (data != "UNINITIALIZED") {Text("Data is: $data")} else {Text("Data is not initialized")}
}

(3).使用 error("...")

在 Jetpack Compose 中,使用 error("reason") 在 compositionLocalOf 初始化时提供一个默认值,实际上是一种确保开发者必须在使用该 CompositionLocal 前明确提供一个值的策略。

使用 error("...") 的原因

主要原因是确保 CompositionLocal 没有被错误或未预期地使用,同时没有提供必要的值。这通常适用于以下情况:

  • 明确性和安全性:通过抛出错误,开发者在开发过程中就能立即发现问题,而不是在应用运行过程中遇到不明确的行为或难以追踪的错误。这样可以确保所有使用这个 CompositionLocal 的组件都能获得正确的数据。

  • 严格的依赖管理:这种方式强迫开发者在使用该 CompositionLocal 的组件中,通过 CompositionLocalProvider 明确地提供一个值。它减少了对默认值的依赖,使得组件的数据流更加清晰和可控。

  • 无合适默认值:在某些情况下,可能没有一个合适的默认值可用。比如,在主题或配置特定的情况下,使用通用的默认值可能不适合所有使用场景。在这种情况下,使用 error() 可以避免不恰当的默认设置。

实例解释

data class CustomColors(val textColor: androidx.compose.ui.graphics.Color
)val LocalCustomColors = compositionLocalOf<CustomColors> { error("No CustomColors provided") 
}@Composable
fun MyApp() {CompositionLocalProvider(LocalCustomColors provides CustomColors(Color.Red)) {// 在这个作用域内,LocalCustomColors有具体的值ShowText()}// 如果在这里调用 ShowText(),将会抛出异常
}@Composable
fun ShowText() {val colors = LocalCustomColors.currentText(text = "Hello, Compose!", color = colors.textColor)
}

3.直接访问CompositionLocal默认值

如果你只定义了 CompositionLocal 而没有使用 CompositionLocalProvider 来覆盖它的值,你可以直接访问 CompositionLocal 中定义的默认值。

val LocalExampleData = compositionLocalOf { "Default Value" }@Composable
fun DisplayData() {val data = LocalExampleData.currentText(text = "Data is: $data")
}@Composable
fun App() {DisplayData() // 这里会显示 "Data is: Default Value"
}

注意事项

尽管可以直接访问 CompositionLocal 的默认值,但实际上这种方式通常不是最佳实践,除非这个默认值确实是你在大部分情况下所需要的。通常,CompositionLocal 被设计用来在 UI 树的不同层级中传递动态数据或配置,其默认值作为一种安全的回退。

4.CompositionLocal+CompositionLocalProvider 的基本使用

  • 定义 CompositionLocal:首先定义一个 CompositionLocal。这是一个可以在组件树中传递的数据容器。

  • 使用 CompositionLocalProvider 设置值:在组件树的适当位置,使用 CompositionLocalProvider 来注入数据。

  • 在组件中访问这些值:在子组件中,你可以直接访问这些通过 CompositionLocal 提供的数据。

传递单个值

下面是一个具体的例子,展示了如何使用 CompositionLocalProvider 来传递主题信息.

import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocal
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.material.Text
import androidx.compose.material.MaterialTheme
import androidx.compose.ui.graphics.Color// 步骤 1: 定义一个 CompositionLocal,用于存储颜色
val LocalCustomTextColor = compositionLocalOf { Color.Black }@Composable
fun App() {// 步骤 2: 使用 CompositionLocalProvider 提供颜色值CompositionLocalProvider(LocalCustomTextColor provides Color.Red) {// 步骤 3: 在子组件中使用这个颜色Greeting("Android")}
}@Composable
fun Greeting(name: String) {// 在 Text 组件中使用 CompositionLocal 中的颜色Text(text = "Greetings, $name",color = LocalCustomTextColor.current // 直接访问颜色)
}

传递多个值

如果想要在整个应用中传递和使用多个主题属性(如颜色、形状和字体),你可以定义多个 CompositionLocal 对象来存储这些属性。然后,使用 CompositionLocalProvider 在组件树中提供这些属性的值。

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.CornerSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontFamily.Companion.Monospace
import androidx.compose.ui.text.font.FontFamily.Companion.SansSerif
import androidx.compose.ui.unit.dp// 定义颜色
val LocalCustomTextColor = compositionLocalOf { Color.Black }// 定义形状
val LocalCustomShape = compositionLocalOf { RoundedCornerShape(corner = CornerSize(4.dp)) }// 定义字体
val LocalCustomFontFamily = compositionLocalOf { SansSerif }class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {Column {// 步骤 2: 使用 CompositionLocalProvider 来设置这些属性的值CompositionLocalProvider(LocalCustomTextColor provides Color.Red,LocalCustomShape provides RoundedCornerShape(10.dp),LocalCustomFontFamily provides Monospace) {// 步骤 3: 在子组件中使用这些值Greeting("Android")}}}}
}@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {Box(modifier = Modifier.background(color = Color.Gray, shape = LocalCustomShape.current).padding(16.dp)) {Text(text = "Hello $name!",color = LocalCustomTextColor.current,fontFamily = LocalCustomFontFamily.current)}
}

5.嵌套使用 CompositionLocalProvider

这种嵌套使用 CompositionLocalProvider 的方法非常有用,特别是在你需要根据界面的不同部分调整样式或功能时。每个 CompositionLocalProvider 可以覆盖其父 CompositionLocalProvider 提供的值,从而实现精细化的控制。

假设你有一个全局的颜色主题,但在某个特定屏幕或组件中,你想要一个不同的颜色主题。你可以通过嵌套使用 CompositionLocalProvider 来实现这一点:

// 定义一个 CompositionLocal 来存储颜色
val LocalCustomTextColor = compositionLocalOf { Color.Black }@Composable
fun App() {// 全局设置为红色CompositionLocalProvider(LocalCustomTextColor provides Color.Red) {Greeting("Hello, Compose!")// 在特定区域覆盖为蓝色CompositionLocalProvider(LocalCustomTextColor provides Color.Blue) {SpecialSection()}}
}@Composable
fun Greeting(name: String) {// 这里使用的是红色Text("Greetings, $name", color = LocalCustomTextColor.current)
}@Composable
fun SpecialSection() {// 这里使用的是蓝色Text("Welcome to the special section", color = LocalCustomTextColor.current)
}


http://www.mrgr.cn/p/63541774

相关文章

壁纸测试

本文来自博客园,作者:舟清颺,转载请注明原文链接:https://www.cnblogs.com/zqingyang/p/18156634

文献学习-37-动态场景中任意形状针的单目 3D 位姿估计:一种高效的视觉学习和几何建模方法

On the Monocular 3D Pose Estimation for Arbitrary Shaped Needle in Dynamic Scenes: An Efficient Visual Learning and Geometry Modeling Approach Authors: Bin Li,† , Student Member, IEEE, Bo Lu,† , Member, IEEE, Hongbin Lin, Yaxiang Wang, Fangxun Zhong, Me…

第二期书生浦语大模型训练营第四次笔记

大模型微调技术 大模型微调是一种通过在预训练模型的基础上&#xff0c;有针对性地微调部分参数以适应特定任务需求的方法。 微调预训练模型的方法 微调所有层&#xff1a;将预训练模型的所有层都参与微调&#xff0c;以适应新的任务。 微调顶层&#xff1a;只微调预训练模型…

Net8微服务之Consul、Ocelot、IdentityServer4

前言 情绪的尽头是沉默 1.微服务概念 1.1微服务发展 分布式解决性能问题,微服务解决维护性、扩展性、灵活性。1.2微服务概念 微服务(或称微服务架构),是一种现代化的软件架构方法,它将一个应用程序分解为多个小型、独立的服务单元,每个服务都负责特定的业务功能,并且可以独…

后台管理系统加水印(react)

效果 代码图片 代码 window.waterMark function (config) {var defaultConfig {content: 我是水印,fontSize: 16px,opacity: 0.3,rotate: -15,color: #ADADAD,modalId: J_waterMarkModalByXHMAndDHL,};config Object.assign({}, defaultConfig, config);var existMarkModal…

Net8微服务实战

前言 学习杨中科老师开源项目在线英语网站微服务 1.需求 服务拆分2.项目源码项目 类 说明Peng.ASPNETCore DistributedCacheHelper 分布式缓存帮助类MemoryCacheHelper 内存缓存帮助类UnitOfWorkFilter 工作单元筛选器Peng.Commons Validators文件夹 FluentValidation的扩展类L…

OpenCV 如何实现边缘检测器

返回:OpenCV系列文章目录&#xff08;持续更新中......&#xff09; 上一篇&#xff1a;OpenCV如何实现拉普拉斯算子的离散模拟 下一篇 :OpenCV系列文章目录&#xff08;持续更新中......&#xff09; 目标 在本教程中&#xff0c;您将学习如何&#xff1a; 使用 OpenCV 函数…

java实现wav的重采样

原因是之前写的TTS文件&#xff0c;需要指定采样率和单声道 但是TTS是用的Jacob调用COMsapi实现的 javaWNI10JACOB方式 SAPI底层支持的是C&#xff0c;C#【官方文档】 SpAudioFormat SetWaveFormatEx method (SAPI 5.4) | Microsoft Learn 用C实现的方式【可指定输出的WAV…

vue3的ref和reactive

ref RefImpl&#xff1a;引用对象&#xff0c;如果想让一个普通变量变成响应式的&#xff0c;就需要把这个变量丢给ref。 修改的时候需要使用name.value进行修改。使用的时候直接使用name字段就行。 补充&#xff1a;const obj{name:’li’}定义的对象是可以修改对象里面的属性…

Oracle 脑残 CBO 优化案例

今天晚上下班回来才有空看群,群友发了一条很简单的慢SQL问怎么优化。 非常简单,我自己模拟的数据。 表结构:-- auto-generated definition CREATE TABLE HHHHHH (ID NUMBER NOT NULLPRIMARY KEY,NAME VARCHAR2(20),PARAGRAPH_ID NUMBER ) /CREATE INDEX I…

甘特图是什么?如何利用其优化项目管理流程?

甘特图是项目管理软件中十分常见的功能&#xff0c;可以说每一个项目经理都要学会使用甘特图才能更好的交付项目。什么是甘特图&#xff1f;甘特图用来做什么&#xff1f;简单来说一种将项目任务与时间关系直观表示的图表&#xff0c;直观地展示了任务进度和持续时间。 一、甘特…

【pytorch学习】之线性神经网络-实现线性回归

线性回归的从零开始实现 在了解线性回归的关键思想之后,我们可以开始通过代码来动手实现线性回归了。我们将从零 开始实现整个方法,包括数据流水线、模型、损失函数和小批量随机梯度下降优化器。虽然现代的深度学习框架几乎可以自动化地进行所有这些工作,但从零开始实现可以…

RK3588 Android13 鼠标风格自定义动态切换

前言 电视产品,客户提供了三套鼠标图标过来,要求替换系统中原有丑陋风格且要支持动态切换, 并且在 TvSetting 中要有菜单,客户说啥就是啥呗,开整。 效果图 test framework 部分修改文件清单 png 为鼠标风格资源图片,这里就不提供了,可自由找一个替换一下就行 framew…

苹果开发初学者指南:Xcode 如何为运行的 App 添加环境变量(Environmental Variable)

概览 Xcode 15 在运行 SwiftUI 代码时突然报告如下警告&#xff1a; Error: this application, or a library it uses, has passed an invalid numeric value (NaN, or not-a-number) to CoreGraphics API and this value is being ignored. Please fix this problem. 不仅如此…

cdh cm界面HDFS爆红:不良 : 该 DataNode 当前有 1 个卷故障。 临界阈值:任意。(Linux磁盘修复)

一、表现 1.cm界面 报错卷故障 检查该节点&#xff0c;发现存储大小和其他节点不一致&#xff0c;少了一块物理磁盘 2.查看该磁盘 目录无法访问 dmesg检查发现错误 dmesg | grep error二、解决办法 移除挂载 umount /data10 #可以移除挂载盘&#xff0c;或者移除挂载目…

boss直聘__zp_stoken__逆向

声明 本文章中所有内容仅供学习交流,抓包内容、敏感网址、数据接口均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关,若有侵权,请联系我立即删除! 目标网站 aHR0cHM6Ly93d3cuemhpcGluLmNvbS93ZWIvZ2Vlay9qb2I/cXVlcnk9SmF2YSZjaXR5PTEwM…

带头循环双向链表专题

1. 双向链表的结构 带头链表⾥的头节点&#xff0c;实际为“哨兵位”&#xff0c;哨兵位节点不存储任何有效元素&#xff0c;只是站在这⾥“放哨 的” “哨兵位”存在的意义&#xff1a; 遍历循环链表避免死循环。 2. 双向链表的实现 2.1双向链表结构 typedef int DataTyp…

delphi清理txt文件多余的空格

PDF文件转存为文本,多了一堆不需要的空格,写个小程序处理一下,没逻辑,直接上代码。 delphi用的是XE11.3unit UnitSmallMain;interfaceusesSystem.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,FMX.Types, FMX.Controls, FMX.Forms, FMX.Graph…

[算法学习笔记] 并查集

并查集。非基础教学。提示:本文并非并查集模板讲解,是在模板基础上的进一步理解以及拓展。 Review 并查集可以用来维护集合问题。例如,已知 \(a,b\) 同属一个集合,\(b,c\) 同属一个集合。那么 \(a,b,c\) 都属一个集合。 并查集分为 合并,查询 操作。定义 \(fa_i\) 表示点 …

sherpa + ncnn 离线语音识别

目录结构 前言音视频格式转为wavsherpa-ncnn编译LinuxWindowswindows编译中遇到的问题问题“nmake -? failed with: no such file or directory”编译失败原因 成功编译截图 可执行程序说明模型下载语言识别测试LinuxWindows 参考文献 前言 小编需要实现离线音视频语言部分识…