android属性动画

一、概述

android动画总共分为三种逐帧动画、补间动画、属性动画。

逐帧动画:主要就是将几张图片放在一起播放形成动画。

补间动画:补间动画还是比较局限的,能实现view的旋转、横竖拉伸、横竖平移、透明度等简单的变化。

由于android速度发展之快,原有的两种动画已经不能满足我们的需求,所以android在3.0版本推出了一个高大上的动画效果,属性动画。




二、相关API:

ValueAnimator:属性动画的执行类,主要负责计算各个帧所对应的属性的值,可以处理动画的更新事件,它可以定义属性动画绝大部分的核心功能;

ObjectAnimator:他是ValueAnimator的子类,之一指定动画改变的属性,不过某些场景下,objectAnimator有局限性,那就需要通过ValueAnimator来解决。
AnimatorSet:用于控制多个动画的执行顺序,下文会讲到有哪些方法可以用到。
AnimatorInflater:用于加载属性动画的xml文件。
TimeInterpolator:时间插值器,用于控制动画播放的速度,是一个接口可以实现它去控制动画播放的速度。此类有许多系统默认的子类,可以实现动画的匀速,加速等一些类别的播放。
PropertyValuesHolder:简单的属性执行类,相对于objectAnimator和valueAnimator使用起来更方便,也更好理解。
TypeEvaluator:此API在我看来是实现炫酷属性动画的核心部分,此API主要负责在动画执行到某个值的时候需要展示什么样的效果。



本篇博客将介绍:objectAnimator和valueAnimator的简单用法,如何监听动画的各个过程,AnimatorSet实现动画的衔接和同时播放,AnimatorInflater加载xml动画文件,PropertyValuesHolder加载多个动画,查看timeInterpolator的相关子类和重写TimeInterpolator实现动画的先减速后加速的效果,最后使用objectAnimator和valueAnimator去加载重写

TypeEvaluator的动画效果。那么下面我们进入正题。






三、objectAnimator和valueAnimator的简单用法

1、其实这两个动画先介绍哪两个都无所谓,但由于objectAnimatorValueAnimator的子类,那我们就先编写一个ValueAnimator的例子,之后再去扩展ObjectAnimator下面看代码:

ValueAnimator valueAnimator = ValueAnimator.ofFloat(0.0f,360.0f);
valueAnimator.setDuration(1000);
valueAnimator.setTarget(imSimpleValueanimactionIcon);   //这个地方是一定要设置的 不然不知道是哪个对象的  设置是哪个对象使用此动画
valueAnimator.start();
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        imSimpleValueanimactionIcon.setRotationY((Float)animation.getAnimatedValue());
    }
});


此段代码主要就是实现了一个功能,那就是imSimpleValueanimactionIcon这个View的旋转,首先我们去创建一个ValueAnimator的对象,创建ValueAnimator的方法有很多ofFloat是设置动画执行的开始和结束的float类型的值,0.0f为开始360f为结束,还有很多方法,ofInt、ofObject等。用法都是大同小异。在穿件对象之后,我们又去通过setDuration设置了动画执行的时间,ms是时间单位。然后我们去设置了这个动画要在哪个对象上面执行,调用了setTarget方法。最后设置了valueAnimator的开始,不过ValueAnimator并不知道我们设置的值需要执行在view的哪个属性上面,所以我们 需要监听动画的执行过程,通过获取执行过程中的值去设置view属性,这样我们通过addUpdateListener来获取动画执行到当前的值,animation.getAnimatedValue()此方法可以获取动画执行到当下的值是多少,一定是一个我们设置的值的中间数。然后我们通过设置旋转Y的角度去设置一下view的属性就可以实现动画效果了。

看完此段代码我们会想到去监听动画的值有些麻烦,也就是因为有这样麻烦的思想我们也就进入了ObjectAnimator,

ObjectAnimator是不需要监听动画执行过程中的值的,因为系统会自动为我们设置下面看代码:


ObjectAnimator animator = ObjectAnimator.ofFloat(view,"rotation",0.0f,360f);   //这个rotation是系统内置的
        animator.setDuration(1000);
        animator.start();

