Android 动画——属性动画
从 Android 3.0 就推出了属性动画,在以前使用的是视图动画,那么属性动画的优势在哪呢?都知道,视图动画对于一个 View 的操作仅仅只是一个表象的操作,就是说,视图动画中,仅仅只是对它视图的操作,而它真实的坐标和属性并没有发生变化,那么属性动画就是对一个 View 或者非 View 属性的操作,这一强大功能让它解决了视图动画的缺陷。
和视图动画一样的是,属性动画(Animator )也分为动画和动画集:ValueAnimator(ValueAnimator 算不上实现动画,ObjectAnimator 和TimeAnimator 继承自 ValueAnimator,一般由前者 ObjectAnimator 和 TimeAnimator 来实现动画,后面会说明 ValueAnimator) 和 AnimatorSet,而 ValueAnimator 又分为 ObjectAnimator 和 TimeAnimator,一般我们都使用 ObjectAnimator。
属性动画和视图动画一样,都可以使用 xml 和 代码的方式实现,大家可以看一下 视图动画 来了解一下,其 xml 的实现方式是大同小异的。所以这里只对一些 xml 实现方式的不同之处作解释,不会像视图动画那篇做详细说明。
因为属性动画和视图动画实现方式不太一样,所以我们可能先了解一下代码实现方式,这样对于 xml 理解会更家容易。
- 代码方法
ObjectAnimator 有很多实现动画的方法,一般我们使用 ofFloat() 方法:
public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
throw new RuntimeException("Stub!");
}
这个方法被重载了多次,我们一般使用这一个,下面说明一下其参数的意义:
参数名 | 说明 |
target | 属性动画作用的对象 |
propertyName | 属性名,代表要做什么动画 |
values | 形参,一般来说是传入两个参数,代表从..到.. |
下面我们分动画说明一下:
- 渐变动画:
// 通过代码完成渐变动画
private void doAlphaByCode(){
ObjectAnimator animator = ObjectAnimator.ofFloat(imageView,"alpha",1.0f,0.0f)
// 设置动画时常
.setDuration(1000);
// 设置重复次数
animator.setRepeatCount(2);
animator.start();
}
- 平移动画:
// 平移 X 轴
private void doTranslateXByCode(){
ObjectAnimator animator = ObjectAnimator.ofFloat(imageView,"translationX",0f,200f)
.setDuration(1000);
animator.start();
}
// 平移 Y 轴
private void doTranslateYByCode(){
ObjectAnimator animator = ObjectAnimator.ofFloat(imageView,"translationY",0f,200f)
.setDuration(1000);
animator.start();
}
- 缩放动画:
// 通过代码完成缩放动画
// 缩放 X 轴
private void doScaleXByCode(){
ObjectAnimator animator = ObjectAnimator.ofFloat(imageView,"scaleX",1f,3f);
animator.start();
}
// 缩放 Y 轴
private void doScaleByCode(){
ObjectAnimator animator = ObjectAnimator.ofFloat(imageView,"scaleX",1,3);
animator.start();
}
- 旋转动画:
// 通过代码完成旋转动画
private void doRotateByCode(){
ObjectAnimator animator = ObjectAnimator.ofFloat(imageView,"rotation",0f,360f)
.setDuration(1000);
animator.start();
}
这里我们放一下效果图(此处的平移和缩放,都只对 X 轴 进行操作):
注意:我们看到,当渐变动画结束后,点击其它动画就没有效果了,这是为什么呢?因为属性动画结束后,作用的对象会保留结束后的状态,所以当前作用对象是全透明的状态,所以看不见,我们试试最后进行渐变动画:
这时候就可以看到完整的动画效果了。
我们可以看到,缩放平移在一个动画中只能改变 X 轴 或者 Y 轴的值,但是实际需求中一般会要求同时改变,那么在聊组合动画之前,我们可以来说另一个东西。因为属性动画是来对属性进行操作的,而且它作用的不只是 View,所以我们可以对要作用的对象包装一下,来改变包装体的属性,从而达到改变作用对象的效果:
我们来新建一个类,ScaleImage.java:
public class ScaleImage {
private ImageView mImageView;
private float mScale;
public ScaleImage(ImageView imageView){
this.mImageView = imageView;
}
public float getmScale() {
return mScale;
}
public void setmScale(float mScale) {
this.mScale = mScale;
mImageView.setScaleX(mScale);
mImageView.setScaleY(mScale);
}
}
接下来我们对 ScaleImage 进行属性动画:
// 对 ScaleImage 进行属性动画,也叫为 ImageView 添加属性
private void addProperty(){
ScaleImage image = new ScaleImage(imageView);
ObjectAnimator animator =ObjectAnimator.ofFloat(image,"mScale",1.0f,3.0f)
.setDuration(1000);
animator.start();
}
但是我们可以看到,这里报了一个错误:Could not find property setter method setMScale on ...
这里说,没有在 ScaleImage 里面没有找到该属性的 setter 方法 setMScale(),所以这里我们就明白了,属性动画找属性时是通过你属性的 setter 方法,所以我们设置属性时,一定要与我们要做用的对象中 setter 属性一致,所以当我们需要给一个不熟悉的作用对象设置动画属性时,我们也就知道怎么给它找属性了。
那我们这里的错误是为什么呢?因为笔者是一键创建 getter 和 setter,这里自动创建时,把 setter 的 setMScale() 的 m 小写了,成了 setmScale() 导致了错误,我们只需要改回来就可以了。
先把代码放下来:
// 对 ScaleImage 进行属性动画,也叫为 ImageView 添加属性
private void addProperty(){
ScaleImage image = new ScaleImage(imageView);
ObjectAnimator animator =ObjectAnimator.ofFloat(image,"mScale",1.0f,3.0f)
.setDuration(1000);
animator.start();
}
接下来我们看一看效果图:
我们说了添加属性,就该说一下组合动画了,属性动画有很多组合动画的方式:
- 组合动画
第一种方式:使用 AnimatorSet:
// 组合动画方式一
private void doGroupByCode1(){
ObjectAnimator animator1 = ObjectAnimator.ofFloat(imageView,"TranslationX",0f,200f)
.setDuration(1000);
ObjectAnimator animator2 = ObjectAnimator.ofFloat(imageView,"alpha",1.0f,0.0f)
.setDuration(1000);
ObjectAnimator animator3 = ObjectAnimator.ofFloat(imageView,"rotation",0f,360f)
.setDuration(1000);
AnimatorSet set = new AnimatorSet();
// 串行
set.playSequentially(animator1,animator2);
// 并行
set.playTogether(animator2,animator3);
set.start();
/**
* 这里也可以使用这样
set.play(animator1).before(animator2);
set.play(animator2).with(animator3);
* 或者
set.play(animator2).with(animator3).after(animator1);
*/
}
串行的方式就上面两种,使用 playSequentially() 或者 before() 或者 after(),而并行的方式还有几种:
//组合动画并行1
private void doGroupByCode2(){
PropertyValuesHolder holder1 = PropertyValuesHolder.ofFloat("translationX",0f,200f);
PropertyValuesHolder holder2 = PropertyValuesHolder.ofFloat("rotation",0f,360f);
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(imageView,holder1,holder2);
animator.start();
}
//组合动画并行2
private void doGroupByCode3(){
imageView.animate().translationX(200f).rotation(360f).alpha(0.0f).start();
}
这里放一下第一种的效果图:
属性动画也是可以设置监听器的:
animator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
// 动画开始
}
@Override
public void onAnimationEnd(Animator animator) {
// 动画结束
}
@Override
public void onAnimationCancel(Animator animator) {
// 动画取消
}
@Override
public void onAnimationRepeat(Animator animator) {
// 动画重复
}
});
如果我们用不了这么多方法怎么办呢?我们就不传入 AnimatorListener,而是传入 AnimatorListenerAdapter:
animator.addListener(new AnimatorListenerAdapter() {
// 想要哪个或者哪几个方法都行
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
}
});
- xml 方法
和视图动画不一样的是,属性动画的 xml 文件是在 res 目录下新建一个 animator 文件,创建 xml 文件时,如果是单个属性动画,其根目录就是 <objectAnimator> 或者 <animator>,如果是动画集就是 <set>。
因为属性动画使用 xml 方式实现不同的动画效果方式差不多,这里就对一个动画组合进行讨论,先放代码:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:ordering="sequentially">
<objectAnimator
android:valueType="floatType"
android:propertyName="translationX"
android:valueFrom="1.0f"
android:valueTo="200f"
android:duration="1000"/>
<set
android:ordering="together">
<objectAnimator
android:valueType="floatType"
android:propertyName="translationX"
android:valueTo="-200f"
android:duration="2000"/>
<objectAnimator
android:valueType="floatType"
android:propertyName="translationY"
android:valueTo="-200f"
android:duration="2000"/>
<objectAnimator
android:valueType="floatType"
android:propertyName="rotation"
android:valueTo="720f"
android:duration="2000"/>
</set>
<objectAnimator
android:valueType="floatType"
android:propertyName="alpha"
android:valueTo="0.0f"
android:duration="1000"/>
</set>
对 <set> 的重要属性说明:
属性名 | 说明 |
android:ordering="" | 动画集的运行方式:并行/串行 |
对 <objectAnimator> 的重要属性说明:
属性名 | 说明 |
android:valueType="" | 属性的类型 |
android:propertyName="" | 属性名 |
android:valueFrom="" | 当前属性动画开始时状态 |
android:valueTo="" | 当前属性动画结束时状态 |
android:duration="" | 动画时常,单位:毫秒 |
android:repeatCount="" | 动画重复次数 |
接下来是在代码中加载 xml 动画文件了:
// 使用 xml 进行组合动画
private void doGroupByXML(){
Animator animator = AnimatorInflater.loadAnimator(this,R.animator.animator_group);
// 设置动画作用对象
animator.setTarget(imageView);
animator.start();
}
效果图:
项目地址:源代码