本文正在参与「金石计划 . 瓜分6万现金大奖」

一、前语

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

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

咱们的动画作用参阅自:cassie-codes的水母SVG

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

ArkUI是一套构建分布式运用界面的声明式UI开发结构。

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

ArkTS承继了TS的所有特性。

咱们用一个简略示例,来阐明ArkTS的根本组成:

HarmonyOS玩转ArkUI动效 - 水母动画

关于ArkUI更多内容,感兴趣的同学,能够点击这儿快速入门,下面咱们进入正题。

二、源码目录结构

HarmonyOS玩转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: 关闭途径

这些指令区别大小写大写字母表明肯定坐标,而小写字母表明指令相对于当前位置。

HarmonyOS玩转ArkUI动效 - 水母动画

可视化途径操作

更多细节和知识点请查阅:途径数据指令标准。

2.ArkUI中怎样制造

那么咱们怎样在ArkUI中运用这段途径数据呢?

咱们在HarmonyOS文档中看到了Path制造组件

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

Path接口如下:

Path(value?: { width?: number | string; height?: number | string; commands?: string })

参数意义如下:

参数名 参数类型 必填 参数描绘
width number 或 string 途径地点矩形的宽度默认值:0
height number 或 string 途径地点矩形的高度默认值:0
commands string 途径制造的指令字符串默认值:”

它还有许多通用的特点,那么咱们把水母的第一个途径数据传递到commands里边试试:

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

HarmonyOS玩转ArkUI动效 - 水母动画

第一条触手

咱们看到第一条触手就这么被咱们烘托出来了,是不是感觉也挺简略的。

3.元素标签g

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

问的好,这个g表明

组合目标的容器,添加到g元素上的改换会运用到其所有的子元素上。

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

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

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

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

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

假如你用Stack直接包裹Path,或许会出现的过错作用如下:

HarmonyOS玩转ArkUI动效 - 水母动画

// 类似如下代码:
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特点的问题,官方给我的回复是

HarmonyOS玩转ArkUI动效 - 水母动画

所以本项目也不能运用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的值的
      // 感兴趣的能够翻开咱们的源码检查
   }
})

假如学完觉得有帮助的,能够点赞❤️+保藏❤️+分享❤️+关注❤️