NineOldAnimations 设计解析

NineOldAnimations 源码解析

1. 功能介绍

NineOldAndroids是一款支持在低版本( api 11以下 )使用Android属性动画以及3D旋转动画的框架,它提供了一系列如ViewAnimator,ObjectAnimator,ViewPropertyAnimator等API来完成这些动画,解决了Android动画框架在低版本的兼容性问题。在api 11 ( Honeycomb (Android 3.0) )后Android推出了属性动画、X轴翻转等动画效果,但是这些效果却不能运行在api 11以下,NineOldAndroids的出现使得这些动画效果能够兼容低版本系统,保证动画在各个系统版本能够完美运行。

2. 总体设计

以上是NineoldAnimations的整体设计图,Animator通过PropertyValuesHolder来更新对象的目标属性。如果用户没有设置目标属性的Property对象,那么会通过反射的形式调用目标属性的setter方法来更新属性值;否则则通过Property的set方法来设置属性值。这个属性值则通过KeyFrameSet的计算得到,而KeyFrameSet又是通过时间插值器和类型估值器来计算。在动画执行过程中不断地计算当前时刻目标属性的值,然后更新属性值来达到动画效果。

2.1 类详细介绍

在进行下一步的分析之前,我们先来了解一下NineOldAndroids中一些核心的类以及它们的作用。

* ValueAnimator : 该类是Animator的子类,实现了动画的整个处理逻辑,也是NineOldAndroids最为核心的类;

* ObjectAnimator : 对象属性动画的操作类,继承自ValueAnimator,通过该类使用动画的形式操作对象的属性;

* TimeInterpolator : 时间插值器,它的作用是根据时间流逝的百分比来计算出当前属性值改变的百分比,系统预置的有LinearInterpolator(线性插值器:匀速动画)、AccelerateDecelerateInterpolator(加速减速插值器:动画两头慢中间快)和DecelerateInterpolator(减速插值器:动画越来越慢)等;

* TypeEvaluator : TypeEvaluator的中文翻译为类型估值算法,它的作用是根据当前属性改变的百分比来计算改变后的属性值,系统预置的有IntEvaluator(针对整型属性)、FloatEvaluator(针对浮点型属性)和ArgbEvaluator(针对Color属性);

* Property : 属性对象,主要是定义了属性的set和get方法;

* PropertyValuesHolder : PropertyValuesHolder是持有目标属性Property、setter和getter方法、以及关键帧集合的类。

* KeyframeSet : 存储一个动画的关键帧集合;

* AnimationProxy : 在API 11以下使用View的属性动画的辅助类。

2.2 基本使用

示例1:

改变一个对象(myObject)的 translationY属性,让其沿着Y轴向上平移一段距离:它的高度,该动画在默认时间内完成,动画的完成时间是可以定义的,想要更灵活的效果我们还可以定义插值器和估值算法,但是一般来说我们不需要自定义,系统已经预置了一些,能够满足常用的动画。

ObjectAnimator.ofFloat(myObject, "translationY", -myObject.getHeight()).start();  

示例2:

改变一个对象的背景色属性,典型的情形是改变View的背景色,下面的动画可以让背景色在3秒内实现从0xFFFF8080到0xFF8080FF的渐变,并且动画会无限循环而且会有反转的效果。

ValueAnimator colorAnim = ObjectAnimator.ofInt(this, "backgroundColor", /*Red*/0xFFFF8080, /*Blue*/0xFF8080FF);
colorAnim.setDuration(3000);
colorAnim.setEvaluator(new ArgbEvaluator());
colorAnim.setRepeatCount(ValueAnimator.INFINITE);
colorAnim.setRepeatMode(ValueAnimator.REVERSE);
colorAnim.start();  

示例3:

动画集合,5秒内对View的旋转、平移、缩放和透明度都进行了改变 。

AnimatorSet set = new AnimatorSet();
set.playTogether(
    ObjectAnimator.ofFloat(myView, "rotationX", 0, 360),
    ObjectAnimator.ofFloat(myView, "rotationY", 0, 180),
    ObjectAnimator.ofFloat(myView, "rotation", 0, -90),
    ObjectAnimator.ofFloat(myView, "translationX", 0, 90),
    ObjectAnimator.ofFloat(myView, "translationY", 0, 90),
    ObjectAnimator.ofFloat(myView, "scaleX", 1, 1.5f),
    ObjectAnimator.ofFloat(myView, "scaleY", 1, 0.5f),
    ObjectAnimator.ofFloat(myView, "alpha", 1, 0.25f, 1)
);
set.setDuration(5 * 1000).start();  

