Android动画整理和属性动画源码分析

一、基础

1.动画总结

2.补间动画总结

3.帧动画总结

4.属性动画总结

属性动画 Property Animation(上手篇)

属性动画 Property Animation(进阶篇)

二、属性动画源码分析

以 ObjectAnimator 为例来写一个简单的右移动画

ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(iv,"translationX",0,100).setDuration(1 * 1000);
objectAnimator.setInterpolator(new LinearInterpolator());
objectAnimator.start();
1.创建动画

从 ObjectAnimator.ofFloat()开始

    /**
     * 构建一个返回值为 float 的 ObjectAnimator 的实例
     * @param target 作用于动画的对象。
     * @param propertyName 属性名称,要求对象须有setXXX() 方法,且是 public 的。
     * @param values,属性变化的值
     */
public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
        ObjectAnimator anim = new ObjectAnimator(target, propertyName);
        anim.setFloatValues(values);
        return anim;
    }

ofFloat()中首先创建一个 ObjectAnimator 的对象,然后设置值。看一下ObjectAnimator的构造方法:

    private ObjectAnimator(Object target, String propertyName) {
        setTarget(target);
        setPropertyName(propertyName);
    }

分别调用了setTargetsetPropertyName方法,再看一下setTarget

    @Override
    public void setTarget(@Nullable Object target) {
        final Object oldTarget = getTarget();
        if (oldTarget != target) {
            if (isStarted()) {
                cancel();
            }
            mTarget = target == null ? null : new WeakReference<Object>(target);
            // 开始动画前需要重新初始化target
            mInitialized = false;
        }
    }

先判断目标对象是否相同,不相同则判断是否动画在执行,执行就取消动画,将目标对象用弱引用保存。再看下setPropertyName

    public void setPropertyName(@NonNull String propertyName) {
        // mValues could be null if this is being constructed piecemeal. Just record the
        // propertyName to be used later when setValues() is called if so.
        if (mValues != null) {
            PropertyValuesHolder valuesHolder = mValues[0];
            String oldName = valuesHolder.getPropertyName();
            valuesHolder.setPropertyName(propertyName);
            mValuesMap.remove(oldName);
            mValuesMap.put(propertyName, valuesHolder);
        }
        mPropertyName = propertyName;
        // New property/values/target should cause re-initialization prior to starting
        mInitialized = false;
    }

大体是保存propertyName,并且给PropertyValuesHolder设置propertyName并存入map中。对于属性动画来说,其属性相关的变量都被封装在了 PropertyValuesHolder 里。

再回到ofFloat中,接下来执行了ObjectAnimatorsetFloatValues方法:

    @Override
    public void setFloatValues(float... values) {
        if (mValues == null || mValues.length == 0) {
            // No values yet - this animator is being constructed piecemeal. Init the values with
            // whatever the current propertyName is
            if (mProperty != null) {
                setValues(PropertyValuesHolder.ofFloat(mProperty, values));
            } else {
                setValues(PropertyValuesHolder.ofFloat(mPropertyName, values));
            }
        } else {
            super.setFloatValues(values);
        }
    }

mValues没有值的时候调用父类的setFloatValues,父类是ValueAnimatorsetFloatValues如下:

    public void setFloatValues(float... values) {
        if (values == null || values.length == 0) {
            return;
        }
        if (mValues == null || mValues.length == 0) {
            setValues(PropertyValuesHolder.ofFloat("", values));
        } else {
            PropertyValuesHolder valuesHolder = mValues[0];
            valuesHolder.setFloatValues(values);
        }
        // New property/values/target should cause re-initialization prior to starting
        mInitialized = false;
    }

可以看出,不管是否调用父类的 setFloatValues()。最后都是要将 values 逐个构造成 PropertyValuesHolder,最后存放在前面所说的 HashMap 里面。继续来看看 PropertyValuesHolder#ofFloat() 方法:

    public static PropertyValuesHolder ofFloat(String propertyName, float... values) {
        return new FloatPropertyValuesHolder(propertyName, values);
    }

