先看一下效果图(这个效果图是以前的,后面更新了代码,画面更加流程,完全没有卡顿):
一开始我看了这个效果图,是一脸的懵逼,完全没有思路。按照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);
//从数据源中找出需要绘制的矩形
List<SampleLineModel> drawRectList = 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格式,支持暂停录制,删除上一段录音的功能。
源码里面还有一个播放声音的波形图,原理和上面类似,效果如下: