HarmonyOS 鸿蒙Next【挑战赛第三期】玩转ArkUI动效 - 水母动画

HarmonyOS 鸿蒙Next【挑战赛第三期】玩转ArkUI动效 - 水母动画

HarmonyOS【挑战赛第三期】玩转ArkUI动效

本项目通过ArkTS结合SVG相关知识点制作了一个水母动画效果。

我的博客

项目源码

哔哩哔哩在线演示视频

灵感来自这个 Html+Css+JS制作的一个水母动画

我通过HarmonyOS的ArkUI实现了它。

一、工程目录树

工程目录树

二、拆解SVG

我们简单看一下这个 水母的SVG内容

我们看第一个触角的pathData,<path d="M226.31 258.64c.77 8.68......." />

M226.31 258.64c.77 8.68 2.71 16.48 1.55.....

如果你不熟悉SVG相关内容,你可能看不懂,甚至有点烦躁,不过也不要急,看不懂也不要紧。

那我们如何去渲染这段数据呢,ArkTS给我们提供了一个 绘制组件Path,我们简单看下,如何渲染:

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

我们可以看到这样就可以把我们的水母触手绘制出来了,不过这里,可能很多同学实战的时候还是不明白为什么要这么写,或者说参考了官方示例之后,换个SVG例子就写不出来了,这都是由于理解不够深,导致出现部分错误的写法。

别急,请跟着我慢慢来,我们下面先简单看下,PathData里面的命令规范:

  • M,m: Move to:移至,移动到
  • L, l, H, h, V, v: Line to:画线
  • C, c, S, s: 三次贝塞尔曲线
  • Q, q, T, t: 二次贝塞尔曲线
  • A,a: 椭圆弧曲线
  • Z, z: 关闭路径

更多细节请查阅: 路径数据命令规范,这些命令区分大小写,大写字母表示绝对坐标,而小写字母表示命令相对于当前位置。

可能有细心的同学,这里已经看到我们给Path设置了width/height,如果不设置或者设置的太小了,commands里面执行的命令会导致内容移动出你设置的范围,就会看不到你要绘制的内容。

其实这里,一开始我参考HarmonyOS的文档的时候我也犯了类似这个错误,是这样的:

一开始我以为ArkTS里面没有SVG的group概念,就想着用Stack来包裹子组件, 然后给Stack设置了全屏的宽度和高度,发现整个水母body完全不对称,脸和body不在同一处。

说道这里,我们看到上面的SVG有个元素标签<g class="..." />,这个g表示:组合对象的容器,添加到g元素上的变换会应用到其所有的子元素上。添加到g元素的属性会被其所有的子元素继承。

这里我还犯了个错误,就是没有在文档中发现对标SVG里面的g元素,导致我们饶了很多弯路,行驶弯路的时候一直用的Stack去包裹我们的Path,以及单独给Path设置width/height

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

  1. 绘制组件使用Shape作为父组件,实现类似SVG的效果。
  2. 绘制组件单独使用,用于在页面上绘制指定的图形。

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

我们在SVG的XML中通过 viewBox属性 获取到,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最重的内容都拆解介绍完了,可能这个时候又有同学问了,就那个SVG里面的这个:

<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"/>
    <feDisplacementMap id="displacement" xChannelSelector="R" yChannelSelector="G" in="SourceGraphic" in2="turbulence" scale="13" />
</filter>    

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

本项目,目前没有实现这个功能,一开始想着使用Shape里面的mesh属性来做的,发现不好处理,提的工单回复说HarmonyOs不支持触摸mesh效果。

三、制作动画

项目里面用到了:显示动画 animateTo 和 属性动画 animation

数据状态管理存储都放在 JellyFishViewModel 里面

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

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

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的值的
      // 感兴趣的可以打开我们的源码查看
   }
})

更多关于HarmonyOS 鸿蒙Next【挑战赛第三期】玩转ArkUI动效 - 水母动画的实战教程也可以访问 https://www.itying.com/category-93-b0.html

11 回复

牛,我还没搞懂路径绘制

更多关于HarmonyOS 鸿蒙Next【挑战赛第三期】玩转ArkUI动效 - 水母动画的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


可以先跟着我的文章一步一步的操作一下去学习,一定要动手敲代码,不清楚的地方,欢迎评论交流。

可以,谢谢大佬,

学习了!

大佬,api9下 arkts如何跳转安卓应用啊

本来我想让足球按path来一个曲线路径,涉及到path,花了点时间没搞成。path是一个学问哪

嗯呐,鸿蒙ArkUI也在逐步完善,后面可能会更丰富,

针对帖子标题“HarmonyOS 鸿蒙Next【挑战赛第三期】玩转ArkUI动效 - 水母动画”的问题,以下是专业且简洁的回答:

在HarmonyOS鸿蒙系统中,利用ArkUI框架实现水母动画,关键在于掌握动画属性与事件处理。ArkUI提供了丰富的动画API,可用于创建和控制动画效果。

要实现水母动画,首先需定义动画对象,设置动画的起始与结束状态,如位置、透明度、缩放比例等。对于水母动画,可能还需模拟水母的柔软摆动,这可通过关键帧动画或连续动画链实现,结合贝塞尔曲线调整动画的平滑度。

在ArkUI中,可通过定时器或动画框架触发动画,并监听动画事件以执行后续逻辑。例如,当动画完成时,可重置动画状态以实现循环播放。

此外,还需注意动画性能优化,避免动画过程中出现卡顿。可通过减少动画元素数量、简化动画逻辑、利用硬件加速等方式提升性能。

若在实现过程中遇到具体问题,如动画效果不理想、性能瓶颈等,可参考ArkUI官方文档或社区资源寻求解决方案。

如果问题依旧没法解决请联系官网客服,官网地址是:https://www.itying.com/category-93-b0.html

回到顶部