Android高仿iOS Messages声音播放波形效果
文接《Android低仿iOS Messages录音波形效果》。
上一次开发中,因为无法完美实现波形的收敛效果,因此只能算是一个低仿的版本。
声音波形效果对比之下,比较容易实现。
一、目标
实现声音播放的波形效果,为神马笔记增加录音及播放功能做准备。
二、功能分析
截图 | 描述 |
---|---|
停止时的波形显示为全白色。 | |
播放过程中,波形有暗变亮,以呈现进度。 |
三、实现效果
基本上实现了播放的波形效果。
四、实现过程
1. 创建波形
绘制波形的前提条件是有波形数据。
波形数据的来源有2种。
- 从音频文件读取数据,然后转换为波形数据
- 录音时进行采样保存
通过MediaRecorder
实现录音时,通过getMaxAmplitude()
获取最近最大的振幅,通过一段时间的连续调用,最终可以组成一个波形数据。
有了波形数据后,还需要根据控件大小转换为目标数据。
2. Waveform
从波形数据创建Waveform
,用来显示完整波形。
sample()
是Waveform
的核心方法,实现转换波形数据的功能。
/**
*
*/
private static class Waveform {
ArrayList<Wave> list;
ArrayList<Wave> recycler;
TapePlayView parent;
Waveform(TapePlayView parent) {
this.parent = parent;
this.list = new ArrayList<>(100);
this.recycler = new ArrayList<>(100);
for (int i = 0; i < 100; i++) {
recycler.add(new Wave());
}
}
void clear() {
this.recycler.addAll(list);
list.clear();
}
void sample() {
this.recycler.addAll(list);
this.list.clear();
int count = parent.getWidth() / parent.getWaveWidth();
if (count == 0) {
return;
}
TapeView.Sampler sampler = parent.sampler;
if (sampler == null || sampler.size() == 0) {
return;
}
int size = sampler.size();
if (count == size) { // equals
for (int i = 0; i < size; i++) {
int value = sampler.get(i);
this.addSquare(value);
}
} else if (count > size) { // more than
{
int length;
if (count >= 2 * size) { // more than twice
length = (count - 2 * size) / 2;
} else {
length = (count - size) / 2;
}
while (length > 0) {
this.add(0);
--length;
}
}
if (count >= 2 * size) {
this.addSquare(sampler.get(0) / 2);
this.addSquare(sampler.get(0));
for (int i = 1; i < size; i++) {
double a = sampler.get(i - 1);
a *= a;
double b = sampler.get(i);
b *= b;
a = (a + b) / 2;
this.add(a);
this.add(b);
}
} else {
for (int i = 0; i < size; i++) {
int value = sampler.get(i);
this.addSquare(value);
}
}
{
while (this.size() < count) {
this.add(0);
}
}
} else { // less than
long[] values = new long[count];
Arrays.fill(values, 0);
int[] numbers = new int[count];
Arrays.fill(numbers, 0);
for (int i = 0; i < size; i++) {
double value = sampler.get(i);
value *= value;
int index = i * count / size;
values[index] += value;
numbers[index] += 1;
}
for (int i = 0; i < count; i++) {
if (numbers[i] == 0) {
this.add(0);
} else {
this.add(values[i] / numbers[i]);
}
}
}
}
int size() {
return list.size();
}
void draw(Canvas canvas) {
for (Wave wave : list) {
wave.draw(canvas);
}
}
void addSquare(int value) {
this.add(value * value);
}
void add(double amplitudeSquare) {
int position = list.size();
Wave wave = fetch(position, amplitudeSquare);
list.add(wave);
}
Wave fetch(int position, double amplitudeSquare) {
Wave wave;
int index = recycler.size() - 1;
if (index < 0) {
wave = new Wave();
} else {
wave = recycler.remove(index);
}
wave.init(parent, position, amplitudeSquare);
return wave;
}
}
3. Wave
Wave
用来绘制单个的波形柱状图。
根据播放时间的进度显示不同的效果。
private static class Wave {
int wave;
int position;
TapePlayView parent;
Wave() {
}
void init(TapePlayView parent, int position, double amplitudeSquare) {
this.parent = parent;
this.position = position;
this.wave = parent.transform(amplitudeSquare);
}
void draw(Canvas canvas) {
int width = parent.getWaveWidth();
int height = parent.getWaveHeight(wave);
int x = position * width;
int y = (parent.getHeight() - height) / 2;
Drawable d;
if (parent.isRunning()) {
int progress = parent.getProgress();
if (x >= progress) { // dark
d = parent.darkDrawable;
d.setBounds(x, y, x + parent.sampleSolid, y + height);
d.draw(canvas);
} else if ((progress - x) >= parent.sampleSolid) { // bright
d = parent.drawable;
d.setBounds(x, y, x + parent.sampleSolid, y + height);
d.draw(canvas);
} else {
int diff = progress - x;
d = parent.darkDrawable;
d.setBounds(x, y, x + parent.sampleSolid, y + height);
d.draw(canvas);
d = parent.drawable;
d.setBounds(x, y, x + diff, y + height);
d.draw(canvas);
}
} else {
d = parent.drawable;
d.setBounds(x, y, x + parent.sampleSolid, y + height);
d.draw(canvas);
}
}
}
4. 播放声音
使用MediaPlayer
来播放声音,通过getDuration()
和getCurrentPosition()
获取总时长和当前进度,从而绘制出播放时的波形。
五、开发过程回顾
从转换波形数据开始,创建波形效果,然后绘制了静态的波形图。
之后结合播放进度,绘制不同状态的波形效果。
难点在于获取波形数据。
六、接下来
组合录音和播放,完整录音编辑器功能。
七、Finally
我应灭度一切众生。
灭度一切众生已。
而无有一众生实灭度者。