[Android]使用CompositionLocal隐式传值

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.mzph.cn/pingmian/2623.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用Python实现批量删除MYSQL数据库的全部外键

我先说下场景&#xff0c;昨天因为我们使用了Java工作流框架flowable&#xff0c;它自动生成了许多工作流相关的表&#xff0c;但是这些表都有外键关联&#xff0c;如果单纯的使用sql语句去一个一个的删除外键&#xff0c;那会非常麻烦&#xff0c;所以我写了一个Python脚本来进…

云上如何实现 Autoscaling: AutoMQ 的实战经验与教训

01 背景 弹性是云原生、Serverless 的基础。AutoMQ 从软件设计之初即考虑将弹性作为产品的核心特质。对于 Apache Kafka 而言&#xff0c;由于其存储架构诞生于 IDC 时代&#xff0c;针对物理硬件设计&#xff0c;存储层强依赖本地存储&#xff0c;已不能很好地适应现在云的时…

Python自动化系列---Python基础2

1、 变量&#xff1a;存储数据的 保险柜&#xff1a;钱&#xff0c;金条&#xff0c;户口本&#xff0c;珠宝&#xff0c;古董 — 存储东西 数据类型&#xff1a; int float bool str 变量名&#xff08;见名知意&#xff09;&#xff1a;标识符&#xff08;不能用关键字&…

Linux:Centos7.x系统,无效的密码问题处理

一、情景说明 我新创建了Centos7系统&#xff0c;在使用的过程中&#xff0c;我需要创建一个test账号 那么&#xff0c;同时我就要给这个账号设置一个密码 为了方便&#xff0c;我设置成123456 就报错了 二、解决办法 其实这个问题很容易处理&#xff0c;不需要像其他帖子说…

项目报错com.mall.common.domain.request那么就说明你的项目里面是找不到导入类的包名或者路径

当你的项目里面一直报错是找不到导入类的包名或者路径的时候&#xff1a;com.mall.common.domain.request 这个问题我们阔以分为几个角度来想 1、包路径错误&#xff1a;确保com.mall.common.domain.request这个包路径在项目中是正确的。可能的情况是包名写错了&#xff0c;或…

java包装类型详解

一、用途 在Java语言中&#xff0c;包装类型&#xff08;Wrapper Classes&#xff09;是一种特殊的类&#xff0c;它们将八个基本数据类型&#xff08;byte、short、int、long、float、double、char、boolean&#xff09;封装在一个类中。这些包装类&#xff08;如Integer、Lon…

识别有效的IP地址和掩码并进行分类统计

问题概要 请解析IP地址和对应的掩码&#xff0c;进行分类识别。要求按照A/B/C/D/E类地址归类&#xff0c;不合法的地址和掩码单独归类。 所有的IP地址划分为 A,B,C,D,E五类 A类地址从1.0.0.0到126.255.255.255; B类地址从128.0.0.0到191.255.255.255; C类地址从192.0.0.0到223.…

opencv改变像素点的颜色---------c++

改变像素点的颜色 #include <opencv2/opencv.hpp> #include <opencv2/core.hpp> #include <filesystem>bool opencvTool::changeColor(const std::string image_p, int x_coor, int y_coor, const cv::Scalar color) {std::filesystem::path file(image_p);…

大模型检索召回系统:RAG技术的全面调查与未来展望

随着人工智能技术的飞速发展&#xff0c;大型语言模型&#xff08;LLMs&#xff09;在自然语言处理&#xff08;NLP&#xff09;领域取得了显著成就。然而&#xff0c;这些模型在处理特定领域或知识密集型任务时仍面临挑战&#xff0c;如产生错误信息或“幻觉”。为了克服这些难…

MC33665 + MC33774 控制流程及 TPL3 帧结构介绍

一. 概述&#xff1a; MC33665A&#xff1a;通用电池管理通信网关和变压器物理层 (TPL) 收发器。该设备通过标准通信协议转发来自不同 TPL&#xff08;NXP 的隔离菊花链协议&#xff09;端口的消息&#xff0c;标准通信协议可确保与市场上可用的微控制器兼容。 MC33774&…

vue实现进入某个页面后替换地址栏路径

需求背景&#xff1a;a系统进入b系统首页&#xff08;"/index"&#xff09;需要携带token&#xff0c;如 example.com/index?token"thisIsMyToken" 需要再b系统中将地址栏携带的token清除 getBtnType(type) {this.$router.push({path: "/",quer…

Fork for Mac v2.42 激活版 Git客户端

Fork for Mac是一款运行在Mac平台上的Git客户端&#xff0c;Fork Mac版具备基本的取、推、提交、修改、创建和删除分支和标签、创建和删除远程备份等功能&#xff0c;还有实用的差异查看器&#xff0c;你可以通过清晰的视图快速发现源代码中的更改。 Fork for Mac v2.42 激活版…

Golang | Leetcode Golang题解之第42题接雨水

题目&#xff1a; 题解: func trap(height []int) (ans int) {n : len(height)if n 0 {return}leftMax : make([]int, n)leftMax[0] height[0]for i : 1; i < n; i {leftMax[i] max(leftMax[i-1], height[i])}rightMax : make([]int, n)rightMax[n-1] height[n-1]for i…

常见术语:DI/DO、AI/AO 和 I/O

在自动化和控制系统领域&#xff0c;DI/DO、AI/AO 和 I/O 是常见的术语&#xff0c;分别代表不同类型的输入输出接口。它们在工业自动化、楼宇自动化、机器人技术等领域有广泛应用。下面详细解释这些术语及其使用示例。 一、DI/DO&#xff08;数字输入/数字输出&#xff09; 数…

React vs React Native写法上的不同

标签 <div> -> <View> / <ScrollView><p> -> <Text><input> -> <TextInput><image> -> <Image><button> -> <Button>css background-image -> <ImageBackground> 除此之外还有一…

一个简单的kafka 消费者

写一个简单的kafka 消费者 1. 依赖 <dependency><groupId>org.springframework.kafka</groupId><artifactId>spring-kafka</artifactId></dependency>2. 消费者 import com.xxxx.npi.module.common.msg.enums.Topic; import org.apache.…

watchEffect的使用

watchEffect 是 Vue 3 Composition API 中的一个函数&#xff0c;它用于在响应式数据变化时自动执行一个副作用函数。与 watch 不同&#xff0c;watchEffect 会自动收集其执行过程中访问到的响应式依赖&#xff0c;并在这些依赖发生变化时重新运行副作用函数。这意味着你不需要…

预防oracle的漏洞及其提权

防止Oracle数据库的漏洞及其潜在的权限提升&#xff0c;需要实施一系列综合的安全措施。这些措施不仅涉及技术配置&#xff0c;还包括过程管理和持续的安全评估。以下是有效防御Oracle数据库漏洞和提权攻击的一些关键步骤&#xff1a; 1. 安装和配置 安装最新安全补丁 定期更…

git提交注释规范插件

1、前言 为什么要注重代码提交规范&#xff1f; 在团队协作开发时&#xff0c;每个人提交代码时都会写 commit message。 每个人都有自己的书写风格&#xff0c;翻看我们组的git log, 可以说是五花八门&#xff0c;十分不利于阅读和维护。 一般项目开发都是多分支共存&#x…

关于CPP类中字符串成员初始化

直接看代码吧 #include <iostream> #include <string>/* A string is actually an object of the C++ Standard Library class string. This class is defined in header <string>, and the name string, like cout, belongs to namespace std. To enable …