【Android】声浪 UI 效果并附上详细代码

news2024/9/20 13:27:21

声浪效果是基于第三方实现的。
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() {
                @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);
    }
}

相关枚举类

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
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1238391.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

prometheus热更新失败failed to reload config

一、问题描述 k8s部署的prometheus服务在请求热更新时报错: failed to reload config: one or more errors occurred while applying the new configuration (--config.file"/etc/prom/config/file/prometheus.yml")请求命令:curl -X POST http://monitor-cp-prom:…

echarts实现如下图功能代码

这里写自定义目录标题 const option {tooltip: {trigger: axis},legend: {left: "-1px",top:15px,type: "scroll",icon:rect,data: [{name:1, textStyle:{color: theme?"#E5EAF3":#303133,fontSize:14}}, {name: 2, textStyle:{color: theme…

【面试HOT300】滑动窗口篇

系列综述&#xff1a; &#x1f49e;目的&#xff1a;本系列是个人整理为了秋招面试的&#xff0c;整理期间苛求每个知识点&#xff0c;平衡理解简易度与深入程度。 &#x1f970;来源&#xff1a;材料主要源于【CodeTopHot300】进行的&#xff0c;每个知识点的修正和深入主要参…

焦炉加热系统简述

烟道吸力 焦炉负压烘炉分烟道的吸力会影响立火道温度&#xff0c;具体影响因素如下&#xff1a; 烟道吸力过大会导致热量被抽走&#xff0c;使立火道温度降低。烟道吸力不足会导致烟气在烘炉内停留时间过长&#xff0c;使热量无法充分利用&#xff0c;也会导致立火道温度降低…

监控摄像头连接NAS,实现监控管理一体化

嗯&#xff1f;你问干嘛要把摄像头连到NAS&#xff1f; 小马给家里安了个监控摄像头 本意是想家里有啥事也能查监控 却没想到这些监控不仅存储回放有限制 要想更多功能还是得多花钱 恰好&#xff0c;我有铁威马NAS 打开Surveillance Manager 轻松搭建网络摄像头管理系统 …

Pytorch中的tensor维度理解

Pytorch中的tensor维度理解 文章目录 Pytorch中的tensor维度理解摘要打消心理恐惧&#xff0c;从三维学起三维tensor参考文献 摘要 面对pytorch编程中的tensor时&#xff0c;我不时会感到恐惧。对里面数据是怎么排布的&#xff0c;一直没有一个直观的理解。今天我想把这个事情…

【Windows 常用工具系列 12 -- win11怎么设置不睡眠熄屏 |win11设置永不睡眠的方法】

文章目录 win11 怎么设置不睡眠熄屏 使用笔记本电脑的时候&#xff0c;如果离开电脑时间稍微长一点就会发现息屏了&#xff0c;下面介绍 设置 Win11 永不睡眠息屏的方法&#xff0c;有需要的朋友们快来看看以下详细的教程。 win11 怎么设置不睡眠熄屏 在电脑桌面上&#xff0c…

Missing file libarclite_iphoneos.a 问题解决方案

问题 在Xcode 运行项目会报以下错误 File not found: /Applications/Xcode-beta.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/arc/libarclite_iphoneos.a解决方案 打开URL https://github.com/kamyarelyasi/Libarclite-Files &#xff0c;下载liba…

坚鹏:中国工商银行数字化转型发展现状与成功案例培训圆满结束

中国工商银行围绕“数字生态、数字资产、数字技术、数字基建、数字基因”五维布局&#xff0c;深入推进数字化转型&#xff0c;加快形成体系化、生态化实施路径&#xff0c;促进科技与业务加速融合&#xff0c;以“数字工行”建设推动“GBC”&#xff08;政务、企业、个人&…

【uni-app】uniapp中弹出输入框的示例

uni.showModal({title: 请输入企业名称,content: ,editable: true, //是否显示输入框placeholderText: 请输入企业名称, //输入框提示内容confirmText: 确认,cancelText: 取消,success: (res) > {if (res.confirm) {this.checkDesc.name res.content;// console.log(输入的…

数字化时代的政务服务:构建便捷高效的线上政务大厅

引言&#xff1a; 随着数字化时代的来临&#xff0c;如何通过线上政务大厅搭建一个便捷高效的服务平台&#xff0c;以更好地满足公众需求值得探究。线上政务大厅是政务服务的新方式&#xff0c;但搭建线上政务大厅并不是一件容易的事情&#xff0c;需要精心的规划和设计。 一…

【Vue】生命周期一文详解

目录 前言 生命周期 钩子函数使用方法 ​编辑 周期-----创建阶段 创建阶段做了些什么事 该阶段可以干什么 周期----挂载阶段 挂载阶段做了什么事 该阶段适合干什么 周期----更新阶段 更新阶段做了什么事 该阶段适合做什么 周期----销毁阶段 销毁阶段做了什么事 …

No such module ‘FacebookCore‘

在下面的地方添加这个库

常用的工作资料怎么在电脑上记录呢?

在现代工作中&#xff0c;我们经常需要记录各种各样的工作资料&#xff0c;例如会议记录、项目计划、待办事项等等。传统的纸质笔记本虽然方便携带&#xff0c;但难以整理和检索。而在电脑上直接记录常用的工作资料&#xff0c;在记录、整理、查看、使用等方面都是更为高效、便…

SUDS: Scalable Urban Dynamic Scenes

SUDS: Scalable Urban Dynamic Scenes&#xff1a;可扩展的城市动态场景 创新点 1.将场景分解为三个单独的哈希表数据结构&#xff0c;以高效地编码静态、动态和远场辐射场 2.利用无标签的目标信号&#xff0c;包括RGB图像、稀疏LiDAR、现成的自监督2D描述符&#xff0c;以及…

消息中间件——RabbitMQ(三)理解RabbitMQ核心概念和AMQP协议!

前言 本章学习&#xff0c;我们可以了解到以下知识点&#xff1a; 互联网大厂为什么选择RabbitMQ&#xff1f;RabbiMQ的高性能之道是如何做到的&#xff1f;什么是AMQP高级协议&#xff1f;AMQP核心概念是什么&#xff1f;RabbitMQ整体架构模型是什么样子的&#xff1f;Rabbi…

计算机基础知识——字,字节,进制,short,byte等

目录 进制位&#xff0c;字节&#xff0c;字Byte&#xff0c;ShortByteBuf有符号数和无符号数 进制 HEX&#xff0c;Hexadecimal &#xff0c;十六进制。 DEC&#xff0c;Decimal &#xff0c;十进制。 OCT&#xff0c;Octal &#xff0c;八进制。 BIN&#xff0c;Binary &a…

语音识别技术在医疗行业中的应用案例

随着语音识别技术和计算机视觉技术的不断提高&#xff0c;现代医学正在进入全面数字化时代。 追求高质量的训练数据是人工智能产业的信条&#xff0c;得到更为精准的语音机器模型更离不开语音数据的不断供给。本文讲介绍: 什么是语音识别技术语音识别技术如何应用于医疗行业 …

SpringBoot:邮件发送

官网文档&#xff1a;39. Sending Email (spring.io)。 Sending Email Spring框架提供了JavaMailSender实例&#xff0c;用于发送邮件。 如果SpringBoot项目中包含了相关的启动器&#xff0c;那么就会自动装配一个Bean实例到项目中。 在SpringBoot项目中引入如下Email启动器&a…