Android属性动画源码解析(一)

本文对Android属性动画中ValueAnimator的启动流程进行了详细解析。包括创建属性动画、基于源码分析动画启动过程,如检查handler、注册回调等。还阐述了ObjectAnimator更新对象属性原理、path动画特点,以及Property、TypeConverter的作用,并对属性动画的执行特点进行了总结。

Android属性动画源码解析(一)

ValueAnimator动画启动流程

1. 创建属性动画


    private void initAnim() {
        //最简单属性动画
        simple = ObjectAnimator.ofFloat(0, 300);
        simple.setDuration(1000);
        simple.addUpdateListener(animation -> view.setTranslationX((Float) animation.getAnimatedValue()));

        //直接操作对象属性的动画,propertyName在对象中要有和bean一样的get和set方法
        property = ObjectAnimator.ofFloat(view, "translationX", 0, 300);
        property.setDuration(1000);

        Path path = new Path();
        path.moveTo(0, 0);
        path.lineTo(300, 0);
        path.lineTo(300, 300);
        path.lineTo(0, 300);
        path.close();
        //path创建的动画
        this.path = ObjectAnimator.ofFloat(view, "translationX", "translationY", path);
        this.path.setDuration(2000);

        //更新bean属性
        customProperty = ObjectAnimator.ofFloat(new Data(), "value", 0, 300);
        customProperty.setDuration(100);
    }

    static class Data {
        private float value;

        public float getValue() {
            return value;
        }

        public void setValue(float value) {
            this.value = value;
            log.dMKV("setValue", "value", value);
        }
    }

这四个动画的效果如下:

2. 基于第一个动画的源码分析

首先来看动画创建,仅仅创建了一个ValueAnimator,构造器什么都没做:

    public void setFloatValues(float... values) {
    	...
        if (mValues == null || mValues.length == 0) {
            setValues(PropertyValuesHolder.ofFloat("", values));
        } else {
            PropertyValuesHolder valuesHolder = mValues[0];
            valuesHolder.setFloatValues(values);
        }
        ...
    }

新创建动画,直接走setValues,先来看PropertyValuesHolder.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);
    }

  如果仅一个参数,会在前面补mFraction和mValue都是0的Keyframe,从else可以知道,values.length为3以上时,mFraction会均分赋值,比如:如果values是0,1,2,3,四个数分成三段,fraction分别是0,1/3,2/3,1。

  再回到setValues,实际上就赋值了一个PropertyValuesHolder参数,实际上是可以有多个参数,path的动画就有两个参数,但是getAnimatedValue等方法仅操作第一个PropertyValuesHolder,需要注意使用。

接下来,来看动画的启动

    private void start(boolean playBackwards) {
        if (Looper.myLooper() == null) {
            throw new AndroidRuntimeException("Animators may only be run on Looper threads");
        }
        mReversing = playBackwards;
        mSelfPulse = !mSuppressSelfPulseRequested;
        ...
        mStarted = true;
        mPaused = false;
        mRunning = false;
        mAnimationEndRequested = false;
        mLastFrameTime = -1;
        mFirstFrameTime = -1;
        mStartTime = -1;
        addAnimationCallback(0);

        if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
            startAnimation();
            if (mSeekFraction == -1) {
                setCurrentPlayTime(0);
            } else {
                setCurrentFraction(mSeekFraction);
            }
        }
    }

  首先检查了有没有handler,这是因为编舞者需要handler,并没有检查主线程,属性动画可以在子线程执行,当然更新UI还是不行,没有调度线程的能力,得开发者自己搞。
  addAnimationCallback是一个关键方法,它向AnimationHandler注册了一个回调,AnimationHandler本质是对编舞者(Choreographer)的一个简单封装,AnimationHandler和Choreographer都不是真正的单例,而是用ThreadLocal修饰的一个相对于线程的单例,Choreographer利用SurfaceFlinger的脉冲信号或Handler的延时信息来达成一定频率的回调,让动画得以执行。

    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));
        }
    }
    public void removeCallback(AnimationFrameCallback callback) {
        mCommitCallbacks.remove(callback);
        mDelayedCallbackStartTime.remove(callback);
        int id = mAnimationCallbacks.indexOf(callback);
        if (id >= 0) {
            mAnimationCallbacks.set(id, null);
            mListDirty = true;
        }
    }
    private class MyFrameCallbackProvider implements AnimationFrameCallbackProvider {

        final Choreographer mChoreographer = Choreographer.getInstance();

        @Override
        public void postFrameCallback(Choreographer.FrameCallback callback) {
            mChoreographer.postFrameCallback(callback);
        }

        @Override
        public long getFrameTime() {
            return mChoreographer.getFrameTime();
        }
    }

