一、前言
在很多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录音监听动画的资料请关注代码网其它相关文章!
发表评论