目次
- アニメーションと作画の流れ
- LayerView ツリー
- ShapeLayerの分析
- Lottieのメリットとデメリット、rLottieとPAGの導入
- 材料
- 褒美
前回の記事では、 Lottie の json 解析部分を学び、分析しましたが、この記事では、アニメーションとレンダリング部分を分析しました。
分析の焦点: 多層レイヤーの関係を整理し、異なるレイヤーの描画とアニメーションを制御する方法。
1.アニメーションと描画のプロセス
エントリ API 関数 (LottieDrawable#setComposition、LottieDrawable#playAnimation) を通じて分析します。
1.1 LottieDrawable#setComposition プロセス
public boolean setComposition(LottieComposition composition) {
//......
clearComposition();
this.composition = composition;
//构建图层layer compositionlayer它的作用有点先andoid View树中ViewGroup,可以包含其他的View和ViewGroup
//完成CompositionLayer和ContentGroup的初始化 主要是两个里面TransformKeyframeAnimation
buildCompositionLayer();
//触发notifyUpdate,进而触发个Layer的progress的重新计算以及draw的回调(当然此时进度为0,各种判断之后也不会触发composition的drawlayer)
animator.setComposition(composition);
//设置当前动画的进度
setProgress(animator.getAnimatedFraction());
......
}
setComposition は主に buildCompositionLayer と animator.setComposition を呼び出して、CompositionLayer と他のレイヤー (json の対応するレイヤー フィールド)、ContentGroup、TransformKeyframeAnimation などを初期化することがわかります。
Lottie アニメーションで最も使用されるレイヤーは、CompositionLayer、ShapeLayer、および ImageLayer です。
考えてみてください: ContentGroup、TransformKeyframeAnimation とは何ですか? また、レイヤーとの関係は何ですか? (後で答えを分析しようとします)
1.2 LottieDrawable#playAnimation プロセス
1. LottieDrawable.playAnimation
2. LottieValueAnimator.playAnimation
3. LottieValueAnimator.setFrame
4. BaseLottieAnimator.notifyUpdate
5.然后触发回调(LottieDrawable.progressUpdateListener)AnimatorUpdateListener.onAnimationUpdate
6. CompositionLayer.setProgress --》计算当前的progress,然后倒序设置每个图层进度 BaseLayer.setProgress
6.1(transform.setProgress(progress))TransformKeyframeAnimation.setProgress 设置矩阵变换的进度(缩放、透明度、位移等)--》需要重点分析
6.2 animations.get(i).setProgress(progress); 遍历设置每个animation的进度
7. BaseKeyframeAnimation.notifyListeners 回调给监听者
8. BaseLayer.onValueChanged (invalidateSelf())触发页面的重新绘制,--》即LottieDrawable.draw(android.graphics.Canvas, android.graphics.Matrix)
9. compositionLayer.draw(canvas, matrix, alpha) 即 BaseLayer.draw --》这也是一个关键的方法
10. drawLayer(canvas, matrix, alpha); 即 BaseLayer.drawLayer这个方法是抽象方法,各layer具体实现
10.1 我们以ImageLayer为例来来看 (重点分析) ImageLayer.drawLayer 首先通过BaseKeyframeAnimation.getValue() 这个就用到前面动画改变的progress的值,根据差值器获取到当前的Bitmap
10.2 然后使用canvas来进行绘制,完成图片的变换
LottieValueAnimator は ValueAnimator のサブクラスであり、Choreographer.FrameCallback インターフェイスを実装します。アトリビュート アニメーションの進行変換コールバックと VSYNC 信号の doframe コールバックを通じて、Layer に進行と値の計算を実行するように通知し、LottieDrawble に再描画を通知することで、レイヤーのアニメーションと描画を json で実現します。つまり、さまざまなレイヤーレイヤーです。
特定の描画は、ImageLayer の drawLayer を通じて実現できる Canvas によって実現されます。
public void drawLayer(@NonNull Canvas canvas, Matrix parentMatrix, int parentAlpha) {
Bitmap bitmap = getBitmap();
if (bitmap == null || bitmap.isRecycled()) {
return;
}
float density = Utils.dpScale();
paint.setAlpha(parentAlpha);
if (colorFilterAnimation != null) {
paint.setColorFilter(colorFilterAnimation.getValue());
}
//将画布的当前状态保存
canvas.save();
//对matrix的变换应用到canvas上的所有对象
canvas.concat(parentMatrix);
//src用来设定要绘制bitmap的区域,即是否进行裁剪
src.set(0, 0, bitmap.getWidth(), bitmap.getHeight());
//dst用来设置在canvas画布上的显示区域。这里可以看到显示的宽高会根据像素密度进行等缩放
dst.set(0, 0, (int) (bitmap.getWidth() * density), (int) (bitmap.getHeight() * density));
//第一个Rect(src) 代表要绘制的bitmap 区域,可以对是对图片进行裁截,若是空null则显示整个图片。第二个 Rect(dst) 是图片在Canvas画布中显示的区域,即要将bitmap 绘制在屏幕的什么地方
// 通过动态的改变dst,可以实现 移动、缩放等效果,以及根据屏幕的像素密度进行缩放,通过改变src 对绘制的图片需求做处理,也能够实现很多有趣的效果,比如 显示一部分,或者逐渐展开等
canvas.drawBitmap(bitmap, src, dst, paint);
//恢复之前保存的画布状态,和sava一一对应
canvas.restore();
}
ShapeLayer と CompositionLayer については、少し複雑なので、以下で個別に分析します。
思考:複数のレイヤーがある場合、複数のレイヤー間の相関をどのように確保するか (ViewTree のように、それらの関係と描画の順序をどのように管理するか)。
2、LayerView ツリー
Lottie にはさまざまなレイヤーがあります。
1.jpg
それで、それらの間の関係は何ですか?管理と階層制御を実行するには?
コンポジションレイヤーの構築
public CompositionLayer(LottieDrawable lottieDrawable, Layer layerModel, List<Layer> layerModels,
LottieComposition composition) {
//主要是TransformKeyframeAnimation的初始化
super(lottieDrawable, layerModel);
LongSparseArray<BaseLayer> layerMap =
new LongSparseArray<>(composition.getLayers().size());
BaseLayer mattedLayer = null;
//根据layers大小,倒序生产每个Layer
for (int i = layerModels.size() - 1; i >= 0; i--) {
Layer lm = layerModels.get(i);
//这个是一个工程方法,根据layerType构造对应的Layer
BaseLayer layer = BaseLayer.forModel(this, lm, lottieDrawable, composition);
if (layer == null) {
continue;
}
layerMap.put(layer.getLayerModel().getId(), layer);
......
}
for (int i = 0; i < layerMap.size(); i++) {
long key = layerMap.keyAt(i);
BaseLayer layerView = layerMap.get(key);
if (layerView == null) {
continue;
}
// 确定layer之间的父子关系
BaseLayer parentLayer = layerMap.get(layerView.getLayerModel().getParentId());
if (parentLayer != null) {
layerView.setParentLayer(parentLayer);
}
}
}
ファクトリ メソッド: BaseLayer#forModel
static BaseLayer forModel(
CompositionLayer compositionLayer, Layer layerModel, LottieDrawable drawable, LottieComposition composition) {
//对应json中 object->layers->ty
switch (layerModel.getLayerType()) {
//轮廓/形态图层 这个是再lottie动画中用的基本上是最多的类型
case SHAPE:
return new ShapeLayer(drawable, layerModel, compositionLayer);
//合成图层,相当于ViewTree的ViewGroup的角色
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);
//空图层,可以作为其他图层的parent
case NULL:
return new NullLayer(drawable, layerModel);
//文本图层
case TEXT:
return new TextLayer(drawable, layerModel);
case UNKNOWN:
default:
// Do nothing
Logger.warning("Unknown layer type " + layerModel.getLayerType());
return null;
}
}
上で layerView.setParentLayer(parentLayer)を見ましたが、この ParentLayer の用途は何ですか?
主に各レイヤーの境界や描画を決める際に使用
// BaseLayer#buildParentLayerListIfNeeded
//该方法会在确定当前图层边界getBounds以及绘制该图层的时候调用draw
private void buildParentLayerListIfNeeded() {
if (parentLayers != null) {
return;
}
//如果该图层有父图层,则创新
if (parentLayer == null) {
parentLayers = Collections.emptyList();
return;
}
//该图层的LayerViewTree
parentLayers = new ArrayList<>();
BaseLayer layer = parentLayer;
//递归找到该图层的父图层、祖父图层、曾祖图层等等
while (layer != null) {
parentLayers.add(layer);
layer = layer.parentLayer;
}
}
BaseLayer#getBounds
public void getBounds(
RectF outBounds, Matrix parentMatrix, boolean applyParents) {
rect.set(0, 0, 0, 0);
//确定该图层的LayerViewTree:parentLayers
buildParentLayerListIfNeeded();
//子图层的矩阵变换,以作用再父图层的矩阵变换为基础
boundsMatrix.set(parentMatrix);
if (applyParents) {
//递归调用父图层额矩阵变换,进行矩阵相乘
if (parentLayers != null) {
for (int i = parentLayers.size() - 1; i >= 0; i--) {
boundsMatrix.preConcat(parentLayers.get(i).transform.getMatrix());
}
} else if (parentLayer != null) {
boundsMatrix.preConcat(parentLayer.transform.getMatrix());
}
}
//最后再乘以当前图层的矩阵变换,以确定最终的边界矩阵
boundsMatrix.preConcat(transform.getMatrix());
}
BaseLayer#draw は、
BaseLayer#getBounds と同じマトリックス処理メソッドです。
parentid を介してレイヤーの LayerViewTree を確立し、測定および描画時に LayerView に従って独自の境界と描画を決定します。
3. ShapeLayer の解析
ShapeLayer が選ばれる理由は、それが宝くじアニメーションで非常に重要であるためです.
ShapeLayer は、ビットマップの代わりにベクター グラフィックスによって描画されるレイヤー サブクラスです。色や線幅などのプロパティを指定し、Path を使用して描画するグラフィックを定義します。
public class ShapeLayer extends BaseLayer {
......
//这个ContentGroup是什么呐?可以看到ShapeLayer的drawLayer和getBound都是通过contentGroup代理的。
private final ContentGroup contentGroup;
ShapeLayer(LottieDrawable lottieDrawable, Layer layerModel, CompositionLayer compositionLayer) {
......
//ContentGroup构造
contentGroup = new ContentGroup(lottieDrawable, this, shapeGroup);
contentGroup.setContents(Collections.<Content>emptyList(), Collections.<Content>emptyList());
}
@Override void drawLayer(@NonNull Canvas canvas, Matrix parentMatrix, int parentAlpha) {
//调用了contentGroup的draw
contentGroup.draw(canvas, parentMatrix, parentAlpha);
}
@Override public void getBounds(RectF outBounds, Matrix parentMatrix, boolean applyParents) {
......
contentGroup.getBounds(outBounds, boundsMatrix, applyParents);
}
......
}
コンテンツ グループとは何ですか?
ShapeLayer の drawLayer と getBound の両方が contentGroup を介してプロキシされていることがわかります。
ContentGroup の draw の実装を見てみましょう
public void draw(Canvas canvas, Matrix parentMatrix, int parentAlpha){
//遍历调用content,如果是DrawingContent则进行draw,那边什么是DrawingContent呐
for (int i = contents.size() - 1; i >= 0; i--) {
Object content = contents.get(i);
if (content instanceof DrawingContent) {
((DrawingContent) content).draw(canvas, matrix, childAlpha);
}
}
}
トラバースはコンテンツを呼び出します。それが DrawingContent の場合、次に描画します。どのコンテンツが DrawingContent ですか?
例として FillContent を取り上げて、その描画の実装を見てみましょう
public void draw(Canvas canvas, Matrix parentMatrix, int parentAlpha) {
......
//获取颜色 透明度等 设置画笔paint的颜色
int color = ((ColorKeyframeAnimation) this.colorAnimation).getIntValue();
int alpha = (int) ((parentAlpha / 255f * opacityAnimation.getValue() / 100f) * 255);
paint.setColor((clamp(alpha, 0, 255) << 24) | (color & 0xFFFFFF));
//设置colorFilter
if (colorFilterAnimation != null) {
paint.setColorFilter(colorFilterAnimation.getValue());
}
......
//设置path路径
path.reset();
for (int i = 0; i < paths.size(); i++) {
path.addPath(paths.get(i).getPath(), parentMatrix);
}
//用cavas drawpath
canvas.drawPath(path, paint);
}
ShapeContent にできる DrawingContent も、Canvas を介して描画されます。
Lottieのアニメーションとレンダリングの解析編はここまでです.LayerとDrawingContentで主にアニメーションの補間計算を実現しているBaseKeyframeAnimationについては詳細な解析がありませんので,必要に応じて読み直しましょう.
考察: レンダリングと描画に OpenGL ES を使用できますか?
5.ロッティのメリット・デメリットとPAGとの簡単な比較
ロッティの長所と短所
优点:
支持跨平台(虽然每个端各自实现一套)
性能好
可以通过配置下发“json和素材”进行更新。
不足点:
Lottie不支持交互和编辑
Lottie不支持压缩位图,如果使用png等位图,需要自行在tiny等压缩平台进行图片压缩、降低包体积。
Lottie存在mask、matters 时,需要先saveLayer,再调用drawLayer返回。
saveLayer是一个耗时的操作,需要先分配、绘制一个offscreen的缓冲区,这增加了渲染的时间
PAG の長所と短所の簡単な紹介
PAG是腾讯昨天刚开源的动画组件,除lottie的优点外,
支持更多AE特效,
支持文本和序列帧,
支持模版的编辑,
采用二级值文件而不是json,文件大小和解析的性能都会更好些
渲染层面:Lottie渲染层面的实现依赖平台端接口,不同平台可能会有所差异。PAG渲染层面使用C++实现,所有平台共享同一套实现,平台端只是封装接口调用,提供渲染环境,渲染效果一致。
PAG的不足,渲染基于google开源的skia 2d来实现。增加了包大小。4.0的版本会有改善,去掉skia 2d。自己实现简单的渲染封装(估计也是opengl或者metal 、vulkan)。
rlottieの簡単な紹介
[Samsung-rlottie](https://github.com/Samsung/rlottie)
rLottie 与 lottie 工作流一致,在 SDK 上实现不一样,rLottie 没有使用平台特定实现,是统一 C++实现,素材支持 lottie 的 json 文件,矢量渲染性能还不错,但缺少各平台封装,支持的 AE 特性不全,也不支持文本、序列帧等
这个还没有分析它的源码实现。抽时间可以分析学习下。
6. 情報
- Lottie の実装アイデアとソースコード分析
- ロッティのアニメーション原理の分析
- Lottie アニメーションとその原理の長所と短所を分かりやすく解説
- Lottie-android フレームワークの使用とソースコード分析
- Lottie アニメーション ライブラリ Android ソース コード分析
- Tencent オープン ソース PAG
- Samsung-rlottie
- デコードとレンダリングの観点からのPAGとlottieの比較
7.収穫
この記事の研究分析を通して
- 宝くじのアニメーションとレンダリングのプロセスを整理する
- LayerView ツリーの概念と理解、lottie が異なるレイヤー間の関係をどのように管理するかを理解する
- ShapeLayer に ContentGroup が含まれている場合、CompositionLayer、BaseLayer、ImageLayer、および ShapeLayer の分析に焦点を当てます。
- lottie、PAG、rlottieの簡単な比較
読んでくれてありがとう.
公式アカウント「オーディオとビデオの開発の旅」に注目して、一緒に学び、成長してください.
交換へようこそ