最近写了一个关于音频分析的app,实时的音频频率信息获取到了,怎么在一个view中显示折线图,按照时间去添加频率数据,当数组中个数大于屏幕宽度的时候,左边消失一个点,右边添加一个点,像流水线一样将数据显示出来。没有插件可以使用,只好自己写一个自定义的view去显示数据。通过定时刷新的方式去显示。
声音的频率会实时获取到,这给了数据的来源,下面具体怎么实现这个折线图。关于声音频率获取这儿就不具体说了。
首先,你得画出这个自定义的view,并在数据传入的时候对界面重绘。
/**
* 自定义的画折线图的view
* @author wangjian
*
*/
public class MyChartView extends View{
private AppContext mAppContext;//主要用到里面的dptopx()方法,将dp转为px值
private int chartH;//组件存放位置高度
private int chartW;//宽度
private int left;//边框离左边的宽度
private int top;//边框离上边的宽度
private double scale;//中线代表的频率大小与中线到底线长度的比例
private int maxDataSize;//x轴最大存放数据个数
private int xWidth = 2;//x轴每个数据存放的宽度
private List<Point> plist = new ArrayList<Point>();//点集
private VoiceUtil mVoiceUtil;//声音实体类,有主音,唱音,频率等属性
private int frequency;//存放声音频率
//构造函数需要传过来空间的宽高
public MyChartView(Context context,int chartH,int chartW,VoiceUtil mVoiceUtil) {
super(context);
setWillNotDraw(false);//保证重绘不会被制止
mAppContext = AppContext.getInstance();
this.chartH = chartH;
this.chartW = chartW;
this.mVoiceUtil = mVoiceUtil;
this.frequency = (int) mVoiceUtil.getFrequency();
left = mAppContext.dp2px(5);
top = mAppContext.dp2px(5);
scale = mVoiceUtil.getFrequency()/(chartH/2-top);//用来根据频率获取点的位置
maxDataSize = (chartW-2*left)/xWidth-1;//剪掉边框的宽度
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//画边框
drawBorder(canvas);
//准备画折线的数据画折线
preparePoint(canvas);
}
//画边框
private void drawBorder(Canvas canvas) {
//画外框
Paint paint = new Paint();
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(mAppContext.dp2px(2));
Rect chartRec = new Rect(left, top,chartW-left, chartH-top);
canvas.drawRect(chartRec, paint);
//画中心线
Paint midpaint = new Paint();
midpaint.setColor(Color.BLACK);
midpaint.setStyle(Paint.Style.STROKE);
midpaint.setStrokeWidth(mAppContext.dp2px(1));
canvas.drawLine(left, chartH/2, chartW-left, chartH/2, midpaint);
//画声音提示信息
Path textPath = new Path();
textPath.moveTo(left+mAppContext.dp2px(10), mAppContext.dp2px(20));
textPath.lineTo(chartW-left, mAppContext.dp2px(20));
Paint textpaint = new Paint();
textpaint.setStyle(Paint.Style.FILL);
textpaint.setColor(Color.WHITE);
textpaint.setTextSize(32);
textpaint.setAntiAlias(true);
String text = "主音:"+mVoiceUtil.getKeynote()+",唱音:"+mVoiceUtil.getSingnote()+",八度:"+mVoiceUtil.getOctave();
canvas.drawTextOnPath(text, textPath, 0, 0, textpaint);
//画频率值
Path frePath = new Path();
frePath.moveTo(left+mAppContext.dp2px(10), chartH/2-mAppContext.dp2px(5));
frePath.lineTo(chartW-left, chartH/2-mAppContext.dp2px(5));
String fretext = "频率:"+(int)mVoiceUtil.getFrequency();
canvas.drawTextOnPath(fretext, frePath, 0, 0, textpaint);
}
//准备画折线的数据并画折线
private void preparePoint(Canvas canvas) {
//准备数据
//判断纵坐标是不是超出界限,让频率的点处在边框内
int pointY = 0;
if(frequency/scale>=(chartH-top)){
pointY = top;
}else if(frequency/scale<=top){
pointY = chartH-top;
}else{
pointY = chartH-top-(int) (frequency/scale);
}
Point p = new Point(chartW-left , pointY );
//判断点集有没有超出界限
if(plist.size()>(maxDataSize+1)){
plist.remove(0);
//循环处理点
for(int i=0;i<maxDataSize;i++){
//将点的横坐标向左平移一格
plist.get(i).x -= xWidth;
}
plist.add(p);//将新的点添加进去
}else{
for(int i = 0; i<plist.size()-1; i++){
plist.get(i).x -= xWidth;
}
plist.add(p);
}
//准备画笔
Paint paint = new Paint();
paint.setColor(Color.WHITE);
paint.setStrokeWidth(3);
paint.setAntiAlias(true);
//画折线图
if(plist.size() >= 2){
for(int i = 0; i<plist.size()-1; i++){
canvas.drawLine(plist.get(i).x, plist.get(i).y, plist.get(i+1).x, plist.get(i+1).y, paint);
}
}
}
//更新界面显示
public void updateLine(VoiceUtil voiceUtil,int frequencys){
frequency = frequencys;
mVoiceUtil = voiceUtil;
// invalidate();
}
}
在主界面中使用Handler和Runnable的方式去定时将数据传给view并去重绘
mHandler = new Handler();
mHandler.post(new TimerProcess());
private class TimerProcess implements Runnable {
public void run() {
if(started){//当开始录音
if(isDrawNew){//是否改变了主音等一些参数
drawNewChaertView();//销毁并重绘
isDrawNew = false;
}else{
mVoiceUtil = mDbmanager.findFrequencyByKeynoteAndSingnote(keynote, singnote, octave);
//更新数据
myChartView.updateLine(mVoiceUtil, frequencys);
//重绘
myChartView.invalidate();
}
}
mHandler.postDelayed(this, 1);
}
}
//重绘chartview
private void drawNewChaertView() {
ll_mylinechart.removeAllViews();
mVoiceUtil = mDbmanager.findFrequencyByKeynoteAndSingnote(keynote, singnote, octave);
myChartView = new MyChartView(this, chartH, chartW,mVoiceUtil);
myChartView.invalidate();
ll_mylinechart.addView(myChartView);
}
相关bug
在调用myChartView.invalidate();去更新数据的时候却发现了一个bug,在程序切换到主界面再进去时,虽然一直在定时执行,但不会调用myChartView中的ondraw()方法。佷蛋疼。。就剩最后一步。。查找资料,也找不到为什么。后来有一位网友说需要将添加view的外面的linearlayout换了,我将linearlayout换成RelativeLayout结果好了。可以断点续传了。。。
<RelativeLayout
android:id="@+id/ll_mylinechart"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="4"
android:background="#03a9f4">
</RelativeLayout>