Android基础之-属性动画 ViewAnimator

553 阅读15分钟

概述

在上一篇中也大致介绍了视图动画和属性动画各自的特点以及区别,本文主要讲属性动画,属性动画的基是:Animator ,它的主要的两个类是:ValueAnimator和ObjectAnimator,今天主要讲 ValueAnimator This class provides a simple timing engine for running animations * which calculate animated values and set them on target objects.这个官方注释 ,再参考名字 ,我就大致就知道这是一个关于数值的动画,没有涉及到具体的控件 ,在一段时间范围内值的动态变化。同样我们也可以设置插值器(Interpolator)Interpolator

ValueAnimator的用法

/*
    * ValueAnimator只负责对指定的数字区间进行动画运算
    * 我们需要对运算过程进行监听,然后自己对控件做动画操作
    * */

    //新建一个实体
    //有三种方法 ofInt  ofFloat  ofObject 分别是int , float  , object值的动画操作
    ValueAnimator valueAnimator1 = ValueAnimator.ofInt(0,200);
    //valueAnimator = ValueAnimator.ofFloat(0,200);
    //valueAnimator = ValueAnimator.ofObject()
    valueAnimator1.setDuration(2000);   //设置时长
    valueAnimator1.setInterpolator(new LinearInterpolator()); //设置插值器 默认的就是 LinearInterpolator 可以设置不同的插值器
    valueAnimator1.start();  //开始

    //一些常用函数
    //valueAnimator.cancel();    取消动画
    //valueAnimator.setEvaluator();  设置取值器
    //valueAnimator.setRepeatCount();    设置重复次数
    //valueAnimator.setRepeatMode();  这是重复模式


    //设置监听器
    valueAnimator1.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            int currentValue = (Integer) animation.getAnimatedValue(); //获取当前的值 因为我们是ofInt ,所以可以强制转为int
            Log.e(TAG,"currentValue : "+currentValue);//可以根据拿到的值进行动画展示 如下:
            anim_text.layout(currentValue,currentValue,currentValue+anim_text.getWidth(),currentValue+anim_text.getHeight()); //移动anim_text
        }
    });

    //设置监听器
    valueAnimator1.addListener(new Animator.AnimatorListener() {
        @Override
        public void onAnimationStart(Animator animation) {
            //动画开始
        }

        @Override
        public void onAnimationEnd(Animator animation) {
            //动画结束
        }

        @Override
        public void onAnimationCancel(Animator animation) {
            //动画取消
        }

        @Override
        public void onAnimationRepeat(Animator animation) {
            //重复动画
        }
    });


    ValueAnimator valueAnimator2 = ValueAnimator.ofFloat(0,200);
    valueAnimator2.setDuration(2000);   //设置时长
    valueAnimator2.setInterpolator(new LinearInterpolator()); //设置插值器 默认的就是 LinearInterpolator 可以设置不同的插值器
    valueAnimator2.start();  //开始

ValueAnimator关于插值器的用法(Interpolator)

valueAnimator2.setInterpolator(new LinearInterpolator());

这里再讲一下什么是插值器。我们知道,我们通过ofInt(0,400)定义了动画的区间值是0到400;然后通过添加AnimatorUpdateListener来监听动画的实时变化。那么问题来了,0-400的值是怎么变化的呢?像我们骑自行车,还有的快有的慢呢;这个值是匀速变化的吗?如果是,那我如果想让它先加速再减速的变化该怎么办? 这就是插值器的作用!插值器就是用来控制动画区间的值被如何计算出来的。比如LinearInterpolator插值器就是匀速返回区间点的值;而DecelerateInterpolator则表示开始变化快,后期变化慢;其它都类似,下面我们就看看ValueAnimator中插值器的应用方法,然后通过自定义一个插值器来看看插值器到底是什么。

我们看一下LinearInterpolator

public class LinearInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {

        public LinearInterpolator() {
        }

        public LinearInterpolator(Context context, AttributeSet attrs) {
        }

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

        /** @hide */
        @Override
        public long createNativeInterpolator() {
            return NativeInterpolatorFactoryHelper.createLinearInterpolator();
        }
    }

