Android动画完全解析--属性动画

一、概述

上篇博客介绍了View动画的简单使用和基本工作原理原理,这篇来学习下属性动画。和View动画不同的是,属性动画不再简单的使用平移、旋转、缩放、透明度这4种变换,代替它们的是ValueAnimator、ObjectAnimator等概念。

二、运行截图

三、TimeInterpolator和TypeEvaluator

在真正学习属性动画之前,我们需要理解TimeInterpolator和TypeEvaluator这两个概念。

1.TimeInterpolator

中文翻译为时间插值器。它的作用是根据当前时间流逝的百分比来计算当前某个属性改变的百分比。查看结构,发现只有一个方法。


    float getInterpolation(float input);

当然,它是一个接口,我们使用更多的是它的实现类。常用的实现类有LinearInterpolator、AccelerateInterpolator、DecelerateInterpolator等等。下面就简单分析下LinearInterpolator、AccelerateInterpolator两个插值器

查看LinearInterpolator源码


    public class LinearInterpolator implements Interpolator {

    public LinearInterpolator() {
    }

    public LinearInterpolator(Context context, AttributeSet attrs) {
    }

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

源码很简单, 核心方法是getInterpolation(),我们可以将其理解成数学中的函数。


    y=x ;

查看AccelerateInterpolator源码



    /**
     * An interpolator where the rate of change starts out slowly and
     * and then accelerates.
     *
     */
    public class AccelerateInterpolator implements Interpolator {
        private final float mFactor;
        private final double mDoubleFactor;

        public AccelerateInterpolator() {
            mFactor = 1.0f;
            mDoubleFactor = 2.0;
        }

        /**
         * Constructor
         *
         * @param factor Degree to which the animation should be eased. Seting
         *        factor to 1.0f produces a y=x^2 parabola. Increasing factor above
         *        1.0f  exaggerates the ease-in effect (i.e., it starts even
         *        slower and ends evens faster)
         */
        public AccelerateInterpolator(float factor) {
            mFactor = factor;
            mDoubleFactor = 2 * mFactor;
        }

        public AccelerateInterpolator(Context context, AttributeSet attrs) {
            TypedArray a =
                context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.AccelerateInterpolator);

            mFactor = a.getFloat(com.android.internal.R.styleable.AccelerateInterpolator_factor, 1.0f);
            mDoubleFactor = 2 * mFactor;

            a.recycle();
        }

        public float getInterpolation(float input) {
            if (mFactor == 1.0f) {
                return input * input;
            } else {
                return (float)Math.pow(input, mDoubleFactor);
            }
        }
    }

同样,我们查看getInterpolation()方法,同样,我们可以将其视为数学中的函数y等于x的n次方法。

2.TypeEvaluator

TypeEvaluator中文翻译为类型估算器。和TimeInterpolator类似,它也是一个接口,而我们使用的也是它的实现类。它一共有4个实现类,ArgbEvaluator、FloatEvaluator、IntEvaluator、RectEvaluator。下面就其中一种分析–RectEvaluator。查看源码:


    public class IntEvaluator implements TypeEvaluator<Integer> {

    public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
        int startInt = startValue;
        return (int)(startInt + fraction * (endValue - startInt));
    }
}

由其源码可以知道它主要的作用是根据当前属性改变的百分比来获取改变后的属性值。

TimeInterpolator和TypeEvaluator的配合使用可以帮助我们实现好多复杂的效果

四、属性动画入门

简单做一个属性动画的入门例子,这里我们采用java代码来实现,当然xml也可以实现,比较简单,就不演示了。


    //创建集合对象
    AnimatorSet animatorSet = new AnimatorSet() ;

    animatorSet.playTogether(ObjectAnimator.ofFloat(btn_attr, "translationX",0, 50)
            ,ObjectAnimator.ofFloat(btn_attr, "translationY",0, 50)
            );
    animatorSet.setDuration(3000);
    animatorSet.start();

使用animatorset来实现平移的效果。注意,这里的平移可是实现了真正位置上的平移,不像View动画。

五、属性动画的工作原理

首先从Animator的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();
    }

首先,第一行代码,我们需要知道sAnimationHandler是什么,由于ObjectAnimator是ValueAnimator的子类,从ValueAnimator的源码可以知道它是一个本地线程,主要的作用是不同的线程有不同的AnimatorHandler。如果还是不理解ThreadLocal的概念,请看ThreadLocal的源码分析


    protected static ThreadLocal<AnimationHandler> sAnimationHandler =
            new ThreadLocal<AnimationHandler>();

