美文网首页工作生活
Android源码学习-属性动画源码浅析

Android源码学习-属性动画源码浅析

作者: m1Ku | 来源:发表于2019-06-30 16:40 被阅读0次

    介绍

    安卓的动画有:View动画,帧动画和属性动画。其中View动画只是让View的影像发生变化,并不能真的改变View的属性,例如平移动画后的View并不能响应点击事件,而且也只有平移、缩放、旋转和透明度四种动画。帧动画是按照顺序播放一组定义好的图片,这种动画比较简单,但是如果图片过大会引起OOM。属性动画能作用于任何对象,而且能真正的改变对象的属性,使用简单且灵活,它对应于ValueAnimator和ObjectAnimator,ObjectAnimator继承自ValueAnimator,ValueAnimator可以不指定对象,ObjectAnimator需要指定动画作用的对象。

    以ObjectAnimator为例,下面代码让一个Button在3秒内沿X方向平移200像素

    ObjectAnimator animator = ObjectAnimator
                    .ofFloat(btn, "translationX", 200)
                    .setDuration(3000);
            animator.setInterpolator(new DecelerateInterpolator());
            animator.start();
    

    源码分析

    下面进入源码,分析下属性动画的原理

    ObjectAnimator.ofFloat方法

    public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
        ObjectAnimator anim = new ObjectAnimator(target, propertyName);
        anim.setFloatValues(values);
        return anim;
    }
    

    这里首先初始化了ObjectAnimator对象,并记录了动画作用的目标对象和属性

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

    在初始化ObjectAnimator后,又调用了其setFloatValues方法

    @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);
        }
    }
    

    该方法的入参是可变参数,是我们在ofFloat方法中传入的参数。上例中只传了一个参数,它会作为可变参数的最后一项,如果未指定起始值,程序会获取该属性的起始值。

    这里mProperty为空,走到else分支。分支中调用PropertyValuesHolder.ofFloat方法构建FloatPropertyValuesHolder对象,并将其传入到setValues方法中。PropertyValuesHolder对象是对属性以及该属性要变化的值的封装,这些值的类型为Float时对应着FloatPropertyValuesHolder对象。

    public static PropertyValuesHolder ofFloat(String propertyName, float... values) {
        return new FloatPropertyValuesHolder(propertyName, values);
    }
    
    public FloatPropertyValuesHolder(String propertyName, float... values) {
        super(propertyName);
        setFloatValues(values);
     }
     @Override
     public void setFloatValues(float... values) {
         super.setFloatValues(values);
         mFloatKeyframes = (Keyframes.FloatKeyframes) mKeyframes;
     }
    

    下面看对values的处理,调用父类PropertyValuesHolder的setFloatValues

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

    调用KeyframeSet.ofFloat

    public static KeyframeSet ofFloat(float... values) {
        boolean badValue = false;
        int numKeyframes = values.length;
        FloatKeyframe keyframes[] = new FloatKeyframe[Math.max(numKeyframes,2)];
        if (numKeyframes == 1) {
            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) {
                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);
    }
    

    这里有一个新的对象Keyframe,其名为关键帧。它是一个抽象类,可以通过工厂方法得到特定类型的实现类,这里是FloatKeyframe。这个对象包装了动画的时间以及对应的值,该时间是当前动画执行时间相对于动画总时间的进度比例。这里会初始化一个最小长度为2的Keyframe数组,然后判断传入的values长度为1的话,会构造一个Keyframe对象赋值给数组第一个元素。如果values长度大于1的话,遍历构造Keyframe对象并赋值给数组。最后使用Keyframe数组构造FloatKeyframeSet对象返回。

    FloatPropertyValuesHolder对象构建完成,继续回到ObjectAnimator中将其传入setValues方法

    public void setValues(PropertyValuesHolder... values) {
        int numValues = values.length;
        mValues = values;
        mValuesMap = new HashMap<String, PropertyValuesHolder>(numValues);
        for (int i = 0; i < numValues; ++i) {
            PropertyValuesHolder valuesHolder = values[i];
            mValuesMap.put(valuesHolder.getPropertyName(), valuesHolder);
        }
        // New property/values/target should cause re-initialization prior to starting
        mInitialized = false;
    }
    

    这里遍历将属性名和其对应的PropertyValuesHolder添加到一个HashMap中管理。

    setDuration方法

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

    这个方法就是设置到动画的执行时长,为mDuration属性赋值。

    setInterpolator方法

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

    这里采用策略设计模式,将差值器从外界注入,默认为加速减速差值器。差值器决定了属性值随时间的变化情况,比如是加速还是加速等,在计算属性值时用到。

    start方法

    @Override
    public void start() {
        AnimationHandler.getInstance().autoCancelBasedOn(this);
        //..省略log代码
        super.start();
    }
    

    第一句代码是根据mAutoCancel标志位以及当前动画和前面的动画是否有相同的对象和属性,如果标志位设为true并且后面条件满足的话,就会自动取消动画。

    调用父类start方法

    @Override
    public void start() {
        start(false);
    }
    
    private void start(boolean playBackwards) {
        if (Looper.myLooper() == null) {
            throw new AndroidRuntimeException("Animators may only be run on Looper threads");
        }
        mReversing = playBackwards;
        mSelfPulse = !mSuppressSelfPulseRequested;
        //...
        addAnimationCallback(0);
    
        if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
            startAnimation();
            if (mSeekFraction == -1) {
                setCurrentPlayTime(0);
            } else {
                setCurrentFraction(mSeekFraction);
            }
        }
    }
    

    调用addAnimationCallback添加动画回调

    private void addAnimationCallback(long delay) {
        if (!mSelfPulse) {
            return;
        }
        getAnimationHandler().addAnimationFrameCallback(this, delay);
    }
    
    public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
        if (mAnimationCallbacks.size() == 0) {
            getProvider().postFrameCallback(mFrameCallback);
        }
        if (!mAnimationCallbacks.contains(callback)) {
            mAnimationCallbacks.add(callback);
        }
      //...
    }
    

    判断mAnimationCallbacks集合size为0即属性动画第一次启动时,这里会调用mChoreographer的postFrameCallback方法添加VSYNC的刷新信号回调,然后再添加动画的AnimationFrameCallback回调。

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

    这个就是接收到垂直信号的回调,判断当前还存在动画回调的话,再将这个mFrameCallback回调添加到mChoreographer以继续进行垂直信号的回调。这里doAnimationFrame方法的逻辑留在后面分析,我们继续看start方法接下来的逻辑。

    没有设置延迟,延迟时间为0,直接调用startAnimation方法

    private void startAnimation() {
        //...省略log代码
        mAnimationEndRequested = false;
        initAnimation();
        mRunning = true;
        if (mSeekFraction >= 0) {
            mOverallFraction = mSeekFraction;
        } else {
            mOverallFraction = 0f;
        }
        if (mListeners != null) {
            notifyStartListeners();
        }
    }
    

    调用initAnimation方法,并且回调外部设置的listener的onAnimationStart方法。

    @CallSuper
    @Override
    void initAnimation() {
        if (!mInitialized) {
            final Object target = getTarget();
            if (target != null) {
                final int numValues = mValues.length;
                for (int i = 0; i < numValues; ++i) {
                    mValues[i].setupSetterAndGetter(target);
                }
            }
            super.initAnimation();
        }
    }
    

    遍历mValues数组,调用PropertyValuesHolder的setupSetterAndGetter方法

    void setupSetterAndGetter(Object target) {
        //...省略mProperty != null的分支
        // We can't just say 'else' here because the catch statement sets mProperty to null.
        if (mProperty == null) {
            Class targetClass = target.getClass();
            if (mSetter == null) {
                setupSetter(targetClass);
            }
            List<Keyframe> keyframes = mKeyframes.getKeyframes();
            int keyframeCount = keyframes == null ? 0 : keyframes.size();
            for (int i = 0; i < keyframeCount; i++) {
                Keyframe kf = keyframes.get(i);
                if (!kf.hasValue() || kf.valueWasSetOnStart()) {
                    if (mGetter == null) {
                        setupGetter(targetClass);
                        if (mGetter == null) {
                            // Already logged the error - just return to avoid NPE
                            return;
                        }
                    }
                    try {
                        Object value = convertBack(mGetter.invoke(target));
                        kf.setValue(value);
                        kf.setValueWasSetOnStart(true);
                    } catch (InvocationTargetException e) {
                        Log.e("PropertyValuesHolder", e.toString());
                    } catch (IllegalAccessException e) {
                        Log.e("PropertyValuesHolder", e.toString());
                    }
                }
            }
        }
    }
    

    如果mSetter或者mGetter为空的话,就分别调用setupSetter和setupGetter通过反射获取到对应的方法。初始化get方法后,调用目标对象的get方法获取到属性的初始值并转化为set方法需要的类型,赋值给KeyFrame。

    调用父类ValueAnimator的initAnimation

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

    遍历mValues数组为PropertyValuesHolder的KeyFrameSet设置估值器对象。看完了startAnimation的逻辑,我们继续回到start方法中,接下来执行setCurrentPlayTime

    public void setCurrentPlayTime(long playTime) {
        float fraction = mDuration > 0 ? (float) playTime / mDuration : 1;
        setCurrentFraction(fraction);
    }
    
    public void setCurrentFraction(float fraction) {
        initAnimation();
        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();
            mStartTime = currentTime - seekTime;
        } else {
            mSeekFraction = fraction;
        }
        mOverallFraction = fraction;
        final float currentIterationFraction = getCurrentIterationFraction(fraction, mReversing);
        animateValue(currentIterationFraction);
    }
    

    方法的最后调用了animateValue方法

     @CallSuper
        @Override
        void animateValue(float fraction) {
            final Object target = getTarget();
            if (mTarget != null && target == null) {
                cancel();
                return;
            }
            super.animateValue(fraction);
            int numValues = mValues.length;
            for (int i = 0; i < numValues; ++i) {
                mValues[i].setAnimatedValue(target);
            }
        }
    

    调用父类的animateValue

    @CallSuper
    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);
            }
        }
    }
    

    遍历PropertyValuesHolder数组并调用calculateValue方法,最后调用动画更新回调方法。

    @Override
    void calculateValue(float fraction) {
        mFloatAnimatedValue = mFloatKeyframes.getFloatValue(fraction);
    }
    

    调用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的animateValue方法,遍历mValues并调用setAnimatedValue方法

    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());
            }
        }
    }
    

    最后调用目标对象的set方法将为目标对象的属性进行了赋值,这样就完成了目标对象属性值的改变。

    前面还有个方法没分析,在接收到垂直信号回调时调用了doAnimationFrame方法

    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();
    }
    

    遍历动画回调,调用了回调的doAnimationFrame方法,该方法在ValueAnimator中实现

    public final boolean doAnimationFrame(long frameTime) {
        //...省略代码
        final long currentTime = Math.max(frameTime, mStartTime);
        boolean finished = animateBasedOnTime(currentTime);
    
        if (finished) {
            endAnimation();
        }
        return finished;
    }
    

    调用animateBasedOnTime方法

    boolean animateBasedOnTime(long currentTime) {
        boolean done = false;
        if (mRunning) {
            final long scaledDuration = getScaledDuration();
            final float fraction = scaledDuration > 0 ?
                    (float)(currentTime - mStartTime) / scaledDuration : 1f;
            final float lastFraction = mOverallFraction;
            final boolean newIteration = (int) fraction > (int) lastFraction;
            final boolean lastIterationFinished = (fraction >= mRepeatCount + 1) &&
                    (mRepeatCount != INFINITE);
            if (scaledDuration == 0) {
                // 0 duration animator, ignore the repeat count and skip to the end
                done = true;
            } else if (newIteration && !lastIterationFinished) {
                // Time to repeat
                if (mListeners != null) {
                    int numListeners = mListeners.size();
                    for (int i = 0; i < numListeners; ++i) {
                        mListeners.get(i).onAnimationRepeat(this);
                    }
                }
            } else if (lastIterationFinished) {
                done = true;
            }
            mOverallFraction = clampFraction(fraction);
            float currentIterationFraction = getCurrentIterationFraction(
                    mOverallFraction, mReversing);
            animateValue(currentIterationFraction);
        }
        return done;
    }
    

    最后又调用到了熟悉的animateValue方法,于是随着每一个垂直信号的到来都会进行属性值的计算和赋值。该方法有一个布尔类型的返回值,表示当前动画是否结束,如果结束调用endAnimation方法。

    private void endAnimation() {
        if (mAnimationEndRequested) {
            return;
        }
        removeAnimationCallback();
    
        mAnimationEndRequested = true;
        mPaused = false;
        boolean notify = (mStarted || mRunning) && mListeners != null;
        if (notify && !mRunning) {
            // If it's not yet running, then start listeners weren't called. Call them now.
            notifyStartListeners();
        }
        mRunning = false;
        mStarted = false;
        mStartListenersCalled = false;
        mLastFrameTime = -1;
        mFirstFrameTime = -1;
        mStartTime = -1;
        if (notify && mListeners != null) {
            ArrayList<AnimatorListener> tmpListeners =
                    (ArrayList<AnimatorListener>) mListeners.clone();
            int numListeners = tmpListeners.size();
            for (int i = 0; i < numListeners; ++i) {
                tmpListeners.get(i).onAnimationEnd(this, mReversing);
            }
        }
        // mReversing needs to be reset *after* notifying the listeners for the end callbacks.
        mReversing = false;
    }
    

    在该方法中移除了动画的回调,将相关参数值重置,最后调用了动画结束的回调方法通知外界动画执行完毕。

    相关文章

      网友评论

        本文标题:Android源码学习-属性动画源码浅析

        本文链接:https://www.haomeiwen.com/subject/jrubcctx.html