03.4 精通自定义 View 之属性动画——ObjectAnimator

返回自定义 View 目录

3.4.1 概述

1. 引入

ObjectAnimator 派生自 ValueAnimator,所以 ValueAnimator 能用的方法,ObjectAnimator 都能用,ObjectAnimator 是 ValueAnimator 的子类。

ObjectAnimator 重载了几个方法,例如 ofInt(),ofFloat() 等,这里我以 ofFloat() 做个例子:

1
2
3
ObjectAnimator animator = ObjectAnimator.ofFloat(mView, "alpha", 1, 0, 1);
animator.setDuration(2000);
animator.start();

其构造函数如下:

1
public static ObjectAnimator ofFloat(Object target, String propertyName,float... values)

  • 第一个参数用于指定这个动画要操作的是哪个控件。
  • 第二个参数用于指定这个动画要操作这个控件的哪个属性。
  • 第三个参数是可变长参数,是指这个属性值如何变化。

2. set 函数

ObjectAnimator 做动画,是通过指定属性所对应的 set 方法来改变的。比如,我们上面指定的改变 alpha 的属性值,ObjectAnimator 在做动画时就会到指定控件(TextView)中去找对应的 setAlpha() 方法来改变控件中对应的值。在 View 中有关动画,总共有下面几组 set 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 1. 透明度:alpha
public void setAlpha(float alpha)
// 2. 旋转度数:rotation、rotationX、rotationY
public void setRotation(float rotation)
public void setRotationX(float rotationX)
public void setRotationY(float rotationY)
// 3. 平移:translationX、translationY
public void setTranslationX(float translationX)
public void setTranslationY(float translationY)
// 4. 缩放:scaleX、scaleY
public void setScaleX(float scaleX)
public void setScaleY(float scaleY)

注意:

  • 要使用 ObjectAnimator 来构造对画,要操作的控件中,必须存在对应的属性的 set 方法,而且参数类型必须与构造所使用的 ofFloat() 或者 ofInt() 函数一致。
  • set 方法的命名必须以骆驼拼写法命名,即 set 后每个单词首字母大写,其余字母小写,即类似于 setPropertyName 所对应的属性为propertyName。
1)改变旋转度数 rotation、rotationX、rotationY
  • float rotationX:表示围绕 X 轴旋转,rotationX 表示旋转度数
  • float rotationY:表示围绕 Y 轴旋转,rotationY 表示旋转度数
  • float rotation:表示围绕 Z 轴(垂直于屏幕)旋转,rotation 表示旋转度数

示例:

1
2
3
4
5
ObjectAnimator animator = ObjectAnimator.ofFloat(mView, "rotationX", 0, 270, 0);
// ObjectAnimator animator = ObjectAnimator.ofFloat(mView, "rotationY", 0, 180, 0);
// ObjectAnimator animator = ObjectAnimator.ofFloat(mView, "rotation", 0, 270, 0);
animator.setDuration(2000);
animator.start();

2)移动 translationX、translationY
  • float translationX:表示在 X 轴上的平移距离,以当前控件为原点,向右为正方向,参数 translationX 表示移动的距离。
  • float translationY:表示在 Y 轴上的平移距离,以当前控件为原点,向下为正方向,参数 translationY 表示移动的距离。
3)缩放 scaleX、scaleY
  • float scaleX:在 X 轴上缩放,scaleX 表示缩放倍数
  • float scaleY:在 Y 轴上缩放,scaleY 表示缩放倍数
4)改变透明度 alpha
  • float alpha:改变透明度

3.4.2 ObjectAnimator 动画原理

与 ValueAnimator 不同的是最后一步,在 ValueAnimator 中,我们要通过添加监听器来监听当前数字值。而在 ObjectAnimator 中,则是先根据属性值拼装成对应的 set 函数的名字,比如这里的 scaleY 的拼装方法就是将属性的第一个字母强制大写后,与 set 拼接,所以就是 setScaleY 。然后通过反射找到对应控件的 setScaleY(float scaleY) 函数,将当前数字值做为 setScaleY(float scale) 的参数将其传入。