里面就有一个getInterpolation(float input)方法 参数input:input参数是一个float类型,它取值范围是0到1,表示当前动画的进度,取0时表示动画刚开始,取1时表示动画结束,取0.5时表示动画中间的位置,其它类推。 其实就是时间进度。。。从开始到结束(0到1) 返回值:表示当前实际想要显示的进度。取值可以超过1也可以小于0,超过1表示已经超过目标值,小于0表示小于开始位置。 我们知道,在我们添加了AnimatorUpdateListener的监听以后,通过在监听函数中调用 animation.getAnimatedValue()就可以得到当前的值; 那当前的值是怎么来的呢?见下面的计算公式:(目前这么理解,后面会细讲真实情况) 当前的值 = 100 + (400 - 100)* 显示进度 其中100和400就是我们设置的ofInt(100,400)中的值,这个公式应该是比较容易理解的,就相当于我们做一个应用题: 小明从100的位置开始出发向400的位置开始跑去,在走到全程距离20%位置时,请问小明在哪个数字点上? 当前的值 = 100 + (400 -100)* 0.2; 很简单的应用题,ofInt()中AnimatorUpdateListener中的当前值就是这么来的。从这里大家可以看到,显示进度就表示的是当前的值的位置。但由于我们可以通过指定getInterpolation()的返回值来指定当前的显示值的进度,所以随着时间的增加,我们可以让值随意在我们想让它在的位置。 再重复一遍,input参数与任何我们设定的值没关系,只与时间有关,随着时间的增长,动画的进度也自然的增加,input参数就代表了当前动画的进度。而返回值则表示动画的当前数值进度

Evaluator

这幅图讲述了从定义动画的数字区间到通过AnimatorUpdateListener中得到当前动画所对应数值的整个过程。下面我们对这四个步骤具体讲解一下: (1)、ofInt(0,400)表示指定动画的数字区间,是从0运动到400; (2)、加速器:上面我们讲了,在动画开始后,通过加速器会返回当前动画进度所对应的数字进度,但这个数字进度是百分制的,以小数表示,如0.2 (3)、Evaluator:我们知道我们通过监听器拿到的是当前动画所对应的具体数值,而不是百分制的进度。那么就必须有一个地方会根据当前的数字进度,将其转化为对应的数值,这个地方就是Evaluator;Evaluator就是将从加速器返回的数字进度转成对应的数字值。所以上部分中,我们讲到的公式:当前的值 = 100 + (400 -100)* 当前显示进度 这个公式就是在Evaluator计算的;在拿到当前数字进度所对应的值以后,将其返回 (4)、监听器:我们通过在AnimatorUpdateListener监听器使用animation.getAnimatedValue()函数拿到Evaluator中返回的数字值。 讲了这么多,Evaluator其实就是一个转换器,他能把小数进度转换成对应的数值位置

各种Evaluator

首先,加速器返回的小数值,表示的是当前动画的数值进度。无论是利用ofFloat()还是利用ofInt()定义的动画都是适用的。因为无论是什么动画,它的进度必然都是在0到1之间的。0表示没开始,1表示数值运动的结束,对于任何动画都是适用的。 但Evaluator则不一样,我们知道Evaluator是根据加速器返回的小数进度转换成当前数值进度所对应的值。这问题就来了,如果我们使用ofInt()来定义动画,动画中的值应该都是Int类型,如果我用ofFloat()来定义动画,那么动画中的值也都是Float类型。所以如果我用ofInt()来定义动画,所对应的Evaluator在返回值时,必然要返回Int类型的值。同样,我们如果用ofFloat来定义动画,那么Evaluator在返回值时也必然返回的是Float类型的值。 所以每种定义方式所对应的Evaluator必然是它专用的;Evaluator专用的原因在于动画数值类型不一样,在通过Evaluator返回时会报强转错误;所以只有在动画数值类型一样时,所对应的Evaluator才能通用。所以ofInt()对应的Evaluator类名叫IntEvaluator,而ofFloat()对应的Evaluator类名叫FloatEvaluator; 下面是IntEvaluator

public class IntEvaluator implements TypeEvaluator<Integer> {

