Android 动画之属性动画ValueAnimator和ObjectAnimator

简介

属性动画是API 11新加入的特性,和View动画不同,它对作用对象进行了扩展,属性动画可以对任意对象做动画,也不像View动画只支持四种简单的变化。

属性动画的默认时间是300ms,默认频率是10ms/帧

ObjectAniamtor

先看看一个简单例子的实现效果,使用ObjectAnimator,它使用起来比较简单


布局就是一个ImageView,看看实现代码

 //透明度变化动画
    public void alpha(View view) {
//        1、通过调用ofFloat()方法创建ObjectAnimator对象,并设置目标对象、需要改变的目标属性名、初始值和结束值;
        ObjectAnimator mAnimatorAlpha = ObjectAnimator.ofFloat(mImageView, "alpha", 1.0f, 0.0f);
        //2、设置动画的持续时间、是否重复及重复次数属性;
        mAnimatorAlpha.setRepeatMode(Animation.REVERSE);
        mAnimatorAlpha.setRepeatCount(1);
        mAnimatorAlpha.setDuration(1000);
        //3、启动动画
        mAnimatorAlpha.start();
    }
    
    //翻转动画,翻转360度
    public void flip(View view) {
        ObjectAnimator visibleToInVisable = ObjectAnimator.ofFloat(mImageView, "rotationX", 0.0f, 360.0f);
        //设置插值器
        visibleToInVisable.setInterpolator(new LinearInterpolator());
        visibleToInVisable.setDuration(1000);
        visibleToInVisable.start();
    }
    //缩放动画
    public void scale(View view) {
        Animator animator = AnimatorInflater.loadAnimator(this, R.animator.scale_anim);
        animator.setTarget(mImageView);
        animator.start();
    }
    
    //平移动画
    public void translate(View view) {
        ObjectAnimator mAnimatorTranslateX = ObjectAnimator.ofFloat(mImageView, "translationX", 0.0f, screenWidth / 2);
        mAnimatorTranslateX.setRepeatMode(Animation.REVERSE);
        mAnimatorTranslateX.setRepeatCount(1);
        mAnimatorTranslateX.setDuration(1000);

        ObjectAnimator mAnimatorTranslateY = ObjectAnimator.ofFloat(mImageView, "translationY", 0.0f, screenHeight / 2);
        mAnimatorTranslateY.setRepeatMode(Animation.REVERSE);
        mAnimatorTranslateY.setRepeatCount(1);
        mAnimatorTranslateY.setDuration(1000);

        mAnimatorTranslateX.start();
        mAnimatorTranslateY.start();
    }
    //旋转动画
    public void rotate(View view) {
        ObjectAnimator mAnimatorRotate = ObjectAnimator.ofFloat(mImageView, "rotation", 0.0f, 360.0f);
        mAnimatorRotate.setRepeatMode(Animation.REVERSE);
        mAnimatorRotate.setRepeatCount(1);
        mAnimatorRotate.setDuration(2000);

        mAnimatorRotate.start();
    }


就是根据ObjectAnimator的ofFloat(Object target, String propertyName, float... values)构造得到一个ObjectAnimator,可以看到第一个参数是Object类型的,意味着我们可以传入自定义的类型,而不局限于补间动画只能用view来做,扩展性也就更好。第二个参数是动画名称,可以传递系统定义好的,比如上面的几个,也可以自己随便写,然后在属性变化的监听里改变。第三个参数就是属性值,如果只有一个的话会默认为结束值。

上面缩放动画是用xml实现的,需要在res下面建一个animator文件夹,然后创建相应的xml动画,如下



    


组合动画

现在要多个动画效果一起实现怎么办,有下面几种方式

1.PropertyValuesHolder

 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0.0f, 1.0f);
        PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f, 0.0f);

        ObjectAnimator animator1 = ObjectAnimator.ofPropertyValuesHolder(mImageView, alpha, scaleX);
        animator1.setDuration(1000);
        animator1.start();
2.监听属性变化

 ObjectAnimator animator = ObjectAnimator.ofFloat(mImageView, "lzy", 0.0f, 1.0f);
        animator.setDuration(1000);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float value = (float) animation.getAnimatedValue();
                mImageView.setAlpha(value);
                mImageView.setScaleX(1 - value * 0.5f);
            }
        });
        animator.start();
设置属性变化值在0-1之间,然后在onAnimationUpdate中监听得到,通过这个值调用view自身的方法改变。

3.AnimatorSet

