[Android]自定义绘制一个简易的音频条形图,附上对MP3音频波形数据的采集与展现
来源:互联网 发布:手机淘宝怎么开通不了? 编辑:程序博客网 时间:2024/05/29 21:17
在项目中需要到数据统计的地方,往往都需要到一些图的展示,比如曲线图、折线图、饼状图、圆形图、条形图等等。在本文中我们来实现一个简易的条形图的绘制。
首先,我们创建一个BarGraphView类,让这个类继承自View,一般重写View都必须重写View的一参构造方法和二参构造方法,如下:
public class BarGraphView extends View {public BarGraphView(Context context) { super(context); } public BarGraphView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); }}
其次,绘制的过程在于onDraw方法的回调,我们重写这个方法,来绘制条形图:
@Override protected void onDraw(Canvas canvas) { Log.i("bar","onDraw"); super.onDraw(canvas); Random random = new Random(); rectWidth = canvas.getWidth()/barCount; for (int i = 0;i<barCount;i++){ //生成0-99的随机数,作为高度的百分比,得出一个随机高度 int currentHeight = (random.nextInt(10))*canvas.getHeight()/100; Log.i("bar","currentHeight:"+currentHeight); canvas.drawRect(rectWidth*i+offset,currentHeight,rectWidth*(i+1),getHeight(),mPaint); } postInvalidateDelayed(speed); }
上文代码中,我们先通过canvas.getWidth()/barCount;计算出每个条形的宽度,而高度则是随机取出总高度的百分比。
最后调用canvas.drawRect(rectWidth*i+offset,currentHeight,rectWidth*(i+1),getHeight(),mPaint);
Canvas代表一个画板,他可以画出很图案,比如条形图其实就是一个个矩形组成的,那么我们利用drawRect可以画出一个矩形来作为一个条形。
在drawRect方法中五个参数分别代表着:left、top,right、bottom,画笔。
其实也就是左上顶点坐标(left,top)和一个右下顶点的坐标来确定一个矩形(right,bottom)
我们用rectWidth*i+offset,来定义矩形的left,用随机数来定义矩形的top,用计算出的宽度来定义矩形的right,用总体高度来定义矩形的bottm。
而第五个参数Paint,代表着一个画笔,有画板了,也知道要画什么,但也得有个笔来画才能展现出来是吧,所以我们增加一个init方法来定义一个Paint变量,让构造方法调用这个init方法:
private void init(){ mPaint = new Paint(); mPaint.setColor(getResources().getColor(R.color.colorAccent)); mPaint.setStyle(Paint.Style.FILL); }
这里为了简单,我们只是把画笔定义为默认的colorAccent颜色。
在onDraw方法的最后调用postInvalidateDelayed(speed);来刷新,模拟一个条形图动态变化的效果,调用postInvalidateDelayed时View会再回调onDraw方法。
然后,我们提供几个设置的方法:
public void setBarCount(int barCount) { this.barCount = barCount; } public void setOffset(int offset) { this.offset = offset; } public void setSpeed(int speed) { this.speed = speed; }
好了,一个简易的条形图就已经初具规模了。通常我们为了更为直观的看到一个效果,会把条形图的条形设置成一个颜色渐变的效果,怎么做呢?
可以重写View的onSizeChanged方法,在该方法中,我们利用LinearGradient这个颜色渐变的类来装入画笔:
@Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); LinearGradient linearGradient = new LinearGradient(0,0,rectWidth,getHeight(),Color.RED,Color.GREEN, Shader.TileMode.CLAMP); mPaint.setShader(linearGradient); }
代码很简单,我们只是创建一个LinearGradient对象,对象的前面四个参数代表着两个坐标,也就是从(x1,y1)到(x2,y2)的渐变过程,我们让起点坐标都定位0,然后让终点坐标设置为条形图的最高点。第五个参数表示起始颜色,第六个颜色表示终止颜色,我们分别用红和绿,来表示一个绿到红的渐变过程。最后一个参数Shader.TileMode.CLAMP表示如果着色器超出原始边界范围,会复制边缘颜色。最后我们把创建出来的LinearGradient对象装入画笔,就实现一个条形图的渐变效果了。
到这里,我们就已经完成了一个简易条形图的制作了,另外一个不得不说的点是,由于我们是继承自View,默认View的warp_content模式是填充父布局,也就是跟match_content一样的效果了,那么我们可以来设置一个值,使得如果定义属性为warp_content的话,则有一个默认的值。
我们重写onMeasure方法,而其实onMeasure方法中,默认就是调用setMeasuredDimension方法,所以我们可以直接把自定义好的宽高值传递给setMeasuredDimension。
我们定义一个宽度的测量:
private int measureWidth(int widthMeasureSpec){ int width; int spacMode = MeasureSpec.getMode(widthMeasureSpec); int size = MeasureSpec.getSize(widthMeasureSpec); if(spacMode==MeasureSpec.EXACTLY){ width = size; }else { width = 300; if(spacMode==MeasureSpec.AT_MOST){ width = Math.min(width,size); } } Log.i("bar","width:"+width); return width; }
在Android中,采用了一个int数值来代表一个测量值,用高二位来代表测量的模式,其余位数代表测量的数值。利用MeasureSpec的getMode和getSize方法我们很容易得到这两个数值。
当我们定义宽或高是match_content或者给定了一个确切的数值的话,则模式就是MeasureSpec.EXACTLY,否则就是MeasureSpec.AT_MOST。其实还有个MeasureSpec.UNSPECIFIED,表示空间不受限制,一般View里面不用到这个属性。如果我们设置为warp_content的话则模式就是MeasureSpec.AT_MOST,我们进行判断,取出一个最小值作为默认包裹的大小。
另外再定义一个高度的测量,写法几乎一致:
private int measureHeight(int heightMeasureSpec){ int height; int spacMode = MeasureSpec.getMode(heightMeasureSpec); int size = MeasureSpec.getSize(heightMeasureSpec); if(spacMode==MeasureSpec.EXACTLY){ height = size; }else { height = 300; if(spacMode==MeasureSpec.AT_MOST){ height = Math.min(height,size); } } Log.i("bar","height:"+height); return height; }
最后我们在onMeasure方法中,这样子写:
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec)); }
好了,我们来测试一下,创建一个Activity:
public class BarGraphActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_bar_graph); BarGraphView view = (BarGraphView) findViewById(R.id.bargraph); view.setOffset(10); view.setSpeed(300);//设置间隔刷新速度 view.setBarCount(20);//设置条形图的数量 } }
在布局里面定义:
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="15dp" tools:context="com.example.qyz.BarGraphActivity"><com.example.qyz.BarGraphView android:id="@+id/bargraph" android:layout_width="match_parent" android:layout_height="wrap_content" /></LinearLayout>
然后运行,截图如下:
这样子我们的一个简易条形图就制作完毕了。
最后贴出BarGraphView的代码:
public class BarGraphView extends View { private int barCount =30;//条形的数量 private int rectWidth = 15;//条形的宽度 private int offset = 10; private int speed = 300; Paint mPaint; public BarGraphView(Context context) { super(context); init(); } public BarGraphView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); init(); } private void init(){ mPaint = new Paint(); mPaint.setColor(getResources().getColor(R.color.colorAccent)); mPaint.setStyle(Paint.Style.FILL); } public void setBarCount(int barCount) { this.barCount = barCount; } public void setOffset(int offset) { this.offset = offset; } public void setSpeed(int speed) { this.speed = speed; } @Override protected void onDraw(Canvas canvas) { Log.i("bar","onDraw"); super.onDraw(canvas); Random random = new Random(); rectWidth = canvas.getWidth()/barCount; for (int i = 0;i<barCount;i++){ //生成0-99的随机数,作为高度的百分比,得出一个随机高度 int currentHeight = (random.nextInt(100))*canvas.getHeight()/100; Log.i("bar","currentHeight:"+currentHeight); canvas.drawRect(rectWidth*i+offset,currentHeight,rectWidth*(i+1),getHeight(),mPaint); } postInvalidateDelayed(speed); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec)); } private int measureWidth(int widthMeasureSpec){ int width; int spacMode = MeasureSpec.getMode(widthMeasureSpec); int size = MeasureSpec.getSize(widthMeasureSpec); if(spacMode==MeasureSpec.EXACTLY){ width = size; }else { width = 300; if(spacMode==MeasureSpec.AT_MOST){ width = Math.min(width,size); } } Log.i("bar","width:"+width); return width; } private int measureHeight(int heightMeasureSpec){ int height; int spacMode = MeasureSpec.getMode(heightMeasureSpec); int size = MeasureSpec.getSize(heightMeasureSpec); if(spacMode==MeasureSpec.EXACTLY){ height = size; }else { height = 300; if(spacMode==MeasureSpec.AT_MOST){ height = Math.min(height,size); } } Log.i("bar","height:"+height); return height; } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); LinearGradient linearGradient = new LinearGradient(0,0,rectWidth,getHeight(),Color.RED,Color.GREEN, Shader.TileMode.CLAMP); mPaint.setShader(linearGradient); }}
为了一个真实效果,我们来接入实际MP3的波形音频,让条形图显示MP3的波形数值,
怎么得到mp3的波形数值呢?可以利用Visualizer类来进行采集,这部分代码我直接贴出来,在相关代码处已经做了注释:
public class BarGraphActivity extends AppCompatActivity { // 定义播放声音的MediaPlayer private MediaPlayer mPlayer; Visualizer mVisualizer; WaveView waveView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_bar_graph); waveView = (WaveView) findViewById(R.id.bargraph); waveView.setOffset(10); mPlayer = MediaPlayer.create(this, R.raw.demo); setupVisualizer(); // 开发播放音乐 mPlayer.start(); } /** * 初始化频谱 */ private void setupVisualizer() { // 以MediaPlayer的AudioSessionId创建Visualizer // 显示该MediaPlayer播放的MP3音频数据 mVisualizer = new Visualizer(mPlayer.getAudioSessionId()); //设置数据采样值,一般为2的指数倍,如64,128,256,512,1024。 mVisualizer.setCaptureSize(Visualizer.getCaptureSizeRange()[0]); /** * listener,监听采样过程 *rate, 采样的周期,即隔多久采样一次 *iswave,波形信号 *isfft,是FFT信号 */ mVisualizer.setDataCaptureListener( new Visualizer.OnDataCaptureListener() { //采集快速傅里叶变换有关的数据 @Override public void onFftDataCapture(Visualizer visualizer, byte[] fft, int samplingRate){ } //采集波形数据 @Override public void onWaveFormDataCapture(Visualizer visualizer, byte[] waveform, int samplingRate) { waveView.update(waveform); Log.i("bar","onWaveFormDataCapture length:"+waveform.length); } }, Visualizer.getMaxCaptureRate() / 5, true, false); //必须设置为true后,采集工作才会开始 mVisualizer.setEnabled(true); }}
因为我们在上文的数值都是写死成随机的,我们复制BarGraphView重命名为:WaveView,把onDraw方法的代码改为如下:
@Override protected void onDraw(Canvas canvas) { Log.i("bar","onDraw"); super.onDraw(canvas); if(data==null)return; rectWidth = canvas.getWidth()*3/data.length; for (int i = 0,j=0;j<data.length;i++,j+=3){ int currentHeight = (int) (getHeight()*((data[i]+128)/256.0)); canvas.drawRect(rectWidth*i+offset,currentHeight,rectWidth*(i+1),getHeight(),mPaint); } }
由于数据比较多,我们把采集到的byte数组数据,128个采集值每3个显示一个出来。
最后,还要申请一下权限:
- [Android]自定义绘制一个简易的音频条形图,附上对MP3音频波形数据的采集与展现
- android绘制播放音频的波形图
- AS3实现的音频波形与条形EQ显示
- android音频波形图绘制
- android音频波形图绘制
- 自定义动态的音频条形图
- PCM音频波形的绘制以及注意事项
- 使用Flash as3实现音频波形与条形EQ显示的方法
- Android自定义View之音频条形图
- Android自定义View之音频条形图
- Android的音频采集
- Android MP3录制,波形显示,音频权限兼容与播放
- Android MP3录制,波形显示,音频权限兼容与播放
- java绘制音频波形图
- 群英传笔记:自定义view一个音频跳动图的绘制
- 获取音频的波形
- [Android] Android的音频采集
- 基于matlab的音频波形实时采集显示 v0.1
- JAVA substring的用法
- JAVA并发编程(四)——线程状态与中断
- 【小程序】调用wx.request接口时需要注意的几个问题
- Python脚本进行游戏专区拉新方面的计算案例
- ThreadLocal
- [Android]自定义绘制一个简易的音频条形图,附上对MP3音频波形数据的采集与展现
- Tensorflow卷积神经网络
- HashMap&ConcurrentHashMap的比较
- Oracle使用游标查询所有数据表备注
- Android-Root静默安装
- Photoshop详细教程二之文件/编辑/图像基本功能介绍
- 又来了,麦枫版通达OA/office anywhere2017.10.8.171010无限用户破解可定制名称
- 与时俱进版前端资源教程
- spring 实现邮件发送 JavaMailSender