Android动画教程之属性动画详解
简介
android 开发中,总是需要一些动画来优化用户的交互体验,提高用户满意度。因此,google 为我们提供了一些用于处理动画效果的动画框架。android 的动画框架分为两类:
- 传统动画(animation):通过系统不断调用ondraw方法重绘界面,来达到动画的效果。
- 属性动画(animator):通过操纵一个属性的get/set方法,真实地改变目标的某些属性。
传统动画框架的局限性
既然有了传统动画框架,google 为什么还要创造一个属性动画框架呢?
我们下面举个例子来说明一下传统动画的局限性。
在布局中加入一个 imageview 和一个 button,点击 imageview 后弹出一个 toast,点击 button 后使 imageview 展现一个向右平移的动画效果。
下面是使用传统动画实现的代码:
translateanimation animation = new translateanimation(0,200,0,0); // 平移动画x轴移动200,y轴不动 animation.setduration(1000); // 动画时长 animation.setfillafter(true); // 使动画结束后停留在结束的位置 mivpicture.startanimation(animation);
运行后,imageview 确实进行了我们预期的平移的效果。可是当我们尝试点击 imageview 当前的位置时,却没有 toast 弹出。我们再尝试去点击 imageview 开始动画前的位置,却成功弹出了 toast。
这就是传统动画很大的局限性:
- 它仅仅是重绘了控件,改变了其显示的位置。但真正事件响应的位置,却并没有发生改变。因此传统动画不适合做具有交互的动画效果。仅仅能做一些显示的动画效果。
- 传统动画是不断通过 ondraw() 方法重绘界面,必然会十分耗费gpu资源。
- 传统动画所支持的动画类型少,仅有旋转、缩放、位移、透明度这四种动画效果。虽然通过组合可以实现丰富的效果,但相比直接通过改变属性来实现的属性动画来说,还是有很大的局限性的。
因此,google 为我们提供了一套全新的属性动画框架,来让我们实现更丰富的动画效果。
objectanimator
objectanimator 是属性动画中,最简单也最常用的一个对象。
实现 animation 框架的功能
平移
前文提到的使 imageview 向右平移 200 像素的动画效果,使用属性动画只需要很简单的几句代码即可实现:
objectanimator.offloat(mivpicture,"translationx",0f,200f) .setduration(1000) .start();
我们来分析一下这一句代码。我们调用了offloat代码,并传入三个参数。
第一个参数是动画需要操纵的目标,在这里是我们的 imageview。
第二个参数是所需要操纵的目标所具备的属性名称。
第三个参数是这个动画变化的取值范围。
最后设置一下它的动画的属性,便可以 start 了。
这次我们再次点击 imageview 目前的位置,成功地弹出了 toast。这证实了属性动画是通过改变物体的属性来达到动画效果的理论。
当我们需要改变 y 坐标时,只需要把 "translationx" 变为 "translationy" 即可。
其实 ,只要google对一个对象的某个属性提供了get和set方法,我们就可以使用这个属性来实现动画效果。
其实我们还能用 x y 两个属性实现之前的动画效果,那么对象属性中 x 的 y 与 translationx translationy 有什么区别呢?
translationx translationy指的是物体的偏移量,而x y则表示它最终到达的绝对位置。
旋转
旋转属性使用的是 "rotation" 属性,后面的变换范围的单位是角度。
比如想让 imageview 旋转90度,只需要
objectanimator.offloat(mivpicture,"rotation",0f,90f) .setduration(1000) .start();
其他
其实属性动画能操纵的属性,只要具有 set、get 方法,都可以进行操纵。如 scalex、scaley 等等...
插值器
android 为我们内置了插值器,使我们的动画更为自然。比如可以让我们的平移动画像物体的重力加速度由快到慢的 accelerate 等等
android中内置了七种插值器,分别是
- accelerate
- decelerate
- accelerate/decelerate
- anticipate
- overshoot
- anticipate/overshoot
- bounce
要应用插值器,可以调用 objectanimator 的 setinterpolator 方法, new 出对应的插值器作为参数(xxxinterpolator)。比如下面这段代码:
animator.setinterpolator(new accelerateinterpolator());
通过插值器,我们可以让动画的效果更佳自然。
多种属性动画同时作用
当我们把几种动画按顺序写下时,运行程序,会发现效果是三种属性动画的叠加。由此可以发现,属性动画在调用 start 方法后,实际上是一个异步的过程。因此我们就可以看到三个属性动画同时作用的效果。通过这样的方法,其实就可以实现多种属性动画同时作用的效果:
objectanimator.offloat(mivpicture,"translationx",0f,200f).setduration(1000).start(); objectanimator.offloat(mivpicture,"rotationx",0f,360f).setduration(1000).start(); objectanimator.offloat(mivpicture,"translationy",0f,200f).setduration(1000).start();
其实 google 为我们提供了更好的方法,来实现这样的效果。
我们可以使用 propertyvaluesholder 来实现。其构造函数仅仅比 objectanimator 少了一个作用对象参数。之后通过objectanimator 的 ofpropertyvaluesholder 方法,传入作用对象以及要同时作用的 propertyvaluesholder 即可执行。可以看到下面的代码示例:
propertyvaluesholder p1 = propertyvaluesholder.offloat("translationx",0f,200f); propertyvaluesholder p2 = propertyvaluesholder.offloat("rotationx",0f,360f); propertyvaluesholder p3 = propertyvaluesholder.offloat("translationy",0f,200f); objectanimator.ofpropertyvaluesholder(mivpicture,p1,p2,p3).setduration(1000).start();
运行后可以发现,与之前的效果是相同的。
那既然两种方法效果一样,这样相比之前有什么好处么?
- 其实 google 在 propertyvaluesholder 内部进行了一些优化,使得我们使用多个属性动画时更加有效率,节省系统资源。
animatorset 属性集合
playtogether 方法
我们其实还可以通过 animatorset,来实现同样的效果。这里我们调用了 set 的 playtogether 方法,使得这些方法同时执行:
animatorset set = new animatorset(); objectanimator animator1 = objectanimator.offloat(mivpicture, "translationx", 0f, 200f); objectanimator animator2 = objectanimator.offloat(mivpicture, "rotationx", 0f, 360f); objectanimator animator3 = objectanimator.offloat(mivpicture, "translationy", 0f, 200f); set.playtogether(animator1,animator2,animator3); set.setduration(1000); set.start();
playsequentially方法
除了 playtogether 方法外,animatorset 还提供了 playsequentially 方法,它可以使得动画按顺序执行。具体顺序取决于调用时的参数顺序。
animatorset set = new animatorset(); objectanimator animator1 = objectanimator.offloat(mivpicture, "translationx", 0f, 200f); objectanimator animator2 = objectanimator.offloat(mivpicture, "rotationx", 0f, 360f); objectanimator animator3 = objectanimator.offloat(mivpicture, "translationy", 0f, 200f); set.playsequentially(animator1,animator2,animator3); set.setduration(1000); set.start();
play 与 with、after 方法
我们除了可以用上述方法来让动画按顺序执行外,也可以通过 animatorset 的 play、with、after、before 等方法相组合来控制动画播放关系。
例如如下的代码就可以实现先平移,再旋转的效果
set.play(animator1).with(animator3); set.play(animator2).after(animator1);
动画监听事件
通过下面的代码,我们可以实现按钮按下后渐隐的效果。
mbtnpress.setonclicklistener(new onclicklistener() { @override public void onclick(view v) { objectanimator animator = objectanimator.offloat(mbtnpress,"alpha",1f,0f); animator.setduration(1000); animator.start(); } });
但如果我们想要在动画播放完成后再执行一些操作的话,又该如何实现呢?
- 我们可以使用 objectanimator 的 addlistener方法,传入一个animatorlistener,为动画设置监听事件。
animatorlistener
一个animatorlistener,需要实现四个方法,分别是:
- onanimationstart
- onanimationend
- onanimationcancel
- onanimationrepeat
它们的回调时机我们根据字面意思便可以理解。大部分时候,我们需要实现的是onanimationend方法。
animator.addlistener(new animatorlistener() { @override public void onanimationstart(animator animation) { } @override public void onanimationend(animator animation) { toast.maketext(mainactivity.this,"animation end",toast.length_short).show(); } @override public void onanimationcancel(animator animation) { } @override public void onanimationrepeat(animator animation) { } });
animatorlisteneradapter
如果每次监听都需要实现这么多方法,未免太麻烦了一点。因此 android 为我们提供了另一种方法来添加动画的监听事件:在添加 animatorlistener 的时候,传入 animatorlisteneradapter 即可。这样我们就只需要实现自己需要的方法即可。
animator.addlistener(new animatorlisteneradapter() { @override public void onanimationend(animator animation) { super.onanimationend(animation); toast.maketext(mainactivity.this,"animation end",toast.length_short).show(); } });
valueanimator
简介
valueanimator 本身不作用于任何一个属性,也不提供任何一种动画。它就是一个数值发生器,可以产生想要的各种数值。android 系统为它提供了很多计算数值的方法,如 int、float 等等。我们也可以自己实现计算数值的方法。其实,在属性动画中,如何产生每一步的动画效果,都是通过 valueanimator 计算出来的。
比如我们要实现一个从 0-100 的位移动画。随着动画时间的持续,它产生的值也会从 0-100 递增。通过这个 valueanimator 产生的值,再进行属性的设置即可。
那么 valueanimator 究竟是如何产生这些值的呢?
- 首先 valueanimator会根据会根据动画已进行的时间与它持续的总时间的比值,产生一个0-1的时间因子。有了这样的时间因子,经过相应的变换,就可以根据初始值和最终值来生成中间的相应值。同时,通过插值器的使用,我们还可以进一步控制每一个时间因子产生值的变化速率。如果我们使用的是线性插值器,那么它生成值的时候就会呈一个线性变化。如果我们使用一个加速度插值器,那么它生成值时便会呈一个二次曲线,增长率越来越快。
由于 valueanimator 不作用于任何一个属性,也不提供任何一种动画。因此并没有 objectanimator 使用得广泛。
实际上,objectanimator 就是基于 valueanimator 进行的一次封装。我们可以查看 objectanimator 的源码,会发现它继承自 valueanimator,是它的一个子类。正是 valueanimator 产生的变化值,才使得 objectanimator 可以将它应用于各个属性。
使用方法
我们可以通过 valueanimator 的 ofxxx 产生一个 xxx 类型的值(如ofint),然后为 valueanimator 添加一个更新的回调事件。在回调事件中,通过参数 animation 的 getanimationvalue() 方法,来获取对应的 value。有了这个值,我们就可以实现我们所有想要的动画效果。
比如此处就通过 valueanimator 实现了一个计时器的动画效果。
valueanimator animator = valueanimator.ofint(0,100); animator.setduration(5000); animator.addupdatelistener(new animatorupdatelistener() { @override public void onanimationupdate(valueanimator animation) { integer value = (integer) animation.getanimatedvalue(); mbutton.settext(""+value); } }); animator.start();
自定义数值生成器
前面提到,valueanimator 可以创建自定义的数值生成器,做法就是调用 valueanimator 的 ofobject 方法,创建一个 typeevaluator 作为参数。之后我们可以通过重写 typeevaluator 的 evaluate 方法,来按照自己的规则返回具体的值。
valueanimator animator = valueanimator.ofobject(new typeevaluator() { @override public object evaluate(float fraction, object startvalue, object endvalue) { //计算 return null; //返回值 } });
我们来看一下 evaluate 方法的几个参数
- float fraction:前面提到的时间因子
- object startvalue:起始值
- object endvalue:结束值
通过这三个值,我们就可以经过计算产生所有我们想要的值。
其实,通过 typeevaluator,我们不光能产生普通的数据,还能结合泛型,我们还能定义更加复杂的数据:
我们可以在创建 typeevaluator 时指定具体类型,来达到更丰富的效果。比如这里就用到了一个名为 pointf 的数据类型:
valueanimator animator = valueanimator.ofobject(new typeevaluator<pointf>() { @override public pointf evaluate(float fraction, pointf startvalue, pointf endvalue) { //计算 return null; //返回值 } });
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对的支持。
下一篇: Ajax的概述与实现过程