- 属性动画简介
Android开发过程中,适当的使用一些动画可以让自己的应用看起来更棒更炫。最初的时候Google为了实现动画,主要提供了两种基本动画:
帧动画:将一个完整的动画拆分成一张张单独的图片,然后再将它们连贯起来进行播放
补间动画:无需逐一定义每一帧,只要定义开始、结束的帧,和指定动画持续时间。
到了Android3.0,Google推出了一种新的动画模式,属性动画。属性动画是对补间动画的进一步完善和强化。
- 为什么引入属性动画:
关于这个问题,可以借鉴一下郭霖的一段话:
补间动画机制其实还算是比较健全的,可以让我们实现View的移动、缩放、旋转和淡入淡出效果,并且我们还可以借助AnimationSet来将这些动画效果组合起来使用,除此之外还可以通过配置Interpolator来控制动画的播放速度等等等等。那么这里大家可能要产生疑问了,既然之前的动画机制已经这么健全了,为什么还要引入属性动画呢?
其实上面所谓的健全都是相对的,如果你的需求中只需要对View进行移动、缩放、旋转和淡入淡出操作,那么补间动画确实已经足够健全了。但是很显然,这些功能是不足以覆盖所有的场景的,一旦我们的需求超出了移动、缩放、旋转和淡入淡出这四种对View的操作,那么补间动画就不能再帮我们忙了,也就是说它在功能和可扩展方面都有相当大的局限性,那么下面我们就来看看补间动画所不能胜任的场景。
注意上面我在介绍补间动画的时候都有使用“对View进行操作”这样的描述,没错,补间动画是只能够作用在View上的。也就是说,我们可以对一个Button、TextView、甚至是LinearLayout、或者其它任何继承自View的组件进行动画操作,但是如果我们想要对一个非View的对象进行动画操作,抱歉,补间动画就帮不上忙了。可能有的朋友会感到不能理解,我怎么会需要对一个非View的对象进行动画操作呢?这里我举一个简单的例子,比如说我们有一个自定义的View,在这个View当中有一个Point对象用于管理坐标,然后在onDraw()方法当中就是根据这个Point对象的坐标值来进行绘制的。也就是说,如果我们可以对Point对象进行动画操作,那么整个自定义View的动画效果就有了。显然,补间动画是不具备这个功能的,这是它的第一个缺陷。
然后补间动画还有一个缺陷,就是它只能够实现移动、缩放、旋转和淡入淡出这四种动画操作,那如果我们希望可以对View的背景色进行动态地改变呢?很遗憾,我们只能靠自己去实现了。说白了,之前的补间动画机制就是使用硬编码的方式来完成的,功能限定死就是这些,基本上没有任何扩展性可言。
最后,补间动画还有一个致命的缺陷,就是它只是改变了View的显示效果而已,而不会真正去改变View的属性。什么意思呢?比如说,现在屏幕的左上角有一个按钮,然后我们通过补间动画将它移动到了屏幕的右下角,现在你可以去尝试点击一下这个按钮,点击事件是绝对不会触发的,因为实际上这个按钮还是停留在屏幕的左上角,只不过补间动画将这个按钮绘制到了屏幕的右下角而已。正是因为这些原因,功能更为完善和强大的属性动画就应运而生了。
- 继承关系
从图中可以看出,补间动画主要是使用android.view.animation.Animation包下面的五个类来实现不同的动画效果;而属性动画主要是使用android.animation.Animator的ValueAnimator以及其子类ObjectAnimator来实现不同的动画效果。
- ValueAnimator
从属性动画相关对象的继承关系可以看出,ValueAnimator是比较核心的一个类。官方文档的介绍是:this class provides a simple timing engine for running animations which calculate animated values and set them on target objects...也就是说ValueAnimator对象内部维持了一个简单的时间引擎,计算运行动画的具体值同时把它们设置给目标对象。通俗的讲就是,ValueAnimator的内部使用一种时间循环的机制来计算值与值之间的动画过渡,我们只需要将初始值和结束值或者中间值提供给ValueAnimator,并且设置动画运行时长,那么ValueAnimator就会自动完成从初始值平滑过渡到结束值这样的效果。而且,ValueAnimator还负责管理动画的播放次数、播放模式、以及对动画设置监听器等。说的废话比较多,先举个栗子,切身体会一下!
1.效果图
2.具体代码
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0f, 1f);
valueAnimator.setDuration(3000);
//设置监听,把当前AnimatedValue的值时时显示到EditText上面
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float currentValue = (float) animation.getAnimatedValue();
StringBuilder sb = new StringBuilder();
et_Animator.setText(sb.append(currentValue).toString());
}
});
valueAnimator.start();
ValueAnimator比较常用的方法有:设置AnimatorValue区间范围的三个主要方法
ofFloat(),ofInt(),ofObject()
还有设置延时播放时间的setStartDelay()方法,设置重复次数的setRepeatCount()方法,设置重复模式的setRepeatMode()
暂停动画的pause()方法,恢复动画的resume()等等,还有很多方法,具体大家可以查阅文档,把这个核心类的方法掌握了,使用ObjectAnimator就能更加得心应手,因为从继承关系中我们可以知道ObjectAnimator是ValueAnimator的子类。
- ObjectAnimator
ObjectAnimator对象应该是我们产生属性动画效果最常用的类了,ValueAnimator仅仅是对数值进行平滑的过渡变化,而如果我们想对一个对象的任意属性进行动画效果,就需要使用ObjectAnimator对象了,还是先举个简单栗子吧。
1.效果图
2.具体代码
//ofFloat(需要实现动画的控件ID,需要实现的动画效果名称,变化的区间范围)
ObjectAnimator objAnim = bjectAnimator.ofFloat(et_Animator,"alpha",0f, 1f, 0f, 1f);
objAnim.setDuration(6000);
objAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float currentValue = (float) animation.getAnimatedValue();
StringBuilder sb = new StringBuilder();
et_Animator.setText(sb.append(currentValue).toString());
}
});
objAnim.start();
以上代码实现的效果是:在6000毫秒内,让EditText控件平滑的实现了从完全透明>完全不透明>完全透明>完全不透明的过渡效果;并且对整个动画执行过程设置了监听,时时把当前动画的AnimatorValue值显示到EditText控件上面。
根据设置参数的不同,使用ObjectAnimator对象还可以实现translationX(竖直平移),translationY(水平平移),rotation(旋转),scaleX(水平缩放),scaleY(竖直缩放)等其他效果。当然还可以使用AnimatorSet对象来产生组合动画效果。
- 效果图
- 具体代码
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private Button btn_OperationAnimator;
private EditText et_Animator;
private RadioGroup radioGroup;
private ObjectAnimator objAnim;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
et_Animator = (EditText) findViewById(R.id.et_Animator);
radioGroup = (RadioGroup) findViewById(R.id.radioGroup);
btn_OperationAnimator = (Button) findViewById(R.id.btn_OperationAnimator);
btn_OperationAnimator.setOnClickListener(this);
}
@Override
public void onClick(View v) {
int checkedId = radioGroup.getCheckedRadioButtonId();
switch (checkedId) {
case R.id.rb_value:
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0f, 1f);
valueAnimator.setDuration(3000);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float currentValue = (float) animation.getAnimatedValue();
StringBuilder sb = new StringBuilder();
et_Animator.setText(sb.append(currentValue).toString());
}
});
valueAnimator.start();
break;
case R.id.rb_alpha:
objAnim = ObjectAnimator.ofFloat(et_Animator,"alpha",0f, 1f, 0f, 1f);
objAnim.setDuration(3000);
objAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float currentValue = (float) animation.getAnimatedValue();
StringBuilder sb = new StringBuilder();
et_Animator.setText(sb.append(currentValue).toString());
}
});
objAnim.start();
break;
case R.id.rb_translationX:
objAnim = ObjectAnimator.ofFloat(et_Animator,"translationX",0f, 100f, 0f, 200f, 0f);
objAnim.setDuration(3000);
objAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float currentValue = (float) animation.getAnimatedValue();
StringBuilder sb = new StringBuilder();
et_Animator.setText(sb.append(currentValue).toString());
}
});
objAnim.start();
break;
case R.id.rb_translationY:
objAnim = ObjectAnimator.ofFloat(et_Animator,"translationY",0f, 100f, 0f, 200f, 0f);
objAnim.setDuration(3000);
objAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float currentValue = (float) animation.getAnimatedValue();
StringBuilder sb = new StringBuilder();
et_Animator.setText(sb.append(currentValue).toString());
}
});
objAnim.start();
break;
case R.id.rb_rotation:
objAnim = ObjectAnimator.ofFloat(et_Animator,"rotation",0f, 180f, 0f, 360f, 0f);
objAnim.setDuration(6000);
objAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float currentValue = (float) animation.getAnimatedValue();
StringBuilder sb = new StringBuilder();
et_Animator.setText(sb.append(currentValue).toString());
}
});
objAnim.start();
break;
case R.id.rb_scaleX:
objAnim = ObjectAnimator.ofFloat(et_Animator,"scaleX", 4f, 1f, 2f, 1f);
objAnim.setDuration(3000);
objAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float currentValue = (float) animation.getAnimatedValue();
StringBuilder sb = new StringBuilder();
et_Animator.setText(sb.append(currentValue).toString());
}
});
objAnim.start();
break;
case R.id.rb_scaleY:
objAnim = ObjectAnimator.ofFloat(et_Animator,"scaleY",1f, 5f, 2f, 1f);
objAnim.setDuration(3000);
objAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float currentValue = (float) animation.getAnimatedValue();
StringBuilder sb = new StringBuilder();
et_Animator.setText(sb.append(currentValue).toString());
}
});
objAnim.start();
break;
case R.id.rb_animatorSet:
ObjectAnimator objAnim_alpha = ObjectAnimator.ofFloat(et_Animator,"alpha",0f, 1f, 0f, 1f);
ObjectAnimator objAnim_translationX = ObjectAnimator.ofFloat(et_Animator,"translationX",0f, 100f, 0f, 200f, 0f);
ObjectAnimator objAnim_translationY = ObjectAnimator.ofFloat(et_Animator,"translationY",0f, 100f, 0f, 200f, 0f);
ObjectAnimator objAnim_rotation = ObjectAnimator.ofFloat(et_Animator,"rotation",0f, 180f, 0f, 360f, 0f);
ObjectAnimator objAnim_scaleX = ObjectAnimator.ofFloat(et_Animator,"scaleX", 4f, 1f, 2f, 1f);
ObjectAnimator objAnim_scaleY = ObjectAnimator.ofFloat(et_Animator,"scaleY",1f, 5f, 2f, 1f);
AnimatorSet animSet = new AnimatorSet();
animSet.play(objAnim_alpha)
.before(objAnim_rotation) //在play动画之后执行
.after(objAnim_scaleX) //在play动画之前执行
.after(objAnim_scaleY)
.with(objAnim_translationX) //和play动画同时执行
.with(objAnim_translationY);
animSet.setDuration(5000);
animSet.start();
break;
}
}
}
栗子很简单,算是做个自我巩固以及方便日后回顾,如果不小心帮到别人了那自然也是极好的。最后附上Demo下载。