通过sAnimationHandler获取AnimationHandler对象,我们再来看下AnimationHandler又是什么


    protected static class AnimationHandler implements Runnable {
        // The per-thread list of all active animations
        /** @hide */
        protected final ArrayList<ValueAnimator> mAnimations = new ArrayList<ValueAnimator>();

        // Used in doAnimationFrame() to avoid concurrent modifications of mAnimations
        private final ArrayList<ValueAnimator> mTmpAnimations = new ArrayList<ValueAnimator>();

        // The per-thread set of animations to be started on the next animation frame
        /** @hide */
        protected final ArrayList<ValueAnimator> mPendingAnimations = new ArrayList<ValueAnimator>();

        /**
         * Internal per-thread collections used to avoid set collisions as animations start and end
         * while being processed.
         * @hide
         */
        protected final ArrayList<ValueAnimator> mDelayedAnims = new ArrayList<ValueAnimator>();
        private final ArrayList<ValueAnimator> mEndingAnims = new ArrayList<ValueAnimator>();
        private final ArrayList<ValueAnimator> mReadyAnims = new ArrayList<ValueAnimator>();

        private final Choreographer mChoreographer;
        private boolean mAnimationScheduled;

        private AnimationHandler() {
            mChoreographer = Choreographer.getInstance();
        }

        /**
         * Start animating on the next frame.
         */
        public void start() {
            scheduleAnimation();
        }

        private void doAnimationFrame(long frameTime) {
            // mPendingAnimations holds any animations that have requested to be started
            // We‘re going to clear mPendingAnimations, but starting animation may
            // cause more to be added to the pending list (for example, if one animation
            // starting triggers another starting). So we loop until mPendingAnimations
            // is empty.
            while (mPendingAnimations.size() > 0) {
                ArrayList<ValueAnimator> pendingCopy =
                        (ArrayList<ValueAnimator>) mPendingAnimations.clone();
                mPendingAnimations.clear();
                int count = pendingCopy.size();
                for (int i = 0; i < count; ++i) {
                    ValueAnimator anim = pendingCopy.get(i);
                    // If the animation has a startDelay, place it on the delayed list
                    if (anim.mStartDelay == 0) {
                        anim.startAnimation(this);
                    } else {
                        mDelayedAnims.add(anim);
                    }
                }
            }
            // Next, process animations currently sitting on the delayed queue, adding
            // them to the active animations if they are ready
            int numDelayedAnims = mDelayedAnims.size();
            for (int i = 0; i < numDelayedAnims; ++i) {
                ValueAnimator anim = mDelayedAnims.get(i);
                if (anim.delayedAnimationFrame(frameTime)) {
                    mReadyAnims.add(anim);
                }
            }
            int numReadyAnims = mReadyAnims.size();
            if (numReadyAnims > 0) {
                for (int i = 0; i < numReadyAnims; ++i) {
                    ValueAnimator anim = mReadyAnims.get(i);
                    anim.startAnimation(this);
                    anim.mRunning = true;
                    mDelayedAnims.remove(anim);
                }
                mReadyAnims.clear();
            }

            // Now process all active animations. The return value from animationFrame()
            // tells the handler whether it should now be ended
            int numAnims = mAnimations.size();
            for (int i = 0; i < numAnims; ++i) {
                mTmpAnimations.add(mAnimations.get(i));
            }
            for (int i = 0; i < numAnims; ++i) {
                ValueAnimator anim = mTmpAnimations.get(i);
                if (mAnimations.contains(anim) && anim.doAnimationFrame(frameTime)) {
                    mEndingAnims.add(anim);
                }
            }
            mTmpAnimations.clear();
            if (mEndingAnims.size() > 0) {
                for (int i = 0; i < mEndingAnims.size(); ++i) {
                    mEndingAnims.get(i).endAnimation(this);
                }
                mEndingAnims.clear();
            }

            // If there are still active or delayed animations, schedule a future call to
            // onAnimate to process the next frame of the animations.
            if (!mAnimations.isEmpty() || !mDelayedAnims.isEmpty()) {
                scheduleAnimation();
            }
        }

        // Called by the Choreographer.
        @Override
        public void run() {
            mAnimationScheduled = false;
            doAnimationFrame(mChoreographer.getFrameTime());
        }

        private void scheduleAnimation() {
            if (!mAnimationScheduled) {
                mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, this, null);
                mAnimationScheduled = true;
            }
        }
    }

从源码我们可以知道它是一个Runnable对象,里面封装了好多集合,用来存放当前动画、演示动画、等待动画等。再回到star()方法,如果handler不为null,则依次获取这些动画将其取消,最后调用父类的start方法。


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

然后又调用start(false)


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

