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来实现动画的缩放。那这个函数什么时候被调用,如何被调用,一步步来分析下:
那getTransformation函数又是在哪里被调用呢。我们一步步从动画api使用源头开始分析:
我们知道使用Animation执行动画从view.startAnimation(Animation animation)开始,其实现如下:
一看没有啥,就是设置赋值以及清父View Caches的标签设置,没有看出啥是如何让动画执行起来,调用了Animation的getTransformation函数。一般会说看到了invalidate函数调用了,就是刷新了View,那具体是怎么刷新View绘制的呢,那先讲下invalidate(true)执行流程和逻辑是啥,到底怎么样导致了刷新。没有别的办法,看源码,gogogo……详见我的另一遍文章《Android invalidate()和postInvalidate()刷新原理》
1、invalidate最后调用到invalidateInternal函数,把view的相对尺寸和相关状态设置传递
2、invalidateInternal函数中逻辑不少,主要部分如下,有段调用父view进行刷新:
那ViewParent.invalidateChild实现是在哪里呢,猜到ViewGroup,发现其实现了ViewParent的接口,并且在ViewGroup.addView时添加子View的逻辑中会最后调用到`addViewInner方法:
对child的ViewPrarent变量mParent进行赋值,把自己传递给子View,那么定位到ViewGroup对invalidateChild方法实现,发现里面有循环查找父View逻辑,如下:
这个循环直到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)
ViewManager.addView的实现在WindowManagerGlobal的addView方法中:
会看到一个ViewRootImpl,看起来很想最最顶层 的View,查找源码发现:并发View,但是其实现了ViewParent,这就差不多连起来了,然后其调用了setView方法查看实现如下
发现 mView = view进行赋值,并调用assignParent(this),把这个ViewParent实现付给DecorView,从持有父View。既然ViewRootImp实现了ViewParent,并且付给了DecorView,那在DecorView查找父parent时(parent.invalidateChildInParent(location, dirty))就可以定位到ViewRootImpl实现了,找到ViewRootImp.invalidateChildInParent:发现终于返回了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。
ViewGroup重载了dispatchDraw,实现绘制子View的内容,源码如下:
看到调用child.draw(Canvas canvas, ViewGroup parent, long drawingTime),这个draw(canvas)函数不一样,不过前者又会调用到后者。看下child.draw(Canvas canvas, ViewGroup parent, long drawingTime)是如何执行动画调用到animattion.applyTransformation()对Matrix进行矩阵变化,源码如下:
在这么代码中动画animation判断不为空之后,进入more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);,这里面传入了animation,有没有对动画执行操作呢,如下源码:
从 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动画的执行原理
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动画的执行原理
相关文章推荐
- android 动画原理源码分析之Animation
- Android开发之动画源码Animation详细分析
- android动画使用分析[animation原理]
- android动画之从源码角度分析动画原理
- android动画之从源码角度分析动画原理
- android动画原理分析
- Android系统原理与源码分析(1):利用Java反射技术阻止通过按钮关闭对话框
- Android窗口管理服务WindowManagerService显示窗口动画的原理分析
- 【转】Android系统原理与源码分析:利用Java反射技术阻止通过按钮关闭对话框
- Android 开关机动画显示源码分析
- Android窗口管理服务WindowManagerService显示窗口动画的原理分析
- Android系统原理与源码分析(1):利用Java反射技术阻止通过按钮关闭对话框
- android-魔法泡泡动画分析(附源码)
- Android系统原理与源码分析(1):利用Java反射技术阻止通过按钮关闭对话框
- Android phoneGap 源码分析之js-android通讯原理
- Android窗口管理服务WindowManagerService显示窗口动画的原理分析
- Android系统原理与源码分析(1):利用Java反射技术阻止通过按钮关闭对话框
- Android移植: wifi设计原理(源码分析)
- ANDROID移植: WIFI设计原理(源码分析
- ANDROID移植: WIFI设计原理(源码分析