属性动画源码解析一

一、引言

 

      从一个bug说起,有一天晚上,我想写个demo验证下动画的效果,写了几行非常基础的代码,点击run运行,纳尼,说好的动画呢?怎么一动不动,哪里写错了嘛,于是我修改了几个参数,再次运行,发现还是不生效,如此简单的三行代码,怎么会有问题?怎么也想不通,想的都快怀疑人生了。第二天去公司,让同事帮忙看下是什么原因,发现是我手动无脑关闭了手机动画。事后我想为什么自己没找到原因?一是对属性动画原理不熟悉,导致怀疑代码问题,二是对生活中的事情不主动思考。所以写这篇文章目的是理解属性动画的原理,同时向源码学习。

二、基本使用

1.平移动画

setTranslationX表示在x轴方向移动。

setTranslationY表示在Y轴方向移动。

setTranslationZ设置阴影

  ObjectAnimator mFlashAnimator = ObjectAnimator.ofFloat(mLoadingLayout, "translationY", 600);
        mFlashAnimator.setDuration(3000);
        mFlashAnimator.start();
2.伸缩动画

setScaleX(float scaleX):x轴方向伸缩

setScaleY(float scaleY):y轴方向伸缩

      ObjectAnimator animator = ObjectAnimator.ofFloat(rLayout, "scaleX", 0f, 1.5f, 2f, 1.5f, 0f, 0.5f, 0.2f, 1f);
        animator.setDuration(2000);
        animator.start();
3.透明度动画

setAlpha(float alpha)透明度动画

      ObjectAnimator animator = ObjectAnimator.ofFloat(rLayout, "alpha", 0, 0.5f, 1.0f);
        animator.setDuration(2000);
        animator.start();
4.旋转动画

setRotationX(float rotationX):x方向旋转,参数是旋转度数

setRotationY(float rotationY): y轴方向旋转

setRotation(float rotation):z方向旋转

        ObjectAnimator animator = ObjectAnimator.ofFloat(rLayout, "rotation", 0, 180, 0, -180, 0);
        animator.setDuration(3000);
        animator.start();

只要有set方法的都可以用来做属性动画,比如setX(),setBackgroundResource(),setBackgroundColor()方法等。

三、源码分析

1.创建动画对象

以ObjectAnimator.ofFloat()创建对象为例,通过静态方法创建对象,将所需的数据一次性传入,提升的内聚性,易用性。传入参数使用object类型,我觉得是提高扩展性,因为后面设置属性时用的反射,无需关注特定类型。setTarget为null,则使用weakReference弱引用持有,防止内存泄漏。setValues()->PropertyValuesHolder->KeyframeSet->Keyframe,相应创建这些对象。

  public static KeyframeSet ofFloat(float... values) {
        boolean badValue = false;
        int numKeyframes = values.length;
        FloatKeyframe keyframes[] = new FloatKeyframe[Math.max(numKeyframes,2)];
        if (numKeyframes == 1) {
            //只有一个值,默认是0和100%
            keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f);
            keyframes[1] = (FloatKeyframe) Keyframe.ofFloat(1f, values[0]);
            if (Float.isNaN(values[0])) {
                badValue = true;
            }
        } else {
            keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f, values[0]);
            for (int i = 1; i < numKeyframes; ++i) {
                //将values转成百分比的值,比如有5个值,转化为0,25%,50%,75%,100%
                keyframes[i] =
                        (FloatKeyframe) Keyframe.ofFloat((float) i / (numKeyframes - 1), values[i]);
                if (Float.isNaN(values[i])) {
                    badValue = true;
                }
            }
        }
        if (badValue) {
            Log.w("Animator", "Bad value (NaN) in float animator");
        }
        return new FloatKeyframeSet(keyframes);
    }
2.计算每一帧动画要执行的距离

AnimationHandler使用ThreadLocal持有,保住同个线程只有一个对象,autoCancelBasedOn()表示之前有相同的属性动画,且可以自动取消,则取消之前的动画。重点研究addAnimationCallback(0);此方法是将动画的回调间接注册到编舞者对象Choreographer中,编舞者对象是Android绘制的最重要的参与者,控制每一帧绘制的时机,正常情况下是1s中绘制60帧,平均16ms绘制一帧数据。

  private void doAnimationFrame(long frameTime) {
        long currentTime = SystemClock.uptimeMillis();
        final int size = mAnimationCallbacks.size();
        for (int i = 0; i < size; i++) {
            final AnimationFrameCallback callback = mAnimationCallbacks.get(i);
            if (callback == null) {
                continue;
            }
            if (isCallbackDue(callback, currentTime)) {
                callback.doAnimationFrame(frameTime);
                if (mCommitCallbacks.contains(callback)) {
                    getProvider().postCommitCallback(new Runnable() {
                        @Override
                        public void run() {
                            commitAnimationFrame(callback, getProvider().getFrameTime());
                        }
                    });
                }
            }
        }
        cleanUpList();
    }