从源码可以知道,它是运行在有Looper的线程当中的,首先,根据通过getOrCreateAnimationHandler()方法获取AnimationHandler对象,有则创建并且将其赋值,否则直接获取。然后通过AnimationHandler将动画加到mPendingAnimations延时动画里去。最后调用AnimationHandler的start()方法,即执行AnimationHandler的run()方法

    private static AnimationHandler getOrCreateAnimationHandler() {
        AnimationHandler handler = sAnimationHandler.get();
        if (handler == null) {
            handler = new AnimationHandler();
            sAnimationHandler.set(handler);
        }
        return handler;
    }

     @Override
    public void run() {
        mAnimationScheduled = false;
        doAnimationFrame(mChoreographer.getFrameTime());
    }

接着,我们看下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;
            }
        }
        // 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()方法,


    boolean animationFrame(long currentTime) {
        boolean done = false;
        switch (mPlayingState) {
        case RUNNING:
        case SEEKED:
            float fraction = mDuration > 0 ? (float)(currentTime - mStartTime) / mDuration : 1f;
            if (fraction >= 1f) {
                if (mCurrentIteration < mRepeatCount || mRepeatCount == INFINITE) {
                    // Time to repeat
                    if (mListeners != null) {
                        int numListeners = mListeners.size();
                        for (int i = 0; i < numListeners; ++i) {
                            mListeners.get(i).onAnimationRepeat(this);
                        }
                    }
                    if (mRepeatMode == REVERSE) {
                        mPlayingBackwards = !mPlayingBackwards;
                    }
                    mCurrentIteration += (int)fraction;
                    fraction = fraction % 1f;
                    mStartTime += mDuration;
                } else {
                    done = true;
                    fraction = Math.min(fraction, 1.0f);
                }
            }
            if (mPlayingBackwards) {
                fraction = 1f - fraction;
            }
            animateValue(fraction);
            break;
        }

        return done;
    }

前面还是一些计算,当动画应该结束时返回true,否则返回false,方法内部计算出时间比率fraction,然后调用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);
            }
        }
    }

首先通过插值器获取一个按照一定规律的fraction,默认为AccelerateDecelerateInterpolator


    private TimeInterpolator mInterpolator = sDefaultInterpolator;

     // The time interpolator to be used if none is set on the animation
    private static final TimeInterpolator sDefaultInterpolator =
            new AccelerateDecelerateInterpolator();

接着,依次遍历mValues,那么mValues又是什么呢?


    /**
     * The property/value sets being animated.
     */
    PropertyValuesHolder[] mValues;

那么mValues是什么时候设置的呢?还记得我们在使用属性动画的时候调用的ofXxx方法吗?


    public static ValueAnimator ofInt(int... values) {
        ValueAnimator anim = new ValueAnimator();
        anim.setIntValues(values);
        return anim;
    }

首先创建了ValueAnimator对象,然后调用setIntValues()方法


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

接着又调用了setIntValues方法


    public void setIntValues(int... values) {
        mValueType = int.class;
        mKeyframeSet = 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);
    }

KeyframeSet的ofInt主要是创建一个IntKeyframe数组,最后将其返回。表示一组Keyframe,而Keyframe从字面可以理解为关键帧。再回到animateValue方法,依次遍历,并调用。该方法主要完成的事为:

  1. A:通过Interpolator计算出动画运行时间的分数。
  2. B:变量ValueAnimator中的mValues[i].calculateValue(fraction)(也就是 PropertyValuesHolder对象数组)计算当前动画的值。
  3. C:调用animation的onAnimationUpdate(…)通知animation更新的消息

     void calculateValue(float fraction) {
        mAnimatedValue = mKeyframeSet.getValue(fraction);
    }

根据evaluator获取每一帧所对应的属性值。这里主要的是这个PropertyValuesHolder对象,我们可以从这个类的注释中可以看出。PropertyValuesHolder内部维持了一个KeyFrameSet和TypeEvaluator


    /**
     * This class holds information about a property and the values that that property
     * should take on during an animation. PropertyValuesHolder objects can be used to create
     * animations with ValueAnimator or ObjectAnimator that operate on several different properties
     * in parallel.
     */
    public class PropertyValuesHolder implements Cloneable {

主要在动画执行过程中hold属性和值的信息。主要是用来被ObjectAnimator和ValueAnimator来操作不同的属性。那么PropertyValuesHolder又是如何调用set和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());
        }
    }

    /**
     * Utility function to get the getter from targetClass
     */
    private void setupGetter(Class targetClass) {
        mGetter = setupSetterOrGetter(targetClass, sGetterPropertyMap, "get", null);
    }

接着,当动画的下一帧来临的时候,又会调用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());
            }
        }
    }

六、小结