编舞者部分代码:

    private void postCallbackDelayedInternal(int callbackType,
            Object action, Object token, long delayMillis) {
        synchronized (mLock) {
            final long now = SystemClock.uptimeMillis();
            final long dueTime = now + delayMillis;
            mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);

            if (dueTime <= now) {
                scheduleFrameLocked(now);
            } else {
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
                msg.arg1 = callbackType;
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, dueTime);
            }
        }
    }
    private void scheduleFrameLocked(long now) {
        if (!mFrameScheduled) {
            mFrameScheduled = true;
            if (USE_VSYNC) {
                if (isRunningOnLooperThreadLocked()) {
                    scheduleVsyncLocked();
                } else {
                    Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
                    msg.setAsynchronous(true);
                    mHandler.sendMessageAtFrontOfQueue(msg);
                }
            } else {
                final long nextFrameTime = Math.max(
                        mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);
                Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, nextFrameTime);
            }
        }
    }
    void doFrame(long frameTimeNanos, int frame,
            DisplayEventReceiver.VsyncEventData vsyncEventData) {
        final long startNanos;
        final long frameIntervalNanos = vsyncEventData.frameInterval;
        boolean resynced = false;
        try {
            FrameTimeline timeline = mFrameData.update(frameTimeNanos, vsyncEventData);
            synchronized (mLock) {
                long intendedFrameTimeNanos = frameTimeNanos;
                startNanos = System.nanoTime();
                final long jitterNanos = startNanos - frameTimeNanos;
                if (jitterNanos >= frameIntervalNanos) {
                    frameTimeNanos = startNanos;
                    if (frameIntervalNanos == 0) {
                        Log.i(TAG, "Vsync data empty due to timeout");
                    } else {
                        long lastFrameOffset = jitterNanos % frameIntervalNanos;
                        frameTimeNanos = frameTimeNanos - lastFrameOffset;
                        final long skippedFrames = jitterNanos / frameIntervalNanos;
                        if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
                            Log.i(TAG, "Skipped " + skippedFrames + " frames!  "
                                    + "The application may be doing too much work on its main "
                                    + "thread.");
                        }
                    }
                    timeline = mFrameData.update(
                            frameTimeNanos, mDisplayEventReceiver, jitterNanos);
                    resynced = true;
                }
                ...

                mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos,
                        vsyncEventData.preferredFrameTimeline().vsyncId,
                        vsyncEventData.preferredFrameTimeline().deadline, startNanos,
                        vsyncEventData.frameInterval);
                mFrameScheduled = false;
                mLastFrameTimeNanos = frameTimeNanos;
                mLastFrameIntervalNanos = frameIntervalNanos;
                mLastVsyncEventData.copyFrom(vsyncEventData);
            }

            if (resynced && Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
                String message = String.format("Choreographer#doFrame - resynced to %d in %.1fms",
                        timeline.mVsyncId, (timeline.mDeadlineNanos - startNanos) * 0.000001f);
                Trace.traceBegin(Trace.TRACE_TAG_VIEW, message);
            }

            AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);

            mFrameInfo.markInputHandlingStart();
            doCallbacks(Choreographer.CALLBACK_INPUT, frameIntervalNanos);

            mFrameInfo.markAnimationsStart();
            doCallbacks(Choreographer.CALLBACK_ANIMATION, frameIntervalNanos);
            doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameIntervalNanos);

            mFrameInfo.markPerformTraversalsStart();
            doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameIntervalNanos);

            doCallbacks(Choreographer.CALLBACK_COMMIT, frameIntervalNanos);
        } finally {
            AnimationUtils.unlockAnimationClock();
            if (resynced) {
                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

  上面注册使用的callbackType是CALLBACK_ANIMATION,编舞者定义了5种type,各有各的执行顺序,mCallbackQueues是一个队列(是一个链表,类似Message的回收队列)数组,每种type都有一个队列。

  USE_VSYNC为true代表使用SurfaceFilinger来驱动,false意味着通过handler延时消息来驱动(延时是10ms,也就是理论上100fps),两种方式最后都会走到doFrame方法。这里对帧数据尤其是时间进行了一些列的检查,其中有个主线程做太多事的log不少开发者都见过(ViewRootImpl里面也有一个编舞者),CALLBACK_INPUT等5种类型的事件也在这里回调。

  回到start方法,看startAnimation方法,其调用了initAnimation,并回调了notifyStartListeners,AnimatorListener::onAnimationStart就是在这里回调。initAnimation调用了PropertyValuesHolder的init方法。

    void init() {
        if (mEvaluator == null) {

            mEvaluator = (mValueType == Integer.class) ? sIntEvaluator :
                    (mValueType == Float.class) ? sFloatEvaluator :
                    null;
        }
        if (mEvaluator != null) {
            mKeyframes.setEvaluator(mEvaluator);
        }
    }

  init方法给mKeyframes放入了估值器,也就是给KeyframeSet添加估值器,这里非常重要,估值器真正起作用的地方就在这里。

  再次回到start方法,看setCurrentPlayTime(0)

    public void setCurrentFraction(float fraction) {
        initAnimation();
        fraction = clampFraction(fraction);
        mStartTimeCommitted = true;
        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);
    }
    private float getCurrentIterationFraction(float fraction, boolean inReverse) {
        fraction = clampFraction(fraction);
        int iteration = getCurrentIteration(fraction);
        float currentFraction = fraction - iteration;
        return shouldPlayBackward(iteration, inReverse) ? 1f - currentFraction : currentFraction;
    }

  再次init动画,之前已经init了,这个忽略,getCurrentIterationFraction方法通过计算,将动画分为一段一段,每一段都是0~1,此方法也通过shouldPlayBackward简单的完成了动画的反向执行,接下来来看核心方法animateValue。

    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 (mSeekFraction >= 0 || mStartListenersCalled) {
            callOnList(mUpdateListeners, AnimatorCaller.ON_UPDATE, this, false);
        }
    }

  在这里看到了插值器,它计算出了新的fraction,然后将这个值传入PropertyValuesHolder.calculateValue,在这之后通过callOnList调用了ON_UPDATE回调了AnimatorUpdateListener.onAnimationUpdate

	void calculateValue(float fraction) {
        Object value = mKeyframes.getValue(fraction);
        mAnimatedValue = mConverter == null ? value : mConverter.convert(value);
    }

  之前init的时候,将估值器传入了mKeyframes,现在把通过插值器计算后的值给到mKeyframes并获取Value值(这里直接看通用的方法,Float的方法和这个其实没有区别,计算方式完全一样),这个就是getAnimatedValue获取到的值。

    public Object getValue(float fraction) {
        if (mNumKeyframes == 2) {
            if (mInterpolator != null) {
                fraction = mInterpolator.getInterpolation(fraction);
            }
            return mEvaluator.evaluate(fraction, mFirstKeyframe.getValue(),
                    mLastKeyframe.getValue());
        }
        if (fraction <= 0f) {
            final Keyframe nextKeyframe = mKeyframes.get(1);
            final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
            if (interpolator != null) {
                fraction = interpolator.getInterpolation(fraction);
            }
            final float prevFraction = mFirstKeyframe.getFraction();
            float intervalFraction = (fraction - prevFraction) /
                (nextKeyframe.getFraction() - prevFraction);
            return mEvaluator.evaluate(intervalFraction, mFirstKeyframe.getValue(),
                    nextKeyframe.getValue());
        } else if (fraction >= 1f) {
            final Keyframe prevKeyframe = mKeyframes.get(mNumKeyframes - 2);
            final TimeInterpolator interpolator = mLastKeyframe.getInterpolator();
            if (interpolator != null) {
                fraction = interpolator.getInterpolation(fraction);
            }
            final float prevFraction = prevKeyframe.getFraction();
            float intervalFraction = (fraction - prevFraction) /
                (mLastKeyframe.getFraction() - prevFraction);
            return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(),
                    mLastKeyframe.getValue());
        }
        Keyframe prevKeyframe = mFirstKeyframe;
        for (int i = 1; i < mNumKeyframes; ++i) {
            Keyframe nextKeyframe = mKeyframes.get(i);
            if (fraction < nextKeyframe.getFraction()) {
                final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
                final float prevFraction = prevKeyframe.getFraction();
                float intervalFraction = (fraction - prevFraction) /
                    (nextKeyframe.getFraction() - prevFraction);
                if (interpolator != null) {
                    intervalFraction = interpolator.getInterpolation(intervalFraction);
                }
                return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(),
                        nextKeyframe.getValue());
            }
            prevKeyframe = nextKeyframe;
        }
        return mLastKeyframe.getValue();
    }

  这里可以明显看出插值器的输出值是如何被使用的,mKeyframes的各个Fraction值是用1均分而来,插值器的返回值是不定的,可以是负数,也可以大于1,因此有了如上的两个判断。这里用默认插值器来看Value的变化。默认插值器是AccelerateDecelerateInterpolator,数值变化如下:

  这是一个三角函数变化曲线,减去0.5再乘以2就是标准曲线,大致如右图。如果是第一个动画,那Value值的变化就是如此(Fraction*300即可),如果ofFloat传入了多个值,比如:0,1,2,3,比如1~2之间,intervalFraction是(fraction - 1/3) / (2/3 - 1/3),而由于插值器的干预,fraction到这个范围明显只有中间几个数(0.34~0.65),也就是前半段和后半段占用的运行时间段都比中段多,前后都花了更多的时间执行,也就是前后View表现的运动状态是慢一些的,中间比较快。

  intervalFraction的计算是在这个分段计算比率(0~1),估值器也不用是线性估值,可以针对每一段执行不同的估值逻辑,让动画更多样更复杂。setValues的时候将将1平均分成values.length - 1段来赋值每个KeyFrame,这里将每一段都看成独立部分,每一段都是0~1范围的fraction值。

  可以看到这里也存在一个插值器,我们自己创建的动画都是null,不存在插值器的二次计算,但是这个并不是没有用的,在AnimatorInflater有相应的插值器的导入,也就是xml写的属性动画。比如如下动画,当然这样加载出来的动画仍然存在ValueAnimator中的默认插值器,通过xml.setInterpolator(null)可以把默认插值器改成线性插值器,其实就相当于没有插值器。keyframe的插值器都是用next的插值器,第一个就不用设置插值器,那个是不会被使用的

