Lottie是aribnb发布的开源库,它可以将AE制作的动画在Android、iOS和RN代码中渲染出来。
Lottie的功能及其强大,只需要设计师使用AE设计动画,用bodymovin导出,那么我们只需要简单的几行代码,就能实现非常复杂的动画效果。
LottieAnimationView继承自ImageView,通过当前时间绘制canvas显示到界面上。这里有两个关键类:LottieComposition 负责解析json描述文件,把json内容转成Java数据对象;LottieDrawable负责绘制,把LottieComposition转成的数据对象绘制成drawable显示到View上。顺序如下:
LottieAnimationView
继承自 ImageView ,并且是加载 Lottie 动画的默认和最简单的方法。
LottieDrawable
与 LottieAnimationView 有大部分相同的 API,但你可以在任何你想要的视图上使用它。
LottieComposition
是动画的无状态model。只要你需要,此文件就可以安全地缓存,并且可以在drawable/view之间自由重用。
LottieCompositionFactory
允许您从多个输入创建 LottieComposition。这就是
setAnimation(...)
API 在后台使用
LottieDrawable
和
LottieAnimationView
使用的内容。工厂方法也与这些类共享相同的缓存。
airbnb.io/lottie/#/an…
Lottie的使用方法
加载动画资源的方式:
src/main/res/raw 中的 json 动画
src/main/assets 中的 json 文件
src/main/assets 中的 zip 文件
src/main/assets中的
dotLottie
文件(*将Lottie的所有资源打包为一个.lottie文件,有兴趣可查看相关文档)
json 或 zip 文件的 Url
json 字符串
json 或 zip 文件的 InputStream
xml中使用方法
(不再赘述)
xml文件中Lottie的各属性
属性
|
功能
|
lottie_fileName
|
设置播放动画的json文件名称
|
Lottie_rawRes
|
设置播放动画的json文件资源
|
Lottie_autoPlay
|
设置动画是否自动播放(默认为FALSE)
|
Lottie_loop
|
设置动画是否循环(默认为FALSE)
|
Lottie_repeatMode
|
设置动画的重复模式(默认为restart)
|
lottie_repeatCount
|
设置动画的重复次数(默认为-1)
|
Lottie_cacheStrategy
|
设置动画的缓存策略(默认为weak)
|
Lottie_colorFilter
|
设置动画的着色颜色(优先级最低)
|
Lottie_scale
|
设置动画的比例(默认为1f)
|
Lottie_progress
|
设置动画的播放进度
|
Lottie_imageAssetsFolder
|
设置动画依赖的图片资源文件地址
|
代码中使用Lottie
LottieAnimationView animationView = ...
animationView.setAnimation(R.raw.hello_world)
// or
animationView.setAnimation(R.raw.hello_world.json)
animationView.playAnimation()
默认情况下,所有Lottie动画都使用LRU缓存算法进行缓存,所有从raw或者assets文件夹加载出的动画都将默认创建缓存Key,其他API需要设置缓存key。如果需要对同一个动画并行触发多个动画请求,后续请求将加入现有任务,因此只会被解析一次。
Lottie 有一些全局配置选项。默认情况下不需要,但它可用于:
从网络加载动画时,使用你自己的网络堆栈而不是 Lottie 的内置堆栈。
为从网络获取的动画提供您自己的缓存目录,而不是使用 Lottie 的默认目录 ( cacheDir/lottie_network_cache
)。
启用 systrace 进行调试。
要设置它,在应用程序初始化期间的某个地方,包括:
Lottie.initialize(
LottieConfig.Builder()
.setEnableSystraceMarkers(true)
.setNetworkFetcher(...)
.setNetworkCacheDir(...)
注:systrace是Android自带的性能分析工具,详情可以查看文档
Android Systrace 系列文章
Lottie可以通过setRepeatMode和setRepeatCount设置循环播放模式,或者通过在xml中设置 lottie_loop="true"
你同样可以循环动画中的某一段内容,通过调用 setMinFrame
, setMaxFrame
, or setMinAndMaxFrame
,包括帧、进度(从 0.0 到 1.0)或标记名称(在 After Effects 中指定)。
Lottie适配
Lottie 将 After Effects 中的所有 px 值转换为设备上的 dps,以便在设备上以相同大小呈现所有内容。这意味着,*Lottie本身已经自带了适配功能, *与其在 After Effects 中制作 1920x1080 的动画,不如在 After Effects 中制作 411x731px,大致对应于当今大多数手机的 dp 屏幕尺寸。
但是,如果您的动画尺寸不合适,您有两种选择:
ImageView scaleType
LottieAnimationView 是一个包装好的ImageView
,它支持centerCrop
, centerInside
,fitXY
所以你可以像使用imageview一样使用此属性。
Scaling Up/Down
LottieAnimationView
和LottieDrawable
两者都有一个setScale(float)
API,您可以使用它来手动放大或缩小动画。这很少有用,但在某些情况下可能有用。
如果您的动画执行缓慢,请务必查看有关性能的文档。但是,请尝试结合 scaleType 缩小动画。这将减少 Lottie 每帧渲染的数量,特别是Lottie有大的mask或matters,这将特别有用。
理解AE(After Effects)
要了解如何在 Lottie 中更改动画属性,首先应该了解动画属性是如何存储在 Lottie 中的。动画属性存储在模仿 After Effects 信息层次结构的数据树中。在 After Effects 中,Composition
是一个集合Layers
,每个集合都有自己的时间线。Layer
对象具有字符串名称,它们的内容可以是图像、形状图层、填充、描边或任何可绘制的内容。After Effects 中的每个对象都有一个名称。Lottie可以使用这些对象和属性的名称通过KeyPath
找到它们。
Lottie json文件的属性含义
lottie的最外层结构:
"v": "5.8.0", //bodymovin的版本
"fr": 60, //帧率
"ip": 0, //起始关键帧
"op": 102, //结束关键帧
"w": 1350, //动画宽度
"h": 800, //动画高度
"nm": "recommend_turn page_x0.75_original", //名称
"ddd": 0, //是否为3d
"assets":[], //资源信息
"layers":[], //图层信息
"markers": [] //遮罩
注:时间=(op-ip)/fr
assets
"assets": [
"id": "image_0",
"w": 129,
"h": 884,
"u": "images/",
"p": "recommend_bg_book_shadow.png",
"e": 0
layers:动画是由一个一个的图层组合起来,并在图层上进行偏移、缩放等操作来实现动画的。图层的解析是lottie的主要功能模块。
"layers": [
"ddd": 0,
"ind": 1,
"ty": 4,
"nm": "page back 4",
"refId": "comp_0",
"td": 1,
"sr": 1,
"ks": {...},
"ao": 0,
”layer“: [],
“shaps”: [],
"ip": 12,
"op": 1782,
"st": -18,
"bm": 0
ks:对应AE中图层的变换属性,可以通过设置锚点、位置、旋转、缩放、透明度等来控制图层,并设置这些属性的变换曲线,来实现动画。
"ks": { // 变换。对应AE中的变换设置
"o": { // 透明度
"a": 0,
"k": 100,
"ix": 11
"r": { // 旋转
"a": 0,
"k": 0,
"ix": 10
"p": { // 位置
"a": 0,
"k": [-167, 358.125, 0],
"ix": 2
"a": { // 锚点
"a": 0,
"k": [667, 375, 0],
"ix": 1
"s": { // 缩放
"a": 0,
"k": [100, 100, 100],
"ix": 6
shape:对应AE中图层的内容中的形状设置的内容,其主要用于绘制图形
"shapes": [{
"ty": "gr",
"it": [{
"ind": 0,
"ty": "sh",
"ix": 1,
"ks": {
"a": 0,
"k": {
"i": [
[0, 0],
[0, 0]
"o": [
[0, 0],
[0, 0]
"v": [
[182, -321.75],
[206.25, -321.75]
"c": false
"ix": 2
"nm": "路径 1",
"mn": "ADBE Vector Shape - Group",
"hd": false
"ty": "st",
"c": {
"a": 0,
"k": [0, 0, 0, 1],
"ix": 3
"o": {
"a": 0,
"k": 100,
"ix": 4
"w": {
"a": 0,
"k": 3,
"ix": 5
"lc": 2,
"lj": 1,
"ml": 4,
"nm": "描边 1",
"mn": "ADBE Vector Graphic - Stroke",
"hd": false
动态修改属性方法:
如果需要在运行时动态修改属性,需要以下三点:
KeyPath
LottieProperty
LottieValueCallback
KeyPath
KeyPath用于定位特定内容或将要更新的一组内容。KeyPath由字符串列表指定,这些字符串对应于原始动画中After Effectsd的内容层级结构。
KeyPaths 可以包含内容的特定名称或通配符:
Wildcard(通配符)
通配符匹配其在keypath中位置的任意单个内容名称
Globstar(全局星标)
globstar匹配0个或多个层级。
KeyPath resolution
KeyPath能够存储对其解析的内容的内部引用。当您创建一个新的KeyPath对象时,它将被解析。LottieDrawable和LottieAnimationView有一个resolveKeyPath()方法,它接受一个KeyPath并返回一个由零个或多个已解析的KeyPath组成的列表,每个都在内部解析为一个内容片段。如果你不知道,这可以用来发现你的动画结构。为此,在开发环境中,解析新的KeyPath("")并记录返回的列表。然而,你不应该单独使用和ValueCallback,因为它会被应用到动画中的每一个内容片段。如果您解析了您的keypath,并希望随后添加一个值回调,请使用从该方法返回的keypath,因为它们将在内部解析,而不需要执行树遍历来再次查找内容。
LottieProperty
LottieProperty 是可以设置的属性的枚举。它们对应于 After Effects 中的动画值,可用属性在上面和文档中列出LottieProperty
以下属性可以运行时修改:
Transform | Layer | Fill | Stroke | Ellipse | Polystar | Repeater |
---|
TRANSFORM_ANCHOR_POINT | TRANSFORM_ANCHOR_POINT | COLOR | COLOR | ELLIPSE_SIZE | POLYSTAR_POINTS | REPEATER_COPIES |
TRANSFORM_POSITION | TRANSFORM_POSITION | OPACITY | OPACITY | POSITION | POLYSTAR_ROTATION | REPEATER_OFFSET |
TRANSFORM_OPACITY | TRANSFORM_OPACITY | COLOR_FILTER | COLOR_FILTER | | POSITION | TRANSFORM_ROTATION |
TRANSFORM_SCALE | TRANSFORM_SCALE | | STROKE_WIDTH | | POLYSTAR_OUTER_RADIUS | TRANSFORM_START_OPACITY |
TRANSFORM_ROTATION | TRANSFORM_ROTATION | | | | POLYSTAR_OUTER_ROUNDEDNESS | TRANSFORM_END_OPACITY |
| TIME_REMAP | | | | POLYSTAR_INNER_RADIUS | |
ValueCallback
ValueCallback 是每次渲染动画时调用的内容。回调提供:
当前关键帧的起始帧。
当前关键帧的结束帧。
当前关键帧的起始值。
当前关键帧的结束值。
当前关键帧中从 0 到 1 的进度,没有任何时间插值。
当前关键帧的进度(存在插值器)。
整体动画进度从0到1。
ValueCallback类
LottieValueCallback:可以在构造函数中设置静态值,也可以覆盖getValue()来设置每一帧的值。
LottieRelativeTYPEValueCallback:可以在构造函数中设置一个静态值,也可以覆盖getOffset()来设置一个值,该值将被应用于每一帧上的实际动画值的偏移量。TYPE与LottieProperty参数的类型相同。
LottieInterpolatedTYPEValue:提供一个开始值、结束值和可选的插值器,使值在整个动画中自动插入。TYPE与LottieProperty参数的类型相同。
动态修改属性的用法:
动态修改颜色
KeyPath shirt = new KeyPath("Shirt", "Group 5", "Fill 1");
turnpagesLotv.addValueCallback(shirt, LottieProperty.COLOR, new LottieValueCallback<Integer>(){
@Nullable
@Override
public Integer getValue(LottieFrameInfo<Integer> frameInfo) {
return frameInfo.getOverallProgress() > 0.5f ?
COLORS[index] :
COLORS[index++];
修改弹跳高度
private void setJumpHeight(){
final PointF pointF = new PointF()
mAnimationView.addValueCallback(new KeyPath("Body"), LottieProperty.TRANSFORM_POSITION,
new SimpleLottieValueCallback<PointF>() {
@Override
public PointF getValue(LottieFrameInfo<PointF> frameInfo) {
float startX = frameInfo.getStartValue().x
float startY = frameInfo.getStartValue().y
float endY = frameInfo.getEndValue().y
if (startY > endY) {
startY += mJmupArray[mIndex]
} else if (endY > startY) {
endY += mJmupArray[mIndex]
pointF.set(startX, MiscUtils.lerp(startY, endY, frameInfo.getInterpolatedKeyframeProgress()))
return pointF
事件绑定 (与手势事件绑定,本质上还是对position进行操作)
LottieRelativePointValueCallback largeValueCallback = new LottieRelativePointValueCallback(new PointF(0f, 0f));
lottieAnimationView.addValueCallback(new KeyPath("First"),
LottieProperty.TRANSFORM_POSITION, largeValueCallback);
LottieRelativePointValueCallback mediumValueCallback = new LottieRelativePointValueCallback(new PointF(0f, 0f));
lottieAnimationView.addValueCallback(new KeyPath("Fourth"),
LottieProperty.TRANSFORM_POSITION, mediumValueCallback);
LottieRelativePointValueCallback smallValueCallback = new LottieRelativePointValueCallback(new PointF(0f, 0f));
lottieAnimationView.addValueCallback(new KeyPath("Seventh"),
LottieProperty.TRANSFORM_POSITION, smallValueCallback);
ViewDragHelper viewDragHelper = ViewDragHelper.create(container, new ViewDragHelper.Callback() {
@Override
public boolean tryCaptureView(@NonNull View child, int pointerId) {
return child == targetView;
@Override
public int clampViewPositionVertical(@NonNull View child, int top, int dy) {
return top;
@Override
public int clampViewPositionHorizontal(@NonNull View child, int left, int dx) {
return left;
* 拖动的这个View的位置发生变化
* @param changedView 当前拖动的这个View
* @param left 距离左边的距离
* @param top 距离右边的距离
* @param dx x轴的变化量
* @param dy y轴的变化量
@Override
public void onViewPositionChanged(@NonNull View changedView, int left, int top, int dx, int dy) {
totalDx += dx;
totalDy += dy;
smallValueCallback.setValue(getPoint(totalDx, totalDy, 1.2f));
mediumValueCallback.setValue(getPoint(totalDx, totalDy, 1f));
largeValueCallback.setValue(getPoint(totalDx, totalDy, 0.75f));
container.setViewDragHelper(viewDragHelper);
注意:KeyPath构造函数中的字符串对应Lottie的json文件内不同层级的nm字段,通过nm字段,Lottie可以定位到需要动态修改属性的位置,不过当Lottie资源复杂时,比较难以找到对应字段。
更换图片资源
//imageId--图片资源id
lottieView.updateBitmap(imageId, bitmap);
LottieAnimationView继承自AppCompatImageView,Lottie动画能够实现的核心在于LottieDrawable。
以下为Lottie工作的简要流程:
LottieComposition: After Effects/Bodymovin合成模型,这是创建动画的序列化模型。它被设计成无状态、可缓存和可共享的,这是json文件转换后的结果。
LottieDrawable: 将LottieComposition封装为可以调用draw()方法的BaseLayer。
BaseLayer: 当LottieAnimationView需要绘制时,将会逐层调用BaseLayer,从而将图像绘制出来。
Lottie第一步: json解析
通过LottieAnimationView的setAnimation()方法,可以看到
public void setAnimation(@RawRes final int rawRes) {
this.animationResId = rawRes
animationName = null
setCompositionTask(fromRawRes(rawRes))
public void setAnimation(final String assetName) {
this.animationName = assetName
animationResId = 0
setCompositionTask(fromAssets(assetName))
进入fromAssets方法:
private LottieTask<LottieComposition> fromAssets(final String assetName) {
if (isInEditMode()) {
return new LottieTask<>(new Callable<LottieResult<LottieComposition>>() {
@Override public LottieResult<LottieComposition> call() {
return cacheComposition ?
LottieCompositionFactory.fromAssetSync(getContext(), assetName) : LottieCompositionFactory.fromAssetSync(getContext(), assetName, null);
}, true);
} else {
return cacheComposition ?
LottieCompositionFactory.fromAsset(getContext(), assetName) : LottieCompositionFactory.fromAsset(getContext(), assetName, null);
然后对字节流内容进行解析
LottieComposition composition = LottieCompositionMoshiParser.parse(reader)
if (cacheKey != null) {
LottieCompositionCache.getInstance().put(cacheKey, composition)
解析json字段
private static final JsonReader.Options NAMES = JsonReader.Options.of(
"w",
"h",
"ip",
"op",
"fr",
"v",
"layers",
"assets",
"fonts",
"chars",
"markers"
public static LottieComposition parse(JsonReader reader) throws IOException {
float scale = Utils.dpScale()
float startFrame = 0f
float endFrame = 0f
float frameRate = 0f
final LongSparseArray<Layer> layerMap = new LongSparseArray<>()
final List<Layer> layers = new ArrayList<>()
int width = 0
int height = 0
Map<String, List<Layer>> precomps = new HashMap<>()
Map<String, LottieImageAsset> images = new HashMap<>()
Map<String, Font> fonts = new HashMap<>()
List<Marker> markers = new ArrayList<>()
SparseArrayCompat<FontCharacter> characters = new SparseArrayCompat<>()
......
Lottie第二步: LottieAnimationView将解析后生成的LottieComposition对象传递给LottieDrawer
* 设置一个composition.
* 如果这个视图使用R.attr.lottie_cacheComposition填充xml,则可以设置默认缓存策略。
public void setComposition(@NonNull LottieComposition composition) {
if (L.DBG) {
Log.v(TAG, "Set Composition \n" + composition);
lottieDrawable.setCallback(this);
this.composition = composition;
ignoreUnschedule = true;
boolean isNewComposition = lottieDrawable.setComposition(composition);
ignoreUnschedule = false;
enableOrDisableHardwareLayer();
if (getDrawable() == lottieDrawable && !isNewComposition) {
return;
} else if (!isNewComposition) {
setLottieDrawable();
onVisibilityChanged(this, getVisibility());
requestLayout();
for (LottieOnCompositionLoadedListener lottieOnCompositionLoadedListener : lottieOnCompositionLoadedListeners) {
lottieOnCompositionLoadedListener.onCompositionLoaded(composition);
LottieDrawable将LottieComposition对象构造为CompositionLayer
public boolean setComposition(LottieComposition composition) {
if (this.composition == composition) {
return false
isDirty = false
clearComposition()
this.composition = composition
buildCompositionLayer()
private void buildCompositionLayer() {
compositionLayer = new CompositionLayer(
this, LayerParser.parse(composition), composition.getLayers(), composition);
if (outlineMasksAndMattes) {
compositionLayer.setOutlineMasksAndMattes(true);
CompositionLayer继承自Baselayer,并且在构造时会遍历所有layer图层,转换为BaseLayer对象。
public CompositionLayer(LottieDrawable lottieDrawable, Layer layerModel, List<Layer> layerModels,
LottieComposition composition) {
super(lottieDrawable, layerModel)
LongSparseArray<BaseLayer> layerMap =
new LongSparseArray<>(composition.getLayers().size())
BaseLayer mattedLayer = null
for (int i = layerModels.size() - 1
Layer lm = layerModels.get(i)
BaseLayer layer = BaseLayer.forModel(this, lm, lottieDrawable, composition)
这里通过BaseLayer的forModel方法,将BaseLayer的各个子类型抽象出来
@Nullable
static BaseLayer forModel(
CompositionLayer compositionLayer, Layer layerModel, LottieDrawable drawable, LottieComposition composition) {
switch (layerModel.getLayerType()) {
case SHAPE:
return new ShapeLayer(drawable, layerModel, compositionLayer);
case PRE_COMP:
return new CompositionLayer(drawable, layerModel,
composition.getPrecomps(layerModel.getRefId()), composition);
case SOLID:
return new SolidLayer(drawable, layerModel);
case IMAGE:
return new ImageLayer(drawable, layerModel);
case NULL:
return new NullLayer(drawable, layerModel);
case TEXT:
return new TextLayer(drawable, layerModel);
case UNKNOWN:
default:
Logger.warning("Unknown layer type " + layerModel.getLayerType());
return null;
以下是Lottie的不同layer类型
到这里,LottieDrawable就通过CompositionLayer将各个类型的layer实例化,然后在LottieDrawable的draw()方法中完成所有图层的绘制
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public void draw(Canvas canvas, Matrix matrix) {
CompositionLayer compositionLayer = this.compositionLayer;
if (compositionLayer == null) {
return;
compositionLayer.draw(canvas, matrix, alpha);
Lottie第三步: 播放Lottie动画
通过LottieAnimationView的playAnimation方法可以看到,内部会调用LottieDrawable的playAnimation方法,然后会触发LottieValueAnimator的playAnimation方法。LottieValueAnimator实际也是一个ValueAnimator,所以本质上Lottie也是属性动画驱动的。
具体在LottieDrawable中可以看到,LottieValueAnimator调用updateListener后,会刷新CompositionLayer的progress。
private final ValueAnimator.AnimatorUpdateListener progressUpdateListener = new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
if (compositionLayer != null) {
compositionLayer.setProgress(animator.getAnimatedValueAbsolute());
进入setProgress可以看到,CompositionLayer会遍历所有layer图层,并逐个调用其setProgress方法。
@Override public void setProgress(@FloatRange(from = 0f, to = 1f) float progress) {
super.setProgress(progress);
if (timeRemapping != null) {
float durationFrames = lottieDrawable.getComposition().getDurationFrames() + 0.01f;
float compositionDelayFrames = layerModel.getComposition().getStartFrame();
float remappedFrames = timeRemapping.getValue() * layerModel.getComposition().getFrameRate() - compositionDelayFrames;
progress = remappedFrames / durationFrames;
if (timeRemapping == null) {
progress -= layerModel.getStartProgress();
if (layerModel.getTimeStretch() != 0 && !"__container".equals(layerModel.getName())) {
progress /= layerModel.getTimeStretch();
for (int i = layers.size() - 1; i >= 0; i--) {
layers.get(i).setProgress(progress);
进入BaseLayer的setProgress方法会发现,会调用所有BaseKeyframeAnimation的setProgress方法,并会在BaseLayer中回调调用invalidateSelf()方法。
private void invalidateSelf() {
lottieDrawable.invalidateSelf();
回调invalidateSelf()方法后,LottieDrawable会回调draw方法
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public void draw(Canvas canvas, Matrix matrix) {
CompositionLayer compositionLayer = this.compositionLayer;
if (compositionLayer == null) {
return;
compositionLayer.draw(canvas, matrix, alpha);
进入CompositionLayer的draw方法
@Override
public void draw(Canvas canvas, Matrix parentMatrix, int parentAlpha) {
L.beginSection(drawTraceName);
if (!visible || layerModel.isHidden()) {
L.endSection(drawTraceName);
return;
buildParentLayerListIfNeeded();
L.beginSection("Layer#parentMatrix");
matrix.reset();
matrix.set(parentMatrix);
for (int i = parentLayers.size() - 1; i >= 0; i--) {
matrix.preConcat(parentLayers.get(i).transform.getMatrix());
L.endSection("Layer#parentMatrix");
int opacity = transform.getOpacity() == null ? 100 : transform.getOpacity().getValue();
int alpha = (int)
((parentAlpha / 255f * (float) opacity / 100f) * 255);
if (!hasMatteOnThisLayer() && !hasMasksOnThisLayer()) {
matrix.preConcat(transform.getMatrix());
L.beginSection("Layer#drawLayer");
drawLayer(canvas, matrix, alpha);
L.endSection("Layer#drawLayer");
recordRenderTime(L.endSection(drawTraceName));
return;
实际这样构成了一个循环,随着animator动画的进行,LottieDrawable会不断的绘制,这样Lottie动画就跑起来了,流程图如下:
Lottie性能优化
开发过程中经常会出现Lottie跳帧的问题,那么首先要明白,Lottie为何会跳帧?
进入LottieValueAnimator的playAnimation方法,可以看到
@MainThread
public void playAnimation() {
running = true;
notifyStart(isReversed());
setFrame((int) (isReversed() ? getMaxFrame() : getMinFrame()));
lastFrameTimeNs = 0;
repeatCount = 0;
postFrameCallback();
protected void postFrameCallback() {
if (isRunning()) {
removeFrameCallback(false);
Choreographer.getInstance().postFrameCallback(this);
继续顺藤摸瓜,找到FrameCallback的实现doFrame方法:
@Override public void doFrame(long frameTimeNanos) {
postFrameCallback();
if (composition == null || !isRunning()) {
return;
L.beginSection("LottieValueAnimator#doFrame");
long timeSinceFrame = lastFrameTimeNs == 0 ? 0 : frameTimeNanos - lastFrameTimeNs;
float frameDuration = getFrameDurationNs();
float dFrames = timeSinceFrame / frameDuration;
frame += isReversed() ? -dFrames : dFrames;
boolean ended = !MiscUtils.contains(frame, getMinFrame(), getMaxFrame());
frame = MiscUtils.clamp(frame, getMinFrame(), getMaxFrame());
lastFrameTimeNs = frameTimeNanos;
notifyUpdate();
if (ended) {
if (getRepeatCount() != INFINITE && repeatCount >= getRepeatCount()) {
frame = speed < 0 ? getMinFrame() : getMaxFrame();
removeFrameCallback();
notifyEnd(isReversed());
} else {
notifyRepeat();
repeatCount++;
if (getRepeatMode() == REVERSE) {
speedReversedForRepeatMode = !speedReversedForRepeatMode;
reverseAnimationSpeed();
} else {
frame = isReversed() ? getMaxFrame() : getMinFrame();
lastFrameTimeNs = frameTimeNanos;
理解了Lottie跳帧的机制,那么如何进行优化呢?
往往Lottie跳帧是主线程进行了耗时操作,那么最有方案便是优化此耗时操作,放到子线程等。
看Lottie的json结构,如果没有用到遮罩mask(掩膜)或者matte(前景蒙版)标签,那正常来讲性能开销没啥问题,这两个标签会创建bitmap,大幅拉高内存,特别是在recyclerview中。
导出的矢量图层使用1x一倍图,这一点十分重要,Lottie会自动适配屏幕密度
尽量保持图层简洁,预合成嵌套越少越好
开启硬件加速,lotv.setRenderMode(RenderMode.HARDWARE),但是注意开启硬件加速后不支持抗锯齿、笔画上限(API 18前)和其他一些功能。
腾讯VTeam技术团队