创建 FloatPropertyValuesHolder对象,相应的还有 IntPropertyValuesHolderMultiIntValuesHolder以及MultiFloatValuesHolder,都是 PropertyValuesHolder 的子类。

   public FloatPropertyValuesHolder(String propertyName, float... values) 			{
            super(propertyName);
            setFloatValues(values);
        }

调用父类的构造方法并传递了 propertyName,调用 setFloatValues方法的,其又进一步调用了父类的 setFloatValues,在父类的 setFloatValues方法里初始化了动画的关键帧。

    public void setFloatValues(float... values) {
        mValueType = float.class;
        mKeyframes = KeyframeSet.ofFloat(values);
    }

调用KeyframeSet.ofFloat(values)方法,这个方法是将最初传进过来的可变参数,进行重新的封装,并且分为了两种情况,如果可变参数只传递了一个参数,那么就默认添加一个初始关键帧,并将传入的那个参数设置最后一个关键帧。如果已经传递了多个参数,那么就将各个参数设置成关键帧。

然后回到一开始的代码,调用setDuration

    @Override
    @NonNull
    public ObjectAnimator setDuration(long duration) {
        super.setDuration(duration);
        return this;
    }

内部调用了父类的方法,存储下 duration 的值:

    @Override
    public ValueAnimator setDuration(long duration) {
        if (duration < 0) {
            throw new IllegalArgumentException("Animators cannot have negative duration: " +
                    duration);
        }
        mDuration = duration;
        return this;
    }

然后执行到objectAnimator.setInterpolator(new LinearInterpolator());,看下源码:

@Override
    public void setInterpolator(TimeInterpolator value) {
        if (value != null) {
            mInterpolator = value;
        } else {
            mInterpolator = new LinearInterpolator();
        }
    }

如果传递的是 null 的话,则默认使用的便是 LinearInterpolator,即线性插值器。插值器集成自TimeInterpolator接口,来看下代码:

/**
 * 插值器定义了动画变化的频率,其可以是线性的也可以是非线性的,如加速运动或者减速运动。
 */
public interface TimeInterpolator {

    /**
     * 这里传进来的 input 代表当前时间与总时间的比,根据这个时间占比返回当前的变化频率。其输出与输值都在 [0,1] 之间。
     */
    float getInterpolation(float input);
}

来看看 LinearInterpolatorgetInterpolation实现:

    public float getInterpolation(float input) {
        return input;
    }

对,就是返回原值,因为时间的变化肯定始终都是匀速的。

到此动画已经创建完毕,下一步就是执行动画了。

2.执行动画

执行动画从start方法开始,具体代码:

    @Override
    public void start() {
        AnimationHandler.getInstance().autoCancelBasedOn(this);
        if (DBG) {
            Log.d(LOG_TAG, "Anim target, duration: " + getTarget() + ", " + getDuration());
            for (int i = 0; i < mValues.length; ++i) {
                PropertyValuesHolder pvh = mValues[i];
                Log.d(LOG_TAG, "   Values[" + i + "]: " +
                    pvh.getPropertyName() + ", " + pvh.mKeyframes.getValue(0) + ", " +
                    pvh.mKeyframes.getValue(1));
            }
        }
        super.start();
    }

最终调用了父类方法:

    @Override
    public void start() {
        start(false);
    }


    private void start(boolean playBackwards) {
    //需要在有looper的线程执行
        if (Looper.myLooper() == null) {
            throw new AndroidRuntimeException("Animators may only be run on Looper threads");
        }
	...
        addAnimationCallback(0);

        if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
            // If there's no start delay, init the animation and notify start listeners right away
            // to be consistent with the previous behavior. Otherwise, postpone this until the first
            // frame after the start delay.
            startAnimation();
            if (mSeekFraction == -1) {
                // No seek, start at play time 0. Note that the reason we are not using fraction 0
                // is because for animations with 0 duration, we want to be consistent with pre-N
                // behavior: skip to the final value immediately.
                setCurrentPlayTime(0);
            } else {
                setCurrentFraction(mSeekFraction);
            }
        }
    }

