一. 原理

实现动画的原理:直接对对象的属性值进行改变操作,从而实现动画效果

(注意:ObjectAnimator类继承自ValueAnimator类,即底层的动画实现机制是基于ValueAnimator类)

二. 类似ValueAnimator的使用形式

1.Java代码的使用方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//创建动画 
//Object object:需要操作的对象
//String property:需要操作的对象的属性
//values:动画初始值 & 结束值(不固定长度)
ObjectAnimator ofInt(Object target, String propertyName, int... values) ;
ObjectAnimator ofFloat(Object target, String propertyName, float... values);
ObjectAnimator ofArgb(Object target, String propertyName, int... values)
ObjectAnimator ofObject(Object target, String propertyName,
TypeEvaluator evaluator, Object... values)

//设置运行时间
ObjectAnimator setDuration(long duration)

//设置重复次数
//动画重复播放次数 = 重放次数+1
//ValueAnimator.INFINITE 无数次
setRepeatCount(int value)

//设置重复模式
//ValueAnimator.RESTART(默认):正序重放
//ValueAnimator.REVERSE:倒序回放
void setRepeatMode(@RepeatMode int value)

//设置动画延迟时间
void setStartDelay(long startDelay)

//设置插值器
void setInterpolator(TimeInterpolator value)

2.Xml的使用方式

① 在路径 res/animator 的文件夹里创建动画效果.xml文件,其中animator文件夹需要自己去创建。

② 使用AnimatorInflater的public static Animator loadAnimator(Context context, @AnimatorRes int id)方法来加载写好的Xml文件动画。

③ 使用 public void setTarget(@Nullable Object target) ,设置动画对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//初始值
android:valueFrom=""
//结束值
android:valueTo=""

//值的类型 floatType intType 等
android:valueType="floatType"
//属性名称 TranslationX ,Rotation 等
android:propertyName=""

//动画持续时间
android:duration="1000"
//动画重复次数 infinite是无数次
android:repeatCount="1"
//动画重复模式 restart reserve
android:repeatMode="restart"
//设置插值器
android:interpolator=""
1
2
3
4
5
6
7
8
//加载动画文件
ObjectAnimator animator = (ObjectAnimator) AnimatorInflater.loadAnimator(this,R.animator.animation);

//设置动画对象
animator.setTarget(view);

//开启动画
animator.start();

三. 类似补间动画的 平移 缩放 旋转 透明度

1. 平移

① 运行效果

② Java 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class TranslateActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_translate);

//x方向平移动画
final ObjectAnimator translationX = ObjectAnimator.ofFloat(findViewById(R.id.view), "TranslationX", 0f, 100f);
//设置动画时间
translationX.setDuration(1000);
//设置动画重复次数
translationX.setRepeatCount(1);
//设置动画重复模式
translationX.setRepeatMode(ValueAnimator.RESTART);

//y方向平移动画
final ObjectAnimator translationY = ObjectAnimator.ofFloat(findViewById(R.id.view), "TranslationY", 0f, 100f);
//设置动画时间
translationY.setDuration(1000);
//设置动画重复次数
translationY.setRepeatCount(1);
//设置动画重复模式
translationY.setRepeatMode(ValueAnimator.RESTART);

findViewById(R.id.start).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//开启动画
translationX.start();
translationY.start();
}
});
}
}

③ Xml方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!--translatex_animator-->
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:valueFrom="0"
android:valueTo="100"
android:valueType="floatType"
android:propertyName="TranslationX"

android:duration="1000"
android:repeatCount="1"
android:repeatMode="restart"/>

<!--translatey_animator-->
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:valueFrom="0"
android:valueTo="100"
android:valueType="floatType"
android:propertyName="TranslationY"

android:duration="1000"
android:repeatCount="1"
android:repeatMode="restart"/>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class TranslateActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_translate);

//找到动画视图
View view = findViewById(R.id.view);

//加载动画
final ObjectAnimator translateX = (ObjectAnimator) AnimatorInflater.loadAnimator(this, R.animator.translatex_animator);
final ObjectAnimator translateY = (ObjectAnimator) AnimatorInflater.loadAnimator(this, R.animator.translatey_animator);

//设置动画对象
translateX.setTarget(view);
translateY.setTarget(view);