<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000">

    <propertyValuesHolder
        android:propertyName="translationX"
        android:valueType="floatType">

        <keyframe
            android:fraction="0"
            android:value="0" />
        <keyframe
            android:fraction="0.5"
            android:interpolator="@android:anim/cycle_interpolator"
            android:value="150" />
        <keyframe
            android:fraction="1"
            android:interpolator="@android:anim/linear_interpolator"
            android:value="300" />

    </propertyValuesHolder>

</objectAnimator>
        xml = (ValueAnimator) AnimatorInflater.loadAnimator(this, R.animator.test);
        xml.setInterpolator(null);
        xml.setTarget(view);

动画的启动就完成了,后续动画更新由编舞者驱动,具体方法如下:

public final boolean doAnimationFrame(long frameTime) {
        ...
        mLastFrameTime = frameTime;
        final long currentTime = Math.max(frameTime, mStartTime);
        boolean finished = animateBasedOnTime(currentTime);

        if (finished) {
            endAnimation();
        }
        return finished;
    }
    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) {
                done = true;
            } else if (newIteration && !lastIterationFinished) {
                notifyListeners(AnimatorCaller.ON_REPEAT, false);
            } else if (lastIterationFinished) {
                done = true;
            }
            mOverallFraction = clampFraction(fraction);
            float currentIterationFraction = getCurrentIterationFraction(
                    mOverallFraction, mReversing);
            animateValue(currentIterationFraction);
        }
        return done;
    }

  最后还是回到了animateValue,参数计算方式没什么改变,getScaledDuration是一个开发者不可修改的值,但是用户可以在开发者模式中关闭动画,它的值就会变成0,所有动画都会执行的瞬间就结束,最原始的fraction是(当前时间-启动时间)/ duration,动画是可以多次反复执行的,因此fraction是能大于1的,所以需要getCurrentIterationFraction来把值限定到0~1之间。animateBasedOnTime的返回值为true时,动画就执行完毕了。

  这里有ON_REPEAT的回调,其它几个就不说了,很容易找。

