一看就喜欢的loading动画效果Android分析实现
还是比较有新意,复杂度也不是非常高,所以就花时间整理一下,我们先一起看下原gif图效果:
从效果上看,我们需要考虑以下几个问题:
- 1.叶子的随机产生;
- 2.叶子随着一条正余弦曲线移动;
- 3.叶子在移动的时候旋转,旋转方向随机,正时针或逆时针;
- 4.叶子遇到进度条,似乎是融合进入;
- 5.叶子不能超出最左边的弧角;
- 7.叶子飘出时的角度不是一致,走的曲线的振幅也有差别,否则太有规律性,缺乏美感;
总的看起来,需要注意和麻烦的地方主要是以上几点,当然还有一些细节问题,比如最左边是圆弧等等;
那接下来我们将效果进行分解,然后逐个击破:
整个效果来说,我们需要的图主要是飞动的小叶子和右边旋转的风扇,其他的部分都可以用色值进行绘制,当然我们为了方便,就连底部框一起切了;
先从gif 图里把飞动的小叶子和右边旋转的风扇、底部框抠出来,小叶子图如下:
我们需要处理的主要有两个部分:
- 1. 随着进度往前绘制的进度条;
- 2. 不断飞出来的小叶片;
我们先处理第一部分 - 随着进度往前绘制的进度条:
进度条的位置根据外层传入的 progress 进行计算,可以分为图中 1、2、3 三个阶段:
- 1. 当progress 较小,算出的当前距离还在弧形以内时,需要绘制如图所示 1 区域的弧形,其余部分用白色填充;
- 2. 当 progress 算出的距离到2时,需要绘制棕色半圆弧形,其余部分用白色矩形填充;
- 3. 当 progress 算出的距离到3 时,需要绘制棕色半圆弧形,棕色矩形,白色矩形;
- 4. 当 progress 算出的距离到头时,需要绘制棕色半圆弧形,棕色矩形;(可以合并到3中)
首先根据进度条的宽度和当前进度、总进度算出当前的位置:
//mprogresswidth为进度条的宽度,根据当前进度算出进度条的位置 mcurrentprogressposition = mprogresswidth * mprogress / total_progress;
然后按照上面的逻辑进行绘制,其中需要计算上图中的红色弧角角度,计算方法如下:
// 单边角度 int angle = (int) math.todegrees(math.acos((marcradius - mcurrentprogressposition)/ (float) marcradius));
math.acos() -反余弦函数;
math.todegrees() - 弧度转化为角度,math.toradians 角度转化为弧度
所以圆弧的起始点为:
int startangle = 180 - angle;
圆弧划过的角度为:
2 * angle
这一块的代码如下:
// mprogresswidth为进度条的宽度,根据当前进度算出进度条的位置 mcurrentprogressposition = mprogresswidth * mprogress / total_progress; // 即当前位置在图中所示1范围内 if (mcurrentprogressposition < marcradius) { log.i(tag, "mprogress = " + mprogress + "---mcurrentprogressposition = " + mcurrentprogressposition + "--marcprogresswidth" + marcradius); // 1.绘制白色arc,绘制orange arc // 2.绘制白色矩形 // 1.绘制白色arc canvas.drawarc(marcrectf, 90, 180, false, mwhitepaint); // 2.绘制白色矩形 mwhiterectf.left = marcrightlocation; canvas.drawrect(mwhiterectf, mwhitepaint); // 3.绘制棕色 arc // 单边角度 int angle = (int) math.todegrees(math.acos((marcradius - mcurrentprogressposition) / (float) marcradius)); // 起始的位置 int startangle = 180 - angle; // 扫过的角度 int sweepangle = 2 * angle; log.i(tag, "startangle = " + startangle); canvas.drawarc(marcrectf, startangle, sweepangle, false, morangepaint); } else { log.i(tag, "mprogress = " + mprogress + "---transfer-----mcurrentprogressposition = " + mcurrentprogressposition + "--marcprogresswidth" + marcradius); // 1.绘制white rect // 2.绘制orange arc // 3.绘制orange rect // 1.绘制white rect mwhiterectf.left = mcurrentprogressposition; canvas.drawrect(mwhiterectf, mwhitepaint); // 2.绘制orange arc canvas.drawarc(marcrectf, 90, 180, false, morangepaint); // 3.绘制orange rect morangerectf.left = marcrightlocation; morangerectf.right = mcurrentprogressposition; canvas.drawrect(morangerectf, morangepaint); }
接下来再来看叶子部分:
首先根据效果情况基本确定出 曲线函数,标准函数方程为:y = a(wx+q)+h,其中w影响周期,a影响振幅 ,周期t= 2 * math.pi/w;
根据效果可以看出,周期大致为总进度长度,所以确定w=(float) ((float) 2 * math.pi /mprogresswidth);
仔细观察效果,我们可以发现,叶子飘动的过程中振幅不是完全一致的,产生一种错落的效果,既然如此,我们给叶子定义一个type,根据type 确定不同的振幅;
我们创建一个叶子对象:
private class leaf { // 在绘制部分的位置 float x, y; // 控制叶子飘动的幅度 starttype type; // 旋转角度 int rotateangle; // 旋转方向--0代表顺时针,1代表逆时针 int rotatedirection; // 起始时间(ms) long starttime; }
类型采用枚举进行定义,其实就是用来区分不同的振幅:
private enum starttype { little, middle, big }
创建一个leaffactory类用于创建一个或多个叶子信息:
private class leaffactory { private static final int max_leafs = 6; random random = new random(); // 生成一个叶子信息 public leaf generateleaf() { leaf leaf = new leaf(); int randomtype = random.nextint(3); // 随时类型- 随机振幅 starttype type = starttype.middle; switch (randomtype) { case 0: break; case 1: type = starttype.little; break; case 2: type = starttype.big; break; default: break; } leaf.type = type; // 随机起始的旋转角度 leaf.rotateangle = random.nextint(360); // 随机旋转方向(顺时针或逆时针) leaf.rotatedirection = random.nextint(2); // 为了产生交错的感觉,让开始的时间有一定的随机性 maddtime += random.nextint((int) (leaf_float_time * 1.5)); leaf.starttime = system.currenttimemillis() + maddtime; return leaf; } // 根据最大叶子数产生叶子信息 public list<leaf> generateleafs() { return generateleafs(max_leafs); } // 根据传入的叶子数量产生叶子信息 public list<leaf> generateleafs(int leafsize) { list<leaf> leafs = new linkedlist<leaf>(); for (int i = 0; i < leafsize; i++) { leafs.add(generateleaf()); } return leafs; } }
定义两个常亮分别记录中等振幅和之间的振幅差:
// 中等振幅大小 private static final int middle_amplitude = 13; // 不同类型之间的振幅差距 private static final int amplitude_disparity = 5; [html] view plain copy 在code上查看代码片派生到我的代码片 // 中等振幅大小 private int mmiddleamplitude = middle_amplitude; // 振幅差 private int mamplitudedisparity = amplitude_disparity;
有了以上信息,我们则可以获取到叶子的y值:
// 通过叶子信息获取当前叶子的y值 private int getlocationy(leaf leaf) { // y = a(wx+q)+h float w = (float) ((float) 2 * math.pi / mprogresswidth); float a = mmiddleamplitude; switch (leaf.type) { case little: // 小振幅 = 中等振幅 - 振幅差 a = mmiddleamplitude - mamplitudedisparity; break; case middle: a = mmiddleamplitude; break; case big: // 小振幅 = 中等振幅 + 振幅差 a = mmiddleamplitude + mamplitudedisparity; break; default: break; } log.i(tag, "---a = " + a + "---w = " + w + "--leaf.x = " + leaf.x); return (int) (a * math.sin(w * leaf.x)) + marcradius * 2 / 3; }
接下来,我们开始绘制叶子:
/** * 绘制叶子 * * @param canvas */ private void drawleafs(canvas canvas) { long currenttime = system.currenttimemillis(); for (int i = 0; i < mleafinfos.size(); i++) { leaf leaf = mleafinfos.get(i); if (currenttime > leaf.starttime && leaf.starttime != 0) { // 绘制叶子--根据叶子的类型和当前时间得出叶子的(x,y) getleaflocation(leaf, currenttime); // 根据时间计算旋转角度 canvas.save(); // 通过matrix控制叶子旋转 matrix matrix = new matrix(); float transx = mleftmargin + leaf.x; float transy = mleftmargin + leaf.y; log.i(tag, "left.x = " + leaf.x + "--leaf.y=" + leaf.y); matrix.posttranslate(transx, transy); // 通过时间关联旋转角度,则可以直接通过修改leaf_rotate_time调节叶子旋转快慢 float rotatefraction = ((currenttime - leaf.starttime) % leaf_rotate_time) / (float) leaf_rotate_time; int angle = (int) (rotatefraction * 360); // 根据叶子旋转方向确定叶子旋转角度 int rotate = leaf.rotatedirection == 0 ? angle + leaf.rotateangle : -angle + leaf.rotateangle; matrix.postrotate(rotate, transx + mleafwidth / 2, transy + mleafheight / 2); canvas.drawbitmap(mleafbitmap, matrix, mbitmappaint); canvas.restore(); } else { continue; } } }
最后,向外层暴露几个接口:
/** * 设置中等振幅 * * @param amplitude */ public void setmiddleamplitude(int amplitude) { this.mmiddleamplitude = amplitude; } /** * 设置振幅差 * * @param disparity */ public void setmplitudedisparity(int disparity) { this.mamplitudedisparity = disparity; } /** * 获取中等振幅 * * @param amplitude */ public int getmiddleamplitude() { return mmiddleamplitude; } /** * 获取振幅差 * * @param disparity */ public int getmplitudedisparity() { return mamplitudedisparity; } /** * 设置进度 * * @param progress */ public void setprogress(int progress) { this.mprogress = progress; postinvalidate(); } /** * 设置叶子飘完一个周期所花的时间 * * @param time */ public void setleaffloattime(long time) { this.mleaffloattime = time; } /** * 设置叶子旋转一周所花的时间 * * @param time */ public void setleafrotatetime(long time) { this.mleafrotatetime = time;
这些接口用来干嘛呢?用于把我们的动效做成完全可手动调节的,这样做有什么好处呢?
1. 更加便于产品、射鸡湿查看效果,避免yy,自己手动调节,不会出现要你一遍遍的改参数安装、查看、再改、再查看... ... n遍之后说 “这好像不是我想要的” -- 瞬间天崩地裂,天昏地暗,感觉被全世界抛弃;
2. 便于体现你是一个考虑全面,思维缜密,会编程、会设计的艺术家,当然这纯属yy,主要还是方便大家;
如此一来,射鸡湿们只需要不断的调节即可实时的看到展现的效果,最后只需要把最终的参数反馈过来即可,万事大吉,一了百了;
当然,如果对方是个漂亮的妹子,而你又苦于没有机会搭讪,以上内容就当我没说,尽情的不按要求写吧,她肯定会主动找你的,说不定连饭都反过来请了... ...
好啦,言归正传,完成收尾部分,我们让所有的参数都可调节起来:
把剩下的layout 和activity贴出来:
activity:
public class leafloadingactivity extends activity implements onseekbarchangelistener, onclicklistener { handler mhandler = new handler() { public void handlemessage(message msg) { switch (msg.what) { case refresh_progress: if (mprogress < 40) { mprogress += 1; // 随机800ms以内刷新一次 mhandler.sendemptymessagedelayed(refresh_progress, new random().nextint(800)); mleafloadingview.setprogress(mprogress); } else { mprogress += 1; // 随机1200ms以内刷新一次 mhandler.sendemptymessagedelayed(refresh_progress, new random().nextint(1200)); mleafloadingview.setprogress(mprogress); } break; default: break; } }; }; private static final int refresh_progress = 0x10; private leafloadingview mleafloadingview; private seekbar mampireseekbar; private seekbar mdistanceseekbar; private textview mmplitudetext; private textview mdisparitytext; private view mfanview; private button mclearbutton; private int mprogress = 0; private textview mprogresstext; private view maddprogress; private seekbar mfloattimeseekbar; private seekbar mrotatetimeseekbar; private textview mfloattimetext; private textview mrotatetimetext; @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.leaf_loading_layout); initviews(); mhandler.sendemptymessagedelayed(refresh_progress, 3000); } private void initviews() { mfanview = findviewbyid(r.id.fan_pic); rotateanimation rotateanimation = dxanimationutils.initrotateanimation(false, 1500, true, animation.infinite); mfanview.startanimation(rotateanimation); mclearbutton = (button) findviewbyid(r.id.clear_progress); mclearbutton.setonclicklistener(this); mleafloadingview = (leafloadingview) findviewbyid(r.id.leaf_loading); mmplitudetext = (textview) findviewbyid(r.id.text_ampair); mmplitudetext.settext(getstring(r.string.current_mplitude, mleafloadingview.getmiddleamplitude())); mdisparitytext = (textview) findviewbyid(r.id.text_disparity); mdisparitytext.settext(getstring(r.string.current_disparity, mleafloadingview.getmplitudedisparity())); mampireseekbar = (seekbar) findviewbyid(r.id.seekbar_ampair); mampireseekbar.setonseekbarchangelistener(this); mampireseekbar.setprogress(mleafloadingview.getmiddleamplitude()); mampireseekbar.setmax(50); mdistanceseekbar = (seekbar) findviewbyid(r.id.seekbar_distance); mdistanceseekbar.setonseekbarchangelistener(this); mdistanceseekbar.setprogress(mleafloadingview.getmplitudedisparity()); mdistanceseekbar.setmax(20); maddprogress = findviewbyid(r.id.add_progress); maddprogress.setonclicklistener(this); mprogresstext = (textview) findviewbyid(r.id.text_progress); mfloattimetext = (textview) findviewbyid(r.id.text_float_time); mfloattimeseekbar = (seekbar) findviewbyid(r.id.seekbar_float_time); mfloattimeseekbar.setonseekbarchangelistener(this); mfloattimeseekbar.setmax(5000); mfloattimeseekbar.setprogress((int) mleafloadingview.getleaffloattime()); mfloattimetext.settext(getresources().getstring(r.string.current_float_time, mleafloadingview.getleaffloattime())); mrotatetimetext = (textview) findviewbyid(r.id.text_rotate_time); mrotatetimeseekbar = (seekbar) findviewbyid(r.id.seekbar_rotate_time); mrotatetimeseekbar.setonseekbarchangelistener(this); mrotatetimeseekbar.setmax(5000); mrotatetimeseekbar.setprogress((int) mleafloadingview.getleafrotatetime()); mrotatetimetext.settext(getresources().getstring(r.string.current_float_time, mleafloadingview.getleafrotatetime())); } @override public void onprogresschanged(seekbar seekbar, int progress, boolean fromuser) { if (seekbar == mampireseekbar) { mleafloadingview.setmiddleamplitude(progress); mmplitudetext.settext(getstring(r.string.current_mplitude, progress)); } else if (seekbar == mdistanceseekbar) { mleafloadingview.setmplitudedisparity(progress); mdisparitytext.settext(getstring(r.string.current_disparity, progress)); } else if (seekbar == mfloattimeseekbar) { mleafloadingview.setleaffloattime(progress); mfloattimetext.settext(getresources().getstring(r.string.current_float_time, progress)); } else if (seekbar == mrotatetimeseekbar) { mleafloadingview.setleafrotatetime(progress); mrotatetimetext.settext(getresources().getstring(r.string.current_rotate_time, progress)); } } @override public void onstarttrackingtouch(seekbar seekbar) { } @override public void onstoptrackingtouch(seekbar seekbar) { } @override public void onclick(view v) { if (v == mclearbutton) { mleafloadingview.setprogress(0); mhandler.removecallbacksandmessages(null); mprogress = 0; } else if (v == maddprogress) { mprogress++; mleafloadingview.setprogress(mprogress); mprogresstext.settext(string.valueof(mprogress)); } } }
layout:
<?xml version="1.0" encoding="utf-8"?> <linearlayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#fed255" android:orientation="vertical" > <textview android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_margintop="100dp" android:text="loading ..." android:textcolor="#ffa800" android:textsize=" 30dp" /> <relativelayout android:id="@+id/leaf_content" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margintop="50dp" > <com.baidu.batterysaverdemo.ui.leafloadingview android:id="@+id/leaf_loading" android:layout_width="302dp" android:layout_height="61dp" android:layout_centerhorizontal="true" /> <imageview android:id="@+id/fan_pic" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignparentright="true" android:layout_centervertical="true" android:layout_marginright="35dp" android:src="@drawable/fengshan" /> </relativelayout> <scrollview android:layout_width="match_parent" android:layout_height="match_parent" > <linearlayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <linearlayout android:id="@+id/seek_content_one" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginleft="15dp" android:layout_marginright="15dp" android:layout_margintop="15dp" > <textview android:id="@+id/text_ampair" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:textcolor="#ffffa800" android:textsize="15dp" /> <seekbar android:id="@+id/seekbar_ampair" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginleft="5dp" android:layout_weight="1" /> </linearlayout> <linearlayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginleft="15dp" android:layout_marginright="15dp" android:layout_margintop="15dp" android:orientation="horizontal" > <textview android:id="@+id/text_disparity" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:textcolor="#ffffa800" android:textsize="15dp" /> <seekbar android:id="@+id/seekbar_distance" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginleft="5dp" android:layout_weight="1" /> </linearlayout> <linearlayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginleft="15dp" android:layout_marginright="15dp" android:layout_margintop="15dp" android:orientation="horizontal" > <textview android:id="@+id/text_float_time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:textcolor="#ffffa800" android:textsize="15dp" /> <seekbar android:id="@+id/seekbar_float_time" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginleft="5dp" android:layout_weight="1" /> </linearlayout> <linearlayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginleft="15dp" android:layout_marginright="15dp" android:layout_margintop="15dp" android:orientation="horizontal" > <textview android:id="@+id/text_rotate_time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:textcolor="#ffffa800" android:textsize="15dp" /> <seekbar android:id="@+id/seekbar_rotate_time" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginleft="5dp" android:layout_weight="1" /> </linearlayout> <button android:id="@+id/clear_progress" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margintop="15dp" android:text="去除进度条,玩转弧线" android:textsize="18dp" /> <linearlayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginleft="15dp" android:layout_marginright="15dp" android:layout_margintop="15dp" android:orientation="horizontal" > <button android:id="@+id/add_progress" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="增加进度: " android:textsize="18dp" /> <textview android:id="@+id/text_progress" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:textcolor="#ffffa800" android:textsize="15dp" /> </linearlayout> </linearlayout> </scrollview> </linearlayout>
最终效果如下,本来录了20+s,但是ps只能转5s,所以有兴趣的大家自己运行的玩吧:
以上就是本文的全部内容,希望对大家的学习有所帮助。
下一篇: java向文件末尾添加内容示例分享