AnimatorSet mAnimatorSet = new AnimatorSet();

        ObjectAnimator mAnimatorSetRotateX = ObjectAnimator.ofFloat(mImageView, "rotationX", 0.0f, 360.0f);
        mAnimatorSetRotateX.setDuration(3000);

        ObjectAnimator mAnimatorSetRotateY = ObjectAnimator.ofFloat(mImageView, "rotationY", 0.0f, 360.0f);
        mAnimatorSetRotateY.setDuration(3000);

        ObjectAnimator mAnimatorScaleX = ObjectAnimator.ofFloat(mImageView, "scaleX", 1.0f, 0.2f);
        mAnimatorScaleX.setRepeatCount(1);
        mAnimatorScaleX.setRepeatMode(Animation.REVERSE);
        mAnimatorScaleX.setDuration(1500);

        ObjectAnimator mAnimatorScaleY = ObjectAnimator.ofFloat(mImageView, "scaleY", 1.0f, 0.2f);
        mAnimatorScaleY.setRepeatCount(1);
        mAnimatorScaleY.setRepeatMode(Animation.REVERSE);
        mAnimatorScaleY.setDuration(1500);

        ObjectAnimator mAnimatorScaleY2 = ObjectAnimator.ofFloat(mImageView, "scaleY", 1.0f, 0.2f);
        mAnimatorScaleY2.setRepeatCount(1);
        mAnimatorScaleY2.setRepeatMode(Animation.REVERSE);
        mAnimatorScaleY2.setDuration(1500);

        mAnimatorSet.play(mAnimatorSetRotateY)
                .with(mAnimatorScaleX)
                .with(mAnimatorScaleY)
                .before(mAnimatorSetRotateX).before(mAnimatorScaleY2);

        mAnimatorSet.start();
通过animationSet来实现,它提供了一个play()方法,传入Animator返回一个Builder,这个Builder中有以下四个方法

    with(Animator anim),表示同时执行

    before(Animator anim),表示将现有动画插入到传入动画之前执行,也就是后执行

    after(Animator anim),与before相反

    after(long delay),表示延迟多久执行

在测试的时候发现不能重复传入一个动画,所以又写了一个mAnimatorScaleY2 。

AnimationSet中还有playTogether(Animator... items)表示同时执行,playSequentially(Animator... items)表示异步执行。


下面做一个让Button增加宽度的动画效果

属性动画的大致原理:属性动画要求动画作用的对象提供该属性的get和set方法,就像上面的"scaleX"属性,它具有getScaleX和setScale方法,属性动画会根据传递的初始值和结束值,去调用set的方法设置当前的值,如果没有传递初始值会调用get方法去获取初始值。

然后Button中并没有我们想要的get和set方法,所以针对这种问题官方文档给出了下面的解决方法

1.如果有权限的话,给对象加上get和set方法

2.用一个类来包装原始对象,间接为其提供get和set方法

3.采用ValueAnimator,监听动画变化过程,自己实现属性的变化

我们现在以第二种方式来实现以上效果,代码如下

 mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ViewWrapper wrapper = new ViewWrapper(mButton);
                ObjectAnimator.ofInt(wrapper, "width", 1000).setDuration(1000).start();
            }
        });

 //用此类来封装View,间接提供get、set方法
    private static class ViewWrapper {
        private View mView;

        public ViewWrapper(View view) {
            mView = view;
        }

        public void setWidth(int width) {
            mView.getLayoutParams().width = width;
            mView.requestLayout();
        }

        public int getWidth() {
            return mView.getLayoutParams().width;
        }
    }
在这个类中提供了width的get和set方法,getWidth()就是获取View当前的宽度,上面说过在没有指定初始值时会调用获得初始值,而setWidth()方法会不断被调用,直至动画结束去改变View的宽度。

ValueAnimator

ValueAnimator是ObjectAnimator的父类,也就可以理解为ObjectAnimator是一个封装好的ValueAnimator,使其使用起来更加的简单。但是作为父类的ValueAnimator也就使用起来更加的灵活多变。ValueAinamtor本身不会作用于任何对象,直接使用不会有动画效果,可以理解为它对一个值做动画,我们通过这个不断变化的值在监听函数中去修改相应的属性。

看看ValueAnimator的几种构造函数:

ofInt(int... values) 

ofArgb(int... values)

ofFloat(float... values)

ofPropertyValuesHolder(PropertyValuesHolder... values)

ofObject(TypeEvaluator evaluator, Object... values)

前三种参数都是初始值到结束值的变化范围,很简单

ofPropertyValuesHolder代表多种Animator的集合,下面会介绍具体的使用

ofObject很明显它的参数类型是Object类型,大多是我们自定义的类型,第一个参数TypeEvaluator 是估值器,因为像上面的int之类的系统都有定义相应的估值器所以不需要我们传入,然后我们自定义的Object并没有,所以需要自己的来写。

来看看系统的IntEvaluator

public class IntEvaluator implements TypeEvaluator {

   
    public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
        int startInt = startValue;
        return (int)(startInt + fraction * (endValue - startInt));
    }
}
估值器都需要继承自TypeEvaluator,实现里面的evaluate方法,参数分别是完成的百分比,开始值和结束值,文档解释也告诉我们返回的值只需要用结束值减去开始值乘以fraction 然后加上开始值就ok,从而实现值的过渡,所以自定义TypeEvaluator也很简单了。