是不是简单了很多,这也是ObjectAnimator的一个优势,编写简单,不过理解相对繁琐一点,下面解释代码,首先我们还是通过老方法创建ObjectAnimator对象,创建对象的方法和ValueAnimator的方法几本相同,不过参数有些不同,我解释一下参数,第一个参数的意思是动画执行的对象,第二个参数是动画要操作的属性,这个属性系统定义了一些

基本上可以实现和补间动画相同的效果,不过我们也可以在view中自己定义一个属性,不过此属性一定要有getter和setter方法,如果没有这两个方法动画会实现不了,我们可以自定义一个color来改变颜色,在最后我们讲一个自定义属性的ObjectAnimator动画。执行程序我们会看到view旋转的效果。





四、监听动画

在我们实现动画的时候有时需要对动画的开始结束,执行过程中处理一些逻辑,那么我们就想到对动画进行监听,我们知道ValueAnimator和ObjectAnimator都是Animator的子类,那么我们就可以用如下方法进行监听动画
animName.addListener(new AnimatorListener() {  
    @Override  
    public void onAnimationStart(Animator animation) {   //动画的开始调用

    }  
  
    @Override  
    public void onAnimationRepeat(Animator animation) {  //动画重复执行调用
    }  
  
    @Override  
    public void onAnimationEnd(Animator animation) {    //动画的结束调用

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

    }  
});  






五、AnimatorSet实现动画的执行顺序

前面我们介绍了单个动画的执行,那么我们要是想两个动画一起执行或者一个动画在一个动画之前执行,那么我们就用到了AnimatorSet这个类下面看代码。
	
AnimatorSet animSet = new AnimatorSet();
 
   
	animSet.play(valueAnimator1).with(objectAnimator1);   //一起播放
 	animSet.play(valueAnimator2).after(objectAnimator2);  //之后播放
        animSet.play(valueAnimator3).before(objectAnimator3); //之前播放
        animSet.setDuration(5000);
        animSet.start();


 
   
从代码中我们可以看出来可以设置三种播放方式,其实每个AnimatorSet都可以设置很多play和with,但是不要吧所有的play和with都写在一个语句中,这样会造成系统的混乱。
 
   
 
   
 
   
 
  







六、PropertyValuesHolder

上个阶段我们用到了AnimatorSet其实实现动画一起播放的方法有很多, PropertyValuesHolder也是可以实现的,下面看代码。
PropertyValuesHolder X = PropertyValuesHolder.ofFloat("alpha", 1f,
                0f, 1f);
        PropertyValuesHolder Y = PropertyValuesHolder.ofFloat("scaleX", 1f,
                0, 1f);
        PropertyValuesHolder Z = PropertyValuesHolder.ofFloat("scaleY", 1f,
                0, 1f);
        ObjectAnimator.ofPropertyValuesHolder(view, X, Y,Z).setDuration(1000).start();

上述代码中我们看到了我们设置了三个动画,最后用of PropertyValuesHolder方法将他们一起执行其实 PropertyValuesHolder的主要作用不是这个,因为我们通常都是对view执行动画,所以上面的objectAnimator和valueAnimator的设置都有些麻烦,那么我们就会用到PropertyValuesHolder这个API,PropertyValueHolder这个API使用简单,下面就看一下代码:
ballView.animate().alpha(0).setDuration(5000).setInterpolator(new AndyaInterpolator());
//仅此一行代码就可以了

只要objectAnimator和valueAnimator可以设置的属性PropertyValuesHolder都可以设置,其实PropertyValuesHolder主要的核心在于animate这个方法 这个方法会创建并返回一个ViewPropertyAnimator的实例,之后的调用的所有方法,设置的所有属性都是通过这个实例完成的。上面连start方法都省略了 在设置完成之后会自动执行start方法不过我们也可以显示调用。
在代码中你会看到setInterpolator这个方法,这个方法是设置时间差值器,在下个阶段我们会介绍时间插值器的相关内容。






七、TimeInterpolator 时间插值

       如果你执行了我上面的代码,现在你如果把时间设置的久一点,你会发现,view旋转的速度是由慢到快在到慢的,这是因为如果你不设置动画播放的速度系统会帮你设置这样一个效果,那么如果你想自己控制这个这个动画的速度的话,我们就会用到TimeInterpolator这个接口,系统已经实现了几个速度相关的类,可以自行百度,设置播放速度的方法也是很简单的setInterpolator(new  时间插值名字)就可以了,例如animName.setInterpolator(new AccelerateInterpolator(2f));就可以设置动画播放的越来越快,好了我们来一起看一下TimeInterpolator接口需要实现的代码, 
