Android 属性动画(一)

版权声明:原创文章,转载请注明 https://blog.csdn.net/qq_37482202/article/details/82949586

属性动画是在 Android 3.0后才提供的一种全新动画模式,那么为什么要提供属性动画呢?或者说属性动画的优势在哪里呢?

帧动画:

由于逐帧动画的帧序列内容不一样,不仅增加制作负担而且最终输出的文件量也很大。

补间动画:

1.只能够作用在视图View上,即只可以对一个ButtonTextView、甚至是LinearLayout、或者其它继承自View的组件进行动画操作,但无法对非View的对象进行动画操作

2.不会改变View的属性,只是改变视觉效果(比如说视觉上一个按钮从左边移动到了右边,但是他的点击监听却还是在左边)

属性动画不但可以作用在任何对象上,甚至没有对象也可以,而且动画效果多种多样

属性动画最重要的类有两个,一个是ValueAnimator,一个是ObjectAnimator类,我们先说ValueAnimator

 ValueAnimator类

动画到底是如何执行的呢?看图:

图比文字更容易理解,简单来说就是按照一个函数不断地变化要执行动画的对象的属性值,在其中我们可以看到,动画之所以可以执行还是依靠一个核心类ValueAnimator,最重要的三个方法:

ofInt  按照整数来改变指定对象的属性值

ofFloat  按照浮点数来改变指定对象的属性值

offObject  按照给定的属性值名称来改变指定对象的给定属性值

操作值的方式可以使用xml或者java设置,不过开发中经常使用java动态设置,因为设置时可能开始只和结束值需要传参数进来设置,下面是使用ofInt的示例,offFloat的用法类似

        // 步骤1:设置动画属性的初始值和结束值
        ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 3);
        // ofInt()作用有两个
        // 1. 创建动画实例
        // 2. 将传入的多个Int参数进行平滑过渡:此处传入0和1,表示将值从0平滑过渡到1
        // 如果传入了3个Int参数 a,b,c ,则是先从a平滑过渡到b,再从b平滑过渡到C,以此类推
        // ValueAnimator.ofInt()内置了整型估值器,直接采用默认的.不需要设置,即默认设置了如何从初始值 过渡到 结束值
        // 步骤2:设置动画的播放各种属性
        valueAnimator.setDuration(500);
        // 设置动画运行的时长
        valueAnimator.setStartDelay(500);
        // 设置动画延迟播放时间
        valueAnimator.setRepeatCount(0);
        // 设置动画重复播放次数 = 重放次数+1,默认为0
        // 动画播放次数 = infinite时,动画无限重复
        valueAnimator.setRepeatMode(ValueAnimator.RESTART);
        // 设置重复播放动画模式
        // ValueAnimator.RESTART(默认):正序重放
        // ValueAnimator.REVERSE:倒序回放
        // 步骤3:将改变的值手动赋值给对象的属性值:通过动画的更新监听器
        // 设置值的更新监听器
        // 即:值每次改变、变化一次,该方法就会被调用一次
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int currentValue = (int) animation.getAnimatedValue();
                // 获得改变后的值
                Log.e("当前值:",currentValue+"");
                // 输出改变后的值
                // 步骤4:将改变后的值赋给对象的属性值
                // View.setproperty(currentValue);
                // 步骤5:刷新视图,即重新绘制,从而实现动画效果
                //View.requestLayout();
            }
        });
        valueAnimator.start();
        // 启动动画

ofInt运行结果:

ofFloat运行结果:

  xml设置:

<animator
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:valueFrom="0"  //初始值
    android:valueTo="100"  //结束值
    android:valueType="intType"  //变化值类型
    android:duration="2000"  //持续时间
    android:startOffset="500"  //延迟时间
    android:fillBefore="true"  //动画执行之后是否停留在初始状态,默认为true
    android:fillAfter="false"  //动画执行之后是否停留在结束状态,默认为false,优先于fillBefore
    android:fillEnabled="true"  //是否应用fillBefore属性,默认为true
    android:repeatMode="restart"  // 选择重复播放动画模式,restart代表正序重放,reverse代表倒序回放,默认为restart
    android:repeatCount="0"
    />