示例4:

下面是个简单的调用方式,其animate方法是nineoldandroids特有的,动画持续时间为2秒,在Y轴上旋转720度,并且平移到(100, 100)的位置。

Button myButton = (Button)findViewById(R.id.myButton);
animate(myButton).setDuration(2000).rotationYBy(720).x(100).y(100);    

3. 流程图

3.1 ValueAnimator流程图

3.2 View的ObjectAnimator流程图

4. 详细设计

4.1 核心原理分析

4.1.1 ValueAnimator.java

ObjectAnimator是ValueAnimator的子类,ObjectAnimator负责的是属性动画,但是真正对于动画进行操作的类实际上是ValueAnimator,它定义了nineoldandroids的执行逻辑,是nineoldandroids的核心之一。

启动动画时,会将Animator自身添加到等待执行的动画队列中(sPendingAnimations),然后通过AnimationHandler发送一个ANIMATION_START的消息来通知nineoldandroids启动动画。

    private void start(boolean playBackwards) {
        if (Looper.myLooper() == null) {
            throw new AndroidRuntimeException("Animators may only be run on Looper threads");
        }

        // ......
        sPendingAnimations.get().add(this);
        // ...... 

        AnimationHandler animationHandler = sAnimationHandler.get();
        if (animationHandler == null) {
            animationHandler = new AnimationHandler();
            sAnimationHandler.set(animationHandler);
        }
        animationHandler.sendEmptyMessage(ANIMATION_START);
    }

AnimationHandler是ValueAnimator的内部类,它的职责是处理动画的持续执行。

    private static class AnimationHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            boolean callAgain = true;
            ArrayList<ValueAnimator> animations = sAnimations.get();
            ArrayList<ValueAnimator> delayedAnims = sDelayedAnims.get();
            switch (msg.what) {
                case ANIMATION_START:
                    ArrayList<ValueAnimator> pendingAnimations = sPendingAnimations.get();
                    // 启动在等待队列中的动画,然后进入ANIMATION_FRAME进行循环执行
                case ANIMATION_FRAME:
                    // 根据启动时间来判断是否启动等待中的动画,如果是已经启动的动画,则根据时间点来计算此刻该动画应该得到的属性值
                    //,并且跟新属性,如果此时动画没有执行完成,则又会通过发布一个ANIMATION_FRAME消息,使得在此进入到这个代码段,
                    // 这样就相当于循环执行动画,直到动画完成
            }
        }
    }

在前文中已经说过,如果是属性动画,且系统低于API 11时会通过矩阵变换的形式来处理属性动画效果;否则会通过set + 属性名的形式调用目标对象的setter方法来达到更新属性值。这个版本兼容问题会在初始化动画时进行处理,处理这个问题的函数在ObjectAnimator的initAnimation中。

    @Override
    void initAnimation() {
        if (!mInitialized) {
            //   注意这里,很重要
            if ((mProperty == null) && AnimatorProxy.NEEDS_PROXY
                && (mTarget instanceof View) && PROXY_PROPERTIES.containsKey(mPropertyName)) {
                setProperty(PROXY_PROPERTIES.get(mPropertyName));
            }
            int numValues = mValues.length;
            for (int i = 0; i < numValues; ++i) {
                mValues[i].setupSetterAndGetter(mTarget);
            }
            super.initAnimation();
        }
    }

这里进行包装的属性动画主要是View的alpha、缩放、平移、旋转等几个主要动画。如果是其他类型对象,那么会在会通过该对象中的目标属性的setter和getter方法来访问,例如对象类型是一个自定义的MyCustomButton,它有一个属性为myText,用户通过属性动画修改它时就需要定义它的setter和getter方法,比如setMyText和getMyText,这样nineoldanimations就会在动画执行时通过反射来调用setter方法进行更新对象属性。

4.1.2 ObjectAnimator.java

ObjectAnimator是属性动画的入口类,用户通过上述一系列的静态工厂函数来构建ObjectAnimator,设置完基本属性之后,用户需要设置动画执行时间、重复模式等其他属性(可选)。

