欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android动画-属性动画底层原理浅析

程序员文章站 2024-03-24 08:23:52
...

之前发表了三篇文章分别描述了三种动画的使用方式,今天我们来了解一下属性动画的底层原理。
之前的三篇文章链接:
Android动画-视图动画View Animation
Android动画-帧动画Drawable Animation
Android动画-属性动画Property Animation

这样,我们先从代码入口开始,假设我们用的是

ObjectAnimator.ofInt(iv, "width", 500).setDuration(5000).start();

我们进去ofint方法看一看

public static ObjectAnimator ofInt(Object target, String propertyName, int... values) {
        ObjectAnimator anim = new ObjectAnimator(target, propertyName);
        anim.setIntValues(values);
        return anim;
    }

ofint方法里创建了一个ObjectAnimator对象,并向他传入了一个values值,接下来我们看看 ObjectAnimator

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

这里调用了两个方法,我们先看第一个setTarget(target);

@Nullable
    public Object getTarget() {
        return mTarget == null ? null : mTarget.get();
    }

    @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);
            // New target should cause re-initialization prior to starting
            mInitialized = false;
        }
    }

这里只是一个动画对象的判断,如果存在旧对象就覆盖,那我们回过头看看另一个方法setPropertyName(propertyName);

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

这里用了HashMap 来保存 propertyName 和 PropertyValuesHolder,这个方法主要还是记录propertyName以及对应的值。那么接下来我们回过头*2看看anim.setIntValues(values);

@Override
    public void setIntValues(int... 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.ofInt(mProperty, values));
            } else {
                setValues(PropertyValuesHolder.ofInt(mPropertyName, values));
            }
        } else {
            super.setIntValues(values);
        }
    }

这里主要是一个values值的判断,继续 super.setIntValues(values);

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

这里也有一个setValues,所以不管父类还是子类,最终还是调用setValues,那我们看看PropertyValuesHolder.ofInt("", values)是什么

public static PropertyValuesHolder ofInt(String propertyName, int... values) {
        return new IntPropertyValuesHolder(propertyName, values);
    }

public IntPropertyValuesHolder(String propertyName, int... values) {
            super(propertyName);
            setIntValues(values);
        }

@Override
        public void setIntValues(int... values) {
            super.setIntValues(values);
            mIntKeyframes = (Keyframes.IntKeyframes) mKeyframes;
        }

这里我就直接连击了,只是一些方法调用,现在看super.setIntValues(values);

public void setIntValues(int... values) {
        mValueType = int.class;
        mKeyframes = KeyframeSet.ofInt(values);
    }

诶,看到了一个熟悉的身影,ofInt,来看看KeyframeSet.ofInt(values);

public static KeyframeSet ofInt(int... values) {
        int numKeyframes = values.length;
        IntKeyframe keyframes[] = new IntKeyframe[Math.max(numKeyframes,2)];
        if (numKeyframes == 1) {
            keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f);
            keyframes[1] = (IntKeyframe) Keyframe.ofInt(1f, values[0]);
        } else {
            keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f, values[0]);
            for (int i = 1; i < numKeyframes; ++i) {
                keyframes[i] =
                        (IntKeyframe) Keyframe.ofInt((float) i / (numKeyframes - 1), values[i]);
            }
        }
        return new IntKeyframeSet(keyframes);
    }

首先,这里new一个IntKeyframe,并且至少长度是2,就是至少要两帧,接下来赋值每一帧,最后合成一个IntKeyframeSet对象返回出去。
ofint的方法到此就先告一段落,简单来讲就是赋值,做一些准备工作,构造出帧,接下来我们回过头*3看看setDuration()

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

啊,这里只是存了一下这个变量,那我们下一个方法,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();
    }

这里就个打印,以及确认一下动画有没有已经取消,接下来看super.start(); (superstar=.=??)

@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;
        // Special case: reversing from seek-to-0 should act as if not seeked at all.
        if (playBackwards && mSeekFraction != -1 && mSeekFraction != 0) {
            if (mRepeatCount == INFINITE) {
                // Calculate the fraction of the current iteration.
                float fraction = (float) (mSeekFraction - Math.floor(mSeekFraction));
                mSeekFraction = 1 - fraction;
            } else {
                mSeekFraction = 1 + mRepeatCount - mSeekFraction;
            }
        }
        mStarted = true;
        mPaused = false;
        mRunning = false;
        mAnimationEndRequested = false;
        // Resets mLastFrameTime when start() is called, so that if the animation was running,
        // calling start() would put the animation in the
        // started-but-not-yet-reached-the-first-frame phase.
        mLastFrameTime = -1;
        mFirstFrameTime = -1;
        mStartTime = -1;
        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(0);是添加一个callback,来看看

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

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

对吧,这里就是在添加回调,那我们回过头去start里面看看startAnimation();

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

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

看看mValues[i].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);
        }
    }

这里的判断给我们设置了一个估值器,因为我们调用的是ofint,所以这里对应sIntEvaluator,到了这一步这个方法就差不多了,那我们再再回到start方法看看我们的最后一个方法(终于最末尾了啊)setCurrentPlayTime(0);

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

这里都是一些时间变量的计算,合成一个currentIterationFraction对象,最后调用animateValue(currentIterationFraction);这里我们要同时看子类跟父类的两个方法

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

        super.animateValue(fraction);
        int numValues = mValues.length;
        for (int i = 0; i < numValues; ++i) {
            mValues[i].setAnimatedValue(target);
        }
    }

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

先讲父类,这里获取了插值器,计算出了value,然后调用onAnimationUpdate去发送通知,好,回到子类,子类除了调用父类animateValue之外,遍历了numValues,去调用里面的setAnimatedValue(target);

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

这里通过反射mSetter.invoke(target, mTmpValueArray);来修改属性的值,到这里为止,就是动画一帧所走过的流程,那么其他帧的动画执行在哪呢,我们回到start方法-addAnimationCallback(0)-getAnimationHandler().addAnimationFrameCallback(this, delay);这里其实是注册了一个AnimationHandler.AnimationFrameCallback

interface AnimationFrameCallback {
        boolean doAnimationFrame(long frameTime);
        void commitAnimationFrame(long frameTime);
    }

我们找到doAnimationFrame的实现,在 ValueAnimator

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

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

关键我们是来看animateBasedOnTime这里,这里忽略了一些变量计算,最后其实还是调用了animateValue(currentIterationFraction);所以说我们每产生一次回调,都会去执行一轮帧播放的流程,那么在那里一直产生回调的呢?在 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 的同学都知道,Andorid 中的重绘就是由Choreographer在 1 秒内产生 60 个 vsync 来通知 view tree 进行 view 的重绘的。而 vsync 产生后会调用它的监听者回调接口 Choreographer.FrameCallback,也就是说,只要向Choreographer注册了这个接口,就会每 1 秒里收到 60 次回调。因此,在这里就实现了不断地调用 doAnimationFrame() 来驱动动画了。
那么整个动画执行的流程大概就是这样走下来的,我们来简单总结一下几个要点:

1.动画是由许多帧组成的
2.PropertyValuesHolder封装了帧的数据,并被动画类加以利用
3.利用Choreographer.FrameCallback的回调方法,来不停的调用帧执行流程
3.在计算变量数据的时候,插值器会去影响到最后输出的数据,从而达到某些效果
4.通过反射的原理,去修改属性的值,连贯起来出现了动画

浅析结束。

上一篇: Static keyword

下一篇: