在 Android 的 Animation 支持下,衍生出了 ScaleAnimation / AphlaAnimation / 3D Animation …… 及各种自定义 Animation 。Animation 优秀的设计简化和方便了动画的定制(当然也有一些不通用的代码)。
Animation 都是在 View 层就完成的,所以代码原理比较易懂。以下代码分析是建立在 AOSP 6.0 的源码上。
一、调用链路
关键链路 api :在 View 执行 Animation 时候需要的 api
// 初始化 Animation 中的 Transformation (变化)
void initializeInvalidateRegion(int left, int top, int right, int bottom);
// 获取 Animation 执行后应该重绘的区域
void getInvalidateRegion(int left, int top, int right, int bottom, RectF invalidate, Transformation transformation);
// 根据当前时间获取 Animation 中的 Transformation (变化)
boolean getTransformation(long currentTime, Transformation outTransformation, float scale);
复制代码
调用路径
Animation 的绘制是跟随 View 的绘制周期。关键调用链路如下(中间省略大部分):
ViewRootImpl.performDraw() → ViewGroup.drawChild() → View.draw()
来到 View 的 draw 方法,与 Animation 相关的关键思路:
- Vsync 周期,调用 View draw 方法,当前 View Animation 不为空;
- Animation 未初始化,通过 initialize() 方法重置内部属性,initializeInvalidateRegion() 方法,初始化 Animation 第一帧(受 FillBefore 影响),继而发起 onAnimationStart() 通知;
- 根据当前时间通过 Animation.getTransformation() 方法,获取属性在时间轴上的变化和 Animation 是否结束。
- Animation 还需要继续进行,根据 Animation 是否会引起绘制区域改变,请求父亲相应重绘 api,以便下一帧 Animation 能继续进行。
- 当 Animation 会改变矩阵,则将矩阵应用到 Canvas 或者是 RenderNode。
- 后续正常 draw 绘制流程,包括裁剪可用区域,背景、内容、前景。
关键代码和注释 View.java
// 启动 Animation
public void startAnimation(Animation animation) {
// 设置开始时间为下一帧
animation.setStartTime(Animation.START_ON_FIRST_FRAME);
setAnimation(animation);
// 触发重绘
invalidateParentCaches();
invalidate(true);
}
// 设置 Animation
public void setAnimation(Animation animation) {
mCurrentAnimation = animation;
if (animation != null) {
// 在屏幕是暗的情况下,如果 Animation 设置了下一帧启动,由于不会触发重绘,所以需要设置
// 启动时间为当前时间,否则 Animation 的启动时间会等到屏幕亮起后被设置
if (mAttachInfo != null && mAttachInfo.mDisplayState == Display.STATE_OFF
&& animation.getStartTime() == Animation.START_ON_FIRST_FRAME) {
animation.setStartTime(AnimationUtils.currentAnimationTimeMillis());
}
animation.reset();
}
}
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
/* 省略... */
boolean more = false;
final boolean childHasIdentityMatrix = hasIdentityMatrix();
/* 省略... */
Transformation transformToApply = null;
boolean concatMatrix = false;
final boolean scalingRequired = mAttachInfo != null && mAttachInfo.mScalingRequired;
// 获取 Animation
final Animation a = getAnimation();
if (a != null) {
// 进行余下的 Animation
more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
concatMatrix = a.willChangeTransformationMatrix();
if (concatMatrix) {
mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
}
// parent.getChildTransformation() 获取的矩阵就是 applyLegacyAnimation() 方法时
// 操作的矩阵。(这个写法真的很奇怪,因为 transformation 本身就可以直接跟着当前 view,代码中也
// 没有其他和父亲耦合的逻辑)
transformToApply = parent.getChildTransformation();
} else {
/* 省略... */
if (transformToApply != null
/* 省略... */
if (transformToApply != null || !childHasIdentityMatrix) {
/* 省略... */
if (transformToApply != null) {
if (concatMatrix) {
if (drawingWithRenderNode) {
// 如果使用的是硬件绘制,将 Transform 变化结果应用到 renderNode
renderNode
.setAnimationMatrix(transformToApply.getMatrix());
} else {
// 如果使用的是软件绘制,将 Transform 变化结果应用到 Canvas
canvas.translate(-transX, -transY);
canvas.concat(transformToApply.getMatrix());
canvas.translate(transX, transY);
}
parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
}
/* 省略... */
}
private boolean applyLegacyAnimation(ViewGroup parent, long drawingTime,
Animation a, boolean scalingRequired) {
Transformation invalidationTransform;
final int flags = parent.mGroupFlags;
final boolean initialized = a.isInitialized();
if (!initialized) {
// 初始化 Animation
a.initialize(mRight - mLeft, mBottom - mTop, parent.getWidth(), parent.getHeight());
a.initializeInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop);
if (mAttachInfo != null) a.setListenerHandler(mAttachInfo.mHandler);
// 触发 View onAnimationStart 回调
onAnimationStart();
}
final Transformation t = parent.getChildTransformation();
// 获取 Animation 对于矩阵的变化,more 代表 Animation 未结束
boolean more = a.getTransformation(drawingTime, t, 1f);
/* 省略... */
if (more) {
if (!a.willChangeBounds()) {
// 当 Animation 不会涉及到本身视图大小的改变时,重绘 view 本身大小区域
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);
}
} else {
// 当 Animation 涉及到本身视图大小的改变时,重绘 Animation 给出的重绘区域
if (parent.mInvalidateRegion == null) {
parent.mInvalidateRegion = new RectF();
}
final RectF region = parent.mInvalidateRegion;
// 获取需要重新绘制的区域大小
a.getInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop, region,
invalidationTransform);
// 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 int top = mTop + (int) region.top;
parent.invalidate(left, top, left + (int) (region.width() + .5f),
top + (int) (region.height() + .5f));
}
}
return more;
}
复制代码
二、内部实现细节
有一些不为人知的逻辑,必须要通过了解内部实现!下面涉及的代码均在 Animation.java。
关键属性
// 如果当 FillEnabled = true, FillBefore 的设置才能生效
void setFillEnabled(boolean fillEnabled);
// 在动画结束之后保持最后一帧
void setFillAfter(boolean fillAfter);
// 在动画开始之前将第一帧应用在 View 上
void setFillBefore(boolean fillBefore);
// 设置动画开始时的延时,每一次循环都会延时
void setStartOffset(long startOffset);
// 设置重复模式,支持 RESTART 重新开始 / REVERSE 不断反转
void setRepeatMode(int repeatMode);
// 设置重复次数
void setRepeatCount(int repeatCount);
// 设置差值器,决定动画在时间轴的变化率
void setInterpolator(Interpolator i);
复制代码
内部实现原理
View 给出的时间和开始时间和延时时间进行相减,在与设置的时长进行归一化,将归一化时间交给差值器处理,获得变化率,再交给子类的 applyTransformation 去根据这个变化率对矩阵等属性进行任意的变化。
关键 api
// 该方法是每次帧重绘都会调用
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 {
// 瞬时变化
normalizedTime = currentTime < mStartTime ? 0.0f : 1.0f;
}
// 判断动画是否已经结束
final boolean expired = normalizedTime >= 1.0f;
mMore = !expired;
// 如果没有设置 FillEnable,归一化时间一定会在[0,1]的范围,也就是如果 FillBefore 设置成 false了
// 仍然会提前绘制第一帧
if (!mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);
// 当归一化时间在[0, 1],或者 FillBefore = true 或者 FillAfter = true,进行动画内容填充
if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) {
if (!mStarted) {
fireAnimationStart();
mStarted = true;
if (USE_CLOSEGUARD) {
guard.open("cancel or detach or getTransformation");
}
}
// 将时间限制在[0,1]
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);
}
if (expired) {
if (mRepeatCount == mRepeated) {
// 动画结束
if (!mEnded) {
mEnded = true;
guard.close();
fireAnimationEnd();
}
} else {
if (mRepeatCount > 0) {
mRepeated++;
}
// 设置周期反转
if (mRepeatMode == REVERSE) {
mCycleFlip = !mCycleFlip;
}
// 重新开始新的周期,重设开始时间
mStartTime = -1;
mMore = true;
fireAnimationRepeat();
}
}
// OneMoreTime 用于绘制最后一帧,也就是 mPreviousTransformation。具体原因请看
// initializeInvalidateRegion() 方法
if (!mMore && mOneMoreTime) {
mOneMoreTime = false;
return true;
}
return mMore;
}
// 真正根据变化率进行属性变化的 api
protected void applyTransformation(float interpolatedTime, Transformation t) {
}
// 根据给定的区域进行初始化,名字是这样,但真正做的,也就保存了一下更新区域,在之后,有对象来获取
// 更新区域时再使用。
public void initializeInvalidateRegion(int left, int top, int right, int bottom) {
final RectF region = mPreviousRegion;
region.set(left, top, right, bottom);
// Enlarge the invalidate region to account for rounding errors
region.inset(-1.0f, -1.0f);
if (mFillBefore) {
// 如果设置了 FillBefore,那么就提前进行首帧动画的应用,而不通过 getTransformation()方法
// mPreviousTransformation 在这里很关键,原因请看 getInvalidateRegion()
final Transformation previousTransformation = mPreviousTransformation;
applyTransformation(mInterpolator.getInterpolation(0.0f), previousTransformation);
}
}
// 获取动画改变后需要重绘的区域
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);
// mTransformation 只是一个 temp 辅助交换逻辑
final Transformation tempTransformation = mTransformation;
final Transformation previousTransformation = mPreviousTransformation;
// 交换 mPreviousTransformation 和当前 transformation. 这个 api 是
// view.applyLegacyAnimation() 方法时获取 Animation 变化的时机,即获取后马上就绘制的。而这里是
// 把前一帧交换出去绘制,留下了当前帧,所以上述的 mOneMoreTime 的来由就在这里,onMoreTime 把余下的
// 一帧展示。
tempTransformation.set(transformation);
transformation.set(previousTransformation);
previousTransformation.set(tempTransformation);
}
复制代码
到这里,整个 Animation 的关键代码已经分析结束了,类少易懂,作用大。里面的设计可以说很好了,但是仍然有些差强人意的地方,就像是 mPreviousAnimation 和 mOneMoreTime(如果不真正咬文嚼字,重写的时候就可能会遇到各种坑),其实不需要这样做也能把动画的帧完全显示的。
三、设计模式分析
Animation 使用了模板方法的设计模式,定义了一个基类和关键动画链路,由实现的子类去完成关键的动画方法,让整个动画变得易于拓展,隐蔽实现细节。
Intepolator 差值器使用了策略模式,将差值器的算法交给实现类完成,让动画在时间抽上的效果得以丰富多样化。比如衍生出了 LinearIntepolator / AccelerateInterpolator / PathInterpolatorCompatBase(cubic-bezier)...
四、继承类分析
AplhaAnimation
AplhaAnimation 继承自 Animation,进行透明度动画,关键属性:mFromAlpha / mToAlpha。通过变化率改变 Transformation 中的 alpha 值。
// 重写父类 Animation 方法
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
final float alpha = mFromAlpha;
// 根据变化率求出当前 alpha 值,在设置给 Transformation。
t.setAlpha(alpha + ((mToAlpha - alpha) * interpolatedTime));
}
复制代码
Transformation 中的 alpha 怎么生效的?跟进 draw 方法里看看!主要调用到的方法是,当硬件加速时,使用的是 RenderNode.setAlpha();当软件加速时,是 canvas.saveLayoutAplha()方法,以及 paint.setAlpha() 方法。
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
/* 省略... */
more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
/* 省略... */
float alpha = drawingWithRenderNode ? 1 : (getAlpha() * getTransitionAlpha());
if (transformToApply != null
|| alpha < 1
|| !hasIdentityMatrix()
|| (mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_ALPHA) != 0) {
if (transformToApply != null || !childHasIdentityMatrix) {
/* 省略... */
if (transformToApply != null) {
if (concatMatrix) {
// 应用动画矩阵到 canvas 或者 RenderNode
if (drawingWithRenderNode) {
renderNode.setAnimationMatrix(transformToApply.getMatrix());
} else {
canvas.translate(-transX, -transY);
canvas.concat(transformToApply.getMatrix());
canvas.translate(transX, transY);
}
parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
}
// 获取在动画 Transformation 中设置的 alpha 值
float transformAlpha = transformToApply.getAlpha();
if (transformAlpha < 1) {
alpha *= transformAlpha;
parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
}
}
/* 省略... */
}
// 当 alpha 需要设置时
if (alpha < 1 || (mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_ALPHA) != 0) {
/* 省略... */
if (!drawingWithDrawingCache) {
final int multipliedAlpha = (int) (255 * alpha);
if (!onSetAlpha(multipliedAlpha)) {
if (drawingWithRenderNode) {
// 如果是硬件加速,则通过 RenderNode.setAlpha 方法设置透明度
renderNode.setAlpha(alpha * getAlpha() * getTransitionAlpha());
} else if (layerType == LAYER_TYPE_NONE) {
// 如果是软件绘制,则通过 Canvas.saveLayerApla 方法时启用画布新层时
// 设置透明度。
canvas.saveLayerAlpha(sx, sy, sx + getWidth(), sy + getHeight(),
multipliedAlpha);
}
/* 省略... */
if (!drawingWithDrawingCache) {
/* 省略... */
} else if (cache != null) {
// 当需要绘制缓存的内容时
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
if (layerType == LAYER_TYPE_NONE) {
/* 省略... */
// 透明度设置到画笔上
cachePaint.setAlpha((int) (alpha * 255));
canvas.drawBitmap(cache, 0.0f, 0.0f, cachePaint);
} else {
// 合并layerPaint 本身设置的透明度和将要设置的透明度,设置到画笔上
int layerPaintAlpha = mLayerPaint.getAlpha();
mLayerPaint.setAlpha((int) (alpha * layerPaintAlpha));
canvas.drawBitmap(cache, 0.0f, 0.0f, mLayerPaint);
mLayerPaint.setAlpha(layerPaintAlpha);
}
}
/* 省略... */
}
复制代码
ScaleAnimation
ScaleAnimation 继承自 Animation ,缩放动画的实现。关键通过 Matrix.setScale 完成。
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
float sx = 1.0f;
float sy = 1.0f;
float scale = getScaleFactor();
// 根据变化率算出当前应该设置的缩放值
if (mFromX != 1.0f || mToX != 1.0f) {
sx = mFromX + ((mToX - mFromX) * interpolatedTime);
}
if (mFromY != 1.0f || mToY != 1.0f) {
sy = mFromY + ((mToY - mFromY) * interpolatedTime);
}
// 最后设置到矩阵上
if (mPivotX == 0 && mPivotY == 0) {
t.getMatrix().setScale(sx, sy);
} else {
// 同时设置中心点
t.getMatrix().setScale(sx, sy, scale * mPivotX, scale * mPivotY);
}
}
复制代码
AnimationSet
AnimationSet 继承自 Animation,组合动画的实现。通过 AnimationSet 可以实现多个同时执行,利用 StartOffset 也能实现串行,当然也有局限,比如没法设置循环、没有整体的 repeatMode(自己倒腾一下就可以,比如捣腾下 normalizedTime)。
实现细节:遍历孩子的 getTransformation() 方法,将 Transform 结果合成。
注意:(具体可以看代码)
- duration、 repeatMode、 fillBefore、 fillAfter 这些属性是直接设置给子 Animation;
- repeatCount、fillEnabled 这些属性不支持;
- startOffset、 shareInterpolator只能应用在 AnimationSet 本身;
- sharInterpolator 起效时候,子 Animation 的 sharInterpolator 失效;
public void initialize(int width, int height, int parentWidth, int parentHeight) {
/* 省略 */
// 这里就可以看出哪些是被设置进孩子的
for (int i = 0; i < count; i++) {
Animation a = children.get(i);
if (durationSet) {
a.setDuration(duration);
}
if (fillAfterSet) {
a.setFillAfter(fillAfter);
}
if (fillBeforeSet) {
a.setFillBefore(fillBefore);
}
if (repeatModeSet) {
a.setRepeatMode(repeatMode);
}
if (shareInterpolator) {
a.setInterpolator(interpolator);
}
if (startOffsetSet) {
long offset = a.getStartOffset();
// 孩子在 AnimationSet 的 startOffset 上加上了自身的 startOffset
a.setStartOffset(offset + startOffset);
storedOffsets[i] = offset;
}
a.initialize(width, height, parentWidth, parentHeight);
}
}
public boolean getTransformation(long currentTime, Transformation t) {
final int count = mAnimations.size();
final ArrayList animations = mAnimations;
final Transformation temp = mTempTransformation;
boolean more = false;
boolean started = false;
boolean ended = true;
t.clear();
// 遍历子 Animation
for (int i = count - 1; i >= 0; --i) {
final Animation a = animations.get(i);
temp.clear();
more = a.getTransformation(currentTime, temp, getScaleFactor()) || more;
// 合成子 Animation 的操作,Transformation.compose() 方法做的就是矩阵相乘,透明度相乘叠加
// 裁剪区域叠加。
t.compose(temp);
started = started || a.hasStarted();
ended = a.hasEnded() && ended;
}
if (started && !mStarted) {
if (mListener != null) {
mListener.onAnimationStart(this);
}
mStarted = true;
}
// 当所有的子动画结束时,结束本身
if (ended != mEnded) {
if (mListener != null) {
mListener.onAnimationEnd(this);
}
mEnded = ended;
}
return more;
}
复制代码
五、拓展实现示例
支持属性 direction,指定初始运行方向为 FORWARDS / BACKWORDS,结合 RepeatMode 就能支持循环的四种模式。
实现方式:根据指定 direction 模式,取反向变化率,再进行变化。
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
// mDirection 为自定义属性
if (mDirection == BACKWORDS) {
interpolatedTime = 1 - interpolatedTime;
}
// 动画实现细节
}
复制代码