写点什么

HarmonyOS 玩转 ArkUI 动效 - 水母动画

作者:Halifax
  • 2022-12-11
    江苏
  • 本文字数:4649 字

    阅读完需:约 15 分钟

HarmonyOS玩转ArkUI动效 - 水母动画

前言

博客内容首发于:稀土掘金

本文会详细讲解我参加: HarmonyOS【挑战赛第三期】玩转ArkUI动效的项目

我的参赛项目源码:【挑战赛第三期】JellyfishAnimation

我们的动画效果参考自:cassie-codes的水母SVG





华为鸿蒙已经放弃 Java 作为鸿蒙的开发语言,开发了一个申明式 UI 框架 ArkUI,开发语言变成了 ArkTS。


ArkUI 是一套构建分布式应用界面的声明式 UI 开发框架。

ArkTS 基于 TypeScript(简称 TS)语言扩展而来,是 TS 的超集。

ArkTS 继承了 TS 的所有特性。


我们用一个简单示例,来说明 ArkTS 的基本组成:





关于ArkUI更多内容,感兴趣的同学,可以点击这里快速入门,下面我们进入正题。

源码目录结构

拆解 SVG

我们开头提到cassie-codes的水母SVG,如果拿到这个 SVG 的话,要怎么用程序去渲染它呢?

1.组成

我们精简一下看看它的组成内容:


