深入了解一些Android动画
一、propertyvaluesholder
阅读本文需要上一文android属性动画的基础,这样才可以明白接下来要讲什么。
1.理解和使用
propertyvaluesholder
是objectanimation类似的一个方法,只是少了一个target,就是要执行的控件。看看正常的使用方法:会同时执行全部的holder
public void dopropertyvaluesholder(){ //定义一个旋转holder propertyvaluesholder rotationholder= propertyvaluesholder.offloat( "rotation", 60f,40f,100f,-60f,40f,88f,77f); //定义一个透明holder propertyvaluesholder alphaholder= propertyvaluesholder.offloat( "alpha", 0.01f,0.5f,1.0f,0.8f,0.2f,0.0f); //加载进objectanimator objectanimator objectanimator=objectanimator.ofpropertyvaluesholder(ballimageview,rotationholder,alphaholder); objectanimator.setduration(3000); objectanimator.start(); }
2.方法和参数
可以看看这个方法的参数:
objectanimator ofpropertyvaluesholder(object target,propertyvaluesholder... values)
propertyvaluesholder offloat(string propertyname, float... values)
object target
是要显示动画的控件
propertyvaluesholder... values
装载多个propertyvaluesholder
string propertyname
代表要反射的参数,跟objectanimation的参数是一样的
float... values
代表是可变长参数
这样的方法还有以下图片这些:
其中ofobject(
)方法 ,也是跟objectanimation的相似,也是要自定义typeevaluator。
二、keyframe
1.理解和使用
看名字,就是理解为关键帧的意思,在动画中,在某帧做一些操作,从而实现对比效果比较明显的效果。
关键帧表示是某个物体在哪个时间点应该在哪个位置上。
具体使用:
public void dopropertyvaluesholderkeyframe(){ //头keyframe1,从进度0.6开始,在进度60%的时候,数值是0.1f keyframe keyframe1=keyframe.offloat(0.6f,0.1f); //中间keyframe2 keyframe keyframe2=keyframe.offloat(0.1f,0.8f); //尾部keyframe3,以50%进度作为结束,这时候的数值为0.2f keyframe keyframe3=keyframe.offloat(0.5f,0.2f); //装载到holder中,并设置要反射的方法,这是反射的是setalpha()方法,控制透明度 propertyvaluesholder alphaholder=propertyvaluesholder.ofkeyframe("alpha",keyframe1,keyframe2,keyframe3); //把装载到holder中装载到objectanimator或者valueanimation objectanimator objectanimator=objectanimator.ofpropertyvaluesholder(ballimageview,alphaholder); objectanimator.setduration(3000); objectanimator.start(); }
2.方法和参数
keyframe offloat(float fraction, float value)
float fraction
表示进度
float value
表示在这个进度下的数值
propertyvaluesholder ofkeyframe(string propertyname, keyframe... values)
string propertyname
要反射的set方法
keyframe... values
传入keyframe
keyframe的方法,也是和其他的类似的。
keyframe的set方法,设置进度,插值器,数值。
没有设置插值器的时候,默认是线性插值器
keyframe1.setinterpolator(new linearinterpolator()); //默认线性插值器
3.帧的操作
直接写结论:
- 如果去掉0帧,则以第一个关键帧为起始位置
- 如果去掉结束帧(进度为1),则以最后一个关键帧为结束位置
- 使用keyframe来构建动画,至少需要2帧
三、viewpropertyanimator
1.理解和使用
可以通过串行的形式,快速定义动画,省去一些定义,在每次界面绘制的时候,启动动画,比其他的更节省消耗。
比如:
ballimageview.animate().alpha(0.5f).rotation(360f).scalex(1.5f).translationx(100f);
2.参数和方法
可以看到这些方法的返回值,基本都是viewpropertyanimator
再引用一张表格:
函数 | 含义 |
---|---|
alpha(float value) | 设置透明度 |
scaley(float value) | 设置 y轴方向的缩放大小 |
scalex(float value) | 设置x轴方向的缩放大小 |
translationy(float value) | 设置y轴方向的移动值 |
translationx(float value) | 设置x轴方向的移动值 |
rotation(float value) | 设置绕z轴旋转度数 |
rotationx(float value) | 设置绕x轴旋转度数 |
rotationy(float value) | 设置绕 y 轴旋转度数 |
x(float value) | 相对于父容器的左上角坐标在 x轴方向的最终位置 |
y(float value) | 相对于父容器的左上角坐标在y轴方向的最终位置 |
alphaby(float value) | 设置透明度增量 |
rotationby(float value) | 设置绕z轴旋转增量 |
rotationxby(float value) | 设置绕 x 油旋转增量 |
rotationyby(float value) | 设置统y轴旋转增量 |
translationxby(float value) | 设置x轴方向的移动值增量 |
translationyby(float value) | 设置y轴方向的移动值增量 |
scalexby(float value) | 设置x轴方向的缩放大小增量 |
scaleyby(float value) | 设置 y轴方向的缩放大小增量 |
xby(float value) | 相对于父容器的左上角坐标在 x轴方向的位置增量 |
yby(float value) | 相对于父容器的左上角坐标在 y轴方向的位置增量 |
setlnterpolator(timelnterpolator interpolator) | 设置插值器 |
setstartdelay(long startdelay) | 设置开始延时 |
setduration(long duration) | 设置动画时长 |
四、animatelayoutchanges
android:animatelayoutchanges="true"
在layout加入控件,或者移除控件的时候,添加动画,但是只能使用默认动画。
<linearlayout android:animatelayoutchanges="true" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"/>
五、layouttransition
layouttransition可以控制viewgroup的动画,可以使用自定义的动画。
具体使用:
public void dolayouttransition(){ linearlayout linearlayout=new linearlayout(this); //1.创建实例 layouttransition transition=new layouttransition(); //2.创建动画 objectanimator objectanimator=objectanimator.offloat(null,"rotation",0f,90f,0f); //3.动画出现形式进行设置 transition.setanimator(layouttransition.disappearing,objectanimator); //4.将layouttransition设置到viewgroup中 linearlayout.setlayouttransition(transition); //5.开源动画库 nineoldandroids }
setanimator(int transitiontype, animator animator)
这个方法中,transitiontype
有五个选项
change_appearing
由于容器中要显示一个新的元素,其他需要变化的元素所应用的动画(问题多,不常用)
_change_disappearing_
当个容器中某个元素要消失时,其他需要变化的元素所应用的动画(问题多,不常用)
_changing_
容器中正在更改的元素的动画变化
_appearing_
元素在容器中出现时所定义的动画
_disappearing_
元素在容器中消失时所定义的动画
六、pathmeasure
pathmeasure
类似一个计算器,可以计算出目标path的坐标,长度等
1.初始化
public void dopathmeasure(){ path path=new path(); //初始化方法1 pathmeasure pathmeasure1=new pathmeasure(); pathmeasure1.setpath(path,true); //初始化方法2 pathmeasure pathmeasure2=new pathmeasure(path,false); }
setpath(path path, boolean forceclosed)
path
就是代表要计算的目标path。forceclosed
是否闭合,true会计算闭合状态下的path,false会按照path原来情况来计算。
2.函数调用
自定义一个view
public class pathview extends view { path mpath; paint mpaint; pathmeasure mpathmeasure; public pathview(context context, @nullable attributeset attrs) { super(context, attrs); mpath=new path(); mpaint=new paint(); mpathmeasure=new pathmeasure(); } @override protected void ondraw(canvas canvas) { super.ondraw(canvas); canvas.translate(250,250); //画布移动 mpaint.setcolor(color.blue); //画笔颜色 mpaint.setstrokewidth(5); //画笔粗细 mpaint.setstyle(paint.style.stroke); //画笔风格 mpath.moveto(0,0); mpath.lineto(0,100); mpath.lineto(100,100); mpath.lineto(100,0); mpathmeasure.setpath(mpath,true); log.v("showlog", "getlength()=="+mpathmeasure.getlength() +" isclosed()=="+ mpathmeasure.isclosed()); //结果400.0 true mpathmeasure.setpath(mpath,false); log.v("showlog", "getlength()=="+mpathmeasure.getlength() +" isclosed()=="+ mpathmeasure.isclosed()); //结果300.0 false canvas.drawpath(mpath,mpaint); //绘制路径 } }
绘制效果:
2.1 pathmeasure.getlength()
pathmeasure.getlength()
函数用于测量路径的长度
2.2 pathmeasure.isclosed()
pathmeasure.isclosed()
函数用于返回是否测量闭合状态
2.3 pathmeasure.nextcontour()
mpath.addrect(-50, -50, 50, 50, path.direction.cw); canvas.drawpath(mpath, mpaint); mpath.addrect(-100, -100, 100, 100, path.direction.cw); canvas.drawpath(mpath, mpaint); mpath.addrect(-120, -120, 120, 120, path.direction.cw); canvas.drawpath(mpath, mpaint); mpathmeasure.setpath(mpath, false); do { float len = mpathmeasure.getlength(); log.v("showlog", "len=" + len); } while (mpathmeasure.nextcontour());
效果:
打印结果:
len=400.0 len=800.0 len=960.0
pathmeasure.nextcontour()
得到的顺序与添加的path的顺序相同
pathmeasure.getlength()
只是得到当前path的长度,不是全部的长度
2.3 getsegment()
使用getsegment函数需要禁用硬件加速 在构造方法中加入setlayertype(layer_type_software,null);
mpath.addrect(-50, -50, 50, 50, path.direction.cw); mpathmeasure.setpath(mpath,false); //计算的path mpathmeasure.getsegment(0,150,mdstpath,true); //截取并添加到mdstpath,是添加,不是其他 canvas.drawpath(mpath, mpaint); //绘制原来的path canvas.translate(200,0); //画布移动 mpaint.setcolor(color.red); canvas.drawpath(mdstpath, mpaint); //绘制添加后的mdstpath
boolean getsegment(float startd, float stopd, path dst, boolean startwithmoveto)
startd
path开始截取的点,截取的起始点,是以左上角的点开始的
stopd
截取停止的点
dst
截取后添加到的path
startwithmoveto
是否保存原状,true保存原样,false则会连接初始点和终点,和原来的不一定相同形状
以上代码的效果: 截图的方向,与原来的path的生成方向有关
2.4 动态画圆的例子
代码:
public class pathview extends view { path mpath, mdstpath; paint mpaint; pathmeasure mpathmeasure; float mcuranimvalue; public pathview(context context, @nullable attributeset attrs) { super(context, attrs); setlayertype(layer_type_software, null); mpath = new path(); mdstpath = new path(); mpaint = new paint(); mpathmeasure = new pathmeasure(); mpaint.setcolor(color.blue); //画笔颜色 mpaint.setstrokewidth(5); //画笔粗细 mpaint.setstyle(paint.style.stroke); //画笔风格 mpath.addcircle(100, 100, 50, path.direction.cw); //一个完整的圆 mpathmeasure.setpath(mpath, true); //要计算的path valueanimator animator = valueanimator.offloat(0, 1); //进度 0~1 animator.setrepeatcount(valueanimator.infinite); //无限循环 animator.addupdatelistener(new valueanimator.animatorupdatelistener() { @override public void onanimationupdate(valueanimator animation) { mcuranimvalue = (float) animation.getanimatedvalue(); //得到当前的进度 invalidate();//重绘,重新执行ondraw()方法 } }); animator.setduration(5000); animator.start(); } @override protected void ondraw(canvas canvas) { super.ondraw(canvas); canvas.translate(100, 100); //画布移动 float stop=mpathmeasure.getlength()*mcuranimvalue; //一个进度确定一个截取点 mdstpath.reset(); mpathmeasure.getsegment(0,stop,mdstpath,true); //一点点添加 canvas.drawpath(mdstpath,mpaint); //每次有进度更新,就绘制一小段截取 } }
效果:
2.5 getpostan()
先看看函数的定义:
boolean getpostan(float distance, float pos[], float tan[])
float distance
距离path的其实长度
float pos[]
该点的坐标值。x和y pos[0]=x,pos[1]=y
float tan[]
该点的正切值。x和y pos[0]=x,pos[1]=y tan<a=y/x
2.6 箭头画圆的例子
代码:
public class pathview extends view { path mpath, mdstpath; paint mpaint; pathmeasure mpathmeasure; float mcuranimvalue; bitmap marrowbmp; float[] mpos; float[] mtan; int mcenterx,mcentery; float mradius; public pathview(context context, @nullable attributeset attrs) { super(context, attrs); setlayertype(layer_type_software, null); mpath = new path(); mdstpath = new path(); mpaint = new paint(); mpathmeasure = new pathmeasure(); mpos=new float[2]; mtan=new float[2]; //加载箭头图片 marrowbmp= bitmapfactory.decoderesource(getresources(), r.drawable.arrow); mpaint.setcolor(color.blue); //画笔颜色 mpaint.setstrokewidth(5); //画笔粗细 mpaint.setstyle(paint.style.stroke); //画笔风格 mpath.addcircle(540, 972, 486, path.direction.cw); //一个完整的圆 mpathmeasure.setpath(mpath, true); //要计算的path valueanimator animator = valueanimator.offloat(0, 1); //进度 0~1 animator.setrepeatcount(valueanimator.infinite); //无限循环 animator.addupdatelistener(new valueanimator.animatorupdatelistener() { @override public void onanimationupdate(valueanimator animation) { mcuranimvalue = (float) animation.getanimatedvalue(); //得到当前的进度 invalidate();//重绘,重新执行ondraw()方法 } }); animator.setduration(5000); animator.start(); } @override protected void onsizechanged(int w, int h, int oldw, int oldh) { /* * 得到h,w的最小的那个值; * >> 1 移位 跟 /2 相同; * 乘以0.9f,表示占布局的90% * */ mradius = (math.min(h, w) >> 1) * 0.9f; // 中心坐标 mcenterx = w / 2; mcentery = h / 2; log.v("showlog",mcenterx+" "+mcentery+" "+mradius); postinvalidate(); super.onsizechanged(w, h, oldw, oldh); } @override protected void ondraw(canvas canvas) { super.ondraw(canvas); float stop=mpathmeasure.getlength()*mcuranimvalue; //一个进度确定一个截取点 mdstpath.reset(); mpathmeasure.getsegment(0,stop,mdstpath,true); //一点点添加 canvas.drawpath(mdstpath,mpaint); //每次有进度更新,就绘制一小段截取 mpathmeasure.getpostan(stop,mpos,mtan); //获得每点的正切值和坐标 /** * math.atan2(mtan[1],mtan[0])获得tan的弧度值 * *180.0/math.pi将转化为角度值 * */ float degrees=(float)(math.atan2(mtan[1],mtan[0])*180.0/math.pi); matrix matrix=new matrix(); /** * 将图片围绕中心点旋转指定角度 * postrotate(float degrees, float px, float py) * degrees是角度 (px,py)是图片中心点 * */ matrix.postrotate(degrees,marrowbmp.getwidth()/2,marrowbmp.getheight()/2); /** * 将图片从默认的(0,0)点移动到路径的最前端 * */ matrix.posttranslate(mpos[0]-marrowbmp.getwidth()/2,mpos[1]-marrowbmp.getheight()/2); //绘制图片 canvas.drawbitmap(marrowbmp,matrix,mpaint); } }
效果:
2.7 getmatrix()
参数类型:
boolean getmatrix(float distance, matrix matrix, int flags)
使用方法:
//计算方位角 matrix matrix = new matrix(); //获取位置信息 mpathmeasure.getmatrix(stop,matrix,pathmeasure.position_matrix_flag); //获取切边信息 mpathmeasure.getmatrix(stop,matrix,pathmeasure.tangent_matrix_flag);
2.8 支付成功例子
public class tickview extends view { path mpath, mdstpath; paint mpaint; pathmeasure mpathmeasure; float mcuranimvalue; int mcenterx, mcentery; float mradius; public tickview(context context, @nullable attributeset attrs) { super(context, attrs); setlayertype(layer_type_software, null); mpath = new path(); mdstpath = new path(); mpaint = new paint(); mpathmeasure = new pathmeasure(); mpaint.setcolor(color.blue); //画笔颜色 mpaint.setstrokewidth(5); //画笔粗细 mpaint.setstyle(paint.style.stroke); //画笔风格 mcenterx = 540; mcentery = 972; mradius = 486 / 2; /** * 圆 * */ mpath.addcircle(mcenterx, mcentery, mradius, path.direction.cw); /** * 对勾 * */ mpath.moveto(mcenterx - mradius / 2, mcentery); mpath.lineto(mcenterx, mcentery + mradius / 2); mpath.lineto(mcenterx + mradius / 2, mcentery - mradius / 3); mpathmeasure.setpath(mpath, false); //要计算的path valueanimator animator = valueanimator.offloat(0, 2); //进度 0~1 是圆,1~2是对勾 animator.setrepeatcount(valueanimator.restart); animator.addupdatelistener(new valueanimator.animatorupdatelistener() { @override public void onanimationupdate(valueanimator animation) { mcuranimvalue = (float) animation.getanimatedvalue(); //得到当前的进度 invalidate();//重绘,重新执行ondraw()方法 } }); animator.setduration(5000); animator.start(); } @override protected void onsizechanged(int w, int h, int oldw, int oldh) { /* * 得到h,w的最小的那个值; * >> 1 移位 跟 /2 相同; * 乘以0.9f,表示占布局的90% * */ mradius = (math.min(h, w) >> 1) * 0.9f; // 中心坐标 mcenterx = w / 2; mcentery = h / 2; log.v("showlog", mcenterx + " " + mcentery + " " + mradius); postinvalidate(); super.onsizechanged(w, h, oldw, oldh); } @override protected void ondraw(canvas canvas) { super.ondraw(canvas); if (mcuranimvalue < 1) { float stop = mpathmeasure.getlength() * mcuranimvalue; mpathmeasure.getsegment(0, stop, mdstpath, true); } else if (mcuranimvalue == 1) { mpathmeasure.getsegment(0, mpathmeasure.getlength(), mdstpath, true); mpathmeasure.nextcontour(); } else { float stop = mpathmeasure.getlength() * (mcuranimvalue - 1); mpathmeasure.getsegment(0, stop, mdstpath, true); } canvas.drawpath(mdstpath, mpaint); } }
效果:
编程中我们会遇到多少挫折?表放弃,沙漠尽头必是绿洲。
原文来自:https://www.cnblogs.com/lanjiabin/p/12844172.html