我们都知道安卓补间动画的使用,系统为我们封装了几个基本的动画,也就是ScaleAnimation,AlphaAnimatioon,RotateAnimation,TranslateAnimation,它们都是继承了Animation类,然后实现了
applyTransformation()方法,然后通过Transformation转换类和Matrix矩形类实现了各种各样的动画效果。
那么补间动画执行的原理是怎么样的呢?我们从使用的角度去查看源码来分析原理。这里以RotateAnimation来分析:
围绕自身顺时针旋转了500度,旋转了2000ms,最后效果。
下面我们源码进行分析:
从创建rotateAnimation到设置属性然后调用mButton.startAnimation(rotateAnimation);
首先通过setStartTime()设置了动画的开始时间,表示开始时间应该是当前第一个动画帧调用。
下面看第二个方法:
这里也只是对一些变量进行赋值。
下面又调用了setStratTime主要是针对屏幕关闭,开始时间不是我们绘制的下一帧,保持START_ON_FIRST_FRAME开始时间会导致动画在屏幕重新打开开始。(我也不太理解目前)
下面看第三个方法
这里也就是用|运算相当于增加一个标志位PFLAG_INVALIDATED。到目前为止还没有执行补间动画的逻辑。
继续看最后一个方法:
我们主要看p.invalidateChild(this,damage)方法,这个方法是ViewParent接口的一个抽象方法。
p.invalidateChild(this,damage)这里的this就是View, 调用父容器的方法,也就是ViewGroup的invalidateChild方法。
然后ViewGroup实现了这个接口
在do{}while (parent !=null);循环中不断地执行 parent = parent.invalidateChildInParent(location, dirty);
直到parent==null时候,所以它一直在寻找mPrent,而View树的最顶端的mParent就是ViewRootImpl,最后会走到ViewRootImpl的invalidateChildParent里面去
接着调用invalidateRectOnScreen(dirty);这里面会调用scheduleTraversals()
这里会抛一个mTraversalRunnable
主要看mTraversalRunnable,我们找到mTraversalRunnable这个类
这里就调用doTraversal方法。
所以说,ScheduleTraverals()是将performTraversals放到一个Runnable里面,在Choreographer的待执行队列里面,这些待执行的runnable会在最近的一个16.6ms屏幕刷新信号到来的时候执行,而performTraversals()是View的三大操作:测量,布局,绘制的发起者。
在Android屏幕刷新机制里,View树里面不管哪个View发起的绘制请求或者布局请求都会走到ViewRootImpl的schedTraversals()里面,然后再最新的一个屏幕刷新信号来到时,再通过ViewRootImpl的performTraversals从根布局DercorView依次遍历View树去执行测量,布局,绘制三大操作,这就是为什么要求布局不能层次太深,因为每一次的刷刷都会走到ViewRootImpl里面,然后在层层遍历到发生改变的View里去执行相应的布局和绘制操作。
下面我们来看下真正动画执行的地方:
这里的getAnimation()
我们搜索mCurrentAnimation = 只有在setAnimation这里赋值了的,下面继续看
more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired); 这条语句。
前面的a.initialize()和onAnimationStart()主要是动画的初始化和开始。下面继续看boolean more = a.getTransformation(drawingTime, t, 1f);这个语句
1 记录动画第一帧的时间
2 计算动画进度:(已经绘制的时间-动画开始的时间)/动画时长
3将动画的进度控制在0~1之间
最后把normalizedTime放在getTransformationAt里面执行,
根据插值器计算实际的动画进度。然后调用了一个空方法,需要动画的子类重写该方法,实现动画逻辑。
上述几个方法主要就是记录动画第一帧的时间,计算动画进度值,将动画进度控制在0~1之间,根据插值器计算动画的实际进度,最后调用了applyTransformation这个空方法执行动画的具体逻辑。
在这个方法中根据传入的interpoltedTime计算需要动画操作的参数,然后由Tranforation和Matrix做动画操作。
但是applyTransformation被调用的地方没有循环,那么一次View树的遍历绘制也就执行一次,它是怎么多次被回调的呢?
这里当more为true时,就会再调用invalidate方法,层层通知ViewRootImpl再发起一次遍历请求,当下一个屏幕刷新信号的时候,再通过preforTraversals遍历View树的绘制,view的draw方法被执行,然后applyLegacyAnimation方法执行相关操作,以及getTransformation计算动画进度,applyTransformation()执行动画逻辑等。
什么时候mMore为false呢,当动画进度>=1.0时候或者动画取消,那么就不会发起invalidate请求了。
本篇总结
1 当调用View.startAnimation(Animation)时,并没有立即执行动画,而是通过invalidate层层通过ViewRootImpl发起一次遍历View树的请求,在接收到下一个(16.6ms)屏幕信号刷新时才发起遍历View树的绘制操作,当View绑定有动画时,则执行applyLegacyAnimation方法处理相关动画逻辑。
2 applyLegacyAnimation里面,先执行初始化initalize(),再通知动画开始onAnimationStart,然后通过getTransformation计算动画进度,并且它的返回值和动画是否结束决定是否继续通知ViewRootImpl发起遍历请求,如此重复步骤,并且调用applyTransformation方法执行动画的逻辑,直到动画结束。
3 其实补间动画仅仅改变的是控件的位置,并没有改变控件本身的值,在View被draw时Parent View改变它的绘制参数,这样View的大小位置角度等虽然变化了,但是View的实际属性并没有改变。
网友评论