3. ObjectAnimator直接更新对象属性的原理

首先来看动画创建,仅仅赋了值,啥都没干

    private ObjectAnimator(Object target, String propertyName) {
        setTarget(target);
        setPropertyName(propertyName);
    }
    public void setPropertyName(@NonNull String propertyName) {
        if (mValues != null) {
            PropertyValuesHolder valuesHolder = mValues[0];
            String oldName = valuesHolder.getPropertyName();
            valuesHolder.setPropertyName(propertyName);
            mValuesMap.remove(oldName);
            mValuesMap.put(propertyName, valuesHolder);
        }
        mPropertyName = propertyName;
        mInitialized = false;
    }

继续看ObjectAnimator的setFloatValues,和之前的没有任何区别,还是FloatPropertyValuesHolder,只是之前的mPropertyName是""

    public void setFloatValues(float... values) {
        if (mValues == null || mValues.length == 0) {
            if (mProperty != null) {
                setValues(PropertyValuesHolder.ofFloat(mProperty, values));
            } else {
            	//基于第四个动画,应该看这里
                setValues(PropertyValuesHolder.ofFloat(mPropertyName, values));
            }
        } else {
            super.setFloatValues(values);
        }
    }

继续看启动,调到关键代码处

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

获取到值后,调用setAnimatedValue方法对对象进行赋值

        void setAnimatedValue(Object target) {
            if (mFloatProperty != null) {
                mFloatProperty.setValue(target, mFloatAnimatedValue);
                return;
            }
            if (mProperty != null) {
                mProperty.set(target, mFloatAnimatedValue);
                return;
            }
            if (mJniSetter != 0) {
                nCallFloatMethod(target, mJniSetter, mFloatAnimatedValue);
                return;
            }
            if (mSetter != null) {
                try {
                    mTmpValueArray[0] = mFloatAnimatedValue;
                    mSetter.invoke(target, mTmpValueArray);
                } catch (InvocationTargetException e) {
                    Log.e("PropertyValuesHolder", e.toString());
                } catch (IllegalAccessException e) {
                    Log.e("PropertyValuesHolder", e.toString());
                }
            }
        }