//开启动画
findViewById(R.id.start).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
translateX.start();
translateY.start();
}
});
}
}

2. 缩放

① 运行效果

② Java 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public class ScaleActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_scale);

//将要动画的控件
final View view = findViewById(R.id.view);

//获取测量好的坐标
view.post(new Runnable() {
@Override
public void run() {
//设置缩放的基准点
view.setPivotX(view.getWidth());
view.setPivotY(view.getHeight());

//创建动画 x方向的缩放大小
final ObjectAnimator scaleX = ObjectAnimator.ofFloat(view, "ScaleX", 1.0f, 1.5f);
//设置动画时间
scaleX.setDuration(1000);
//设置重复次数
scaleX.setRepeatCount(1);
//设置重复模式
scaleX.setRepeatMode(ValueAnimator.RESTART);

//创建动画 y方向的缩放大小
final ObjectAnimator scaleY = ObjectAnimator.ofFloat(view, "ScaleY", 1.0f, 1.5f);
//设置动画时间
scaleY.setDuration(1000);
//设置重复次数
scaleY.setRepeatCount(1);
//设置重复模式
scaleY.setRepeatMode(ValueAnimator.RESTART);

//开启动画
findViewById(R.id.start).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
scaleX.start();
scaleY.start();
}
});
}
});
}
}

③ Xml方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!--scalex_animator-->
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:valueFrom="1.0"
android:valueTo="1.5"
android:valueType="floatType"
android:propertyName="ScaleX"

android:duration="1000"
android:repeatCount="1"
android:repeatMode="restart"/>

<!--scaley_animator-->
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:valueFrom="1.0"
android:valueTo="1.5"
android:valueType="floatType"
android:propertyName="ScaleY"

android:duration="1000"
android:repeatCount="1"
android:repeatMode="restart"/>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class ScaleActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_scale);

//将要动画的控件
final View view = findViewById(R.id.view);

//创建动画 x方向的缩放大小
final ObjectAnimator scaleX = (ObjectAnimator) AnimatorInflater.loadAnimator(this,R.animator.scalex_animator);
scaleX.setTarget(view);

//创建动画 y方向的缩放大小
final ObjectAnimator scaleY = (ObjectAnimator) AnimatorInflater.loadAnimator(this,R.animator.scaley_animator);
scaleY.setTarget(view);

//获取测量好的坐标
view.post(new Runnable() {
@Override
public void run() {
//设置缩放的基准点
view.setPivotX(view.getWidth());
view.setPivotY(view.getHeight());

//开启动画
findViewById(R.id.start).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
scaleX.start();
scaleY.start();
}
});
}
});
}
}

3. 旋转

① 运行效果

② Java 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class RotateActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_rotate);

//将要动画的控件
final View view = findViewById(R.id.view);

//获取测量好的坐标
view.post(new Runnable() {
@Override
public void run() {
//设置旋转的基准点
view.setPivotX(view.getWidth());
view.setPivotY(view.getHeight());

//创建动画
final ObjectAnimator rotation = ObjectAnimator.ofFloat(view, "Rotation", 0.0f, 90.0f);
//设置动画时间
rotation.setDuration(1000);
//设置重复次数
rotation.setRepeatCount(1);
//设置重复模式
rotation.setRepeatMode(ValueAnimator.RESTART);

//开启动画
findViewById(R.id.start).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
rotation.start();
}
});
}
});
}
}

③ Xml方式

1
2
3
4
5
6
7
8
9
10
11
12
<!--rotate_animation-->
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:valueFrom="0"
android:valueTo="90"
android:valueType="floatType"
android:propertyName="Rotation"

android:duration="1000"
android:repeatCount="1"
android:repeatMode="restart"
/>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class RotateActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_rotate);

//将要动画的控件
final View view = findViewById(R.id.view);

//加载动画 旋转动画
final ObjectAnimator rotate = (ObjectAnimator) AnimatorInflater.loadAnimator(this,R.animator.rotate_animation);
rotate.setTarget(view);

//获取测量好的坐标
view.post(new Runnable() {
@Override
public void run() {
//设置缩放的基准点
view.setPivotX(view.getWidth());
view.setPivotY(view.getHeight());

//开启动画
findViewById(R.id.start).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
rotate.start();
}
});
}
});
}
}

