1.前言
多年以前汽車還是以機械儀表主體的年代,各大汽車主機廠商并不十分關(guān)注操作系統(tǒng)UI的交互功能,但是隨著車載SOC算力的不斷提高以及主機廠商對汽車座艙競爭的白熱化。座艙的HMI在設(shè)計上在強調(diào)功能...
1.前言
多年以前汽車還是以機械儀表主體的年代,各大汽車主機廠商并不十分關(guān)注操作系統(tǒng)UI的交互功能,但是隨著車載SOC算力的不斷提高以及主機廠商對汽車座艙競爭的白熱化。座艙的HMI在設(shè)計上在強調(diào)功能性的同時也開始關(guān)注UI的藝術(shù)性,HMI的設(shè)計師們期望藝術(shù)與功能應(yīng)該協(xié)同工作,讓用戶沉浸在“第三空間”的體驗中。
有了需求程序員就需要關(guān)注如何實施和落地,然而Android應(yīng)用本身雖然有著完整的動畫框架支持,但是開發(fā)復(fù)雜、調(diào)試耗時,大型的gif或逐幀動畫對于CPU&內(nèi)存占用都不太理想,所以許多Android的手機應(yīng)用基本上不怎么有動畫。而且車載HMI上越來越多的開始引入各種光影、粒子效果,如果基于Android的原生控件來實現(xiàn)這些粒子效果,難度非常大,這就需要今天的主角Lottie來實現(xiàn)了。
2.Lottie概述
Lottie是一種基于JSON的動畫文件格式,它使設(shè)計師能夠在任何平臺上發(fā)布動畫,就像發(fā)布靜態(tài)資產(chǎn)一樣簡單。它們是在任何設(shè)備上工作的小文件,可以在不進行像素化的情況下放大或縮小。
GitHub:
https://github.com/airbnb/lottie-android 官方文檔:
http://airbnb.io/lottie/
Lottie在車載HMI中的優(yōu)勢
適量圖形,不會出現(xiàn)失真
占用空間比序列幀動畫小
可以修改屬性,動態(tài)生成可交互的動畫(使用視頻動畫難以實現(xiàn)交互功能)
節(jié)省HMI的開發(fā)、調(diào)試時間
可以更輕松的實現(xiàn)粒子、光影等特效
Lottie的使用方法
在build.gradle中添加依賴
dependencies {
def lottieVersion = “5.2.0”
implementation com.airbnb.android:lottie:$lottieVersion
}
使用LottieAnimationView 首先將lottie動畫的json文件放在assets文件夾下
img
然后就可以在布局文件中使用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 設(shè)定lottie動畫所對應(yīng)的json文件地址。json文件默認(rèn)需要放置在assets下,設(shè)定時不需要再強調(diào)assets
app:lottie_fileName=“HamburgerArrow.json”
如果設(shè)定 app:lottie_fileName=”other/HamburgerArrow.json”,那么lottie就會讀取assets/other/HamburgerArrow.json。 void setAnimationFromJson(String jsonString, @Nullable String cacheKey)
lottie_rawRes 設(shè)定lottie動畫的json文件地址。json文件除了可以放置assets文件夾下,還可以放在raw文件夾下。使用時需要注意,利用lottie_rawRes引入資源時,json文件名前需要加上@raw,并且文件名不帶.json后綴
app:lottie_rawRes=“@raw/name”
imglottie_autoPlay 設(shè)定是否自動播放,取值為true | false
lottie_loop 設(shè)定是否循環(huán)播放,取值為true | false
lottie_url 當(dāng)需要加載在線資源時,就可以使用lottie_url void setAnimationFromUrl(String url) void setAnimationFromUrl(String url, @Nullable String cacheKey)
lottie_fallbackRes 設(shè)置一個drawable,如果lotticomposition由于任何原因未能加載,則將呈現(xiàn)該drawable。 如果這是網(wǎng)絡(luò)動畫,可以使用它向用戶顯示錯誤,也可以添加一個失敗的監(jiān)聽器重試下載。 void setFallbackResource(@DrawableRes int fallbackResource)
lottie_repeatMode 設(shè)定循環(huán)播放的順序。取值為restart | reverse 。restart表示正常循環(huán)播放,reverse表示倒序播放 void setRepeatMode(@LottieDrawable.RepeatMode int mode) int getRepeatMode()
lottie_repeatCount 設(shè)定循環(huán)播放次數(shù),取值為整數(shù)類型。 void setRepeatCount(int count) int getRepeatCount()
lottie_imageAssetsFolder 設(shè)定圖片文件在assets文件夾下的訪問路徑。有的時候使用AE導(dǎo)出lottie的json時也會導(dǎo)出一些圖片,這時候就需要該屬性設(shè)定圖片的地址。 void setImageAssetsFolder(String imageAssetsFolder) String getImageAssetsFolder()
void setFrame(int frame) 將進度設(shè)置為指定的幀。將進度設(shè)置為指定的幀。如果尚未設(shè)置合成,則進度將在設(shè)置時設(shè)置為幀。 通過int getFrame()可以獲取當(dāng)前渲染的幀。
void setMaxFrame(int endFrame) 設(shè)置播放或循環(huán)時動畫將結(jié)束的最大幀。 該值將被鉗制到合成邊界。例如,設(shè)置整數(shù)最大值將產(chǎn)生與合成相同的結(jié)果。 通過float getMaxFrame()可以獲取當(dāng)前設(shè)定的最大幀
void setMinFrame(int startFrame) 設(shè)置播放或循環(huán)時動畫開始的最小幀。 設(shè)定最大、最小幀可以只播放lottie動畫中的一部分,例如下面的兩張圖,第一張是完整的從0播放到183幀,第二張則是從60播放到100幀。
lottie_progress 設(shè)定動畫初次顯示時的進度,類型為float。取值范圍0.0 ~ 1.0 void setProgress(@FloatRange(from = 0f, to = 1f) float progress) float getProgress()
lottie_speed 設(shè)定播放速度,取值類型為float。當(dāng)速度<1時,動畫會慢放,當(dāng)速度<0時,可以實現(xiàn)倒序播放。 void setSpeed(float speed) float getSpeed() void reverseAnimationSpeed():反轉(zhuǎn)當(dāng)前動畫速度。這不會播放動畫。
速度是一個比較重要的屬性,與progress、frame等屬性一起靈活運用,我們就可以輕松地在HMI上實現(xiàn)炫酷而復(fù)雜的儀表盤效果,這對車載HMI尤為重要。
lottie_enableMergePathsForKitKatAndAbove 設(shè)定是否開啟MergePath屬性,取值為true | false。默認(rèn)為false void enableMergePathsForKitKatAndAbove(boolean enable) boolean isMergePathsEnabledForKitKatAndAbove()
void playAnimation() 從頭開始播放動畫。如果速度<0,它將從終點開始,并向起點播放。必須在主線程中調(diào)用。
void cancelAnimation() 取消動畫,必須在主線程中調(diào)用。
void pauseAnimation() 暫停動畫,必須在主線程中調(diào)用。
void resumeAnimation() 從當(dāng)前位置繼續(xù)播放動畫。如果速度<0,它將從當(dāng)前位置向后播放。必須在主線程中調(diào)用。
long getDuration() 獲取動畫的播放時長。
void setTextDelegate(TextDelegate textDelegate) 設(shè)置此選項可在運行時用自定義文本替換動畫文本
lottie_cacheComposition 設(shè)定是否開啟緩存,取值 true | false,默認(rèn)開啟。開啟緩存可以提升動畫的加載效率。
void setCacheComposition(boolean cacheComposition)
lottie_ignoreDisabledSystemAnimations 允許忽略系統(tǒng)動畫設(shè)置,因此即使禁用動畫,也允許運行動畫。取值 true | false,默認(rèn)為false。 void setIgnoreDisabledSystemAnimations(boolean ignore)lottie_clipToCompositionBounds 設(shè)置lottie是否應(yīng)剪輯到原始動畫合成邊界。設(shè)置為true時,父視圖可能需要禁用clipChildren,以便Lottie可以在LottieAnimationView邊界之外進行渲染。默認(rèn)為true。 void setClipToCompositionBounds(boolean clipToCompositionBounds)lottie_renderMode 設(shè)定渲染模式,取值為 automatic | hardware | software。設(shè)定渲染模式為hardware時,可以顯著提升動畫的渲染效率,但是有些系統(tǒng)函數(shù)可能并不支持硬件加速,實際使用時需要結(jié)合調(diào)試時的效果選擇是否開啟。 void setRenderMode(RenderMode renderMode) RenderMode getRenderMode()void addAnimatorListener(Animator.AnimatorListener listener) 添加動畫的屬性監(jiān)聽。 對應(yīng)也提供了removeUpdateListener(ValueAnimator.AnimatorUpdateListener updateListener)用來移除指定的監(jiān)聽?;蛘咭部梢允褂胷emoveAllAnimatorListeners()移除所有監(jiān)聽。
binding.animationView.addAnimatorUpdateListener(object : ValueAnimator.AnimatorUpdateListener {
override fun onAnimationUpdate(animation: ValueAnimator?) {
}
})
void addAnimatorPauseListener(Animator.AnimatorPauseListener listener) 添加動畫暫停/恢復(fù)監(jiān)聽。 對應(yīng)也提供了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)聽 對應(yīng)也提供了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
可以解析為多個內(nèi)容,在這種情況下,回調(diào)的值將應(yīng)用于所有回調(diào)。在內(nèi)部會首先檢查是否已使用resolveKeyPath(KeyPath)解析keypath,如果尚未解析,則將對其進行解析。
img
Lottie動畫的Json中屬性都是英文簡寫,我們很難把json中key與實際的屬性對應(yīng)起來,所以有了第二個參數(shù)
LottieProperty
,它的內(nèi)部定義了大量的屬性,當(dāng)我們需要修改json時,只需要傳入
LottieProperty
中屬性即可。
例如,需要監(jiān)聽json中
LeftArmWave
的持續(xù)時間,就可以這么寫
animationView.addValueCallback(KeyPath(“LeftArmWave”),LottieProperty.TIME_REMAP){frameInfo->
}
4.Lottie的常見用法
Lottie的Demo中內(nèi)置了很多官方自己開發(fā)的動畫效果,目的是為我們展示Lottie的常見用法,作為開發(fā)者我們必須掌握,并在適當(dāng)?shù)臅r候運用到我們的應(yīng)用中。
動態(tài)屬性效果
該效果展示了lottie支持動態(tài)修改json,讓動畫中的一小部分屬性發(fā)生改變。
修改局部動畫的速度
binding.animationView.addValueCallback(KeyPath(“LeftArmWave”),LottieProperty.TIME_REMAP){frameInfo->
2*speed.toFloat()*frameInfo.overallProgress}
KeyPath中的LeftArmWave是Json中的一個屬性
img
修改的效果如下。注意看右手的擺動頻率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
}
修改后的效果如下
動畫文字效果
img
該效果展示了動畫文字效果。這個效果實現(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)替換動畫中的文字。使用就可以在動畫運行中修改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,分別設(shè)定了不同的文字。
<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)思路相同,都是通過修改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)思路。
以上就是車載HMI—Lottie的一些講解。更多有關(guān)車載可以參考BYD高工整理出的《車載技術(shù)開發(fā)手冊》,市面上車載技術(shù)資料少之又少;希望這本電子手冊能夠幫助到你。
5.總結(jié)
在車載HMI開發(fā)中往往我們會在實現(xiàn)、調(diào)試UI上花費大量的時間,如果能夠靈活的運用Lottie,就可以顯著節(jié)省程序的開發(fā)時間。例如,光影、粒子等特效雖然可以也考慮用Kanzi等3D引擎實現(xiàn),但是3D引擎會消耗成倍的SOC性能,實際開發(fā)過程中,簡單的特效使用Lottie實現(xiàn),可以極大的優(yōu)化應(yīng)用的性能,給用戶一個更優(yōu)秀的體驗。
該效果展示通過監(jiān)聽點擊事件來播放不同的lottie動畫。這個效果最常見,APP中的點贊效果大多都是這樣的實現(xiàn)思路。
轉(zhuǎn)載:https://www.jianshu.com/p/2388c72d5aff
以上就是車載HMI—Lottie的一些講解。更多有關(guān)車載可以參考BYD高工整理出的《車載技術(shù)開發(fā)手冊》,市面上車載技術(shù)資料少之又少;希望這本電子手冊能夠幫助到你。