public interface TimeInterpolator {

    /**
     * Maps a value representing the elapsed fraction of an animation to a value that represents
     * the interpolated fraction. This interpolated value is then multiplied by the change in
     * value of an animation to derive the animated value at the current elapsed animation time.
     *
     * @param input A value between 0 and 1.0 indicating our current point
     *        in the animation where 0 represents the start and 1.0 represents
     *        the end
     * @return The interpolation value. This value can be more than 1.0 for
     *         interpolators which overshoot their targets, or less than 0 for
     *         interpolators that undershoot their targets.
     */
    float getInterpolation(float input);

可以看到如果实现测接口需要实现getInterpolation方法 此方法里面有一个input值,此值介于0到1之间,如果想改变动画的速度,就是通过此值实现的,input值的作用就是计算用户设置的值现在播放到什么程度的然后给予返回,TypeEvaluator的evaluate方法中的值就是通过input计算得到的,关于TypeEvaluator下节会介绍。

下面看我写的一个先减速后加速的TimeInterplator,控制速度主要是控制input的变化,变化快则动画播放的速度就快看代码:
package com.transfar.andya.propertyanimactiondemo.TimeInterpolatorClass;

import android.animation.TimeInterpolator;

/**
 * Created by andYa on 2017/1/7.
 */
public class AndYaTimeInterpolator implements TimeInterpolator{
    @Override
    public float getInterpolation(float input) {
        return (float) (Math.tan((input * 2 - 1) / 4 * Math.PI)) / 2.0f + 0.5f;
    }
}

此段代码是我从一个数学大神的手里拿到的,如果数学好的同学可以画一下此数学公式的x/y图,x为input,return为Y值,从斜率就可以看出来变化,如果你在你动画中设置valueAnimator.setInterpolator(new AndYaTimeInterpolator());
那么你将会看到动画是先快速变化,然后慢速然后在快速。
此API在我看来也是动画中的一个比较核心的部分,如果你想打造炫酷的动画,那么久要计算好这个动画什么时候加速什么时候减速什么时候匀速,所以此地方是一个重点也是难点。








八、重写TyepEvaluator


在动画的执行过程中,有许多属性是我们android系统没有的,所以需要我们自己去定义一个属性,然后去改变他们,
当时介绍api的时候我就说过,这个方法就是返回动画播放过程中用户设定的某个值对应的可以设置在view的值,但TypeEvaluator是一个接口,我们需要自己去实现一个类,我们先看一下源代码:
public interface TypeEvaluator<T> {

    /**
     * This function returns the result of linearly interpolating the start and end values, with
     * fraction representing the proportion between the start and end values. The
     * calculation is a simple parametric calculation: result = x0 + t * (x1 - x0),
     * where x0 is startValue, x1 is endValue,
     * and t is fraction.
     *
     * @param fraction   The fraction from the starting to the ending values
     * @param startValue The start value.
     * @param endValue   The end value.
     * @return A linear interpolation between the start and end values, given the
     *         fraction parameter.
     */
    public T evaluate(float fraction, T startValue, T endValue);

}

从上面的代码中可以看出,我们可以定义任何类型来传入和传出这个方法,startValue是我们设置的开始的值,endValue是我们设置的结束的值,fraction这我要说一下,这个值在上面的地方出现过,时间插值,动画执行的过程中,系统会计算动画执行到哪然后返回一个input值,通过我们自己定义可以用input值计算出一个fraction值,那么这个fraction值就是当前动画执行到的某一个点的值,那么我们通过这个fraction就可以算出当前执行到startValue和endValue中间的一个值,然后去操作UI,这个fraction也就是这个方法的关键。
下面我们来实现一个动画,动画的要求是一个小球左上角到左下角的移动。
思路:
1、首先我们要先去定义一个数据类型,这个数据类型就包括两个属性x,y两个属性分别代表,view的位置的x值和Y值
2、然后我们在去自定义一个view去绘制这个小球。
3、小球的xy坐标我们通过重写TypeEvaluator来去给予。
首先第一步很简单,看代码:
/**
 * Created by andYa on 2017/1/6.
 */
public class Point {

