属性动画之 ValueAnimator

在将属性动画之前,我们先看一个例子:

public class MainActivity extends Activity {
    private Button button;
    private Animation animation;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        button = findViewById(R.id.button);
        animation = new TranslateAnimation(0, 0, 0, 320);
        animation.setDuration(3000);
        animation.setFillAfter(true);
        button.startAnimation(animation);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d("TTT", "按钮被点击");
            }
        });
    }
}

代码很简单,只是简单的移动一个按钮,最后按钮的位置保持在动画结束的地方。

这个时候我们去点击按钮,就会发现并不会触发按钮上的监听器,但是点击按钮原来的位置,则会触发。

所以可以看出来,补间动画只是单纯的改变的一下(设置动画组件)的外观,其本质还是没有改变(不知道这么说是否合适,领会精神即可),因为还是只可以在原来位置点击,Android 提供了另外一种动画:属性动画:PropertyAnimation,这种动画不光可以给 View 设置,它可以给任何对象设置,并且不单单是改变组件的显示效果,而可以直接修改其属性。

按照官方的说法是,属性动画可以在一个事件段内,改变一个对象的属性值,从而定义一个动画,而不用关心该对象是否需要在屏幕上绘制

我们需要先了解 PropertyAnimation 的几个概念:

  • Animator:创建属性动画的基类,一般使用它的两个子类,或者在自定义属性动画时候让我们的类继承它。

  • ValueAnimator:属性动画主要的时间引擎,它负责计算各个帧的属性值。它定义了属性动画的绝大部分核心功能,包括计算各帧的相关属性值,负责处理更新事件,按属性值的类型控制计算规则。

    属性动画主要由两个方面组成

    1. 计算各帧的相关属性值
    2. 为指定对象设置这些计算后的值。

ValueAnimator 值负责第一方面的内容,因此程序员必须根据 ValueAnimator 计算并监听值更新来更新对象的相关属性值。

  • ObjectAnimator:Animator 的子类,允许程序员对指定对象的属性执行动画。在实际应用中,ObjectAnimator 使用起来更加简单,因此更加常用。在少数场景下,由于 ObjectAnimator 存在一些限制,可能需要考虑使用 ValueAnimator。

  • AnimatorSet:Animator 的子类,用于组合多个 Animator,并指定多个 Animator 是按次序播放,还是同时播放。

除此之外,属性动画还需要利用 Evaluator(计算器),该工具类控制属性动画如何计算属性值。Android 提供了如下 Evaluator。

  • IntEvaluator:用于计算int类型属性值的计算器

  • FloatEvaluator:用于计算float类型属性值的计算器

  • ArgbEvaluator:用于计算以十六进制形式表示的颜色值的计算器

  • TypeEvaluator:它是计算器接口,开发者可以通过实现该接口来实现自定义计算器。如果需要对 int、float 或颜色值以外类型的属性执行属性动画,可能需要实现 TypeEvaluator 接口来实现自定义计算器。

ValueAnimator

上面已经说了,属性动画是通过直接修改对象的属性,通过实现动画效果的,例如我们在 10 秒之内,逐渐的修改一张图片的宽度和高度,提现出来的效果自然就是图片慢慢缩小的动画效果。

所以说,有一定规律的属性值就是重中之重了,属性动画中,ValueAnimator 可以帮助我们获取到这些逐渐改变的值。

