Github上的Lottie
LottieComposition
public ArrayList<String> getWarnings() {
return new ArrayList<>(Arrays.asList(warnings.toArray(new String[warnings.size()])));
}
这个方法通过HashSet<String>转换为ArrayList来获取warnings保存的一些报错日志。其中Array做为其中的转换桥梁。
Layer layerModelForId(long id) {
return layerMap.get(id);
}
Layer主要是AE的图层概念转换,以Model的形式保存在layerMap。其中layerMap的数据来源于AE导出的json解析出来的layers数组。每个Layer具有对应的id,这个方法就是通过id找到对应的Layer。在LottieComposition这个类里面有四个数据集合,分为precomps、images、layerMap、layers。它们获取数据简单流程图如下

其中
fromAssetFileName(Context context, String fileName,OnCompositionLoadedListener loadedListener)fromFileSync(Context context, String fileName)fromJson(Resources res, JSONObject json,OnCompositionLoadedListener loadedListener)fromInputStream(Context context, InputStream stream,OnCompositionLoadedListener loadedListener)fromInputStream(Resources res, InputStream stream)fromJsonSync(Resources res, JSONObject json)
我们可以通过上面的任何一个方法设置要做动画的json文件
LottieComposition其他的一些属性,会在fromJsonSync从json格式里面读取它们对应的初始值
private final Rect bounds; //绘制动画的范围
private final long startFrame; // 开始帧数
private final long endFrame; // 结束帧数
private final int frameRate; //帧数率
private final float dpScale; //缩放大小
LottieDrawable
自定义的一个Drawable,主要是管控动画的start、resume、pause、reverse等等,以及创建compositionLayer,为绘制动画的图层做准备,简单逻辑如下

在我们使用的动画的fragment里面可以调用LottieAnimationView的resumeAnimation()、pasureAnimation()、reverseAnimation()等方法来控制动画的状态。然后然后通过LottieAnimationView分配到创建的LottieDrawable里面的对应的各个方法,然后对动画进行各种状态处理。LottieDrawable大概代码如下
private final ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
public LottieDrawable() {
animator.setRepeatCount(0);
animator.setInterpolator(new LinearInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override public void onAnimationUpdate(ValueAnimator animation) {
if (systemAnimationsAreDisabled) {
animator.cancel();
setProgress(1f);
} else {
setProgress((float) animation.getAnimatedValue());
}
}
});
}
public void resumeAnimation() {
playAnimation(true);
}
private void playAnimation(boolean setStartTime) {
...
long playTime = setStartTime ? (long) (progress * animator.getDuration()) : 0;
animator.start();
if (setStartTime) {
animator.setCurrentPlayTime(playTime);
}
}
public void cancelAnimation() {
...
animator.cancel();
}
private void reverseAnimation(boolean setStartTime) {
...
if (setStartTime) {
animator.setCurrentPlayTime((long) (progress * animator.getDuration()));
}
animator.reverse();
}
同时LottieDrawable也会在LottieAnimationView调用的setComposition()方法里面创建一个CompositionLayer对象,通过这个对象实现动画图层的各种绘制。大概代码如下
public boolean setComposition(LottieComposition composition) {
...
buildCompositionLayer();
applyColorFilters();
setProgress(progress);
...
return true;
}
private void buildCompositionLayer() {
compositionLayer = new CompositionLayer(
this, Layer.Factory.newInstance(composition), composition.getLayers(), composition);
}
然后动画的执行后,会在动画的AnimatorUpdateListener回调中调用compositionLayer的setProgress()方法,从而开始执行动画。
Layer
通过上文setProgress()方法的跟踪,最终会发现在BaseKeyframeAnimation类里面可以找到AnimationListener接口的onValueChanged()的调用
void setProgress(@FloatRange(from = 0f, to = 1f) float progress) {
...
for (int i = 0; i < listeners.size(); i++) {
listeners.get(i).onValueChanged();
}
}
而BaseKeyframeAnimation.AnimationListener接口,可以在BaseLayer里面有实现
@Override public void onValueChanged() {
invalidateSelf();
}
private void invalidateSelf() {
lottieDrawable.invalidateSelf();
}
通过invalidateSelf()方法的调用,LottieDrawable的draw()方法也得到不断的执行,从而驱使BaseLayer的draw()方法也得到执行,draw()方法如下
@Override
public void draw(Canvas canvas, Matrix parentMatrix, int parentAlpha) {
...
drawLayer(canvas, matrix, alpha);
...
}
通过drawLayer()这个抽象方法,实现各种不同的Layer效果,主要以下几个Layer

