美文网首页
Android动画四:属性动画的工作原理

Android动画四:属性动画的工作原理

作者: IT前沿技术分享 | 来源:发表于2019-03-13 11:47 被阅读0次

    前言

      本文为Android动画系列的最后一篇文章,通过对源码的分析,能够让大家更深刻地理解属性动画的工作原理,这有助于我们更好地使用属性动画。但是,由于动画的底层实现已经深入到jni层,并且涉及到显示子系统,因此,深入地分析动画的底层实现不仅比较困难而且意义不大,因此,本文的分析到jni层为止。

    Android动画系列:

    属性动画的原理

      属性动画要求动画作用的对象提供该属性的set方法,属性动画根据你传递的该熟悉的初始值和最终值,以动画的效果多次去调用set方法,每次传递给set方法的值都不一样,确切来说是随着时间的推移,所传递的值越来越接近最终值。如果动画的时候没有传递初始值,那么还要提供get方法,因为系统要去拿属性的初始值。对于属性动画来说,其动画过程中所做的就是这么多,下面看源码分析。

    源码分析

      首先我们要找一个入口,就从ObjectAnimator.ofInt(mButton, "width", 500).setDuration(5000).start()开始吧,其他动画都是类似的。

      看ObjectAnimator的start方法

        @Override
        public void start() {
            // See if any of the current active/pending animators need to be canceled
            AnimationHandler handler = sAnimationHandler.get();
            if (handler != null) {
                int numAnims = handler.mAnimations.size();
                for (int i = numAnims - 1; i >= 0; i--) {
                    if (handler.mAnimations.get(i) instanceof ObjectAnimator) {
                        ObjectAnimator anim = (ObjectAnimator) handler.mAnimations.get(i);
                        if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) {
                            anim.cancel();
                        }
                    }
                }
                numAnims = handler.mPendingAnimations.size();
                for (int i = numAnims - 1; i >= 0; i--) {
                    if (handler.mPendingAnimations.get(i) instanceof ObjectAnimator) {
                        ObjectAnimator anim = (ObjectAnimator) handler.mPendingAnimations.get(i);
                        if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) {
                            anim.cancel();
                        }
                    }
                }
                numAnims = handler.mDelayedAnims.size();
                for (int i = numAnims - 1; i >= 0; i--) {
                    if (handler.mDelayedAnims.get(i) instanceof ObjectAnimator) {
                        ObjectAnimator anim = (ObjectAnimator) handler.mDelayedAnims.get(i);
                        if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) {
                            anim.cancel();
                        }
                    }
                }
            }
            if (DBG) {
                Log.d("ObjectAnimator", "Anim target, duration: " + mTarget + ", " + getDuration());
                for (int i = 0; i < mValues.length; ++i) {
                    PropertyValuesHolder pvh = mValues[i];
                    ArrayList<Keyframe> keyframes = pvh.mKeyframeSet.mKeyframes;
                    Log.d("ObjectAnimator", "   Values[" + i + "]: " +
                        pvh.getPropertyName() + ", " + keyframes.get(0).getValue() + ", " +
                        keyframes.get(pvh.mKeyframeSet.mNumKeyframes - 1).getValue());
                }
            }
            super.start();
        }
    

      说明:上面的代码别看那么长,其实做的事情很简单,首先会判断一下,如果当前动画、等待的动画(Pending)和延迟的动画(Delay)中有和当前动画相同的动画,那么就把相同的动画给取消掉,接下来那一段是log,再接着就调用了父类的super.start()方法,因为ObjectAnimator继承了ValueAnimator,所以接下来我们看一下ValueAnimator的Start方法

        private void start(boolean playBackwards) {
            if (Looper.myLooper() == null) {
                throw new AndroidRuntimeException("Animators may only be run on Looper threads");
            }
            mPlayingBackwards = playBackwards;
            mCurrentIteration = 0;
            mPlayingState = STOPPED;
            mStarted = true;
            mStartedDelay = false;
            mPaused = false;
            AnimationHandler animationHandler = getOrCreateAnimationHandler();
            animationHandler.mPendingAnimations.add(this);
            if (mStartDelay == 0) {
                // This sets the initial value of the animation, prior to actually starting it running
                setCurrentPlayTime(0);
                mPlayingState = STOPPED;
                mRunning = true;
                notifyStartListeners();
            }
            animationHandler.start();
        }
    

      说明:上述代码最终会调用AnimationHandler的start方法,这个AnimationHandler并不是Handler,它是个Runnable。看下它的代码,通过代码我们发现,很快就调到了jni层,不过jni层最终还是要调回来的。它的run方法会被调用,这个Runnable涉及到和底层的交互,我们就忽略这部分,直接看重点:ValueAnimator中的doAnimationFrame方法

        final boolean doAnimationFrame(long frameTime) {
            if (mPlayingState == STOPPED) {
                mPlayingState = RUNNING;
                if (mSeekTime < 0) {
                    mStartTime = frameTime;
                } else {
                    mStartTime = frameTime - mSeekTime;
                    // Now that we're playing, reset the seek time
                    mSeekTime = -1;
                }
            }
            if (mPaused) {
                if (mPauseTime < 0) {
                    mPauseTime = frameTime;
                }
                return false;
            } else if (mResumed) {
                mResumed = false;
                if (mPauseTime > 0) {
                    // Offset by the duration that the animation was paused
                    mStartTime += (frameTime - mPauseTime);
                }
            }
            // 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);
            return animationFrame(currentTime);
        }
    

    注意到上述代码末尾调用了animationFrame方法,而animationFrame内部调用了animateValue,下面看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 (mUpdateListeners != null) {
                int numListeners = mUpdateListeners.size();
                for (int i = 0; i < numListeners; ++i) {
                    mUpdateListeners.get(i).onAnimationUpdate(this);
                }
            }
        }
    

      上述代码中的calculateValue方法就是计算每帧动画所对应的属性的值,下面着重看一下到底是在哪里调用属性的get和set方法的,毕竟这个才是我们最关心的。
    get方法:在初始化的时候,如果属性的初始值没有提供,则get方法将会被调用。

        private void setupValue(Object target, Keyframe kf) {
            if (mProperty != null) {
                kf.setValue(mProperty.get(target));
            }
            try {
                if (mGetter == null) {
                    Class targetClass = target.getClass();
                    setupGetter(targetClass);
                    if (mGetter == null) {
                        // Already logged the error - just return to avoid NPE
                        return;
                    }
                }
                kf.setValue(mGetter.invoke(target));
            } catch (InvocationTargetException e) {
                Log.e("PropertyValuesHolder", e.toString());
            } catch (IllegalAccessException e) {
                Log.e("PropertyValuesHolder", e.toString());
            }
        }
    

      set方法:当动画的下一帧到来的时候,PropertyValuesHolder中的setAnimatedValue方法会将新的属性值设置给对象,调用其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());
                }
            }
        }
    

    总结

      我觉得这篇源码分析写的逻辑有点混乱,希望不要给大家带来误导。从源码上来说,属性动画的源码逻辑层次有点跳跃,不过没关系,大家只要了解属性动画的工作原理就好,源码的作用在于让我们发现其工作原理的确如此。到此为止,Android动画系列已经全部完成,十分感谢大家阅读,希望能给大家带来一点帮助!

    原文:https://blog.csdn.net/singwhatiwanna/article/details/17853275

    相关文章

      网友评论

          本文标题:Android动画四:属性动画的工作原理

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