这里是 ValueAnimator 对象的常用方法:

  • 获取 ValueAnimator 对象的方法:

    • ofInt (int... values)
    • ofFloat (float... values)
    • ofObject (TypeEvaluator evaluator, Object... values)
    • ofArgb(int... values)
    • ofPropertyValuesHolder(PropertyValuesHolder... values)
      > 可以看到,上面五个方法都是可变长参数,以 ofInt 方法为例,假如我们将其参数设为(1, 10),那么其值的计算范围就是:1...2...3.......10;如果其参数为(1, 5, 1),那么其值的计算范围为:1...2...3...4...5...4...3...2...1,明白了吧。
  • 设置动画属性的方法:

    • setCurrentFraction(float fraction):设置当前时间因子。即时间到达的百分比。
    • setDuration (long duration):设置动画总时长,单位毫秒。
    • setCurrentPlayTime (long playTime):设置当前的时间,取值为 0 - duration,单位毫秒。
    • setFrameDelay (long frameDelay):设置每一帧之间间隔多少毫秒。
    • setInterpolator (TimeInterpolator value):设置动画的Interpolator,和 ViewAnimation 的 Interpolator 通用。
    • setRepeatCount(int value):设置重复次数。
    • setRepeatMode(int value):设置重复模式。有RESTART和REVERSE两种。
    • setStartDelay(long startDelay):设置开始前延迟毫秒数。
    • setEvaluator(TypeEvaluator value):设置求值器。
    • setFloatValues(float... values):设置Float型变化值,一般设置初始值和结束值,当然你也可以设置中间值,因为这是一个可变参数,长度可变。
    • setIntValues(int... values):设置Int型变化值,一般设置初始值和结束值,当然你也可以设置中间值,因为这是一个可变参数,长度可变。
    • setObjectValues(Object... values):设置Object型变化值,一般设置初始值和结束值,当然你也可以设置中间值,因为这是一个可变参数,长度可变。
  • 获取动画属性的方法:

    • getAnimatedFraction():获取当前时间因子。即时间到达的百分比。
    • getCurrentPlayTime ():获取当前的时间,单位毫秒。
    • getDuration ():获取动画总时长,单位毫秒。
    • getFrameDelay ():获取每一帧之间间隔多少毫秒。
    • getInterpolator ():获取当前使用的插值器。
    • getRepeatCount():获取重复次数。
    • getRepeatMode():获取重复模式。
    • getStartDelay():获取开始前延迟毫秒数。
    • getAnimatedValue():获取计算出来的当前属性值。
    • getAnimatedValue(String propertyName):获取计算出来的当前某个属性的值。
  • 其他方法:

    • void addUpdateListener(AnimatorUpdateListener listener):添加动画状态监听器。重写动画开始、结束、取消、重复四个方法,监听不同状态。
    • cancel (): 取消动画。
    • end ():让动画到达最后一帧。
    • start():开始动画。
    • pause():暂停动画。
    • resume():继续动画。
    • reverse ():反向播放动画。
    • isRunning():是否在运行中。
    • isStarted():是否已经开始。

那我们来一个例子,演示一下 ValueAnimator 的使用:

public class MainActivity extends Activity {

    private Button button;

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        button = findViewById(R.id.button);


        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ValueAnimator animator = ValueAnimator.ofInt(button.getWidth(), getWindowManager().getDefaultDisplay().getWidth());
                animator.setDuration(500);
                animator.setStartDelay(1000);
                animator.start();

                animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        int values = (int) animation.getAnimatedValue();
                        Log.d("TTT", "values = " + values);
                        button.setWidth(values);
                    }
                });
            }
        });
    }
}

可以看到,点击按钮之后,按钮会在半秒之内,宽度 match_parent 效果。

利用 View 的属性,实现动画效果

在上一个例子当中,我们通过调用 Button 的 setWidth 方法改变了按钮的宽度,Android 当中的 View 有一系列方法可以改变他的外观:

  • View.layout:改变view的位置
  • View.setScaleX:水平方向缩放比例
  • View.setScaleY:垂直方向缩放比例
  • View.setRotation:旋转度数
  • View.setAlpha:视图透明度,值在0-1之间。0为完全透明,1为完全不透明

我们动态的修改 View 的上述方法,不就可以实现动画效果了么。

