1.前言
多年以前汽車還是以機械儀表主體的年代,各大汽車主機廠商并不十分關注操作系統(tǒng)UI的交互功能妨退,但是隨著車載SOC算力的不斷提高以及主機廠商對汽車座艙競爭的白熱化。座艙的HMI在設計上在強調功能性的同時也開始關注UI的藝術性子寓,HMI的設計師們期望藝術與功能應該協(xié)同工作梁厉,讓用戶沉浸在“第三空間”的體驗中辜羊。
有了需求程序員就需要關注如何實施和落地,然而Android應用本身雖然有著完整的動畫框架支持词顾,但是開發(fā)復雜八秃、調試耗時,大型的gif或逐幀動畫對于CPU&內存占用都不太理想肉盹,所以許多Android的手機應用基本上不怎么有動畫昔驱。而且車載HMI上越來越多的開始引入各種光影、粒子效果上忍,如果基于Android的原生控件來實現(xiàn)這些粒子效果骤肛,難度非常大,這就需要今天的主角Lottie來實現(xiàn)了窍蓝。
2.Lottie概述
Lottie是一種基于JSON的動畫文件格式腋颠,它使設計師能夠在任何平臺上發(fā)布動畫,就像發(fā)布靜態(tài)資產一樣簡單吓笙。它們是在任何設備上工作的小文件淑玫,可以在不進行像素化的情況下放大或縮小。
GitHub:github.com/airbnb/lott…
官方文檔:airbnb.io/lottie/
Lottie在車載HMI中的優(yōu)勢
適量圖形,不會出現(xiàn)失真
占用空間比序列幀動畫小
可以修改屬性絮蒿,動態(tài)生成可交互的動畫(使用視頻動畫難以實現(xiàn)交互功能)
節(jié)省HMI的開發(fā)尊搬、調試時間
可以更輕松的實現(xiàn)粒子、光影等特效
Lottie的使用方法
- 在build.gradle中添加依賴
dependencies {
def lottieVersion = "5.2.0"
implementation 'com.airbnb.android:lottie:$lottieVersion'
}
- 使用LottieAnimationView 首先將lottie動畫的json文件放在assets文件夾下
然后就可以在布局文件中使用LottieAnimationView了
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/dynamic_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:lottie_fileName="HamburgerArrow.json"
app:lottie_autoPlay="true"
app:lottie_loop="true"/>
然后運行APP就可以看到動畫效果
3.Lottie的常用屬性&API
LottieAnimationView
繼承自AppCompatImageView土涝,所以ImageView支持的屬性毁嗦,LottieAnimationView
都是支持的,這部分就不再介紹了回铛。
- lottie_fileName 設定lottie動畫所對應的json文件地址。json文件默認需要放置在assets下克锣,設定時不需要再強調assets
app:lottie_fileName="HamburgerArrow.json"
如果設定 app:lottie_fileName="other/HamburgerArrow.json"茵肃,那么lottie就會讀取assets/other/HamburgerArrow.json。
void setAnimationFromJson(String jsonString, @Nullable String cacheKey)
- lottie_rawRes 設定lottie動畫的json文件地址袭祟。json文件除了可以放置assets文件夾下验残,還可以放在raw文件夾下。使用時需要注意巾乳,利用lottie_rawRes引入資源時您没,json文件名前需要加上@raw,并且文件名不帶.json后綴胆绊。
app:lottie_rawRes="@raw/name"
lottie_autoPlay 設定是否自動播放氨鹏,取值為true | false
lottie_loop 設定是否循環(huán)播放,取值為true | false
lottie_url 當需要加載在線資源時压状,就可以使用lottie_url
void setAnimationFromUrl(String url)
void setAnimationFromUrl(String url, @Nullable String cacheKey)
- lottie_fallbackRes
設置一個drawable仆抵,如果lotticomposition由于任何原因未能加載,則將呈現(xiàn)該drawable种冬。
如果這是網(wǎng)絡動畫镣丑,可以使用它向用戶顯示錯誤,也可以添加一個失敗的監(jiān)聽器重試下載娱两。
void setFallbackResource(@DrawableRes int fallbackResource)
- lottie_repeatMode
設定循環(huán)播放的順序莺匠。取值為restart | reverse 。restart表示正常循環(huán)播放十兢,reverse表示倒序播放
void setRepeatMode(@LottieDrawable.RepeatMode int mode)
int getRepeatMode()
- lottie_repeatCount
設定循環(huán)播放次數(shù)趣竣,取值為整數(shù)類型。
void setRepeatCount(int count)
int getRepeatCount()
- lottie_imageAssetsFolder
設定圖片文件在assets文件夾下的訪問路徑纪挎。有的時候使用AE導出lottie的json時也會導出一些圖片期贫,這時候就需要該屬性設定圖片的地址。
void setImageAssetsFolder(String imageAssetsFolder)
String getImageAssetsFolder()
- void setFrame(int frame)
將進度設置為指定的幀异袄。將進度設置為指定的幀通砍。如果尚未設置合成,則進度將在設置時設置為幀。
通過int getFrame()
可以獲取當前渲染的幀封孙。
- void setMaxFrame(int endFrame)
設置播放或循環(huán)時動畫將結束的最大幀迹冤。
該值將被鉗制到合成邊界。例如虎忌,設置整數(shù)最大值將產生與合成相同的結果泡徙。
通過float getMaxFrame()
可以獲取當前設定的最大幀
- void setMinFrame(int startFrame)
設置播放或循環(huán)時動畫開始的最小幀。
設定最大膜蠢、最小幀可以只播放lottie動畫中的一部分堪藐,例如下面的兩張圖,第一張是完整的從0播放到183幀挑围,第二張則是從60播放到100幀礁竞。
- lottie_progress
設定動畫初次顯示時的進度,類型為float杉辙。取值范圍0.0 ~ 1.0
void setProgress(@FloatRange(from = 0f, to = 1f) float progress)
float getProgress()
- lottie_speed
設定播放速度模捂,取值類型為float。當速度<1時蜘矢,動畫會慢放狂男,當速度<0時,可以實現(xiàn)倒序播放品腹。
void setSpeed(float speed)
float getSpeed()
void reverseAnimationSpeed()
:反轉當前動畫速度岖食。這不會播放動畫。
速度是一個比較重要的屬性珍昨,與progress县耽、frame等屬性一起靈活運用,我們就可以輕松地在HMI上實現(xiàn)炫酷而復雜的儀表盤效果镣典,這對車載HMI尤為重要兔毙。
- lottie_enableMergePathsForKitKatAndAbove
設定是否開啟MergePath屬性,取值為true | false兄春。默認為false
void enableMergePathsForKitKatAndAbove(boolean enable)
boolean isMergePathsEnabledForKitKatAndAbove()
- void playAnimation()
從頭開始播放動畫澎剥。如果速度<0,它將從終點開始赶舆,并向起點播放哑姚。必須在主線程中調用。
- void cancelAnimation()
取消動畫芜茵,必須在主線程中調用叙量。
- void pauseAnimation()
暫停動畫,必須在主線程中調用九串。
- void resumeAnimation()
從當前位置繼續(xù)播放動畫绞佩。如果速度<0寺鸥,它將從當前位置向后播放。必須在主線程中調用品山。
- long getDuration()
獲取動畫的播放時長胆建。
- void setTextDelegate(TextDelegate textDelegate)
設置此選項可在運行時用自定義文本替換動畫文本
- lottie_cacheComposition
設定是否開啟緩存,取值 true | false肘交,默認開啟笆载。開啟緩存可以提升動畫的加載效率。
void setCacheComposition(boolean cacheComposition)
- lottie_ignoreDisabledSystemAnimations
允許忽略系統(tǒng)動畫設置涯呻,因此即使禁用動畫凉驻,也允許運行動畫。取值 true | false复罐,默認為false沿侈。
void setIgnoreDisabledSystemAnimations(boolean ignore)
- lottie_clipToCompositionBounds
設置lottie是否應剪輯到原始動畫合成邊界。設置為true時市栗,父視圖可能需要禁用clipChildren,以便Lottie可以在LottieAnimationView邊界之外進行渲染咳短。默認為true填帽。
void setClipToCompositionBounds(boolean clipToCompositionBounds)
- lottie_renderMode
設定渲染模式,取值為 automatic | hardware | software咙好。設定渲染模式為hardware時篡腌,可以顯著提升動畫的渲染效率,但是有些系統(tǒng)函數(shù)可能并不支持硬件加速勾效,實際使用時需要結合調試時的效果選擇是否開啟嘹悼。
void setRenderMode(RenderMode renderMode)
RenderMode getRenderMode()
- void addAnimatorListener(Animator.AnimatorListener listener)
添加動畫的屬性監(jiān)聽。
對應也提供了removeUpdateListener(ValueAnimator.AnimatorUpdateListener updateListener)
用來移除指定的監(jiān)聽层宫⊙罨铮或者也可以使用removeAllAnimatorListeners()
移除所有監(jiān)聽。
binding.animationView.addAnimatorUpdateListener(object : ValueAnimator.AnimatorUpdateListener {
override fun onAnimationUpdate(animation: ValueAnimator?) {
}
})
- void addAnimatorPauseListener(Animator.AnimatorPauseListener listener)
添加動畫暫停/恢復監(jiān)聽萌腿。
對應也提供了removeAnimatorPauseListener(Animator.AnimatorPauseListener listener)
用來移除指定的監(jiān)聽限匣。
binding.animationView.addAnimatorPauseListener(object : Animator.AnimatorPauseListener{
override fun onAnimationPause(animation: Animator?) {
}
override fun onAnimationResume(animation: Animator?) {
}
})
- void addAnimatorUpdateListener(ValueAnimator.AnimatorUpdateListener updateListener)
添加動畫發(fā)生更新時的監(jiān)聽
對應也提供了removeUpdateListener(ValueAnimator.AnimatorUpdateListener updateListener)
用來移除指定的監(jiān)聽』倭猓或者也可以使用removeAllUpdateListeners()
移除所有監(jiān)聽米死。
binding.animationView.addAnimatorUpdateListener(object : ValueAnimator.AnimatorUpdateListener{
override fun onAnimationUpdate(animation: ValueAnimator?) {
}
})
- void addValueCallback(KeyPath keyPath, T property, LottieValueCallback callback)
監(jiān)聽lottie動畫json中某個片段的屬性。
此keypath
可以解析為多個內容贮庞,在這種情況下峦筒,回調的值將應用于所有回調。在內部會首先檢查是否已使用resolveKeyPath(KeyPath)解析keypath窗慎,如果尚未解析物喷,則將對其進行解析。
Lottie動畫的Json中屬性都是英文簡寫,我們很難把json中key與實際的屬性對應起來脯丝,所以有了第二個參數(shù)LottieProperty
商膊,它的內部定義了大量的屬性,當我們需要修改json時宠进,只需要傳入LottieProperty
中屬性即可晕拆。
例如,需要監(jiān)聽json中LeftArmWave的持續(xù)時間材蹬,就可以這么寫
animationView.addValueCallback(KeyPath("LeftArmWave"), LottieProperty.TIME_REMAP) { frameInfo ->
}
4.Lottie的常見用法
Lottie的Demo中內置了很多官方自己開發(fā)的動畫效果实幕,目的是為我們展示Lottie的常見用法,作為開發(fā)者我們必須掌握堤器,并在適當?shù)臅r候運用到我們的應用中昆庇。
動態(tài)屬性效果
該效果展示了lottie支持動態(tài)修改json,讓動畫中的一小部分屬性發(fā)生改變闸溃。
- 修改局部動畫的速度
binding.animationView.addValueCallback(KeyPath("LeftArmWave"), LottieProperty.TIME_REMAP) { frameInfo ->
2 * speed.toFloat() * frameInfo.overallProgress
}
KeyPath
中的LeftArmWave是Json中的一個屬性
修改的效果如下整吆。注意看右手的擺動頻率X3后比X1高,以至于錄制的GIF直接丟幀了辉川。
- 修改局部動畫的顏色
val shirt = KeyPath("Shirt", "Group 5", "Fill 1")
val leftArm = KeyPath("LeftArmWave", "LeftArm", "Group 6", "Fill 1")
val rightArm = KeyPath("RightArm", "Group 6", "Fill 1")
binding.animationView.addValueCallback(shirt, LottieProperty.COLOR) { COLORS[colorIndex] }
binding.animationView.addValueCallback(leftArm, LottieProperty.COLOR) { COLORS[colorIndex] }
binding.animationView.addValueCallback(rightArm, LottieProperty.COLOR) { COLORS[colorIndex] }
修改后的效果如下:
- 修改局部動畫的運動范圍
val point = PointF()
binding.animationView.addValueCallback(
KeyPath("Body"),
LottieProperty.TRANSFORM_POSITION
) { frameInfo ->
val startX = frameInfo.startValue.x
var startY = frameInfo.startValue.y
var endY = frameInfo.endValue.y
if (startY > endY) {
startY += EXTRA_JUMP[extraJumpIndex]
} else if (endY > startY) {
endY += EXTRA_JUMP[extraJumpIndex]
}
point.set(startX, lerp(startY, endY, frameInfo.interpolatedKeyframeProgress))
point
}
修改后的效果如下
動畫文字效果
[圖片上傳失敗...(image-4c54db-1672925171869)]
該效果展示了動畫文字效果表蝙。這個效果實現(xiàn)起來其實不難,從程序中捕獲輸入的字母乓旗,再替換成lottie的資源文件即可府蛇。
val letter = "" + Character.toUpperCase(event.unicodeChar.toChar())
val fileName = "Mobilo/$letter.json"
LottieCompositionFactory.fromAsset(context, fileName)
.addListener { addComposition(it) }
動態(tài)文字效果
該效果展示動態(tài)替換動畫中的文字。使用setTextDelegate
就可以在動畫運行中修改lottie動畫中的文字
val textDelegate = TextDelegate(binding.dynamicTextView)
binding.nameEditText.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
textDelegate.setText("NAME", s.toString())
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
})
binding.dynamicTextView.setTextDelegate(textDelegate)
注意屿愚,這里其實用了兩個lottieView汇跨,分別設定了不同的文字。
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:orientation="horizontal">
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/originalTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="16dp"
app:lottie_rawRes="@raw/name"
app:lottie_autoPlay="true"
app:lottie_loop="true"/>
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/dynamicTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:lottie_rawRes="@raw/name"
app:lottie_autoPlay="true"
app:lottie_loop="true"/>
</LinearLayout>
手勢交互效果
該效果展示了Lottie的手勢交互妆距。其實和第一個效果實現(xiàn)思路相同穷遂,都是通過addValueCallback
修改json中的屬性來實現(xiàn)的。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val largeValueCallback = LottieRelativePointValueCallback(PointF(0f, 0f))
binding.animationView.addValueCallback(KeyPath("First"), LottieProperty.TRANSFORM_POSITION, largeValueCallback)
val mediumValueCallback = LottieRelativePointValueCallback(PointF(0f, 0f))
binding.animationView.addValueCallback(KeyPath("Fourth"), LottieProperty.TRANSFORM_POSITION, mediumValueCallback)
val smallValueCallback = LottieRelativePointValueCallback(PointF(0f, 0f))
binding.animationView.addValueCallback(KeyPath("Seventh"), LottieProperty.TRANSFORM_POSITION, smallValueCallback)
var totalDx = 0f
var totalDy = 0f
val viewDragHelper = ViewDragHelper.create(binding.containerView, object : ViewDragHelper.Callback() {
override fun tryCaptureView(child: View, pointerId: Int) = child == binding.targetView
override fun clampViewPositionVertical(child: View, top: Int, dy: Int): Int {
return top
}
override fun clampViewPositionHorizontal(child: View, left: Int, dx: Int): Int {
return left
}
override fun onViewPositionChanged(changedView: View, left: Int, top: Int, dx: Int, dy: Int) {
totalDx += dx
totalDy += dy
smallValueCallback.setValue(getPoint(totalDx, totalDy, 1.2f))
mediumValueCallback.setValue(getPoint(totalDx, totalDy, 1f))
largeValueCallback.setValue(getPoint(totalDx, totalDy, 0.75f))
}
})
binding.containerView.viewDragHelper = viewDragHelper
}
在RecyclerView中使用
該效果展示通過監(jiān)聽點擊事件來播放不同的lottie動畫娱据。這個效果最常見塞颁,APP中的點贊效果大多都是這樣的實現(xiàn)思路。
5.總結
在車載HMI開發(fā)中往往我們會在實現(xiàn)吸耿、調試UI上花費大量的時間祠锣,如果能夠靈活的運用Lottie,就可以顯著節(jié)省程序的開發(fā)時間咽安。例如伴网,光影、粒子等特效雖然可以也考慮用Kanzi等3D引擎實現(xiàn)妆棒,但是3D引擎會消耗成倍的SOC性能澡腾,實際開發(fā)過程中沸伏,簡單的特效使用Lottie實現(xiàn),可以極大的優(yōu)化應用的性能动分,給用戶一個更優(yōu)秀的體驗毅糟。
當然lottie也有她的缺點,并不是無所不能澜公。實際開發(fā)中我們還可以用下面的網(wǎng)站來對lottie的json進一步優(yōu)化:design.alipay.com/lolita
當然這一切的前提是姆另,UI設計師愿意為程序員切出一套Lottie的動畫(F**K!)
本文轉自 https://juejin.cn/post/7116124486218285093坟乾,如有侵權迹辐,請聯(lián)系刪除。