声浪效果是基于第三方实现的。
https://github.com/xfans/VoiceWaveView
将三方的 Kotlin 代码转 java 使用(按照他的readme 进行依赖,好像少了点东西,至少本项目跑不起来)
声浪效果在android 8 以上都是比较好的,不会出现断点的情况。但是在 android 8下,就会出现如下图所示的断点情况。
主类
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.os.Handler;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;import androidx.annotation.Nullable;import java.util.ArrayList;
import java.util.List;/*** 音浪线*/
public class VoiceWaveView extends View {private static final String TAG = "VoiceWaveView";private List<Integer> bodyWaveList = new ArrayList<>();private List<Integer> headerWaveList = new ArrayList<>();private List<Integer> footerWaveList = new ArrayList<>();private List<Integer> waveList = new ArrayList<>();private float lineSpace = 10f;private float lineWidth = 20f;private long duration = 200;private int lineColor = Color.BLUE;private Paint paintLine;private Paint paintPathLine;private ValueAnimator valueAnimator = ValueAnimator.ofFloat(0f, 1f);private float valueAnimatorOffset = 1f;private Handler valHandler = new Handler();private Path linePath = new Path();private boolean isStart = false;private WaveMode waveMode = WaveMode.UP_DOWN;private LineType lineType = LineType.BAR_CHART;private int showGravity = Gravity.LEFT | Gravity.BOTTOM;private Runnable runnable;public VoiceWaveView(Context context) {this(context, null);}public VoiceWaveView(Context context, @Nullable AttributeSet attrs) {this(context, attrs, 0);}public VoiceWaveView(Context context, @Nullable AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);init(attrs);}private void init(@Nullable AttributeSet attrs) {if (attrs != null) {// Read and initialize attributes here}paintLine = new Paint();paintLine.setAntiAlias(true);paintLine.setStrokeCap(Paint.Cap.ROUND);paintPathLine = new Paint();paintPathLine.setAntiAlias(true);paintPathLine.setStyle(Paint.Style.STROKE);valueAnimator.addUpdateListener(animation -> {valueAnimatorOffset = (float) animation.getAnimatedValue();invalidate();});}public void setLineSpace(float lineSpace) {this.lineSpace = lineSpace;}public void setLineWidth(float lineWidth) {this.lineWidth = lineWidth;}public void setDuration(long duration) {this.duration = duration;}public void setLineColor(int lineColor) {this.lineColor = lineColor;}public void setWaveMode(WaveMode waveMode) {this.waveMode = waveMode;}public void setLineType(LineType lineType) {this.lineType = lineType;}public void setShowGravity(int showGravity) {this.showGravity = showGravity;}public VoiceWaveView addBody(int soundLevel) {checkNum(soundLevel);bodyWaveList.add(soundLevel);return this;}public VoiceWaveView initBody(int length, int soundLevel) {bodyWaveList.clear();for (int i = 0; i < length; i++) {addBody(soundLevel);}return this;}// TODO: 2023/11/1 中间弹的的逻辑public VoiceWaveView refreshBody(int soundLevel) {// 添加 soundLevel 到头部bodyWaveList.add(0, soundLevel);// 递减相邻元素的值for (int i = 1; i < bodyWaveList.size() - 1; i++) {int previousValue = bodyWaveList.get(i - 1);int currentValue = bodyWaveList.get(i);int nextValue = bodyWaveList.get(i + 1);int updatedValue = Math.max(currentValue - 1, Math.max(previousValue, nextValue) - 2);bodyWaveList.set(i, updatedValue);}return this;}/*** 刷新最后一个** @param soundLevel*/public void updateBody(int soundLevel) {bodyWaveList.remove(bodyWaveList.size() - 1);addBody(soundLevel);}public VoiceWaveView addHeader(int soundLevel) {checkNum(soundLevel);headerWaveList.add(soundLevel);return this;}public VoiceWaveView addFooter(int soundLevel) {checkNum(soundLevel);footerWaveList.add(soundLevel);return this;}private void checkNum(int soundLevel) {if (soundLevel < 0 || soundLevel > 100) {throw new IllegalArgumentException("num must be between 0 and 100");}}public void start() {if (isStart) {return;}L.i(TAG, "start ");isStart = true;if (waveMode == WaveMode.UP_DOWN) {valueAnimator.setDuration(duration);valueAnimator.setRepeatMode(ValueAnimator.REVERSE);valueAnimator.setRepeatCount(ValueAnimator.INFINITE);valueAnimator.start();} else if (waveMode == WaveMode.LEFT_RIGHT) {runnable = new Runnable() {@Overridepublic void run() {//日志类,自己构建即可L.i(TAG, bodyWaveList.toString());Integer last = bodyWaveList.remove(bodyWaveList.size() - 1);bodyWaveList.add(0, last);invalidate();valHandler.postDelayed(this, duration);}};valHandler.post(runnable);}}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);L.i(TAG, "onDraw ");waveList.clear();waveList.addAll(headerWaveList);waveList.addAll(bodyWaveList);waveList.addAll(footerWaveList);linePath.reset();paintPathLine.setStrokeWidth(lineWidth);paintPathLine.setColor(lineColor);paintLine.setStrokeWidth(lineWidth);paintLine.setColor(lineColor);float measuredWidth = getMeasuredWidth();float measuredHeight = getMeasuredHeight();float startX = 0f;float startY = 0f;float endX = 0f;float endY = 0f;for (int i = 0; i < waveList.size(); i++) {float offset = 1f;if (i >= headerWaveList.size() && i < (waveList.size() - footerWaveList.size())) {offset = valueAnimatorOffset;}float lineHeight = (waveList.get(i) / 100.0f) * measuredHeight * offset;int absoluteGravity = Gravity.getAbsoluteGravity(showGravity, getLayoutDirection());switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {case Gravity.CENTER_HORIZONTAL:int lineSize = waveList.size();float allLineWidth = lineSize * (lineSpace + lineWidth);if (allLineWidth < measuredWidth) {startX = (i * (lineSpace + lineWidth) + lineWidth / 2) + ((measuredWidth - allLineWidth) / 2);} else {startX = i * (lineSpace + lineWidth) + lineWidth / 2;}endX = startX;break;case Gravity.RIGHT:lineSize = waveList.size();allLineWidth = lineSize * (lineSpace + lineWidth);if (allLineWidth < measuredWidth) {startX = (i * (lineSpace + lineWidth) + lineWidth / 2) + (measuredWidth - allLineWidth);} else {startX = i * (lineSpace + lineWidth) + lineWidth / 2;}endX = startX;break;case Gravity.LEFT:startX = i * (lineSpace + lineWidth) + lineWidth / 2;endX = startX;break;}switch (showGravity & Gravity.VERTICAL_GRAVITY_MASK) {case Gravity.TOP:startY = 0f;endY = lineHeight;break;case Gravity.CENTER_VERTICAL:startY = (measuredHeight / 2 - lineHeight / 2);endY = (measuredHeight / 2 + lineHeight / 2);break;case Gravity.BOTTOM:startY = (measuredHeight - lineHeight);endY = measuredHeight;break;}if (lineType == LineType.BAR_CHART) {canvas.drawLine(startX, startY, endX, endY, paintLine);}if (lineType == LineType.LINE_GRAPH) {if (i == 0) {linePath.moveTo(startX, startY);float pathEndX = endX + (lineWidth / 2) + (lineSpace / 2);linePath.lineTo(pathEndX, endY);} else {linePath.lineTo(startX, startY);float pathEndX = endX + (lineWidth / 2) + (lineSpace / 2);linePath.lineTo(pathEndX, endY);}}}if (lineType == LineType.LINE_GRAPH) {canvas.drawPath(linePath, paintPathLine);}}public void stop() {L.i(TAG, "stop ");isStart = false;if (runnable != null) {valHandler.removeCallbacks(runnable);}valueAnimator.cancel();}@Overrideprotected Parcelable onSaveInstanceState() {// TODO onSaveInstanceStatereturn super.onSaveInstanceState();}@Overrideprotected void onRestoreInstanceState(Parcelable state) {// TODO onRestoreInstanceStatesuper.onRestoreInstanceState(state);}
}
相关枚举类
public enum LineType {LINE_GRAPH(0),BAR_CHART(1);private int value;private LineType(int value) {this.value = value;}public int value() {return this.value;}
}
public enum WaveMode {UP_DOWN,LEFT_RIGHT
}