连云港市城乡建设管理局网站,国外网站代理,网站流量指的是什么意思,苏州网站建设软件Jetpack Compose实战教程#xff08;五#xff09;
第五章 如何在Compose UI中使用基于命令式UI的自定义View 文章目录 Jetpack Compose实战教程#xff08;五#xff09;一、前言二、本章目标三、开始编码3.1 先让自定义控件能跑起来3.2给自定义控件使用compose的方式赋值…Jetpack Compose实战教程五
第五章 如何在Compose UI中使用基于命令式UI的自定义View 文章目录 Jetpack Compose实战教程五一、前言二、本章目标三、开始编码3.1 先让自定义控件能跑起来3.2给自定义控件使用compose的方式赋值3.3如何在非composable作用域下使用被记忆的变量3.4 及时释放资源 一、前言
刚从命令式UI转向compose ui的小伙伴往往会有一个疑问如果我的项目代码中用到了一些第三方的sdk它们里面有一些自定义控件我总不能去改别人的源码用compose ui 重写一遍吧成本多大啊。或者我自己项目本身就写了一些自定义控件功能很多的全部要用compose重写一遍成本也很高。不用慌compose提供了一个支持调用命令式UI的自定义View的组件。
二、本章目标
能完整的把目前项目中暂时无法用compose重写的控件熟练的运用至compose中
友情提醒如果各位看官有不懂的代码可以先看一下之前的章节循序渐进如果还是有不懂的可以给我留言
三、开始编码
3.1 先让自定义控件能跑起来
这里我以引用的第三方sdk的自定义控件腾讯自研的pag动画举例
BaseTheme {Surface(modifier Modifier.fillMaxSize(), color MaterialTheme.colorScheme.background) {ConstraintLayout {//AndroidView这个就是支持我们调用命令式UI的组件了在factory中声明这个控件是什么AndroidView(factory {PAGView(it).apply {this.composition PAGFile.Load(assets, Constant.PagConstant.getSourceByName(002901))this.setRepeatCount(0)this.play()}})}}}运行之后程序正常运行跑了起来但它是铺满屏幕的我们实际使用时可能需要指定它的位置和大小这里提供两个方案
BaseTheme {Surface(modifier Modifier.fillMaxSize(), color MaterialTheme.colorScheme.background) {ConstraintLayout {AndroidView(factory {PAGView(it).apply {//方案一使用我们熟知的kotlin代码来指定大小val params ConstraintLayout.LayoutParams(200f.dp2px().toInt(),200f.dp2px().toInt())this.layoutParams paramsthis.composition PAGFile.Load(assets, Constant.PagConstant.getSourceByName(002901))this.setRepeatCount(0)this.play()}}, modifier Modifier //方案二使用compose 的modifier来指定大小.width(200.dp).height(200.dp))}}}两个方案都可以将这个PAGView的大小和宽高指定为200dp但建议使用方案二因为如果这个AndroidView是需要依赖其它compose控件的位置而发生改变的话那么方案一就无效了。
3.2给自定义控件使用compose的方式赋值
上述代码我们是写死了动画的执行资源以及执行次数的那么想要动态改变它要怎么处理呢 在实现这个逻辑之前我需要先解释一个东西上述代码只贴了compose ui的具体代码它的完整代码是这样的
override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent(content {viewRoot()})}Composable
fun viewRoot() {BaseTheme {Surface(modifier Modifier.fillMaxSize(), color MaterialTheme.colorScheme.background) {ConstraintLayout {AndroidView(factory {PAGView(it).apply {//方案一使用我们熟知的kotlin代码来指定大小val params ConstraintLayout.LayoutParams(200f.dp2px().toInt(),200f.dp2px().toInt())this.layoutParams paramsthis.composition PAGFile.Load(assets, Constant.PagConstant.getSourceByName(002901))this.setRepeatCount(0)this.play()}}, modifier Modifier //方案二使用compose 的modifier来指定大小.width(200.dp).height(200.dp))}}}}请留意这个 Composable注解它代表了viewRoot 这个函数可以使用compose 的相关代码这是因为compose有要求所有compose ui的代码只能在Composable作用域下执行。这意味着如果我们是要在kotlin代码中去动态改变值的话那么我们就不能使用compose ui的相关参数我们先来看在使用compose ui相关参数的办法
BaseTheme {Surface(modifier Modifier.fillMaxSize(), color MaterialTheme.colorScheme.background) {ConstraintLayout {//这里我们定义了一个用于观察的可变参数var compositionValue remember {mutableStateOf(PAGFile.Load(assets, Constant.PagConstant.getSourceByName(002901)))}var testBoolean false //定义一个boolean变量AndroidView(factory {PAGView(it).apply {this.composition compositionValue.value //然后将它的值赋值给PAGView使用this.setRepeatCount(0)this.play()}}, modifier Modifier.width(200.dp).height(200.dp).clickable {testBoolean !testBoolean //点击一次就改变一次值从而改变PAGView播放的动画资源if(testBoolean){compositionValue.value PAGFile.Load(assets, Constant.PagConstant.getSourceByName(002201))}else{compositionValue.value PAGFile.Load(assets, Constant.PagConstant.getSourceByName(002901))}})}}}代码写完了然后运行一下发现点击之后并没有任何变化这里就要提及一下AndroidView的这个组件了我们先点击进去看一下AndroidView里面都干了啥
Composable
UiComposable
fun T : View AndroidView(factory: (Context) - T,modifier: Modifier Modifier,update: (T) - Unit NoOpUpdate
) {AndroidView(factory factory,modifier modifier,update update,onRelease NoOpUpdate)
}如果再点击里面的AndroidView则能看到它的具体实现但我们目前所引用的这个就能解决我们的问题先不做过深讲解我们先来看上面的几个函数factory是我们声明了这个自定义View是啥我们上述的代码是初始化了这个PAGView并给它赋了值但如果要它的参数动态改变的话我们要使用update函数将代码改成如下
BaseTheme {Surface(modifier Modifier.fillMaxSize(), color MaterialTheme.colorScheme.background) {ConstraintLayout {var compositionValue remember {mutableStateOf(PAGFile.Load(assets, Constant.PagConstant.getSourceByName(002901)))}var testBoolean falseAndroidView(factory {PAGView(it).apply {this.composition compositionValue.valuethis.setRepeatCount(0)this.play()}}, modifier Modifier.width(200.dp).height(200.dp).clickable {testBoolean !testBooleanif(testBoolean){compositionValue.value PAGFile.Load(assets, Constant.PagConstant.getSourceByName(002201))}else{compositionValue.value PAGFile.Load(assets, Constant.PagConstant.getSourceByName(002901))}},update {it.composition compositionValue.value //将数据的改动设置到update函数中来})}}}再次运行得到了我们想要的效果。细心的网友应该会发现一些问题为什么上面的compositionValue要用remember括起来并且里面还用了一个mutableStateOf来初始化参数值直接像testBoolean一样不行吗 所谓实践出真知那么我们来修改一下代码改成如下
BaseTheme {Surface(modifier Modifier.fillMaxSize(), color MaterialTheme.colorScheme.background) {ConstraintLayout {var compositionValue PAGFile.Load(assets, Constant.PagConstant.getSourceByName(002901))var testBoolean falseAndroidView(factory {PAGView(it).apply {this.composition compositionValuethis.setRepeatCount(0)this.play()}}, modifier Modifier.width(200.dp).height(200.dp).clickable {testBoolean !testBooleanif(testBoolean){compositionValue PAGFile.Load(assets, Constant.PagConstant.getSourceByName(002201))}else{compositionValue PAGFile.Load(assets, Constant.PagConstant.getSourceByName(002901))}},update {it.composition compositionValue})}}}运行发现点击并没有改变PAGView的播放资源 这是因为compose是使用观察者模式通过记录每个参数的变化来刷新UI的而我们自行定义的参数并不能让compose记住它的值的变化。
3.3如何在非composable作用域下使用被记忆的变量
好接下来假设我们有这么一个需求我们要监听手机电量的变化当电量低的时候播放一个电量比较低的PAG动画而监听电量的变化是通过系统的广播来实现的这就意味着我们需要把 compositionValue这个变量提取出来变成整个Activity的局部变量才能在收到电量广播的变化时修改它的值于是我们依葫芦画瓢写出如下代码
private var compositionValue remember {mutableStateOf(PAGFile.Load(assets, Constant.PagConstant.getSourceByName(002901)))}override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent(content {viewRoot()})}然而当我们刚把这个代码复制上去的时候android studio就提示编译错误了 再次提醒我们Composable调用只能发生在Composable函数的上下文中所以我们需要做一些简单的改动提示编译错误的是remember这个函数我们将它干掉即可
private var compositionValue mutableStateOf(PAGFile.Load(assets, Constant.PagConstant.getSourceByName(002901)))override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent(content {viewRoot()})}Composable
fun viewRoot() {BaseTheme {Surface(modifier Modifier.fillMaxSize(), color MaterialTheme.colorScheme.background) {ConstraintLayout {var testBoolean falseAndroidView(factory {PAGView(it).apply {this.composition compositionValue.valuethis.setRepeatCount(0)this.play()}}, modifier Modifier.width(200.dp).height(200.dp).clickable {testBoolean !testBooleanif(testBoolean){compositionValue.value PAGFile.Load(assets, Constant.PagConstant.getSourceByName(002201))}else{compositionValue.value PAGFile.Load(assets, Constant.PagConstant.getSourceByName(002901))}},update {it.composition compositionValue.value})}}}}这样编译是没有报错了但当我们运行时Logcat抛给了我们另外一个错误 很明显我们在初始化compositionValue的时候不能在参数那里直接调用PAGFile.Load(assets, Constant.PagConstant.getSourceByName(“002901”))此时界面还没绘制读取资源文件会报空指针然后我们尝试修改一下
private var compositionValue mutableStateOf(PAGFile)
Composableoverride fun viewRoot() {compositionValue.value PAGFile.Load(assets, Constant.PagConstant.getSourceByName(002901))BaseTheme {Surface(modifier Modifier.fillMaxSize(), color MaterialTheme.colorScheme.background) {ConstraintLayout {var testBoolean falseAndroidView(factory {PAGView(it).apply {this.composition compositionValue.valuethis.setRepeatCount(0)this.play()}}, modifier Modifier.width(200.dp).height(200.dp).clickable {testBoolean !testBooleanif(testBoolean){compositionValue.value PAGFile.Load(assets, Constant.PagConstant.getSourceByName(002201))}else{compositionValue.value PAGFile.Load(assets, Constant.PagConstant.getSourceByName(002901))}},update {it.composition compositionValue.value})}}}}此时android studio又报编译错误了 原来这个PAGFile的构造函数是私有的于是我们只能改成这样
private var pagFile:PAGFile?null //我们定义一个为空的PAGFile然后赋值给compositionValueprivate var compositionValue mutableStateOf(pagFile)Composableoverride fun viewRoot() {//然后尽可能早的给compositionValue赋值一个真实的数据compositionValue.value PAGFile.Load(assets, Constant.PagConstant.getSourceByName(002901))BaseTheme {Surface(modifier Modifier.fillMaxSize(), color MaterialTheme.colorScheme.background) {ConstraintLayout {var testBoolean falseAndroidView(factory {PAGView(it).apply {this.composition compositionValue.valuethis.setRepeatCount(0)this.play()}}, modifier Modifier.width(200.dp).height(200.dp).clickable {testBoolean !testBooleanif(testBoolean){compositionValue.value PAGFile.Load(assets, Constant.PagConstant.getSourceByName(002201))}else{compositionValue.value PAGFile.Load(assets, Constant.PagConstant.getSourceByName(002901))}},update {it.composition compositionValue.value})}}}}运行程序按我们的要求跑起来了。那么举一反三设置播放次数 以及何时播放都可以用外部参数来控制这里建议大家亲自尝试一下
3.4 及时释放资源
我们再回到刚才AndroidView的实现这里
Composable
UiComposable
fun T : View AndroidView(factory: (Context) - T,modifier: Modifier Modifier,update: (T) - Unit NoOpUpdate
) {AndroidView(factory factory,modifier modifier,update update,onRelease NoOpUpdate)
}它还有一个函数 onRelease当我们需要在生命周期结尾的时候释放资源就需要用到它了那么我们来编写一下代码
private var pagFile:PAGFile?nullprivate var compositionValue mutableStateOf(pagFile)Composableoverride fun viewRoot() {compositionValue.value PAGFile.Load(assets, Constant.PagConstant.getSourceByName(002901))BaseTheme {Surface(modifier Modifier.fillMaxSize(), color MaterialTheme.colorScheme.background) {ConstraintLayout {var testBoolean falseAndroidView(factory {PAGView(it).apply {Log.e(test,初始化资源)this.composition compositionValue.valuethis.setRepeatCount(0)this.play()}}, modifier Modifier.width(200.dp).height(200.dp).clickable {testBoolean !testBooleanif(testBoolean){compositionValue.value PAGFile.Load(assets, Constant.PagConstant.getSourceByName(002201))}else{compositionValue.value PAGFile.Load(assets, Constant.PagConstant.getSourceByName(002901))}},update {Log.e(test,加载资源)it.composition compositionValue.value},onRelease {Log.e(test,释放资源)it.freeCache()})}}}}本章内容至此结束各位看官一定要亲自编写代码才能更加熟练的使用compose ui祝各位都能更上一层楼