如果我们要自定义 ObjectAnimator 属性就要注意以下几个点:

  • 在命名时 set 之后的名字就是属性的名字。同时属性名字第一个字母不区分大小写,后面的名字必须与属性的名字相同。
  • 在知道参数类型后我们才能确定我们是调用 ofFloat 还是 ofInt,这个参数是根据 setXXX 方法中的参数类型决定的。例如:setAlpha(float alpha) 其中参数类型是 float 所以上面我们使用的是 ofFloat,当然 ObjectAnimator 继承 ValueAnimatior 也有。ofObject() 支持任意类型,不过和前面我们讲到的一样这里我们就要自定义自己的估值器了。

3.4.3 自定义 ObjectAnimator 属性

自定义 FallingBallImageView 和 Evaluator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class FallingBallImageView extends AppCompatImageView {
public FallingBallImageView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public void setFallingPos(Point pos) {
layout(pos.x, pos.y, pos.x + getWidth(), pos.y + getHeight());
}
public static class FallingBallEvaluator implements TypeEvaluator<Point> {
private Point point = new Point();
@Override
public Point evaluate(float fraction, Point startValue, Point endValue) {
point.x = (int)(startValue.x + fraction * (endValue.x - startValue.x));
if (fraction * 2 < 1) {
point.y = (int)(startValue.y + fraction*2*(endValue.y - startValue.y));
} else {
point.y = endValue.y;
}
return point;
}
}
}

在代码中,开始动画:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.act_main);
final FallingBallImageView view = findViewById(R.id.ball_img);
findViewById(R.id.start_btn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ObjectAnimator animator = ObjectAnimator.ofObject(view, "fallingPos",
new FallingBallImageView.FallingBallEvaluator(),
new Point(0, 0), new Point(500, 500));
animator.setDuration(2000);
animator.start();
}
});
}
}

布局文件 act_main.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?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:layout_gravity="center_horizontal"
android:orientation="horizontal">
<com.xxt.xtest.FallingBallImageView
android:id="@+id/ball_img"
android:layout_width="50dp"
android:layout_height="50dp"
android:src="@drawable/circle"/>
<Button
android:id="@+id/start_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="30dp"
android:text="开启动画"/>
</LinearLayout>

其中,drawable/circle.xml

1
2
3
4
5
6
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="#FF0000"/>
</shape>

3.4.4 何时需要实现对应的 get 函数

ObjectAnimator 有三个构造函数:ofInt()、ofFloat() 和 ofObject(),它们的最后一个参数都是可变长参数,用于指定动画值的变化区间。如果我们只定义一个值,如上例中改成:

1
2
3
ObjectAnimator animator = ObjectAnimator.ofObject(view, "fallingPos",
new FallingBallImageView.FallingBallEvaluator(),
new Point(500, 500));

则会发生异常,信息如下:

当且仅当我们只给动画一个值时,程序才会调用属性对应的 get 函数来得到动画初始值。如果没有初始值,就会使用系统默认值。比如 ofInt() 函数中使用的参数类型是 int 类型,而 int 类型的默认值是 0,动画就会从 0 开始。所以上述自定义控件 FallingBallImageView 设置 get 函数,那么将会以 get 函数的返回值作为初始值。

1
2
3
4
5
6
public class FallingBallImageView extends AppCompatImageView {
...
public Point getFallingPos() {
return new Point(0, 0);
}
}

总结:当且仅当动画只有一个过渡值时,系统才会调用属性对应的 get 函数来得到动画的初始值。当不存在 get 函数时,则会去动画参数类型的默认值作为初始值;当无法取得动画参数类型的默认值时,程序会直接崩溃。

3.4.5 常用函数

用法和效果与 ValueAnimator 的函数是完全一样的。