動態改變Lottie動畫顏色

Kash Yang
9 min readJun 17, 2023

--

沒有想到身為Android Developer的我, 居然天字第一號文章寫了篇iOS的小範例

起因來自於跟幾位想轉職成為軟體工程師的Appworks School學員聊到了Lottie, 也建議他們有空可以玩一玩, 於是乎我想了想乾脆來一寫一個我以前在Android上面也實現過, 簡單的動態變色功能來拋磚引玉吧😁

功能有哪些?

  • 使用Lottie, 在首頁上以loop的方式輪播一個動畫
  • 在首頁上放上一個按鈕, 讓使用者可以自由選擇顏色
  • 以Dark mode開啟的話, 預設動畫跟按鈕皆為白色, 反之則為灰色
  • 根據使用者選擇的顏色, 改變動畫以及按鈕字的顏色

接下來就開始一步步完成這個小程式吧

開一個新專案並引入Lottie

https://lottiefiles.com/blog/working-with-lottie/how-to-add-lottie-animation-ios-app-swift

去LottieFiles抓一個動畫

我範例裡面使用的是免費版的鑽石動畫 (https://lottiefiles.com/143952-diamond-back-to-basics)

UI設定

ColorPicker使用原生的 UIColorPickerViewController

放動畫跟按鈕到首頁上

// setup button
button.setTitle("Pick Diamond color", for: .normal)
button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
self.view.addSubview(button)

// setup lottie animation view
animationView = .init(name: "animation")
animationView.contentMode = .scaleAspectFit
animationView.loopMode = .loop
animationView.animationSpeed = 1
self.view.addSubview(animationView)

設constraint, 範例中讓動畫佔滿畫面高度的一半, 並且將按鈕對齊動畫的下方中央

// add constraint
animationView.translatesAutoresizingMaskIntoConstraints = false
animationView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
animationView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
animationView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
animationView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.5).isActive = true

button.translatesAutoresizingMaskIntoConstraints = false
button.topAnchor.constraint(equalTo: animationView.bottomAnchor).isActive = true
button.centerXAnchor.constraint(equalTo: animationView.centerXAnchor).isActive = true

別忘了我們的dark mode需求

// setup initial color of the animation and button
if self.traitCollection.userInterfaceStyle == .dark {
self.updateDiamondColor(.white)
self.button.setTitleColor(.white, for: .normal)
self.picker.selectedColor = .white
} else {
self.updateDiamondColor(.black)
self.button.setTitleColor(.black, for: .normal)
self.picker.selectedColor = .black
}

動態改變動畫顏色

我們這邊需要使用Lottie Value Provider (細節就不深入討論啦…), 這邊要處理的是變色, 所以我們可以使用Default的ColorValueProvider

新增一個LottieAnimationView的extension來協助我們處理換色

extension LottieAnimationView {
func setAnimationWithChangeColor(name: String, color: UIColor, paths: [String]) {
paths.forEach {
self.setValueProvider(ColorValueProvider(color.lottieColorValue), keypath: AnimationKeypath(keypath: $0))
}}
}

不過… 什麼是 AnimationKeypath 啊..?

可以看一下定義: https://airbnb.io/lottie/#/ios?id=animation-keypaths

AnimationKeypath is an object that describes a keypath search for nodes in the animation JSON.

Lottie把動畫需要的相關元素, 都記錄在JSON裏面, 成為一個一個的node, 而keypath就是讓我們找到這些node的路徑, 找到那些node之後, 就可以透過Lottie的API, 達到換色的效果

這時心中又冒出了疑問: 但我哪知道這個動畫有哪些node, 還有這些node的keypath分別又是什麼啊?

莫驚慌, 官方有提供方法log出所有動畫成像所需的keypath

animationView.logHierarchyKeypaths()

不過數量會有點多, 範例的動畫你會在console看到類似這樣的log

Lottie: Logging Animation Keypaths
33
33.Transform
33.Transform.Rotation Y
33.Transform.Anchor Point
33.Transform.Position
33.Transform.Scale
33.Transform.Rotation Z
33.Transform.Opacity
33.Transform.Rotation
33.Transform.Rotation X
33.Pre-comp 1
...
...

所以我們要自己找出需要的, 這邊要處理的是顏色, 所以要找出Color nodes (可以使用grep)

33.Pre-comp 1.hareket Outlines 3.Group 1.Stroke 1.Color
33.Pre-comp 1.hareket Outlines 2.Group 1.Stroke 1.Color
33.Pre-comp 1.hareket Outlines.Group 1.Stroke 1.Color
33.Pre-comp 1.ort Outlines.Group 1.Stroke 1.Color
...

最後指定這些keypath給ColorValueProvider就大功告成囉!

可以一條一條指定, 或是使用keypath wildcard, 用法在官方範例中也有提到, 這邊示範的是 * (star), 跟** (double star) 的用法

// Represents the color node for every Stroke named "Stroke 1" in the animation.
func updateDiamondColor( _ color: UIColor) {
self.animationView.setAnimationWithChangeColor(
name: "animation",
color: color,
paths: [
"**.Stroke 1.Color"
]
)
}
// list all the color nodes we need
func updateDiamondColor( _ color: UIColor) {
self.animationView.setAnimationWithChangeColor(
name: "animation",
color: color,
paths: [
"33.Pre-comp 1.hareket Outlines *.Group 1.Stroke 1.Color",
"33.Pre-comp 1.hareket Outlines.Group 1.Stroke 1.Color",
"33.Pre-comp 1.ort Outlines.Group 1.Stroke 1.Color",
"33.Pre-comp 1.taban Outlines.Group *.Stroke 1.Color",
"prlt Outlines *.Group *.Stroke 1.Color"
]
)
}

最後來看一下成果吧

github: https://github.com/kash-yang/LottiePlayground

祝各位Coding愉快

--

--