降Compose十八掌之『利涉大川』| Canvas

news2024/11/13 23:32:13

公众号「稀有猿诉」        原文链接 降Compose十八掌之『利涉大川』| Canvas

任何一个GUI框架都会提供大量的预定义的UI部件,让开发者构建UI页面,但有些时候预定义的部件无法满足需求,这时就需要定制,甚至是自定义绘制的内容。对于Android开发者来说,这已经是家常便饭了,因为肯定有过用自定义View来实现一些特殊设计需求的经验。在Jetpack Compose中也有同样的方法来实现自定义绘制内容,今天就来学习一下。

banner

使用Canvas来自定义内容

在Compose中, 我们用Canvas函数来绘制自定义内容,可以把它理解成为自定义View,但,它是一个函数,把绘制指令传给它就可以了:

    val textMeasurer = rememberTextMeasurer()

    Canvas(modifier = Modifier.fillMaxSize()) {
        drawRect(Color.LightGray)

        drawText(
            textMeasurer = textMeasurer,
            text = "降Compose十八掌",
            topLeft = Offset(size.width / 4f, size.height / 2.2f)
        )

        drawCircle(
            color = Color.Magenta,
            radius = size.width / 10f,
            center = Offset(size.width / 1.8f, size.height / 3f)
        )
        drawCircle(
            color = Color.Yellow,
            radius = size.width / 12f,
            center = Offset(size.width / 1.6f, size.height / 4.5f)
        )
        drawCircle(
            color = Color.Green,
            radius = size.width / 14f,
            center = Offset(size.width / 1.46f, size.height / 7f)
        )
    }

hello_canvas.png

坐标系统

坐标系统,与常见的GUI坐标系统,以及View的坐标系统都是一样的,左上角是原点(0,0),x轴向右,y轴向下。

绘图上下文DrawScope

仔细看Canvas函数,可以发现,写绘制指令的地方是一个尾部lambda,这是Compose中非常常见的一种设计方式。这个lambda被定义为DrawScope对象的一个扩展函数,所以在这个lambda中可以隐式的访问DrawScope对象。我们所使用的绘制指令,以及很多参数其实都是在通过this指针隐式的调用DrawScope。对于扩展函数不熟悉的同学可以去复习一下Kotlin中函数的一些高级用法。

通过AndroidStudio的提示,也能看到隐式的this指针是一个DrawScope对象。

所以呢,当查找API文档时记得要去找DrawScope,而不是Canvas函数。其实Canvas就是一个封装的函数,也没啥东西。但还有一个略微底层一些的作为Graphics接口的对象Canvas,它与Android SDK中的Canvas对象是差不多的概念。

接下来我们重点看看如何使用绘制指令绘制出我们需要的内容。

画图形

图形(Shape)是最为常见的一类绘制目标,比如圆,椭圆,矩形,线,扇形等等。不难,看一眼就会用:

    Canvas(modifier = Modifier.fillMaxSize()) {
        drawRect(Color.LightGray)

        drawOval(
            color = Color.Green,
            topLeft = Offset(50f, 50f),
            size=  Size(size.width / 10f, size.height / 12f)
        )

        drawLine(
            color = Color.Yellow,
            start = Offset(50 + size.width / 20f, 50f + size.height / 24f),
            end = Offset(size.width / 1.8f, size.height / 3f),
            strokeWidth = Stroke.DefaultMiter
        )

        drawCircle(
            color = Color.Magenta,
            radius = size.width / 5f,
            center = Offset(size.width / 1.8f, size.height / 3f)
        )

        drawPoints(
            color = Color.DarkGray,
            pointMode = PointMode.Points,
            strokeWidth = 50f,
            points = genPoints(size.width / 2f, size.height / 3f)
        )
    }

canvas_shapes.png

画路径

路径(Path)是把一系列的数学指令转化为绘制命令,可以更为灵活的画一些曲线和图形,比如说画一个三角函数曲线:

@Composable
fun PathDemo() {
    val tm = rememberTextMeasurer()

    Canvas(modifier = Modifier.fillMaxSize()) {
        drawRect(Color.LightGray)

        drawText(tm, "cosine of [-PI, PI]", Offset(size.width / 3f, 60f))

        drawLine(
            color = Color.DarkGray,
            start = Offset(0f, size.height / 2f),
            end = Offset(size.width, size.height / 2f),
            strokeWidth = 3f
        )

        drawLine(
            color = Color.DarkGray,
            start = Offset(size.width / 2f, size.height / 3f),
            end = Offset(size.width / 2f, size.height * 2/ 3f),
            strokeWidth = 3f
        )

        drawPath(genPath(size.width, size.height), Color.Magenta, style = Stroke(width = 10f))
    }
}