    private float Y;
    private float X;

    public Point(float X,float Y){
        this.X = X;
        this.Y = Y;
    }

    public float getY() {
        return Y;
    }

    public float getX() {
        return X;
    }

    public void setY(float y) {
        Y = y;
    }

    public void setX(float x) {
        X = x;
    }
}
entry我们定义完成了,那么我们就去自定义一下view,这个view我们只要定义一个画笔,然后去ondraw就可以了
需要定义一个public的点,通过外部去改变这个点的值,然后去ondraw()下面看代码:
/**
 * Created by andYa on 2017/1/6.
 */
public class BallView extends View {


    public static final float RADIUBALL = 30f;

    public Point currentPoint;

    private Paint mPaint;


    public BallView(Context context) {
        super(context);
    }

    public BallView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(Color.BLACK);
    }

    public BallView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public void onDraw(Canvas canvas){

        if(currentPoint == null){
            currentPoint = new Point(RADIUBALL, RADIUBALL);
            drawCricle(canvas);
        }else{
            drawCricle(canvas);
        }

    }

    private void drawCricle(Canvas canvas){
        float x = currentPoint.getX();
        float y = currentPoint.getY();
        canvas.drawCircle(x,y,RADIUS,mPaint);
    }


}
此段代码我们首先初始化了一个画笔,然后设置为黑色,然后我们又设置了半径currentPoint这个点就是给外界暴露的一个点,如果这个点为空,那我们就先给这个点赋上初始值,如果不为空我们就会ondraw()这个点,在动画改变点的时候我们一定要去调用invalidate()这个方法不然不会重绘的。下面我们就看一下最关键的地方TypeEvaluator:
public class PorpertyTypeEvaluator implements TypeEvaluator{
    @Override
    public Object evaluate(float fraction, Object startValue, Object endValue) {

        Point startPoint = (Point)startValue;
        Point endPoint = (Point)endValue;
        float x = startPoint.getX() + fraction * (endPoint.getX() - startPoint.getX());
        float y = startPoint.getY() + fraction * (endPoint.getY() - startPoint.getY());
        Point point = new Point(x, y);
        return point;
    }
}
//调用的局部代码:
ValueAnimator valueAnimator = ValueAnimator.ofObject(new PorpertyTypeEvaluator(),new Point(0f,0f),new Point(300f,300f));
        valueAnimator.setTarget(ballView);
        valueAnimator.setInterpolator(new AndYaTimeInterpolator());
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                ballView.currentPoint  = (Point) animation.getAnimatedValue();
                ballView.invalidate();     //这个地方一定要让小球重绘不然是没效果的
            }
        });

 从代码中我们可以看出来TypeEvaluator就是告诉你动画过渡阶段的每个值应该返回什么,从第一段代码中可以看出返回的就是fraction所对应的点的坐标,其实在第一节中介绍的offloat也是有 
   TypeEvaluator属性的只不过系统默认加上去的,是FloatEvaluator他呈现给我们的就是平滑的过渡效果。那么第二段代码就是调用了,我们去监听动画的改变,如果动画有变化我们就去得到这个点,然后重绘小球,是不是很简单。 
   
 
   
 
  

好了这个例子就到此为止了,记得在第一节的时候说过ObjectAnimator是可以自定义属性的,其实他的自定义属性也是需要我们重写TypeEvaluator来实现的,在网上看到一个很不错的Demo,这个Demo的用处就是实现小球颜色的从一个颜色到一个颜色的改变的动画,我先说一下思路:
1、我们先要在view中定义一个属性color,而且千万别忘记了设置color的setter和getter方法。
2、我们去重写TypeEvaluator,此子类返回的时候每个fraction对应的颜色是什么,然后就可以的了。

我们延用了上面的point和view这里我就不贴代码了,只不过我们需要在view中加一段代码,那么就是
private String color;

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
        mPaint.setColor(Color.parseColor(color));
        invalidate();
    }

此段代码的setColor方法直接设置了画笔的颜色然后去重绘了一下。然后我们看一下第二点的实现,第二点的实现先对较为复杂一些,因为我们需要把传入的颜色编码进行转换,下面我贴上某位大神的代码:
public class ColorTypeEvaluator implements TypeEvaluator{            //这个typeevaluator就是返回当前的object所以此color就是返回fraction对应的颜色的值

