您的位置:首页 > 文旅 > 旅游 > seo资讯推推蛙_中国建筑界网官网_西安整站优化_烟台百度推广公司

seo资讯推推蛙_中国建筑界网官网_西安整站优化_烟台百度推广公司

2024/12/28 5:51:12 来源:https://blog.csdn.net/yus201120/article/details/144733826  浏览:    关键词:seo资讯推推蛙_中国建筑界网官网_西安整站优化_烟台百度推广公司
seo资讯推推蛙_中国建筑界网官网_西安整站优化_烟台百度推广公司

Lottie是一个很成熟的开源动画框架,它支持直接使用从AE导出的动画文件,在不同平台均可快速使用,大大减轻了程序员的工作量,也让复杂的动画成为可能。该动画文件使用Json格式来描述内容,可以大大缩减文件的体积。在Android平台,Lottie的作用就是把Json文件做解析,使用code做控制,并通过Canvas绘制出来。Lottie效果如下:
请添加图片描述如果用纯代码去手搓以上的动画效果,估计码农们会原地狗带。而用Lottie作为衔接,这一切就很丝滑,就像显示一张图片一样简单,这就是Lottie的强大,可以说是降维打击。

1.文件结构

1.1最外层文件

Json的最外层结构如下:

{"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": []  //遮罩
}

其中的layers是动画最核心的内容,因为Lottie的动画是由不同的Layer组合而成的。

1.2 Layers层

结构如下:

"layers": [                            //图层信息{"ddd": 0,         //是否为3d"ind": 1,                     //图层id 唯一性"ty": 4,            //图层类型"nm": "page back 4",//图层名称"refId": "comp_0", // 引用的资源,图片/预合成层"td": 1,"sr": 1,"ks": {...},              // 变换。对应AE中的变换设置"ao": 0,"layer": [],         // 该图层包含的子图层"shaps": [],         // 形状图层"ip": 12,                     //该图层起始关键帧"op": 1782,         //该图层结束关键帧"st": -18,         "bm": 0}

ty : 定义了图层的类型,类型有ImageLayer、ShapeLayer、ScaleLayer、SolidLayer、TextLayer和NullLayer,不同的layer会使用不同的变化策略以及不同的资源。
refId :这是ImageLayer会用到的图片资源。
shaps :ShapeLayer会用到的资源,描述形状。
ks :变换的描述,所有图层都会用到这个资源,它是对动画怎么变化的具体描述,它内部包含了很多维度的变。

1.3 KS层

具体如下:

"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}
}

2.文件解析过程

2.1 LottieTask

在LottieAnimationView的setAnimation方法是加载资源的入口,它内部会把加载资源的任务创建一个LottieTask,并且在执行完文件解析之后,创建相关的Layer,并封装在LottieComposition作为结果返回给LottieAnimationView,如下:

public void setAnimation(@RawRes final int rawRes) {LottieTask<LottieComposition> task = cacheComposition ?LottieCompositionFactory.fromRawRes(getContext(), rawRes) : LottieCompositionFactory.fromRawRes(getContext(), rawRes, null);//添加任务监听,在任务执行成功之后回调setCompositionTask(task); }

LottieCompositionFactory内部会创建一个LottieTask并添加到线程池中去异步执行:

public static LottieTask<LottieComposition> fromRawRes(Context context, @RawRes final int rawRes, @Nullable String cacheKey) {return cache(cacheKey, new Callable<LottieResult<LottieComposition>>() {@Overridepublic LottieResult<LottieComposition> call() {// 这里执行异步解析任务return fromRawResSync(context, rawRes);}});}

在LottieTask内部中添加线程池任务:

LottieTask(Callable<LottieResult<T>> runnable, boolean runNow) {EXECUTOR.execute(new LottieFutureTask(runnable));}

2.2 LayerParser

跟进fromRawResSync方法里面去,会调用到LottieCompositionMoshiParser去做解析最外层的Json数据,我们主要关注是解析layers字段:
LottieCompositionMoshiParser.java

public static LottieComposition parse(JsonReader reader) throws IOException {switch (reader.selectName(NAMES)) {case 6:parseLayers(reader, composition, layers, layerMap);}
}private static void parseLayers(JsonReader reader, LottieComposition composition,List<Layer> layers, LongSparseArray<Layer> layerMap) throws IOException {Layer layer = LayerParser.parse(reader, composition);layers.add(layer);layerMap.put(layer.getId(), layer);}

最后再调用了LayerParser做生成各个Layer数据结构,当然LayerParser内部还要对下一层的数据做解析,最后形成一个树形数据结构返回给LottieAnimationView。

3.创建绘制图层

3.1 CompositionLayer和BaseLayer

LottieTask在解析完之后,会把LottieComposition返回给LottieAnimationView:
LottieAnimationView.java

public void setComposition(@NonNull LottieComposition composition) {boolean isNewComposition = lottieDrawable.setComposition(composition);}

LottieAnimationView又把数据透传给LottieDrawable:
LottieDrawable.java

public boolean setComposition(LottieComposition composition) {buildCompositionLayer();
}compositionLayer = new CompositionLayer(this, LayerParser.parse(composition), composition.getLayers(), composition);

这里会创建一个CompositionLayer,它继承自BaseLayer,而在它内部会创建各个类型的图层,如下:

 static BaseLayer forModel(Layer layerModel, LottieDrawable drawable, LottieComposition composition) {switch (layerModel.getLayerType()) {case SHAPE:return new ShapeLayer(drawable, layerModel);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);}}

这些不同类型的Layer也是继承自BaseLayer,所以这里创建图层的过程,就是把之前Json文件解析的树形数据结构,转换成绘制图层的树形结构。CompositionLayer和其他各个BaseLayer的关系,就类似于View和ViewGroup之间的关系一样。

3.2 关键帧动画

同时在创建各个BaseLayer的时候,它们内部还会继续创建对应的关键帧动画类,我们重点看一下TransformKeyframeAnimation,也就是上面Json文件中对应的“ks”数据:
TransformKeyframeAnimation.java

  @NonNull private BaseKeyframeAnimation<PointF, PointF> anchorPoint;@NonNull private BaseKeyframeAnimation<?, PointF> position;@NonNull private BaseKeyframeAnimation<ScaleXY, ScaleXY> scale;@NonNull private BaseKeyframeAnimation<Float, Float> rotation;@NonNull private BaseKeyframeAnimation<Integer, Integer> opacity;@Nullable private FloatKeyframeAnimation skew;@Nullable private FloatKeyframeAnimation skewAngle;

可以看到内部定义了很多关键帧动画类,它们决定了该Layer在某个时刻应该绘制怎样的内容。
关系图如下:
在这里插入图片描述

4.动画绘制过程

4.1LottieValueAnimator计算progress

准备好了资源,创建好了图层,在LottieAnimationView 调用了playAnimation()方法之后,就开始播放动画了。同样,也会调用到LottieDrawable的playAnimation()方法:
LottieDrawable.java

public void playAnimation() {//调用animator去播放动画animator.playAnimation();   }

LottieDrawable会调用它内部的LottieValueAnimator去计算动画播放的具体帧:
LottieValueAnimator.java

 @MainThreadpublic void playAnimation() {  //这里会通知更新动画setFrame((int) (isReversed() ? getMaxFrame() : getMinFrame()));//这里注册Choreographer,以便同步vsync信号,并在doframe方法中继续更新动画postFrameCallback();}

setFrame会调用到父类里的notifyUpdate()方法,然后回调通知到LottieDrawable里面的监听接口:
LottieDrawable.java

private final ValueAnimator.AnimatorUpdateListener  progressUpdateListener = new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {if (compositionLayer != null) {compositionLayer.setProgress(animator.getAnimatedValueAbsolute());}}};

从这里可以看出,LottieValueAnimator的作用主要是根据屏幕的刷新信号,来计算应该播放的帧序列,也就是播放的具体进度。当存在卡断的情况下,Choreographer的doframe方法会延迟通知,就会导致LottieValueAnimator存在跳帧的情况。

4.2关键帧动画设置progress

接着看绘制流程,播放进度会给到CompositionLayer,而CompositionLayer又会把播放进度再分发给具体的每一个Layer,如下:
CompositionLayer.java

 setProgress(@FloatRange(from = 0f, to = 1f) float progress) {for (int i = layers.size() - 1; i >= 0; i--) {layers.get(i).setProgress(progress);}
}

每个Layer都是继承自BaseLayer,在BaseLayer中又会把进度给到内部的各个关键帧动画,最重要的是TransformKeyframeAnimation,它内部包含各个维度的变化序列:
TransformKeyframeAnimation.java

setProgress(float progress) {
if (opacity != null) {opacity.setProgress(progress);  //透明度}if (anchorPoint != null) {anchorPoint.setProgress(progress); //锚点}if (position != null) {position.setProgress(progress);  //位置}if (scale != null) {scale.setProgress(progress);  //缩放}if (rotation != null) {rotation.setProgress(progress); //旋转}if (skewAngle != null) {skewAngle.setProgress(progress); //斜角
}

4.3Layer请求刷新

在设置完progress之后,各个Layer就会触发onValueChanged()方法,并请求刷新自己:
BaseLayer.java

public void onValueChanged() {invalidateSelf();}private void invalidateSelf() {lottieDrawable.invalidateSelf();}

接着会调用到LottieDrawable的绘制:
LottieDrawable.java

public void invalidateSelf() {final Callback callback = getCallback();if (callback != null) {callback.invalidateDrawable(this);}}

4.4Layer绘制

当自定义的Drawable需要重绘的时候,就需要调用invalidateDrawable方法,请求绘制自己,然后会通过ImageView的onDraw方法里调用到Drawable的draw方法:
Drawable.java

 public void draw(@NonNull Canvas canvas) {drawInternal(canvas)}

接下来就会调用到CompositionLayer中的draw方法,在CompositionLayer中又会把实际绘制的工作交给各个Layer去完成:
CompositionLayer.java

 draw(Canvas canvas, Matrix parentMatrix, int parentAlpha) {drawLayer(canvas, matrix, alpha);
}drawLayer(Canvas canvas, Matrix parentMatrix, int parentAlpha) {for (int i = layers.size() - 1; i >= 0; i--) {BaseLayer layer = layers.get(i);layer.draw(canvas, parentMatrix, childAlpha);}
}

不同的Layer层,对draw的实现也不一样,比如ImageLayer是drawBitmap,绘制前会在父类调关键帧动画计算出来的参数,对Bitmap做位移、旋转、透明度变化等等变化操作,最后才是使用Canvas做绘制:
BaseLayer.java:

public void draw(Canvas canvas, Matrix parentMatrix, int parentAlpha) {matrix.preConcat(transform.getMatrix()); //通过关键帧动画做变化操作drawLayer(canvas, matrix, alpha); //调用子类的绘制
}

ImageLayer.java

 @Override public void drawLayer(@NonNull Canvas canvas, Matrix parentMatrix, int parentAlpha) {Bitmap bitmap = getBitmap(); //读取图片内容canvas.drawBitmap(bitmap, src, dst , paint);//绘制图片
}

至此,一帧动画的绘制就完成了,后面会由LottieValueAnimator的doFrame回调不停地重复上面的步骤,实现连续的动画效果。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com