看下addAnimationCallback方法:

	//1.
    private void addAnimationCallback(long delay) {
        if (!mSelfPulse) {
            return;
        }
        getAnimationHandler().addAnimationFrameCallback(this, delay);
    }

/**
     * Register to get a callback on the next frame after the delay.
     */
     //2.
    public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
        if (mAnimationCallbacks.size() == 0) {
            getProvider().postFrameCallback(mFrameCallback);
        }
        if (!mAnimationCallbacks.contains(callback)) {
            mAnimationCallbacks.add(callback);
        }

        if (delay > 0) {
            mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay));
        }
    }

主要是向 AnimationHander 添加一个回调接口AnimationHandler.AnimationFrameCallback,而ValueAnimator 就实现了 AnimationFrameCallback,传递的this,具体实现的方法后面会用到,后面再说。

接下来回到start方法,看下startAnimation方法,

	//在内部调用,通过将动画添加到动画列表来启动动画。必须在UI线程上调用。
    private void startAnimation() {
        if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
            Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, getNameForTrace(),
                    System.identityHashCode(this));
        }

        mAnimationEndRequested = false;
        initAnimation();
        mRunning = true;
        if (mSeekFraction >= 0) {
            mOverallFraction = mSeekFraction;
        } else {
            mOverallFraction = 0f;
        }
        if (mListeners != null) {
            notifyStartListeners();
        }
    }

看到内部调用了initAnimation

void initAnimation() {
        if (!mInitialized) {
            int numValues = mValues.length;
            for (int i = 0; i < numValues; ++i) {
                mValues[i].init();
            }
            mInitialized = true;
        }
    }

mValuesPropertyValuesHolder 数组,这里的目的是初始化 PropertyValuesHolder,再看下init方法:

    void init() {
        if (mEvaluator == null) {
            // We already handle int and float automatically, but not their Object
            // equivalents
            mEvaluator = (mValueType == Integer.class) ? sIntEvaluator :
                    (mValueType == Float.class) ? sFloatEvaluator :
                    null;
        }
        if (mEvaluator != null) {
            // KeyframeSet knows how to evaluate the common types - only give it a custom
            // evaluator if one has been set on this class
            mKeyframes.setEvaluator(mEvaluator);
        }
    }

init方法的主要目的是就是给关键帧设置估值器。因为我们前面调用的是 ObjectAnimator#ofFloat() 方法,所以这里默认给的就是 FloatEvaluator。这里也来分析一下估值器。

估值器继承自TypeEvaluator接口:

public interface TypeEvaluator<T> {
    public T evaluate(float fraction, T startValue, T endValue);
}

fraction 代表了startValueendValue 之间的比例,startValueendValue 是我们自己在 ofFloat() 调用时设定的那个。返回结果就是一个线性的结果,在 startValueendValue 之间。那么来看看 FloatEvaluator 的实现:

public class FloatEvaluator implements TypeEvaluator<Number> {
    public Float evaluate(float fraction, Number startValue, Number endValue) {
        float startFloat = startValue.floatValue();
        return startFloat + fraction * (endValue.floatValue() - startFloat);
    }
}

很简单,就是起点值加上当前的段值。

再回到最初的 start方法里,经 startAnimation设置了 KeyFrame 的估值器后,接下来就会进一步调用 setCurrentPlayTime 来开始动画:

    public void setCurrentPlayTime(long playTime) {
        float fraction = mDuration > 0 ? (float) playTime / mDuration : 1;
        setCurrentFraction(fraction);
    }

初始时调用的是setCurrentPlayTime(0),也就是 playTime 为 0,而 mDuration 是我们自己通过 setDuration() 来设置的。所以这里得到的 fraction 也是 0。进一步看 setCurrentFraction() :

