RE: 從零開始的車載Android HMI(一) - Lottie

1.前言

多年以前汽車還是以機械儀表主體的年代,各大汽車主機廠商并不十分關注操作系統(tǒng)UI的交互功能妨退,但是隨著車載SOC算力的不斷提高以及主機廠商對汽車座艙競爭的白熱化。座艙的HMI在設計上在強調功能性的同時也開始關注UI的藝術性子寓,HMI的設計師們期望藝術與功能應該協(xié)同工作梁厉,讓用戶沉浸在“第三空間”的體驗中辜羊。

有了需求程序員就需要關注如何實施和落地,然而Android應用本身雖然有著完整的動畫框架支持词顾,但是開發(fā)復雜八秃、調試耗時,大型的gif或逐幀動畫對于CPU&內存占用都不太理想肉盹,所以許多Android的手機應用基本上不怎么有動畫昔驱。而且車載HMI上越來越多的開始引入各種光影、粒子效果上忍,如果基于Android的原生控件來實現(xiàn)這些粒子效果骤肛,難度非常大,這就需要今天的主角Lottie來實現(xiàn)了窍蓝。

image

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的使用方法

  1. 在build.gradle中添加依賴
dependencies {
  def lottieVersion = "5.2.0"
  implementation 'com.airbnb.android:lottie:$lottieVersion'
}

  1. 使用LottieAnimationView 首先將lottie動畫的json文件放在assets文件夾下
image

然后就可以在布局文件中使用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就可以看到動畫效果

image

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"

image
  • 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幀礁竞。

image
image
  • 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():反轉當前動畫速度岖食。這不會播放動畫。

image

速度是一個比較重要的屬性珍昨,與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窗慎,如果尚未解析物喷,則將對其進行解析。

image

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ā)生改變闸溃。

  1. 修改局部動畫的速度
binding.animationView.addValueCallback(KeyPath("LeftArmWave"), LottieProperty.TIME_REMAP) { frameInfo ->
2 * speed.toFloat() * frameInfo.overallProgress
}

KeyPath中的LeftArmWave是Json中的一個屬性

image

修改的效果如下整吆。注意看右手的擺動頻率X3后比X1高,以至于錄制的GIF直接丟幀了辉川。

image
  1. 修改局部動畫的顏色
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] } 

修改后的效果如下:

image
  1. 修改局部動畫的運動范圍
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

動畫文字效果

[圖片上傳失敗...(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)文字效果

image

該效果展示動態(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>

手勢交互效果

image

該效果展示了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中使用

image

該效果展示通過監(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)系刪除。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末甚侣,一起剝皮案震驚了整個濱河市明吩,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌殷费,老刑警劉巖印荔,帶你破解...
    沈念sama閱讀 212,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異详羡,居然都是意外死亡躏鱼,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評論 3 385
  • 文/潘曉璐 我一進店門殷绍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人鹊漠,你說我怎么就攤上這事主到。” “怎么了躯概?”我有些...
    開封第一講書人閱讀 158,369評論 0 348
  • 文/不壞的土叔 我叫張陵登钥,是天一觀的道長。 經(jīng)常有香客問我娶靡,道長牧牢,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,799評論 1 285
  • 正文 為了忘掉前任姿锭,我火速辦了婚禮塔鳍,結果婚禮上,老公的妹妹穿的比我還像新娘呻此。我一直安慰自己轮纫,他們只是感情好,可當我...
    茶點故事閱讀 65,910評論 6 386
  • 文/花漫 我一把揭開白布焚鲜。 她就那樣靜靜地躺著掌唾,像睡著了一般放前。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上糯彬,一...
    開封第一講書人閱讀 50,096評論 1 291
  • 那天凭语,我揣著相機與錄音,去河邊找鬼撩扒。 笑死似扔,一個胖子當著我的面吹牛,可吹牛的內容都是我干的却舀。 我是一名探鬼主播虫几,決...
    沈念sama閱讀 39,159評論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼挽拔!你這毒婦竟也來了辆脸?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,917評論 0 268
  • 序言:老撾萬榮一對情侶失蹤螃诅,失蹤者是張志新(化名)和其女友劉穎啡氢,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體术裸,經(jīng)...
    沈念sama閱讀 44,360評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡倘是,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,673評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了袭艺。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片搀崭。...
    茶點故事閱讀 38,814評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖猾编,靈堂內的尸體忽然破棺而出瘤睹,到底是詐尸還是另有隱情,我是刑警寧澤答倡,帶...
    沈念sama閱讀 34,509評論 4 334
  • 正文 年R本政府宣布轰传,位于F島的核電站,受9級特大地震影響瘪撇,放射性物質發(fā)生泄漏获茬。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,156評論 3 317
  • 文/蒙蒙 一倔既、第九天 我趴在偏房一處隱蔽的房頂上張望恕曲。 院中可真熱鬧,春花似錦渤涌、人聲如沸码俩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽稿存。三九已至笨篷,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間瓣履,已是汗流浹背率翅。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留袖迎,地道東北人冕臭。 一個月前我還...
    沈念sama閱讀 46,641評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像燕锥,于是被迫代替她去往敵國和親辜贵。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,728評論 2 351

推薦閱讀更多精彩內容