Android特效之水波纹的实现
前言
水波纹特效,想必大家或多或少见过,在我的印象中,大致有如下几种:
支付宝 "咻咻咻" 式
流量球 "荡漾" 式
真实的水波纹效果,基于bitmap处理式
话不多说,先来看看效果:
填充式水波纹,间距相等
非填充式水波纹,间距相等
非填充式水波纹,间距不断变大
填充式水波纹,间距不断变小
想必大家已经知道基本的原理了,就是用canvas来画嘛,但可不是简单的画哦,请往下看。
分析
这种类型的水波纹,其实无非就是画圆而已,在给定的矩形中,一个个圆由最小半径扩大到最大半径,伴随着透明度从1.0变为0.0。我们假定这种扩散是匀速的,则一个圆从创建(透明度为1.0)到消失(透明度为0.0)的时长就是定值,那么某一时刻某一个圆的半径以及透明度完全可以由扩散时间(当前时间 - 创建时间)决定。
实现
按照上面的分析,我们写出以下circle
类来表示一个圆:
private class circle { private long mcreatetime; public circle() { this.mcreatetime = system.currenttimemillis(); } public int getalpha() { float percent = (system.currenttimemillis() - mcreatetime) * 1.0f / mduration; return (int) ((1.0f - percent) * 255); } public float getcurrentradius() { float percent = (system.currenttimemillis() - mcreatetime) * 1.0f / mduration; return minitialradius + percent * (mmaxradius - minitialradius); } }
自然而然,在waveview
中,要有一个list来
保存当前正在显示的圆:
private list<circle> mcirclelist = new arraylist<circle>();
我们定义一个start
方法,用来启动扩散:
public void start() { if (!misrunning) { misrunning = true; mcreatecircle.run(); } } private runnable mcreatecircle = new runnable() { @override public void run() { if (misrunning) { newcircle(); postdelayed(mcreatecircle, mspeed); // 每隔mspeed毫秒创建一个圆 } } }; private void newcircle() { long currenttime = system.currenttimemillis(); if (currenttime - mlastcreatetime < mspeed) { return; } circle circle = new circle(); mcirclelist.add(circle); invalidate(); mlastcreatetime = currenttime; }
start
方法只是简单的创建了一个圆并添加到了mcirclelist
中,同时开启了循环创建圆的runnable
,然后通知界面刷新,我们再看看ondraw
方法:
protected void ondraw(canvas canvas) { iterator<circle> iterator = mcirclelist.iterator(); while (iterator.hasnext()) { circle circle = iterator.next(); if (system.currenttimemillis() - circle.mcreatetime < mduration) { mpaint.setalpha(circle.getalpha()); canvas.drawcircle(getwidth() / 2, getheight() / 2, circle.getcurrentradius(), mpaint); } else { iterator.remove(); } } if (mcirclelist.size() > 0) { postinvalidatedelayed(10); } }
ondraw
方法遍历了每一个circle
,判断circle
的扩散时间是否超过了设定的扩散时间,如果是则移除,如果不是,则计算circle
当前的透明度和半径并绘制出来。我们添加了一个延时刷新来不断重绘界面,以达到连续的波纹扩散效果。
现在运行程序,应该能看到图2中的效果了,不过有点别扭,按常识,水波的间距是越来越大的,如何做到呢?
技巧
要让水波纹的半径非匀速变大,我们只能去修改circle.getcurrentradius()
方法了。我们再次看看这个方法:
public float getcurrentradius() { float percent = (system.currenttimemillis() - mcreatetime) * 1.0f / mduration; return minitialradius + percent * (mmaxradius - minitialradius); }
percent
表示circle
当前扩散时间和总扩散时间的一个百分比,考虑到当前扩散时间超过总扩散时间时circle
会被移除,因此percent
的实际区间为[0, 1],看到[0, 1],我不知道大家想到的是什么,我首先想到的就是差值器(interpolator
),我们可以通过定义差值器来实现对circle
半径变化的控制!
我们修改代码:
private interpolator minterpolator = new linearinterpolator(); public void setinterpolator(interpolator interpolator) { minterpolator = interpolator; if (minterpolator == null) { minterpolator = new linearinterpolator(); } } private class circle { private long mcreatetime; public circle() { this.mcreatetime = system.currenttimemillis(); } public int getalpha() { float percent = (system.currenttimemillis() - mcreatetime) * 1.0f / mduration; return (int) ((1.0f - minterpolator.getinterpolation(percent)) * 255); } public float getcurrentradius() { float percent = (system.currenttimemillis() - mcreatetime) * 1.0f / mduration; return minitialradius + minterpolator.getinterpolation(percent) * (mmaxradius - minitialradius); } }
这样,外部使用waveview
时,只需调用setinterpolator()
来定义不同的插值器即可实现不同的效果。
图3效果的代码:
mwaveview = (waveview) findviewbyid(r.id.wave_view); mwaveview.setduration(5000); mwaveview.setstyle(paint.style.stroke); mwaveview.setspeed(400); mwaveview.setcolor(color.parsecolor("#ff0000")); mwaveview.setinterpolator(new accelerateinterpolator(1.2f)); mwaveview.start();
图4效果的代码:
mwaveview = (waveview) findviewbyid(r.id.wave_view); mwaveview.setduration(5000); mwaveview.setstyle(paint.style.fill); mwaveview.setcolor(color.parsecolor("#ff0000")); mwaveview.setinterpolator(new linearoutslowininterpolator()); mwaveview.start();
附上waveview的所有代码:
/** * 水波纹特效 * created by hackware on 2016/6/17. */ public class waveview extends view { private float minitialradius; // 初始波纹半径 private float mmaxradiusrate = 0.85f; // 如果没有设置mmaxradius,可mmaxradius = 最小长度 * mmaxradiusrate; private float mmaxradius; // 最大波纹半径 private long mduration = 2000; // 一个波纹从创建到消失的持续时间 private int mspeed = 500; // 波纹的创建速度,每500ms创建一个 private interpolator minterpolator = new linearinterpolator(); private list<circle> mcirclelist = new arraylist<circle>(); private boolean misrunning; private boolean mmaxradiusset; private paint mpaint; private long mlastcreatetime; private runnable mcreatecircle = new runnable() { @override public void run() { if (misrunning) { newcircle(); postdelayed(mcreatecircle, mspeed); } } }; public waveview(context context) { this(context, null); } public waveview(context context, attributeset attrs) { super(context, attrs); mpaint = new paint(paint.anti_alias_flag); setstyle(paint.style.fill); } public void setstyle(paint.style style) { mpaint.setstyle(style); } @override protected void onsizechanged(int w, int h, int oldw, int oldh) { if (!mmaxradiusset) { mmaxradius = math.min(w, h) * mmaxradiusrate / 2.0f; } } public void setmaxradiusrate(float maxradiusrate) { this.mmaxradiusrate = maxradiusrate; } public void setcolor(int color) { mpaint.setcolor(color); } /** * 开始 */ public void start() { if (!misrunning) { misrunning = true; mcreatecircle.run(); } } /** * 停止 */ public void stop() { misrunning = false; } protected void ondraw(canvas canvas) { iterator<circle> iterator = mcirclelist.iterator(); while (iterator.hasnext()) { circle circle = iterator.next(); if (system.currenttimemillis() - circle.mcreatetime < mduration) { mpaint.setalpha(circle.getalpha()); canvas.drawcircle(getwidth() / 2, getheight() / 2, circle.getcurrentradius(), mpaint); } else { iterator.remove(); } } if (mcirclelist.size() > 0) { postinvalidatedelayed(10); } } public void setinitialradius(float radius) { minitialradius = radius; } public void setduration(long duration) { this.mduration = duration; } public void setmaxradius(float maxradius) { this.mmaxradius = maxradius; mmaxradiusset = true; } public void setspeed(int speed) { mspeed = speed; } private void newcircle() { long currenttime = system.currenttimemillis(); if (currenttime - mlastcreatetime < mspeed) { return; } circle circle = new circle(); mcirclelist.add(circle); invalidate(); mlastcreatetime = currenttime; } private class circle { private long mcreatetime; public circle() { this.mcreatetime = system.currenttimemillis(); } public int getalpha() { float percent = (system.currenttimemillis() - mcreatetime) * 1.0f / mduration; return (int) ((1.0f - minterpolator.getinterpolation(percent)) * 255); } public float getcurrentradius() { float percent = (system.currenttimemillis() - mcreatetime) * 1.0f / mduration; return minitialradius + minterpolator.getinterpolation(percent) * (mmaxradius - minitialradius); } } public void setinterpolator(interpolator interpolator) { minterpolator = interpolator; if (minterpolator == null) { minterpolator = new linearinterpolator(); } } }
总结
想必大家看完这篇文章会觉得原来插值器还可以这么用。其实,有些时候我们使用系统提供的api,往往过于局限其中,有时候换个思路,说不定会得到奇妙的效果。以上就是在android实现水波纹特效的全部内容,希望对大家开发android有所帮助。
。