引言
上周周會了解到Lottie這個開源項目,用json文件控制播放動畫缎玫,跟我之前做主題,用cocos-2dx引擎做游戲動畫思想有些類似努释,當時我就想體驗一下這個項目碘梢。
初識Lottie
首先看一組效果圖:
感覺上面這個效果挺贊的,通過動作分解伐蒂,也能用Android提供的位移煞躬、旋轉(zhuǎn)、透明動畫組合,配合一些Interpolator(加速恩沛、減速在扰、彈跳)來實現(xiàn);但是下面這個用上面的方法進行動畫控制就太麻煩了(從效果看出雷客,下面的圖形都不是通過圖片控制的芒珠,全部都是通過path繪制的),當前也可以通過gif動畫來播放搅裙,不過gif動畫在低端手機可能會出現(xiàn)不流暢現(xiàn)象皱卓,再者就是gif圖片size較大,對包體大小有限制的App可能就不能接受:
Lottie介紹
介紹
Today, we’re happy to introduce our solution. Lottie is an iOS, Android, and React Native library that renders After Effects animations in real time, and allows native apps to use animations as easily as they use static assets. Lottie uses animation data exported as JSON files from an open-source After Effects extension called Bodymovin. The extension is bundled with a JavaScript player that can render the animations on the web. Since February of 2015, Bodymovin’s creator, Hernan Torrisi, has built a solid foundation by adding features and improvements to the plugin on a monthly basis. Our team (Brandon Withrow on iOS, Gabriel Peal on Android, Leland Richardson on React Native, and I on experience design) began our journey by building on top of Torrisi’s phenomenal work.
就是說Lottie支持Android部逮、iOS娜汁、React Native三個平臺,支持實時渲染After Effects動畫兄朋,使得app中使用動畫可以像使用靜態(tài)資源一樣簡單掐禁。 Lottie使用從Bodymovin(開源的After Effects插件)導(dǎo)出的json數(shù)據(jù)來作為動畫數(shù)據(jù)。
源碼分析
github上下載lottie-android源碼颅和,導(dǎo)入AS傅事,容易找到加載本地的動畫json文件入口方法:
void onLoadAssetClicked() {
animationView.cancelAnimation();
android.support.v4.app.DialogFragment assetFragment = ChooseAssetDialogFragment.newInstance();
assetFragment.setTargetFragment(this, RC_ASSET);
assetFragment.show(getFragmentManager(), "assets");
}
Lottie支持從本地加載json文件來實現(xiàn),也支持從網(wǎng)絡(luò)加載json格式的動畫數(shù)據(jù)來實現(xiàn)播放動畫(原理一樣)峡扩。加載完成后蹭越,會通過下面函數(shù)對動畫進行處理:
@Override public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode != Activity.RESULT_OK) {
return;
}
switch (requestCode) {
case RC_ASSET:
final String assetName = data.getStringExtra(EXTRA_ANIMATION_NAME);
animationView.setImageAssetsFolder(assetFolders.get(assetName));
LottieComposition.Factory.fromAssetFileName(getContext(), assetName,
new OnCompositionLoadedListener() {
@Override
public void onCompositionLoaded(LottieComposition composition) {
setComposition(composition, assetName);
}
});
break;
case RC_FILE:
onFileLoaded(data.getData());
break;
case RC_URL:
break;
}
}
其中最核心的方法就是setComposition(composition, assetName),這個方法最核心的一句是animationView.setComposition(composition)教届;下面看一下這個方法的實現(xiàn):
/**
* Sets a composition.
* You can set a default cache strategy if this view was inflated with xml by
* using {@link R.attr#lottie_cacheStrategy}.
*/
public void setComposition(@NonNull LottieComposition composition) {
if (L.DBG) {
Log.v(TAG, "Set Composition \n" + composition);
}
lottieDrawable.setCallback(this);
boolean isNewComposition = lottieDrawable.setComposition(composition);
if (!isNewComposition) {
// We can avoid re-setting the drawable, and invalidating the view, since the composition
// hasn't changed.
return;
}
int screenWidth = Utils.getScreenWidth(getContext());
int screenHeight = Utils.getScreenHeight(getContext());
int compWidth = composition.getBounds().width();
int compHeight = composition.getBounds().height();
if (compWidth > screenWidth ||
compHeight > screenHeight) {
float xScale = screenWidth / (float) compWidth;
float yScale = screenHeight / (float) compHeight;
setScale(Math.min(xScale, yScale));
Log.w(L.TAG, String.format(
"Composition larger than the screen %dx%d vs %dx%d. Scaling down.",
compWidth, compHeight, screenWidth, screenHeight));
}
// If you set a different composition on the view, the bounds will not update unless
// the drawable is different than the original.
setImageDrawable(null);
setImageDrawable(lottieDrawable);
this.composition = composition;
requestLayout();
}
可以看到般又,通過composition的設(shè)置和請求requestLayout刷新界面,即可實現(xiàn)動畫的繪制巍佑,那么究竟composition和lottieDrawable是什么關(guān)系?以及他們是怎么實現(xiàn)動畫的繪制的寄悯?下面通過類圖來表達這一它們之間的關(guān)系:
看起來比較復(fù)雜的動畫邏輯萤衰,其實實現(xiàn)起來并不是特別復(fù)雜,他們之間的關(guān)系很清晰:LottieAnimationView繼承AppCompatImageView猜旬,即可以當做一個增強版的ImageView來使用脆栋,只不過它里面包含了兩個核心成員:LottieDrawable和LottieComposition,所有的動畫播放和完成洒擦,都是通過控制這兩個核心成員來完成的椿争。
LottieComposition
負責解析Effects/Bodymovin產(chǎn)生的json動畫文件,并將其按照功能分類保存起來熟嫩∏刈伲可以這樣理解:LottieComposition就是動畫json數(shù)據(jù)的對象模型,通過LottieComposition,實現(xiàn)對動畫的分解和序列化椅邓,json動畫文件示例:
LottieDrawable
LottieDrawable就是根據(jù)LottieComposition保存的分層和序列動作柠逞,不停的進行繪制,實現(xiàn)連續(xù)動畫效果景馁,其核心邏輯如下:
@Override public void draw(@NonNull Canvas canvas) {
if (compositionLayer == null) {
return;
}
matrix.reset();
matrix.preScale(scale, scale);
compositionLayer.draw(canvas, matrix, alpha);
}
真正的實現(xiàn)在CompositionLayer的draw函數(shù)板壮,即:
@Override
public void draw(Canvas canvas, Matrix parentMatrix, int parentAlpha) {
if (!visible) {
return;
}
buildParentLayerListIfNeeded();
matrix.reset();
matrix.set(parentMatrix);
for (int i = parentLayers.size() - 1; i >= 0; i--) {
matrix.preConcat(parentLayers.get(i).transform.getMatrix());
}
int alpha = (int)
((parentAlpha / 255f * (float) transform.getOpacity().getValue() / 100f) * 255);
if (!hasMatteOnThisLayer() && !hasMasksOnThisLayer()) {
matrix.preConcat(transform.getMatrix());
drawLayer(canvas, matrix, alpha);
return;
}
rect.set(0, 0, 0, 0);
getBounds(rect, matrix);
intersectBoundsWithMatte(rect, matrix);
matrix.preConcat(transform.getMatrix());
intersectBoundsWithMask(rect, matrix);
rect.set(0, 0, canvas.getWidth(), canvas.getHeight());
canvas.saveLayer(rect, contentPaint, Canvas.ALL_SAVE_FLAG);
// Clear the off screen buffer. This is necessary for some phones.
clearCanvas(canvas);
drawLayer(canvas, matrix, alpha);
if (hasMasksOnThisLayer()) {
applyMasks(canvas, matrix);
}
if (hasMatteOnThisLayer()) {
canvas.saveLayer(rect, mattePaint, SAVE_FLAGS);
clearCanvas(canvas);
//noinspection ConstantConditions
matteLayer.draw(canvas, parentMatrix, alpha);
canvas.restore();
}
canvas.restore();
}
小結(jié)
真心覺得這個項目實在是太贊了,倒不是說這個項目在技術(shù)上有多高深合住,而是這個項目把軟件開發(fā)(或者說是編碼實現(xiàn)需求)的方法想得很極致:不是通過復(fù)雜的組合動畫和波動函數(shù)來控制绰精,而是通過對數(shù)據(jù)建模,通過設(shè)計師導(dǎo)出的數(shù)據(jù)透葛,在不同平臺上還原出效果笨使,完成設(shè)計稿與實現(xiàn)效果的完全還原(如果是通過系統(tǒng)動畫,很多情況下難以完全符合設(shè)計師的動畫要求)
- 有了lottie获洲,設(shè)計師再也不用擔心開發(fā)不能達到預(yù)期效果了阱表,因為壓根不需要額外開發(fā)
- 解放了開發(fā),啟迪了思想贡珊,我還需要學(xué)習一個最爬!
參考文章
https://github.com/airbnb/lottie-android
https://medium.com/airbnb-engineerin
http://www.reibang.com/p/d887c96684be