在android系统中动画分为两种分别是基础动画和属性动画。对于动画的工作原理主要涉及到的是基础动画属性动画的实现。
本章主要分两大块:基础动画和属性动画

1.基础动画

对于基础动画的实现主要是嵌套在View的绘制流程中的,基础动画主要有旋转RotatleAnimation,缩放ScaleAnimation,透明AlphaAnimation,平移TranslateAnimation等都是Animation的子类。
基础动画的实现主要涉及到两个类Animation 和 Transformation。
Animation主要代表动画类型运行状态,Transformation 是对动画的计算和保存。
    1》基础动画流程
          我们从ScaleAnimation子类入手做例子,其他类似。
先看使用方式:

<!--缩放动画标签-->
    <scale
        android:fromXScale="1.0"
        android:toXScale="0.2"
        android:fromYScale="1.0"
        android:toYScale="0.2"
        android:pivotX="50%"
        android:pivotY="50%"
        android:duration="4000"/>

Animation animation = AnimationUtils.loadAnimation(MainActivity.this,R.anim.viewanimation);
View.startAnimation(animation);
loadAnimation()主要是创建ScaleAnimation对象这里不多解释,主要看下startAnimation()方法。
public void startAnimation(Animation animation) {
        animation.setStartTime(Animation.START_ON_FIRST_FRAME);
        setAnimation(animation);
        invalidateParentCaches();
        invalidate(true); //主要出发刷新的动作
    }

这个方法中主要涉及到 invalidate(true); 刷新的机制。这里简单引入invalidate(true);出发的流程,后面章节介绍Activity窗口的创建显示时会详细介绍。
invalidate(true);的作用主要是会把需要重绘的矩形区域标记成dirty区域提交到ViewRootImpl中等待下一次VSYS信号来刷新遍历view树。

大致流程如下:

Android 动画技术 android 动画实现原理_Android 动画技术

对于整个View的绘制流程本章不多介绍主要讲一下流程中设计到动画相关代码,从 applyLegacyAnimation() 开始涉及到动画的实现 ,调用

boolean more = a.getTransformation(drawingTime, t, 1f);

继续到Animation中看

public boolean getTransformation(long currentTime, Transformation outTransformation) {
        //开始之后的等待时间
        final long startOffset = getStartOffset();
        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;
        }

        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);
            //调用子类的实现
            applyTransformation(interpolatedTime, outTransformation);
        }
         .......
        return mMore;
    }

这是主要的方法,主要做了两件事1.对总时长进行归一化转换到0~1。2.调用applyTransformation()回调子类实现。看代码解释。接下里看看applyTransformation()实现。

protected void applyTransformation(float interpolatedTime, Transformation t) {
  
        //mFromX 开始位置,mToX结束位置
        if (mFromX != 1.0f || mToX != 1.0f) {
            //sx 当前时间应该显示的位置
            sx = mFromX + ((mToX - mFromX) * interpolatedTime);
        }
        if (mFromY != 1.0f || mToY != 1.0f) {
            sy = mFromY + ((mToY - mFromY) * interpolatedTime);
        }
        // 把当前显示的值保存到Transformaiton中的Matrix中去,等待使用
        if (mPivotX == 0 && mPivotY == 0) {
            t.getMatrix().setScale(sx, sy);
        } else {
            t.getMatrix().setScale(sx, sy, scale * mPivotX, scale * mPivotY);
        }
    }

看代码可以看出此段代码主要计算当前时间点应该显示的状态,然后保存到Matrix等待使用。

当前时间点的值已经计算完成接下来就看怎么使用Matrix中保存的值,继续回到View的draw(3)3个参数的方法