public void setCurrentFraction(float fraction) {
        // 再次调用 initAnimation()
        initAnimation();
        // 校准 fraction 为 [0, mRepeatCount + 1]
        fraction = clampFraction(fraction);
        mStartTimeCommitted = true; // do not allow start time to be compensated for jank
        if (isPulsingInternal()) {
            long seekTime = (long) (getScaledDuration() * fraction);
            // 获取动画的当前运行时间
            long currentTime = AnimationUtils.currentAnimationTimeMillis();
            // Only modify the start time when the animation is running. Seek fraction will ensure
            // non-running animations skip to the correct start time.
            // 得到开始时间
            mStartTime = currentTime - seekTime;
        } else {
            // If the animation loop hasn't started, or during start delay, the startTime will be
            // adjusted once the delay has passed based on seek fraction.
            mSeekFraction = fraction;
        }
        mOverallFraction = fraction;
        final float currentIterationFraction = getCurrentIterationFraction(fraction, mReversing);
        // 执行动画,注意这里会先调用子类的 animateValue() 方法
        animateValue(currentIterationFraction);
    }

在看子类和父类的animateValue

//子类 ObjectAnimator#animateValue()
void animateValue(float fraction) {
        final Object target = getTarget();
        if (mTarget != null && target == null) {
            // We lost the target reference, cancel and clean up. Note: we allow null target if the
            /// target has never been set.
            cancel();
            return;
        }
        // 调用父类的 animateValue() ,这个很关键,时间插值与估值器的计算都在父类的 animateValue() 方法中进行的。
        super.animateValue(fraction);
        int numValues = mValues.length;
        for (int i = 0; i < numValues; ++i) {
            // 这里的 mValues 的是PropertyValuesHolder[],也就是在 PropertyValuesHolder 里面来改变了目标 target 的属性值。
            mValues[i].setAnimatedValue(target);
        }
    }
//父类 ValueAnimator#animateValue()
void animateValue(float fraction) {
        // 获取时间插值
        fraction = mInterpolator.getInterpolation(fraction);
        mCurrentFraction = fraction;
        int numValues = mValues.length;
        // 将时间插值送给估值器,计算出 values
        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);
            }
        }
    }

在看setAnimatedValue方法,通过反射来调用属性的get和set方法:

void setAnimatedValue(Object target) {
        if (mProperty != null) {
            mProperty.set(target, getAnimatedValue());
        }
        if (mSetter != null) {
            try {
                mTmpValueArray[0] = getAnimatedValue();
                // 通过反射调用来修改属性值
                mSetter.invoke(target, mTmpValueArray);
            } catch (InvocationTargetException e) {
                Log.e("PropertyValuesHolder", e.toString());
            } catch (IllegalAccessException e) {
                Log.e("PropertyValuesHolder", e.toString());
            }
        }
    }

分析到这里,就完成了动画的一帧关键帧的执行。那么你一定会感到奇怪了,剩下的帧是怎么驱动的呢?还是得回到 start方法里面,在这里最初分析的 addAnimationFrameCallback方法。ValueAnimator 实现了 AnimationFrameCallback,实现了doAnimationFrame方法:

public final boolean doAnimationFrame(long frameTime) {
        .....
        boolean finished = animateBasedOnTime(currentTime);
        if (finished) {
            endAnimation();
        }
        return finished;
    }

在看animateBasedOnTime

boolean animateBasedOnTime(long currentTime) {
        boolean done = false;
        if (mRunning) {
            .....
            float currentIterationFraction = getCurrentIterationFraction(
                    mOverallFraction, mReversing);
            animateValue(currentIterationFraction);
        }
        return done;
    }

这里主要的目的也还是计算出 currentIterationFraction。然后再通过 animateValue方法来执行动画。可以看到只要 doAnimationFrame被不断的调用,就会产生动画的一个关键帧。如果关键帧是连续的,那么最后也就产生了我们所看到的动画

再来分析doAnimationFrame 是如何被不断调用的。这个需要回到 AnimationHandler 中来,在 AnimationHandler 中有一个非常重要的 callback 实现:Choreographer.FrameCallback

private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
        @Override
        public void doFrame(long frameTimeNanos) {
            doAnimationFrame(getProvider().getFrameTime());
            if (mAnimationCallbacks.size() > 0) {
                getProvider().postFrameCallback(this);
            }
        }
    };

这里监听的是Vsync信号,每 1 秒里收到 60 次回调,在这里就实现了不断地调用 doAnimationFrame() 来驱动动画了。

到这里,属性动画的整个过程就大体走了一遍。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值