前言:厚积才能薄发,未来只属于为梦想而奋斗的人们,今天的你决定未来的自己。
一、概述
大家可能觉得补间动画和逐帧动画已经很全面了,为什么还要引入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)
每个监听器都有至指定的移除方法,而且AnimatorUpdateListener和AnimatorListener还有移除所有监听的方法,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的使用
以上几篇动画文章是一定要掌握的,写的不好请多多指出!