Android的属性动画

来源:互联网 发布:淘宝网拍下商品的步骤 编辑:程序博客网 时间:2024/05/30 22:44

# Android的属性动画

概述

补间动画只能完成移动,缩放,旋转和淡入淡出的操作,而且只能作用在View上,只是改变了View的显示效果,而不会真正改变view的属性。比如通过补间动画把button移到屏幕其它位置,现在去点击这个button是不会触发点击事件,button实际还是停留在原来的位置。属性动画可以对任意对象进行动画而不仅仅是view,在一定时间间隔里完成对象从一个属性值到另一个属性值的改变。动画默认时间间隔是300ms,默认帧率是10 ms/帧。在api11以前,导入nineoldandroids动画库使用属性动画。

使用属性动画的常用方法:
1. 采用ValueAnimator,监听动画过程,自己实现属性的改变
2. 采用ObjectAnimator直接对任意对象的任意属性进行动画操作。
3. 使用xml编写动画

ValueAnimator

ValueAnimator本身不作用于任何对象,也就是直接使用它没有任何动画效果。它可以对一个值做动画,完成从初始值平滑地过渡到结束值这样的效果,然后我们可以监听其动画过程,在动画过程中修改我们对象的属性值,相当于我们的对象做了动画。

1.简单用法 ValueAnimator.ofFloat(float… values)

在3s内设置buton透明度,从0f到1f,最后再到0.5f。

                ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f,0.5f);                anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {                    @Override                    public void onAnimationUpdate(ValueAnimator animation) {                        button.setAlpha((Float) animation.getAnimatedValue());                    }                });                anim.setDuration(3000);                anim.start();

这里写图片描述

在这个例子中我们通过给值做动画,在onAnimationUpdate监听值的变化,通过setAlpha()方法修改透明度属性。相同的,我们也可以通过setRotation(),setTranslationX(),setScaleX()方法分别完成旋转,水平移动,水平缩放的动画。也可以更改其他属性,完成其他类型的动画。ValueAnimator.ofInt(),与ValueAnimator.ofFloat()类似。

ValueAnimator对象调用setStartDelay()方法来设置动画延迟播放的时间,调用setRepeatCount()设置动画循环播放的次数和setRepeatMode()方法设置循环播放的模式

2.高级用法 ValueAnimator.ofObject(TypeEvaluator evaluator, Object… values)

ValueAnimator.ofObject()是对任意对象进行动画操作的,如果要对其他类型(非int,float,color)做动画,那么需要自定义类型估值算法,告诉系统如何从初始值过渡到结束值。

先了解TypeEvaluator的作用:告诉动画系统如何从初始值过渡到到结束值。

如:调用了ValueAnimator.ofFloat()方法,之后通过系统内置FloatEvaluator计算告知动画系统如何从初始值过渡到结束值。

public class FloatEvaluator implements TypeEvaluator<Number> {    public Float evaluate(float fraction, Number startValue, Number endValue) {        float startFloat = startValue.floatValue();        return startFloat + fraction * (endValue.floatValue() - startFloat);    }}

FloatEvaluator实现了TypeEvaluator接口,然后重写evaluate()方法。evaluate()方法当中传入了三个参数,第一个参数fraction,这个参数表示动画的完成度,第二第三个参数分别表示动画的初始值和结束值。evaluate()方法返回当前动画的值,并在AnimatorUpdateListener.onAnimationUpdate()中可以获取然后使用

例子:在5s的时间里,实现小球的自由落体

定义一个Point类,管理坐标

public class Point {    private float x;    private float y;    public Point(float x, float y) {        this.x = x;        this.y = y;    }    public float getX() {        return x;    }    public float getY() {        return y;    }}

定义PointEvaluator,实现了TypeEvaluator接口并重写了evaluate()方法,告诉动画系统坐标如何从初始值过渡到到结束值

public class PointEvaluator 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;    }}

新建一个MyAnimView继承自View,绘制的逻辑是由currentPoint这个对象控制的,如果currentPoint对象不等于空,那么就调用drawCircle()方法在currentPoint的坐标位置画出一个半径为50的圆,如果currentPoint对象是空,那么就调用startAnimation()方法来启动动画。

