Compose中CompositionLocal
是一种用于在Compose组件树中传递和共享数据的机制,它允许在组件树中向下传递并且在任何地方可以访问此数据。本文主要从简介、简单使用CompositionLocal
、自定义CompositionLocal
和实现换肤功能四个模块介绍。
简介
文章开头我们知道了CompositionLocal
是用于组件树中传递和共享数据的作用,Compose原生也有很多地方使用了它,比如LocalContext
、LocalContentColor
等都是采用CompositionLocal
的方式进行数据的共享。
在没有使用CompositionLocal
之前,我们传递数据通常都是从组件树的上级一层一层的传递到下级,这样数据的传递就会呈显示传递;如果使用CompositionLocal
之后,只需要在顶层方法中定义好数据,然后组件树中任何层级都可以获取和更改它的数值,这样在数据的传递中它是呈隐式传递,这样在维护代码和阅读代码的过程中都是提供了良好的可读性。
简单使用CompositionLocal
为了了解CompositionLocal
的机制,我么首先通过自带的LocalContentColor
来学习下如何使用和局部修改它的值
LocalContentColor
是Compose定义的一个CompositionLocal
对象,它是用于传递和共享当前主题下内容的颜色值
val LocalContentColor = compositionLocalOf { Color.Black }
它采用的是compositionLocalOf
的方式来定义,具体使用看下方代码
这里我们用三个Text
来展示初始颜色值和提供局部颜色之后的区别,通过CompositionLocalProvider
方法来提供局部的值改变,它接收两个参数,分别为value
和content
,value
就是我们提供的颜色值,然后在content
中组件的颜色就会获取到我们提供的颜色。
LocalContentColor provides Color.Blue
是提供颜色值的方法,providers
是一个infix
修饰的方法,此写法等同于LocalContentColor.provides(Color.Blue)
,下面我们看看具体实现的效果:
通过截图可以看出,最上面字体颜色就是系统提供的原始黑色,后面两行字体颜色分别是我们局部修改LocalContentColor
的颜色值,最下面那行文字依旧是系统提供的原始颜色,它不收CompositionLocalProvider
影响。
自定义CompositionLocal
Compose为我么提供了两种创建CompositionLocal
的方式,分别为:
compositionLocalOf{}
:使用这种方式定义的CompositionLocal
在值更改的时候,只会重组CompositionLocalProvider{ content()}
中content
读取current
的地方;
staticCompositionLocalOf{}
:与compositionLocalOf
不一样,更改该值会导致CompositionLocalProvider{ content() }
的content
Lambda被整体重组,而不仅仅是content
中读取current
值的地方。
从上面可以看出static
的方式在值更改后影响更大,一般static
方式更适用全局定义的某个数据或者标志,Compose中LocalLifecycleOwner
就是采用的static
的方式
接着我们来创建自己维护的一个CompositionLocal
,在简单使用环节我们了解了官方提供的LocalContentColor
机制之后,我们先仿照它来自己写一个CompositionLocal
,也是用于传递和共享字体颜色使用。
首先定义自己的CompositionLocal
,并且设置默认颜色为Green
然后将上面代码稍微修改一下,修改如下:
这里注意的一个点就是需要显示的设置Text
的字体颜色,如果不设置的话还是默认使用系统主题的颜色,然后在CompositionLocalProvider
中将LocalContentColor
修改为我们自己的CustomColor
,再来看下具体效果
从截图中可以看到我们自定义的颜色也是生效了,而且局部修改的情况下,颜色值也是正确替换。
本小节的最后我们再来看下compositionLocalOf{}
和staticCompositionLocalOf{}
的区别,通过下面的例子可直观的了解二者对于重组的不同之处。
首先分别定义compositionLocalOf{}
和staticCompositionLocalOf{}
的对象
然后定义三个方块,中间那个方块的背景颜色引用CompositionLocal
对应的颜色值,每个方块正上方添加一个文本,此文本的文字初始一致,但是在界面组合完成之后更改,再更改CompositionLocal
值之后,观察三个颜色和上方文本是否有变化
方块的具体代码如下:
接着编码具体切换代码
这里先体验的是NormalComposotionLocalOf{}
方式,先看此方式的效果
然后将NormalCompositionLocal
替换成StaticCompositionLocal
的方式,再看看其实现的效果
通过上面两个GIF可以看出,在compositionLocalOf
的方式下,之后中间方块发生了重组,上方的文字从init变成了recompose,而staticCompositionLocal
的方式,所有方块都发生了重组,方块的上方文字都发生了变化,这个例子就可以直观的表现出二者对于重组的不同之处。
实现动态换肤功能
在具体编码之前,我们先理一下换肤的几个步骤:
- 通过枚举定义我们需要的主题皮肤类型
- 定义一个管理类来保存当前主题皮肤的值(在实际项目中还需要保存到本地,下次启动时需要获取到切换的具体值)
- 定义主题颜色,有几个主题就需要定义对应几个颜色
- 通过
CompositionLocal
引用当前主题对应的具体颜色,向全局传递当前主题类型,并且在切换皮肤的时候通知全局更换对应的颜色 - 在组件中引用
CompositionLocal
的值,这样就可以在切换主题时动态改变组件引用的颜色值。
了解了具体思路之后,下面我们就开始实际的编码过程。
首先定义主题皮肤类型
然后定义一个静态类,里面通过State
保存当前的主题类型,默认为白色主题
接着开始定义四种主题各自对应的具体颜色
现在我们就可以通过CompositionLocal
来定义App全局的主题颜色了,默认采用白色主题颜色,注意这里采用的是staticCompositionLocalOf{}
方式,因为切换主题我们大多数组件都是需要来重组改变颜色的。
最后一步,自定义一个主题,在主题外层通过CompositionLocalProvider
提供AppColors
,这样就可以通过LocalThemeColor
在具体的组件中引用对应的颜色
上面代码有几点需要注意:
- 在
CompositionLocalProvider
之前我们需要根据ThemeManager
中当前主题获取到对应的颜色 - 无论是组件的背景色还是文字的颜色都直接使用
LocalThemeColor.current.**
- 切换主题时,直接调用
ThemeManager.currentThemeType
,修改其值即可完成主题切换
最后我们来看下具体的效果
背景色和文字的颜色都是跟随主题变换随之切换的,状态栏和导航栏的颜色也可以跟随主题变换而改变哦~
关于我
我是Taonce,如果觉得本文对你有所帮助,帮忙点个赞或者收藏,谢谢~