LottieAndroid使用詳解及源碼解析,讓你的應用加載動畫變得輕而易舉。
看源碼的時候跟著我最下面的兩張時序圖慢慢走,一遍看不懂就再看一遍,別著急,多看幾遍就能懂了。雖然這里不是用的最新框架源碼,但是基本原理都是一致的,不影響你使用最新版本時候遇到問題,然后定位解決。
看懂源碼有什么用?當你使用這個框架遇到一些奇怪的Bug的時候,有時候不一定是你的問題,可能是源碼中的問題,追蹤源碼有利于你快速定位問題。我在使用的時候遇到過源碼的問題,后來在GitHub的issue中看到有人提了,再后來的版本就修復了。
為了寫好這篇文章,我花了一周多的時間,因為是給公司做分享,所以很多細節(jié)我是講出來的,并沒有寫出來,因為要寫的話會很多,但這篇文章基本上完全可以滿足你對Lottie的使用和對源碼的理解,接下來耐心看吧。
我們主要從以下四個方面來講解:
一、Lottie簡介
二、LottieAndroid的使用
三、LottieAndroid源碼解析
四、可能遇到的問題會有哪些
一、Lottie簡介
-
Lottie是什么?
Lottie是Airbnb開源的一個動畫渲染庫,同時支持Android、IOS、React Native和Web平臺,Lottie目前只支持渲染播放After Effects動畫。Lottie使用bobymovin(After Effects插件)到處的json數據作為動畫數據源。使用Lottie可以讓動畫顯示變得簡單方便。
-
從動畫制作到動畫顯示流程如下:
-
工作流程:
- 設計師使用Affer Effects制作動畫并導出json文件,參考:Lottie動畫社區(qū)
- 各端開發(fā)使用相應的LottieSDK實現動畫效果,GitHub下載地址:Lottie Android,Lottie IOS,React Native,Web
-
注意事項:
設計師同學制作各個平臺(Android、IOS、React Netive)動畫時需要查看Lottie在不同平臺支持的特性,否則制作出來的動畫顯示可能會有問題,設計同學制作動畫參考:不同平臺Lottie支持特性
-
為什么要使用Lottie?
-
先看看在沒有Lottie之前我們是怎么實現相對復雜動畫的:
- 使用GIF,占用空間大,有些動畫顯示效果不佳,需要適配分辨率,Android原生不支持GIF動畫的顯示。
- 使用幀動畫,占用空間大,依然會遇到不同分辨率適配的問題。
- 組合式動畫,通過大量代碼實現復雜的動畫效果。
-
使用Lottie可以解決的問題:
- 降低動畫設計和開發(fā)成本
- 解決設計提供動畫效果與實現不一致問題
- 占用空間更小
- 不同的手機分辨率不需要適配
-
-
Lottie適用于哪些場景?
- 啟動(splash)動畫:典型場景是APP logo動畫的播放
- 上下拉刷新(refresh)動畫:所有APP都必備的功能,利用 Lottie 可以做的更加簡單酷炫了
- 加載(loading)動畫:典型場景是網絡請求的loading動畫
- 提示(tips)動畫:典型場景是空白頁的提示
- 按鈕(button)動畫:典型場景如switch按鈕、編輯按鈕、播放按鈕等按鈕的
- 禮物(gift)動畫:典型場景是直播類APP的高級動畫播放
-
我們想要使用Lottie替代哪些動畫?
- 首先并不是在APP中所有的動畫都要用Lottie來替換
- 一些可以通過屬性動畫來實現的簡單動畫就不需要用Lottie來實現了
- 替代一些通過代碼不好實現的動畫效果
- 替代GIF動畫和幀動畫
二、LottieAndroid的使用
-
集成到項目中(以2.2.0版本為例)
-
添加依賴:compile 'com.airbnb.android:lottie:2.2.0'
Lottie版本號參考:Maven庫查看Lottie各版本號
-
Gradle依賴修改:
最低版本:MIN_SDK_VERSION = 16
編譯版本:COMPILE_SDK_VERSION = 25
所有的兼容包需要升級到版本號為25.3.1compile 'com.android.support:appcompat-v7:25.3.1' compile 'com.android.support:cardview-v7:25.3.1' compile 'com.android.support:design:25.3.1' compile 'com.android.support:recyclerview-v7:25.3.1' compile 'com.android.support:palette-v7:25.3.1' compile 'com.android.support:support-v4:25.3.1' -
如何查看Lottie各版本需要對應的AndroidSDK編譯版本和兼容包版本號?
以Lottie2.2.0版本為例:mvnrepository lottie2.2.0
-
-
使用方法
-
Lottie基本用法查看官方文檔
-
首先將json文件放到assets文件夾下:
可以直接放到assets目錄下,或者在assets目錄下創(chuàng)建一個二級目錄放在二級目錄下。 -
在布局中添加LottieAnimationView控件:
LottieAnimationView可以設置的屬性如下:<com.airbnb.lottie.LottieAnimationView android:id="@+id/animation_view" android:layout_width="wrap_content" android:layout_height="wrap_content" app:lottie_fileName="hello-world.json" app:lottie_loop="true" app:lottie_autoPlay="true" /> 如上圖,如果json文件在assets子文件夾中,lottie_fileName="lottieani/stars.json" -
得到LottieAnimationView對象進行動畫操作:
setAnimation()有三種方法,可以直接設置動畫的Json對象,或者設置Json文件相對路徑名,且支持設置緩存類型:LottieAnimationView animationView = (LottieAnimationView)findViewById(R.id.animation_view); // 布局中不指定文件可以在此設置,路徑設置同布局文件 animationView.setAnimation("hello-world.json"); // 是否循環(huán)播放 animationView.loop(true); // 設置播放速率,例如:2代表播放速率是不設置時的二倍 animationView.setSpeed(2f); // 開始播放 animationView.playAnimation(); // 暫停播放 animationView.pauseAnimation(); // 取消播放 animationVIew.cancelAnimation(); // 設置播放進度 animationView.setProgress(0.5f); // 判斷是否正在播放 animationView.isAnimating();playAnimation()有三種方法:支持三種緩存類型,不緩存、弱類型和強類型: -
添加動畫監(jiān)聽
mAnimationView.addAnimatorListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { Log.d(TAG, "onAnimationStart : " + animation.getDuration()); } @Override public void onAnimationEnd(Animator animation) { Log.d(TAG, "onAnimationEnd"); } @Override public void onAnimationCancel(Animator animation) { Log.d(TAG, "onAnimationCancel"); } @Override public void onAnimationRepeat(Animator animation) { Log.d(TAG, "onAnimationRepeat"); } }); mAnimationView.addAnimatorUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { //Log.d(TAG, "onAnimationUpdate : " + animation.getCurrentPlayTime()); } });
-
-
其他用法
-
自定義動畫的速率和時長
ValueAnimator valueAnimator = ValueAnimator .ofFloat(0f, 1f) .setDuration(5000); valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mAnimationView.setProgress((Float) animation.getAnimatedValue()); } }); valueAnimator.start(); -
給整個動畫添加一個特定圖層,或者一個圖層的特定內容添加一個顏色過濾器
// 任何符合顏色過濾界面的類 final PorterDuffColorFilter colorFilter = new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.LIGHTEN); // 在整個視圖中添加一個顏色過濾器 animationView.addColorFilter(colorFilter); //在特定的圖層中添加一個顏色濾鏡 animationView.addColorFilterToLayer("hello_layer", colorFilter); // 添加一個彩色過濾器特效“hello_layer”上的內容 animationView.addColorFilterToContent("hello_layer", "hello", colorFilter); // 清除所有的顏色濾鏡 animationView.clearColorFilters(); -
在列表中添加動畫,每個item循環(huán)播放該動畫
如果按照下面寫法會有什么問題?如果按照下面寫法,即便是已經設置了lottieViewLive.loop(true)循環(huán)播放,item被劃走再劃回來動畫依舊會停止播放。
<com.airbnb.lottie.LottieAnimationView android:id="@+id/lottieViewLive" android:layout_width="30dp" android:layout_height="30dp"/> // 在viewHolder中initView的時候開始動畫 LottieAnimationView lottieViewLive = (LottieAnimationView) layout.findViewById(R.id.lottieViewLive); lottieViewLive.setAnimation("lottiejson/living.json"); // 循環(huán)播放動畫 lottieViewLive.loop(true); lottieViewLive.playAnimation();為了解決上訴問題,只需要在布局中添加,屬性app:lottie_autoPlay="true";可以直接按照寫法在xml布局中添加動畫即可。為什么會這樣?待會在源碼解析部分講解。
<com.airbnb.lottie.LottieAnimationView android:id="@+id/lottieViewLive" android:layout_width="30dp" android:layout_height="30dp" app:lottie_autoPlay="true" app:lottie_fileName="lottiejson/living.json" app:lottie_loop="true"/> -
從網絡直接獲取json數據顯示動畫(Lottie本身不提供網絡請求)
為什么要使用下面方式,而不是從網絡獲取json數據后直接animationView.setAnimation(json)。主要是為了使用OnCompositionLoadedListener,防止從網絡獲取的json數據錯誤解析出來的composition為null。
LottieComposition.Factory.fromJson(getResources(), jsonObject, new OnCompositionLoadedListener() { @Override public void onCompositionLoaded(@Nullable LottieComposition composition) { if (composition != null) { mAnimationView.setComposition(composition); mAnimationView.playAnimation(); } else { // showErrorView(); } } }); -
加載含有圖片的復雜文件
有些動畫比較復雜不是簡單的線條、圖塊所能實現的,這樣的動畫通過AE插件bobymovin生成的文件除了有一個json文件外,還會有一些png圖片。如何加載這樣的動畫呢?
① 加載本地含有圖片的動畫資源,json文件放在assets文件夾下,生成的png圖片同樣放在assets文件夾下(或者自己在assets文件夾下創(chuàng)建一個子目錄,假如叫l(wèi)ottieimage)。
② 加載從網絡獲取的含有圖片的動畫資源,首先需要將資源下載到手機SD卡中// 代碼這樣寫,需要設置圖片的文件夾地址 mAnimationView.setImageAssetsFolder("lottieweaccept/images"); mAnimationView.setAnimation("lottieweaccept/WeAccept.json"); mAnimationView.playAnimation(); // 或者在布局文件中添加這句代碼,指定圖片路徑 app:lottie_imageAssetsFolder="lottieweaccept/images"final String absolutePath = imagesDir.getAbsolutePath(); mAnimationView.setImageAssetDelegate(new ImageAssetDelegate() { @Override public Bitmap fetchBitmap(LottieImageAsset asset) { return BitmapFactory.decodeFile(absolutePath + File.separator + asset.getFileName()); } }); LottieComposition.Factory.fromInputStream(LottieViewActivity.this, inputStream, new OnCompositionLoadedListener() { @Override public void onCompositionLoaded(@Nullable LottieComposition composition) { if (composition != null) { mAnimationView.setComposition(composition); mAnimationView.playAnimation(); } else { // doOtherThing(); } } });
-
-
三、源碼解析
-
從Json到動畫顯示的實現思路
將復雜的圖片使用圖層表示,每一層表示不同的內容
-
根據動畫需求可以只針對某一層做相應的平移、旋轉、縮放等動畫
-
Json文件中數據轉成LottieComposition數據對象,LottieDrawable負責將數據繪制成drawable,LottieAnimationView負責將LottieDrawable顯示出來。LottieAnimationView繼承自AppCompatImageView,LottieDrawable繼承自Drawable。
-
先看看生成的Json數據,參考這里:Lottie:讓動畫如此簡單
-
如何加載json數據并顯示圖像的?
-
animationView.setAnimation("hello-world.json");
通過setAnimation()來看看,上面json數據到顯示圖像的過程。源碼時序圖如下(以2.2.0版本為例):LottieAnimationView初始化的時候會首先創(chuàng)建LottieDrawable對象,private final LottieDrawable lottieDrawable = new LottieDrawable()。init(),進行初始化的時候,解析xml設置的屬性。
setAnimation(String fileName),加載Json文件。
通過異步加載,最終會調用到fromJsonSync()對Json文件進行解析。
解析結果通過onCompositionLoaded()回調到主線程,然后會將Json數據轉換成LottieComposition對象。
lottieDrawable.setComposition(),將LottieComposition對象設置給LottieDrawable。
生成CompositionLayer對象,這個對象就是每一個圖層的對象。
通過setImageDrawable(lottieDrawable)將圖像顯示出來,顯示第一幀動畫。
-
-
動畫如何運行起來的?
-
animationView.playAnimation();
調用playAnimation()動畫是如何動起來的,源碼時序圖如下(以2.2.0版本為例):ValueAnimator實現的控制動畫進度。
setProgress實現的顯示具體進度動畫。
LottieDrawable中的draw繪制圖像,Canvas進行繪制。
-
-
為什么在列表中加載動畫時設置了循環(huán)播放,item劃走再劃回來動畫還是會停止播放?為什么添加app:lottie_autoPlay="true"才行?
-
我們來看看這兩句代碼,有沒有什么問題?
setAnimation是異步加載json文件的,調用setAnimation之后直接調用playAnimation()源碼是如何保證加載完數據之后再開始動畫呢?最終會走到LottieDrawable類中,如下:// 異步加載json文件 animationView.setAnimation("hello-world.json"); animationView.playAnimation(); animationView.setSpeed(2f);
性能問題
-
性能和內存
- 如果沒有mask和mattes,那么性能和內存非常好,沒有bitmap創(chuàng)建,大部分操作都是簡單的cavas繪制。
- 如果存在mattes,將會創(chuàng)建2~3個bitmap。bitmap在動畫加載到window時被創(chuàng)建,被window刪除時回收。所以不宜在RecyclerView中使用包涵mattes或者mask的動畫,否則會引起bitmap抖動。除了內存抖動,mattes和mask中必要的bitmap.eraseColor()和canvas.drawBitmap()也會降低動畫性能。對于簡單的動畫,在實際使用時性能不太明顯。
- 如果在列表中使用動畫,推薦使用緩存LottieAnimationView.setAnimation(String, CacheStrategy) 。
全屏適配參考
更新:添加2.5.1版本時遇到的問題
1、 編譯版本需要 27
2、所有的suport包版本需要27.1.0
compile 'com.android.support:appcompat-v7:27.1.0'
3、如果有引入lifecycle,版本需要1.1.0
android.arch.lifecycle:extensions:1.1.0
android.arch.lifecycle:compiler:1.1.0
如果項目中引入了livedata并且lifecycle版本號不對,會報如下錯誤:
Error:Program type already present: android.arch.lifecycle.LiveData
參考:https://stackoverflow.com/questions/49056723/errorprogram-type-already-present-android-arch-lifecycle-livedata
com.firebaseui:firebase-ui-firestore:3.1.0 depends on android.arch.lifecycle:extensions:1.0.0-beta1. Switching to version 3.2.2 fixes the issue by using the Lifecycle 1.1 libraries that Support Library 27.1.0 are built upon.