Jetpack 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 = params
                            this.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 = params
                            this.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 = false
                    AndroidView(factory = {
                        PAGView(it).apply {
                            this.composition = compositionValue.value
                            this.setRepeatCount(0)
                            this.play()
                        }
                    }, modifier = Modifier
                        .width(200.dp)
                        .height(200.dp)
                        .clickable {
                            testBoolean = !testBoolean
                            if(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 = false
                    AndroidView(factory = {
                        PAGView(it).apply {
                            this.composition = compositionValue
                            this.setRepeatCount(0)
                            this.play()
                        }
                    }, modifier = Modifier
                        .width(200.dp)
                        .height(200.dp)
                        .clickable {
                            testBoolean = !testBoolean
                            if(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 = false
                    AndroidView(factory = {
                        PAGView(it).apply {
                            this.composition = compositionValue.value
                            this.setRepeatCount(0)
                            this.play()
                        }
                    }, modifier = Modifier
                        .width(200.dp)
                        .height(200.dp)
                        .clickable {
                            testBoolean = !testBoolean
                            if(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)
@Composable
    override fun viewRoot() {
        compositionValue.value = PAGFile.Load(assets, Constant.PagConstant.getSourceByName("002901"))
        BaseTheme {
            Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
                ConstraintLayout {
                    var testBoolean = false
                    AndroidView(factory = {
                        PAGView(it).apply {
                            this.composition = compositionValue.value
                            this.setRepeatCount(0)
                            this.play()
                        }
                    }, modifier = Modifier
                        .width(200.dp)
                        .height(200.dp)
                        .clickable {
                            testBoolean = !testBoolean
                            if(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,然后赋值给compositionValue
    private var compositionValue = mutableStateOf(pagFile)


    @Composable
    override fun viewRoot() {
    //然后尽可能早的给compositionValue赋值一个真实的数据
        compositionValue.value = PAGFile.Load(assets, Constant.PagConstant.getSourceByName("002901"))
        BaseTheme {
            Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
                ConstraintLayout {
                    var testBoolean = false
                    AndroidView(factory = {
                        PAGView(it).apply {
                            this.composition = compositionValue.value
                            this.setRepeatCount(0)
                            this.play()
                        }
                    }, modifier = Modifier
                        .width(200.dp)
                        .height(200.dp)
                        .clickable {
                            testBoolean = !testBoolean
                            if(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?=null
    private var compositionValue = mutableStateOf(pagFile)


    @Composable
    override fun viewRoot() {
        compositionValue.value = PAGFile.Load(assets, Constant.PagConstant.getSourceByName("002901"))
        BaseTheme {
            Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
                ConstraintLayout {
                    var testBoolean = false
                    AndroidView(factory = {
                        PAGView(it).apply {
                            Log.e("test","初始化资源")
                            this.composition = compositionValue.value
                            this.setRepeatCount(0)
                            this.play()
                        }
                    }, modifier = Modifier
                        .width(200.dp)
                        .height(200.dp)
                        .clickable {
                            testBoolean = !testBoolean
                            if(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,祝各位都能更上一层楼

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/776287.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【设计模式】工厂模式(定义 | 特点 | Demo入门讲解)

文章目录 定义简单工厂模式案例 | 代码Phone顶层接口设计Meizu品牌类Xiaomi品牌类PhoneFactory工厂类Customer 消费者类 工厂方法模式案例 | 代码PhoneFactory工厂类 Java高级特性---工厂模式与反射的高阶玩法方案&#xff1a;反射工厂模式 总结 其实工厂模式就是用一个代理类帮…

Java项目:基于SSM框架实现的学生公寓管理中心系统【ssm+B/S架构+源码+数据库+毕业论文】

一、项目简介 本项目是一套基于SSM框架实现的学生公寓管理中心系统 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严格调试&#xff0c;eclipse或者idea 确保可以运行&#xff01; 该系统功能完善、界面美观、操作简单、…

【CentOS 7.6】Linux版本 portainer本地镜像导入docker安装配置教程,不需要魔法拉取!(找不着镜像的来看我)

吐槽 我本来根本不想写这篇博客&#xff0c;但我很不解也有点生气&#xff0c;CSDN这么大没有人把现在需要魔法才能拉取的镜像放上来。 你们都不放&#xff0c;根本不方便。我来上传资源。 portainer-ce-latest.tar Linux/amd64 镜像下载地址&#xff1a; 链接&#xff1a;h…

加法器的基本操作

基本单元 与门(AND) 全1为1&#xff0c;有0为0 或门(OR) 全0为0&#xff0c;有1为1 非门(NOT) 为1则0&#xff0c;为0则1 异或门(XOR) 两个输入端&#xff0c;相同为0&#xff0c;不同为1 与非门(NADD) 全1为0&#xff0c;有0为1 或非门(NOR) 全0为1&#xff0c;有1为0。刚…

H5 Canvas实现转盘效果,控制指定数字

效果图 实现思路&#xff1a; 用Canvas画圆&#xff0c;然后再画扇形&#xff0c;然后中奖的开始用一张图片代替&#xff0c;点击的时候触发转动效果。 实现代码&#xff1a; <!DOCTYPE html> <html> <head><meta charset"utf-8"><tit…

UNDO 表空间使用率高 active段占用高 无对应事务执行

UNDO表空间使用率告警&#xff0c;查看占用情况 active段占比很高 select tablespace_name,status,sum(bytes/1024/1024) mb from dba_undo_extents group by tablespace_name,status;不同状态的含义&#xff1a;**ACTIVE **&#xff1a;有活动事务在使用 Undo&#xff0c;这…

【JavaSE】数据类型与变量

目录 1. 字面常量 2. 数据类型 3. 变量 3.1 变量概念 3.2 语法格式 3.3 整型变量 3.3.1 整型变量 3.3.2 长整型变量 3.3.3 短整型变量 3.3.4 字节型变量 3.4 浮点型变量 3.4.1 双精度浮点型 3.4.2 单精度浮点型 3.5 字符型变量 3.6 布尔型变量 3.7 类型转换 3…

WEB安全-靶场

1 需求 2 语法 3 示例 男黑客|在线渗透测试靶场|网络安全培训基地|男黑客安全网 4 参考资料

windows 7 安装IPP协议,支持Internet打印

1 windows 7 安装IPP协议,支持Internet打印 #控制面板--打开或关闭Windows功能 3 复制Printers 文件夹 到 c:\inetpub\wwwroot\,复制msw3prt.dll到c:\windows\system32\ 4 打开IIs管理器 #报错:模块列表中不存在此处理程序所需的指定模块。如果您添加脚本映射处理程序映射&…

【力扣】数组中的第K个最大元素

一、题目描述 给定整数数组 nums 和整数 k&#xff0c;请返回数组中第 k 个最大的元素。 请注意&#xff0c;你需要找的是数组排序后的第 k 个最大的元素&#xff0c;而不是第 k 个不同的元素。 你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。 示例 1: 输入: [3,2,1,5,…

记一次EasyExcel的错误使用导致的频繁FullGC

记一次EasyExcel的错误使用导致的频繁FullGC 一、背景描述二、场景复现三、原因分析四、解决方案五、思考复盘 一、背景描述 繁忙的校招结束了&#xff0c;美好的大学四年也结束了&#xff0c;作者也有10个月没有更新了。拿到心仪的offer之后也开始了苦B的打工生活。 最近接到…

汽车信息安全--欧盟汽车法规

目录 General regulation 信息安全法规 R155《网络安全及网络安全管理系统》解析 R156《软件升级与软件升级管理系统》解析 General regulation 欧洲的汽车行业受到一系列法律法规的约束&#xff0c;包括 各个方面包括&#xff1a; 1.安全要求&#xff1a;《通用安全条例&a…

10.x86游戏实战-汇编指令lea

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 本次游戏没法给 内容参考于&#xff1a;微尘网络安全 工具下载&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1rEEJnt85npn7N38Ai0_F2Q?pwd6tw3 提…

SpringBoot之内容协商

现象演示 假设有一个需求是根据终端的不同&#xff0c;返回不同形式的数据&#xff0c;比如 PC 端需要以 HTML 格式返回数据&#xff0c;APP、小程序端需要以 JSON 格式返回数据。这时我们是 coding 几个相似的接口&#xff1f;还是在一个接口里面做复杂判断处理&#xff1f;两…

测试驱动开发(TDD)方法详解

目录 前言1. 什么是测试驱动开发1.1 TDD的基本原则1.2 TDD的优势 2. 测试驱动开发的流程2.1 编写测试2.2 运行测试2.3 编写实现代码2.4 重构代码 3. 常用工具和框架3.1 单元测试框架3.2 Mock框架3.3 集成工具 4. TDD在实际项目中的应用4.1 应用场景4.2 面临的挑战4.3 最佳实践 …

计算机的错误计算(二十二)

摘要 计算机的错误计算&#xff08;十九&#xff09;展示了计算机的一个错误计算&#xff1a;本应该为 0的算式的结果不为0. 那么&#xff0c;增加计算精度&#xff0c;能确定是0吗&#xff1f;不一定。 计算机的错误计算&#xff08;十九&#xff09;展示了计算机对 的错误计…

liunx清理服务器内存和日志

1、查看服务器磁盘占用情况 # 查看磁盘占用大小 df -h 2、删除data文件夹下面的日志 3、查看每个服务下面的日志输出文件&#xff0c;过大就先停掉服务再删除out文件再重启服务 4、先进入想删除输入日志的服务文件夹下&#xff0c;查看服务进程&#xff0c;杀掉进程&#xff…

代码随想录算法训练营第二天|【数组】209.长度最小的子数组

题目 给定一个含有 n 个正整数的数组和一个正整数 s &#xff0c;找出该数组中满足其和 ≥ s 的长度最小的 连续 子数组&#xff0c;并返回其长度。如果不存在符合条件的子数组&#xff0c;返回 0。 示例&#xff1a; 输入&#xff1a;s 7, nums [2,3,1,2,4,3] 输出&#…

FPGA开发笔试1

1. 流程简介 我是两天前有FPGA公司的HR来问我实习的事情&#xff0c;她当时问我距离能不能接受&#xff0c;不过确实距离有点远&#xff08;地铁通勤要将近一个半小时&#xff09;&#xff0c;然后她说给我约个时间&#xff0c;抽空做1个小时的题目&#xff08;线上做&#xf…

使用flask的web网页部署介绍

使用flask的web网页部署介绍 文章目录 前言一、网页介绍二、数据库设计介绍总结 前言 flaskbootstrapjquerymysql搭建三叶青在线识别网站&#xff0c;使用nginxgunicorn将网站部署在腾讯云上&#xff0c;配置SSL证书。网站地址&#xff1a;https://www.whtuu.cn 三叶青图像识…