这里对属性动画工作流程的几个重要类进行总结

  • ObjectValue:ObjectAnimator我们可以将其理解为外界API接口,ObjectAnimator持有PropertyValuesHolder作为存储关于将要进行动画的具体对象(通常是View类型的控件)的属性和动画期间需要的值。
  • PropertyValueHolder:PropertyValueHolder又使用KeyframeSet来保存animator从开始到结束期间关键帧的值。
  • KeyframeSet:保存每一帧的值。

OK,这篇关于属性动画就介绍到这里了。

源码下载

时间: 2024-11-05 23:16:59

Android动画完全解析--属性动画的相关文章

Android图文具体解释属性动画

Android中的动画分为视图动画(View Animation).属性动画(Property Animation)以及Drawable动画.从Android 3.0(API Level 11)開始.Android開始支持属性动画,本文主要解说怎样使用属性动画. 关于视图动画能够參见博文<Android四大视图动绘图文具体解释>. 概述 视图动画局限比較大.例如以下所述: 视图动画仅仅能使用在View上面. 视图动画并没有真正改变View相应的属性值,这导致了UI效果与实际View状态存在差异

Android图文详解属性动画

Android中的动画分为视图动画(View Animation).属性动画(Property Animation)以及Drawable动画.从Android 3.0(API Level 11)开始,Android开始支持属性动画,本文主要讲解如何使用属性动画.关于视图动画可以参见博文<Android四大视图动画图文详解>. 概述 视图动画局限比较大,如下所述: 视图动画只能使用在View上面. 视图动画并没有真正改变View相应的属性值,这导致了UI效果与实际View状态存在差异,并导致了一

Android攻城狮属性动画赏析

1 import android.support.v7.app.ActionBarActivity; 2 import android.support.v7.app.ActionBar; 3 import android.support.v4.app.Fragment; 4 import android.animation.AnimatorSet; 5 import android.animation.ObjectAnimator; 6 import android.animation.Prop

Android 一般动画animation和属性动画animator

package com.example.animation; import android.app.Activity; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.animation.TranslateAnimation; import android.widget.ImageView;

Android笔记(六十六) android中的动画——XML文件定义属性动画

除了直接在java代码中定义动画之外,还可以使用xml文件定义动画,以便重用. 如果想要使用XML来编写动画,首先要在res目录下面新建一个animator文件夹,所有属性动画的XML文件都应该存放在这个文件夹当中.然后在XML文件中我们一共可以使用如下三种标签: <animator>  对应代码中的ValueAnimator <objectAnimator>  对应代码中的ObjectAnimator <set>  对应代码中的AnimatorSet 使用XML设置动

Android属性动画完全解析(上)

Android属性动画完全解析(上) 转载:http://blog.csdn.net/guolin_blog/article/details/43536355 在手机上去实现一些动画效果算是件比较炫酷的事情,因此Android系统在一开始的时候就给我们提供了两种实现动画效果的方式,逐帧动画(frame-by-frame animation)和补间动画(tweened animation).逐帧动画的工作原理很简单,其实就是将一个完整的动画拆分成一张张单独的图片,然后再将它们连贯起来进行播放,类似

Android 属性动画 源码解析 深入了解其内部实现

转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/42056859,本文出自:[张鸿洋的博客] 1.概述 Android中想做很炫酷的动画效果,相信在很多时候你都可以选择使用属性动画,关于属性动画如何使用,我们已经很详细的写过两篇博客讲解.如果你还不了解,请参考: Android 属性动画(Property Animation) 完全解析 (上) Android 属性动画(Property Animation) 完全解析 (下)

android属性动画

一.概述 android动画总共分为三种逐帧动画.补间动画.属性动画. 逐帧动画:主要就是将几张图片放在一起播放形成动画. 补间动画:补间动画还是比较局限的,能实现view的旋转.横竖拉伸.横竖平移.透明度等简单的变化. 由于android速度发展之快,原有的两种动画已经不能满足我们的需求,所以android在3.0版本推出了一个高大上的动画效果,属性动画. 二.相关API: ValueAnimator:属性动画的执行类,主要负责计算各个帧所对应的属性的值,可以处理动画的更新事件,它可以定义属性

Android开发实战之补间动画和属性动画

说起动画,其实一点也不陌生,在使用一款app的时候为了优化用户体验,多多少少的,都会加入动画. 安卓中的动画,分为两大类:补间动画和属性动画.本篇博文会详细介绍总结这两大动画,希望本篇博文对你的学习和生活有所帮助. **补间动画** 补间动画分为四类:平移动画,旋转动画,缩放动画和渐变动画.这几类动画用法都差不多,只是对象参数不同这里我统一展示出来.以下是效果图: 实现代码很简单: btn1.setOnClickListener(new View.OnClickListener() { @Ove