转一个网上的例子:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private Button btn_one;
    private Button btn_two;
    private Button btn_three;
    private Button btn_four;
    private LinearLayout ly_root;
    private ImageView img_babi;
    private int width;
    private int height;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        bindViews();
    }

    private void bindViews() {
        ly_root = (LinearLayout) findViewById(R.id.ly_root);
        btn_one = (Button) findViewById(R.id.btn_one);
        btn_two = (Button) findViewById(R.id.btn_two);
        btn_three = (Button) findViewById(R.id.btn_three);
        btn_four = (Button) findViewById(R.id.btn_four);
        img_babi = (ImageView) findViewById(R.id.img_babi);

        btn_one.setOnClickListener(this);
        btn_two.setOnClickListener(this);
        btn_three.setOnClickListener(this);
        btn_four.setOnClickListener(this);
        img_babi.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_one:
                lineAnimator();
                break;
            case R.id.btn_two:
                scaleAnimator();
                break;
            case R.id.btn_three:
                raAnimator();
                break;
            case R.id.btn_four:
                circleAnimator();
                break;
            case R.id.img_babi:
                Toast.makeText(MainActivity.this, "不愧是coder-pig~", Toast.LENGTH_SHORT).show();
                break;
        }
    }


    //定义一个修改ImageView位置的方法
    private void moveView(View view, int rawX, int rawY) {
        int left = rawX - img_babi.getWidth() / 2;
        int top = rawY - img_babi.getHeight();
        int width = left + view.getWidth();
        int height = top + view.getHeight();
        view.layout(left, top, width, height);
    }


    //定义属性动画的方法:

    //按轨迹方程来运动
    private void lineAnimator() {
        width = ly_root.getWidth();
        height = ly_root.getHeight();
        ValueAnimator xValue = ValueAnimator.ofInt(height,0,height / 4,height / 2,height / 4 * 3 ,height);
        xValue.setDuration(3000L);
        xValue.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                // 轨迹方程 x = width / 2
                int y = (Integer) animation.getAnimatedValue();
                int x = width / 2;
                moveView(img_babi, x, y);
            }
        });
        xValue.setInterpolator(new LinearInterpolator());
        xValue.start();
    }

    //缩放效果
    private void scaleAnimator(){

        //这里故意用两个是想让大家体会下组合动画怎么用而已~
        final float scale = 0.5f;
        AnimatorSet scaleSet = new AnimatorSet();
        ValueAnimator valueAnimatorSmall = ValueAnimator.ofFloat(1.0f, scale);
        valueAnimatorSmall.setDuration(500);

        ValueAnimator valueAnimatorLarge = ValueAnimator.ofFloat(scale, 1.0f);
        valueAnimatorLarge.setDuration(500);

        valueAnimatorSmall.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float scale = (Float) animation.getAnimatedValue();
                img_babi.setScaleX(scale);
                img_babi.setScaleY(scale);
            }
        });
        valueAnimatorLarge.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float scale = (Float) animation.getAnimatedValue();
                img_babi.setScaleX(scale);
                img_babi.setScaleY(scale);
            }
        });

        scaleSet.play(valueAnimatorLarge).after(valueAnimatorSmall);
        scaleSet.start();

        //其实可以一个就搞定的
//        ValueAnimator vValue = ValueAnimator.ofFloat(1.0f, 0.6f, 1.2f, 1.0f, 0.6f, 1.2f, 1.0f);
//        vValue.setDuration(1000L);
//        vValue.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
//            @Override
//            public void onAnimationUpdate(ValueAnimator animation) {
//                float scale = (Float) animation.getAnimatedValue();
//                img_babi.setScaleX(scale);
//                img_babi.setScaleY(scale);
//            }
//        });
//        vValue.setInterpolator(new LinearInterpolator());
//        vValue.start();
    }


    //旋转的同时透明度变化
    private void raAnimator(){
        ValueAnimator rValue = ValueAnimator.ofInt(0, 360);
        rValue.setDuration(1000L);
        rValue.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int rotateValue = (Integer) animation.getAnimatedValue();
                img_babi.setRotation(rotateValue);
                float fractionValue = animation.getAnimatedFraction();
                img_babi.setAlpha(fractionValue);
            }
        });
        rValue.setInterpolator(new DecelerateInterpolator());
        rValue.start();
    }

    //圆形旋转
    protected void circleAnimator() {
        width = ly_root.getWidth();
        height = ly_root.getHeight();
        final int R = width / 4;
        ValueAnimator tValue = ValueAnimator.ofFloat(0,
                (float) (2.0f * Math.PI));
        tValue.setDuration(1000);
        tValue.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                // 圆的参数方程 x = R * sin(t) y = R * cos(t)
                float t = (Float) animation.getAnimatedValue();
                int x = (int) (R * Math.sin(t) + width / 2);
                int y = (int) (R * Math.cos(t) + height / 2);
                moveView(img_babi, x, y);
            }
        });
        tValue.setInterpolator(new DecelerateInterpolator());
        tValue.start();
    }
}
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/ly_root"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <Button
        android:id="@+id/btn_one"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="动画1" />

    <Button
        android:id="@+id/btn_two"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="动画2" />

    <Button
        android:id="@+id/btn_three"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="动画3" />

    <Button
        android:id="@+id/btn_four"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="动画4" />

    <ImageView
        android:id="@+id/img_babi"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:background="@drawable/dog" />

</LinearLayout>
Copyright© 2020-2022 li-xyz 冀ICP备2022001112号-1