        /**
         * This function returns the result of linearly interpolating the start and end values, with
         * <code>fraction</code> representing the proportion between the start and end values. The
         * calculation is a simple parametric calculation: <code>result = x0 + t * (v1 - v0)</code>,
         * where <code>x0</code> is <code>startValue</code>, <code>x1</code> is <code>endValue</code>,
         * and <code>t</code> is <code>fraction</code>.
         *
         * @param fraction   The fraction from the starting to the ending values
         * @param startValue The start value; should be of type <code>int</code> or
         *                   <code>Integer</code>
         * @param endValue   The end value; should be of type <code>int</code> or <code>Integer</code>
         * @return A linear interpolation between the start and end values, given the
         *         <code>fraction</code> parameter.
         */
        public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
            int startInt = startValue;
            return (int)(startInt + fraction * (endValue - startInt));
        }
    }

可以看到在IntEvaluator中只有一个函数(float fraction, Integer startValue, Integer endValue) ; 其中fraction就是加速器中的返回值,表示当前动画的数值进度,百分制的小数表示。 startValue和endValue分别对应ofInt(int start,int end)中的start和end的数值; 比如我们假设当我们定义的动画ofInt(100,400)进行到数值进度20%的时候,那么此时在evaluate函数中,fraction的值就是0.2,startValue的值是100,endValue的值是400;

关于ArgbEvalutor

我们上面讲了IntEvaluator和FloatEvalutor,还说了Evalutor一般来讲不能通用,会报强转错误,也就是说,只有在数值类型相同的情况下,Evalutor才能共用。 其实除了IntEvaluator和FloatEvalutor,在android.animation包下,还有另外一个Evalutor叫ArgbEvalutor。 ArgbEvalutor是用来做颜色值过渡转换的。可能是谷歌的开发人员觉得大家对颜色值变换可能并不知道要怎么做,所以特地给我们提供了这么一个过渡Evalutor;

用法如下

    ValueAnimator valueAnimator2 = ValueAnimator.ofInt(0xffffff,0x000000);
    valueAnimator2.setDuration(2000);   //设置时长
    valueAnimator2.setEvaluator(new ArgbEvaluator());
    valueAnimator2.setInterpolator(new LinearInterpolator()); //设置插值器 默认的就是 LinearInterpolator 可以设置不同的插值器
    valueAnimator2.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            int currentColor = (Integer) animation.getAnimatedValue();
            anim_text.setBackgroundColor(currentColor);
        }
    });
    valueAnimator2.start();  //开始

ofObject()概述

前面我们讲了ofInt()和ofFloat()来定义动画,但ofInt()只能传入Integer类型的值,而ofFloat()则只能传入Float类型的值。那如果我们需要操作其它类型的变量要怎么办呢?其实ValueAnimator还有一个函数ofObject(),可以传进去任何类型的变量,定义如下

 public static ValueAnimator ofObject(TypeEvaluator evaluator, Object... values) {
        ValueAnimator anim = new ValueAnimator();
        anim.setObjectValues(values);
        anim.setEvaluator(evaluator);
        return anim;
    }

它有两个参数,第一个是自定义的Evaluator,第二个是可变长参数,Object类型的; 大家可能会疑问,为什么要强制传进去自定义的Evaluator?首先,大家知道Evaluator的作用是根据当前动画的显示进度,计算出当前进度下把对应的值。那既然Object对象是我们自定的,那必然从进度到值的转换过程也必须由我们来做,不然系统哪知道你要转成个什么鬼。 好了,现在我们先简单看一下ofObject这个怎么用。

ValueAnimator valueAnimator2 = ValueAnimator.ofObject(new CharEvaluator(),new Character('a'),new Character('z'));

    valueAnimator2.setDuration(2000);   //设置时长
    valueAnimator2.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            char currentColor = (Character) animation.getAnimatedValue();
            anim_text.setText(currentColor);
        }
    });
    valueAnimator2.start();  //开始

下面是CharEvaluator的实现

 public class CharEvaluator implements TypeEvaluator<Character>{

        @Override
        public Character evaluate(float fraction, Character startValue, Character endValue) {
            int startInt  = (int)startValue;
            int endInt = (int)endValue;
            int curInt = (int)(startInt + fraction *(endInt - startInt));
            char result = (char)curInt;
            return result;
        }
    }

