Compose CompositionLocal的应用场景
- CompositionLocal的使用场景
- CompositionLocalOf()和staticCompositionLocalOf()
- Material中CompositonLocal的应用
CompositionLocal的用法
CompositionLocal的使用场景
在Compose函数里,如果要用到函数外提供的值,一般都要通过函数定义好参数,外部调用时传入进去。
这种方法表面看没啥问题,但在一些场景下就不是很方便。比如一个函数A嵌套一个函数B,函数B嵌套函数C,并且这A、B、C的函数都用到这个参数,那对三个函数来说都需要定义这个参数,就不是那么方便。
那用个全局变量不就可以解决需要重复定义参数,确实可以。但全局变量有个副作用就是影响的范围比较大。本身我们定义的参数只会影响到函数内部。这个时候我们就可以使用CompositionLocal
:它具有穿透函数功能的局部变量
CompositionLocal
适用场景:用来提供上下文数据,不扩大影响范围。
一个简单的例子
举个例子:一个Text文本
根据提供的颜色设置背景
使用方法:
1.使用compositionLocalOf
先定义一个LocalColor
变量,提供一个默认值
val LocalColor = compositionLocalOf { Color.Black }
如果无法提供默认值,可以使用error
抛出一个异常:
val LocalColor = compositionLocalOf { error("LocalColor 没有提供值!") }
2.使用CompositionLocalProvider
提供一个蓝色值:
CompositionLocalProvider(LocalColor provides Color.Blue) {
LocalColorText()
}
3.函数中使用LocalColor.current
获取提供的颜色值
@Composable
fun LocalColorText() {
Text("我是什么颜色的", Modifier.background(LocalColor.current))
}
完整代码示例如下:
// 定义好本地颜色,默认是颜色是Black
val LocalColor = compositionLocalOf { Color.Black }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Column {
// 该Provider下提供蓝色
CompositionLocalProvider(LocalColor provides Color.Blue) {
LocalColorText()
}
// 该Provider下提供绿色
CompositionLocalProvider(LocalColor provides Color.Green) {
LocalColorText()
}
}
}
@Composable
fun LocalColorText() {
Text("我是什么颜色的", Modifier.background(LocalColor.current))
}
}
上述代码中,两次使用CompositionLocalProvider
,分别提供不同的颜色值,它们不互相影响,只影响它括号包住的内容,对本代码来说也就是它们各自的LocalColorText函数
。
CompositionLocalOf()和staticCompositionLocalOf()
除了compositionLocalOf
定义,还可以使用staticCompositionLocalOf()
,用法类似:
val LocalColor = staticCompositionLocalOf { Color.Black }
compositionLocalOf()
和staticCompositionLocalOf()
的区别:
compositionLocalOf()
会精准订阅、值改变时精准Recompose
,所以性能的消耗在于订阅阶段。staticCompositionLocalOf()
不支持订阅、值改变时全量Recompose
,所以性能的消耗在于更新阶段。
所以基于性能消耗的阶段,要根据不同场景选择:
compositionLocalOf()
适合值可能会经常改变的场景。staticCompositionLocalOf()
适合值不会改变或⼏乎不会改变的场景。比如compose源码里提供的LocalView
val LocalView = staticCompositionLocalOf<View> {
noLocalProvidedFor("LocalView")
}
Material中CompositonLocal的应用
CompositonLocal
很适用的场景之一是主题,比如我们看Material
库中MaterialTheme
的源码对它就有很好的应用:
@Composable
fun MaterialTheme(
colors: Colors = MaterialTheme.colors, // 默认使用MaterialTheme.colors
typography: Typography = MaterialTheme.typography,
shapes: Shapes = MaterialTheme.shapes,
content: @Composable () -> Unit
) {
val rememberedColors = remember {
colors.copy()
}.apply { updateColorsFrom(colors) }
val rippleIndication = rememberRipple()
val selectionColors = rememberTextSelectionColors(rememberedColors)
// 关注这个LocalColors,提供rememberedColors值
CompositionLocalProvider(
LocalColors provides rememberedColors,
LocalContentAlpha provides ContentAlpha.high,
LocalIndication provides rippleIndication,
LocalRippleTheme provides MaterialRippleTheme,
LocalShapes provides shapes,
LocalTextSelectionColors provides selectionColors,
LocalTypography provides typography
) {
ProvideTextStyle(value = typography.body1) {
PlatformMaterialTheme(content)
}
}
}
Material
的colors
就用到了LocalColors
,
val colors: Colors
@Composable
@ReadOnlyComposable
get() = LocalColors.current
// 默认值是lightColors方法提供的
internal val LocalColors = staticCompositionLocalOf { lightColors() }
如果我们在MaterialTheme
里,使用Material
的Button
控件,它就会使用lightColors()
提供的主题属性
MaterialTheme() {
Button(onClick = { }) {
Text("对比按钮的颜色和colors里的background")
}
}
比如Button
的背景就会使用MaterialTheme.colors.primary
fun Button(
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
elevation: ButtonElevation? = ButtonDefaults.elevation(),
shape: Shape = MaterialTheme.shapes.small,
border: BorderStroke? = null,
colors: ButtonColors = ButtonDefaults.buttonColors(), // 背景颜色在这里面
contentPadding: PaddingValues = ButtonDefaults.ContentPadding,
content: @Composable RowScope.() -> Unit
){ ...}
fun buttonColors(
backgroundColor: Color = MaterialTheme.colors.primary, // 背景颜色在这里
contentColor: Color = contentColorFor(backgroundColor),
disabledBackgroundColor: Color = MaterialTheme.colors.onSurface.copy(alpha = 0.12f)
.compositeOver(MaterialTheme.colors.surface),
disabledContentColor: Color = MaterialTheme.colors.onSurface
.copy(alpha = ContentAlpha.disabled)
){...}
所以,如果没有为LocalColors
提供值的话,Button
的背景就会使用默认值lightColors()
提供的primary
颜色
fun lightColors(
primary: Color = Color(0xFF6200EE),
primaryVariant: Color = Color(0xFF3700B3),
secondary: Color = Color(0xFF03DAC6),
secondaryVariant: Color = Color(0xFF018786),
background: Color = Color.White,
surface: Color = Color.White,
error: Color = Color(0xFFB00020),
onPrimary: Color = Color.White,
onSecondary: Color = Color.Black,
onBackground: Color = Color.Black,
onSurface: Color = Color.Black,
onError: Color = Color.White
): Colors = Colors(...)
当我们想改个主题也很简单:
val colors = darkColors()
MaterialTheme(colors = colors) {
Button(onClick = { }) {
Text("对比按钮的颜色和colors里的background")
}
}