先看一下效果图(这个效果图是以前的,后面更新了代码,画面更加流程,完全没有卡顿):
一开始我看了这个效果图,是一脸的懵逼,完全没有思路。按照sdk提供的控件肯定是做不出来这种效果图,只能自己画,通过继承View,绘制UI。
先说一下整体思路
- 首先这个效果图,需要拆分成两部分,一部分是上面表示录音时间的刻度尺,还有一部分是下面的录音的采样波形图
- 整个画面的移动,这部分也是最难的一部分。我通过录音时间,和采样频率,计算出刻度尺上滑动的距离,然后通过scrollTo方法移动整个画布,来达到效果的
- 绘制的过程中,只能绘制当前屏幕的内容,绘制整个刻度尺范围内的采样图的肯定会卡顿
整体的思路基本是这样子。下面讲解一下具体的实现过程。
- 先定义一个
BaseAudioRecord
继承自View
,该类主要控制滑动,初始化自定义参数等 - 创建一个
AudioRecord
类继承BaseAudioRecord
, 该类主要负责画面绘制工作
AudioRecord绘制
1.先讲解一下AudioRecord绘制的第一步,绘制上面体现录音时间的刻度尺的绘制。重写onDraw方法,通过canvas来绘制。
代码如下:
private void drawScale(Canvas canvas) { int firstPoint = (getScrollX() - mDrawOffset) / scaleIntervalLength; int lastPoint = (getScrollX() + canvas.getWidth() + mDrawOffset) / (scaleIntervalLength); for (int i = firstPoint; i < lastPoint; i++) { float locationX = i * scaleIntervalLength; if (i % intervalCount == 0) { canvas.drawLine(locationX, ruleHorizontalLineHeight - bigScaleStrokeLength, locationX, ruleHorizontalLineHeight, bigScalePaint); if (showRuleText) { int index = i / intervalCount; canvas.drawText(formatTime(index), locationX + bigScaleStrokeWidth + 5, ruleHorizontalLineHeight - bigScaleStrokeLength + ruleTextSize / 1.5f, ruleTextPaint); } } else { canvas.drawLine(locationX, ruleHorizontalLineHeight - smallScaleStrokeLength, locationX, ruleHorizontalLineHeight, smallScalePaint); } } //画轮廓线 canvas.drawLine(getScrollX(), ruleHorizontalLineHeight, getScrollX() + canvas.getWidth(), ruleHorizontalLineHeight, ruleHorizontalLinePaint); }复制代码
上面代码说明如下:
getScrollX()
表示画布移动的距离,往右侧移动是正数,往左侧移动是负数,值表示画布在屏幕内移动的平素点的个数firstPoint
表示绘制的第一个点,减去一个 mDrawOffset 表示往左侧屏幕多绘制了一个缓冲区域lastPoint
表示绘制的最后一个点,加上一个 mDrawOffset表示往右侧屏幕多绘制了一个缓冲区域scaleIntervalLength
表示刻度间隔intervalCount
表示两个大刻度之间小刻度的间隔数
2.绘制中间的采样波形图
代码如下:
private void drawLine(Canvas canvas) { int middleLineY = canvas.getHeight() / 2; canvas.drawLine(getScrollX(), middleLineY, getScrollX() + canvas.getWidth(), middleLineY, middleHorizontalLinePaint); //从数据源中找出需要绘制的矩形 ListdrawRectList = getDrawSampleLineList(canvas); if (drawRectList == null || drawRectList.size() == 0) { return; } //绘制采样点 for (SampleLineModel sampleLineModel : drawRectList) { canvas.drawLine(sampleLineModel.startX, sampleLineModel.startY, sampleLineModel.stopX, sampleLineModel.stopY, linePaint); int invertedStartY = canvas.getHeight() / 2; float invertedStopY = invertedStartY + sampleLineModel.stopY - sampleLineModel.startY; canvas.drawLine(sampleLineModel.startX, invertedStartY, sampleLineModel.stopX, invertedStopY, lineInvertedPaint); } }复制代码
- 矩形的绘制,是用drawLine来表示的,矩形宽用线宽表示即可。从中心的水平横线上下各绘制了同等长度的线,用来表示采样的波形图
- 同样采样波形的绘制,也只绘制屏幕内的采样波形
AudioRecord
两块区域绘制的核心代码基本就是这样子了
BaseAudioRecord 控制移动
1.重写onTouchEvent()方法,根据手势移动,来移动画布
核心代码如下:
@Override public boolean onTouchEvent(MotionEvent event) { float currentX = event.getX(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mLastX = currentX; break; case MotionEvent.ACTION_MOVE: float moveX = mLastX - currentX; mLastX = currentX; scrollBy((int) (moveX), 0); break; } return true; }复制代码
- return true; 将onTouchEvent触摸事件消费掉,一遍能够执行到ACTION_MOVE
- 通过scrollBy来移动画布,距离通过,手指滑动的距离获取
float moveX = mLastX - currentX;
2.通过ObjectAnimator.ofFloat()方法来移动画布,开启动画
核心代码如下: 动画开始:
float startX = getScrollX(); //小于半屏的时候,要重新计算偏移量,因为有个左滑的动作 float endX = maxLength - getMeasuredWidth() / 2; float dx = Math.abs(endX - startX); final double duration = 1000 * dx / (recordSamplingFrequency * (lineWidth + rectGap)); animator = ObjectAnimator.ofFloat(this, "translateX", startX, endX); animator.setInterpolator(new LinearInterpolator()); animator.setDuration((long) Math.floor(duration)); animator.removeAllListeners(); animator.start();复制代码
移动画布:
public void setTranslateX(float translateX) { this.translateX = translateX; scrollTo((int) translateX, 0); if (isStartRecordTranslateCanvas) { translateVerticalLineX = getScrollX() + getMeasuredWidth() / 2 + rectGap; } onTick(getScrollX() + getMeasuredWidth() / 2); }复制代码
根据画布移动的距离,算出时间,再根据定义好的采样频率,回调采样函数,生成波形图:
private void onTick(float translateX) { if (isRecording) { long duration = (long) (translateX * recordTimeInMillis / maxLength); if (duration > getSampleCount() * recordDelayMillis) { makeSampleLine(recordCallBack.getSamplePercent()); } } }复制代码
这个自定义的录音采集声音波形的UI基本上就完成了,有兴趣的小伙伴可以去查看源码,有什么不对的地方,欢迎指正交流。
源码中,有一个播放器的类AudioRecordMp3.java
,采用了AudioRecord录制的音频,使用了Lame将AudioRecord录制的pcm格式的音频实时转码成MP3格式,支持暂停录制,删除上一段录音的功能。
源码里面还有一个播放声音的波形图,原理和上面类似,效果如下: