本文会详细讲解我参加: HarmonyOS【挑战赛第三期】玩转ArkUI动效的项目
我的参赛项目源码:【挑战赛第三期】JellyfishAnimation
动画效果参考自:cassie-codes的水母SVG

华为鸿蒙已经放弃Java作为鸿蒙的开发语言,开发了一个申明式UI框架ArkUI,开发语言变成了ArkTS。
ArkUI是一套构建分布式应用界面的声明式UI开发框架。
ArkTS基于TypeScript(简称TS)语言扩展而来,是TS的超集。
ArkTS继承了TS的所有特性。
我们用一个简单示例,来说明ArkTS的基本组成:

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

我们开头提到cassie-codes的水母SVG,如果拿到这个SVG的话,要怎么用程序去渲染它呢?
我们精简一下看看它的组成内容:
<!-- 为了直观的查看组成结构,我们删除了路径数据 -->
<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练手。
我们简单看一下路径数据里面的一些常见命令含义:
这些命令区分大小写,大写字母表示绝对坐标,而小写字母表示命令相对于当前位置。

更多细节和知识点请查阅:路径数据命令规范。
那么我们如何在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)

我们看到第一条触手就这么被我们渲染出来了,是不是感觉也挺简单的。
这时候可能有同学会问,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最重要的内容都拆解介绍完了。
可能这个时候又有同学问了,那个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滤波器会产生噪声,这用于模拟一些自然现象,如:云、火和烟, 有助于生成复杂的纹理,如大理石或花岗岩等效果。
那么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的值的
// 感兴趣的可以打开我们的源码查看
}
})
如果你看到了这里,说明你真的来学了,
我再推荐一下,我本次参加的“挑战代码画颗圣诞树”的活动。

我们看一下,最终的圣诞树的效果:

HarmonyOS绘制圣诞树源码下载地址:
https://download.csdn.net/download/logicsboy/87312440
如果学完觉得有帮助的,可以点赞❤️+收藏❤️+评论❤️+分享❤️+关注❤️
Unity自动旋转动画1.开门需要门把手先动,门再动2.关门需要门先动,门把手再动3.中途播放过程中不可以再次进行操作觉得太复杂?查看我的文章开关门简易进阶版效果:如果这个门可以直接打开的话,就不需要放置"门把手"如果门把手还有钥匙需要旋转,那就可以把钥匙放在门把手的"门把手",理论上是可以无限套娃的可调整参数有:角度,反向,轴向,速度运行时点击Test进行测试自己写的代码比较垃圾,命名与结构比较拉,高手轻点喷,新手有类似的需求可以拿去做参考上代码usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;u
术语中文解释Ability原子化服务帮助用户完成任务的原子化服务,和用户的意图进行关联。Fulfillment服务履行通过图标,卡片,语音等形式呈现用户意图。开发者通过接口的方式,处理用户意图,返回内容。Intent意图用于表达用户想要达成的目标或完成的任务。HUAWEIAssistant智能助手“无微不智”的个人助手,通过不断的学习用户的使用习惯,不断的为用户提供贴心的精准的便捷的个性化服务。AISearch全局搜索用户可快速搜索关键词,与之匹配的原子化服务则会出现在搜索结果中。SmartService智慧服务用户订阅原子化服务,在到达特定触发条件(时间、地点、事件)后,卡片推送至用户智能助
动画/*INITIALIZEANANIMATION 初始化一个动画*-----------------------*/lv_anim_ta;lv_anim_init(&a);/*MANDATORYSETTINGS 必选设置*------------------*//*Setthe"animator"function 设置“动画”功能*/lv_anim_set_exec_cb(&a,(lv_anim_exec_xcb_t)lv_obj_set_x);/*Setthe"animator"function*/lv_anim_set_var(&a,obj);/*Lengthoftheanim
有人知道如何使用Carrierwave+MiniMagick将动画GIF压缩到第一帧吗? 最佳答案 我认为MiniMagick有一些变化,因为我只花了三个小时试图找出为什么Andrey的代码对我不起作用。我收到以下错误:ActiveRecord::RecordInvalid(Validationfailed:ImageFailedtomanipulatewithMiniMagick,maybeitisnotanimage?OriginalError:Command("mogrify-scene/var/folders/0o/0oqN
我正在尝试为每个ajax请求显示一个加载指示器,我在Rails3应用程序中工作。HTML:"loading-indicator",:style=>"display:none")%>CSS:#loading-indicator{position:absolute;left:10px;top:10px;}loading.js:我放在assest/javascripts/$(document).ready(function(){$(document).ajaxSend(function(event,request,settings){$('#loading-indicator').show(
相信有很多人和我一样,最近的快乐都来自于《黑暗荣耀2》。 令人奇怪的是,但比起故事的主线,剧中妍珍等配角的”发疯”片段却成为了网友造梗的来源。 “妍珍疯驴子”“妍珍呐””“黑暗荣耀演我每天精神状态”等。让这部剧话题热度持续上涨。与《黑暗荣耀2》同理,最近的引发热度的网络事件:张大大的抓马直播、汪小菲与大S的口水战、v我50的肯德基疯四文学,都主打一股子歇斯底里的疯劲。 为什么邀请全民围观“发疯”,也能成为一种宣传方式?营销“疯学”为何走红?又给品牌什么启示?营销“疯学”的前世今生营销“疯学”来源于当代最实用的互联网文体——发疯文学。而发疯文学最早来源于各大古早影视剧。现在的发疯文学被网友
我有一个非常基本的应用程序,可让您创建形状并用一条线将它们连接起来。为此,您需要执行以下操作。Example1.Clicknewanimation2.addrectangle3.addchild4.addcircle您可以移动形状、拖动和调整大小。我想知道是否可以在两个对象之间添加动画。因此,例如,一个小圆球会在两个物体之间的线上移动。我已经查看了fabricjs动画页面上的演示,但不确定是否可以从对象b执行。这是FIDDLE. 最佳答案 我不知道你是否可以在fabric中使用内置的动画功能,因为正如你所说,这些对象可能会自己移动。
当然,我们可以使用关键帧创建CSS动画,并从那里控制它。但是,理想情况下,我想通过单击按钮来触发此动画-因此单击按钮将是一个事件...@keyframesfade-in{0%{opacity:0;}100%{opacity:1;}}现在,点击时,我想触发这个动画;而不是在CSS动画属性中。 最佳答案 看这里jsfiddle如果您希望每次按下按钮时动画都起作用,请使用此代码:$('button').click(function(){$(".fademe").addClass('animated');setTimeout(functio
我想画一个点,大约1秒后我想画下一个点。这是否可能:我已经试过了:functionsimulate(i){setTimeout(function(){drawPoint(vis,i,i);},1000);}for(vari=1;i不幸的是,这是行不通的。它只是立即绘制整条线。 最佳答案 这是行不通的,因为for循环将立即运行到结束,setTimeouts将被同时调度,所有函数将同时触发。取而代之的是,这样做:vari=1;(functionloop(){if(i++>200)return;setTimeout(function(){
在我的ReactNative应用程序中,我有一张带有条件的卡片按下按钮时呈现并在触发相同按钮时删除的组件。这是我的代码的样子:this.setState({triggered:!this.state.triggered})}title="ClicktoExpand"/>Loremipsumdolorsitamet,consecteturadipiscingelit,seddoeiusmodtemporincididuntutlaboreetdoloremagnaaliqua.Utenimadminimveniam,quisnostrudexercitationullamcolabori