4. 透明度

① 运行效果

② Java 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class AlphaActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_alpha);

//透明度动画
final ObjectAnimator alpha = ObjectAnimator.ofFloat(findViewById(R.id.view), "Alpha", 1.0f, 0.5f);
//设置动画时间
alpha.setDuration(1000);
//设置动画重复次数
alpha.setRepeatCount(1);
//设置动画重复模式
alpha.setRepeatMode(ValueAnimator.RESTART);

findViewById(R.id.start).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//开启动画
alpha.start();
}
});
}
}

③ Xml方式

1
2
3
4
5
6
7
8
9
10
11
12
<!--alpha_animator-->
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:valueFrom="0"
android:valueTo="90"
android:valueType="floatType"
android:propertyName="Rotation"

android:duration="1000"
android:repeatCount="1"
android:repeatMode="restart"
/>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class AlphaActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_alpha);

//找到动画视图
View view = findViewById(R.id.view);

//加载动画
final ObjectAnimator alpha = (ObjectAnimator) AnimatorInflater.loadAnimator(this, R.animator.alpha_animator);

//设置动画对象
alpha.setTarget(view);

//开启动画
findViewById(R.id.start).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
alpha.start();
}
});
}
}

四. ObjectAnimator的实现原理

1. String property

前面的例子中,我们给String property传入alpha、rotation、translationX 和 scaleY 等,还可以传入什么呢?

答案是任意属性值。

①ObjectAnimator类,通过不断控制 值 的变化,再不断 自动 赋给对象的属性,从而实现动画效果。而自动赋给对象的属性的本质是调用该对象属性的 set() & get() 方法进行赋值。

第二个参数传入值的作用是:让ObjectAnimator类 根据传入的属性名去寻找该对象对应属性名的 set() & get() 方法,从而进行对象属性值的赋值。

2. 自动赋值的逻辑

① 初始化时,如果属性的初始值没有提供,则调用属性的 get() 进行取值;

② 当值变化时,用对象该属性的 set() 方法,从而从而将新的属性值设置给对象属性。

3. 总结

① ObjectAnimator 类针对的是任意对象 & 任意属性值,并不是单单针对于View对象。

② 如果需要采用ObjectAnimator 类实现动画效果,那么需要操作的对象就必须有该属性的 set() & get()。

五. 实例使用

1. 实现步骤

对于属性动画,其拓展性在于:不局限于系统限定的动画,可以自定义动画,即自定义对象的属性,并通过操作自定义的属性从而实现动画。

步骤:

① 为对象设置需要操作属性的 set() & get() 方法。

② 通过实现TypeEvaluator类从而定义属性变化的逻辑。

③ 使用 ObjectAnimator 的 ofObject() 创建动画。

2. 第一种方式添加 set() & get() 方法

通过继承原始类,直接给类加上该属性的 get() & set(),从而实现给对象加上该属性的 get() & set() 。

① 运行效果

② 代码

