-- 矩阵(Matrix), Matrix动画
矩阵(Matrix)是一个按照长方阵列排列的复数或实数集合,最早来自于方程组的系数及常数所构成的方阵。在物理学中,矩阵于电路学、力学、光学和量子物理中都有应用;计算机科学中,三维动画制作也需要用到矩阵。 矩阵的运算是数值分析领域的重要问题。
Matrix动画 ColorMatrix动画.利用 Matrix 来进行 Translate(平移)、Scale(缩放)、Rotate(旋转)的操作,就是在操作着这个矩阵中元素的数值来达到我们想要的效果。
-- Android动画框架实现原理
Animation框架定义了透明度,旋转,缩放和位移几种常见的动画,而且控制的是整个View,实现原理是每次绘制视图时View所在的ViewGroup中的drawChild函数获取该View的Animation的Transformation值,然后调用canvas.concat(transformToApply.getMatrix()),通过矩阵运算完成动画帧,如果动画没有完成,继续调用invalidate()函数,启动下次绘制来驱动动画,动画过程中的帧之间间隙时间是绘制函数所消耗的时间,可能会导致动画消耗比较多的CPU资源,最重要的是,动画改变的只是显示,并不能响应事件。
-- Android Animation动画原理源码分析- https://blog.csdn.net/u010019468/article/details/73469410
view tween动画的执行原理
从 view.applyLegacyAnimation()源码可以看到调用了animation.getChildTransformation()并通过返回的boolean值覆盖more变量,并且同时把对传入animation.getTransformation(drawingTime, t, 1f)中的transformToApply(parent.getChildTransformation()获取)进行变化矩阵,当more为true,看到又调用父View parent.invalidate(),重新刷新view树进行再一次绘制剩余动画内容。当applyLegacyAnimation执行完成之后draw(Canvas canvas, ViewGroup parent, long drawingTime)里面继续transformToApply = parent.getChildTransformation()获取变化之后transformToApply,若不为空,又传递给canvas.concat(transformToApply.getMatrix())来对canvas进行矩阵变化操作,从而对绘制的view内容发生动画。
> 动画优化
动画生万物(动画优化)- https://zhuanlan.zhihu.com/p/45597573
动画除了可以实现App里的5毛钱特效,实际跟UI性能息息相关。单纯站在UI的角度看,App的本质就是响应用户输入产生UI变化的过程,而UI变化的过程就是广义上的动画过程。 比如说,我们可以把Scroller,computeScroll + invalidate的过程理解成一个逐帧动画;可以把列表滑动,onTouchEvent + offsetTopAndBottom的过程也理解成一个持续性的动画过程;同样我们也可以利用在onLayout来不断改变View的布局状态,形成一个持续性的动画;我们使用的Webp、Gif等动画素材,Lottie、SVGA等动画库,本质不也是一个draw + invalidate的动画过程;同时系统也默默为我们完成了很多动画过程,比如随处可见的水波纹动画、比如Window发生切换时的窗口动画。
广义上的动画性能优化就是UI性能优化.Android中的动画实现,首先绕不开是就是Choreographer中现行的这套垂直同步的绘制机制,但关于绘制渲染、垂直同步的介绍.
动画和普通UI变化最大的不同点,动画过程是一个相对连续的绘制过程,对UI线程空闲更加敏感,动画过程中即使UI线程被短暂占用,也会发生比较明显的丢帧。
(不知道大家有没有体会,关于动画,实际有一个很大的痛点——很多时候,我们之所以需要显示动画,就是想利用动画来掩盖一些耗时操作,但如果这些耗时操作一定要放在UI线程实现(比如inflate新的布局、比如截图),那动画也是无法正常工作的。在这种场景下,你会发现我们根本没有办法来实现平滑的过度,卡顿是必然的。)
-- 从动画实现的原理就可以看出它们性能和能力的差异:
窗口动画和Render Thread动画,都是可以在非UI线程实现的动画,由于前文提到的动画跟UI线程耦合这个痛点,能跟UI线程解耦的动画肯定是性能最好的,后面我们会再简单讨论一下这两种动画;
属性动画,可以广义的理解成可以直接操作View属性——也就是RenderNode对应的Api——的动画,这种动画方式通过直接修改渲染线程的RenderProperty实现,可以避免触发View Tree重绘,也就避免了UI线程绘制的过程,只需要Render线程重新渲染。但这种动画的局限是只能使用RenderNode的Api,只有一些相对基本的矩阵变换,不能实现太过复杂的绘制需求;
补间动画,这是一种比较古老的动画实现,是在硬件加速出现之前,用来优化绘制效率的一种动画形式,它的性能高于直接重绘;但由于代码向下兼容的原因,在硬件加速时代,它的性能会略低于属性动画;
Draw动画和帧动画,都是需要利用invalidate触发View Tree重绘的动画方式,这种动画需要触发UI线程重绘,自然也需要Render线程重新渲染,根据View Tree的具体结构,性能上的差异可能会比较大。但draw动画的原理决定了它可以实现所有绘制指令的更新,所以这也是应用最普遍的一种动画形式;
Layout动画,是利用requestLayout触发View Tree重新布局的动画,这种动画除了需要UI线程重绘,还需要对View Tree重新measure和layout,尤其布局结构不合理时,耗时是灾难性的。
从榜单也可以看出,往往能力越强的动画性能就越差,这也说明我们还是要根据实际需求来选择最合适的动画。
补间动画是早期的动画实现,所以设计的初衷是优化非硬件加速下的动画效率。因此在非硬件加速的状态下,实际它的效率才是最高的(高于属性动画和逐帧绘制);但在硬件加速的状态下,它的功能已经被属性动画完美覆盖。所以两者到底怎么选择,还得看具体的情况,不过考虑到目前绝大部分情况下,都是硬件加速绘制,属性动画显然更胜一筹。
两种动画最直观的差异就是它们的基类不同,补间是Animation,属性是Animator。
-- 在榜单中,有3种转场相关的动画实现,分别是Window切换动画、ActivityOptions转场动画和Fragment切换动画。
Window切换动画不是运行在本进程,是在SystemServer中由WindowManager实现,所以Window的切换动画可以完全不受App的UI线程影响,不过Window切换动画的功能比较有限,使用场景也很固定。
ActivityOptions转场动画是普通Window切换动画的升级版本,由于窗口动画是跨进程实现,所以可支持的功能非常有限;而ActivityOptions转场动画是在本进程实现的,可以支持非常多的动画效果,而且从源码可以看出,ActivityOptions的内部基本都利用属性动画实现,而且用到了部分不太常用的API,大家有兴趣可以阅读一下相关源码,应该会对动画优化很有帮助。
Fragment切换动画主要有两个小问题,一个是它默认的动画实现是补间动画,除非自己去重载Fragment的onCreateAnimator接口,这一点往往不容易被关注,而且很多时候我们也懒于去实现这个接口;另一点是动画start时间偏早,容易发生卡顿。
总之,虽然ActivityOptions转场动画和Fragment切换动画虽然都在不同维度上对Window切换动画进行了补充,但它们的性能表现还是不可同日而语的。很多时候,我们要评估打开一个新的页面是用一个新的Window实现(Activity、Dialog、Pop Window等)还是Add一个Fragment,过度动画的性能可能也是一个值得考虑的因素。
在L版本上,RippleCompount和RevealAnimator这两种动画就率先使用了Render动画;而在N版本上,AnimatedVectorDrawable也改用Render动画实现。
另外不知道大家有没有注意到,原生ProgressBar的默认样式就换成了用AnimatedVectorDrawable实现,可以说基本上解决了前文提到的——UI线程有耗时操作,无法进行Loading动画——这个问题。大家日后使用ProgressBar时,也可以多尝试用AnimatedVectorDrawable来实现。
QA反馈展开动画卡顿比较严重,遂进行分析,先看一下systrace。
首帧和尾帧的耗时是最容易被忽视的一点,首帧就是动画的第一帧,尾帧是动画的最后一帧,这两帧发生卡顿,往往最容易被用户注意到,却也最容易被开发忽视。 AnimatorListener的回调都是发生的doFrame过程中的。对于onAnimationEnd来说,回调触发时,最后一帧还没有绘制。 onAnimationEnd确实有间接初始化UI的操作,对应systrace中inflate的耗时。最简单的改法就是把onAnimationEnd中的耗时操作改为异步执行,先让动画绘制完成,再执行需要的耗时操作,简单修改后的systrace如下,尾帧终于顺利绘制出来了。 尾帧的丢帧往往都会是这种场景,因为我们在动画结束后,一般都需要处理一些页面转换或者其他善后操作,一旦没有注意调用时机,同时逻辑中有一些隐性的耗时,就会导致尾帧绘制不流畅。 首帧的优化与尾帧相比,有一个相反的原则——如果对尾帧来说,我们要保证先动画再切换,那对于首帧来说,就是先绘制再动画。
无论是利用Trace View,还是升级版的Android Profiler的CPU火焰图,都能较快的定位到耗时的函数。
自定义View动画过程主要是一个矩阵变换的过程。
-- 从View.draw是怎么走到对Animation的处理函数的:
View.draw(Canvas) —> ViewGroup.dispatchDraw(Canvas) —> ViewGroup.drawChild(Canvas, View, long) —> View.draw(Canvas, ViewGroup, long) —> View.applyLegacyAnimation(ViewGroup, long, Animation, boolean)
-- pre、set和post: 在Android中,已经为每种动画变换提供了pre、set和post三种操作方式。
set 用于设置Matrix中的值。
pre 是先乘,因为矩阵的乘法不满足交换律,因此先乘、后乘必须要严格区分。先乘相当于矩阵运算中的右乘。
post 是后乘,因为矩阵的乘法不满足交换律,因此先乘、后乘必须要严格区分。后乘相当于矩阵运算中的左乘。
在计算机图形应用涉及到几何变换,主要包括平移、旋转、缩放。以矩阵表达式来计算这些变换时,平移是矩阵相加,旋转和缩放则是矩阵相乘。那些数学大神们为了方便计算,所以引入了一样神器叫做齐次坐标(不懂的童鞋,老规矩自行搜索),将平移的加法合并用乘法表示。
> 动画渲染过程
分析Android属性动画源码的执行过程- https://www.jianshu.com/p/47f51e87e3ed
Android 属性动画:这是一篇很详细的 属性动画 总结&攻略- https://blog.csdn.net/carson_ho/article/details/72909894
动画播放由原来Activity级别降成View级别,Activity的动画是Window(窗口)级别的,系统对Activity动画播放原理上是通过矩阵变换并且是通过WindowManager所在进程执行,而改成View后切换动画则是通过本进程不断的重绘View自身来实现,效率上会降低。
在播放动画过程中,如果主线程刚好执行到此前通过定时器分发过来的一些较为耗时的任务,会导致动画丢帧,针对该问题,我们有自己的线程池及Handler消息队列管理,在播放过程中暂停Handler的消息派发及降低线程池内其他线程的优先级来解决。
-- 动画的绘制过程 Android;GIF图与动画。三种动画的绘制都要经历onLayout()/onMeasure()/onDraw()
GIF animations gif-movie-view- https://github.com/sbakhtiarov/gif-movie-view
-- 属性动画和一般的View动画的重要区别在于:
1.属性动画控制的是实实在在的属性,但是View动画只是产生一个动画,并没有改变控件的属性。
2.View动画只能控制View的属性,属性动画可以控制所有属性,只要这个属性有get/set 方法。
-- View动画是一种渐进式动画,定义动画开始和结束的两帧,并指定动画变化的时间和方式。并通过平移、缩放、旋转和透明度四种效果结合成复杂的动画效果。而在开始和结束帧之间插入的渐变值依据的是插值器。
帧动画是通过不停的切换图片实现动画效果。
属性动画是不停的改变对象的属性来实现动画效果。4.4新出的过渡动画只是对属性动画的一层封装。
自定义动画需要继承Animation类,并重写其initialize()以及applyTransformation()。前者用于一些初始化的操作,后者用于进行矩阵变换。
属性动画本质是通过改变新增的属性(如平移translationX/Y、缩放scaleX/Y、旋转rotationX/Y等)并刷新屏幕来实现动画效果,并且实现点击位置的实时改变。但是属性动画仍然不会修改原始的上下左右四个值。最后需要注意的是,属性动画不止用于View,还可以用于任何对象。
-- Animation框架定义了透明度、旋转、缩放、位移等几种常见的动画, 实现原理:
1.每次绘制View时,ViewGroup中的drawChild函数获取该view的Animation的Transformation值,然后调用canvas.concat
(transformToApply.getMatrix())
2.通过矩阵运算完成帧动画,如果动画没有完成,就继续调用invalidate() 函数,启动下次绘制来驱动动画,从而完成整个动画的绘制。
Android 动画机制与使用技巧- https://www.cnblogs.com/rocomp/p/5742056.html
Android帧动画实现,防OOM,比原生动画集节约超过十倍的资源- https://github.com/ansen360/FrameAnimation
View动画、帧动画和属性动画详解- https://blog.csdn.net/seu_calvin/article/details/52724655
Android 自定义属性动画&Camera动画- https://www.cnblogs.com/xgjblog/p/6283757.html
Android 属性动画:这是一篇很详细的 属性动画 总结&攻略- https://www.jianshu.com/p/2412d00a0ce4
补间动画通过不断的调用OnDraw方法来进行UI的绘制,而属性动画一般只调用ViewGroup进行绘制。
-- View动画绘制过程:
View动画先是调用View.setAnimation(Animation)方法给自己设置一个Animation对象,这个对象是View类中的一个名为mCurrentAnimation的成员变量。然后它调用invalidate()来重绘自己。
调用 invalidate() 方法之后,会让 ViewRootImpl 调用 scheduleTraversals() 发起一个重绘请求,通过 Choreographer 发送一个异步消息,同时在 Choreographer 中处理消息,最终回调到 performTraversals() 执行重绘。
当执行 performTraversals() 方法后,会进行重绘,最终会调用 view 的 draw() 函数进行绘制,在绘制函数中如果发现 getAnimation() 不为 null,将进行动画绘制,执行 applyLegacyAnimation() 方法。
View.startAnimation()到最终执行动画的一帧applyTransformation()的方法调用过程。
> matrix动画
Android中Matrix动画- https://blog.csdn.net/qq_23077365/article/details/51697621
浅析Android View动画高级实例探究- https://www.cnblogs.com/wondertwo/p/5295976.html
Android拍照适配问题,Matrix,夜间模式实现套路- https://github.com/D-clock/AndroidStudyCode
在 Android 开发中,矩阵是一个功能强大并且应用广泛的神器,例如:用它来制作动画效果、改变图片大小、给图片加各类滤镜等。
Android的Animations动画效果归根结底是利用cavas根据matrix对bitmap进行绘制,不同的matrix可让bitmap进行不同的显示,变换方式有平移、旋转、伸缩、扭曲这四种。
初只有两种动画即逐帧动画(frame-by-frame animation)和补间动画(tweened animation),补间动画只需要开发者设置动画的起始值和结束值,中间的动画由系统自动帮我们完成。
从Android 3.x开始谷歌引入了一种全新的动画—属性动画,只要是一个Object对象属性动画都能为其设置动画属性,不管这个对象能不能看得见,会不会与用户产生交互。
-- 利用matrix进行自定义动画的方法:
1、重写控件onTouchEvent方法,然后检测手势移动的距离,根据移动的距离和方向为matrix设置不同的值,让bitmap产生动画
2、自定义一个Animation,重写其applyTranslation方法,在Animation的duration内该方法会不断调用,所以可在applyTranslation可以不断地改变matrix的值,近而让bitmap产生动画。
public class SimpleCustomAnimation extends Animation {
private int mWidth, mHeight;
@Override
public void initialize(int width, int height, int parentWidth, int parentHeight) {
super.initialize(width, height, parentWidth, parentHeight);
this.mWidth = width;
this.mHeight = height;
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
Matrix matrix = t.getMatrix();
matrix.preScale(interpolatedTime, interpolatedTime);//缩放
matrix.preRotate(interpolatedTime * 360);//旋转
//下面的Translate组合是为了将缩放和旋转的基点移动到整个View的中心,不然系统默认是以View的左上角作为基点
matrix.preTranslate(-mWidth / 2, -mHeight / 2);
matrix.postTranslate(mWidth / 2, mHeight / 2);
}
}
* QQ抖一抖特效的自定义View动画实现
* Created by wondertwo on 2016/3/17.
*/
public class QQTrembleAni extends Animation {
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
t.getMatrix().setTranslate(
(float) Math.sin(interpolatedTime * 50) * 8,
(float) Math.sin(interpolatedTime * 50) * 8
);// 50越大频率越高,8越小振幅越小
super.applyTransformation(interpolatedTime, t);
}
}
> 动画插值器
动画插值器Interpolator源码及图解- http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0110/2292.html
Android动画-Interpolator(插值器)大全- http://blog.csdn.net/cjh_android/article/details/51508634
public class AccelerateInterpolator implements Interpolator {
private final float mFactor;
private final double mDoubleFactor;
public AccelerateInterpolator() {
mFactor = 1.0f;
mDoubleFactor = 2.0;
}
/**
* Constructor
*
* @param factor Degree to which the animation should be eased. Seting
* factor to 1.0f produces a y=x^2 parabola. Increasing factor above
* 1.0f exaggerates the ease-in effect (i.e., it starts even
* slower and ends evens faster)
*/
public AccelerateInterpolator(float factor) {
mFactor = factor;
mDoubleFactor = 2 * mFactor;
}
public AccelerateInterpolator(Context context, AttributeSet attrs) {
TypedArray a =
context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.AccelerateInterpolator);
mFactor = a.getFloat(com.android.internal.R.styleable.AccelerateInterpolator_factor, 1.0f);
mDoubleFactor = 2 * mFactor;
a.recycle();
}
public float getInterpolation(float input) {
if (mFactor == 1.0f) {
return input * input;
} else {
return (float)Math.pow(input, mDoubleFactor);
}
}
}
-- 由此SDK中扩展了另外几个常用Interpolator类,分别是:
1..AccelerateDecelerateInterpolator 在动画开始与结束的地方速率改变比较慢,在中间的时候加速,先加速后减速的过程
2.AccelerateInterpolator 在动画开始的地方速率改变比较慢,然后开始加速,变化率是一个加速的过程。
3.AnticipateInterpolator 开始的时候向后然后向前甩
4.AnticipateOvershootInterpolator 开始的时候向后然后向前甩一定值后返回最后的值
5.BounceInterpolator 动画结束的时候弹起
6.CycleInterpolator 动画循环播放特定的次数,速率改变沿着正弦(sin)曲线
7.DecelerateInterpolator 在动画开始的地方快然后慢,变化率是一个减速的过程。
8.LinearInterpolator:动画从开始到结束,变化率是线性变化。
9.OvershootInterpolator 向前甩一定值后再回到原来位置
-- 自定义动画:
public class SimpleCustomAnimation extends Animation {
private int mWidth, mHeight;
@Override
public void initialize(int width, int height, int parentWidth, int parentHeight) {
super.initialize(width, height, parentWidth, parentHeight);
this.mWidth = width;
this.mHeight = height;
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
Matrix matrix = t.getMatrix();
matrix.preScale(interpolatedTime, interpolatedTime);//缩放
matrix.preRotate(interpolatedTime * 360);//旋转
//下面的Translate组合是为了将缩放和旋转的基点移动到整个View的中心,不然系统默认是以View的左上角作为基点
matrix.preTranslate(-mWidth / 2, -mHeight / 2);
matrix.postTranslate(mWidth / 2, mHeight / 2);
}
}
-- android 3D旋转效果Camera类实现
public class _3DAnimation extends Animation {
private float mFromDegrees;
private float mToDegrees;
private float mCenterX;
private float mCenterY;
private Camera mCamera;
public _3DAnimation(float fromDegress,float toDegress){
this.mFromDegrees=fromDegress;
this.mToDegrees=toDegress;
}
@Override
public void initialize(int width, int height, int parentWidth, int parentHeight) {
super.initialize(width, height, parentWidth, parentHeight);
this.mCenterX=width/2;
this.mCenterY=height/2;
mCamera=new Camera();
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
super.applyTransformation(interpolatedTime, t);
final float fromDegrees = mFromDegrees;
float degrees = fromDegrees + (mToDegrees - mFromDegrees) * interpolatedTime;
final Matrix matrix = t.getMatrix();
//interpolatedTime 0~1变化
mCamera.save();
mCamera.rotateY(degrees);
mCamera.getMatrix(matrix);
mCamera.restore();
matrix.preTranslate(-mCenterX, -mCenterY);//相机位于(0,0),移动图片,相机位于图片中心
matrix.postTranslate(mCenterX, mCenterY);
}
}