Android动画篇(三)—— 属性动画ValueAnimator的使用

前言:厚积才能薄发,未来只属于为梦想而奋斗的人们,今天的你决定未来的自己。

一、概述

大家可能觉得补间动画和逐帧动画已经很全面了,为什么还要引入Property Animator(属性动画)呢?有关View Animation(视图动画)和Property Animator(属性动画)的分类区别可以参考我的《Android动画篇(一)—— alpha、scale、translate、rotate、set的xml属性及用法

1.为什么引入Property Animator(属性动画)

(1)Property Animator(属性动画)能实现补间动画无法实现的功能

大家都知道补间动画和逐帧动画统称为View Animation,也就是说两个动画只能对派生子View的控件实例起作用,而Property Animator则不同,从名字可以看出属性动画是作用于控件属性的,正因为属性动画能够针对控件某一属性来做动画,所以他能够单独改变该控件属性的值。比如颜色,实现控件在一定时间内颜色渐变,这就是Property Animator(属性动画)能实现补间动画无法实现的功能的最重要原因。

(2)View Animation(视图动画)仅能对指定的控件做动画,Property Animator(属性动画)能通过改变控件的某个属性值做动画

假设我将一个按钮从左上角通过补间动画移动到右下角,在移动过程中和移动后这个按钮都不会响应点击事件的。为什么呢?

因为补间动画仅仅改变的是控件的显示位置而已,并没有改变控件本身的值,View Animation动画是通过Parent View实现的,在view被draw时Parent View改变它的绘制参数,这样view的大小位置角度等虽然变化了,但是view的实际属性并没有改变。所以有效区域还是动画之前的区域,我们看到的效果仅仅是系统作用在按钮上的显示效果,利用动画将控件从原来的位置移动到右下角,但是控件的内部值是没有任何变化的。所以点击区域还是原来的点击区域(下面会实例验证)。

2、补间动画点击区域问题

下面我们就用TranslateAnimation来做移动动画的例子,看他的点击区域是否变化。

首先我给TextView添加响应事件,点击TextView时会弹出Toast,然后我点击开始动画时,TextView向右下角移动

从下图可以看出,移动前TextView可以点击弹出Toast,移动后点击TextView则没有弹出Toast,相反点击TextView原来的区域则可以弹出Toast。这就说明:补间动画只能对控件做动画,并不能改变控件内部的属性值。

这个效果的代码我就不贴出来了,具体可以参考《 Android动画篇(二)—— 代码生成alpha、scale、translate、rotate、set及插值器动画

二、ValueAnimator的使用

Property Animator(属性动画)包括ValueAnimator和ObjectAnimator,这里主要讲讲ValueAnimator的主要用法,ValueAnimator是针对值的,这个不会对控件做任何操作,我们可以设置它在某个值运动到另一个值,通过监听它的值变化来操作控件。

1、ValueAnimator的简单使用

(1)创建ValueAnimator实例

                ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 200);
                valueAnimator.setDuration(2000);
                valueAnimator.start();

这里我们利用ValueAnimator.ofInt创建了一个0到200的动画运算,动画时长2s,然后开始动画,从这段代码看出ValueAnimator没有跟任何控件相关,也正好说明ValueAnimator对值做动画运算,而不是针对控件,我们要通过监听ValueAnimator的动画过程来自己多控件做操作。

(2)添加监听

上面三行代码实现了值的动画变化,我们来添加监听:

                 valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        int value = (int) animation.getAnimatedValue();
                        Log.e(TAG, "value:" + value);
                    }
                });

我们通过addUpdateListener添加监听,从监听动画运算的回传结果中,是表示当前ValueAnimator实例,我们通过animation.getAnimatedValue()得到当前值,打印出来看看:

这就是ValueAnimator的功能,对指定区间值做动画运算,我们通过运算过程做监听来操作自己的控件。

核心就两点:

A:ValueAnimator只负责对指定区间的数值进行动画运算;
B:我们需要对运算过程做监听,然后自己对控件进行动画操作。

2、ValueAnimator的使用实例

我们使用ValueAnimator实现上面的动画效果,将控件从(0,0)位移到(400,400)的位置,得到的结果却是,点击开启动画按钮后,TextView从(0,0)2秒内移动到(400,400),运动前后TextView都可以点击

效果如下:

代码如下:

xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:id="@+id/tv_start_anim"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@color/colorAccent"
        android:padding="10dp"
        android:text="start anim"/>

    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:background="#0000ff"
        android:padding="10dp"
        android:text="Hello World!"
        android:textColor="#ffffff"/>

