一、前言
在很多app种内置了语音助手,也存在各种动画,主要原因是处理2个阶段问题,第一个是监听声音的等待效果,第二个是语意解析存在一定耗时的等待效果,前者要求有声音输入时有视觉反馈,后者让用户知道在处理某些事情,同时呢,这个效果还能互相切换,这是一般语音监听动画的设计逻辑。本文提供一种,希望对大家有所帮助。
效果图
(gif 有些卡,可能是压缩的原因)
二、实现方法
2.1 过渡动画
必须等待上一个动画结束后再切换为制定状态
2.2 声音抖动计算
本文没有明确计算线性音量,取出音量数据,进行了简单的计算
public void updateshakevalue(int volume) { if (this.getvisibility() != view.visible || !isattachedtowindow()) return; if (!isplaying) return; float ratio = volume * 1.0f / this.mmaxshakerange; if (ratio < 1f / 4) { ratio = 0; } if (ratio >= 1f / 4 && ratio < 2f / 4) { ratio = 1f / 4; } if (ratio >= 2f / 4 && ratio < 3f / 4) { ratio = 2f / 4; } if (ratio >= 3f / 4) { ratio = 1f; } updateshakeratio(ratio); }
2.3 模式切换
需要listening和loading 模式之间互相切换
public void startplay(final int state) { post(new runnable() { @override public void run() { setstate(state); if (!isplaying) { mcurrentstate = mnextstate; } isplaying = true; if (mnextstate == mcurrentstate) { if (state == state_listening) { startlisteninganim(); } else if (state == state_loading) { startloadinganim(); } } else { starttransformanim(); } } }); } #loading 效果 radarview.startplay(speechradarview.state_loading); #listening效果 radarview.startplay(speechradarview.state_listening); #停止播放 radarview.stopplay();
2.4 抖动幅度范围,以适应不同类型的需求
#最大振幅 radarview.setmaxshakerange(30); #当前值 radarview.updateshakevalue(20);
三、全部代码
public class speechradarview extends view { private static final long animation_circle_timeout = 1000; private static final long animation_loading_timeout = 800; private valueanimator mshakeanimatortimer; private int mfixedradius = 0; private int mmaxradius = 0; private textpaint mpaint; private animationcircle[] manimationcircle = new animationcircle[2]; private float mbullketstrokewidthsize; private animatorset manimatortimerset = null; private animatorset mnextanimatortimerset = null; private valueanimator mtransformanimatortimer; private int animation_main_color = 0x99ff8c14; private static final int main_color = 0xffff8c14; rectf arcbounds = new rectf(); linearinterpolator linearinterpolator = new linearinterpolator(); acceleratedecelerateinterpolator acceleratedecelerateinterpolator = new acceleratedecelerateinterpolator(); public static final int state_loading = 0; public static final int state_listening = 1; private int mcurrentstate = state_loading; private int mnextstate = state_loading; //过渡值 private float loading_stoke_width = 0; private int loading_start_angle = 90; private int mcurrentangle = loading_start_angle; private int mtransformloadingcolor = color.transparent; private int mtransformlisteningcolor = color.transparent; private boolean isplaying = false; private float mshakeratio = 0; private float mnextshakeratio = 0; private long mstartshaketime = 0; private int mmaxshakerange = 100; public speechradarview(context context) { this(context, null); } public speechradarview(context context, attributeset attrs) { this(context, attrs, 0); } public speechradarview(context context, attributeset attrs, int defstyleattr) { super(context, attrs, defstyleattr); initpaint(); } private void setstate(int state) { if (this.mnextstate == state) { return; } this.mnextstate = state; } public int getstate() { return mnextstate; } private void initpaint() { // 实例化画笔并打开抗锯齿 mpaint = new textpaint(paint.anti_alias_flag); mpaint.setantialias(true); mpaint.setpatheffect(new cornerpatheffect(10)); //设置线条类型 mpaint.setstrokewidth(dip2px(1)); mpaint.settextsize(dip2px((12))); mpaint.setstyle(paint.style.stroke); mbullketstrokewidthsize = dip2px(5); loading_stoke_width = dip2px(5); } @override protected void onmeasure(int widthmeasurespec, int heightmeasurespec) { super.onmeasure(widthmeasurespec, heightmeasurespec); super.onmeasure(widthmeasurespec, heightmeasurespec); int widthmode = measurespec.getmode(widthmeasurespec); int width = measurespec.getsize(widthmeasurespec); int heightmode = measurespec.getmode(heightmeasurespec); int height = measurespec.getsize(heightmeasurespec); if (widthmode != measurespec.exactly) { width = (int) dip2px(210); } if (heightmode != measurespec.exactly) { height = (int) dip2px(210); } setmeasureddimension(width, height); } public float dip2px(float dp) { return typedvalue.applydimension(typedvalue.complex_unit_dip, dp, getresources().getdisplaymetrics()); } @override protected void ondraw(canvas canvas) { super.ondraw(canvas); int width = getwidth(); int height = getheight(); if (width == 0 || height == 0) return; int centerx = width / 2; int centery = height / 2; int diameter = math.min(width, height) / 2; mfixedradius = diameter / 3; mmaxradius = diameter; initanimationcircle(); if (!isineditmode() && !isplaying) return; int layerid = savelayer(canvas, centerx, centery); if (mnextstate == mcurrentstate) { if (mcurrentstate == state_listening) { drawanimationcircle(canvas); drawfixcircle(canvas, main_color); drawflashbullket(canvas, color.white, mshakeratio); mshakeratio = 0; } else if (mcurrentstate == state_loading) { drawloadingarc(canvas, main_color); drawflashbullket(canvas, main_color, 0); } } else { if (this.mnextstate == state_listening) { drawloadingarc(canvas, mtransformloadingcolor); drawfixcircle(canvas, mtransformlisteningcolor); drawflashbullket(canvas, color.white, 0); } else { drawfixcircle(canvas, mtransformlisteningcolor); drawloadingarc(canvas, mtransformloadingcolor); drawflashbullket(canvas, main_color, 0); } } restorelayer(canvas, layerid); } private void drawloadingarc(canvas canvas, int color) { int oldcolor = mpaint.getcolor(); paint.style style = mpaint.getstyle(); float strokewidth = mpaint.getstrokewidth(); mpaint.setstrokewidth(loading_stoke_width); float inneroffset = loading_stoke_width / 2; mpaint.setcolor(color); mpaint.setstyle(paint.style.stroke); arcbounds.set(-mfixedradius inneroffset, -mfixedradius inneroffset, mfixedradius - inneroffset, mfixedradius - inneroffset); canvas.drawarc(arcbounds, mcurrentangle, 270, false, mpaint); mpaint.setcolor(oldcolor); mpaint.setstyle(style); mpaint.setstrokewidth(strokewidth); } private void drawflashbullket(canvas canvas, int color, float fraction) { int bullketzonewidth = mfixedradius; int bullketzoneheight = mfixedradius * 2 / 3; int minheight = (int) (bullketzoneheight / 3f); int maxrangeheight = (int) (bullketzoneheight * 2 / 3f); drawflashbullket(canvas, bullketzonewidth, color, minheight, (maxrangeheight * fraction)); } private void drawflashbullket(canvas canvas, int width, int color, int height, float delta) { int offset = (int) ((width - mbullketstrokewidthsize * 4) / 3); int oldcolor = mpaint.getcolor(); float strokewidth = mpaint.getstrokewidth(); if (delta < 0f) { delta = 0f; } mpaint.setcolor(color); mpaint.setstrokecap(paint.cap.round); mpaint.setstrokewidth(mbullketstrokewidthsize); for (int i = 0; i < 4; i ) { int startx = (int) (i * (offset mbullketstrokewidthsize) - width / 2 mbullketstrokewidthsize / 2); if (i == 0 || i == 3) { canvas.drawline(startx, -height / 2f delta * 1 / 3, startx, height / 2f delta * 1 / 3, mpaint); } else { canvas.drawline(startx, -(height / 2f delta * 2 / 3), startx, (height / 2f delta * 2 / 3), mpaint); } } mpaint.setcolor(oldcolor); mpaint.setstrokewidth(strokewidth); } @override protected void onsizechanged(int w, int h, int oldw, int oldh) { super.onsizechanged(w, h, oldw, oldh); } private void drawanimationcircle(canvas canvas) { for (int i = 0; i < manimationcircle.length; i ) { animationcircle circle = manimationcircle[i]; if (circle.radius > mfixedradius) { drawcircle(canvas, circle.color, circle.radius); log.e("animationcircle", "i=" i " , radius=" circle.radius); } else { log.d("animationcircle", "i=" i " , radius=" circle.radius); } } } private void initanimationcircle() { for (int i = 0; i < manimationcircle.length; i ) { if (manimationcircle[i] == null) { if (i == 0) { manimationcircle[i] = new animationcircle(mmaxradius, mfixedradius, 0x88ff8c14); } else { manimationcircle[i] = new animationcircle(mmaxradius, mfixedradius, 0x99ff8c14); } } else { if (manimationcircle[i].token != mmaxradius) { manimationcircle[i].radius = mfixedradius; manimationcircle[i].token = mmaxradius; } } } } private void drawcircle(canvas canvas, int color, float radius) { int oldcolor = mpaint.getcolor(); paint.style style = mpaint.getstyle(); float strokewidth = mpaint.getstrokewidth(); mpaint.setstrokewidth(0); mpaint.setcolor(color); mpaint.setstyle(paint.style.fill); canvas.drawcircle(0, 0, radius, mpaint); mpaint.setcolor(oldcolor); mpaint.setstyle(style); mpaint.setstrokewidth(strokewidth); } private void restorelayer(canvas canvas, int save) { canvas.restoretocount(save); } private int savelayer(canvas canvas, int centerx, int centery) { int save = canvas.save(); canvas.translate(centerx, centery); return save; } private void drawfixcircle(canvas canvas, int color) { drawcircle(canvas, color, mfixedradius); } public void startplay(final int state) { post(new runnable() { @override public void run() { setstate(state); if (!isplaying) { mcurrentstate = mnextstate; } isplaying = true; if (mnextstate == mcurrentstate) { if (state == state_listening) { startlisteninganim(); } else if (state == state_loading) { startloadinganim(); } } else { starttransformanim(); } } }); } public void startloadinganim() { if (manimatortimerset != null) { manimatortimerset.cancel(); } manimatortimerset = getanimatorloadingset(); if (manimatortimerset != null) { manimatortimerset.start(); } } private void starttransformanim() { if (mnextanimatortimerset != null) { mnextanimatortimerset.cancel(); } if (mtransformanimatortimer != null) { mtransformanimatortimer.cancel(); } mtransformanimatortimer = buildtransformanimatortimer(mcurrentstate, mnextstate); if (mnextstate == state_listening) { mnextanimatortimerset = getanimatorcircleset(); } else { mnextanimatortimerset = getanimatorloadingset(); } if (mtransformanimatortimer != null) { mtransformanimatortimer.start(); } if (mnextanimatortimerset != null) { mnextanimatortimerset.start(); } } public void startlisteninganim() { if (manimatortimerset != null) { manimatortimerset.cancel(); } animatorset animatortimerset = getanimatorcircleset(); if (animatortimerset == null) return; manimatortimerset = animatortimerset; manimatortimerset.start(); } @nullable private animatorset getanimatorcircleset() { animatorset animatortimerset = new animatorset(); valueanimator firstanimatortimer = buildcircleanimatortimer(manimationcircle[0]); valueanimator secondanimatortimer = buildcircleanimatortimer(manimationcircle[1]); if (firstanimatortimer == null || secondanimatortimer == null) return null; secondanimatortimer.setstartdelay(animation_circle_timeout / 2); animatortimerset.playtogether(firstanimatortimer, secondanimatortimer); return animatortimerset; } @nullable private animatorset getanimatorloadingset() { valueanimator valueanimator = buildloadinganimatortimer(); if (valueanimator == null) return null; animatorset animatortimerset = new animatorset(); animatortimerset.play(valueanimator); return animatortimerset; } @nullable private valueanimator buildcircleanimatortimer(final animationcircle circle) { if (mfixedradius <= 0 || circle == null) return null; valueanimator animatortimer = valueanimator.offloat(mfixedradius, math.min(getwidth(),getheight()) / 2f); animatortimer.setduration(animation_circle_timeout); animatortimer.setrepeatcount(valueanimator.infinite); animatortimer.setinterpolator(linearinterpolator); animatortimer.addupdatelistener(new valueanimator.animatorupdatelistener() { @override public void onanimationupdate(valueanimator animation) { float dx = (float) animation.getanimatedvalue(); float fraction = 1 - animation.getanimatedfraction(); float radius = dx; int color = argb((int) (color.alpha(animation_main_color) * fraction), color.red(animation_main_color), color.green(animation_main_color), color.blue(animation_main_color)); if (mcurrentstate != mnextstate) { color = color.transparent; } if (circle.radius != radius || circle.color != color) { circle.radius = radius; circle.color = color; postinvalidate(); } } }); return animatortimer; } @nullable private valueanimator buildloadinganimatortimer() { if (mfixedradius <= 0) return null; valueanimator animatortimer = valueanimator.offloat(0, 1); animatortimer.setduration(animation_loading_timeout); animatortimer.setrepeatcount(valueanimator.infinite); animatortimer.setinterpolator(new acceleratedecelerateinterpolator()); animatortimer.addupdatelistener(new valueanimator.animatorupdatelistener() { @override public void onanimationupdate(valueanimator animation) { float fraction = animation.getanimatedfraction(); int angle = (int) (loading_start_angle fraction * 360); if (mcurrentangle != angle) { mcurrentangle = angle; postinvalidate(); } } }); return animatortimer; } @nullable private valueanimator buildtransformanimatortimer(final int currentstate, final int nextstate) { if (mfixedradius <= 0) return null; final int alpha = color.alpha(main_color); final int red = color.red(main_color); final int green = color.green(main_color); final int blue = color.blue(main_color); valueanimator animatortimer = valueanimator.offloat(currentstate, nextstate); animatortimer.setduration(animation_loading_timeout); animatortimer.setinterpolator(acceleratedecelerateinterpolator); animatortimer.addupdatelistener(new valueanimator.animatorupdatelistener() { @override public void onanimationupdate(valueanimator animation) { float animatedvalue = (float) animation.getanimatedvalue(); if (mcurrentstate != mnextstate) { mtransformlisteningcolor = argb((int) (alpha * animatedvalue), red, green, blue); mtransformloadingcolor = argb((int) (alpha * (1 - animatedvalue)), red, green, blue); log.d("animatedvalue", " --- >" animatedvalue); postinvalidate(); } } }); animatortimer.addlistener(new animatorlisteneradapter() { @override public void onanimationend(animator animation) { super.onanimationend(animation); resetanimationstate(); } @override public void onanimationcancel(animator animation) { super.onanimationcancel(animation); resetanimationstate(); } }); return animatortimer; } private void resetanimationstate() { mcurrentstate = mnextstate; if (manimatortimerset != null) { if (manimatortimerset != mnextanimatortimerset) { manimatortimerset.cancel(); } } manimatortimerset = mnextanimatortimerset; } @override protected void ondetachedfromwindow() { super.ondetachedfromwindow(); stopplay(); } public void stopplay() { isplaying = false; mcurrentangle = loading_start_angle; try { if (manimatortimerset != null) { manimatortimerset.cancel(); } if (mnextanimatortimerset != null) { mnextanimatortimerset.cancel(); } if (mshakeanimatortimer != null) { mshakeanimatortimer.cancel(); } } catch (exception e) { e.printstacktrace(); } resetanimationcircle(); postinvalidate(); } private void resetanimationcircle() { for (animationcircle circle : manimationcircle) { if (circle != null) { circle.radius = mfixedradius; } } } public static int argb( @intrange(from = 0, to = 255) int alpha, @intrange(from = 0, to = 255) int red, @intrange(from = 0, to = 255) int green, @intrange(from = 0, to = 255) int blue) { return (alpha << 24) | (red << 16) | (green << 8) | blue; } public boolean isplaying() { return isplaying; } private void updateshakeratio(final float ratio) { long currenttimemillis = system.currenttimemillis(); if (currenttimemillis - mstartshaketime >= 150) { mnextshakeratio = ratio; if (mshakeratio != mnextshakeratio) { startshakeanimation(); } mstartshaketime = currenttimemillis; } } private void startshakeanimation() { if (mshakeanimatortimer != null) { mshakeanimatortimer.cancel(); } mshakeanimatortimer = valueanimator.offloat(mshakeratio, mnextshakeratio); mshakeanimatortimer.setduration(100); mshakeanimatortimer.setinterpolator(acceleratedecelerateinterpolator); mshakeanimatortimer.addupdatelistener(new valueanimator.animatorupdatelistener() { @override public void onanimationupdate(valueanimator animation) { float ratio = (float) animation.getanimatedvalue(); if (mshakeratio != ratio) { mshakeratio = ratio; postinvalidate(); } } }); mshakeanimatortimer.start(); } public void setmaxshakerange(int maxshakerange) { this.mmaxshakerange = maxshakerange; if (this.mmaxshakerange <= 0) this.mmaxshakerange = 100; } public void updateshakevalue(int volume) { if (this.getvisibility() != view.visible || !isattachedtowindow()) return; if (!isplaying) return; float ratio = volume * 1.0f / this.mmaxshakerange; if (ratio < 1f / 4) { ratio = 0; } if (ratio >= 1f / 4 && ratio < 2f / 4) { ratio = 1f / 4; } if (ratio >= 2f / 4 && ratio < 3f / 4) { ratio = 2f / 4; } if (ratio >= 3f / 4) { ratio = 1f; } updateshakeratio(ratio); } public boolean isattachedtowindow() { if (build.version.sdk_int >= build.version_codes.kitkat) { return super.isattachedtowindow(); } else { return getwindowtoken() != null; } } private static class animationcircle { private float radius; private int color; private int token; animationcircle(int token, int radius, int color) { this.radius = radius; this.color = color; this.token = token; } } }
四、总结
总体上这个设计不是很难,难点是状态切换的一些过渡设计,保证上一个动画结束完成之后才能展示下一个动画,其词就是抖动逻辑,实际上也不是很复杂,第三方sdk的音量值一般都是有的,实时获取就好了。
以上就是android实现录音监听动画的示例代码的详细内容,更多关于android录音监听动画的资料请关注其它相关文章!