您的位置:首页 > 移动开发 > Android开发

Android Animation动画原理源码分析

2017-06-19 17:14 513 查看
Android 平台提供了三类动画,一类是 Tween 动画-Animation,即通过对场景里的对象不断做图像变换 ( 平移、缩放、旋转 ) 产生动画效果;第二类是 Frame 动画,即顺序播放事先做好的图像,跟电影类似。最后一种就是3.0之后才出现的属性动画PropertyAnimator ,这个分享的是第一类动画原理。

Animation动画有4种,TranslateAnimation、ScaleAnimation、RotateAnimation、AlphAnimation,其都继承了Animation这个抽象类,实现了applyTransformation(float interpolatedTime, Transformation t)函数,如下ScaleAnimation的实现:

根据函数的命名来看就是对Transformation进行设置,通过传来interpolatedTime浮点值不断改变Transformation的矩阵Matrix来实现动画的缩放。那这个函数什么时候被调用,如何被调用,一步步来分析下:

可以看到在Animation中getTransformation(long currentTime, Transformation outTransformation)进行了调用,并且判断了动画是否继续下去,还有其他变化帧是否完成,如下:


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 || isCanceled();
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 (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);
//对transformation进行改变
applyTransformation(interpolatedTime, outTransformation);
}

******省略代码******

if (!mMore && mOneMoreTime) {
mOneMoreTime = false;
return true;
}

return mMore;
}


那getTransformation函数又是在哪里被调用呢。我们一步步从动画api使用源头开始分析:

我们知道使用Animation执行动画从view.startAnimation(Animation animation)开始,其实现如下:

public void startAnimation(Animation animation) {
animation.setStartTime(Animation.START_ON_FIRST_FRAME);
setAnimation(animation);
invalidateParentCaches();
invalidate(true);
}


一看没有啥,就是设置赋值以及清父View Caches的标签设置,没有看出啥是如何让动画执行起来,调用了Animation的getTransformation函数。一般会说看到了invalidate函数调用了,就是刷新了View,那具体是怎么刷新View绘制的呢,那先讲下invalidate(true)执行流程和逻辑是啥,到底怎么样导致了刷新。没有别的办法,看源码,gogogo……详见我的另一遍文章《Android invalidate()和postInvalidate()刷新原理》

1、invalidate最后调用到invalidateInternal函数,把view的相对尺寸和相关状态设置传递

void invalidate(boolean invalidateCache) {
invalidateInternal(0, 0, mRight - mLeft, mBottom -  mTop, invalidateCache, true);
}


void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate)


2、invalidateInternal函数中逻辑不少,主要部分如下,有段调用父view进行刷新:

// Propagate the damage rectangle to the parent view.
final AttachInfo ai = mAttachInfo;
final ViewParent p = mParent;
if (p != null && ai != null && l < r && t < b) {
final Rect damage = ai.mTmpInvalRect;
damage.set(l, t, r, b);
p.invalidateChild(this, damage);
}