fun genPath(width: Float, height: Float): Path {
    val slices = 60
    val path = Path();
    path.moveTo(0f, height / 3f)
    for (i in 1..slices) {
        val x0 = 2f * i.toFloat() * PI.toFloat() /  slices.toFloat() - PI.toFloat()
        val y0 = cos(x0) * height / 6f
        val x = i.toFloat() / slices.toFloat() * width
        val y = y0 + height / 2f
        path.lineTo(x, y)
        path.moveTo(x, y)
    }
    path.close()
    return path;
}

canvas_path.png

路径在绘制中是非常强大的功能,可以实现非常炫酷的动画效果。

画文字

文字是特别重要的UI元素,通常情况下我们都是过Text来展示文字,再与其他部件进行组合就能满足需求。一般来说不需要在自定义内容也使用文字,因为文字绘制一般来说比较复杂,因为像基线对齐,字体样式,字体大小等等,都需要考虑。文字部件Text内容其实也是用与自定义一样的更低层的API来实现的,但它把像对齐,样式,富文本等等都封装好了。

DrawScope也提供了绘制文字的函数,不过呢使用起来比较麻烦,需要详细计算文字所占用的区域大小,而文字的measure通常是非常麻烦的,因为像文字的字体以及文字大小都会影响到measure,因此measure要保存成为一个状态,这样当有影响到文字绘制的因素发生变化时,measure就会发生变化,进而触发Re-Composition:

@Composable
fun TextDemo() {
    val textMeasure = rememberTextMeasurer()

    Canvas(modifier = Modifier.fillMaxSize()) {
        val measuredText =
            textMeasure.measure(
                AnnotatedString(
                    text =
                    """
                        “降龙十八掌可说是【武学中的巅峰绝诣】,当真是无坚不摧、无固不破。虽招数有限,但每一招均具绝大威力。
                        北宋年间,丐帮帮主萧峰以此邀斗天下英雄,极少有人能挡得他三招两式,气盖当世,群豪束手。
                        当时共有“降龙廿八掌”,后经萧峰及他义弟虚竹子删繁就简,取精用宏,改为降龙十八掌,掌力更厚。
                        这掌法传到洪七公手上,在华山绝顶与王重阳、黄药师等人论剑时施展出来,王重阳等尽皆称道。”
                    """.trimIndent(),
                    spanStyle = SpanStyle(
                        fontSize = 20.sp,
                        fontWeight = FontWeight.ExtraBold,
                        brush = Brush.verticalGradient(listOf(Color.Magenta, Color.Cyan, Color.Blue))
                    )
                ),
                constraints = Constraints.fixed(
                    width = (size.width / 1.6f).toInt(),
                    height = (size.height / 2f).toInt()
                ),
                overflow = TextOverflow.Ellipsis,
                style = TextStyle(fontSize = 18.sp)
            )

        drawText(
            textLayoutResult = measuredText,
            topLeft = Offset(60f, 60f)
        )
    }
}

canvas_text.png

画图片

图片(Image)是与文字类似的非常重要的UI元素,像图标,头像,表情,背景图,Banner图,以及内容中的图像都属于图片元素,一般情况下用Image函数可以用来展示图片。

对于自定义绘制内容也可以使用图片,DrawScope中有提供绘制图片的方法:

val dogImage = ImageBitmap.imageResource(id = R.drawable.dog)
Canvas(modifier = Modifier.fillMaxSize(), onDraw = {
    drawImage(dogImage)
})

变幻

除了绘制以外,DrawScope还提供了一系列做变幻的函数。包括缩放,位移,旋转这些变幻直接作用于绘制指令上面。

缩放

使用DrawScope.scale函数来对绘制指令进行缩放,参数是x轴方向和y轴方向的缩放倍数(大于1放大,小于1缩小),还可以指定中心坐标,默认是几何中心。

Canvas(modifier = Modifier.fillMaxSize()) {
    scale(scaleX = 10f, scaleY = 15f) {
        drawCircle(Color.Blue, radius = 20.dp.toPx())
    }
}

位移

DrawScope.translate可以实现位移,参数是x方向或者y方向的距离。参数为正,是沿着坐标轴正向,为负就是反向。

Canvas(modifier = Modifier.fillMaxSize()) {
    translate(left = 100f, top = -300f) {
        drawCircle(Color.Blue, radius = 200.dp.toPx())
    }
}

旋转

用DrawScope.rotate函数实现旋转,参数为正时是顺时针的角度,为负就是逆时针,可以指定中心点,默认是几何中心。