自定义的估值器 ColorEvaluator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
public class ColorEvaluator implements TypeEvaluator {

//当前的RGB
private int currentRed;
private int currentGreen;
private int currentBlue;

@Override
public Object evaluate(float fraction, Object startValue, Object endValue) {
//获取颜色开始值和结束值
String startColor = (String) startValue;
String endColor = (String) endValue;

//通过字符串截取的方式将初始颜色分为RGB三个部分
int startRed = Integer.parseInt(startColor.substring(1,3),16);
int startGreen = Integer.parseInt(startColor.substring(3,5),16);
int startBlue = Integer.parseInt(startColor.substring(5,7),16);

//通过字符串截取的方式将结束颜色分为RGB三个部分
int endRed = Integer.parseInt(endColor.substring(1,3),16);
int endGreen = Integer.parseInt(endColor.substring(3,5),16);
int endBlue = Integer.parseInt(endColor.substring(5,7),16);

//计算RGB三部分的差值
int redDiff = Math.abs(startRed-endRed);
int greenDiff = Math.abs(startGreen-endGreen);
int blueDiff = Math.abs(startBlue-endBlue);
int colorDiff = redDiff + greenDiff + blueDiff;

//计算RGB三部分当前的值
if (currentRed != endRed){
currentRed = getCurrentColor(startRed,endRed,colorDiff,0,fraction);
}
if (currentGreen != endGreen){
currentGreen = getCurrentColor(startGreen,endGreen,colorDiff,redDiff,fraction);
}
if (currentBlue != endBlue){
currentBlue = getCurrentColor(startBlue,endBlue,colorDiff,redDiff+greenDiff,fraction);
}

//计算当前RGB三部分的组装值 返回
return "#" + getHexString(currentRed) + getHexString(currentGreen) + getHexString(currentBlue);
}

/**
* 根据 fraction 计算 RGB 某部分的值
* @param startColor
* @param endColor
* @param colorDiff
* @param fraction
* @return
*/
public int getCurrentColor(int startColor,int endColor,int colorDiff,int offset,float fraction){
//存储新的值
int currentColor;

//判断是增加还是减少
if (startColor >= endColor){
currentColor = (int) (startColor - (fraction * colorDiff - offset));
}else {
currentColor = (int) (startColor + (fraction * colorDiff - offset));
}

return currentColor;
}

/**
* 10进制->16进制
* @param value
* @return
*/
private String getHexString(int value){
//转化为16进制
String hexString = Integer.toHexString(value);

//如果只有一位,需要补0
if (hexString.length() == 1){
hexString = "0" + hexString;
}

return hexString;
}
}

自定义的View MyView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class MyView extends View {

//圆的半径
public static final float RADIUS = 100f;
//画笔
private Paint paint;
//圆的背景颜色
private String color;

/**
* 构造方法
* @param context
* @param attrs
*/
public MyView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);

//初始化画笔 并赋予默认颜色
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(Color.BLUE);
}

/**
* 重绘
* @param canvas
*/
@Override
protected void onDraw(Canvas canvas) {
canvas.drawCircle(500,500,RADIUS,paint);
}

//setter getter 方法
public String getColor() {
return color;
}

public void setColor(String color) {
this.color = color;

//设置画笔颜色
paint.setColor(Color.parseColor(color));

//刷新 重绘
invalidate();
}
}

通过对 自定义的 MyView 中的 Color 属性操作,每次自动赋值后都会刷新界面重绘一个圆。

这就是通过继承原始类,直接给类加上该属性的 get() & set()。

2. 第二种方式添加 set() & get() 方法

通过包装原始动画对象,间接给对象加上该属性的 get()&
set()。即 用一个类来包装原始对象。

从原理上说,如果想让对象的属性a的动画生效,属性a需要同时满足下面两个条件:

  • 对象必须要提供属性a的set()方法,如果没传递初始值,那么需要提供get()方法,因为系统要去拿属性a的初始值。若该条件不满足,程序直接Crash。

  • 对象提供的 属性a的set()方法 对 属性a的改变 必须通过某种方法反映出来,比如带来ui上的变化,若这条不满足,动画无效,但不会Crash。

不满足第二条的例子:由于View的setWidth()并不是设置View的宽度,而是设置View的最大宽度和最小宽度的;所以通过setWidth()无法改变控件的宽度;所以对View视图的width做属性动画没有效果。

① 运效果

② 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package swu.xl.property_object_example_two;

import androidx.appcompat.app.AppCompatActivity;

import android.animation.ObjectAnimator;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {

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

//找到待包装的类
View view = findViewById(R.id.btn);
//包装
final ViewWrapper viewWrapper = new ViewWrapper(view);

//点击
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ObjectAnimator.ofInt(viewWrapper,"width",300,600).setDuration(3000).start();
}
});
}

//包装类
private static class ViewWrapper{
private View target;

//设置待包装对象
public ViewWrapper(View target){
this.target = target;
}

//添加的属性
public void setWidth(int width){
//找到待包装类的布局
ViewGroup.LayoutParams layoutParams = target.getLayoutParams();
//重新设置宽度
layoutParams.width = width;
//重新复制约束
target.setLayoutParams(layoutParams);
}
public int getWidth(){
return target.getLayoutParams().width;
}

}

}

通过包装原始动画对象完成View的Width属性动画。本质上是采用了设计模式中的装饰模式,即通过包装类从而扩展对象的功能。

参考文章

Android ObjectAnimator类学习指南:手把手带你学会如何自定义属性动画