上述几个静态函数中,参数1都是需要动画的目标对象,参数二要操作的属性名称,参数三是目标属性的取值范围,如果传递一个值那么该值就是目标属性的最终值;如果传递的是两个值,那么第一个值为起始值,第二个为最终值。

(1)主要函数

    public static ObjectAnimator ofInt(Object target, String propertyName, int... values) ;

    public static <T> ObjectAnimator ofInt(T target, Property<T, Integer> property, int... values) ;

    public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) ;

    public static <T> ObjectAnimator ofFloat(T target, Property<T, Float> property,
            float... values) ;

    public static ObjectAnimator ofObject(Object target, String propertyName,
            TypeEvaluator evaluator, Object... values) ;
4.1.3 KeyFrameSet.java

关键帧集合类在动画运行时会根据流逝的时间因子 ( fraction )和类型估值器来计算当前时间目标属性的最新值,然后将这个值通过反射或者Property的set方法设置给目标对象。下面是获取当前属性值的计算函数。


    public Object getValue(float fraction) {

        // Special-case optimization for the common case of only two keyframes
        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 /*Time*/Interpolator 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 /*Time*/Interpolator 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 /*Time*/Interpolator interpolator = nextKeyframe.getInterpolator();
                if (interpolator != null) {
                    fraction = interpolator.getInterpolation(fraction);
                }
                final float prevFraction = prevKeyframe.getFraction();
                float intervalFraction = (fraction - prevFraction) /
                    (nextKeyframe.getFraction() - prevFraction);
                return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(),
                        nextKeyframe.getValue());
            }
            prevKeyframe = nextKeyframe;
        }
        // shouldn‘t reach here
        return mLastKeyframe.getValue();
    }
4.1.4 PropertyValuesHolder.java

PropertyValuesHolder是持有目标属性Property、setter和getter方法、以及关键帧集合的类。如果没有属性的mProperty不为空,比如用户使用了内置的Property或者自定义实现了Property,并且设置给了动画类,那么在更新动画时则会使用Property对象的set方法来更新属性值。否则在初始化时PropertyValuesHolder会拼装属性的setter和getter函数 ( 注意这里的setter和上面说的Property对象的set方法是两码事 ) ,然后检测目标对象中是否含有这些方法,如果含有则获取setter和getter。

    void setupSetter(Class targetClass) {
        mSetter = setupSetterOrGetter(targetClass, sSetterPropertyMap, "set", mValueType);
    }

    // 通过KeyFrameSet计算属性值
    void calculateValue(float fraction) {
        mAnimatedValue = mKeyframeSet.getValue(fraction);
    }

    // ...... 

    void setAnimatedValue(Object target) {
        if (mProperty != null) {
            // 获取新值,并且通过Property的set方法更新值
            mProperty.set(target, getAnimatedValue());
        }
        // 如果没有设置Property,那么通过属性的setter来反射调用更新
        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());
            }
        }
    }

在动画执行时通过关键帧中的插值器和类型估值器来计算最新的属性值( 见calculatVealue函数 ),然后通过反射调用setter方法或者Property对象的set方法设置给目标对象来更新目标属性,循环执行这个过程就实现了动画效果。

5. 杂谈

NineoldAnimations总得来说还是比较不错的,在开发过程中起到了很大的作用。但是从设计角度上看,它可能并不是特别的好,例如代码中到处充斥着没有进行类型检查的警告,也可能是这个库本身存在太多的可变性,导致难以周全。

该项目目前已经标识为DEPRECATED,作者的原意应该是不再更新该库,因为它已经比较稳定,希望朋友们不要误以为是不再建议使用该库的意思。

时间: 2024-10-09 10:05:26

NineOldAnimations 设计解析的相关文章

Kafka设计解析(三)- Kafka High Availability (下)

本文转发自Jason’s Blog,原文链接 http://www.jasongj.com/2015/06/08/KafkaColumn3 摘要 本文在上篇文章基础上,更加深入讲解了Kafka的HA机制,主要阐述了HA相关各种场景,如Broker failover,Controller failover,Topic创建/删除,Broker启动,Follower从Leader fetch数据等详细处理过程.同时介绍了Kafka提供的与Replication相关的工具,如重新分配Partition等

响应式页面导航设计解析

