Github上的Lottie
LottieComposition
public ArrayList getWarnings() {
return new ArrayList<>(Arrays.asList(warnings.toArray(new String[warnings.size()])));
}
这个方法通过HashSet
转换为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 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() {
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
,小小学习下
LottieSimple