    private int mCurrentRed = -1;

    private int mCurrentGreen = -1;

    private int mCurrentBlue = -1;
    @Override
    public Object evaluate(float fraction, Object startValue, Object endValue) {
        String startColor = (String) startValue;
        String endColor = (String) endValue;
        int startRed = Integer.parseInt(startColor.substring(1, 3), 16);
        int startGreen = Integer.parseInt(startColor.substring(3, 5), 16);
        int startBlue = Integer.parseInt(startColor.substring(5, 7), 16);
        int endRed = Integer.parseInt(endColor.substring(1, 3), 16);
        int endGreen = Integer.parseInt(endColor.substring(3, 5), 16);
        int endBlue = Integer.parseInt(endColor.substring(5, 7), 16);

        // 初始化颜色的值
        if (mCurrentRed == -1) {
            mCurrentRed = startRed;
        }
        if (mCurrentGreen == -1) {
            mCurrentGreen = startGreen;
        }
        if (mCurrentBlue == -1) {
            mCurrentBlue = startBlue;
        }
        // 计算初始颜色和结束颜色之间的差值
        int redDiff = Math.abs(startRed - endRed);
        int greenDiff = Math.abs(startGreen - endGreen);
        int blueDiff = Math.abs(startBlue - endBlue);
        int colorDiff = redDiff + greenDiff + blueDiff;
        if (mCurrentRed != endRed) {
            mCurrentRed = getCurrentColor(startRed, endRed, colorDiff, 0,
                    fraction);
        } else if (mCurrentGreen != endGreen) {
            mCurrentGreen = getCurrentColor(startGreen, endGreen, colorDiff,
                    redDiff, fraction);
        } else if (mCurrentBlue != endBlue) {
            mCurrentBlue = getCurrentColor(startBlue, endBlue, colorDiff,
                    redDiff + greenDiff, fraction);
        }
        // 将计算出的当前颜色的值组装返回
        String currentColor = "#" + getHexString(mCurrentRed)
                + getHexString(mCurrentGreen) + getHexString(mCurrentBlue);
        return currentColor;
    }

    /**
     * 根据fraction值来计算当前的颜色。
     */
    private int getCurrentColor(int startColor, int endColor, int colorDiff,
                                int offset, float fraction) {
        int currentColor;
        if (startColor > endColor) {
            currentColor = (int) (startColor - (fraction * colorDiff - offset));
            if (currentColor < endColor) {
                currentColor = endColor;
            }
        } else {
            currentColor = (int) (startColor + (fraction * colorDiff - offset));
            if (currentColor > endColor) {
                currentColor = endColor;
            }
        }
        return currentColor;
    }

    /**
     * 将10进制颜色值转换成16进制。
     */
    private String getHexString(int value) {
        String hexString = Integer.toHexString(value);
        if (hexString.length() == 1) {
            hexString = "0" + hexString;
        }
        return hexString;
    }
}

此段代码的重点其实就是通过起始和结束颜色在红绿蓝中的占比然后计算当前应该显示什么颜色的,最后返回了,代码实现起来有点复杂不过还是很好理解的。下面看我们的调用。
ObjectAnimator objectAnimator = ObjectAnimator.ofObject(ballView,"color",new ColorTypeEvaluator(),"#0000FF", "#FF0000");
        objectAnimator.setTarget(ballView);
        objectAnimator.setDuration(5000);
        objectAnimator.start();
调用也是非常方便的如果我们在加上
AnimatorSet animatorset =  new AnimatorSet();
            animatorset.play(valueAnimator).with(objectAnimator);
就可以实现小球从左上角到右下角,并伴随着颜色变化的动画了。

好了,属性动画就介绍到这里了,其实属性动画的强大并不是仅仅与此,布局动画Layout Animations其中这个还没有介绍到,布局动画也是非常抢到的一个动画,有兴趣的可以去看一下文档了解一下,此篇博文到此就要结束了,对此博文有什么错误的地方欢迎指导,也欢迎留言提问。谢谢大家一致这么热心的的阅读到最后。源代码地址:


http://download.csdn.net/detail/a49681109/9731438











你可能感兴趣的:(android)