Canvas(modifier = Modifier.fillMaxSize()) {
    rotate(degrees = 45F) {
        drawRect(
            color = Color.Gray,
            topLeft = Offset(x = size.width / 3F, y = size.height / 3F),
            size = size / 3F
        )
    }
}

画布尺寸调整

用DrawScope.inset函数来对DrawScope的画布进行调整,参数是周围四个方向的边距偏移量。

Canvas(modifier = Modifier.fillMaxSize()) {
    val canvasQuadrantSize = size / 2F
    inset(horizontal = 50f, vertical = 30f) {
        drawRect(color = Color.Green, size = canvasQuadrantSize)
    }
}

这样调整后,inset内部的lambda中的绘制指令的尺寸size会受影响,size.width = width - 2 * horizontal,size.height = height - 2 * vertical,相当于是加了padding。

组合变幻

变幻除了可以单独使用,还可以组合起来使用,能更简便的实现变幻效果。使用DrawScope.withTransform来组合变幻:

Canvas(modifier = Modifier.fillMaxSize()) {
    withTransform({
        translate(left = size.width / 5F)
        rotate(degrees = 45F)
    }) {
        drawRect(
            color = Color.Gray,
            topLeft = Offset(x = size.width / 3F, y = size.height / 3F),
            size = size / 3F
        )
    }
}

总结

今天主要学习了如何通过Canvas函数来实现自定义绘制内容,Canvas给我们了封装了一个包含有DrawScope的lambda,通过DrawScope提供的各种绘制指令可以实现我们的想要的自定义内容。可以自由的通过绘制图形,文字和图像,并且可以做变幻,以实现一些特效。相信通过今天的学习,足可以应付常见的自定义绘制需求。

参考资料

  • Graphics in Compose

subscription

欢迎搜索并关注 公众号「稀有猿诉」 获取更多的优质文章!

保护原创,请勿转载!

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

快手ip地址为什么会乱跳城市

在数字化时代,网络IP地址已成为我们数字身份的一部分,它既是网络空间中的“门牌号”,也是我们在网络世界中的“身份证”。然而,近期有不少快手用户反映,他们的IP地址在使用过程中出现了乱跳城市的现象,引发…

用好六西格玛培训中的DOE工具,让产品和过程优化不再难——张驰咨询

在六西格玛培训中,试验设计(Design of Experiments,简称DOE)是一种至关重要的工具,它不仅有助于系统地规划和分析试验,还能显著提升产品和过程的改进效率。DOE通过最小化试验次数,同时最大化信息…

大数据基础:Hadoop之Yarn重点架构原理

文章目录 Hadoop之Yarn重点架构原理 一、Yarn介绍 二、Yarn架构 三、Yarn任务运行流程 四、Yarn三种资源调度器特点及使用场景 Hadoop之Yarn重点架构原理 一、Yarn介绍 Apache Hadoop Yarn(Yet Another Reasource Negotiator,另一种资源协调者)是Hadoop2.x版…

Windows安装PostgreSQL

PostgreSQL是一种功能齐全的对象-关系型数据库管理系统(ORDBMS),它以加州大学伯克利分校计算机系开发的POSTGRES项目为基础,经过多年的发展,已成为一个高度可扩展、支持多种数据类型、具备复杂查询能力的数据库系统。在…

MoonBit 全新语法:级联运算符

✨MoonBit 引入了 “..” 操作符,能够优雅地对可变 API 进行链式调用,同时保持可变 API 签名的整洁(依然返回 Unit)

昇思25天学习打卡营第19天|sea_fish

打卡第19天。本次学习的内容为生成式中的Diffusion扩散模型。记录学习的过程。 模型简介 什么是Diffusion Model? 如果将Diffusion与其他生成模型(如Normalizing Flows、GAN或VAE)进行比较,它并没有那么复杂,它们都…

PHP转Go系列 | ThinkPHP与Gin框架之OpenApi授权设计实践

大家好,我是码农先森。 我之前待过一个做 ToB 业务的公司,主要是研发以会员为中心的 SaaS 平台,其中涉及的子系统有会员系统、积分系统、营销系统等。在这个 SaaS 平台中有一个重要的角色「租户」,这个租户可以拥有一个或多个子系…

Text Control 控件教程:文本和表格相互转换

文档布局中的一项典型任务是将使用制表位创建的纯文本表格转换为完全格式化的表格。在本文中,我们将向您展示如何通过检测制表位并将其转换为包含位置的表格单元格,将纯文本表格转换为表格。 TX Text Control 是一款功能类似于 MS Word 的文字处理控件&…

统计HTML 标签CSS 属性 和 JS 关键字

<!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><title>统计HTML 标签、CSS 属性 和 JS 关键字</title> </head> <style>#container {display: flex;}li {list-style: none;} </styl…