其中会在CompositionLayer构造函数里面初始化不同的Layer
CompositionLayer(LottieDrawable lottieDrawable, Layer layerModel, List<Layer> layerModels,
LottieComposition composition) {
super(lottieDrawable, layerModel);
...
for (int i = layerModels.size() - 1; i >= 0; i--) {
Layer lm = layerModels.get(i);
BaseLayer layer = BaseLayer.forModel(lm, lottieDrawable, composition);
...
}
...
}
BaseLayer.java
根据不同的LayerType绘制不同的图层
@Nullable
static BaseLayer forModel(
Layer layerModel, LottieDrawable drawable, LottieComposition composition) {
switch (layerModel.getLayerType()) {
case Shape:
return new ShapeLayer(drawable, layerModel);
case PreComp:
return new CompositionLayer(drawable, layerModel,
composition.getPrecomps(layerModel.getRefId()), composition);
case Solid:
return new SolidLayer(drawable, layerModel);
case Image:
return new ImageLayer(drawable, layerModel, composition.getDpScale());
case Null:
return new NullLayer(drawable, layerModel);
case Text:
case Unknown:
default:
// Do nothing
Log.w(L.TAG, "Unknown layer type " + layerModel.getLayerType());
return null;
}
}
而LayerType在Layer里面赋值
static Layer newInstance(JSONObject json, LottieComposition composition) {
....
LayerType layerType;
int layerTypeInt = json.optInt("ty", -1);
if (layerTypeInt < LayerType.Unknown.ordinal()) {
layerType = LayerType.values()[layerTypeInt];
} else {
layerType = LayerType.Unknown;
}
....
}
在BaseLayer.java里面
@Override public void onValueChanged() {
invalidateSelf();
}
通过实现接口BaseKeyframeAnimation.AnimationListener,不断重绘,实现动画
从而调用draw()方法
@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();
}
Lottie的原理
首先会通过LottieComposition.Factory的对应类型设置 json资源文件,然后再fromJsonSync方法里面会把json文件解析出图层的大小并且绘制相应的图片资源文件和图层。资源加载完后,会在回调里面设置LottieAnimationView的Composition,从而调用LottieDrawable的setComposition()方法,在setComposition方法里面会通过buildCompositionLayer()方法去创建一个CompositionLayer图层,其中CompositionLayer继承BaseLayer,通过BaseLayer的forModel()静态方法获取不同的图层类型,然后LottieDrawable的setComposition()方法里面会开始执行一个ValueAnimation动画,这个动画会驱使BaseLayer的draw()方法不断执行,通过Matrix的矩阵形式不断的绘制各个图层从而形成动画,而这些图层的矩阵变换的数据来源于BaseKeyframeAnimation里面有一个Keyframe对象会去Json里面获取相应得数据。
简单使用
- 方式1
fun createFromAssetFile(lottieAnimView: LottieAnimationView, fileName: String, init: (LottieAnimationView.() -> Unit)?) {
lottieAnimView.setAnimation(fileName)
if(init!=null)
lottieAnimView.init()
lottieAnimView.playAnimation()
}
直接传入assets里面的.json文件名字,然后LottieComposition通过fromInputStream()等方法读取文件转换为string类型,从而得到JsonObject对象,最后在fromJsonSync()方法里面绘制图层
- 方式2
private val assetFolders = object : HashMap<String, String>() {
init {
put("WeAccept.json", "Images/WeAccept")
}
}
fun createFromAssetFile(context: Context,lottieAnimView: LottieAnimationView,
fileName: String, init: (LottieAnimationView.() -> Unit)?) {
lottieAnimView.imageAssetsFolder = assetFolders[fileName]
LottieComposition.Factory.fromAssetFileName(context,fileName) { composition ->
lottieAnimView.setComposition(composition!!)
if(init!=null)
lottieAnimView.init()
lottieAnimView.playAnimation()
}
}
其中imageAssetsFolder是设置有些json动画需要的图片资源文件,然后通过LottieComposition里面对应的类型设置json动画资源,LottieComposition.Factory里面主要有如下几种方式

- 方式3
从手机文件夹里面选择 json文件
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.type = "*/*"
intent.addCategory(Intent.CATEGORY_OPENABLE)
startActivityForResult(Intent.createChooser(intent,"Select a JSON file"),RC_FILE)
得到选择json文件,然后得到InputStream对象,从而通过fromInputStream启动动画
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if(resultCode != Activity.RESULT_OK) return
when(requestCode){
RC_FILE -> onFileLoaded(data!!.data)
}
}
fun onFileLoaded(uri:Uri){
var inputStream: InputStream?= null
try {
when(uri.scheme){
"file" -> {
inputStream = FileInputStream(uri.path)
}
"content" -> {
inputStream = contentResolver.openInputStream(uri)
}
else -> onLoadError()
}
lottieAnim.cancelAnimation()
if(inputStream!=null)
createFromFileAnim(ctx,lottieAnim,inputStream)
}catch (e: FileNotFoundException){
onLoadError()
}
}
private fun onLoadError() {
Snackbar.make(drawableLayout!!, "Failed to load animation", Snackbar.LENGTH_LONG).show()
}
fun createFromFileAnim(ctx: Context, lottieAnimView: LottieAnimationView, inputStream: InputStream) {
LottieComposition.Factory.fromInputStream(ctx,inputStream) { composition ->
if (composition == null) {
return@fromInputStream
}
lottieAnimView.setComposition(composition)
lottieAnimView.playAnimation()
}
}
- 方式4
fun createJsonAnim(ctx: Context,lottieAnimView: LottieAnimationView, jsonString: String) {
try {
val json = JSONObject(jsonString)
LottieComposition.Factory
.fromJson(ctx.resources, json, OnCompositionLoadedListener { composition ->
if (composition == null) {
return@OnCompositionLoadedListener
}
lottieAnimView.setComposition(composition)
lottieAnimView.playAnimation()
})
} catch (e: JSONException) {
}
}
最后,写了一个自己的LottieSimle,小小学习下