简析
大家知道,我们在开发一款产品的时候为了达到良好的用户体验,我们可以在应用中适当的加上一些动画效果,譬如平移、缩放、旋转等等,但是这些常用的动画在Android很早期的版本中就存在了,我们称之为传统动画,传统动画一般分为Tween动画和Frame动画,这也是我们最常用的的动画,统称为Animation。传统的Animation动画实现上是通过不停的调用View的onDraw方法来重新绘制View来实现的。
在Android3.0以后,Google为Android新增了属性动画框架Animator,为什么叫做属性动画呢?因为属性动画Animator不像传统动画那样需要不停调用onDraw方法绘制界面,而且可以通过get、set方法,去真实的改变一个view的属性的。Animation动画仅仅给用户一种“虚假”的动画效果,其执行动画view并没有正在的改变自身的属性,例如位置。而属性动画Animator,是真真正正的通过代码将view“动画”到了指定的位置了。
传统动画不能改变view真实属性
下面,我们来看看传统动画实现的效果,我们使用很简单的界面,给指定的ImageView设置上平移动画,给ImageView指定点击事件监听,方便我们测试传统动画的局限性。
public class MainActivity extends Activity { private Button mButton; private ImageView mImageView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.activity_main); mButton = (Button) findViewById(R.id.button); mImageView = (ImageView) findViewById(R.id.imageview); mButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { playAnim(); } }); mImageView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { showToast(); } }); } /** * 播放动画 */ protected void playAnim() { TranslateAnimation ta = new TranslateAnimation(0.0f, 200.0f, 0.0f, 0.0f); ta.setDuration(1000); ta.setFillAfter(true); mImageView.startAnimation(ta); } protected void showToast() { Toast.makeText(MainActivity.this, "Hello", Toast.LENGTH_SHORT).show(); } }
看到运行效果了吧,我们给ImageView的小机器人设置了点击事件并且弹出Toast了,当我们点击Button时,ImageView上的平移动画被执行了,并且平移到了指定的位置(200px),然而我们在此时的ImageView上点击时,并没有弹出Toast提示,也就是说点击的位置不在那里,当我回头再次点击ImageView最初的位置时,Toast又被show了出来,这是不是很“诡异”?是的,上面的小例子足以说明,传统的Animaion动画只能改变动态视觉效果的动画,并不能真实的去改变一个View的属性(位置等)。而且,传统的Animation动画是通过不停的调用onDraw方法去绘制而完成的效果,这样的实现方式很消耗资源(cpu)的。
API概述
类 | 描述 |
ValueAnimator | 属性动画时序引擎也计算属性动画的值。它拥有所有的核心功能,计算动画值,并包含每个动画,有关时序的详细信息是否动画重复,听众接收更新事件,并设置自定义类型的能力评估。有两件,以生动活泼的属性:动画值计算和设置这些对象的属性动画值。ValueAnimator不进行第二件,所以你一定要更新计算值ValueAnimator和修改你想用自己的逻辑动画的对象。 |
ObjectAnimator | ValueAnimator的子类,允许你设置一个目标对象和对象属性的动画。当计算出一个新的动画值,本类更新相应的属性。你大部分情况使用ObjectAnimator,因为它使得动画的目标对象的值更简单。然而,有时你直接使用ValueAnimator,因为ObjectAnimator有一些限制,如对目标对象目前要求的具体acessor方法。 |
AnimatorSet | 提供机制,以组合动画一起,让他们关联性运行。你可以设置动画一起播放,顺序,或在指定的延迟之后。 |
内容来自于:http://developer.android.com/guide/topics/graphics/prop-animation.html
ObjectAnimator
执行单个动画
通常我们会使用ObjectAnimator类来为我们的view设置动画,下面我们来简单的看一下,使用了属性动画后,我们上述的小例子中的ImageView会发生哪些变化?修改上面的playAnim方法,将普通动画换成属性动画:
protected void playAnim() { ObjectAnimator.ofFloat(mImageView, "translationX", 0.0f, 200.0f).setDuration(1000).start(); }
运行效果如上图所示。对比上面的那幅图看,发现当属性动画执行后,不但从视觉上改变了ImageView的位置,而且ImageView上的点击事件的位置也跟着变化了,说明使用属性动画的View,实际上是真实的改变了一个View的属性的。
从上面的一行代码中,可以发现属性动画使用上是非常简单的,ObjectAnimator中的ofFloat方法实际上是static方法,而且返回值还是一个ObjectAnimator对象。ofFloat的参数也很简单,第1个参数是指定需要执行动画的view,第2个参数是动画模式,第三个参数是可变的数组,这里需要描述动画的初始位置和终点位置的坐标。
除了上面例子的中的translationX属性,还可以指定translationY属性,表示ImageView沿着Y轴的方向平移,然后我们也可以指定X或者Y,那么translationX和X或者translationY和Y的区别,就是translationX是指定了ImageView在X轴上的偏移量,而单纯的指定X表示ImageView被移动到指定的X轴上的位置,这点跟View的scrollTo和scrollBy方法有点类似。此外除了平移之外,还可以指定动画的模式有:
translationX、translationY
rotation、rotationX、rotationY
scaleX、scaleY
X、Y
alpha
ObjectAnimator继承自ValueAnimator,要指定一个对象及该对象的一个属性,当属性值计算完成时自动设置为该对象的相应属性,即完成了Property Animation的全部两步操作。实际应用中一般都会用ObjectAnimator来改变某一对象的某一属性,但用ObjectAnimator有一定的限制,要想使用ObjectAnimator,应该满足以下条件:
- 对象应该有一个setter函数:set<PropertyName>(驼峰命名法)
- 如上面的例子中,像ofFloat之类的工场方法,第一个参数为对象名,第二个为属性名,后面的参数为可变参数,如果values…参数只设置了一个值的话,那么会假定为目的值,属性值的变化范围为当前值到目的值,为了获得当前值,该对象要有相应属性的getter方法:get<PropertyName>
- 如果有getter方法,其应返回值类型应与相应的setter方法的参数类型一致。
如果上述条件不满足,则不能用ObjectAnimator,应用ValueAnimator代替。
多个动画同时执行
PropertyValuesHolder
有时候我们需要同时执行多个属性动画的叠加效果的时候,可以使用PropertyValuesHolder工具类来“装载”多种动画,然后调用ObjectAnimator.ofPropertyValuesHolder()方法将装载好的动画交给ObjectAnimator去执行,例如:
protected void playAnim() { PropertyValuesHolder p1 = PropertyValuesHolder.ofFloat("translationX",0.0f, 200.0f); PropertyValuesHolder p2 = PropertyValuesHolder.ofFloat("translationY",0.0f, 200.0f); PropertyValuesHolder p3 = PropertyValuesHolder.ofFloat("rotation",0.0f, 360.0f); ObjectAnimator.ofPropertyValuesHolder(mImageView, p1, p2, p3).setDuration(2000).start(); }
AnimatorSet
跟普通的View动画一样,执行多种动画效果时,属性动画也提供了动画集方便我们执行多种动画。
protected void playAnim() { Animator animator1 = ObjectAnimator.ofFloat(mImageView, "translationX",0.0f, 200.0f); Animator animator2 = ObjectAnimator.ofFloat(mImageView, "translationY",0.0f, 200.0f); Animator animator3 = ObjectAnimator.ofFloat(mImageView, "rotation",0.0f, 360.0f); AnimatorSet set = new AnimatorSet(); set.playTogether(animator1, animator2, animator3); set.setDuration(2000); set.start(); }
AnimationSet提供了一个把多个动画组合成一个组合的机制,并可设置组中动画的时序关系,如同时播放,顺序播放等。
以下例子同时应用5个动画:
- 播放anim1;
- 同时播放anim2,anim3,anim4;
- 播放anim5。
AnimatorSet bouncer = new AnimatorSet(); bouncer.play(anim1).before(anim2); bouncer.play(anim2).with(anim3); bouncer.play(anim2).with(anim4) bouncer.play(anim5).after(amin2); animatorSet.start();
动画监听事件
Android属性动画也为我们提供了对动画播放过程的监听器,我们只需要调用Animator.addListener()方法,将AnimatorListener对象传递进去就可以了:
anim.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { //动画开始时执行 } @Override public void onAnimationRepeat(Animator animation) { //动画重复时执行 } @Override public void onAnimationEnd(Animator animation) { //动画结束时执行 } @Override public void onAnimationCancel(Animator animation) { //动画被取消时执行 } });
Animator.AnimatorListener对象下,有4个未实现的方法,我们可以分别实现一下其中的方法,就可以方便的去监听动画执行整个过程了。但是Animator.AnimatorListener对象不够简洁,因为大部分时候我们只需要监听动画结束时的事件即可,那么Android也为我们提供好了一个简化的监听对象AnimatorListenerAdapter,AnimatorListenerAdapter
是个抽象类,其下面共有onAnimationCancel(),onAnimationEnd(),onAnimationPause(),onAnimationRepeat(),onAnimationResume(),onAnimationStart()几个方法供我们调用,既然我们一般情况下仅仅是需要动画结束时监听,那么我们就按照如下方式使用:
anim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { // TODO Auto-generated method stub super.onAnimationEnd(animation); } });
ValueAnimator
概念
ValueAnimator包含Property Animation动画的所有核心功能,如动画时间,开始、结束属性值,相应时间属性值计算方法等。应用Property Animation有两个步聚:
- 计算属性值
- 根据属性值执行相应的动作,如改变对象的某一属性。
ValuAnimiator只完成了第一步工作,如果要完成第二步,需要实现ValueAnimator.onUpdateListener接口,这个接口只有一个函数onAnimationUpdate(),在这个函数中会传入ValueAnimator对象做为参数,通过这个ValueAnimator对象的getAnimatedValue()函数可以得到当前的属性值如:
protected void playAnim() { ValueAnimator animator = ValueAnimator.ofInt(0, 10); animator.setDuration(100); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { Log.i("TAG", "AnimatedValue : " + animation.getAnimatedValue().toString()); } }); animator.start(); }
从上面的例子可以看到ValueAnimator类实现的是动画的插值因子的计算,大部分情况下我们使用ObjectAnimator就可以轻松实现很多种动画效果了,然后使用ObjectAnimator的View必须满足有getter和setter方法,若没有这些方法,使用ObjectAnimator的动画是无法实现的,我们只好考虑使用ObjectAnimator的父类ValueAnimator了,ValueAnimator实现动画不需要View含有getter和setter方法,它是通过计算动画的插值因子,我们根据这个插值自定义动画效果就可以了。
TypeEvaluator
TypeEvaluator是一个接口,通过实现该接口下的evaluate方法,可以实现我们自定义的各种复杂效果的动画:
ValueAnimator animator =ValueAnimator.ofObject(new TypeEvaluator<Number>() { @Override public Number evaluate(float fraction, Number startValue,Number endValue) { // TODO Auto-generated method stub return null; } });
上面就是实现的TypeEvaluator接口,下面有个未实现的方法,这个回调函数中提供如下三个参数:
fraction:插值因子,取值范围0~1
startValue:动画的起始值
endValue:动画的结束值
我们可以根据这3个参数来编写计算自己需要的动画效果,样式很多样化的,不仅仅是ObjectAnimator里几种动画类型了。由此可以看出,ValueAnimator比ObjectAnimator更加灵活,方式更加繁多,我们自定义动画效果时,可以使用ValueAnimator实现TypeEvaluator接口来写自己的动画算法,实现比较复杂的动画。
关于属性动画的详细介绍,可以参考Google提供的官方文档,里面有非常详细的讲述:
http://developer.android.com/guide/topics/graphics/prop-animation.html