</LinearLayout>

java:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private static final String TAG = MainActivity.class.getSimpleName();
    private TextView mTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextView = findViewById(R.id.tv);
        mTextView.setOnClickListener(this);
        findViewById(R.id.tv_start_anim).setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.tv:
                Toast.makeText(this, "click", Toast.LENGTH_SHORT).show();
                break;
            case R.id.tv_start_anim:
                ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 400);
                valueAnimator.setDuration(2000);
                valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        int value = (int) animation.getAnimatedValue();
                        mTextView.layout(value, value, value + mTextView.getWidth(), value + mTextView.getHeight());
                        Log.e(TAG, "value:" + value);
                    }
                });
                valueAnimator.start();
                break;
            default:
                break;
        }
    }
}

在上面的代码中,监听的时候通过layout函数改变TextView的位置,注意,我们是通过layout函数改变控件的位置的,layout函数改变控件位置这个是永久性的,即通过更改控件的left,top,right,bottom四个点的坐标来更改控件的位置,更改位置后,控件是可以响应事件的。

三、ValueAnimator的常用方法

1.ofInt和ofFloat

与onInt同样功能的还有一个函数,叫ofFloat,具体声明如下:

 public static ValueAnimator ofInt(int... values)
 public static ValueAnimator ofFloat(float... values) 

可以看出,它们的参数都是可变长参数,我们可以传入任何数量的值,传进去的值列表表示动画的变化范围,比如ofInt(0,100,50)表示从数值0变化到100,再从100变化到50。ofInt与ofFloat的唯一区别就是参数值类型不同,ofInt需要传入int类型的参数,ofFloat则需要传入float类型的参数

ofFloat的使用:

        ValueAnimator valueAnimator = ValueAnimator.ofFloat(0f, 400f, 200f, 600f);
        valueAnimator.setDuration(2000);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                Float v = (Float) animation.getAnimatedValue();
                int value = v.intValue();
                mTextView.layout(value, value, value + mTextView.getWidth(), value + 
                mTextView.getHeight());
                Log.e(TAG, "value:" + value);
            }
        });
        valueAnimator.start();

效果如下图:

点击开启动画后,TextView先向右下方运动,然后回来,然后再从向右下方运动。

这里我们使用了ofFloat(0f, 400f, 200f, 600f)构造了一个比较复杂的动画渐变,值从0到400,再回到200,然后再到600的变化。

在监听时,首先要得到动画的值:

Float v = (Float) animation.getAnimatedValue();

在ofFloatz中通过函数getAnimatedValue()获得的值为什么要强转为Float类型,先看下该函数的声明:

public Object getAnimatedValue() 

它的返回值是一个Object的原始类型,那我们知道它要转换成什么类型吗?注意,我们在设定动画初始值时使用的是ofFloat()函数,所以每个值的类型必定是float,那么从返回值取出的值的类型也是float,ofInt()函数同理。

2、其他常用方法

  • setStartDelay(long startDelay)      延时开始动画,单位毫秒

  • setDuration(long duration)            设置动画时长,单位毫秒

  • start()                                             启动动画

  • cancel()                                          退出动画

  • setRepeatCount(int value)             循环次数,INFINITE表示无限循坏

  • setRepeatMode(int)                     重复类型,有REVERSE和RESTART两种类型,与setRepeatCount配合使用,reverse                                                                       表示倒叙回放,restart表示重新开始

举个例子:

 private static final String TAG = MainActivity.class.getSimpleName();
    private TextView mTextView;
    private ValueAnimator mValueAnimator;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextView = findViewById(R.id.tv);
        mTextView.setOnClickListener(this);
        findViewById(R.id.tv_start_anim).setOnClickListener(this);
        findViewById(R.id.tv_cancel).setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.tv:
                Toast.makeText(this, "click", Toast.LENGTH_SHORT).show();
                break;
            case R.id.tv_start_anim:
                mValueAnimator = setAnimator3();
                break;
            case R.id.tv_cancel:
                if (mValueAnimator != null) {
                    //退出动画
                    mValueAnimator.cancel();
                }
                break;
            default:
                break;
        }
    }
 private ValueAnimator setAnimator3() {
        ValueAnimator valueAnimator = ValueAnimator.ofFloat(0f, 600f);
        //设置动画时长 2秒
        valueAnimator.setDuration(2000);
        //设置重复次数,无限次
        valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
        //设置重复类型,倒叙从新开始
        valueAnimator.setRepeatMode(ValueAnimator.REVERSE);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                Float v = (Float) animation.getAnimatedValue();
                int value = v.intValue();
                mTextView.layout(mTextView.getLeft(), value, value + mTextView.getRight(), value + mTextView.getHeight());
                Log.e(TAG, "value:" + value);
            }
        });

        //设置2秒启动动画
        valueAnimator.setStartDelay(2000);
        //开启动画
        valueAnimator.start();
        return valueAnimator;
    }