public class MyAnimView extends View{    public static final float RADIUS = 50f;    private Point currentPoint;    private Paint mPaint;    public MyAnimView(Context context, AttributeSet attrs) {        super(context, attrs);        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);        mPaint.setColor(Color.BLUE);    }    @Override    protected void onDraw(Canvas canvas) {        if (currentPoint == null) {            currentPoint = new Point(RADIUS, RADIUS);            drawCircle(canvas);            startAnimation();        } else {            drawCircle(canvas);        }    }    private void drawCircle(Canvas canvas) {        float x = currentPoint.getX();        float y = currentPoint.getY();        canvas.drawCircle(x, y, RADIUS, mPaint);    }    private void startAnimation() {        Point startPoint = new Point(RADIUS, RADIUS);        Point endPoint = new Point(RADIUS, getHeight() - RADIUS);        ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), startPoint, endPoint);        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {            @Override            public void onAnimationUpdate(ValueAnimator animation) {                currentPoint = (Point) animation.getAnimatedValue();                invalidate();            }        });        anim.setDuration(5000);        anim.start();    }}

布局文件

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    >    <com.yyy.shixi.propertyanimation.MyAnimView        android:layout_width="match_parent"        android:layout_height="match_parent" /></RelativeLayout>

效果图:
这里写图片描述

ObjectAnimator

ObjectAnimator是直接对任意对象的任意属性进行动画操作。属性动画要求动画作用的对象提供该属性的get和set方法,属性动画根据外界传递的初始值和结束值,多次去调用set方法,每次传递给set方法的值都不一样。对object的属性abc做动画,让动画生效必须同时满足两个条件:
1. object必须提供setAbc方法,如果动画没有传递初始值,那么还需要提供getAbc方法,因为系统要去取abc属性的初始值(这条不满足,直接crash)
2. object的setAbc对属性abc所做的改变必须能够通过某种方法反映出来,比如会带来UI的改变之类(这条不满足,动画无效果但不会crash)

如何为原始对象添加其他属性值get和set方法,完成其他类型的动画

  1. 自定义一个类继承原始对象,内部添加get和set方法去更改其他属性
  2. 用一个类来包装原始对象,通过构造函数把原始对象传进去,间接为其提供get和set方法去更改其他属性。

1.简单用法 ObjectAnimator.ofFloat(Object target, String propertyName, float… values)

在3s内设置buton透明度,从0f到1f,最后再到0.5f。

                /*ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f,0.5f);                anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {                    @Override                    public void onAnimationUpdate(ValueAnimator animation) {                        button.setAlpha((Float) animation.getAnimatedValue());                    }                });*/                ObjectAnimator anim = ObjectAnimator.ofFloat(button,"alpha",0f, 1f,0.5f);                anim.setDuration(3000);                anim.start();

效果图
这里写图片描述

代码注释掉的是ValueAnimator.ofFloat(float… values)用法,方便对比。 ObjectAnimator.ofFloat(Object target, String propertyName, float… values),第一个参数是操作的对象,第二个参数是对该对象的哪个属性进行操作。当第二个参数使用alpha、rotation、translationX和scaleY等这几个值,分别可以完成淡入淡出、旋转、水平移动、垂直缩放。我们可以传入任意的值到ofFloat()方法的第二个参数,因为ObjectAnimator在设计的时候就没有针对于View来进行设计,而是针对于任意对象的,它所负责的工作就是不断地向某个对象中的某个属性进行赋值,然后对象根据属性值的改变再来决定如何展现出来。

2.高级用法 ObjectAnimator.ofObject(Object target, String propertyName,TypeEvaluator evaluator, Object… values)

在5s的时间里,实现小球的自由落体

定义一个Point类,管理坐标,定义PointEvaluator,实现了TypeEvaluator接口并重写了evaluate()方法,告诉动画系统坐标如何从初始值过渡到到结束值,上面已经实现,代码省略。

ObjectAnimator内部的工作机制是通过寻找特定属性的get和set方法,然后通过方法不断地对值进行改变,从而实现动画效果的。因此我们就需要在MyAnimView中定义一个point属性,并提供它的get和set方法。

