HarmonyOS玩轉ArkUI動效 - 水母動畫
theme: smartblue
本文正在參加「金石計劃 . 瓜分6萬現金大獎」
一、前言
本文會詳細講解我參加 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.組成
我們精簡一下看看它的組成內容:
```xml
txt
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接口如下:
ts
Path(value?: { width?: number | string; height?: number | string; commands?: string })
參數含義如下:
| 參數名 | 參數類型 | 必填 | 參數描述 | | :------- | :--------------- | :- | :--------------- | | width | number 或 string | 否 | 路徑所在矩形的寬度默認值:0 | | height | number 或 string | 否 | 路徑所在矩形的高度默認值:0 | | commands | string | 否 | 路徑繪製的命令字符串默認值:''|
它還有很多通用的屬性,那麼我們把水母的第一個路徑數據傳遞到commands裏面試試:
ts
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,可能會出現的錯誤效果如下:
ts
// 類似如下代碼:
Stack(){
Path().commands(...)
...
}.width('100%').height('100%')
我們可以看到,內容是繪製了,但是遠遠達不到我們要的效果,甚至醜陋不堪,都亂了,到底是什麼原因呢?
每一步失敗的過程,就不在這裏一一描述😂,原因在於我們:沒有設置“路徑所在的矩形寬高”
那我們如果挨個按照下面這樣設置,是不是太蠢了?
ts
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的情況下,這個元素將不會被渲染出來。
xml
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 530.46 563.1">
......
這裏我們通過Shape組件裏面的viewPort屬性方法來設置viewBox裏面的寬度和高度
所以,我們可以通過下面的方式來實現和SVG等價的內容:
xml
<g class="...">
<path class="..." fill="..." d=""/>
<path class="..." fill="..." d=""/>
</g>
等價於:
```ts
Shape() {
Path()
.commands('....')
.fill(...)
Path()
.commands('...')
.fill(...)
}.viewPort({
width: '530.46px',
height: '563.1px',
})
``` 這裏可能又會有同學問道了,為什麼單位是PX?
問的好👏🏻👏🏻,我想這裏有一篇內容解釋的很詳細了:為什麼viewBox裏面的單位是px?
SVG最重要的內容都拆解介紹完了。
4.feTurbulence
可能這個時候又有同學問了,那個SVG裏面的feTurbulence是幹什麼用的?
xml
<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軸的縮放和不透明度來達到眨眼睛效果。
我們來簡單看下眨眼動畫:
ts
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
})
}
然後給我們的水母眼睛設置縮放和透明度屬性就能眨眼睛了,下面是左側眼睛的部分代碼:
ts
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:
txt
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軸數據達到上下移動的動畫,我們簡單看下下面的偽代碼,它是如何做到不停的上下移動的:
ts
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同步,有錯位和落差的效果呢?
問的好,我們再來看一下水母臉部的動畫怎麼處理的:
```ts 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的值的 // 感興趣的可以打開我們的源碼查看 } })
```
如果學完覺得有幫助的,可以點贊❤️+收藏❤️+分享❤️+關注❤️
- 鴻蒙ArkUI如何開發跨平台應用?
- HarmonyOS玩轉ArkUI動效 - 水母動畫
- Compose挑燈夜看 - 照亮手機屏幕裏面的書本內容
- 順手修復了Jetpack Compose官方文檔中的一個多點觸控示例的Bug
- 正確實踐Jetpack SplashScreen API —— 在所有Android系統上使用總結,內含原理分析
- Jetpack Compose處理“導航欄、狀態欄、鍵盤” 影響內容顯示的問題集錦
- 閒聊Android懸浮的“系統文本選擇菜單”和“ActionMode解析”——附上原理分析
- Jetpack Compose實現bringToFront功能——附上原理分析
- Android跨進程傳大圖思考及實現——附上原理分析