Android属性动画之实践篇
一,写在前面
android动画分为view动画,帧动画,属性动画。 view动画包含四种动画:scaleanimation,translateanimation,alphaanimation,rotateanimation,分别对应缩放,平移,透明度,旋转。view动画有个特点:需要作用在具体的控件view上,可以采用xml文件,java代码两种实现控件的动画效果。 帧动画需要提供使用xml文件定义一个animationdrawable对象,具体来说是在xml文件中提供根节点,并定义若干子节点,给的android:drawable属性设置图片id。然后将animationdrawable对象设置为控件view的background,并通过animationdrawable$start方法来启动播放动画。注意:使用帧动画时,尽量避免使用尺寸过大的图片,否则容易发生oom。
上面简单介绍了view动画和帧动画,由于不是本篇文章描述重点,故不提供相关代码展示两者的具体使用,有需要的哥们可以在网上自行查阅相关知识点。那为什么要使用属性动画呢什么是属性动画呢属性动画的实现方式有哪些呢属性动画有哪些需要注意的呢接下来结合理论加实践的方式一一解答。
二,属性动画的使用
如果想实现一个按钮的背景色从绿色到红色的效果,view动画和帧动画都无法实现,那么这样属性动画就派上用场了。属性动画可以对任意对象的属性进行动画效果,并不仅仅指view对象。在api层面上来看,需要提供一个对象,并提供该对象的属性(这里听起来可能比较懵,是什么样的对象,什么样的属性呢不懂没关系,后面展示一波代码就很容易理解了),属性动画可以在某一段时间内,实现对象的属性值从起始值到最终值的变化。在具体实现效果的层面来看,属性动画都是带来ui的变化。
值得一提的是,属性动画的默认执行时间是300ms,执行一帧的时间是10ms。使用属性动画的api最低版本是11,也就是android3.0版本。属性动画常用的几个类:objectanimator,valueanimator,animatorset。它们直接关系如下图:
第2行,标签对应animatorset对象,它是属性动画集合,可以在其里面存放多个子动画。第3行,ordering属性表示多个子动画执行方式,有两种:sequentially(顺序执行),together(同时执行,属于默认值)。第4行,标签对应objectanimator对象,下面详细介绍它的一些属性:
propertyname:属性动画作用对象的属性名称; duration:属性动画执行的时长,单位是ms; valuefrom:属性值的起始值; valueto:属性值的结束值;
valuetype:属性的类型。有"floattype","inttype","colortype"可选,分别代表浮点型,整型,颜色类型;
repeatcount:属性动画重复执行的次数;
repeatmode:属性动画重复的模式。有"reverse","restart"可选,分别表示逆向重复,连续重复。在本例中,逆向重复是指属性值从1->0.3,然后0.3->1,如果重复次数很多,依次类推;
startoffset:表示延迟多长时间后再执行动画效果,单位是ms;
2.1.2,接下里,使用animatorinflater加载该xml文件,并作用于button对象,并启动动画,具体java代码如下:
private void initanimatorbyxml() {
button btn = (button)findviewbyid(r.id.btn);
animatorset animatorset = (animatorset) animatorinflater.loadanimator(this, r.animator.anim_set);
animatorset.settarget(btn);
animatorset.start();
}
上面代码比较好理解,这里不在阐述。实现效果:1s后,在3s内使一个按钮的宽度比例从1缩写到0.3,然后重复一次动画,在3s内从0.3扩大到1;接着,按钮的背景色在3s内,由红色->绿色的变化。
注意:属性名称"scalex","backgroundcolor"是什么,为什么不可以是"abc"呢后面会给出解释...
2.2,使用java代码,实现属性动画
与view动画一样,属性动画既可以使用xml文件,也可以使用java代码动态的实现属性动画。直接上代码:
private void initanimatorbyjava() {
button btn = (button)findviewbyid(r.id.btn);
valueanimator objectanimator = objectanimator.offloat(btn, "scalex", 1f, 0.3f);
objectanimator.setduration(3000);
objectanimator.setrepeatcount(1);
objectanimator.setrepeatmode(valueanimator.reverse);
objectanimator.start();
}
前面已经讲述过valueanimator是objectanimator的父类,两者的区别:valueanimator的属性动画不需要作用于具体的对象,也没有属性名称;而objectanimator的属性动画需要作用于具体的对象,有具体的属性名称,完成属性值从初始值到最终值的变化。
上述代码实现的效果:在3s内使一个按钮的宽度比例从1缩写到0.3,然后重复一次动画,在3s内从0.3扩大到1。
上面的一些方法与xml文件中的属性对应,首先是调用objectanimator$offloat方法获取一个animator对象,第一个参数指作用的对象,第二个参数指属性的名称,第三个参数指属性的初始值,第四个指属性的最终值。
并调用方法setduration设置动画执行时间,setrepeatcount设置动画重复次数,setrepeatmode设置重复模式,最后调用start方法执行动画。
2.2.1,使用java代码,实现属性动画集合
直接上代码:
private void initanimatorbyjavaset() {
button btn = (button)findviewbyid(r.id.btn);
animatorset set = new animatorset();
objectanimator[] items = new objectanimator[]{
objectanimator.offloat(btn, "scalex", 1f, 0.5f),
objectanimator.ofargb(btn, "backgroundcolor", 0xffff0000, 0xff00ff00)
};
set.playtogether(items);
set.setstartdelay(3000);
set.setduration(3000).start();
}
1,首先创建animatorset对象
2,调用animatorset$playtogether(animator... items)方法,输入参数是一个不定参数类型,表示子动画同时执行;还有一个playsequentially(animator... items)方法,表示子动画顺序执行。与标签的ordering属性对应;
3,剩余步骤应该比较好理解,这里不再讲解;
上述代码实现效果:3s后,在3s内使一个按钮的宽度比例从1缩写到0.3,同时,按钮的背景色由红色->绿色。
三,属性动画,作用于任意属性
上面提到属性名称"scalex","backgroundcolor"是什么,为什么不可以是"abc"呢
属性动画作用的对象属性有这样几个要求:若该属性名为"abc",该对象需提供该属性的setabc方法,以及getabc方法。setabc方法用于设置该属性的值从初始值到最终值的变化,且一般是对ui做一些改变,以达到一种动画的效果;getabc方法提供该属性的初始值。
以属性名称"scalex"为例,button对象没有setscalex方法,但button从view中继承了setscalex方法。
setscalex方法如下:
public void setscalex(float scalex) {
if (scalex != getscalex()) {
invalidateviewproperty(true, false);
mrendernode.setscalex(scalex);
invalidateviewproperty(false, true);
invalidateparentifneededandwasquickrejected();
notifysubtreeaccessibilitystatechangedifneeded();
}
}
getscalex方法源码如下:
public float getscalex() {
return mrendernode.getscalex();
}
好了,那为什么属性名称"abc"不可以使用属性动画呢因为button没有提供setabc方法去改变按钮的ui,也没有提供getabc方法获取属性的初始值。
那如果想使用"abc"属性咋办呢我们可以创建一个viewwrapper类,提供setabc方法,getabc方法。在setabc方法中改变button的ui,在getabc方法中返回属性的初始值,具体代码如下。
viewwrapper代码如下:
static class viewwrapper {
view v;
public viewwrapper(view v) {
this.v = v;
}
public void setabc(int x) {
v.getlayoutparams().height = x;
log.e("wcc","x = " + x);
v.requestlayout();
}
public int getabc() {
int height;
height = v.getlayoutparams().height;
log.e("wcc","height = " + height);
return height;
}
}
setabc方法对对象v的高度设置值,输入参数x由属性abc的初始值到最终值的一直变化。修改了view的测量和布局,需要调用view$requestlayout方法对界面进行刷新。
getabc方法返回对象v的高度作为属性的初始值。值得一体的是,如果属性已经设置初始值,该方法不会被调用。
下面看看如何使用viewwrapper对象的abc属性,代码如下:
private void initanimatorbyanyattr() {
button btn = (button)findviewbyid(r.id.btn);
viewwrapper wrapper = new viewwrapper(btn);
objectanimator objectanimator = objectanimator.ofint(wrapper, "abc",800,1000);
objectanimator.setduration(3000);
objectanimator.start();
}
第5行,ofint方法第一个参数原本是传入button对象,现在传入viewwrapper对象;第二个参数传入viewwrapper对象的属性"abc",800是初始值,1000是最终值。由于这里设置了属性的初始值,那么getabc方法不会被调用。
上述代码实现这样一个效果:button按钮的高度在3秒内,由800->1000的变化。
小结:viewwrapper类实际上是对button的封装,对button的引用可以通过有参构造函数传递,属性动画的效果作用实体仍是button按钮。上面讲解了属性动画作用于任意属性,相信大家对属性动画作用于某一对象的属性的前置条件,有了答案啦~
三,valuaanimator的使用
在使用valueanimator之前,需要先了解两个概念:时间插值器,估值器。
时间插值器:根据时间流逝的百分比(输入参数),计算出属性值改变的百分比(返回值)。需要注意的是输入参数范围是0~1,当时间流逝占比为x%时,属性值改变百分比也为x%,那么该时间插值器为线性插值器。
可以看出,时间插值器描述的是属性值改变的速度。若是线性插值器,则动画的改变是一个匀速的过程。android提供了很多类型的插值器,eg:linearinterpolator(线性插值器),decelerateinterpolator(减速插值器),acceleratedecelerateinterpolator(先加速后减速插值器)等。其中,默认的插值器是acceleratedecelerateinterpolator(加速减速插值器)。下面以默认的插值器为例,从源码角度分析。
如下是timeinterpolator子类结构图:
查看timeinterpolator源码,如下:
public interface timeinterpolator {
/**
* maps a value representing the elapsed fraction of an animation to a value that represents
* the interpolated fraction. this interpolated value is then multiplied by the change in
* value of an animation to derive the animated value at the current elapsed animation time.
*
* @param input a value between 0 and 1.0 indicating our current point
* in the animation where 0 represents the start and 1.0 represents
* the end
* @return the interpolation value. this value can be more than 1.0 for
*interpolators which overshoot their targets, or less than 0 for
*interpolators that undershoot their targets.
*/
float getinterpolation(float input);
}
第15行,参数input值范围是0~1,0代表动画开始,1代表动画结束,那么0.5代表时间流逝了一半。返回一个float类型的值,这个值是多少呢这个方法由子类重写,各个插值器的处理方式都不一样。
以线性插值器为例,查看linearinterpolator源码,如下:
public class linearinterpolator extends baseinterpolator implements nativeinterpolatorfactory {
public linearinterpolator() {
}
public linearinterpolator(context context, attributeset attrs) {
}
public float getinterpolation(float input) {
return input;
}
/** @hide */
@override
public long createnativeinterpolator() {
return nativeinterpolatorfactoryhelper.createlinearinterpolator();
}
}
第10行,getinterpolation方法直接返回intput,也就是说,时间流逝百分比是多少,属性值改变百分比就是多少,这不就是线性插值器的特点么。
以加速减速插值器为例,查看acceleratedecelerateinterpolator源码,如下:
public class acceleratedecelerateinterpolator extends baseinterpolator
implements nativeinterpolatorfactory {
public acceleratedecelerateinterpolator() {
}
@suppresswarnings({"unuseddeclaration"})
public acceleratedecelerateinterpolator(context context, attributeset attrs) {
}
public float getinterpolation(float input) {
return (float)(math.cos((input + 1) * math.pi) / 2.0f) + 0.5f;
}
/** @hide */
@override
public long createnativeinterpolator() {
return nativeinterpolatorfactoryhelper.createacceleratedecelerateinterpolator();
}
}
第11行,一个三角函数的公式,当input处于0~0.5时,速度是逐渐增减的,动画的变化效果越来越快;当input处于0.5~1,速度是逐渐减小的,动画的变化效果越来越慢。在物理学里面,input就是时间t,返回值就是位移s(这里是指属性值改变的百分比)。那么插值器描述的是速度v,也就是说,插值器描述的是动画在一段时间内变化的快慢,有可能是保持一个速度,有可能先快后慢,有可能先慢后快,也可能是其他效果(自定义插值器)。
在实际开发中如果业务需要,若系统的插值器无法满足需求,可以自定义一个插值器。从上面对线性插值器和加速减速插值器的分析可知,自定义插值器的两个步骤:1,实现接口timeinterpolator或interpolator;2,重写getinterpolation方法,并根据业务需求确定返回值。
上面介绍了各类插值器的原理,以及如何自定义插值器,那么如何使用插值器呢valueanimator提供了setinterpolator方法。
valueanimator$setinterpolator方法源码,如下:
public void setinterpolator(timeinterpolator value) {
if (value != null) {
minterpolator = value;
} else {
minterpolator = new linearinterpolator();
}
}
如何获取插值器呢valueanimator提供了getinterpolator方法。
valueanimator$getinterpolator方法源码,如下:
public timeinterpolator getinterpolator() {
return minterpolator;
}
估值器:根据属性值改变的百分比,来计算属性值改变后的具体数值,在android中用typeevaluator描述估值器。
通过估值器的定义可知:估值器是根据插值器的值,计算出属性值改变后的具体数值。
typeevaluator的子类结构图,如下:
查看typeevaluator源码,如下:
public interface typeevaluator {
/**
* this function returns the result of linearly interpolating the start and end values, with
* fraction representing the proportion between the start and end values. the
* calculation is a simple parametric calculation: result = x0 + t * (x1 - x0),
* where x0 is startvalue, x1 is endvalue,
* and t is fraction.
*
* @param fractionthe fraction from the starting to the ending values
* @param startvalue the start value.
* @param endvaluethe end value.
* @return a linear interpolation between the start and end values, given the
*fraction parameter.
*/
public t evaluate(float fraction, t startvalue, t endvalue);
}
第16行,fraction参数:插值器getinterpolation方法返回的值,也就是当前属性值改变的百分比;startvalue:初始的属性值;endvalue:结束的属性值。
系统提供了各种类型的估值器,eg:intevaluator(整型估值器),floatevaluator(浮点型估值器),argbevaluator(颜色估值器)。下面以intevaluator为例,分析估值器的内部实现。
查看intevaluator源码,如下:
public class intevaluator implements typeevaluator {
/**
* this function returns the result of linearly interpolating the start and end values, with
* fraction representing the proportion between the start and end values. the
* calculation is a simple parametric calculation: result = x0 + t * (v1 - v0),
* where x0 is startvalue, x1 is endvalue,
* and t is fraction.
*
* @param fractionthe fraction from the starting to the ending values
* @param startvalue the start value; should be of type int or
* integer
* @param endvaluethe end value; should be of type int or integer
* @return a linear interpolation between the start and end values, given the
*fraction parameter.
*/
public integer evaluate(float fraction, integer startvalue, integer endvalue) {
int startint = startvalue;
return (int)(startint + fraction * (endvalue - startint));
}
}
第17行,类intevaluator实现了接口typeevaluator,并重写了evaluate方法;
第19行,如果输入参数fraction = 0.5,startvalue = 100,endvalue = 200,那么返回值结果为100 + (200 - 100)*0.5 = 150。
由上可知,因为业务需求需要自定义估值器时,需要遵循这样三个步骤:1,创建一个类实现typeevaluator接口;2,重写evaluate方法;3,根据业务具体需要,通过fraction,startvalue ,endvalue 计算属性值改变的数值。
那么intevaluator,floatevaluator,argbevaluator三种常用的估值器类型,如何使用呢
接下来介绍valueanimator在估值器类型为intevaluator,argbevaluator,自定义估值器时的使用,由于floatevaluator与intevaluator用法类似,不再重复阐述。
valueanimator.ofint(...)与inevaluator的使用:
直接上代码,如下:
private void startvalueanimatorbyint() {
final button btn = (button)findviewbyid(r.id.btn);
valueanimator animator = valueanimator.ofint(100,300);
animator.addupdatelistener(new valueanimator.animatorupdatelistener() {
@override
public void onanimationupdate(valueanimator animation) {
int animatedvalue = (int) animation.getanimatedvalue();
btn.getlayoutparams().height = animatedvalue;
btn.requestlayout();
}
});
animator.setstartdelay(2000);
animator.setduration(3000).start();
}
实现效果:一个按钮经过2秒的延迟后,在3秒内按钮的高度由100->300发生变化,动画是先快后慢。
第4行,调用valueanimator$ofint方法创建valueanimator对象,该方法内部维护了一个intevaluator的整型估值器,且默认是加速减速插值器。100是属性的初始值,300是属性的结束值。
第5行,valueanimator$addupdatelistener方法是对属性动画的每一帧进行监听,默认一帧为10ms;
第8行,获取当前时刻的属性值。如果插值器的值为0.5,and使用的是intevaluator,那么valueanimator$getanimatedvalue方法返回值为200。
第13行,设置2秒延迟后再执行动画;
第14行,启动动画;
第9行,valueanimator只能计算出当前时刻属性值的数值,并没有像objectanimator那样去操作ui。因此需要手动设置按钮的布局参数,并刷新界面。
valueanimator.ofargb(...)与argbevaluator的使用
直接上代码,如下:
private void startvalueanimatorbyargb() {
final button btn = (button)findviewbyid(r.id.btn);
final valueanimator animator = valueanimator.ofargb(0xffff0000, 0xff00ff00);
animator.addupdatelistener(new valueanimator.animatorupdatelistener() {
@override
public void onanimationupdate(valueanimator animation) {
int animatedvalue = (int) animator.getanimatedvalue();
btn.setbackgroundcolor(animatedvalue);
}
});
animator.setstartdelay(1000);
animator.setduration(5000).start();
}
实现效果:经过1秒的延迟后,按钮的背景色在5秒内由红色->绿色,动画是先快后慢。
第3行,调用valueanimator$ofargb方法创建valueanimator对象,该方法内部维护了一个argbevaluator估值器,且默认插值器是加速减速插值器。0xffff0000为属性值的初始值,0xff00ff00为属性值的结束值。
第7行,getanimatedvalue方法获取当前时刻的属性值,具体计算方式由argbevaluator颜色估值器的内部实现来确定。
第8行,由于valueanimator值计算出当前时刻的属性值,因此需要手动设置按钮的背景色。
valueanimator.ofobject(...)与自定义估值器
直接上代码,如下:
private void startvalueanimatorbyobj() {
final button btn = (button) findviewbyid(r.id.btn);
typeevaluator evaluator = new typeevaluator() {
@override
public object evaluate(float fraction, object startvalue, object endvalue) {
int color = 0xffff0000;
if (fraction < 0.2f) {
color = (int) startvalue;
} else if (fraction >= 0.2f && fraction < 0.8f) {
//black
color = 0xffff0000;
} else if (fraction >= 0.8f) {
color = (int) endvalue;
}
return color;
}
};
valueanimator animator = valueanimator.ofobject(evaluator, 0xff000000, 0xff00ff00);
animator.setinterpolator(new linearinterpolator());
animator.addupdatelistener(new valueanimator.animatorupdatelistener() {
@override
public void onanimationupdate(valueanimator animation) {
int animatedvalue = (int) animation.getanimatedvalue();
btn.setbackgroundcolor(animatedvalue);
}
});
animator.setstartdelay(2000);
animator.setduration(6000).start();
}
实现效果:动画在延迟2秒后执行,在6秒的时间内,按钮的背景色在前2秒是黑色,中间6秒是红色,最后2秒是绿色。
第20行,使用ofobject方法创建valueanimator对象,相比ofint,ofargb方法,需要多传递一个typeevaluator类型的参数。从上面描述的实现效果可知,系统没有提供相应的估值器来实现2秒黑色,6秒红色,2秒绿色的效果,因此需要自定义估值器。于是使用valueanimator的ofobject方法,配合自定义估值器完成业务需求。0xff000000(黑色)是属性值的初始值,0xff00ff00(绿色)是属性值的结束值。
第4行,创建一个typeevaluator类型的匿名内部类,用于替换创建typeevaluator的子类;
第6行,重写evaluator方法;
第9行,当插值器的值小于0.2(属性值变化的百分比小于0.2)时,估值器(改变后的属性值)的值为startvalue,即0xff000000(黑色)。
第12行,当插值器的值大于0.2,小于0.8时,估值器的值为0xffff0000(红色)。
第14行,当插值器的值大于0.8时,估值器的值为endvalue,即0xff00ff00(绿色)。
第21行,设置valueanimator的插值器为线性插值器;
第22行,对动画的每一帧进行监听,并在回调方法onanimationupdate操作ui;
第25行,获取当前时刻属性值的具体数值(前面设置好了插值器,估值器);
第26行,由于valueanimator最终只是获取当前的属性值,因此需要手动调用button的setbackgroundcolor方法,设置按钮的背景颜色。
其他行的代码应该比较好理解,这里不再阐述。
注意:文章前面提到“属性动画可以不作用于具体的对象”,学习了valueanimator实现属性动画,发现valueanimator确实没有作用于任何对象。只是借助valueanimator相关的方法,插值器,估值器,监听每一帧动画,确定了当前时刻的属性值,最后手动更新ui。
四,最后
本篇文章,先后介绍了使用xml,java代码两种方式实现属性动画,以及动画集合;然后介绍了如何使属性动画,作用于任意的属性;最后介绍了valueanimator的使用,包括插值器,估值器的概念及内部实现,ofint,ofargb,ofobject方法的使用。
上一篇: 23种设计模式之组合模式