在android系统中动画分为两种分别是基础动画和属性动画。对于动画的工作原理主要涉及到的是基础动画和属性动画的实现。
本章主要分两大块:基础动画和属性动画
对于基础动画的实现主要是嵌套在View的绘制流程中的,基础动画主要有旋转RotatleAnimation,缩放ScaleAnimation,透明AlphaAnimation,平移TranslateAnimation等都是Animation的子类。
基础动画的实现主要涉及到两个类Animation 和 Transformation。
Animation主要代表动画类型运行状态,Transformation 是对动画的计算和保存。
1》基础动画流程
我们从ScaleAnimation子类入手做例子,其他类似。
先看使用方式:
Animation animation = AnimationUtils.loadAnimation(MainActivity.this,R.anim.viewanimation);
View.startAnimation(animation);
loadAnimation()主要是创建ScaleAnimation对象这里不多解释,主要看下startAnimation()方法。
public void startAnimation(Animation animation) {
animation.setStartTime(Animation.START_ON_FIRST_FRAME);
setAnimation(animation);
invalidateParentCaches();
invalidate(true); //主要出发刷新的动作
}
这个方法中主要涉及到 invalidate(true); 刷新的机制。这里简单引入invalidate(true);出发的流程,后面章节介绍Activity窗口的创建显示时会详细介绍。
invalidate(true);的作用主要是会把需要重绘的矩形区域标记成dirty区域提交到ViewRootImpl中等待下一次VSYS信号来刷新遍历view树。
大致流程如下:
对于整个View的绘制流程本章不多介绍主要讲一下流程中设计到动画相关代码,从 applyLegacyAnimation() 开始涉及到动画的实现 ,调用
boolean more = a.getTransformation(drawingTime, t, 1f);
继续到Animation中看
public boolean getTransformation(long currentTime, Transformation outTransformation) {
//开始之后的等待时间
final long startOffset = getStartOffset();
final long duration = mDuration; //动画总时间
float normalizedTime; //时间归一化 (0~1)
if (duration != 0) {
normalizedTime = ((float) (currentTime - (mStartTime + startOffset))) /
(float) duration;
} else {
// time is a step-change with a zero duration
normalizedTime = currentTime < mStartTime ? 0.0f : 1.0f;
}
if (!mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);
if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) { //在动画运行周期内
if (!mStarted) {
fireAnimationStart();
mStarted = true;
if (NoImagePreloadHolder.USE_CLOSEGUARD) {
guard.open("cancel or detach or getTransformation");
}
}
if (mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);
if (mCycleFlip) {
normalizedTime = 1.0f - normalizedTime;
}
//插值器修改时间返回
final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);
//调用子类的实现
applyTransformation(interpolatedTime, outTransformation);
}
.......
return mMore;
}
这是主要的方法,主要做了两件事1.对总时长进行归一化转换到0~1。2.调用applyTransformation()回调子类实现。看代码解释。接下里看看applyTransformation()实现。
protected void applyTransformation(float interpolatedTime, Transformation t) {
//mFromX 开始位置,mToX结束位置
if (mFromX != 1.0f || mToX != 1.0f) {
//sx 当前时间应该显示的位置
sx = mFromX + ((mToX - mFromX) * interpolatedTime);
}
if (mFromY != 1.0f || mToY != 1.0f) {
sy = mFromY + ((mToY - mFromY) * interpolatedTime);
}
// 把当前显示的值保存到Transformaiton中的Matrix中去,等待使用
if (mPivotX == 0 && mPivotY == 0) {
t.getMatrix().setScale(sx, sy);
} else {
t.getMatrix().setScale(sx, sy, scale * mPivotX, scale * mPivotY);
}
}
看代码可以看出此段代码主要计算当前时间点应该显示的状态,然后保存到Matrix等待使用。
当前时间点的值已经计算完成接下来就看怎么使用Matrix中保存的值,继续回到View的draw(3)3个参数的方法
if (transformToApply != null) {
if (concatMatrix) {
if (drawingWithRenderNode) { //硬件绘制使用Matrix
renderNode.setAnimationMatrix(transformToApply.getMatrix());
} else {
// 软件绘制Canvas使用Matrix转换画布状态
canvas.translate(-transX, -transY);
canvas.concat(transformToApply.getMatrix());
canvas.translate(transX, transY);
}
}
这个段代码主要是使用我们动画保存的Matrix到Canvas上,发现没有动画并没有直接作用到VIew上 而是作用到view对应的画布上,这也是为什么基础动画并不能真正改变view的属性的原因。
Android 基础动画的工作原理到此结束,接下来让我们看看,属性动画的工作原理。
对于属性动画主要对应与抽象类Animator 以及子类ValueAnimator,孙子类ObjectAnimator对象,这里主要以ValueAnimator引入动画原理流程。其实属性动画的实现思想和基础动画的思想很像。为了不影响我们看源码的心情,其他reapet,restart等等状态我们都先不考虑,先看主要的,不然你会痛苦万分,然后放弃。
1>属性动画的简单使用
从API的使用入手
ValueAnimator animator = ValueAnimator.ofFloat(0,10,20);//起止位置
animator.setDuration(); //动画总时长
animator.setInterpolator(); //时间插值器,对时间效果进行模拟
animator.setEvaluator(); //结果插值器,对计算结果进行模拟
animator.addUpdateListener();//动画value更新
animator.start(); //启动动画
从api使用可以看出属性动画的流程可以分两部分:1.封装PropertyValueHolder对象、2.start启动动画等待刷新
对于调用的流程就看流程图就行,简单的代码就不一一贴到博客中防止篇幅过长,对于重要的代码在流程图下方会贴出并解释。
1.封装PropertyValueHolder对象
在KeyFrameSet 关键帧中根据传入的float数组值进行分数转换到0~1,这和基础动画时间归一化和相似。
public static KeyframeSet ofFloat(float... values) {
int numKeyframes = values.length;
FloatKeyframe keyframes[] = new FloatKeyframe[Math.max(numKeyframes,2)];
if (numKeyframes == 1) {
....无法执行动画...
} else {
// 取出第一个value值 ,并把值转换到keyframes[0]中
keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f, values[0]);
// 从第二帧开始 转换成小于 1 的分数值,保存。
for (int i = 1; i < numKeyframes; ++i) {
keyframes[i] =
(FloatKeyframe) Keyframe.ofFloat((float) i / (numKeyframes - 1), values[i]);
if (Float.isNaN(values[i])) {
badValue = true;
}
}
}
//封装返回
return new FloatKeyframeSet(keyframes);
}
2.start启动动画等待刷新
启动start方法之后
private void start(boolean playBackwards) {
.....重复播放,可逆等省略......
//1.主要代码添加回调,链接Choreograther 进行等待VSYS垂直同步信号的刷新
addAnimationCallback(0);
if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
startAnimation();
if (mSeekFraction == -1) {
// 2.从0开始播放动画
setCurrentPlayTime(0);
} else {
// 从某一个进度开始播放动画,目前不考虑
setCurrentFraction(mSeekFraction);
}
}
}
在start方法中主要涉及到两个重要的方法:1.addAnimationCallback(0); 得到线程唯一的 AnimationHandler 封装了Choreograther 信号。2.setCurrentPlayTime(0); 开始从0播放动画。先看1.addAnimationCallback(0);
根据流程图看,这里直接到doframe()方法
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
// 回调一帧动画
doAnimationFrame(getProvider().getFrameTime());
// 动画未运行完,需要等待下一帧屏幕刷新
if (mAnimationCallbacks.size() > 0) {
getProvider().postFrameCallback(this);
}
}
};
当屏幕收到VSYS屏幕的垂直刷新同步信号过来之后进行刷新动画的值,信号大约16ms发送一次从SurfaceFlinger触发。
继续走到 animateBasedOnTime()
boolean animateBasedOnTime(long currentTime) {
if (mRunning) {
final long scaledDuration = getScaledDuration();//总时长
// 计算当前时间在总时间的站比
final float fraction = scaledDuration > 0 ?
(float)(currentTime - mStartTime) / scaledDuration : 1f;
final float lastFraction = mOverallFraction;
.............判断是否有 repeat 重播 省略............
// 判断值是否在0~1之间
mOverallFraction = clampFraction(fraction);
//判断是否进入了repeat循环播放
float currentIterationFraction = getCurrentIterationFraction(
mOverallFraction, mReversing);
// 真正开始计算
animateValue(currentIterationFraction);
}
return done;
}
这个方法主要是判断当前时间是否在0~1直接,并且是否进入了repeat模式
void animateValue(float fraction) {
fraction = mInterpolator.getInterpolation(fraction);
mCurrentFraction = fraction;
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
//根据当前分数开始计算
mValues[i].calculateValue(fraction);
}
if (mUpdateListeners != null) {
int numListeners = mUpdateListeners.size();
for (int i = 0; i < numListeners; ++i) {
// 把值返回给动画使用着,刷新属性
mUpdateListeners.get(i).onAnimationUpdate(this);
}
}
}
继续进入 calculateValue() 方法
void calculateValue(float fraction) {
// 得到当前时间的值
Object value = mKeyframes.getValue(fraction);
// 赋值,等待回调给动画使用者
mAnimatedValue = mConverter == null ? value : mConverter.convert(value);
}
下面最后一步 getValue() 方法中计算
public float getFloatValue(float fraction) {
if (fraction <= 0f) {
......动画错误.........
} else if (fraction >= 1f) {
.......动画错误.............
}
// 真正开始计算当前时间点的值
FloatKeyframe prevKeyframe = (FloatKeyframe) mKeyframes.get(0);
for (int i = 1; i < mNumKeyframes; ++i) {
FloatKeyframe nextKeyframe = (FloatKeyframe) mKeyframes.get(i);
//首先确定当前时间点所在的传入的值区间
if (fraction < nextKeyframe.getFraction()) {
final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
// 在当前区间范围内的比值
float intervalFraction = (fraction - prevKeyframe.getFraction()) /
(nextKeyframe.getFraction() - prevKeyframe.getFraction());
float prevValue = prevKeyframe.getFloatValue();
float nextValue = nextKeyframe.getFloatValue();
// Apply interpolator on the proportional duration.
if (interpolator != null) {
// 根据插值器计算当前时间对应的插值器处理后的时间
intervalFraction = interpolator.getInterpolation(intervalFraction);
}
// 跟据当前插值器处理后的时间,计算当前区间上的值 value 然后返回
return mEvaluator == null ?
prevValue + intervalFraction * (nextValue - prevValue) :
((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
floatValue();
}
prevKeyframe = nextKeyframe;
}
// shouldn't get here
return ((Number)mKeyframes.get(mNumKeyframes - 1).getValue()).floatValue();
}
这个方法有点长不过只看动画计算的部分,其他部分都是动画为开始或者超过了动画的运行时间,不考虑。
这个方法比较多,主要分三部分:
至此关于Android动画的工作原理几经梳理清楚,后面会梳理一个Acitvity创建和显示流程,也会和基础动画的部分重叠。
对于Interpolator 和Evaluator 可以去了解了解,可以自定义,也可以使用系统定义的。