那ViewParent.invalidateChild实现是在哪里呢,猜到ViewGroup,发现其实现了ViewParent的接口,并且在ViewGroup.addView时添加子View的逻辑中会最后调用到`addViewInner方法:

private void addViewInner(View child, int index, LayoutParams params,
boolean preventRequestLayout) {

......

if (child.getParent() != null) {
throw new IllegalStateException("The specified child already has a parent. " +
"You must call removeView() on the child's parent first.");
}

if (mTransition != null) {
mTransition.addChild(this, child);
}

if (!checkLayoutParams(params)) {
params = generateLayoutParams(params);
}

if (preventRequestLayout) {
child.mLayoutParams = params;
} else {
child.setLayoutParams(params);
}

if (index < 0) {
index = mChildrenCount;
}

addInArray(child, index);

// tell our children
//设置父view
if (preventRequestLayout) {
child.assignParent(this);
} else {
child.mParent = this;
}

if (child.hasFocus()) {
requestChildFocus(child, child.findFocus());
}

.....
}


对child的ViewPrarent变量mParent进行赋值,把自己传递给子View,那么定位到ViewGroup对invalidateChild方法实现,发现里面有循环查找父View逻辑,如下:

do {
View view = null;
if (parent instanceof View) {
view = (View) parent;
}

**********
省略代码
// If the parent is dirty opaque or not dirty, mark it dirty with the opaque
**********
parent = parent.invalidateChildInParent(location, dirty);
if (view != null) {
// Account for transform on current parent
********
}
} while(parent!=null)


这个循环直到parent!=null才停止,那什么时候parent.invalidateChildInParent(location, dirty)会返回null呢。这里就要分析View树形结构了,布局结构中最上层的ViewParent是谁,什么时候赋值的。这个要从setContentView函数设置布局文件开始讲,有点长,但也许都是到布局的最顶层view就是DecorView(可以查源码),那DecorView的ViewParent又是谁,这个就必须从添加decorView定位。参考http://blog.csdn.net/luoshengyang/article/details/6689748这边老罗的文章,分析应用启动以及View显示加载过程,从中可以知道在AMS通知PerformResumeActivity命令时开始显示界面,会调用activity的makeVisible(),该函数添加了mDecor(DecorView)

void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}


ViewManager.addView的实现在WindowManagerGlobal的addView方法中:

会看到一个ViewRootImpl,看起来很想最最顶层 的View,查找源码发现:并发View,但是其实现了ViewParent,这就差不多连起来了,然后其调用了setView方法查看实现如下

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;

************
很多代码省略
*************
view.assignParent(this);
mAddedTouchMode = (res & WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE) != 0;
mAppVisible = (res & WindowManagerGlobal.ADD_FLAG_APP_VISIBLE) != 0;

************
很多代码省略
*************
}
}
}


发现 mView = view进行赋值,并调用assignParent(this),把这个ViewParent实现付给DecorView,从持有父View。既然ViewRootImp实现了ViewParent,并且付给了DecorView,那在DecorView查找父parent时(parent.invalidateChildInParent(location, dirty))就可以定位到ViewRootImpl实现了,找到ViewRootImp.invalidateChildInParent:发现终于返回了null,结束了循环。

@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
checkThread();
if (DEBUG_DRAW) Log.v(TAG, "Invalidate child: " + dirty);

if (mCurScrollY != 0 || mTranslator != null) {
mTempRect.set(dirty);
dirty = mTempRect;
if (mCurScrollY != 0) {
dirty.offset(0, -mCurScrollY);
}
if (mTranslator != null) {
mTranslator.translateRectInAppWindowToScreen(dirty);
}
if (mAttachInfo.mScalingRequired) {
dirty.inset(-1, -1);
}
}

invalidateRectOnScreen(dirty);

return null;
}


invalidateRectOnScreen(dirty);又调用了谁,继续定位

可以查源码分析就不一步步写了,schuduleTraversals()调用是固定最后调用到performTraversals函数,这个是view绘制的源头开始处,代码很多,里面调用了mView.measure() mView.layout 、mView.draw等方法。终于差不多看到希望了,mView.draw()就是调用了DecorView的draw(),然后就把这个布局遍历绘制了一遍。那么走到执行动画的View的其父View绘制draw方法时候,会走到dispatchDraw,在View.draw(canvas)里面按步骤分别回执如下1、2、3、4、5、6步,其中有dispatchDraw来绘制子View。

public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;

/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
*      1. Draw the background
*      2. If necessary, save the canvas' layers to prepare for fading
*      3. Draw view's content
*      4. Draw children
*      5. If necessary, draw the fading edges and restore layers
*      6. Draw decorations (scrollbars for instance)
*/


ViewGroup重载了dispatchDraw,实现绘制子View的内容,源码如下:

protected void dispatchDraw(Canvas canvas) {
boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
final int childrenCount = mChildrenCount;
final View[] children = mChildren;
int flags = mGroupFlags;
//执行布局动画
if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate(){
final boolean buildCache = !isHardwareAccelerated();
for (int i = 0; i < childrenCount; i++) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
final LayoutParams params = child.getLayoutParams();
attachLayoutAnimationParameters(child, params, i, childrenCount);
bindLayoutAnimation(child);
}
}
final LayoutAnimationController controller = mLayoutAnimationController;
if (controller.willOverlap()) {
mGroupFlags |= FLAG_OPTIMIZE_INVALIDATE;
}
controller.start();
mGroupFlags &= ~FLAG_RUN_ANIMATION;
mGroupFlags &= ~FLAG_ANIMATION_DONE;

if (mAnimationListener != null) {
mAnimationListener.onAnimationStart(controller.getAnimation());
}
}
*******省略部分代码 *********
// We will draw our child's animation, let's reset the flag

//more为true表示动画没有执行完
boolean more = false;
final long drawingTime = getDrawingTime();
*******省略部分代码 *********
for (int i = 0; i < childrenCount; i++) {
*******省略部分代码 *********
int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
final View child = (preorderedList == null) ? children[childIndex] :
preorderedList.get(childIndex);
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
//绘制子View,直接调用了child.draw(canvas, this(ViewParent), drawingTime);
// 动画真正执行地方
more |= drawChild(canvas, child, drawingTime);
}
}

*******省略部分代码 *********
// mGroupFlags might have been updated by drawChild()
flags = mGroupFlags;
if ((flags & FLAG_INVALIDATE_REQUIRED) == FLAG_INVALIDATE_REQUIRED) {
invalidate(true);
}
*******省略部分代码 *********
}


看到调用child.draw(Canvas canvas, ViewGroup parent, long drawingTime),这个draw(canvas)函数不一样,不过前者又会调用到后者。看下child.draw(Canvas canvas, ViewGroup parent, long drawingTime)是如何执行动画调用到animattion.applyTransformation()对Matrix进行矩阵变化,源码如下:

boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {

****************省略部分代码 ***************
boolean more = false;
Transformation transformToApply = null;
boolean concatMatrix = false;
final boolean scalingRequired = mAttachInfo != null && mAttachInfo.mScalingRequired;
final Animation a = getAnimation();//获取ziView的动画,在View.startAnimation是就开始赋值了。
if (a != null) {
more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
concatMatrix = a.willChangeTransformationMatrix();
if (concatMatrix) {
mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
}
transformToApply = parent.getChildTransformation();// a.applyLegacyAnimation进行赋值
} else {
****************省略代码 ***************
}
****************省略代码 ***************
if (transformToApply != null || alpha < 1 || !
hasIdentityMatrix() || (mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_ALPHA) != 0) {
if (transformToApply != null || !childHasIdentityMatrix) {
int transX = 0;
int transY = 0;
if (offsetForScroll) {
transX = -sx;
transY = -sy;
}
if (transformToApply != null) {
if (concatMatrix) {
if (drawingWithRenderNode) {
renderNode.setAnimationMatrix(transformToApply.getMatrix());
} else {
// Undo the scroll translation, apply the transformation matrix,                        // then redo the scroll translate to get the correct result.
canvas.concat(transformToApply.getMatrix());//传递给canvas进行矩阵实现动画
canvas.translate(transX, transY);
}
parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
}
float transformAlpha = transformToApply.getAlpha();
if (transformAlpha < 1) {
alpha *= transformAlpha;
parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
}
}
if (!childHasIdentityMatrix && !drawingWithRenderNode) {
canvas.translate(-transX, -transY);
canvas.concat(getMatrix());
canvas.translate(transX, transY);
}
}
****************省略代码 ***************
return more;
}


在这么代码中动画animation判断不为空之后,进入more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);,这里面传入了animation,有没有对动画执行操作呢,如下源码:

private boolean applyLegacyAnimation(ViewGroup parent, long drawingTime,
Animation a, boolean scalingRequired) {
Transformation invalidationTransform;
*********省略部分代码 **********
final Transformation t = parent.getChildTransformation();
boolean more = a.getTransformation(drawingTime, t, 1f);///调用了Animation的
///getTransformation方法,这个方法进而调用了applyTransformation
if (scalingRequired && mAttachInfo.mApplicationScale != 1f) {
if (parent.mInvalidationTransformation == null) {
parent.mInvalidationTransformation = new Transformation();
}
invalidationTransform = parent.mInvalidationTransformation;
a.getTransformation(drawingTime, invalidationTransform, 1f);
} else {
invalidationTransform = t;
}
if (more) {//为true又重新刷新,调用viewparent.invalidate
if (!a.willChangeBounds()) {
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 {
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;
}


从 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内容发生动画。

到这里就讲完了view tween动画的执行原理
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息