public class MyAnimView extends View{    public static final float RADIUS = 50f;    private Point currentPoint;    private Paint mPaint;    public MyAnimView(Context context, AttributeSet attrs) {        super(context, attrs);        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);        mPaint.setColor(Color.BLUE);    }    @Override    protected void onDraw(Canvas canvas) {        if (currentPoint == null) {            currentPoint = new Point(RADIUS, RADIUS);            drawCircle(canvas);            startAnimation();        } else {            drawCircle(canvas);        }    }    private void drawCircle(Canvas canvas) {        float x = currentPoint.getX();        float y = currentPoint.getY();        canvas.drawCircle(x, y, RADIUS, mPaint);    }    private void startAnimation() {        Point startPoint = new Point(RADIUS, RADIUS);        Point endPoint = new Point(RADIUS, getHeight() - RADIUS);        /*ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), startPoint, endPoint);        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {            @Override            public void onAnimationUpdate(ValueAnimator animation) {                currentPoint = (Point) animation.getAnimatedValue();                invalidate();            }        });*/        ObjectAnimator anim = ObjectAnimator.ofObject(this,"point",new PointEvaluator(),startPoint, endPoint);        anim.setDuration(5000);        anim.start();    }    public Point getPoint(){        return currentPoint;    }    public void setPoint(Point point){        this.currentPoint = point;        invalidate();    }}

代码注释掉的是ValueAnimator.ofObject(TypeEvaluator evaluator, Object… values)用法,方便对比。

效果图:
这里写图片描述

Interpolator

Interpolator中文翻译为插值器,控制动画变化的速率。属性动画新增了一个TimeInterpolator接口,这个接口是用于兼容之前的Interpolator的,这使得所有过去的Interpolator实现类都可以直接拿过来放到属性动画当中使用。使用属性动画时,系统默认的Interpolator其实就是一个先加速后减速的Interpolator,对应的实现类就是AccelerateDecelerateInterpolator。

Interpolator的系统值:

AccelerateDecelerateInterpolator 在动画开始与结束的地方速率改变比较慢,在中间的时候加速

AccelerateInterpolator 在动画开始的地方速率改变比较慢,然后开始加速

AnticipateInterpolator 开始的时候向后然后向前甩

AnticipateOvershootInterpolator 开始的时候向后然后向前甩一定值后返回最后的值

BounceInterpolator 动画结束的时候弹起

CycleInterpolator 动画循环播放特定的次数,速率改变沿着正弦曲线

DecelerateInterpolator 在动画开始的地方快然后慢

LinearInterpolator 以常量速率改变

OvershootInterpolator 向前甩一定值后再回到原来位置

例子:在上个例子模拟球体下落并会反复弹起

anim.setInterpolator(new BounceInterpolator());

效果图:
这里写图片描述

TimeInterpolator的接口定义,代码:

/** * A time interpolator defines the rate of change of an animation. This allows animations * to have non-linear motion, such as acceleration and deceleration. */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的值是由系统经过计算后传入到getInterpolation()方法中的,然后我们可以自己实现getInterpolation()方法中的算法,根据input的值来计算出一个返回值,而这个返回值就是fraction了。返回值可以大于1.0,也可以小于0。所以input的值决定了fraction的值。

系统中内置的LinearInterpolator,它是以以常量速率改变,代码:

/** * An interpolator where the rate of change is constant */@HasNativeInterpolatorpublic 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()方法中直接把参数中传递的input值返回,所以fraction的值等于input的值。求导为1,所以为匀速。

系统中内置的AccelerateDecelerateInterpolator,它是在动画开始与结束的地方速率改变比较慢,在中间的时候加速。即先加速后减速。代码:

/** * An interpolator where the rate of change starts and ends slowly but * accelerates through the middle. */@HasNativeInterpolatorpublic class AccelerateDecelerateInterpolator extends BaseInterpolator        implements NativeInterpolatorFactory {    public AccelerateDecelerateInterpolator() {    }    @SuppressWarnings({"UnusedDeclaration"})    public AccelerateDecelerateInterpolator(Context context, AttributeSet attrs) {    }    public float getInterpolation(float input) {        return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;    }    /** @hide */    @Override    public long createNativeInterpolator() {        return NativeInterpolatorFactoryHelper.createAccelerateDecelerateInterpolator();    }}