在这里,我们就利用A-Z字符在ASCII码表中对应数字是连续且递增的原理,先求出来对应字符的数字值,然后再转换成对应的字符

ObjectAnimator

上面给大家讲了ValueAnimator,但ValueAnimator有个缺点,就是只能对数值对动画计算。我们要想对哪个控件操作,需要监听动画过程,在监听中对控件操作。这样使用起来相比补间动画而言就相对比较麻烦。 为了能让动画直接与对应控件相关联,以使我们从监听动画过程中解放出来,谷歌的开发人员在ValueAnimator的基础上,又派生了一个类ObjectAnimator; 由于ObjectAnimator是派生自ValueAnimator的,所以ValueAnimator中所能使用的方法,在ObjectAnimator中都可以正常使用。 但ObjectAnimator也重写了几个方法,比如ofInt(),ofFloat()等。我们先看看利用ObjectAnimator重写的ofFloat方法如何实现一个动画:(改变透明度)

    ObjectAnimator objectAnimator3 = ObjectAnimator.ofFloat(anim_text,"alpha",0,1);
    objectAnimator.setDuration(200);
    objectAnimator.start();

上面就是改变anim_text空间透明度的动画(透明度从0到1)

    public static ObjectAnimator ofFloat(Object target, String propertyName, float... values)
  • 第一个参数用于指定这个动画要操作的是哪个控件
  • 第二个参数用于指定这个动画要操作这个控件的哪个属性
  • 第三个参数是可变长参数,这个就跟ValueAnimator中的可变长参数的意义一样了,就是指这个属性值是从哪变到哪。像我们上面的代码中指定的就是将anim_text的alpha属性从0变到1

setter函数 我们再看构造改变rotation值的ObjectAnimator的方法

ObjectAnimator animator = ObjectAnimator.ofFloat(tv,"rotation",0,180,0); 

TextView控件有rotation这个属性吗?没有,不光TextView没有,连它的父类View中也没有这个属性。那它是怎么来改变这个值的呢?其实,ObjectAnimator做动画,并不是根据控件xml中的属性来改变的,而是通过指定属性所对应的set方法来改变的。比如,我们上面指定的改变rotation的属性值,ObjectAnimator在做动画时就会到指定控件(TextView)中去找对应的setRotation()方法来改变控件中对应的值。同样的道理,当我们在最开始的示例代码中,指定改变”alpha”属性值的时候,ObjectAnimator也会到TextView中去找对应的setAlpha()方法。那TextView中都有这些方法吗,有的,这些方法都是从View中继承过来的,在View中有关动画,总共有下面几组set方法:

 //1、透明度:alpha  
    public void setAlpha(float alpha)

    //2、旋转度数:rotation、rotationX、rotationY  
    public void setRotation(float rotation)
    public void setRotationX(float rotationX)
    public void setRotationY(float rotationY)

    //3、平移:translationX、translationY  
    public void setTranslationX(float translationX)
    public void setTranslationY(float translationY)

    //缩放:scaleX、scaleY  
    public void setScaleX(float scaleX)
    public void setScaleY(float scaleY)

可以看到在View中已经实现了有关alpha,rotaion,translate,scale相关的set方法。所以我们在构造ObjectAnimator时可以直接使用。 在开始逐个看这些函数的使用方法前,我们先做一个总结:

  • 要使用ObjectAnimator来构造对画,要操作的控件中,必须存在对应的属性的set方法 s
  • etter 方法的命名必须以骆驼拼写法命名,即set后每个单词首字母大写,其余字母小写,即类似于setPropertyName所对应的属性为propertyName

根据上面的流程,这里有几个注意事项:
(1)、拼接set函数的方法:上面我们也说了是首先是强制将属性的第一个字母大写,然后与set拼接,就是对应的set函数的名字。注意,只是强制将属性的第一个字母大写,后面的部分是保持不变的。反过来,如果我们的函数名命名为setScalePointX(float ),那我们在写属性时可以写成”scalePointX”或者写成“ScalePointX”都是可以的,即第一个字母大小写可以随意,但后面的部分必须与set方法后的大小写保持一致。
(2)、如何确定函数的参数类型:上面我们知道了如何找到对应的函数名,那对应的参数方法的参数类型如何确定呢?我们在讲ValueAnimator的时候说过,动画过程中产生的数字值与构造时传入的值类型是一样的。由于ObjectAnimator与ValueAnimator在插值器和Evaluator这两步是完全一样的,而当前动画数值的产生是在Evaluator这一步产生的,所以ObjectAnimator的动画中产生的数值类型也是与构造时的类型一样的

