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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【JavaEE】操作系统与进程

作者主页&#xff1a;paper jie_博客 本文作者&#xff1a;大家好&#xff0c;我是paper jie&#xff0c;感谢你阅读本文&#xff0c;欢迎一建三连哦。 本文录入于《JavaEE》专栏&#xff0c;本专栏是针对于大学生&#xff0c;编程小白精心打造的。笔者用重金(时间和精力)打造&…

Oracle的控制文件多路复用,控制文件备份,控制文件手工恢复

一.配置控制文件多路复用 1.查询Oracle的控制文件所在位置 SQL> select name from v$controlfile;NAME -------------------------------------------------------------------------------- /u01/app/oracle/oradata/orcl/control01.ctl /u01/app/oracle/fast_recovery_a…

规划类3d全景线上云展馆帮助企业轻松拓展海外市场

科技3D线上云展馆作为一种基于VR虚拟现实和互联网技术的新一代展览平台。可以在线上虚拟空间中模拟真实的展馆&#xff0c;让观众无需亲自到场&#xff0c;即可获得沉浸式的参观体验。通过这个展馆&#xff0c;您可以充分、全面、立体展示您的产品、服务以及各种创意作品&#…

实用篇 | T-SNE可视化工具详情及代码示例

本文主要是为了快速的了解t-sne和如何快速使用&#xff01; 简要了解TSNE TSNE&#xff0c;降维方法之一。降维在机器学习中非常重要。这是因为如果使用高维数据创建模型&#xff0c;则很容易欠拟合。换句话说&#xff0c;有太多无用的数据需要学习。可以通过从各种数据中仅…

Golang版本处理Skywalking Trace上报数据

Tips: 中间记录了解决问题的过程&#xff0c;如不感兴趣可直接跳至结尾 首先去es里查询skywalking trace的元数据 可以拿到一串base64加密后的data_binary(直接解密不能用&#xff0c;会有乱码&#xff0c;可参考https://github.com/apache/skywalking/issues/7423) 对data_b…

第四代智能井盖传感器:智能井盖位移怎么进行监测

井盖是城市基础设施的一个重要组成部分&#xff0c;若井盖出现移位等现象&#xff0c;可能会对路过的车辆和行人造成潜在危险。特别是那些含有甲烷气体的井盖&#xff0c;一旦气体超过阈值且被意外踩踏&#xff0c;可能会导致气体的释放&#xff0c;这便会引发一系列安全事故&a…

Linux wait函数用法

wait 函数是用于等待子进程结束并获取子进程的终止状态的系统调用。它在父进程中使用&#xff0c;用于等待其子进程终止并获得子进程的退出状态。 函数原型&#xff1a; pid_t wait(int *status);status 是一个指向整型的指针&#xff0c;用于存储子进程终止时的退出状态&…

clang+llvm多进程gdb调试

clangllvm多进程gdb调试 前言1. 命令行gdb2. 父进程调试3. 子进程调试4. 返回父进程 前言 在学习新增llvm的优化pass时&#xff0c;需要跟踪clang及llvm的调用栈。然而llvm通过posix_spawn()创建了新进程&#xff0c;这使得gdb调试必须有一定的技巧了。 1. 命令行gdb 以下命…

小红书干货类笔记怎么写?建议收藏

小红书干货类笔记是指在小红书这个社交平台上&#xff0c;用户分享的各种实用、有价值的生活技巧、经验、心得等内容的笔记。这类笔记通常具有以下特点&#xff1a;内容详实、实用性强、独特见解、图文并茂。 比如&#xff1a;某个妆要怎么化、某种技能该怎么学、某个城市该怎…

详细梳理山姆·奥特曼离职闹剧 仍试图重返OpenAI

大家好,我是极智视界,欢迎关注我的公众号,获取我的更多前沿科技分享 邀您加入我的知识星球「极智视界」,星球内有超多好玩的项目实战源码和资源下载,链接:https://t.zsxq.com/0aiNxERDq OpenAI 与微软围绕 Sam Altman 的离职风波,这场肥皂剧似乎还没到终结的样子。目前 …

跨越行业边界,CodeMeter护航AI领域安全与合规