第四个动画是传入了PropertyName,因此是使用反射,FloatPropertyValuesHolder的反射在native进行,因此是mJniSetter,相关反射在initAnimation执行

    void initAnimation() {
        if (!mInitialized) {
            // mValueType may change due to setter/getter setup; do this before calling super.init(),
            // which uses mValueType to set up the default type evaluator.
            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();
        }
    }

这种方式调用的动画是有限制的,propertyName首字母大写以后,拼接set和get在后的方法名必须在对象中找到。

4. path动画

Path动画其实就是一组多点float数据

    public PathKeyframes(Path path, float error) {
        if (path == null || path.isEmpty()) {
            throw new IllegalArgumentException("The path must not be null or empty");
        }
        mKeyframeData = path.approximate(error);
    }

approximate方法会返回很多个点,这些点构成了Path路径

    public Object getValue(float fraction) {
        int numPoints = mKeyframeData.length / 3;
        if (fraction < 0) {
            return interpolateInRange(fraction, 0, 1);
        } else if (fraction > 1) {
            return interpolateInRange(fraction, numPoints - 2, numPoints - 1);
        } else if (fraction == 0) {
            return pointForIndex(0);
        } else if (fraction == 1) {
            return pointForIndex(numPoints - 1);
        } else {
            // Binary search for the correct section
            int low = 0;
            int high = numPoints - 1;

            while (low <= high) {
                int mid = (low + high) / 2;
                float midFraction = mKeyframeData[(mid * NUM_COMPONENTS) + FRACTION_OFFSET];

                if (fraction < midFraction) {
                    high = mid - 1;
                } else if (fraction > midFraction) {
                    low = mid + 1;
                } else {
                    return pointForIndex(mid);
                }
            }

            // now high is below the fraction and low is above the fraction
            return interpolateInRange(fraction, high, low);
        }
    }
    private PointF interpolateInRange(float fraction, int startIndex, int endIndex) {
        int startBase = (startIndex * NUM_COMPONENTS);
        int endBase = (endIndex * NUM_COMPONENTS);

        float startFraction = mKeyframeData[startBase + FRACTION_OFFSET];
        float endFraction = mKeyframeData[endBase + FRACTION_OFFSET];

        float intervalFraction = (fraction - startFraction)/(endFraction - startFraction);

        float startX = mKeyframeData[startBase + X_OFFSET];
        float endX = mKeyframeData[endBase + X_OFFSET];
        float startY = mKeyframeData[startBase + Y_OFFSET];
        float endY = mKeyframeData[endBase + Y_OFFSET];

        float x = interpolate(intervalFraction, startX, endX);
        float y = interpolate(intervalFraction, startY, endY);

        mTempPointF.set(x, y);
        return mTempPointF;
    }

  通过二分法查找相应的点,如果没找到,找到相邻的两个点,计算比率,然后按比率来获取点的值,和最上面的getValue是同一种计算方式,PathKeyframes没有估值器,它的职能决定了它不允许开发者进一步的调整。

  查看之前ObjectAnimator.animateValue的源码可以知道,animateValue会遍历PropertyValuesHolder列表,每一个都进行赋值,再看Path创建时

    public static ObjectAnimator ofFloat(Object target, String xPropertyName, String yPropertyName,
            Path path) {
        PathKeyframes keyframes = KeyframeSet.ofPath(path);
        PropertyValuesHolder x = PropertyValuesHolder.ofKeyframes(xPropertyName,
                keyframes.createXFloatKeyframes());
        PropertyValuesHolder y = PropertyValuesHolder.ofKeyframes(yPropertyName,
                keyframes.createYFloatKeyframes());
        return ofPropertyValuesHolder(target, x, y);
    }
    public FloatKeyframes createXFloatKeyframes() {
        return new FloatKeyframesBase() {
            @Override
            public float getFloatValue(float fraction) {
                PointF pointF = (PointF) PathKeyframes.this.getValue(fraction);
                return pointF.x;
            }
        };
    }
    static PropertyValuesHolder ofKeyframes(String propertyName, Keyframes keyframes) {
        if (keyframes instanceof Keyframes.IntKeyframes) {
            ...
        } else if (keyframes instanceof Keyframes.FloatKeyframes) {
            return new FloatPropertyValuesHolder(propertyName,
                    (Keyframes.FloatKeyframes) keyframes);
        } else {
            ...
        }
    }