外部的回调保存在arraylist集合中,for循环依次集合中的callback,执行doAnimationFrame()方法,将时间赋值给mStartTime,这个方法用了boolean值作为方法返回值,可以用来处理回调。

    public final boolean doAnimationFrame(long frameTime) {
        if (mStartTime < 0) {
            // First frame. If there is start delay, start delay count down will happen *after* this
            // frame.
            //第一次回调,将第一帧的时间赋值给mStartTime
            mStartTime = mReversing
                    ? frameTime
                    : frameTime + (long) (mStartDelay * resolveDurationScale());
        }

        // Handle pause/resume
        if (mPaused) {
            mPauseTime = frameTime;
            removeAnimationCallback();
            return false;
        } else if (mResumed) {
            mResumed = false;
            if (mPauseTime > 0) {
                // Offset by the duration that the animation was paused
                mStartTime += (frameTime - mPauseTime);
            }
        }

        if (!mRunning) {
            // If not running, that means the animation is in the start delay phase of a forward
            // running animation. In the case of reversing, we want to run start delay in the end.
            if (mStartTime > frameTime && mSeekFraction == -1) {
                // This is when no seek fraction is set during start delay. If developers change the
                // seek fraction during the delay, animation will start from the seeked position
                // right away.
                return false;
            } else {
                // If mRunning is not set by now, that means non-zero start delay,
                // no seeking, not reversing. At this point, start delay has passed.
                mRunning = true;
                startAnimation();
            }
        }

        if (mLastFrameTime < 0) {
            if (mSeekFraction >= 0) {
                long seekTime = (long) (getScaledDuration() * mSeekFraction);
                mStartTime = frameTime - seekTime;
                mSeekFraction = -1;
            }
            mStartTimeCommitted = false; // allow start time to be compensated for jank
        }
        mLastFrameTime = frameTime;
        // The frame time might be before the start time during the first frame of
        // an animation.  The "current time" must always be on or after the start
        // time to avoid animating frames at negative time intervals.  In practice, this
        // is very rare and only happens when seeking backwards.
        //计算当前刷新时间
        final long currentTime = Math.max(frameTime, mStartTime);
        boolean finished = animateBasedOnTime(currentTime);

        if (finished) {
            endAnimation();
        }
        return finished;
    }

animateBaseOnTime方法算出时间的百分比,用来计算移动的距离。关键代码

          //将每次时间和第一次相比,再除以mDuration,算出百分比
            final float fraction = scaledDuration > 0 ?
                    (float)(currentTime - mStartTime) / scaledDuration : 1f;

计算移动距离,会走插值器逻辑,然后调用FloatKeyFrameSet的getFloatValue方法,

 @Override
    public float getFloatValue(float fraction) {
        if (fraction <= 0f) {
            final FloatKeyframe prevKeyframe = (FloatKeyframe) mKeyframes.get(0);
            final FloatKeyframe nextKeyframe = (FloatKeyframe) mKeyframes.get(1);
            float prevValue = prevKeyframe.getFloatValue();
            float nextValue = nextKeyframe.getFloatValue();
            float prevFraction = prevKeyframe.getFraction();
            float nextFraction = nextKeyframe.getFraction();
            final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
            if (interpolator != null) {
                fraction = interpolator.getInterpolation(fraction);
            }
            float intervalFraction = (fraction - prevFraction) / (nextFraction - prevFraction);
            return mEvaluator == null ?
                    prevValue + intervalFraction * (nextValue - prevValue) :
                    ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
                            floatValue();
        } else if (fraction >= 1f) {
            final FloatKeyframe prevKeyframe = (FloatKeyframe) mKeyframes.get(mNumKeyframes - 2);
            final FloatKeyframe nextKeyframe = (FloatKeyframe) mKeyframes.get(mNumKeyframes - 1);
            float prevValue = prevKeyframe.getFloatValue();
            float nextValue = nextKeyframe.getFloatValue();
            float prevFraction = prevKeyframe.getFraction();
            float nextFraction = nextKeyframe.getFraction();
            final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
            if (interpolator != null) {
                fraction = interpolator.getInterpolation(fraction);
            }
            float intervalFraction = (fraction - prevFraction) / (nextFraction - prevFraction);
            return mEvaluator == null ?
                    prevValue + intervalFraction * (nextValue - prevValue) :
                    ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
                            floatValue();
        }
        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);
                }
                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();
    }

这里以ObjectAnimator.ofFloat(rLayout, "translationX", 100,200, 500)的值举例说明,在初始化时values数组会将每个value封装成FloatKeyFrame对象,保存在FloatKeyFrameSet数组里,keyFrame对象的值分别为(0,100),(0.5,200),(1,500)。在第一次刷新时间回调时,假设currentFraction为0.000453,会走fraction=0的if判断,此次的value计算prevValue + intervalFraction * (nextValue - prevValue),下次会走fraction不等于0的判断,算出这次走的值。保存到PropertyValuesHolder中。

3.反射修改属性值。

在调用start方法时,会initAnimation方法,这个方法会先走子类的方法,再走父类的方法,接着会调用setupSetterAndGetter(),接着会创建set和get的metho对象实例,采用拼接字符串的形式。然后调用setAnimatedValue(),最后调用mSetter.invoke(target, mTmpValueArray)修改控件属性的值。方法执行顺序:start()->initAnimation()->setupSetterAndGetter()->getPropertyFunction()->getMethodName()->setAnimatedValue()。

四、总结

对一个view的某个属性设置不同的时间节点,应该达到的值,注册动画回调到Choreographer对象中,每隔16ms回调一次,执行相应的距离,在规定的时间内执行完成,完成动画。本篇文章主要分析属性动画执行的原理,还有一些问题,还有待研究,如:

1.动画只能在主线程执行吗?

2.多个动画同时执行会造成卡顿嘛?

3.动画卡顿的本质原因是什么?

5.values设置多个值的意义在哪?

6.插值器和估值器的区别?

7.自定义属性动画如何实现?

这些问题的答案留在下次揭晓,敬请期待!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值