1.相关概念
CompositionLocal
是定义数据的方式,而 CompositionLocalProvider
是在 Compose UI 树中传递这些数据的工具。二者合作,为 Compose 应用提供了一个强大的状态和数据流管理机制,使得数据可以在组件间按需传递,而无需通过复杂的层级传递或全局状态。
这种模式非常适合于主题、语言偏好、UI配置等的全局管理,极大地简化了复杂应用中的数据传递和状态管理问题。
CompositionLocal
CompositionLocal
是一个特殊的类型,用于定义和存储可以在 Compose UI 树中访问的数据。你可以将其视为一个可在组件间传递的容器,它携带数据。创建 CompositionLocal
时,你通常会提供一个默认值,这是为了在没有明确提供值的上下文中使用。
例如:
Kotlin
val LocalExampleData = compositionLocalOf { "Default Value" }
这行代码定义了一个 CompositionLocal
对象,其默认值为 "Default Value"
。CompositionLocal
本身不传递任何数据;它仅定义了数据的类型和默认值。
CompositionLocalProvider
CompositionLocalProvider 是一个 Composable 函数,用来在 Compose 的 UI 树中的某个点提供 CompositionLocal 的值。这个函数允许你在其作用域内覆盖 CompositionLocal 的值,从而所有在此作用域内的 Composable 函数都可以访问到这个新值。
例如,如果你想在特定的 UI 部分中使用不同的数据,你可以这样做:
Kotlin
CompositionLocalProvider(LocalExampleData provides "Special Value") {
// 这里的 Composable 函数可以使用 "Special Value"
Text(
text = LocalExampleData.current,
)
}
在这个作用域内,任何访问 LocalExampleData
的 Composable 都将获得 "Special Value"
而不是默认值。
2.设置CompositionLocal的默认值
为什么需要默认值?
默认值的主要目的是提供一个后备值,这样当数据没有在上游通过 CompositionLocalProvider
显式提供时,组件仍然可以正常访问一个有效的值。这是一种防止应用在运行时因为缺少所需数据而崩溃的安全措施。
如何定义 CompositionLocal
如果您想创建一个 CompositionLocal
,您必须在声明时提供一个默认值。
Kotlin
val LocalExampleData = compositionLocalOf { "Default Value" }
如何让创建时不给定默认值?
如果您的设计中需要在某个点后才确定 CompositionLocal
的值,而又不想在一开始就给出一个具体的默认值,您可以考虑以下几种方法:
(1).使用可空类型
您可以将 CompositionLocal
的默认值设置为 null
,这表示在没有提供值的情况下,默认值是 null
。然后,您可以在适当的时候通过 CompositionLocalProvider
提供具体的值。
Kotlin
val LocalExampleData = compositionLocalOf<String?> { null }
(2).使用哨兵值或逻辑检查
如果 null 对于您的应用场景不合适,您也可以使用一个特殊的值作为默认值,或者在使用时添加逻辑检查。
Kotlin
val LocalExampleData = compositionLocalOf { "UNINITIALIZED" }
然后,在使用时检查这个值:
Kotlin
@Composable
fun ExampleComponent() {
val data = LocalExampleData.current
if (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()
可以避免不恰当的默认设置。
实例解释
Kotlin
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.current
Text(text = "Hello, Compose!", color = colors.textColor)
}
3. 直接访问CompositionLocal默认值
如果你只定义了 CompositionLocal
而没有使用 CompositionLocalProvider
来覆盖它的值,你可以直接访问 CompositionLocal
中定义的默认值。
Kotlin
val LocalExampleData = compositionLocalOf { "Default Value" }
@Composable
fun DisplayData() {
val data = LocalExampleData.current
Text(text = "Data is: $data")
}
@Composable
fun App() {
DisplayData() // 这里会显示 "Data is: Default Value"
}
注意事项
尽管可以直接访问 CompositionLocal
的默认值,但实际上这种方式通常不是最佳实践,除非这个默认值确实是你在大部分情况下所需要的。通常,CompositionLocal
被设计用来在 UI 树的不同层级中传递动态数据或配置,其默认值作为一种安全的回退。
4.**CompositionLocal+**CompositionLocalProvider 的基本使用
-
定义 CompositionLocal :首先定义一个
CompositionLocal
。这是一个可以在组件树中传递的数据容器。 -
使用 CompositionLocalProvider 设置值 :在组件树的适当位置,使用
CompositionLocalProvider
来注入数据。 -
在组件中访问这些值 :在子组件中,你可以直接访问这些通过
CompositionLocal
提供的数据。
传递单个值
下面是一个具体的例子,展示了如何使用 CompositionLocalProvider
来传递主题信息.
Kotlin
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
在组件树中提供这些属性的值。
Kotlin
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
来实现这一点:
Kotlin
// 定义一个 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)
}