实际上是创建了FloatPropertyValuesHolder,然后又是propertyName那套逻辑,和上面的动画2没区别,获取值时是调用的FloatKeyframesBase的getFloatValue,而它的值是PathKeyframes.getValue提供。

5. Property

  使用propertyName来直接操作对象是创建动画常用的方式,但是这里有个问题,它需要反射,从源码也可以看出,谷歌特意使用了native来进行反射,进一步提速,其实是可以避免反射的,自定义Property即可。

		propertyTest = ObjectAnimator.ofFloat(new Data(), new Property<Data, Float>(Float.class, "value") {
            @Override
            public Float get(Data data) {
                return data.value;
            }

            @Override
            public void set(Data data, Float value) {
                log.dMKV("custom property: setValue", "value", value);
                data.setValue(value);
            }
        }, 0, 300);

  这个Property同样会赋值到FloatPropertyValuesHolder,再看setAnimatedValue,直接调用了set方法。

        void setAnimatedValue(Object target) {
            if (mFloatProperty != null) {
                mFloatProperty.setValue(target, mFloatAnimatedValue);
                return;
            }
            if (mProperty != null) {
                mProperty.set(target, mFloatAnimatedValue);
                return;
            }
        }

  Property还存在一个特殊子类ReflectiveProperty

    public static <T, V> Property<T, V> of(Class<T> hostType, Class<V> valueType, String name) {
        return new ReflectiveProperty<T, V>(hostType, valueType, name);
    }

    public void set(T object, V value) {
        if (mSetter != null) {
            try {
                mSetter.invoke(object, value);
            } catch (IllegalAccessException e) {
                throw new AssertionError();
            } catch (InvocationTargetException e) {
                throw new RuntimeException(e.getCause());
            }
        } else if (mField != null) {
            try {
                mField.set(object, value);
            } catch (IllegalAccessException e) {
                throw new AssertionError();
            }
        } else {
            throw new UnsupportedOperationException("Property " + getName() +" is read-only");
        }
    }

    @Override
    public V get(T object) {
        if (mGetter != null) {
            try {
                return (V) mGetter.invoke(object, (Object[])null);
            } catch (IllegalAccessException e) {
                throw new AssertionError();
            } catch (InvocationTargetException e) {
                throw new RuntimeException(e.getCause());
            }
        } else if (mField != null) {
            try {
                return (V) mField.get(object);
            } catch (IllegalAccessException e) {
                throw new AssertionError();
            }
        }
        // Should not get here: there should always be a non-null getter or field
        throw new AssertionError();
    }

  该类本质上和propertyName没有任何区别,只是将PropertyValuesHolder中的反射移植到了这里,多了一个is开头的get方法支持,多了直接操作属性,暂时看不出这种方式有什么其它优势。

