【Android】Sound wave UI effect with detailed code attached

The sound effect is implemented based on a third party.
https://github.com/xfans/VoiceWaveView
Convert the third-party Kotlin code to Java (depend on it according to its readme, it seems that something is missing , at least this project cannot run)

The sound effect is relatively good on Android 8 and above, and there will be no breakpoints. But under Android 8, a breakpoint situation will appear as shown in the figure below.
Insert image description here

main class

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() {
    
    
                @Override
                public 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);
        }
    }

    @Override
    protected 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();
    }

    @Override
    protected Parcelable onSaveInstanceState() {
    
    
        // TODO onSaveInstanceState
        return super.onSaveInstanceState();
    }

    @Override
    protected void onRestoreInstanceState(Parcelable state) {
    
    
        // TODO onRestoreInstanceState
        super.onRestoreInstanceState(state);
    }
}

Related enumeration classes

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
}

Guess you like

Origin blog.csdn.net/weixin_44002043/article/details/134556699