if (transformToApply != null) {
      if (concatMatrix) {
         if (drawingWithRenderNode) { //硬件绘制使用Matrix
                renderNode.setAnimationMatrix(transformToApply.getMatrix());
            } else { 
                    // 软件绘制Canvas使用Matrix转换画布状态
                    canvas.translate(-transX, -transY);
                    canvas.concat(transformToApply.getMatrix());
                    canvas.translate(transX, transY);
            }
      }

这个段代码主要是使用我们动画保存的Matrix到Canvas上,发现没有动画并没有直接作用到VIew上 而是作用到view对应的画布上,这也是为什么基础动画并不能真正改变view的属性的原因。

Android 基础动画的工作原理到此结束,接下来让我们看看,属性动画的工作原理。

2.属性动画的工作原理

对于属性动画主要对应与抽象类Animator 以及子类ValueAnimator,孙子类ObjectAnimator对象,这里主要以ValueAnimator引入动画原理流程。其实属性动画的实现思想和基础动画的思想很像。为了不影响我们看源码的心情,其他reapet,restart等等状态我们都先不考虑,先看主要的,不然你会痛苦万分,然后放弃。

    1>属性动画的简单使用
        从API的使用入手

ValueAnimator animator = ValueAnimator.ofFloat(0,10,20);//起止位置
            animator.setDuration();      //动画总时长
            animator.setInterpolator();  //时间插值器,对时间效果进行模拟
            animator.setEvaluator();     //结果插值器,对计算结果进行模拟
            animator.addUpdateListener();//动画value更新
            animator.start();            //启动动画

从api使用可以看出属性动画的流程可以分两部分:1.封装PropertyValueHolder对象、2.start启动动画等待刷新

对于调用的流程就看流程图就行,简单的代码就不一一贴到博客中防止篇幅过长,对于重要的代码在流程图下方会贴出并解释。

Android 动画技术 android 动画实现原理_android_02

1.封装PropertyValueHolder对象
在KeyFrameSet 关键帧中根据传入的float数组值进行分数转换到0~1,这和基础动画时间归一化和相似。

public static KeyframeSet ofFloat(float... values) {
        int numKeyframes = values.length;
        FloatKeyframe keyframes[] = new FloatKeyframe[Math.max(numKeyframes,2)];
        if (numKeyframes == 1) {
            ....无法执行动画...
        } else {
            // 取出第一个value值 ,并把值转换到keyframes[0]中
            keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f, values[0]);
            // 从第二帧开始 转换成小于 1 的分数值,保存。
            for (int i = 1; i < numKeyframes; ++i) {
                keyframes[i] =
                        (FloatKeyframe) Keyframe.ofFloat((float) i / (numKeyframes - 1), values[i]);
                if (Float.isNaN(values[i])) {
                    badValue = true;
                }
            }
        }
        //封装返回
        return new FloatKeyframeSet(keyframes);
    }

2.start启动动画等待刷新

启动start方法之后

private void start(boolean playBackwards) {
        .....重复播放,可逆等省略......
        //1.主要代码添加回调,链接Choreograther 进行等待VSYS垂直同步信号的刷新
        addAnimationCallback(0);

        if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {

            startAnimation();
            if (mSeekFraction == -1) {
                // 2.从0开始播放动画
                setCurrentPlayTime(0);
            } else {
                // 从某一个进度开始播放动画,目前不考虑
                setCurrentFraction(mSeekFraction);
            }
        }
    }

在start方法中主要涉及到两个重要的方法:1.addAnimationCallback(0); 得到线程唯一的 AnimationHandler 封装了Choreograther 信号。2.setCurrentPlayTime(0); 开始从0播放动画。先看1.addAnimationCallback(0);
根据流程图看,这里直接到doframe()方法

private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
        @Override
        public void doFrame(long frameTimeNanos) {
            // 回调一帧动画
            doAnimationFrame(getProvider().getFrameTime());
            // 动画未运行完,需要等待下一帧屏幕刷新
            if (mAnimationCallbacks.size() > 0) {
                getProvider().postFrameCallback(this);
            }
        }
    };

当屏幕收到VSYS屏幕的垂直刷新同步信号过来之后进行刷新动画的值,信号大约16ms发送一次从SurfaceFlinger触发。

继续走到 animateBasedOnTime()