6. TypeConverter

  属性动画还提供了数据转换器,先看一个例子

        TypeEvaluator<PointF> typeEvaluator = new TypeEvaluator<PointF>() {
            @Override
            public PointF evaluate(float fraction, PointF startValue, PointF endValue) {
                float x = startValue.x + (endValue.x - startValue.x) * fraction;
                float y = startValue.y + (endValue.y - startValue.y) * fraction;
                return new PointF(x, y);
            }
        };
        Property<TypeCovertData, float[]> property1 = new Property<TypeCovertData, float[]>(float[].class, "Points") {
            @Override
            public float[] get(TypeCovertData object) {
                return object.points;
            }

            @Override
            public void set(TypeCovertData object, float[] value) {
                object.setPoints(value);
            }
        };
        TypeConverter<PointF, float[]> tc = new TypeConverter<PointF, float[]>(PointF.class, float[].class) {
            @Override
            public float[] convert(PointF value) {
                return new float[]{value.x, value.y};
            }
        };
        typeCovert = ObjectAnimator.ofObject(new TypeCovertData(), property1, tc, typeEvaluator, new PointF(0, 0), new PointF(200, 300));

	static class TypeCovertData {
        private float[] points = new float[2];

        public float[] getPoints() {
            return points;
        }

        public void setPoints(float[] points) {
            this.points = points;
            log.dMKV("setPoints", "points", Arrays.toString(points));
        }
    }

  可以将数据转换成另一种格式,也可以再计算,比如获取坐标乘积,还比如,如果你想修改view背景色和背景色的透明度,但是其背景色和透明度变化规律不一样,需要分开计算,可以在TypeEvaluator里面分开计算数值,可以在TypeConverter里面合并成一个颜色,最后直接赋值给View背景色就行了。

数据转换器的作用位置在PropertyValuesHolder,计算值之后就转换好了

    void calculateValue(float fraction) {
        Object value = mKeyframes.getValue(fraction);
        mAnimatedValue = mConverter == null ? value : mConverter.convert(value);
    }

上面几个参数调用顺序如下:

ObjectAnimatorPropertyValuesHolderKeyframesTypeEvaluatorTypeConverterPropertyanimateValuegetValueevaluate返回估值返回估值convert返回转换值返回转换值setAnimatedValuesetObjectAnimatorPropertyValuesHolderKeyframesTypeEvaluatorTypeConverterProperty

7. 总结

  • 属性动画执行的线程必须有handler;
  • 属性动画可以在子线程执行,但在子线程执行无法直接更新UI;
  • 属性动画估值器的fraction在每一段(比如:ofFloat传入多个值,有values.length - 1段)都是0~1;
  • 可以通过xml给keyframe赋予插值器,让不同的阶段执行不同的插值器,代码也可以实现这个效果,只要自定义插值器,并在不同的阶段使用不同的计算方式就行了,阶段通过fraction判断即可;
  • 属性动画start所在的线程就是之后执行的线程,也是编舞者调度的线程;
  • 利用propertyName对对象部分值直接更新的方式存在限制,propertyName首字母大写以后,拼接set和get后的字符串要在对象中找得到对应的方法;
  • 不是所有的动画的估值器都是有效的,ofFloat如果添加了Path,估值器就无效;
  • 使用Property相对于propertyName有一定性能提升,可以避免反射,也不限制方法名;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值