Android 属性动画
一、valueanimator
valueanimator是值的变动,可以控制控件的一些值,从而达到变化动画的效果。
public void doanimation() { // final valueanimator valueanimatorint = valueanimator.ofint(0,400,100,555,250); //输入需要变化的值,是个变化的数组,可以有int类型和float类型 final valueanimator valueanimator = valueanimator.offloat(0.0f,400.0f,100.0f,555.0f,250.0f); valueanimator.setduration(9000);//动画持续时间 //监听动画的变化时间,在变化中对控件进行操作,也可以通过handle来做一些有趣的事情 valueanimator.addupdatelistener(new valueanimator.animatorupdatelistener() { @override public void onanimationupdate(valueanimator animation) { //获得变化的值 float curvaluefloat = (float) valueanimator.getanimatedvalue(); //设置为整型 int curvalue = curvaluefloat.intvalue(); //改变控件的位置,layout对应的是控件的位置 valuetv.layout(curvalue, curvalue, curvalue + imageview.getwidth(), curvalue + imageview.getheight()); } }); valueanimator.start(); }
监听器三个
//监听1 valueanimator.addpauselistener(new animator.animatorpauselistener() { @override public void onanimationpause(animator animation) { //暂停 } @override public void onanimationresume(animator animation) { //运行 } }); //监听2 valueanimator.addlistener(new animator.animatorlistener() { @override public void onanimationstart(animator animation) { //开始 } @override public void onanimationend(animator animation) { //结束 } @override public void onanimationcancel(animator animation) { //取消 } @override public void onanimationrepeat(animator animation) { //循环一次 } }); //监听3 valueanimator.addupdatelistener(new valueanimator.animatorupdatelistener() { @override public void onanimationupdate(valueanimator animation) { //数值更新 } });
移除监听器
当移除监听器时,正在执行的动画不会受到影响,但是之后再执行动画,动画的监听效果将不会再呈现。
/** * 移除animatorupdatelistener */ void removeupdatelistener(animatorupdatelistener listener); void removeallupdatelisteners(); /** * 移除animatorlistener */ void removelistener(animatorlistener listener); void removealllisteners();
不常用函数
/** * 延时多久时间开始,单位是毫秒 */ public void setstartdelay(long startdelay) /** * 完全克隆一个valueanimator实例,包括它所有的设置以及所有对监听器代码的处理 */ public valueanimator clone()
常用函数
/** * 设置动画时长,单位是毫秒 */ valueanimator setduration(long duration) /** * 获取valueanimator在运动时,当前运动点的值 */ object getanimatedvalue(); /** * 开始动画 */ void start() /** * 设置循环次数,设置为infinite表示无限循环 */ void setrepeatcount(int value) /** * 设置循环模式 * value取值有restart,reverse, */ void setrepeatmode(int value) /** * 取消动画 */ void cancel()
效果:
二、自定义插值器
1.插值器的理解
首先看看自动自定义的插值器
匀速插值器:
看看继承关系:linearinterpolator
---继承--->baseinterpolator
---继承--->interpolator
---实现-->timeinterpolator
最后看看timeinterpolator
都写了啥:
只定义了一个getinterpolation(float input)方法。
package android.animation; /** * a time interpolator defines the rate of change of an animation. this allows animations * to have non-linear motion, such as acceleration and deceleration. */ 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); }
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(); } }
accelerateinterpolator
开始慢,后面一直加速插值器,也叫幂函数插值器,核心方法
public float getinterpolation(float input) { if (mfactor == 1.0f) { return input * input; } else { //返回的是时间的次幂数,比如 input=3,mdoublefactor=2, //那么返回的就是,3的2次方,就是9 //所以会按照几何倍增,这是一个幂函数 return (float)math.pow(input, mdoublefactor); } }
所有的速度都离不开这个方法:getinterpolation
而最为关键的就是input
这个数字。以下是经典解释:
input
参数代表了当前动画的进度,而返回值则代表了当前动画的数值进度。
上面的匀速,返回的就是时间的值,所以,动画进度和动画的数值持平。
幂函数的时候,随着动画进度的增加,动画的数值进度也就越来越大,从而一直加速。
input
的取值范围是0~1之间,返回值可以超过1,也可以小于0,超过1表示已经超过目标位置,小于0表示远离初始位置。
简单的公式就是
y= -> x
y
代表返回的值,也就是动画需要的数值进度,x
代表时间进度,->
则是通过一些数学手段,来得到想要的y
值。
- 当一些动画定义这些插值器的时候,返回的数值进度越大,速度越快。比如你在匀速运动的时候,时间进度是0.5s,数值进度也是0.5,那就是匀速运动。
2.定义一个简单的插值器
我们用数学中的定义来做一个插值器。
y=1-x
把进度反过来,当进度传入0的时候,数值进度已经在目标位置了。当传入1时,数值则在刚开始的位置。
class fiveinterpolator implements timeinterpolator { @override public float getinterpolation(float input) { return 1-input; } }
valueanimator.setinterpolator(``new ``fiveinterpolator())``;
一个简单的自定义插值器就完成了。
三、evaluator
- evaluator是数值转换器,就是将数值进度转化为具体的数值。
- 就是0~400的数值变换,当数值进度是50%的时候,那通过evaluator来转换,就变成了200
- oflnt()函 数对应 evauator 类名为 intevauaor ,而 offloat()函数对应的 evauator 类名为 floatevaluator
自定义数值转换器:
//自定义数值转换器 class myfloatevaluator implements typeevaluator<float>{ /** * @param fraction 代表数值进度的值,就是上面getinterpolation()的返回值 * @param startvalue 代表offloat(float startvalue,float endvalue) * @param endvalue * */ @override public float evaluate(float fraction, float startvalue, float endvalue) { //初始值 float startfloat=startvalue; //当前值=初始值+总值*进度 float inputvalue=startfloat+(endvalue-startfloat)*fraction; return inputvalue; } }
使用:
valueanimator.setevaluator(new myfloatevaluator());
所以可以通过插值器和数值转化器来改变控件的数值变化
valueanimator.setinterpolator(new fiveinterpolator()); valueanimator.setevaluator(new myfloatevaluator());
四、argbevaluator
argbevaluator可以把颜色转换过渡。
具体实现:
//颜色的数值变换 public void docoloranimation(){ valueanimator valueanimator=valueanimator.ofint(0xffffff00,0xff0000ff); valueanimator.setevaluator(new argbevaluator()); valueanimator.setduration(3000); valueanimator.addupdatelistener(new valueanimator.animatorupdatelistener() { @override public void onanimationupdate(valueanimator animation) { int curvalue=(int)animation.getanimatedvalue(); valuetv.setbackgroundcolor(curvalue); } }); valueanimator.start(); }
效果:
颜色必须包含argb四个值。
五、valueanimation-ofobject
首先看看这个方法是如何传值的。
public static valueanimator ofobject(typeevaluator evaluator, object... values) { valueanimator anim = new valueanimator(); anim.setobjectvalues(values); anim.setevaluator(evaluator); return anim; }
typeevaluator evaluator
需要传入自定义的数值转换器object... values
可变长参数
实例
实现一个字母从a到z的过程
public void doobjectvalue() { valueanimator valueanimator = valueanimator.ofobject(new charinterpolator(), new character('a'), new character('z')); valueanimator.addupdatelistener(new valueanimator.animatorupdatelistener() { @override public void onanimationupdate(valueanimator animation) { char str = (char) animation.getanimatedvalue(); valuetv.settext(string.valueof(str)); } }); valueanimator.setduration(7000); valueanimator.setinterpolator(new accelerateinterpolator()); valueanimator.start(); } class charinterpolator implements typeevaluator<character> { @override public character evaluate(float fraction, character startvalue, character endvalue) { int startint = (int) startvalue; //ascii转换 a代表56 以此递增 int endint = (int) endvalue; int curint = (int) (startint + fraction * (endint - startint)); char result = (char) curint; return result; } }
效果:
六、objectanimator
objeceanimation--继承--->valueanimation
与控件之间相关联,从监听动画中解放出来。
先看看这个方法:
objectanimator offloat(object target, string propertyname, float... values)
具体使用
public void doobjectanimationbyalpha(){ objectanimator objectanimator=objectanimator.offloat(valuetv,"alpha",1,0,1); objectanimator.setduration(6000); objectanimator.start(); }
object target
要控制的控件string propertyname
要改变的动画效果float... values
传入的具体变化值
具体效果就是跟视图动画中设置的动画是一样的效果,透明度从1到0再到1.
“alpha”中,是对应view中的setalpha()方法,后面的可变成参数就是可以传入具体是变换数值。
看看view中有多少个set函数:
透明度:alpha
setalpha(@floatrange(from=0.0, to=1.0) float alpha) //透明度
旋转角度:rotation,rotationx,rotationy
setrotation(float rotation) //围绕z轴旋转,z轴指的是垂直屏幕的方向 setrotationx(float rotationx) //围绕x轴旋转 setrotationy(float rotationy) //围绕y轴旋转
平移:translationx,translationy
settranslationx(float translationx) //x轴屏幕,右为正方向,当前控件为原点 settranslationy(float translationy)
缩放:scalex,scaley
setscalex(float scalex) //x轴缩放 setscaley(float scaley)
看看旋转是三个效果:
public void doobjectanimationbyalpha(){ objectanimator objectanimator=objectanimator.offloat(valuetv,"rotationy",360); objectanimator.setduration(6000); objectanimator.start(); }
z轴:
x轴:
y轴:
七、自定义objectanimator
因为objectanimator是通过set来反射实现的,所以自己也可以通过这样的操作来实现自己view的set函数,从而实现简单的动画效果。
1.自定义view的set函数
fallingballimageview.java
public class fallingballimageview extends imageview { public fallingballimageview(context context, @nullable attributeset attrs) { super(context, attrs); } public void setfallingpos(point pos){ layout(pos.x,pos.y,pos.x+getwidth(),pos.y+getheight()); } }
布局使用
<com.lanjiabin.systemtest.anim.fallingballimageview android:id="@+id/imageball" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_margintop="30dp" android:background="@drawable/shape" android:layout_marginbottom="25dp" />
set函数的名字是setfallingpos
,所以在传递反射函数名字的时候,应该是fallingpos
或者fallingpos
,必须是这两个名字中其中一个的格式,否则就不会正确反射。参数类型是point,所以使用函数是ofobject()
。
2.自定义evaluator
class divevaluator implements typeevaluator<point> { point point = new point(); @override public point evaluate(float fraction, point startvalue, point endvalue) { point.x = (int) (startvalue.x + (endvalue.x - startvalue.x) * fraction); if (fraction * 2 <= 1) { point.y = (int) (startvalue.y + (endvalue.y - startvalue.y) * fraction); } else { point.y = endvalue.y; } return point; } }
3.实现最终反射调用
public void doobjectanimationbydiv() { objectanimator objectanimator=objectanimator .ofobject( ballimageview, //自定义view的小球 "fallingpos", //反射名字,fallingpos或者fallingpos都可以 new divevaluator(), //自定义转换器 new point(0,0), //起始坐标 new point(300,300)); //目标坐标 objectanimator.setduration(4000); //动画时长 objectanimator.start(); }
4.效果
5.get函数
当我们在上述函数的时候,ofobject()传的都是可变长的参数,也就是两个参数以上,当我们只传递一个参数的时候,这个参数只是目标参数,没有初始参数,系统就会默认调用系统自带的get方法,来获得初始值。当没有这个get方法的时候,就会报错,以至于崩溃。
所以想传递一个参数,就需要自定义get()方法,返回的,就是初始值。对应名字也和set的名字类似。setfallingpos(point pos)
的名字就是getfallingpos(point pos)
在自定义view中加入get方法:返回控件的初始point
public point getfallingpos() { int[] location = new int[2]; this.getlocationonscreen(location); return new point(location[0], location[1]); }
八、animatorset
1.animatorset理解和使用
animatorset组合动画,对valueanimation和objectanimation都有一样的效果。
有两个播放方法:只管播放的时间,不管动画个体是如何操作的,不管动画的执行时间,循环次数等。playsequentially()
是顺序播放,当前一个动画播放完毕以后,才会执行下一个动画。当前一个动画是无限循环时,后一个动画也就无法播放。有两个构造方法。
playsequentially(animator... items) playsequentially(list<animator> items)
playtogether()
是一起播放,同一个时间内,在列表中所有动画同一时间启动。
playtogether(animator... items) playtogether(collection<animator> items)
具体实例,有一个缩放动画和位移动画,分别实现同时播放和顺序播放。
public void doanimationset() { //缩放 objectanimator objectanimatorscaley = objectanimator.offloat(ballimageview, "scaley", 0.0f, 1.6f, 1.0f); //平移 objectanimator objectanimatortranslationx = objectanimator.offloat(ballimageview, "translationx", 400); //组合动画 animatorset animator = new animatorset(); //每个动画的播放时间 animator.setduration(3000); //顺序播放 animator.playsequentially(objectanimatorscaley, objectanimatortranslationx); //一起播放 animator.playtogether(objectanimatorscaley, objectanimatortranslationx); animator.start(); }
同时播放:动画效果同时体现出来,缩放和位移
顺序播放:先缩放完毕再位移
2.animatorset.builder
//组合动画 animatorset animator = new animatorset(); //目标动画 animatorset.builder builder=animator.play(objectanimatorscaley); //执行目标动画后再执行该动画 builder.after(objectanimatorscaley); //执行该动画后再执行目标动画 builder.before(objectanimatorscaley); //和目标动画一起播放 builder.with(objectanimatorscaley); //延迟时间执行目标动画 builder.after(3000); //串行方式 animatorset animator = new animatorset(); animatorset.builder builder=animator .play(objectanimatorscaley) .after(objectanimatorscaley) .before(objectanimatorscaley);
//如果animatorset设置了动画时长,循环次数等,都以animatorset为准,单个设置不起作用。 //每个动画的播放时间 animator.setduration(3000);
//所有的动画都集中于这个控件上,其它的不起作用 animator.settarget(ballimageview);
九、实例-卫星菜单
1.实现原理
实现一个放射卫星的效果,点击一下,放射出菜单,再点击一下,收回菜单。
原理就是,将所有的菜单重叠在一起,点击最上面的菜单,按照不同的角度,实现位移,缩放,透明度的效果,将下面的菜单都位移出去。看看位移的计算方式,每个菜单,与主菜单都形成了直角形式,水平x轴的位移和y轴的水平位移都可以计算出来。 就是从主菜单,位移到不同位置的x轴和y轴。
2.布局
布局非常简单,全部控件叠加在一起,而且子菜单的属性全部一致,省略了一些重复的空间。
<?xml version="1.0" encoding="utf-8"?> <linearlayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <framelayout android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margintop="20dp"> <!-- shadow 代表阴影,对阴影的一些处理--> <button android:id="@+id/mainbtn" android:layout_width="50dp" android:layout_height="50dp" android:layout_gravity="center_vertical|right" android:layout_marginright="40dp" android:background="@drawable/menu_main" android:outlineambientshadowcolor="@android:color/transparent" android:outlinespotshadowcolor="@android:color/transparent" android:shadowcolor="@android:color/transparent" /> <button android:id="@+id/rockbtn" android:layout_width="50dp" android:layout_height="50dp" android:layout_gravity="center_vertical|right" android:layout_marginright="20dp" android:background="@drawable/menu_rock" android:outlineambientshadowcolor="@android:color/transparent" android:outlinespotshadowcolor="@android:color/transparent" android:shadowcolor="@android:color/transparent" android:visibility="gone" /> <!-- 以下省略六个子按钮菜单 --> </framelayout> </linearlayout>
3.java代码
核心思想就是,添加要控制的子菜单,开启动画方法,关闭动画方法。
public class exampleactivity extends baseactivity { private boolean mismenuopen = false; private button mmainbtn, mrockbtn, mairbtn, mtrainbtn, mcarbtn, mmotorbikebtn, mbicyclebtn, mwalkbtn; list<button> mbtnarray; @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); initview(); onclick(); } public void initview() { setcontentview(r.layout.activity_example); mmainbtn = findviewbyid(r.id.mainbtn); mrockbtn = findviewbyid(r.id.rockbtn); mairbtn = findviewbyid(r.id.airbtn); mtrainbtn = findviewbyid(r.id.trainbtn); mcarbtn = findviewbyid(r.id.carbtn); mmotorbikebtn = findviewbyid(r.id.motorbikebtn); mbicyclebtn = findviewbyid(r.id.bicyclebtn); mwalkbtn = findviewbyid(r.id.walkbtn); //添加子菜单 mbtnarray = new arraylist<button>(); mbtnarray.add(mrockbtn); mbtnarray.add(mairbtn); mbtnarray.add(mtrainbtn); mbtnarray.add(mcarbtn); mbtnarray.add(mmotorbikebtn); mbtnarray.add(mbicyclebtn); mbtnarray.add(mwalkbtn); } public void onclick() { mmainbtn.setonclicklistener(new view.onclicklistener() { @override public void onclick(view v) { switchanimation(); } }); mrockbtn.setonclicklistener(new view.onclicklistener() { @override public void onclick(view v) { toast.maketext(exampleactivity.this, "你选择了火箭出行!", toast.length_short).show(); } }); } //开关 public void switchanimation() { if (!mismenuopen) { mismenuopen = true; for (int i = 0; i < mbtnarray.size(); i++) { doanimationopen(mbtnarray.get(i), i, mbtnarray.size(), 500); } } else { mismenuopen = false; for (int i = 0; i < mbtnarray.size(); i++) { doanimationclose(mbtnarray.get(i), i, mbtnarray.size(), 500); } } } /** * 开启动画/展开菜单 * @param view 要控制的控件/子菜单 * @param index 要控制控件的顺序 * @param total 子菜单的总数 * @param radius 主菜单到子菜单的距离/半径 * */ private void doanimationopen(view view, int index, int total, int radius) { //显示菜单 if (view.getvisibility() != view.visible) { view.setvisibility(view.visible); } //计算每个菜单的角度,toradians()将度数转换为弧度 //七个子菜单,有六个夹角角,180/(7-1)*2 2代表第二个夹角 double degree = math.toradians(180) / (total - 1) * index; //x轴位移 int translationx = -(int) (radius * math.sin(degree)); //y轴位移 int translationy = -(int) (radius * math.cos(degree)); animatorset animatorset = new animatorset(); //动画合集 animatorset.playtogether( //从原来控件的位置往x轴移动多少 objectanimator.offloat(view, "translationx", 0, translationx), //从原来控件的位置往y轴移动多少 objectanimator.offloat(view, "translationy", 0, translationy), //x轴缩放 objectanimator.offloat(view, "scalex", 0.01f, 1f), //y轴缩放 objectanimator.offloat(view, "scaley", 0.01f, 1.0f), //透明度 objectanimator.offloat(view, "alpha", 0.01f, 1.0f) ); animatorset.setduration(500); animatorset.start(); } //关闭菜单/动画 private void doanimationclose(view view, int index, int total, int radius) { if (view.getvisibility() != view.visible) { view.setvisibility(view.visible); } double degree = math.toradians(180) / (total - 1) * index; int translationx = -(int) (radius * math.sin(degree)); int translationy = -(int) (radius * math.cos(degree)); animatorset animatorset = new animatorset(); animatorset.playtogether( objectanimator.offloat(view, "translationx", translationx, 0), objectanimator.offloat(view, "translationy", translationy, 0), objectanimator.offloat(view, "scalex", 1.0f, 0.01f), objectanimator.offloat(view, "scaley", 1.0f, 0.01f), objectanimator.offloat(view, "alpha", 1.0f, 0.01f) ); animatorset.setduration(500); animatorset.start(); } }
3.效果
十、xml实现animator
1.animator
在animator下建立animator.xml
xml代码:
<?xml version="1.0" encoding="utf-8"?> <animator xmlns:android="http://schemas.android.com/apk/res/android" android:duration="1000" android:interpolator="@android:anim/accelerate_interpolator" android:valuefrom="0" android:valueto="300" android:valuetype="inttype" />
java代码使用
valueanimator valueanimator=(valueanimator)animatorinflater.loadanimator( exampleactivity.this, r.animator.animator); valueanimator.addupdatelistener(new valueanimator.animatorupdatelistener() { @override public void onanimationupdate(valueanimator animation) { int offset=(int)animation.getanimatedvalue(); mrockbtn.layout(offset,offset,mrockbtn.getwidth()+offset,mrockbtn.getheight()+offset); } }); valueanimator.start();
2.objectanimator
object_animator.xml
<?xml version="1.0" encoding="utf-8"?> <objectanimator xmlns:android="http://schemas.android.com/apk/res/android" android:duration="1000" //时间 android:interpolator="@android:anim/accelerate_interpolator" //插值器 android:propertyname="string" //要映射的名字 android:repeatcount="11" //循环次数 android:repeatmode="restart" //循环模式 android:startoffset="777" android:valuefrom="99" //开始 android:valueto="199" //目标 android:valuetype="inttype" //数据类型 />
java代码使用
objectanimator animator=(objectanimator)animatorinflater.loadanimator( exampleactivity.this, r.animator.object_animator); animator.settarget(mairbtn); animator.start();
十一、后语
包括其它的知识点,都只能说一些基础的内容,很多方法和拓展知识都没有说道,需要自己去探索,多阅读sdk源码。属性动画,有一些高级的内容,后续会持续拓展。
编程中我们会遇到多少挫折?表放弃,沙漠尽头必是绿洲。