<!-- 为了直观的查看组成结构,我们删除了路径数据 --> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 530.46 563.1">  <defs>  <filter id="turbulence" filterUnits="objectBoundingBox" x="0" y="0" width="100%" height="100%">    <feTurbulence data-filterId="3" baseFrequency="0.02 0.03" result="turbulence" id="feturbulence" type="fractalNoise" numOctaves="1" seed="1"></feTurbulence>    <feDisplacementMap id="displacement" xChannelSelector="R" yChannelSelector="G" in="SourceGraphic" in2="turbulence" scale="13" />  </filter>      </defs>  <g class="jellyfish" filter="url(#turbulence)">    <path class="tentacle"/>    <path class="tentacle"/>    <path class="tentacle" />    <path class="tentacle" />    <path class="tentacle"/>    <path class="tentacle"/>    <path class="tentacle"/>    <path class="tentacle"/>    <path class="tentacle"/>    <path class="face" />    <path class="outerJelly"/>    <path id="freckle" />    <path id="freckle"/>    <path id="freckle-4"/>  </g>  <g id="bubbles" fill="#fff">    <path class="bubble"/>    <path class="bubble"/>    <path class="bubble" />    <path class="bubble"/>    <path class="bubble"/>    <path class="bubble"/>    <path class="bubble" />  </g>  <g class="jellyfish face">    <path class="eye lefteye"  fill="#b4bebf" d=""/>    <path class="eye righteye" fill="#b4bebf" d=""/>    <path class="mouth" fill="#d3d3d3" opacity=".72"/>  </g></svg>
复制代码


点击查看SVG全部内容,我们先拿第一个路径数据来看一下:


M226.31 258.64c.77 8.68 2.71 16.48 1.55 25.15-.78 8.24-5 15.18-7.37 23-3.1 10.84-4.65 22.55 1.17 32.52 4.65 7.37 7.75 11.71 5.81 21.25-2.33 8.67-7.37 16.91-2.71 26 4.26 8.68 7.75 4.34 8.14-3 .39-12.14 0-24.28.77-36 .78-16.91-12-27.75-2.71-44.23 7-12.15 11.24-33 7.76-46.83z
复制代码


对于不熟悉 SVG 相关内容的同学,你可能看不懂,甚至有点烦躁,不过也不要急,看不懂也不要紧,跟着我们一起往下看,学完你也可以在GSAP动画平台里面找一些相关的 SVG 练手。


我们简单看一下路径数据里面的一些常见命令含义:


  • M,m:  Move to:移至,移动到

  • L, l, H, h, V, v: Line to:画线

  • C, c, S, s:   三次贝塞尔曲线

  • Q, q, T, t:  二次贝塞尔曲线

  • A,a:  椭圆弧曲线

  • Z, z:  关闭路径


这些命令区分大小写大写字母表示绝对坐标,而小写字母表示命令相对于当前位置。





更多细节和知识点请查阅:路径数据命令规范

2.ArkUI 中如何绘制

那么我们如何在 ArkUI 中使用这段路径数据呢?


我们在 HarmonyOS 文档中看到了Path绘制组件


Path 绘制组件: 根据绘制路径生成封闭的自定义形状


Path 接口如下:


Path(value?: { width?: number | string; height?: number | string; commands?: string })
复制代码


参数含义如下:



它还有很多通用的属性,那么我们把水母的第一个路径数据传递到 commands 里面试试:


Path()    .commands('M226.31 258.64c.77 8.68 2.71 16.48 1.55....')    .fillOpacity(0.49)    .fill(Color.White)
复制代码





我们看到第一条触手就这么被我们渲染出来了,是不是感觉也挺简单的。

3.元素标签 g

这时候可能有同学会问,path 外层还有一个有个元素标签<g class="...">包裹着,那么这个元素标签<g class="...">是干什么的呢?


问的好👏🏻👏🏻,这个 g 表示


组合对象的容器,添加到 g 元素上的变换会应用到其所有的子元素上。

添加到 g 元素的属性会被其所有的子元素继承。


这里有个小插曲:一开始我也犯了个错误,在华为的官方文档里面没有看到 Group 组件


为什么会联想到 Group 呢?下意识的去联想 ArkUI 应该和其他平台的一样,应该也有。


我想着既然是组合对象的容器,又没有找到 Group 那我用 Stack 不就完事了吗?


然而,发现事情并不是想象的那么简单。


如果你用 Stack 直接包裹 Path,可能会出现的错误效果如下:





// 类似如下代码:Stack(){    Path().commands(...)    ...}.width('100%').height('100%')
复制代码


我们可以看到,内容是绘制了,但是远远达不到我们要的效果,甚至丑陋不堪,都乱了,到底是什么原因呢?


每一步失败的过程,就不在这里一一描述😂,原因在于我们:没有设置“路径所在的矩形宽高”


那我们如果挨个按照下面这样设置,是不是太蠢了?


Path().commands(...).width(...).height(...)
复制代码


后来我再次仔细查阅 HarmonyOS 文档,在绘制组件中找到了Shape组件,官方文档的解释,它有 2 种意思:


1、绘制组件使用 Shape 作为父组件,实现类似 SVG 的效果。

2、绘制组件单独使用,用于在页面上绘制指定的图形。


至此,我们再回头看一下,width/height 造成的一些问题。


我们在 SVG 的 XML 中通过viewBox属性获取到,viewBox="0 0 530.46 563.1"


viewBox 属性的值是一个包含 4 个参数的列表 min-x, min-y, width and height,以空格或者逗号分隔开。

不允许宽度和高度为负值,width 或 height 的值,等于 0 的情况下,这个元素将不会被渲染出来。


<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 530.46 563.1">......
复制代码


这里我们通过Shape组件里面的 viewPort 属性方法来设置 viewBox 里面的宽度和高度


所以,我们可以通过下面的方式来实现和 SVG 等价的内容:


<g class="...">    <path class="..."  fill="..." d=""/>    <path class="..." fill="..." d=""/>  </g>
复制代码


等价于


Shape() {    Path()      .commands('....')      .fill(...)
Path() .commands('...') .fill(...) }.viewPort({ width: '530.46px', height: '563.1px', })
复制代码


这里可能又会有同学问道了,为什么单位是 PX?


问的好👏🏻👏🏻,我想这里有一篇内容解释的很详细了:为什么viewBox里面的单位是px?


SVG 最重要的内容都拆解介绍完了。

4.feTurbulence

可能这个时候又有同学问了,那个 SVG 里面的 feTurbulence 是干什么用的?


<filter id="turbulence" filterUnits="objectBoundingBox" x="0" y="0" width="100%" height="100%">    <feTurbulence data-filterId="3" baseFrequency="0.02 0.03" result="turbulence" id="feturbulence" type="fractalNoise" numOctaves="1" seed="1"></feTurbulence>    <feDisplacementMap id="displacement" xChannelSelector="R" yChannelSelector="G" in="SourceGraphic" in2="turbulence" scale="13" />  </filter>    
复制代码


feTurbulence 含义


SVG 滤波器会产生噪声,这用于模拟一些自然现象,如:云、火和烟, 有助于生成复杂的纹理,如大理石或花岗岩等效果。


点击查看feTurbulence了解更多


那么 ArkUI 中如何实现这个这种效果呢?HarmonyOS 文档里面Shape组件有个Mesh属性,按理说它可以实现这种效果,我就提了个工单,询问关于 Mesh 属性的问题,官方给我的回复是





所以本项目也不能使用 Mesh 的特性了,期待官方更新。

制作动画

华为官方对参加挑战赛的要求是:


参赛者需要 :①使用 animateTo 实现显式动画,②使用 animation 为组件添加属性动画


点击查看animation属性动画点击查看animateTo显示动画


所以我们就根据官方的要求来写了个水母动画参赛作品。


我们的数据状态存储都放在 JellyFishViewModel 里面。


水母的眨眼睛,使用的是 animateTo,通过显示动画修改:水母眼睛 Y 轴的缩放和不透明度来达到眨眼睛效果。


我们来简单看下眨眼动画:


  blinkAnimateTo() {    animateTo({      duration: 150,      curve: Curve.EaseOut,      iterations: 1,      playMode: PlayMode.Normal,      onFinish:()=> {        // 闭眼之后,再恢复回睁眼状态        this.blinkScale =this.blinkScale == 0.3?1:0.3        this.blinkAlpha =this.blinkAlpha == 0? 1: 0      }    }, () => {        this.blinkScale =this.blinkScale == 0.3?1:0.3        this.blinkAlpha =this.blinkAlpha == 0? 1: 0    })  }
复制代码


然后给我们的水母眼睛设置缩放透明度属性就能眨眼睛了,下面是左侧眼睛的部分代码:


    Shape() {      Path()        .fill(...)        .commands(...)    }    .scale({ y: this.blinkScale, centerY: '233px' })    .opacity(this.blinkAlpha)    .viewPort({      width: '530.46px',      height: '563.1px'    })
复制代码


到这里可能又有同学,又要疑惑提问题了,怎么设置缩放还要设置 centerY 呢?


我们当然要设置,缩放的中心点啦,并且现在是针对于 Y 轴,所以需要:设置 Y 轴中心点,不然它缩放就偏了。


那为什么是 233px 呢?


问的好,我们看一下左侧眼睛的 pathData:


M262 233.63a3.1 3.1 0 1 0-3 3.19 3.1 3.1 0 0 0 3-3.19z
复制代码


我们上面拆解 SVG 的时候,介绍了 M 的含义是:移动到,且大写字母表示:绝对坐标,当然你填 233.63px 也可以。


我们整个水母 body 上下移动,是如何做到的呢?


我们利用了属性动画 animation 更新组件的属性 translate 里面的 y 轴数据达到上下移动的动画,我们简单看下下面的伪代码,它是如何做到不停的上下移动的:


Stack() {    // 水母的body元素分组    ...}.translate({ y : this.translateY }).onAreaChange(()=>{   this.translateY = -30}).animation({    duration: 3000, // 动画时长    iterations: 1, // 播放次数    playMode: PlayMode.Normal, // 动画模式    onFinish: () => {      // 动画播放完成回调      this.translateY = this.translateY == 0? -30 : 0   }})
复制代码


这样我们就做到,让水母整个 body 元素上下动画移动了,且不会停止。


那么水母的脸部怎么做到上下左右动画移动,且不会和 body 同步,有错位和落差的效果呢?


问的好,我们再来看一下水母脸部的动画怎么处理的:


Stack() {    // 脸部数据    ...}.translate({ y : this.translateY, x: this.translateX }).onAreaChange(()=>{   this.translateY = -25   this.translateX = ...    // 这里其实我们是在viewModel中,使用Math.random来计算translateX的值的   // 感兴趣的可以打开我们的源码查看}).animation({    duration: 3000, // 动画时长    iterations: 1, // 播放次数    playMode: PlayMode.Normal, // 动画模式    onFinish: () => {      // 动画播放完成回调      this.translateY = this.translateY == 0? -25 : 0      this.translateX = ...      // 这里其实我们是在viewModel中,使用Math.random来计算translateX的值的      // 感兴趣的可以打开我们的源码查看   }})
复制代码


如果学完觉得有帮助的,可以点赞❤️+收藏❤️+分享❤️+关注❤️

发布于: 2022-12-11阅读数: 29
用户头像

Halifax

关注

Android 2021-09-13 加入

星光不问赶路人,岁月不负有心人

评论

发布
暂无评论
HarmonyOS玩转ArkUI动效 - 水母动画_前端_Halifax_InfoQ写作社区