Android使用音频信息绘制动态波纹
在一些音乐类应用中, 经常会展示随着节奏上下起伏的波纹信息, 这些波纹形象地传达了声音信息, 可以提升用户体验, 那么是如何实现的呢? 可以使用visualizer类获取当前播放的声音信息, 并绘制在画布上, 使用波纹展示即可. 我来讲解一下使用方法.
主要
(1) visualizer类提取波纹信息的方式.
(2) 应用动态权限管理的方法.
(3) 分离自定义视图的展示和逻辑.
1. 基础准备
android 6.0引入动态权限管理, 在这个项目中, 会使用系统的音频信息, 因此把权限管理引入这个项目, 参考. gradle配置引入了lambda表达式, 参考.
页面布局, 使用自定义的波纹视图控件.
<!--波纹视图--> <me.chunyu.spike.wcl_visualizer_demo.visualizers.waveformview android:id="@+id/main_wv_waveform" android:layout_width="match_parent" android:layout_height="match_parent"/>
效果
2. 首页逻辑
添加动态权限管理, 在启动页面时, 获取应用所需的音频权限.
rendererfactory工厂类创建波纹的绘制类simplewaveformrender.
startvisualiser方法获取当前播放音乐的音频信息.
注意页面关闭, 在onpause时, 释放visualiser类.
public class mainactivity extends appcompatactivity { private static final int capture_size = 256; // 获取这些数据, 用于显示 private static final int request_code = 0; // 权限 private static final string[] permissions = new string[]{ manifest.permission.record_audio, manifest.permission.modify_audio_settings }; @bind(r.id.main_wv_waveform) waveformview mwvwaveform; // 波纹视图 private visualizer mvisualizer; // 音频可视化类 @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); butterknife.bind(this); rendererfactory rendererfactory = new rendererfactory(); mwvwaveform.setrenderer(rendererfactory.createsimplewaveformrender(contextcompat.getcolor(this, r.color.colorprimary), color.white)); } @override protected void onresume() { super.onresume(); permissionschecker checker = new permissionschecker(this); if (checker.lakespermissions(permissions)) { permissionsactivity.startactivityforresult(this, request_code, permissions); } else { startvisualiser(); } } @override protected void onactivityresult(int requestcode, int resultcode, intent data) { super.onactivityresult(requestcode, resultcode, data); if (requestcode == request_code && resultcode == permissionsactivity.permissions_denied) { finish(); } } // 设置音频线 private void startvisualiser() { mvisualizer = new visualizer(0); // 初始化 mvisualizer.setdatacapturelistener(new visualizer.ondatacapturelistener() { @override public void onwaveformdatacapture(visualizer visualizer, byte[] waveform, int samplingrate) { if (mwvwaveform != null) { mwvwaveform.setwaveform(waveform); } } @override public void onfftdatacapture(visualizer visualizer, byte[] fft, int samplingrate) { } }, visualizer.getmaxcapturerate(), true, false); mvisualizer.setcapturesize(capture_size); mvisualizer.setenabled(true); } // 释放 @override protected void onpause() { if (mvisualizer != null) { mvisualizer.setenabled(false); mvisualizer.release(); } super.onpause(); } }
visualizer类
new visualizer(0), 初始化; setcapturesize, 获取波纹数量; setenabled, 启动监听;
setdatacapturelistener, 第一个参数是回调, 使用waveformdata或fftdata; 第二个是更新率; 第三个是判断使用waveformdata; 第四个是判断使用fftdata, 第三\四个均与回调的返回值有关.
3. 波纹视图
页面框架, 分离显示和逻辑, 使用接口渲染, 输入画布canvas和波纹waveform.
/** * 音频波纹视图 * <p> * created by wangchenlong on 16/2/11. */ public class waveformview extends view { private waveformrenderer mrenderer; // 绘制类 private byte[] mwaveform; // 波纹形状 public waveformview(context context) { super(context); } public waveformview(context context, attributeset attrs) { super(context, attrs); } public waveformview(context context, attributeset attrs, int defstyleattr) { super(context, attrs, defstyleattr); } @targetapi(21) public waveformview(context context, attributeset attrs, int defstyleattr, int defstyleres) { super(context, attrs, defstyleattr, defstyleres); } public void setrenderer(waveformrenderer renderer) { mrenderer = renderer; } public void setwaveform(byte[] waveform) { mwaveform = arrays.copyof(waveform, waveform.length); // 数组复制 invalidate(); // 设置波纹之后, 需要重绘 } @override protected void ondraw(canvas canvas) { super.ondraw(canvas); if (mrenderer != null) { mrenderer.render(canvas, mwaveform); } } }
数组复制arrays.copyof(), 在设置波纹后重绘页面invalidate().
4. 波纹逻辑
核心部分renderwaveform, 渲染波纹.
把页面分为网格样式, 根据波纹值, 绘制曲线; 没有波纹, 绘制居中水平直线.
/** * 波纹渲染逻辑 * <p> * created by wangchenlong on 16/2/12. */ public class simplewaveformrenderer implements waveformrenderer { private static final int y_factor = 0xff; // 2的8次方 = 256 private static final float half_factor = 0.5f; @colorint private final int mbackgroundcolor; private final paint mforegroundpaint; private final path mwaveformpath; private simplewaveformrenderer(@colorint int backgroundcolor, paint foregroundpaint, path waveformpath) { mbackgroundcolor = backgroundcolor; mforegroundpaint = foregroundpaint; mwaveformpath = waveformpath; } public static simplewaveformrenderer newinstance(@colorint int backgroundcolor, @colorint int foregroundcolour) { paint paint = new paint(); paint.setcolor(foregroundcolour); paint.setantialias(true); // 抗锯齿 paint.setstrokewidth(8.0f); // 设置宽度 paint.setstyle(paint.style.stroke); // 填充 path waveformpath = new path(); return new simplewaveformrenderer(backgroundcolor, paint, waveformpath); } @override public void render(canvas canvas, byte[] waveform) { canvas.drawcolor(mbackgroundcolor); float width = canvas.getwidth(); float height = canvas.getheight(); mwaveformpath.reset(); // 没有数据 if (waveform != null) { // 绘制波形 renderwaveform(waveform, width, height); } else { // 绘制直线 renderblank(width, height); } canvas.drawpath(mwaveformpath, mforegroundpaint); } private void renderwaveform(byte[] waveform, float width, float height) { float xincrement = width / (float) (waveform.length); // 水平块数 float yincrement = height / y_factor; // 竖直块数 int halfheight = (int) (height * half_factor); // 居中位置 mwaveformpath.moveto(0, halfheight); for (int i = 1; i < waveform.length; ++i) { float yposition = waveform[i] > 0 ? height - (yincrement * waveform[i]) : -(yincrement * waveform[i]); mwaveformpath.lineto(xincrement * i, yposition); } mwaveformpath.lineto(width, halfheight); // 最后的点, 水平居中 } // 居中画一条直线 private void renderblank(float width, float height) { int y = (int) (height * half_factor); mwaveformpath.moveto(0, y); mwaveformpath.lineto(width, y); } }
绘制移动moveto, 绘制直线lineto.
动画效果
通过绘制波纹, 可以类似地绘制一些连续数据, 更加直观地展示, 提升用户体验.