在项目中需要到数据统计的地方,往往都需要到一些图的展示,比如曲线图、折线图、饼状图、圆形图、条形图等等。在本文中我们来实现一个简易的条形图的绘制。

  首先,我们创建一个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方法的回调,我们重写这个方法,来绘制条形图:

 @Overrideprotected 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这个颜色渐变的类来装入画笔:

@Overrideprotected 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方法中,这样子写:

  @Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));}

好了,我们来测试一下,创建一个Activity:

public class BarGraphActivity extends AppCompatActivity {@Overrideprotected 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;}@Overrideprotected 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);}@Overrideprotected 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;}@Overrideprotected 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 {// 定义播放声音的MediaPlayerprivate MediaPlayer mPlayer;Visualizer mVisualizer;WaveView waveView;@Overrideprotected 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(){//采集快速傅里叶变换有关的数据@Overridepublic void onFftDataCapture(Visualizer visualizer,byte[] fft, int samplingRate){}//采集波形数据@Overridepublic 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方法的代码改为如下:

    @Overrideprotected 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个显示一个出来。

  最后,还要申请一下权限:

<uses-permission android:name="android.permission.RECORD_AUDIO"></uses-permission><uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"></uses-permission>

[Android]自定义绘制一个简易的音频条形图,附上对MP3音频波形数据的采集与展现相关推荐

  1. Android 中编写一个简易购物车,商品包括商品名称,单价,数量,可以对商品进行增删改查功能。(ArrayList,SQLite)

    Android 中编写一个简易购物车,商品包括商品名称,单价,数量,可以对商品进行增删改查功能.(ArrayList,SQLite) 布局(activity_main.xml): <?xml v ...

  2. 10分钟手把手教你用Android手撸一个简易的个人记账App

    用Android手撸一个简易的个人记账系统 ⛱️序言

  3. 画正方形圆角android,Android 快速绘制一个圆角矩形的实例

    android 开发过程中,经常需要绘制一些简单的颜色图形.下面贴上一段最简单的图形. @H_301_4@圆角矩形: @H_301_4@res\drawable\shape_small_label.x ...

  4. 【Android】制作一个简易的画板

    前言 本文介绍使用Canvas.Paint.Path等原生API,实现一个简易的画板. 绘图原理 Paint Paint可以理解为我们现实中的画笔,在使用前我们需要对Paint进行初始化,比如颜色.画 ...

  5. Android studio设计一个简易微信界面

    一.设计要求及实现构想 1.设计一个简易微信界面框架,包含至少4个tab页面(我设计的4个分别为message.contact.find.config),要求能实现四个页面之间的点击切换. 2.首先分 ...

  6. android圆形的按钮样式,android – 如何绘制一个完美的圆形按钮?

    我想绘制一个完美的圆形按钮. 我尝试使用以下代码: android:shape="oval" > android:width="3dip" android ...

  7. android 六边形布局,Android自定义View——一个可定制的六边形阵列

    关于六边形的自定义View网上已经有很多了,但目前来看都是固化的UI,可定制性不高,所以我这里将六边形与坐标绑定,这样的话我们就可以随意组合六边形形成我们需要的一个图案. 基本思路也很简单,一句话-- ...

  8. QPainter绘制一个简易的饼图(含动画和tip显示)

    (文本只是一个饼图的雏形,还未进行封装,并且功能也很少,等有空我再进一步完善他.如果你需要的是一个现成的控件或完善的代码,可以在百度上找找其他的.) 先说下我的实现思路:首先,构建Slice结构体类型 ...

  9. java音频剪辑_Android实现mp3音频剪辑(带试听)

    [实例简介] 1.读取本地里所有的音频文件 2.使用MediaPlayer进行音频播放 3.对音频文件进行剪辑,截取想要的音频片段,可以试听 具体实现代码,请查看以下文章: https://blog. ...

最新文章

  1. clion 代码格式化 大括号换行对齐
  2. LINKs: Xamarin.Forms + Prism
  3. JMeter接口测试通过企业微信API记录(二)创建审批接口并测试
  4. 对象属性对话框只能放大不能缩小
  5. OO第三次博客总结作业
  6. 退出Activity(转)
  7. 2.1.5编码与调制(1)
  8. 任意点 曲线距离_中级数学11-曲线函数
  9. java反向链表列表写法,一个使用链表追加,反向和显示的java程序
  10. pythonlist详解_零基础入门Python3-列表list详解
  11. JAVA动态加载JAR包执行程序
  12. 【CS229】代价函数与梯度下降
  13. MyBatis的四种资源加载方式以及优先级
  14. python处理时间序列非平稳_用python做时间序列预测4:平稳/非平稳时间序列
  15. Programer四境界-摘自《代码大全》
  16. 运维审计平台之堡垒机和跳板机
  17. 对Orders订单表中的常见统计查询
  18. 现在的FM电台呀,什么玩艺儿
  19. 【C语言管理系统】 医院住院病人信息管理系统
  20. 加入域时“找不到网络路径”的解决办法

热门文章

  1. Altium Designer--如何制作过孔(Via)库
  2. 数学分析(2):自然数集的构建
  3. 用计算机的说法,关于计算机系统,下列哪几个说法是正确的?
  4. 各种手机处理器排行榜_手机处理器排行前十位,分别是什么?
  5. 2020-11-05 Win10自带虚拟机 Hyper-V使用方法
  6. 自我总结前端vue笔记
  7. Linux也可以这样美——Ubuntu18.04安装、配置、美化
  8. z变换公式表_小白学物理之狭义相对论(1)——洛伦兹变换
  9. Vs调试报错:不能将 “const char *“ 类型的值分配到 “char *“ 类型的实体
  10. 超猛tuntap虚拟网卡实现超猛UDP隧道