【青书学堂】2024年第一学期 平面设计(高起专) 作业

【青书学堂】2024年第一学期 平面设计(高起专) 作业 为了方便日后复习&#xff0c;青书学堂成人大专试题整理。 若有未整理的课程&#xff0c;请私信我补充&#xff0c;欢迎爱学习的同学们收藏点赞关注&#xff01;文章内容仅限学习使用&#xff01;&#xff01;&#xff01; 第…

MacCleaner Pro Mac系统综合清理工具包 释放磁盘空间,提高整体性能

MacCleaner Pro 是一款专为 Mac 用户设计的软件工具&#xff0c;用于优化和清理他们的系统。它提供了一系列功能来帮助用户加速他们的 Mac&#xff0c;释放磁盘空间&#xff0c;并提高整体性能。 MacCleaner Pro 的一些主要功能包括&#xff1a; 系统清理&#xff1a;此功能有…

捷配笔记-如何确保PCB信号完整性?

三十年的电子设计历程&#xff0c;是一段从微米到纳米的跨越之旅。1987年&#xff0c;0.5微米工艺曾被视为技术的极限&#xff0c;而如今22纳米工艺已成为行业的新标准。本文将回顾这段技术革新的历程&#xff0c;并探讨在这一过程中我们所面临的挑战与应对策略。 技术演进的里…

python自动化flask库-从数据库里取出数据

实现效果&#xff1a;写一个接口&#xff0c;从mysql数据库读到user表的数据&#xff08;用户名和密码&#xff09;&#xff0c;把数据作为回参 用到的库&#xff1a;flask&#xff0c;pymysql 代码&#xff1a; from flask import Flask, jsonify import pymysql# 连接数据…

python-箭形图案(赛氪OJ)

[题目描述] 小理学习了循环&#xff0c;老师给他出了一系列打印图案的练习&#xff0c;该任务是打印用“ ∗ ”组成的箭形图案。输入格式&#xff1a; 一行一个整数 n。输出格式&#xff1a; 针对输入的 n &#xff0c;输出用“ ∗ ”组成的箭形。 …

AirSim+PX4联合仿真

AirSim启动设置 windows上的AirSim要想通过PX4进行控制,需要配置一下参数,进入如下路径的AirSim文件,找到settings.json文件,采用记事本打开,并编辑里面的内容。 可以参考如下内容:其中ip要对应,linux上PX4导入的ip即为此处的localhostip,也是WSL服务的ip。 {"S…

Java中的Stack(栈)(如果想知道Java中有关Stack的知识点,那么只看这一篇就足够了!)

前言&#xff1a;栈&#xff08;Stack&#xff09;是一种基础且重要的数据结构&#xff0c;以其后进先出&#xff08;LIFO, Last In First Out&#xff09;的特性广泛应用于计算机科学和编程中。 ✨✨✨这里是秋刀鱼不做梦的BLOG ✨✨✨想要了解更多内容可以访问我的主页秋刀鱼…

XLPR车牌自动识别开发包

XLPR SDK适用于为各种应用增加车牌自动识别能力&#xff0c;支持多个区域检测&#xff0c;支持车牌颜色和号码输出&#xff0c;提供Web API和 原生API。官方下载地址&#xff1a;XLPR车牌识别开发包。 XLPR主要由三个主要部分构成&#xff1a;D-Net、R-NET和C-Net&#xff0c;…

初识Spring6框架

个人笔记梳理&#xff0c;仅供参考 Spring是一款主流的JavaEE轻量级开源框架 Spring的狭义和广义 广义的Spring&#xff1a;Spring技术栈 泛指以Spring Framework为核心的Spring技术栈 经过十多年的发展&#xff0c;Spring已经不再是一个单纯的应用框架&#xff0c;而是逐…

mybatis语法进阶1

日志的使用 我们在使用MyBatis的时候, 其实MyBatis框架会打印一些必要的日志信息, 在开发阶段这些日志信息对我们分析问题,理解代码的执行是特别有帮助的; 包括项目上线之后,我们也可以收集项目的错误日志到文件里面去; 所以我们采用专门的日志系统来处理. 步骤 导入坐标拷贝…

电脑技巧:分享10个有趣的的网站

目录 1、少女生成器 2、动漫捏脸 3、小霸王其乐无穷 4、Theuselessweb 5、Mikutap 6、nazo.one-story 7、在线连连看 8、中午吃什么 9、假装更新系统 10、越摇摆越快乐 分享10个好玩有趣的小众宝藏网站&#xff0c;90%的人都没有玩过&#xff0c;随便哪个都能玩上一天…