java设置

Animator animator = AnimatorInflater.loadAnimator(MainActivity.this, R.animator.set_animator);
        // 载入XML动画
        animator.setTarget(view);
        // 设置动画对象
        animator.start();
        // 启动动画

让我们结合控件来尝试一下

 private class ValueAnimatorButtonListener implements View.OnClickListener {
        @Override
        public void onClick(final View v) {

            ValueAnimator valueAnimator = ValueAnimator.ofInt(v.getLayoutParams().width, v.getLayoutParams().width+500);
            valueAnimator.setDuration(2000);
            valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    int currentValue = (Integer) animation.getAnimatedValue();
                    Log.e("当前值:",currentValue+"");
                    v.getLayoutParams().width=currentValue;
                    v.requestLayout();
                }
            });
            valueAnimator.start();
        }
    }

我们知道动画的执行是和值的变化相关的,那么这些值是如何变化的呢?这就涉及到了估值器,TypeEvaluator,系统内置的两种估值器

    // type evaluators for the primitive types handled by this implementation
    private static final TypeEvaluator sIntEvaluator = new IntEvaluator();
    private static final TypeEvaluator sFloatEvaluator = new FloatEvaluator();

 如果你不设置自己的估值器,系统便会在init的时候判断你的值并为你添加相应的默认估值器

    /**
     * Internal function, called by ValueAnimator, to set up the TypeEvaluator that will be used
     * to calculate animated values.
     */
    void init() {
        if (mEvaluator == null) {
            // We already handle int and float automatically, but not their Object
            // equivalents
            mEvaluator = (mValueType == Integer.class) ? sIntEvaluator :
                    (mValueType == Float.class) ? sFloatEvaluator :
                    null;
        }
        if (mEvaluator != null) {
            // KeyframeSet knows how to evaluate the common types - only give it a custom
            // evaluator if one has been set on this class
            mKeyframes.setEvaluator(mEvaluator);
        }
    }

让我们来看看这两种估值器

整数类型:

public class IntEvaluator implements TypeEvaluator {   
    public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
        int startInt = startValue;
        return (int)(startInt + fraction * (endValue - startInt));
    }
}

浮点类型:

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

大体的思路是这样的先算出来初始值和结束值的差值,乘上一个系数再加上初始值,得到当前值

那就让我们自己写一个估值器来执行动画

private class PointEvaluatorButtonListener implements View.OnClickListener {
        @Override
        public void onClick(final View v) {
            Point startPoint=new Point(v.getX(),v.getY());
            Point endPoint=new Point(v.getX(),v.getY()-500);
            ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), startPoint, endPoint);
            anim.setDuration(2000);
            anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    Point currentPoint=(Point)animation.getAnimatedValue();
                    Log.e("当前坐标",currentPoint.getX()+"  "+currentPoint.getY());
                    v.setX(currentPoint.getX());
                    v.setY(currentPoint.getY());
                    v.requestLayout();
                }
            });
            anim.start();

        }
    }


 
private class PointEvaluator implements TypeEvaluator{

        @Override
        public Object evaluate(float fraction, Object startValue, Object endValue) {
            // 将动画初始值startValue和动画结束值endValue 强制类型转换成Point对象
            Point startPoint = (Point) startValue;
            Point endPoint = (Point) endValue;
            // 根据fraction来计算当前动画的x和y的值
            float x = startPoint.getX() + fraction * (endPoint.getX() - startPoint.getX());
            float y = startPoint.getY() + fraction * (endPoint.getY() - startPoint.getY());
            // 将计算后的坐标封装到一个新的Point对象中并返回
            Point point = new Point(x, y);
            return point;
        }
    }

 

我们还可以将其应用到自定义视图当中:

public class MyView extends View {