对 “(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f”求导 得“(-Math.PI*Math.sin((input + 1) * Math.PI) / 2.0f) + 0.5f”,求导函数在0到0.5是增函数,0.5到1是减函数,原函数“(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f”最终返回值还是在0到1之间

自己定义一个先加速后保持另一个速度匀速的AccelerateLinearInterpolator
代码:

public class AccelerateLinearInterpolator implements TimeInterpolator{    @Override    public float getInterpolation(float input) {        float result;        if (input <= 0.5) {            result = 2*input*input;        } else {            result = input;        }        return result;    }}

使用:

anim.setInterpolator(new DecelerateLinearInterpolator());

效果图:
这里写图片描述

Animator监听器

Animator类当中提供了一个addListener()方法,这个方法接收一个AnimatorListener。ObjectAnimator是继承自ValueAnimator的,而ValueAnimator又是继承自Animator的,AnimatorSet也是继承自Animator的,所以ValueAnimator,ObjectAnimator和AnimatorSet都可以使用addListener()这个方法。

添加一个监视器的代码

                animSet.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) {                    }                });

只想监听动画结束这个事件,用AnimatorListenerAdapter类,就不用四个接口全部实现一遍。

                animSet.addListener(new AnimatorListenerAdapter() {                    @Override                    public void onAnimationEnd(Animator animation) {                        super.onAnimationEnd(animation);                    }                });

组合动画

实现组合动画功能主要需要借助AnimatorSet这个类,这个类提供了一个play()方法,如果我们向这个方法中传入一个Animator对象(ValueAnimator或ObjectAnimator)将会返回一个AnimatorSet.Builder的实例,AnimatorSet.Builder中包括以下四个方法:

after(Animator anim) 将现有动画插入到传入的动画之后执行
after(long delay) 将现有动画延迟指定毫秒后执行
before(Animator anim) 将现有动画插入到传入的动画之前执行
with(Animator anim) 将现有动画和传入的动画同时执行

例子:让button在5s内先开始旋转360度,然后屏幕水平来回移动一次,移动同时淡入淡出

                ObjectAnimator moveInOut = ObjectAnimator.ofFloat(button, "translationX", 0f, 200f,0f);                ObjectAnimator rotate = ObjectAnimator.ofFloat(button, "rotation", 0f, 360f);                ObjectAnimator fadeInOut = ObjectAnimator.ofFloat(button, "alpha", 1f, 0f, 1f);                AnimatorSet animSet = new AnimatorSet();                animSet.play(moveInOut).with(fadeInOut).after(rotate);                animSet.setDuration(5000);                animSet.start();

这里写图片描述

使用xml编写动画

在res目录下新建animator文件夹,所有属性动画的XML文件都应该存放在这个文件夹当中。

在XML文件中我们一共可以使用如下三种标签:

animator 对应代码的ValueAnimator
objectAnimator 对应代码的ObjectAnimator
set 对应代码的AnimstorSet

例子:button在2s内,透明度逐渐变到0.2
res\animator\alpha.xml

<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"    android:valueFrom="1"    android:valueTo="0.2"    android:duration="2000"    android:valueType="floatType"    android:propertyName="alpha" />

代码加载

                Animator animator = AnimatorInflater.loadAnimator(MainActivity.this, R.animator.alpha);                animator.setTarget(button);                animator.start();

例子:让button在5s内先开始旋转360度,然后屏幕水平来回移动一次,移动同时淡入淡出

代码

<set xmlns:android="http://schemas.android.com/apk/res/android"    android:ordering="sequentially" >    <objectAnimator        android:duration="2000"        android:propertyName="rotation"        android:valueFrom="0"        android:valueTo="360"        android:valueType="floatType" >    </objectAnimator>    <set android:ordering="together" >        <set android:ordering="sequentially" >            <objectAnimator                android:duration="1500"                android:propertyName="translationX"                android:valueFrom="0"                android:valueTo="200"                android:valueType="floatType" >            </objectAnimator>            <objectAnimator                android:duration="1500"                android:propertyName="translationX"                android:valueFrom="200"                android:valueTo="0"                android:valueType="floatType" >            </objectAnimator>        </set>        <set android:ordering="sequentially" >            <objectAnimator                android:duration="1500"                android:propertyName="alpha"                android:valueFrom="1"                android:valueTo="0"                android:valueType="floatType" >            </objectAnimator>            <objectAnimator                android:duration="1500"                android:propertyName="alpha"                android:valueFrom="0"                android:valueTo="1"                android:valueType="floatType" >            </objectAnimator>        </set>    </set></set>

效果图

这里写图片描述

参考:

郭霖 Android属性动画完全解析

Android开发艺术探索

0 0