效果如下:

这里有两个按钮,点击start anim时,TextView垂直向下运动,我定义的运动值是ofFloat(0f,600f),从效果可以看出,我设置了两秒的动画时长,ValueAnimator.INFINITE无限循环,每次循环都是ValueAnimator.REVERSE使其倒叙重新开始循环,两秒后启动动画,点击cancel anim按钮后取消动画。由于最后取消动画还需要用到ValueAnimator实例,所以我们在方法中返回animtor。

3、三个监听器

(1)添加监听器

/**
 * 监听器一:监听动画变化时的实时值
 */
public static interface AnimatorUpdateListener {
    void onAnimationUpdate(ValueAnimator animation);
}
//添加方法为:public void addUpdateListener(AnimatorUpdateListener listener)

/**
 * 监听器二:监听动画变化时四个状态
 */
public static interface AnimatorListener {
    void onAnimationStart(Animator animation);
    void onAnimationEnd(Animator animation);
    void onAnimationCancel(Animator animation);
    void onAnimationRepeat(Animator animation);
}
//添加方法为:public void addListener(AnimatorListener listener) 

/**
 *监听器三:监听动画暂停和暂停后再恢复的状态
 */
    public static interface AnimatorPauseListener {
        void onAnimationPause(Animator animation);
        void onAnimationResume(Animator animation);
    }
//添加方法:public void addPauseListener(AnimatorPauseListener listener)

监听器一:AnimatorUpdateListener就是监听动画实时变化的状态,在onAnimationUpdate(ValueAnimator animation)函数中返回的就是动画的实例animator,根据实例可以获取到不同的数据,比如:animation.getAnimatedValue()获取动画实时变化的值,这里就不细讲了。添加方法是addUpdateListener(AnimatorUpdateListener listener)

监听器二:AnimatorListener主要是监听动画的四个状态:start,end,cancel,repeat;当动画开始时,会调用onAnimationStart(Animator animation)函数;当动画结束时,调用onAnimationEnd(Animator animation)函数;当动画退出时,调用onAnimationCancel(Animator animation)函数;当动画重复时,调用onAnimationRepeat(Animator animation)函数。添加方法是addPauseListener(AnimatorPauseListener listener)

监听器三:android在api19之后增加了AnimatorPauseListener(),主要是监听动画暂停和暂停后再次动画的状态:pause和resume,在animator设置暂停后pause()会调用onAnimationPause(Animator animation)函数并且返回animator实例,当animator暂停后再次进入动画时调用resume()会调用onAnimationResume(Animator animation)函数。

我们添加上面的监听,代码如下:

private static final String TAG = MainActivity.class.getSimpleName();
    private TextView mTextView;
    private ValueAnimator mValueAnimator;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextView = findViewById(R.id.tv);
        mTextView.setOnClickListener(this);
        findViewById(R.id.tv_start_anim).setOnClickListener(this);
        findViewById(R.id.tv_cancel).setOnClickListener(this);
        findViewById(R.id.tv_pause).setOnClickListener(this);
        findViewById(R.id.tv_resume).setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.tv:
                Toast.makeText(this, "click", Toast.LENGTH_SHORT).show();
                break;
            case R.id.tv_start_anim:
                mValueAnimator = setAnimatorListener();
                break;
            case R.id.tv_cancel:
                if (mValueAnimator != null) {
                    //退出动画
                    mValueAnimator.cancel();
                }
                break;
            case R.id.tv_pause:
                if (mValueAnimator != null) {
                    //暂停动画
                    mValueAnimator.pause();
                }
                break;
            case R.id.tv_resume:
                if (mValueAnimator != null) {
                    //继续动画
                    mValueAnimator.resume();
                }
                break;
            default:
                break;
        }
    }
 /**
     * 监听器的使用
     *
     * @return
     */
    private ValueAnimator setAnimatorListener() {
        ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 200);
        //设置动画时长 2秒
        valueAnimator.setDuration(1000);
        //设置重复次数,无限次
        valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
        //设置重复类型,倒叙从新开始
        valueAnimator.setRepeatMode(ValueAnimator.REVERSE);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int value = (int) animation.getAnimatedValue();
                mTextView.layout(mTextView.getLeft(), value, mTextView.getRight(), value + mTextView.getHeight());
                Log.e(TAG, "AnimatorUpdateListener: value:" + value);
            }
        });

        valueAnimator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {
                Log.e(TAG, "AnimatorListener:Start");
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                Log.e(TAG, "AnimatorListener:End");
            }

            @Override
            public void onAnimationCancel(Animator animation) {
                Log.e(TAG, "AnimatorListener:Cancel");
            }

            @Override
            public void onAnimationRepeat(Animator animation) {
                Log.e(TAG, "AnimatorListener:Repeat");
            }
        });

        valueAnimator.addPauseListener(new Animator.AnimatorPauseListener() {
            @Override
            public void onAnimationPause(Animator animation) {
                Log.e(TAG, "AnimatorPauseListener:Pause");
            }

            @Override
            public void onAnimationResume(Animator animation) {
                Log.e(TAG, "AnimatorPauseListener:Resume");
            }
        });
        //开启动画
        valueAnimator.start();
        return valueAnimator;
    }

动画效果如下:

                    图一                                                   图二

动画设置了无限循环,开启动画后再关闭动画,看日志:

从图一中,可以看出,动画开始时回调了onAnimationStart()函数;动画值我设置的是(0,200),当数值到达20后,回调onAnimationRepeat()函数,并且每次重复都会执行该方法;点击退出动画时回调onAnimationCancel()函数;然后动画结束回调onAnimationEnd()函数。

图二,我们在看看开始动画后,点击pause anim按钮后,再点击resume anim按钮,看打印日志:

从日志可以看出,pause()后动画暂停了onAnimationPause()函数,Resume()后动画继续执行回调onAnimationResume()函数,我们可以在暂停动画期间做相关的操作。

(2)移除监听器

上面我们做了如何添加监听器,现在我们来看看如何移除监听器

/**
 * 移除AnimatorUpdateListener
 */
void removeUpdateListener(AnimatorUpdateListener listener);
void removeAllUpdateListeners();
 /**
  * 移除AnimatorListener
  */
void removeListener(AnimatorListener listener);
void removeAllListeners();
/**
  * 移除AnimatorPauseListener 
  */
void removePauseListener(AnimatorPauseListener listener)

每个监听器都有至指定的移除方法,而且AnimatorUpdateListenerAnimatorListener还有移除所有监听的方法,animator移除监听器后,就不会再接收到动画监听,我们给animator移除所有监听实验一下:

//移除动画监听
mValueAnimator.removeAllListeners();

在前面代码的基础上,我们在动画重复第三次的时候,点击移除按钮,移除所有的动画监听。

效果图:

可见只打印了循环三次以前的log,在移除我们添加的AnimatorListener之后,我们打印log的代码就不会再执行了,所以也就不会再有log了。

到这里有关ValueAnimator的常用函数基本就讲完,更深入的ValueAnimator的高级用法将另外开篇讲。 

源码下载地址:https://github.com/FollowExcellence/AndroidAnimation

请大家尊重原创者版权,转载请标明出处: https://blog.csdn.net/m0_37796683/article/details/90440702 谢谢!

动画系列文章:

1、 Android动画篇(一)—— alpha、scale、translate、rotate、set的xml属性及用法

  • 补间动画的XML用法以及属性详解

2、Android动画篇(二)—— 代码实现alpha、scale、translate、rotate、set及插值器动画

  • 代码动态实现补间动画以及属性详解

3、 Android动画篇(三)—— 属性动画ValueAnimator的使用

  • ValueAnimator的基本使用

4、 Android动画篇(四)—— 属性动画ValueAnimator的高级进阶

  • 插值器(Interpolator)、计算器(Evaluator)、ValueAnimator的ofObject用法等相关知识

5、 Android动画篇(五)—— 属性动画ObjectAnimator基本使用

  • ObjectAnomator的基本使用以及属性详解

6、 Android动画篇(六)—— 组合动画AnimatorSet和PropertyValuesHolder的使用

  • AnimatorSet动画集合和PropertyValuesHolder的使用

以上几篇动画文章是一定要掌握的,写的不好请多多指出!

  • 7
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值