    private Point leftTop;//矩形左上角点
    private Point rightDown;//矩形右下角点
    private Paint mPaint;// 绘图画笔
    private ValueAnimator anim;

    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
        // 初始化画笔
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(Color.BLUE);
    }


    @Override
    protected void onDraw(final Canvas canvas) {
        // 如果当前点坐标为空(即第一次)
        if (leftTop == null) {
            leftTop = new Point(0, 0);
            rightDown=new Point(canvas.getWidth()/2,canvas.getHeight());

            canvas.drawRect(leftTop.getX(),leftTop.getY(),rightDown.getX(),rightDown.getY(),mPaint);

            // (重点关注)将属性动画作用到View中
            // 步骤1:创建初始动画时的对象点  & 结束动画时的对象点
            Point startPoint = leftTop;
            Point endPoint = new Point(canvas.getWidth()/2, 0);

            // 步骤2:创建动画对象 & 设置参数
            anim = ValueAnimator.ofObject(new AnimationActivity.PointEvaluator(), startPoint, endPoint);
            // 参数说明
            // 参数1:TypeEvaluator 类型参数 - 使用自定义的PointEvaluator(实现了TypeEvaluator接口)
            // 参数2:初始动画的对象点
            // 参数3:结束动画的对象点

            anim.setDuration(2000);
            // 设置动画时长

            // 设置 属性值的更新监听器
            // 即每当坐标值(Point值)更新一次,该方法就会被调用一次
            anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    leftTop = (Point) animation.getAnimatedValue();
                    rightDown.setX(leftTop.getX()+canvas.getWidth()/2);
                    // 返回当前值到当前坐标值(currentPoint)
                    // 从而更新当前坐标值(currentPoint)
                    invalidate();
                    // 调用invalidate()后,就会刷新View,即才能看到重新绘制的界面,即onDraw()会被重新调用一次
                    // 所以坐标值每改变一次,就会调用onDraw()一次
                }
            });

        } else {
            // 如果坐标值不为0,则画矩形
            canvas.drawRect(leftTop.getX(), leftTop.getY(), rightDown.getX(),rightDown.getY(), mPaint);
        }
    }

    public void play(){
        anim.start();
        // 启动动画
    }


}

 

ObjectAnimator

 我们之前说属性动画可以控制很多属性,包括大小,位置,颜色等等属性,只要你在对应的对象中存在get和set方法,那么就都可以控制,get方法也只是为了初始化,如果已经设定好初始值,那么只需要set方法即可

它的使用方法和前面讲的大体相似,如果想控制较为复杂的属性就需要自己多写点代码了,我这里放一个简单的小例子

public class MyView2 extends View {

    private Paint mPaint;
    private float radius;

    public float getRadius() {
        return radius;
    }

    public void setRadius(float radius) {
        this.radius = radius;
        invalidate();
    }

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

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawCircle(canvas.getWidth()/2,canvas.getHeight()/2,radius, mPaint);
    }
}
 private class MyView2Listener implements View.OnClickListener {
        @Override
        public void onClick(View v) {
            final ObjectAnimator anim = ObjectAnimator.ofObject((MyView2)v, "radius", new RadiusEvaluator(),
                    0.0f, 200.0f);
            anim.setDuration(5000);
            anim.start();
        }
    }

    private class RadiusEvaluator implements TypeEvaluator {
        @Override
        public Object evaluate(float fraction, Object startValue, Object endValue) {
            float startRadius=(float)startValue;
            float endRadius=(float)endValue;
            float currentRadius=startRadius+fraction*(endRadius-startRadius);
            return currentRadius;
        }
    }

ofObject方法第一个参数是要执行动画的对象,不局限view对象,第二个是要改变属性的名字,会根据这个名字找对应的get和set方法,第三个是估值器,第四个和第五个参数分别是开始至和结束值

未完待续。。。

猜你喜欢

转载自blog.csdn.net/qq_37482202/article/details/82949586