boolean animateBasedOnTime(long currentTime) {
        if (mRunning) {
            final long scaledDuration = getScaledDuration();//总时长
            // 计算当前时间在总时间的站比
            final float fraction = scaledDuration > 0 ?
                    (float)(currentTime - mStartTime) / scaledDuration : 1f;
            final float lastFraction = mOverallFraction;
            .............判断是否有 repeat 重播 省略............
            // 判断值是否在0~1之间
            mOverallFraction = clampFraction(fraction);
            //判断是否进入了repeat循环播放
            float currentIterationFraction = getCurrentIterationFraction(
                    mOverallFraction, mReversing);
            // 真正开始计算
            animateValue(currentIterationFraction);
        }
        return done;
    }

这个方法主要是判断当前时间是否在0~1直接,并且是否进入了repeat模式

void animateValue(float fraction) {
        fraction = mInterpolator.getInterpolation(fraction);
        mCurrentFraction = fraction;
        int numValues = mValues.length;
        for (int i = 0; i < numValues; ++i) {
            //根据当前分数开始计算
            mValues[i].calculateValue(fraction);
        }
        if (mUpdateListeners != null) {
            int numListeners = mUpdateListeners.size();
            for (int i = 0; i < numListeners; ++i) {
                // 把值返回给动画使用着,刷新属性
                mUpdateListeners.get(i).onAnimationUpdate(this);
            }
        }
    }

继续进入 calculateValue() 方法

void calculateValue(float fraction) {
        // 得到当前时间的值
        Object value = mKeyframes.getValue(fraction);
        // 赋值,等待回调给动画使用者
        mAnimatedValue = mConverter == null ? value : mConverter.convert(value);
    }

下面最后一步 getValue() 方法中计算

public float getFloatValue(float fraction) {
        if (fraction <= 0f) {
            ......动画错误.........
        } else if (fraction >= 1f) {
            .......动画错误.............
        }
        // 真正开始计算当前时间点的值
        FloatKeyframe prevKeyframe = (FloatKeyframe) mKeyframes.get(0);
        for (int i = 1; i < mNumKeyframes; ++i) {
            FloatKeyframe nextKeyframe = (FloatKeyframe) mKeyframes.get(i);
            //首先确定当前时间点所在的传入的值区间
            if (fraction < nextKeyframe.getFraction()) {
                final TimeInterpolator interpolator = nextKeyframe.getInterpolator(); 
                // 在当前区间范围内的比值
                float intervalFraction = (fraction - prevKeyframe.getFraction()) /
                    (nextKeyframe.getFraction() - prevKeyframe.getFraction());
                float prevValue = prevKeyframe.getFloatValue();
                float nextValue = nextKeyframe.getFloatValue();
                // Apply interpolator on the proportional duration.
                if (interpolator != null) {
                    // 根据插值器计算当前时间对应的插值器处理后的时间
                    intervalFraction = interpolator.getInterpolation(intervalFraction);
                }
                // 跟据当前插值器处理后的时间,计算当前区间上的值 value 然后返回
                return mEvaluator == null ?
                        prevValue + intervalFraction * (nextValue - prevValue) :
                        ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
                            floatValue();
            }
            prevKeyframe = nextKeyframe;
        }
        // shouldn't get here
        return ((Number)mKeyframes.get(mNumKeyframes - 1).getValue()).floatValue();
    }

这个方法有点长不过只看动画计算的部分,其他部分都是动画为开始或者超过了动画的运行时间,不考虑。
这个方法比较多,主要分三部分:

  1. 确定当前分子在传入的值的那个区间。
  2. 根据时间插值器转换当前时间值,例如加速/减速等计算,就是把时间重新计算,时间空间扭曲,达到一种超能力控制时间的运行速度。
  3. 根据插值器转换后的时间来计算当前区间内这个时间对应的值,用户可以自定义效果,比如贝塞尔曲线效果。让最终的值在根据某个公式/规律 计算后返回给用户。
     

至此关于Android动画的工作原理几经梳理清楚,后面会梳理一个Acitvity创建和显示流程,也会和基础动画的部分重叠。

对于Interpolator 和Evaluator 可以去了解了解,可以自定义,也可以使用系统定义的。