下面看例子,还是先看效果在撸代码


 private void marginValueAnimator() {
        ValueAnimator valueAnimator = ValueAnimator.ofInt(0, width - mImageView.getWidth());
        //监听变化过程
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                //获取当前值
                int animatedValueMargin = (int) animation.getAnimatedValue();
                ViewGroup.MarginLayoutParams
                        layoutParams = (ViewGroup.MarginLayoutParams) mImageView.getLayoutParams();
                layoutParams.leftMargin = animatedValueMargin;
                mImageView.setLayoutParams(layoutParams);

            }
        });
        valueAnimator.setDuration(1000);
        valueAnimator.setRepeatCount(3);
        valueAnimator.setRepeatMode(ValueAnimator.REVERSE);

        valueAnimator.setTarget(mImageView);
        valueAnimator.start();
    }
这是个简单的设置view距离左边的动画,构造了ValueAnimator后监听它的更新,在onAnimationUpdate中改变属性值,通过animation.getAnimatedValue()获得当前的值,然后用于更新属性状态,实现动画。

public void scaleValueAnimator() {
        //1.设置目标属性名及属性变化的初始值和结束值
        PropertyValuesHolder mPropertyValuesHolderScaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f, 0.0f);
        PropertyValuesHolder mPropertyValuesHolderScaleY = PropertyValuesHolder.ofFloat("scaleY", 1.0f, 0.0f);
        ValueAnimator mAnimator = ValueAnimator.ofPropertyValuesHolder(mPropertyValuesHolderScaleX, mPropertyValuesHolderScaleY);
        //2.为目标对象的属性变化设置监听器
        mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                // 3.根据属性名获取属性变化的值分别为ImageView目标对象设置X和Y轴的缩放值
                float animatorValueScaleX = (float) animation.getAnimatedValue("scaleX");
                float animatorValueScaleY = (float) animation.getAnimatedValue("scaleY");
                mImageView.setScaleX(animatorValueScaleX);
                mImageView.setScaleY(animatorValueScaleY);

            }
        });
        //4.为ValueAnimator设置自定义的Interpolator
        mAnimator.setInterpolator(new CustomInterpolator());
        //5.设置动画的持续时间、是否重复及重复次数等属性
        mAnimator.setDuration(2000);
        mAnimator.setRepeatCount(3);
        mAnimator.setRepeatMode(ValueAnimator.REVERSE);
        //6.为ValueAnimator设置目标对象并开始执行动画
        mAnimator.setTarget(mImageView);
        mAnimator.start();
    }
这里通过PropertyValuesHolder来控制实现两种属性的变化,其实这里x和y两个方向的缩放值都是1.0f到0.0f,可以用一个ValueAnimator.ofFloat()来完成,这里主要是为了演示
ofPropertyValuesHolder的使用。

首先是要通过PropertyValuesHolder.ofFloat来创建了两个PropertyValuesHolder对象,其中第一个参数是属性名,可以任意取,可以看到在onAnimationUpdate中通过这个属性名来获取不同PropertyValuesHolder的变化值。然后再通过ValueAnimator.ofPropertyValuesHolder来构造ValueAnimator对象就可以,也很容易看懂。

上面那个类似抛物线的圆是采用ValueAnimator.ofObject来实现的。

1.首先要定义一个Point类,里面用于存放x、y坐标

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 void setX(int x) {
        this.x = x;
    }

    public float getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }
}
之前说过要使用ValueAnimator.ofObject需要自定义TypeEvaluator,下面看看定义的PointEvaluator,可以看到和IntEvaluator一样,主要还是计算变化值

public class PointEvaluator implements TypeEvaluator {

    //TypeEvaluator简单来说是实现了初始值和结束值的平滑过渡

    @Override
    public Object evaluate(float fraction, Object startValue, Object endValue) {
        Point start = (Point) startValue;
        Point end = (Point) endValue;
        //开始值减去结束值乘以fraction再加上开始值就是当前的值
        float x = start.getX() + fraction * (end.getX() - start.getX());
        float y = start.getY() + fraction * (end.getY() - start.getY());
        Point point = new Point(x, y);
        return point;
    }
下面自定义一个CircleView类
public class CircleView extends View {

    private static final float Radius = 40.0f;
    private Point mPoint;
    private Paint mPaint;


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

    public CircleView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mPaint = new Paint();
        mPaint.setColor(Color.RED);
//        mPoint = new Point(50, 50);
    }

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

    @Override
    protected void onDraw(Canvas canvas) {
        if (mPoint == null) {
            mPoint = new Point(50, 50);
            canvas.drawCircle(mPoint.getX(), mPoint.getY(), Radius, mPaint);
            startAnimation();
        } else {
            canvas.drawCircle(mPoint.getX(), mPoint.getY(), Radius, mPaint);
        }
    }

    private void startAnimation() {
        //创建开始和结束点坐标
        Point start = new Point(50, 50);
        Point end = new Point(getWidth(), getHeight());
        ValueAnimator animator = ValueAnimator.ofObject(new PointEvaluator(), start, end);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                //把每一帧的值传给mPoint,绘制界面
                mPoint = (Point) animation.getAnimatedValue();
                invalidate();
            }
        });
        animator.setDuration(5000);
        animator.start();
    }
}
这个类也比较简单,主要就是在onDraw里面画一个圆,然后根据Point的变化不断调用onDraw方法来改变圆的位置,实现动画,这也就是ValueAnimator.ofObject的用法。

源码下载地址


你可能感兴趣的:(Android,基础)