我们知道视图动画的启动方式如下:
ImageView image = (ImageView) findViewById(R.id.image);
Animation hyperspaceJump = AnimationUtils.loadAnimation(this, R.anim.hyperspace_jump);
image.startAnimation(hyperspaceJump);
也即从调用view.java类的startAnimation()函数开始,我们先分析如下的函数,drawAnimation()函数是真正通过不断的刷新视图内容绘制一个动态的视图的地方,怎么从startAnimation()到drawAnimation()我们后面再讲,我们的重点是放在视图动画的绘制原理上。
private boolean drawAnimation(ViewGroup parent, long drawingTime,
Animation a, boolean scalingRequired) {
Transformation invalidationTransform;
final int flags = parent.mGroupFlags;
final boolean initialized = a.isInitialized();
if (!initialized) {
a.initialize(mRight - mLeft, mBottom - mTop, parent.getWidth(), parent.getHeight());
a.initializeInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop);//初始化了一个矩形区域,mRight,mLeft,mBottom,mTop是根据该View得到的区域参数
if (mAttachInfo != null) a.setListenerHandler(mAttachInfo.mHandler);
onAnimationStart();
}
boolean more = a.getTransformation(drawingTime, parent.mChildTransformation, 1f);//第一个重要函数,后面源码分析,这里我们关注parent.mChildTransformation这个参数
if (scalingRequired && mAttachInfo.mApplicationScale != 1f) {
if (parent.mInvalidationTransformation == null) {
parent.mInvalidationTransformation = new Transformation();
}
invalidationTransform = parent.mInvalidationTransformation;
a.getTransformation(drawingTime, invalidationTransform, 1f);//比较这个if-else,不管走if还是else,我们可以看到它都走了a.getTransformation这个函数。
} else {
invalidationTransform = parent.mChildTransformation;//不管是if,还是else,invalidationTransform的值都来自于对getTransformation的调用
}
if (more) {
if (!a.willChangeBounds()) {//除了alphaAnimation,其它的该函数都return true
if ((flags & (ViewGroup.FLAG_OPTIMIZE_INVALIDATE | ViewGroup.FLAG_ANIMATION_DONE)) ==
ViewGroup.FLAG_OPTIMIZE_INVALIDATE) {
parent.mGroupFlags |= ViewGroup.FLAG_INVALIDATE_REQUIRED;
} else if ((flags & ViewGroup.FLAG_INVALIDATE_REQUIRED) == 0) {
// The child need to draw an animation, potentially offscreen, so
// make sure we do not cancel invalidate requests
parent.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
parent.invalidate(mLeft, mTop, mRight, mBottom);//对于AlphaAnimation,视图大小没有任何变化
}
} else {
if (parent.mInvalidateRegion == null) {
parent.mInvalidateRegion = new RectF();
}
final RectF region = parent.mInvalidateRegion;
a.getInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop, region,
invalidationTransform);//第二个重要函数,举行参数region动态的得到了下一桢的区域大小
// The child need to draw an animation, potentially offscreen, so
// make sure we do not cancel invalidate requests
parent.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
final int left = mLeft + (int) region.left;//这两个final值不断的更新得到新的区域值
final int top = mTop + (int) region.top;
parent.invalidate(left, top, left + (int) (region.width() + .5f),//绘制,并刷新下一桢,得到下一个区域大小。
top + (int) (region.height() + .5f));
}
}
return more;
}
通过上面这个函数可以了解到,视图通过不断的刷新并得到Animation提供的视图区域的大小的参数而绘制出动态视图。下面来看看上面提到的两个重要函数,通过这两个函数,可以看出Animation是如何提供动态的区域参数的。
/**
* Gets the transformation to apply at a specified point in time. Implementations of this
* method should always replace the specified Transformation or document they are doing
* otherwise.
*
* @param currentTime Where we are in the animation. This is wall clock time.
* @param outTransformation A transformation object that is provided by the
* caller and will be filled in by the animation.
* @return True if the animation is still running
*/
//上面的这段注释清晰的说明了这个函数的功能————在某个指定的时间点获取一个Transformation对象
public boolean getTransformation(long currentTime, Transformation outTransformation) {
if (mStartTime == -1) {
mStartTime = currentTime;
}
final long startOffset = getStartOffset();
final long duration = mDuration;
float normalizedTime;
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;
}
final boolean expired = normalizedTime >= 1.0f;
mMore = !expired;
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 (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);
}
...
}
所有继承自Animation类的函数都需要重写applyTransformation()函数,正是在这个函数中,指定的不同的动画效果所需要的变化。我们以TranslateAnimation的该函数作为示例:
protected void applyTransformation(float interpolatedTime, Transformation t) {
float dx = mFromXDelta;
float dy = mFromYDelta;
if (mFromXDelta != mToXDelta) {
dx = mFromXDelta + ((mToXDelta - mFromXDelta) * interpolatedTime);
}
if (mFromYDelta != mToYDelta) {
dy = mFromYDelta + ((mToYDelta - mFromYDelta) * interpolatedTime);
}
t.getMatrix().setTranslate(dx, dy);
}
Transformation.java类中有两个属性:Matrix和Alpha(float),以及一些改变这两个属性值的方法。上面这里还有一个疑问,就是插值器是如何工作的,留到后面再做解释,我们先看看第二个重要函数,即动态的得到了上面的Transformation对象后是如何得到动态绘制区域的。
public void getInvalidateRegion(int left, int top, int right, int bottom,
RectF invalidate, Transformation transformation) {
final RectF tempRegion = mRegion;
final RectF previousRegion = mPreviousRegion;
invalidate.set(left, top, right, bottom);
transformation.getMatrix().mapRect(invalidate);
// Enlarge the invalidate region to account for rounding errors
invalidate.inset(-1.0f, -1.0f);
tempRegion.set(invalidate);
invalidate.union(previousRegion);
previousRegion.set(tempRegion);
final Transformation tempTransformation = mTransformation;
final Transformation previousTransformation = mPreviousTransformation;
tempTransformation.set(transformation);
transformation.set(previousTransformation);
previousTransformation.set(tempTransformation);
}
根据上面这些代码,可以窥见到Animation.java是如何影响到view不停刷新绘制,从而得到动态效果的。下面解释前面的一些疑问:
先来看看插值器的工作原理
public class AccelerateInterpolator implements Interpolator {
private final float mFactor;
private final double mDoubleFactor;
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);//Math.pow(x, y)——计算x的y次方
}
}
}
插值器的内容很简单,只需要实现getInterpolation(float input)这个接口即可,这个接口的输入input是根据当前时间得到的一个值,每次刷新得到的值不同,因此输出也就成了一个动态值。因此我们可以得知插值器的原理——输出是时间输入的函数。知晓这个原理,我们就可以编写自己的插值器。下面的内容是前面出现的getTransformation()中的interpolatedTime,也就是input。从下面的这段代码中,我们可以看见它是和当前时间currentTime相关的。 if (mStartTime == -1) {
mStartTime = currentTime;
}
final long startOffset = getStartOffset();
final long duration = mDuration;
float normalizedTime;
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;
}