在人工智能&#xff08;AI&#xff09;技术如ChatGPT的推动下&#xff0c;工业视觉、医疗诊断和智能驾驶等领域正在经历重大变革。这些技术不仅扩大了应用范围&#xff0c;也带来了数据安全、软件授权保护和合规性等新挑战。 AI工业视觉正在推动制造和自动化的快速发展&#x…

YOLOv5结合华为诺亚VanillaNet Block模块

🗝️YOLOv5实战宝典--星级指南:从入门到精通,您不可错过的技巧   -- 聚焦于YOLO的 最新版本, 对颈部网络改进、添加局部注意力、增加检测头部,实测涨点 💡 深入浅出YOLOv5:我的专业笔记与技术总结   -- YOLOv5轻松上手, 适用技术小白,文章代码齐全,仅需 …

出海企业首选的免费开源财务管理系统解决方案

计费与订阅管理 Odoo计费与订阅管理解决方案可帮助您同步从订单、计费到收入确认的复杂流程 Odoo Subscriptions将计费与订阅置于核心业务流程中&#xff0c;将其从普通的后端功能转化为具有决定性意义的战略性业务工具。Odoo统一计费框架支持根据事务、订阅、使用量计费以及…

大数据题目的解题技巧

目录 大数据题目的技巧总括 实例精析 实例一 实例二 实例三 大数据题目的技巧总括 &#xff08;1&#xff09;哈希函数可以把数据按照种类均匀分流&#xff1b; &#xff08;2&#xff09;布隆过滤器用于集合的建立与查询&#xff0c;并可以节省大量空间&#xff1b; &…

前缀和的动态维护——树状数组[C/C++]

文章目录 前言lowbitlowbit的定义lowbit的计算 树状数组的思想树状数组的操作单点修改 update前缀查询 query树状数组的建立 build 前言 树状数组巧妙了利用位运算和树形结构实现了允许单点修改的情况下&#xff0c;动态维护前缀和&#xff0c;并且实现单点修改和前缀和查询的效…

柱形图:制作图表时,有时会遇到柱形图系列没有居中显示,例如:

问题描述 制作图表时&#xff0c;有时会遇到柱形图系列没有居中显示&#xff0c;例如&#xff1a; 原因分析 柱形图的「分类」和「系列名」均选择了「地区」&#xff0c;导致分类下存在不同的系列&#xff0c;那么当前分类下没有的系列就会存在「空白占位」。 解决方案 此时…

多普勒流速仪的功能作用是什么?

我国地域广大&#xff0c;各地降雨分布不均&#xff0c;某些城市经常会出现连续的降雨进而导致城市排水压力过大&#xff0c;为了提高城市应对排水过量的极端情况的出现&#xff0c;亟需一种方案能够对城市排水进行有效及时的监测&#xff0c;从而能够及时的采取应对方案。 在污…

区域人员超限AI算法的介绍及TSINGSEE视频智能分析技术的行业应用

视频AI智能分析已经渗透到人类生活及社会发展的各个方面。从生活中的人脸识别、停车场的车牌识别、工厂园区的翻越围栏识别、入侵识别、工地的安全帽识别、车间流水线产品的品质缺陷AI检测等&#xff0c;AI智能分析技术无处不在。在某些场景中&#xff0c;重点区域的人数统计与…

详解Java的static关键字

文章目录 &#x1f384;静态方法&#x1f33a;静态方法和非静态方法对比&#x1f6f8;静态方法实例&#x1f6f8;非静态方法实例 &#x1f339;static关键字⭐static变量⭐static代码块 &#x1f384;静态方法 不依赖于对象实例&#xff1a;静态方法不需要依赖于任何对象实例&…

香港高端人才通行证计划申请(包括条件)你需要知道的这些真相!

香港高端人才通行证计划申请&#xff08;包括条件&#xff09;你需要知道的这些真相&#xff01; 香港高才通计划从刚推出就带着“光速获批“的光环&#xff0c;吸引了大批高学历和高收入人士&#xff0c;后续也因它申请要求简单、明确&#xff0c;获批率高等优势&#xff0c;火…