自定义ObjectAnimator属性

我们在开始之前再来捋一下ObjectAnimator的动画设置流程:ObjectAnimator需要指定操作的控件对象,在开始动画时,到控件类中去寻找设置属性所对应的set函数,然后把动画中间值做为参数传给这个set函数并执行它。 所以,我们说了,控件类中必须所要设置属性所要对应的set函数。所以为了自由控制控件的实现,我们这里自定义一个控件。大家知道在这个自定义控件中,肯定存在一个set函数与我们自定义的属性相对应

    public class MyPointView extends View {
        private Point mPoint = new Point(100);

        public MyPointView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }

        @Override
        protected void onDraw(Canvas canvas) {
            if (mPoint != null){
                Paint paint = new Paint();
                paint.setAntiAlias(true);
                paint.setColor(Color.RED);
                paint.setStyle(Paint.Style.FILL);
                canvas.drawCircle(300,300,mPoint.getRadius(),paint);
            }
            super.onDraw(canvas);
        }

        void setPointRadius(int radius){
            mPoint.setRadius(radius);
            invalidate();
        }

    }

这就是我们自定义的控件,操做如下

    ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(MyPointView,"pointRadius",0,100);
    objectAnimator.setDuration(200);
    objectAnimator.start();

PropertyValuesHolder和AnimatorSet

如果我们想对同一个空间的多个属性同时进行动画,那么我们可以用PropertyValuesHolder或AnimatorSet 由于ValueAnimator和ObjectAnimator都具有ofPropertyValuesHolder()函数,使用方法也差不多,相比而言,ValueAnimator的使用机会不多,这里我们就只讲ObjectAnimator中ofPropertyValuesHolder()的用法。相信大家懂了这篇以后,再去看ValueAnimator的ofPropertyValuesHolder(),也应该是会用的。PropertyValuesHolder这个类的意义就是,它其中保存了动画过程中所需要操作的属性和对应的值。我们通过ofFloat(Object target, String propertyName, float… values)构造的动画,ofFloat()的内部实现其实就是将传进来的参数封装成PropertyValuesHolder实例来保存动画状态。在封装成PropertyValuesHolder实例以后,后期的各种操作也是以PropertyValuesHolder为主的。 说到这里,大家就知道这个PropertyValuesHolder是有多有用了吧,上面我们也说了,ObjectAnimator给我们提供了一个口子,让我们自己构造PropertyValuesHolder来构造动画。

PropertyValuesHolder p1 = PropertyValuesHolder.ofInt("BackgroundColor", 0xffffffff,0x0000ff, 0xff000000);
    p1.setEvaluator(new ArgbEvaluator());
    PropertyValuesHolder p2 = PropertyValuesHolder.ofFloat("rotation", 0, 60, 0, -60, 0, 60, 0, -60, 0);
    
    ObjectAnimator objectAnimator = ObjectAnimator.ofPropertyValuesHolder(anim_text,p1,p2);
    objectAnimator.setDuration(2000);
    objectAnimator.start();

以上是PropertyValuesHolder的用法

ObjectAnimator objectAnimator1 = ObjectAnimator.ofInt(anim_text,"BackgroundColor",0xffffffff,0x0000ff, 0xff000000);
    objectAnimator1.setEvaluator(new ArgbEvaluator());

    ObjectAnimator objectAnimator2 = ObjectAnimator.ofFloat(anim_text,"rotation",0, 60, 0, -60, 0, 60, 0, -60, 0);
    AnimatorSet animatorSet = new AnimatorSet();
    animatorSet.playTogether(objectAnimator1, objectAnimator2);
    animatorSet.setDuration(2000);
    animatorSet.start();

这是AnimatorSet的用法