兔年了,一起用ArkTS来画兔子吧

似曾相识的标题,没错,今年年初就发布过一篇《兔年了,一起用Compose来画兔子吧》的文章,那是我刚开始学习Compose发布的第一篇文章,没放链接的原因是兔子太丑就别点进去看了,辣眼睛辣眼睛,那篇文章主要是针对Compose里面的Canvas做针对性练习,了解一下如何绘制各种形状的几何图形,而在前不久华为推出了HarmonyOS NEXT之后,对于我们Android开发者来讲,所要掌握的技术栈里面毫无疑问又多了一个开发语言,那就是ArkTS,所以跟Compose一样,我学习ArkTS的第一步也从学习它的Canvas开始,今天我也用ArkTS来画一只跟年初差不多画风的兔子,眼睛就再辣一次吧,学习基础知识最重要

准备工作

首先来创建我们的自定义组件,命名为Rabbit

@Entry@Component都是装饰器,@Component表示这个是自定义组件,@Entry表示该组件为入口组件,在build()方法中我们就可以写主要的UI代码,这一点与Flutter类似,至于如何选择我们界面上的父组件,由于最后这里只会有一个Canvas,所以父组件如何选择没有太大要求,我直接将官方api文档里面的样式代码复制过来了

父组件用的是弹性组件Flex,这里设置的是主轴方向是FlexDirection.ColumnalignItems表示里面子组件在交叉轴方向上的对齐方式,这里设置的是ItemAlign.Center居中对齐,justifyContent表示子组件在主轴方向上的对齐方式,这里设置的是FlexAlign.Center居中对齐,然后在组件后面是设置的组件本身的一些属性,这里设置的是宽高,都是占满全屏,这种属性的设置方式类似于Compose里面的Modifier,创建好父组件之后,我们接下来就是创建我们子组件Canvas

这里Canvas的创建方式不得不让我有点槽点不吐不快

  • 槽点一 :这里创建了一个RenderingContextSettings对象和一个CanvasRenderingContext2D,可以理解为就是Canvas的画布对象,但是对于Canvas来说最主要的东西为什么不在Canvas组件内部创建完成以后通过onReady函数回调出来呢,非得让开发者手动new出来再传到Canvas里面去,这个声明式ui也不是特别的"声明"
  • 槽点二 :在槽点一里面说了Canvas的创建过程需要new一个CanvasRenderingContext2D对象后传到Canvas的构造函数里面去,但是我发现如果不传参数的话,也不会编译报错,甚至可以run起来,那会我还没看Canvas源码,琢磨着难道Canvas里面还有一个默认的CanvasRenderingContext2D对象吗,结果一看模拟器界面,一片空白。。。好吧,我知道了,必须传~
  • 槽点三 :在组件内部使用组件的局部变量时候,一定要用this去指向这个变量,不然编译器会报错,说找不到它了,反正最后整只兔子画完我是this麻了

创建完Canvas之后,由于绘制的过程基本上是计算各种坐标的过程,所以需要知道整个画布的宽和高,我们在增加两个变量来表示画布的宽高

这里screenXscreenY分别表示画布的宽和高,我们看到在这俩变量前面也有一个装饰器@State修饰,被这个装饰器修饰的变量我们叫它状态变量,当它的值发生变化的时候会触发ui刷新,类似于Flutter的setState,Compose的remember,所以我们在onReader函数里面将画布的宽高设置到screenXscreenY里面去

开始绘制

这里的头部我们就用一个圆圈来表示,在ArkTS里面使用arc函数来绘制圆圈,这个函数既可以绘制圆圈,也可以画圆弧,提供如下几个参数

前两个参数代表圆心坐标,radius表示圆弧或者圆的半径大小,最后一个参数couterclockwise表示绘制圆弧的方向,默认是逆时针,最关键的是startAngleendAngle这俩个参数,我们注意看这俩参数后面的描述写的是弧线的起始弧度于终止弧度,单位是弧度而不是角度,所以你如果想画个半圆,传的是0和180的话,出来的依然还是个圆,正确的做法是利用计算弧度的公式来做

弧度=度数*π/180

知道关键点以后就要开始画这个圆了,先设置一下画笔的粗细与颜色

接着就调用arc函数来画这个圆,我们圆的中心点就定在了画布的中心点位置,也就是screenX/2screenY/2

绘制圆弧的代码就这一行结束了,但是我们运行一遍代码后发现模拟器上什么都没有,原因是我们少加了一行代码,在ArkTS中你需要显式的表明你画的图形是空心的还是实心的,分别用下面两个函数来表明