有人说,2013将是响应式网页设计之年.自用户体验设计师Ethan Marcotte在2010年提出Responsive Web Design(RWD)的名词,即响应式网页设计,这个概念从Responsive Architecture延伸到web设计领域,让所有的交互设计.视觉.前端开发都开始投入到这个趋势,或者说新的设计解决方案中. 当自己和身边的朋友越来越多的用上了iPhone.iPad.android手机或平板,当越来越多的人都在谈论这个老意识新概念的话题,当我们一直秉承的用户体验第一与网

Kafka设计解析(五)- Kafka性能测试方法及Benchmark报告

本文转发自Jason’s Blog,原文链接 http://www.jasongj.com/2015/12/31/KafkaColumn5_kafka_benchmark 摘要 本文主要介绍了如何利用Kafka自带的性能测试脚本及Kafka Manager测试Kafka的性能,以及如何使用Kafka Manager监控Kafka的工作状态,最后给出了Kafka的性能测试报告. 性能测试及集群监控工具 Kafka提供了非常多有用的工具,如Kafka设计解析(三)- Kafka High Avail

Kafka设计解析(二)- Kafka High Availability (上)

本文转发自Jason’s Blog,原文链接 http://www.jasongj.com/2015/04/24/KafkaColumn2 摘要 Kafka在0.8以前的版本中,并不提供High Availablity机制,一旦一个或多个Broker宕机,则宕机期间其上所有Partition都无法继续提供服务.若该Broker永远不能再恢 复,亦或磁盘故障,则其上数据将丢失.而Kafka的设计目标之一即是提供数据持久化,同时对于分布式系统来说,尤其当集群规模上升到一定程度后,一台或 者多台机器宕

Kafka设计解析:Kafka High Availability

Kafka在0.8以前的版本中,并不提供High Availablity机制,一旦一个或多个Broker宕机,则宕机期间其上所有Partition都无法继续提供服务.若该Broker永远不能再恢复,亦或磁盘故障,则其上数据将丢失.而Kafka的设计目标之一即是提供数据持久化,同时对于分布式系统来说,尤其当集群规模上升到一定程度后,一台或者多台机器宕机的可能性大大提高,对Failover要求非常高.因此,Kafka从0.8开始提供High Availability机制.本文从Data Replic

[Big Data - Kafka] Kafka设计解析(二):Kafka High Availability (上)

Kafka在0.8以前的版本中,并不提供High Availablity机制,一旦一个或多个Broker宕机,则宕机期间其上所有Partition都无法继续提供服务.若该Broker永远不能再恢复,亦或磁盘故障,则其上数据将丢失.而Kafka的设计目标之一即是提供数据持久化,同时对于分布式系统来说,尤其当集群规模上升到一定程度后,一台或者多台机器宕机的可能性大大提高,对Failover要求非常高.因此,Kafka从0.8开始提供High Availability机制.本文从Data Replic

转:Kafka设计解析(二):Kafka High Availability (上)

Kafka在0.8以前的版本中,并不提供High Availablity机制,一旦一个或多个Broker宕机,则宕机期间其上所有Partition都无法继续提供服务.若该Broker永远不能再恢 复,亦或磁盘故障,则其上数据将丢失.而Kafka的设计目标之一即是提供数据持久化,同时对于分布式系统来说,尤其当集群规模上升到一定程度后,一台或 者多台机器宕机的可能性大大提高,对Failover要求非常高.因此,Kafka从0.8开始提供High Availability机制.本文从Data Repl

响应式页面导航的设计解析

有人说,2013将是响应式网页设计之年.自用户体验设计师Ethan Marcotte在2010年提出Responsive Web Design(RWD)的名词,即响应式网页设计,这个概念从Responsive Architecture延伸到web设计领域,让所有的交互设计.视觉.前端开发都开始投入到这个趋势,或者说新的设计解决方案中. 当自己和身边的朋友越来越多的用上了iPhone.iPad.android手机或平板,当越来越多的人都在谈论这个老意识新概念的话题,当我们一直秉承的用户体验第一与网

Kafka设计解析(一)- Kafka背景及架构介绍

本文转发自Jason’s Blog,原文链接 http://www.jasongj.com/2015/03/10/KafkaColumn1 摘要 Kafka是由LinkedIn开发并开源的分布式消息系统,因其分布式及高吞吐率 而被广泛使用,现已与Cloudera Hadoop,Apache Storm,Apache Spark集成.本文介绍了Kafka的创建背景,设计目标,使用消息系统的优势以及目前流行的消息系统对比.并介绍了Kafka的架 构,Producer消息路由,Consumer Gro