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跨程序傳大圖思考及實現——附上原理分析