这看似是一句配置类型的代码,但你在后面会发现每调用一次绘制函数就要写一下stroke()或者fill(),不调用的话绘制的效果就不出来,如果你不信我们接下来就做个试验,来画两个圆,一个在调用好arc函数以后调用了stroke,另一个没有调用,代码是这个样子的

我们在原本兔头的圆心位置下面又增加了一个略小一点的圆,我们运行下代码看看实际效果如何

可以看见屏幕上确实只有一个圆,只有当我们把下面那个圆的stroke函数补充好,那个略小一点的圆才出现了


一个好消息,一个坏消息,好消息是俩圆都出来了,坏消息是,咋俩圆之间还有根黑线呢?咱也没画啥多余的线啊,查了下api文档才知道,我们又少加代码了,文档里面说,如果一个路径画完了要开始下一个路径了,需要调用beginPath函数,不然就是像我们这俩圆一样,两个路径还有一处相连的地方,它认为你第一笔还没有画完,最后当我把beginPath函数加到代码里面之后,最终效果才正常了

好了,槽点再多,这个也毕竟是语法,我们将小一点的圆去调继续来画兔子

鼻子

现在开始画兔子的鼻子,这里我打算使用三角形来表示鼻子,但是在现有的api中没有现成的函数来画三角,我们只能通过画path来实现,画path同我们以前在Android里面画path基本相同,先调用moveTo确定起点,然后调用lineTo来画路径,最终画完以后调用closePath结束当前路径形成一个封闭路径,代码如下所示


鼻子很简单,我们接下来画兔唇

兔唇

兔唇分左兔唇与右兔唇,其实就是两个圆弧,我们可以使用之前用过的arc函数来实现,但是我们这里使用另一个函数来画圆弧,那个就是arcTo,用法如下所示

这个函数是根据圆弧经过的点和半径大小来确定整个圆弧的样式,在调用这个函数之前还必须调用moveTo来确定圆弧的起点,所以画一个圆弧需要确定好三个点,至于如何找出这三个点,这个同绘制二阶贝塞尔曲线的思路是一样的,moveTo是第一个固定点,arcTo函数前两个参数表示曲线控制点的xy坐标,后面两个参数表示曲线终止固定点的xy坐标,这样子应该理解起来会方便一些,所以我们左右兔唇的绘制代码和效果图如下所示


眼珠与眼眶

然后我们在兔子脑袋偏上位置画眼珠,眼珠就是两个实心圆,圆的绘制方法已经知道了,这里是需要注意一下如果希望兔子眼珠的颜色同线条颜色不一样,就需要设置一下画笔实心的颜色,同时绘制结束需要调用fill函数来表明当前绘制的是实心图形,下面就是绘制左右眼珠的代码

运行一下看看效果

只有眼珠子的话看起来较显呆滞,所以需要在眼珠子上面画上眼眶,眼眶现在画起来就容易多了,那就是个半圆,起点与终点的y坐标与眼珠子一致,然后x坐标左右各加上点偏移量就好了,中间的控制点的x坐标与眼珠子的x坐标一致,y坐标向上偏移一点就好,代码如下

现在再看看效果

现在看起来有神了,我们接着下一步

耳朵

记得在之前用Compose画兔子的时候,耳朵是用两个椭圆来完成的,然后椭圆的底部反复调整才跟脑袋连在一起,方式有点太sa了,这次不打算这么做,首先兔子耳朵不可能直溜溜的竖在脑袋上的,肯定会倾斜一点,其次兔子的耳朵严格来讲并不能用椭圆来表示,毕竟贴近脑袋部分并不相连在一起,如果连在一起了,我们拽兔子耳朵的时候,就很容易拽断了。。。更形象的应该是兔子脑袋上找出两个点,然后各自向上画曲线,最终在一个点连在一起形成一只耳朵,所以一只耳朵其实是由两根三阶贝塞尔曲线构成,那么第一步需要先找出两根曲线的起点,这个起点在脑袋上,那么就需要根据半径与角度计算出一个圆周上某一个点的坐标,下面是左耳的两个起点坐标的计算代码

这里就是利用数学公式sin与cos来计算xy坐标,从传值上就能看出,sin与cos函数接收的也是以弧度为单位的数值,至于这里的取值,sin与cos函数里面的角度是按照顺时针来计算的,起点在圆的最左侧,而我们之前在使用arc函数画圆弧的时候,里面传的角度默认按照逆时针方向旋转的,起点在圆的最右侧,这里是存在差异的,要注意一下,所以在代码中,leftxlefty是比较靠近脑袋顶部的点,而leftOutXleftOutY是比较靠外侧的点,然后我们以这两个点为参照点,就能将贝塞尔曲线其余几个点找出来,最终左耳朵的绘制代码如下所示

