Android补间动画原理介绍
一 简述
补间动画是Android诞生便支持的动画,也是App开发中最常用的动画,目前Android支持四种补间动画:位移(TranslateAnimation),旋转(RotateAnimation),缩放(ScaleAnimation)和透明(AlphaAnimation)。补间动画主要作用对象是Android的View。核心类的关系如下图所示:
Animation是一个抽象类,Interpolator用来描述动画的执行过程,Transformation用来描述某一时刻动画参数(位移,缩放比例,旋转角度,透明度,裁剪区域等),其相关核心代码主要在android.view.animation包下。
二 基本流程介绍
1.简单示例
TextView textView = (TextView) findViewById(R.id.testTV);TranslateAnimation translateAnimation = new TranslateAnimation(10, 50, 10, 1080);translateAnimation.setDuration(2000);translateAnimation.setFillEnabled(true);translateAnimation.setFillAfter(true);translateAnimation.setFillBefore(true);translateAnimation.setRepeatCount(2);translateAnimation.setStartOffset(1000);translateAnimation.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { } @Override public void onAnimationEnd(Animation animation) { } @Override public void onAnimationRepeat(Animation animation) { }});textView.startAnimation(translateAnimation);
在创建补间动画的时候,我们也可以使用XML配合AnimationUtils快速创建一个Animation
Animation anim = AnimationUtils.loadAnimation(getContext(), R.anim.XXX);
2.基于上述的示例分析补间动画的执行过程
两种创建补间动画的原理其实都是一致的,都是先实例化一个Animation对象,然后通过它的setXXX方法设置相关的必要参数,最后需要将实例化的Animation与一个需要执行动画的View绑定,最后由View相关的机制驱动动画的执行。关键部分便是与View的绑定和驱动过程:
/** * Start the specified animation now. * * @param animation the animation to start now */public void startAnimation(Animation animation) { animation.setStartTime(Animation.START_ON_FIRST_FRAME);//设置动画的开始时间为-1 setAnimation(animation);//将Animation和View绑定,记录到mCurrentAnimation invalidateParentCaches(); invalidate(true);//执行View重绘操作,可以将它认为是动画的驱动操作}
invalidate调用其实就是通过View Tree层层调用到ViewRootImpl的invalidateChildInParent并最后执行scheduleTraversals操作。熟悉Android View绘制的应该了解,scheduleTraversals会注册一个Vsync信号并等待Vsync来触发View 的三大操作:测量,布局和绘制。那么动画具体是什么时候执行的呢?
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {...... final Animation a = getAnimation();//获取当前View需要执行的动画 if (a != null) { more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);//计算当前帧的动画,more表示是否需要执行更多帧的动画 concatMatrix = a.willChangeTransformationMatrix(); if (concatMatrix) { mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM; } transformToApply = parent.getChildTransformation();//获取当前帧的Transformation } else {...... }...... float alpha = drawingWithRenderNode ? 1 : (getAlpha() * getTransitionAlpha()); if (transformToApply != null//开始将动画的Transformation设置到当前View的Convas中去,直接影响View最终在屏幕上的显示效果 || 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.translate(-transX, -transY); canvas.concat(transformToApply.getMatrix());//设置Convas的显示效果的矩阵 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; } }...... } else if ((mPrivateFlags & PFLAG_ALPHA_SET) == PFLAG_ALPHA_SET) { onSetAlpha(255); mPrivateFlags &= ~PFLAG_ALPHA_SET; }...... return more;}
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) { 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); onAnimationStart(); } final Transformation t = parent.getChildTransformation(); boolean more = a.getTransformation(drawingTime, t, 1f);//获取当前帧的Transformation信息 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) {//继续下一帧动画执行 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;}
public boolean getTransformation(long currentTime, Transformation outTransformation) { if (mStartTime == -1) {//当我们没有明确指定动画的执行事件的时候,一般动画第一次执行的时候mStartTime=-1 mStartTime = currentTime; } final long startOffset = getStartOffset();//获取动画执行的延迟时间,一般没有指定的话,都是0 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; } final boolean expired = normalizedTime >= 1.0f || isCanceled();//表示动画已执行完,或者已被取消 mMore = !expired;//mMore表示是否需要执行下一帧动画 if (!mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);//mFillEnabled相关的判断逻辑//mFillEnabled=true,保持之前计算的normalizedTime值//mFillEnabled=false,修正normalizedTime值到0~1之前//结合上面的分析,mFillEnabled=false的话,不考虑mFillBefore和mFillAfter的值//mFillEnabled=true的话,normalizedTime的值将有可能取值在0~1范围之外 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);//当mFillEnabled=true,mFillBefore=true的情况下就要再次修正时间了 if (mCycleFlip) {//开启repeat的情况 normalizedTime = 1.0f - normalizedTime; } final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime); applyTransformation(interpolatedTime, outTransformation);//调用动画的applyTransformation,当我们自定义动画的时候,一般都需要重写这个方法,实现自己的动画效果 } if (expired) {//结束或者取消动画的相关逻辑 if (mRepeatCount == mRepeated || isCanceled()) { if (!mEnded) { mEnded = true; guard.close(); fireAnimationEnd(); } } else { if (mRepeatCount > 0) { mRepeated++; } if (mRepeatMode == REVERSE) { mCycleFlip = !mCycleFlip; } mStartTime = -1; mMore = true; fireAnimationRepeat(); } } if (!mMore && mOneMoreTime) { mOneMoreTime = false; return true; } return mMore;}
从上述的分析,我们可以看出补间动画的执行是由View的绘制驱动的,详细的说就是注册-接收Vsync事件,当需要View执行的动画的时候便会走View绘制流程注册一个Vsync信号,在Vsync来了之后绘制对应时间点关键帧的View显示信息,如果动画未执行结束或者未被取消,则再次走绘制流程注册一个Vsync信号,直到动画执行结束。同时补间动画的执行并未真正改变View的实际属性,改变是View最终的显示效果,具体通过Convas相关设置得以实现。至此Android补间动画分析结束。最后附上动画执行的时序图:
更多相关文章
- Android之动画Animation的使用
- android 简单动画之 animtion
- Ubantu下搭建Android CTS 兼容性测试环境及单项测试操作步骤
- 2019-12-16 Android中的动画
- Android SVG动画PathView源码解析与使用教程(API 14)
- Android Studio 导出 .aar包的操作流程
- Android启动画面实现
- 补间动画--透明渐变XML