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补间动画分析结束。最后附上动画执行的时序图:

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值