运行一下代码,左耳朵就有了

我们再以同样的方式将右耳的坐标算出来,代码与效果图如下所示


这只兔子可算开始萌起来了~

身体

兔子的身体在之前Compose的文章里面也是用椭圆来表示的,但是同样感觉好像一拽兔子就能把兔子拽断一样,所以这里身体的绘制同耳朵一样,也是用一个三阶贝塞尔曲线从兔子脑袋下边的一头连到另一头,剩下两个点就选择画布靠下一点的位置,代码如下

效果图就是上面这个样子了,算是达到预期要的效果了吧,然后给兔子加上两只爪子

兔爪

兔爪我们用两个椭圆型表示,在ArkTS里面绘制椭圆的api我们使用ellipse函数来实现,它需要的参数如下所示

前两个参数同绘制圆弧一样,是中心点坐标,第三个参数radiusX是x轴的半径,radiusY是y轴半径,rotation是旋转角度,startAngleendAngle是起始点与终止点坐标,最后一个参数同样也是绘制的方向,默认是fasle,逆时针,都是比较容易填的参数,其中中心点的x坐标我们参考之前画身体时候第二,三个点的x坐标,再往里偏移一些,而椭圆的y坐标我们就选择screenY * 3/4的高度,这样绘制两只爪子的代码就有了

我们看到这里还将爪子设置成实心的,实心填充颜色设置成纯黑,由于之前兔子眼珠是红色的,所以这里要注意下眼珠与爪子的绘制顺序,如果眼珠在爪子的下面绘制,那么眼珠也变成黑色的了,除非在绘制眼珠之前重新设置下fillStyle,再更改下填充色值,现在我们运行下代码,两个爪子就出来了

尾巴

最后一个就是画尾巴,尾巴打算用一个圆来表示,圆的话我们上面已经讲过如何绘制了,主要还是寻找中心点坐标,这边中心点的x坐标就用之前画身体用到的第三个控制点的x坐标,也就是变量endX,y坐标的高度就确定为四分之三的画布高度,代码如下所示

这里圆的半径经过反复调试最终确定为20,我们现在跑下代码,尾巴就有了

现在我们在优化下,给这个圆加点分割线,让它看起来毛茸茸一点,分割线我们使用setLineDash函数来完成

这个函数接收一个数组,数组里面第一个值表示实线长度,第二个值表示间隙长度,我们随意设置两个值来看看效果


现在看起来是不是更像一只毛茸茸的尾巴

总结

到这里我们已经完成了使用ArkTS来绘制一只兔子的整个过程,其实兔子本身颜值如何到不是很重要,这篇文章的关键主要还是熟悉一下ArkTS里面的Canvas组件,以及这个组件里面提供的api,虽说仅仅一个组件就有不少槽点,但每个语言都有每个语言的特性,作为一个技术人,对新出来的技术,尤其这个还是国内自己的技术,应该更多的是去挖掘这个技术的优点,以及它与其他语言都有什么异同,这个才是应该做的事情。

相关推荐
也无晴也无风雨1 小时前
深入剖析输入URL按下回车,浏览器做了什么
前端·后端·计算机网络
Martin -Tang2 小时前
Vue 3 中,ref 和 reactive的区别
前端·javascript·vue.js
FakeOccupational3 小时前
nodejs 020: React语法规则 props和state
前端·javascript·react.js
放逐者-保持本心,方可放逐3 小时前
react 组件应用
开发语言·前端·javascript·react.js·前端框架
曹天骄4 小时前
next中服务端组件共享接口数据
前端·javascript·react.js
阮少年、5 小时前
java后台生成模拟聊天截图并返回给前端
java·开发语言·前端
郝晨妤6 小时前
鸿蒙ArkTS和TS有什么区别?
前端·javascript·typescript·鸿蒙
AvatarGiser6 小时前
《ElementPlus 与 ElementUI 差异集合》Icon 图标 More 差异说明
前端·vue.js·elementui
喝旺仔la6 小时前
vue的样式知识点
前端·javascript·vue.js
别忘了微笑_cuicui6 小时前
elementUI中2个日期组件实现开始时间、结束时间(禁用日期面板、控制开始时间不能超过结束时间的时分秒)实现方案
前端·javascript·elementui