开源 java android app 开发(十四)自定义绘图控件--波形图

Android利用surfaceview控件实现波形高速绘制 如图: 实现步骤: 1、activity_main.xml <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" 阅读详情

 文章的目的为了记录使用java 进行android app 开发学习的经历。本职为嵌入式软件开发,公司安排开发app,临时学习,完成app的开发。开发流程和要点有些记忆模糊,赶紧记录,防止忘记。

 

    相关链接:

开源 java android app 开发(一)开发环境的搭建-优快云博客

开源 java android app 开发(二)工程文件结构-优快云博客

开源 java android app 开发(三)GUI界面布局和常用组件-优快云博客

开源 java android app 开发(四)GUI界面重要组件-优快云博客

开源 java android app 开发(五)文件和数据库存储-优快云博客

开源 java android app 开发(六)多媒体使用-优快云博客

开源 java android app 开发(七)通讯之Tcp和Http-优快云博客

开源 java android app 开发(八)通讯之Mqtt和Ble-优快云博客

开源 java android app 开发(九)后台之线程和服务-优快云博客

开源 java android app 开发(十)广播机制-优快云博客

开源 java android app 开发(十一)调试、发布-优快云博客

开源 java android app 开发(十二)封库.aar-优快云博客

开源 java android app 开发(十三)自定义绘图控件--游戏摇杆

开源 java android app 开发(十四)自定义绘图控件--波形图

开源 java android app 开发(十五)自定义绘图控件--仪表盘

开源 java android app 开发(十六)自定义绘图控件--圆环

推荐链接:

开源C# .net mvc 开发(一)WEB搭建_c#部署web程序-优快云博客

开源 C# .net mvc 开发(二)网站快速搭建_c#网站开发-优快云博客

开源 C# .net mvc 开发(三)WEB内外网访问(VS发布、IIS配置网站、花生壳外网穿刺访问)_c# mvc 域名下不可訪問內網,內網下可以訪問域名-优快云博客

开源 C# .net mvc 开发(四)工程结构、页面提交以及显示_c#工程结构-优快云博客

开源 C# .net mvc 开发(五)常用代码快速开发_c# mvc开发-优快云博客

本章节主要内容是:自定义的波形图控件的使用,主活动随机产生多条曲线。

1.代码分析

2.所有源码

3.效果图

一、代码分析

WaveView自定义View详细函数分析
1. 构造函数和初始化
init(AttributeSet attrs) - 核心初始化函数
 

private void init(AttributeSet attrs) {
    waveDataList = new ArrayList<>();  // 初始化波形数据列表
    
    // 设置深蓝色科幻风格背景
    setBackgroundColor(backgroundColor);
    
    // 初始化四种不同的画笔
    initAxisPaint();     // 坐标轴画笔
    initGridPaint();     // 网格画笔  
    initTextPaint();     // 文字画笔
    initGlowPaint();     // 发光效果画笔
    
    // 设置默认坐标轴尺寸
    xAxisSize = 800;     // X轴宽度
    yAxisSize = 400;     // Y轴高度
    
    // 启用硬件加速提升性能
    setLayerType(LAYER_TYPE_HARDWARE, null);
}


画笔初始化细节
坐标轴画笔:

颜色:半透明青色 Color.argb(180, 0, 255, 255)

线宽:3像素

样式:描边 Style.STROKE

抗锯齿:开启

网格画笔:

颜色:更透明的青色 Color.argb(80, 0, 255, 255)

线宽:1像素(比坐标轴细)

文字画笔:

字体大小:28sp

对齐方式:居中 Align.CENTER

发光画笔:

特殊效果:阴影层 setShadowLayer(15, 0, 0, Color.argb(150, 0, 255, 255))

阴影半径15像素,产生光晕效果

2. 数据管理函数
addWaveData(String name, int color) - 添加波形通道


public void addWaveData(String name, int color) {
    WaveData waveData = new WaveData(name, color);  // 创建波形数据对象
    waveDataList.add(waveData);                     // 添加到列表
    // 注意:这里没有调用invalidate(),因为只是添加通道还没有数据
}
addDataToWave(String name, float value) - 添加数据点
java
public void addDataToWave(String name, float value) {
    for (WaveData waveData : waveDataList) {
        if (waveData.getName().equals(name)) {
            waveData.addData(value);  // 添加数据
            
            // FIFO管理:超过200个点移除最旧的数据
            if (waveData.getData().size() > maxDataPoints) {
                waveData.getData().remove(0);  // 移除第一个元素
            }
            break;
        }
    }
    invalidate();  // 触发重绘
}


数据流管理:

查找对应名称的波形通道

添加新数据点

维护固定长度队列(200点)

自动触发界面更新

3. 绘制函数详细分析
onDraw(Canvas canvas) - 主绘制流程
 

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);  // 调用父类绘制(背景色)
    
    // 绘制顺序:从底层到顶层
    if (showGrid) {
        drawGrid(canvas);      // 1. 网格(最底层)
    }
    drawAxis(canvas);          // 2. 坐标轴
    drawWaves(canvas);         // 3. 波形数据(主要内容)
    drawLegend(canvas);        // 4. 图例(覆盖在波形上)
    drawBorder(canvas);        // 5. 边框(最顶层)
}
drawGrid(Canvas canvas) - 网格绘制
java
private void drawGrid(Canvas canvas) {
    float startX = 50;                    // 网格起始X坐标
    float startY = getHeight() - 50;      // 网格起始Y坐标(底部向上50px)
    float endX = startX + xAxisSize;      // 网格结束X坐标
    float endY = startY - yAxisSize;      // 网格结束Y坐标
    
    // 水平网格线:gridDensity+1条线(包含边界)
    for (int i = 0; i <= gridDensity; i++) {
        float y = startY - (i * yAxisSize / gridDensity);  // 等分Y轴
        canvas.drawLine(startX, y, endX, y, gridPaint);    // 绘制水平线
    }
    
    // 垂直网格线:2倍密度,更密的纵向网格
    for (int i = 0; i <= gridDensity * 2; i++) {
        float x = startX + (i * xAxisSize / (gridDensity * 2));
        canvas.drawLine(x, startY, x, endY, gridPaint);
    }
}


网格特点:

水平线:按gridDensity等分(默认10等分)

垂直线:双倍密度,更细的纵向分割

网格区域:固定位置,距离左边和底部各50像素

drawAxis(Canvas canvas) - 坐标轴绘制
 

private void drawAxis(Canvas canvas) {
    float startX = 50;                    // 坐标原点X
    float startY = getHeight() - 50;      // 坐标原点Y
    float endX = startX + xAxisSize;      // X轴终点
    float endY = startY - yAxisSize;      // Y轴终点
    
    // 绘制X轴和Y轴主线
    canvas.drawLine(startX, startY, endX, startY, axisPaint);  // X轴
    canvas.drawLine(startX, startY, startX, endY, axisPaint);  // Y轴
    
    // 绘制箭头(简化版,只是延长线)
    drawArrow(canvas, endX, startY, endX + 15, startY);  // X轴箭头
    drawArrow(canvas, startX, endY, startX, endY - 15);  // Y轴箭头
    
    // X轴刻度(11个刻度:0-10)
    for (int i = 0; i <= 10; i++) {
        float x = startX + (i * xAxisSize / 10);
        canvas.drawLine(x, startY, x, startY - 8, axisPaint);  // 刻度线
        canvas.drawText(String.valueOf(i), x, startY + 25, textPaint);  // 刻度值
    }
    
    // Y轴刻度(6个刻度:0-5)
    for (int i = 0; i <= 5; i++) {
        float y = startY - (i * yAxisSize / 5);
        canvas.drawLine(startX, y, startX + 8, y, axisPaint);
        canvas.drawText(String.valueOf(i), startX - 20, y + 10, textPaint);
    }
    
    // 坐标轴标签(带旋转效果)
    canvas.drawText("时间 (s)", startX + xAxisSize / 2, startY + 50, textPaint);
    canvas.save();  // 保存画布状态
    canvas.rotate(-90, startX - 40, startY - yAxisSize / 2);  // 旋转-90度
    canvas.drawText("幅度 (V)", startX - 40, startY - yAxisSize / 2, textPaint);
    canvas.restore();  // 恢复画布状态
}


坐标轴特性:

固定刻度数量:X轴11个,Y轴6个

Y轴标签通过旋转实现垂直文字

使用save()/restore()保护画布状态

drawWaves(Canvas canvas) - 核心波形绘制
 

private void drawWaves(Canvas canvas) {
    if (waveDataList.isEmpty()) return;
    
    float startX = 50;                    // 波形起始X
    float startY = getHeight() - 50;      // 波形起始Y(坐标原点)
    float xStep = xAxisSize / (maxDataPoints - 1);  // X轴步进(每个数据点间距)
    
    for (WaveData waveData : waveDataList) {
        List<Float> data = waveData.getData();
        if (data.size() < 2) continue;    // 至少需要2个点才能画线
        
        // 1. 绘制发光效果(底层)
        if (showGlowEffect) {
            drawGlowEffect(canvas, waveData, startX, startY, xStep);
        }
        
        // 2. 绘制渐变填充(中层)
        drawGradientFill(canvas, waveData, startX, startY, xStep);
        
        // 3. 绘制主波形线(上层)
        drawMainWaveLine(canvas, waveData, startX, startY, xStep);
        
        // 4. 绘制数据点(可选)
        if (data.size() < 50) {
            drawDataPoints(canvas, waveData, startX, startY, xStep);
        }
    }
}


发光效果绘制
 

// 简化版的发光效果实现
Path glowPath = new Path();
glowPath.moveTo(startX, startY - (data.get(0) * yAxisSize / 5));  // 移动到第一个点

for (int i = 1; i < data.size(); i++) {
    float x = startX + (i * xStep);
    float y = startY - (data.get(i) * yAxisSize / 5);  // 数据映射到坐标
    glowPath.lineTo(x, y);  // 连线到下一个点
}

// 使用半透明的宽线条产生发光效果
glowPaint.setAlpha(100);  // 半透明
glowPaint.setStrokeWidth(5);  // 比主线条粗
canvas.drawPath(glowPath, glowPaint);
渐变填充绘制
java
Path fillPath = new Path();
fillPath.moveTo(startX, startY);  // 从坐标原点开始
fillPath.lineTo(startX, startY - (data.get(0) * yAxisSize / 5));  // 到第一个数据点

// 连接所有数据点
for (int i = 1; i < data.size(); i++) {
    float x = startX + (i * xStep);
    float y = startY - (data.get(i) * yAxisSize / 5);
    fillPath.lineTo(x, y);
}

// 闭合路径:从最后一个点回到坐标原点
fillPath.lineTo(startX + (data.size() - 1) * xStep, startY);
fillPath.close();  // 形成闭合区域

// 创建从上到下的渐变
LinearGradient gradient = new LinearGradient(
    0, startY - yAxisSize,  // 渐变起点(顶部)
    0, startY,              // 渐变终点(底部)
    Color.argb(80, r, g, b),  // 顶部颜色(较深)
    Color.argb(20, r, g, b),  // 底部颜色(较浅)
    Shader.TileMode.CLAMP
);


数据映射公式:

// 将数据值映射到屏幕坐标
float y = startY - (dataValue * yAxisSize / 5);
dataValue:原始数据值(0-5范围)

yAxisSize / 5:每个单位对应的像素数

startY - ...:因为屏幕坐标系Y轴向下,需要取反

drawLegend(Canvas canvas) - 图例绘制
java
private void drawLegend(Canvas canvas) {
    float startX = 60 + xAxisSize;        // 图例在波形区域右侧
    float startY = getHeight() - 50 - yAxisSize + 30;  // 与波形顶部对齐
    
    // 绘制半透明背景
    Paint legendBgPaint = new Paint();
    legendBgPaint.setColor(Color.argb(150, 0, 30, 60));  // 深蓝色半透明背景
    canvas.drawRoundRect(startX - 10, startY - 10, 
                        startX + 180, startY + waveDataList.size() * 40 + 20, 
                        10, 10, legendBgPaint);  // 圆角矩形
    
    // 绘制每个波形的图例项
    for (int i = 0; i < waveDataList.size(); i++) {
        WaveData waveData = waveDataList.get(i);
        
        // 颜色方块
        canvas.drawRect(startX, startY + (i * 40), 
                       startX + 20, startY + (i * 40) + 20, 
                       rectPaint);
        
        // 波形名称
        canvas.drawText(waveData.getName(), 
                       startX + 100, startY + (i * 40) + 15, 
                       textPaint);
    }
}


4. 辅助函数
配置函数族
java

public void setAxisSize(float xAxisSize, float yAxisSize) {
    this.xAxisSize = xAxisSize;
    this.yAxisSize = yAxisSize;
    invalidate();  // 尺寸改变后立即重绘
}

public void setGridDensity(int density) {
    this.gridDensity = density;
    invalidate();  // 网格密度改变后重绘
}

public void setShowGrid(boolean show) {
    this.showGrid = show;
    invalidate();  // 显示状态改变后重绘
}


设计模式:所有改变显示状态的函数都调用invalidate()触发界面更新。

5. 性能优化特性
硬件加速:setLayerType(LAYER_TYPE_HARDWARE, null)

数据量控制:固定200个数据点,避免内存无限增长

条件绘制:数据点少于50时才绘制点状标记

路径绘制:使用Path而非多次drawLine提高效率

6. 坐标系统总结
text
(0,0) ┌────────────────────────────────────────┐
      │          Wave View                     │
      │    ┌─────────────────┐                 │
      │    │                 │  图例区域       │
      │    │   波形区域      │ ┌──────┐        │
      │    │                 │ │信号1│        │
      │    │                 │ │信号2│        │
      │    └─────────────────┘ └──────┘        │
      │                                        │
      └────────────────────────────────────────┘ (width,height)
      
波形区域坐标:
起点:(50, height-50)
尺寸:800×400 像素
数据范围:X轴0-10秒,Y轴0-5伏特
这个自定义View设计完整,功能丰富,具有良好的可扩展性和性能优化。

二、所有源码

源码总共3部分

1.waveview.java源码

package com.example.wave;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Shader;
import android.util.AttributeSet;
import android.view.View;

import java.util.ArrayList;
import java.util.List;

public class WaveView extends View {
    private List<WaveData> waveDataList;
    private Paint axisPaint;
    private Paint gridPaint;
    private Paint textPaint;
    private Paint glowPaint;
    private float xAxisSize;
    private float yAxisSize;
    private int maxDataPoints = 200;
    private int gridDensity = 10;
    private boolean showGrid = true;
    private boolean showGlowEffect = true;

    // 科幻风格颜色
    private int gridColor = Color.argb(80, 0, 255, 255); // 半透明青色网格
    private int axisColor = Color.argb(180, 0, 255, 255); // 青色坐标轴
    private int backgroundColor = Color.argb(255, 10, 20, 40); // 深蓝色背景
    private int textColor = Color.argb(200, 0, 255, 255); // 青色文字

    public WaveView(Context context) {
        super(context);
        init(null);
    }

    public WaveView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(attrs);
    }

    public WaveView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(attrs);
    }

    private void init(AttributeSet attrs) {
        waveDataList = new ArrayList<>();

        // 设置背景色
        setBackgroundColor(backgroundColor);

        // 初始化坐标轴画笔
        axisPaint = new Paint();
        axisPaint.setColor(axisColor);
        axisPaint.setStrokeWidth(3);
        axisPaint.setStyle(Paint.Style.STROKE);
        axisPaint.setAntiAlias(true);

        // 初始化网格画笔
        gridPaint = new Paint();
        gridPaint.setColor(gridColor);
        gridPaint.setStrokeWidth(1);
        gridPaint.setStyle(Paint.Style.STROKE);
        gridPaint.setAntiAlias(true);

        // 初始化文字画笔
        textPaint = new Paint();
        textPaint.setColor(textColor);
        textPaint.setTextSize(28);
        textPaint.setAntiAlias(true);
        textPaint.setTextAlign(Paint.Align.CENTER);

        // 初始化发光效果画笔
        glowPaint = new Paint();
        glowPaint.setStrokeWidth(4);
        glowPaint.setStyle(Paint.Style.STROKE);
        glowPaint.setAntiAlias(true);
        glowPaint.setShadowLayer(15, 0, 0, Color.argb(150, 0, 255, 255));

        // 默认坐标轴大小
        xAxisSize = 800;
        yAxisSize = 400;

        // 启用硬件加速以获得更好的性能
        setLayerType(LAYER_TYPE_HARDWARE, null);
    }

    public void setAxisSize(float xAxisSize, float yAxisSize) {
        this.xAxisSize = xAxisSize;
        this.yAxisSize = yAxisSize;
        invalidate();
    }

    public void setGridDensity(int density) {
        this.gridDensity = density;
        invalidate();
    }

    public void setShowGrid(boolean show) {
        this.showGrid = show;
        invalidate();
    }

    public void setShowGlowEffect(boolean show) {
        this.showGlowEffect = show;
        invalidate();
    }

    public void addWaveData(String name, int color) {
        WaveData waveData = new WaveData(name, color);
        waveDataList.add(waveData);
    }

    public void addDataToWave(String name, float value) {
        for (WaveData waveData : waveDataList) {
            if (waveData.getName().equals(name)) {
                waveData.addData(value);
                if (waveData.getData().size() > maxDataPoints) {
                    waveData.getData().remove(0);
                }
                break;
            }
        }
        invalidate();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        // 绘制网格
        if (showGrid) {
            drawGrid(canvas);
        }

        // 绘制坐标轴
        drawAxis(canvas);

        // 绘制波形
        drawWaves(canvas);

        // 绘制图例
        drawLegend(canvas);

        // 绘制边框效果
        drawBorder(canvas);
    }

    private void drawGrid(Canvas canvas) {
        float startX = 50;
        float startY = getHeight() - 50;
        float endX = startX + xAxisSize;
        float endY = startY - yAxisSize;

        // 绘制水平网格线
        for (int i = 0; i <= gridDensity; i++) {
            float y = startY - (i * yAxisSize / gridDensity);
            canvas.drawLine(startX, y, endX, y, gridPaint);
        }

        // 绘制垂直网格线
        for (int i = 0; i <= gridDensity * 2; i++) {
            float x = startX + (i * xAxisSize / (gridDensity * 2));
            canvas.drawLine(x, startY, x, endY, gridPaint);
        }
    }

    private void drawAxis(Canvas canvas) {
        float startX = 50;
        float startY = getHeight() - 50;
        float endX = startX + xAxisSize;
        float endY = startY - yAxisSize;

        // 绘制X轴
        canvas.drawLine(startX, startY, endX, startY, axisPaint);
        // 绘制Y轴
        canvas.drawLine(startX, startY, startX, endY, axisPaint);

        // 绘制箭头
        drawArrow(canvas, endX, startY, endX + 15, startY); // X轴箭头
        drawArrow(canvas, startX, endY, startX, endY - 15); // Y轴箭头

        // 绘制刻度
        for (int i = 0; i <= 10; i++) {
            float x = startX + (i * xAxisSize / 10);
            canvas.drawLine(x, startY, x, startY - 8, axisPaint);
            canvas.drawText(String.valueOf(i), x, startY + 25, textPaint);
        }

        for (int i = 0; i <= 5; i++) {
            float y = startY - (i * yAxisSize / 5);
            canvas.drawLine(startX, y, startX + 8, y, axisPaint);
            canvas.drawText(String.valueOf(i), startX - 20, y + 10, textPaint);
        }

        // 绘制坐标轴标签
        canvas.drawText("时间 (s)", startX + xAxisSize / 2, startY + 50, textPaint);
        canvas.save();
        canvas.rotate(-90, startX - 40, startY - yAxisSize / 2);
        canvas.drawText("幅度 (V)", startX - 40, startY - yAxisSize / 2, textPaint);
        canvas.restore();
    }

    private void drawArrow(Canvas canvas, float x1, float y1, float x2, float y2) {
        canvas.drawLine(x1, y1, x2, y2, axisPaint);
    }

    private void drawWaves(Canvas canvas) {
        if (waveDataList.isEmpty()) return;

        float startX = 50;
        float startY = getHeight() - 50;
        float xStep = xAxisSize / (maxDataPoints - 1);

        for (WaveData waveData : waveDataList) {
            List<Float> data = waveData.getData();
            if (data.size() < 2) continue;

            // 绘制主波形线
            Paint mainPaint = new Paint();
            mainPaint.setColor(waveData.getColor());
            mainPaint.setStrokeWidth(3);
            mainPaint.setStyle(Paint.Style.STROKE);
            mainPaint.setAntiAlias(true);

            // 绘制发光效果
            if (showGlowEffect) {
                Paint glowPaint = new Paint();
                glowPaint.setColor(waveData.getColor());
                glowPaint.setStrokeWidth(5);
                glowPaint.setStyle(Paint.Style.STROKE);
                glowPaint.setAlpha(100);
                glowPaint.setAntiAlias(true);

                Path glowPath = new Path();
                glowPath.moveTo(startX, startY - (data.get(0) * yAxisSize / 5));

                for (int i = 1; i < data.size(); i++) {
                    float x = startX + (i * xStep);
                    float y = startY - (data.get(i) * yAxisSize / 5);
                    glowPath.lineTo(x, y);
                }

                canvas.drawPath(glowPath, glowPaint);
            }

            // 绘制渐变填充效果
            Path fillPath = new Path();
            fillPath.moveTo(startX, startY);
            fillPath.lineTo(startX, startY - (data.get(0) * yAxisSize / 5));

            for (int i = 1; i < data.size(); i++) {
                float x = startX + (i * xStep);
                float y = startY - (data.get(i) * yAxisSize / 5);
                fillPath.lineTo(x, y);
            }

            fillPath.lineTo(startX + (data.size() - 1) * xStep, startY);
            fillPath.close();

            Paint fillPaint = new Paint();
            fillPaint.setStyle(Paint.Style.FILL);
            fillPaint.setShader(new LinearGradient(
                    0, startY - yAxisSize,
                    0, startY,
                    Color.argb(80, Color.red(waveData.getColor()),
                            Color.green(waveData.getColor()),
                            Color.blue(waveData.getColor())),
                    Color.argb(20, Color.red(waveData.getColor()),
                            Color.green(waveData.getColor()),
                            Color.blue(waveData.getColor())),
                    Shader.TileMode.CLAMP
            ));

            canvas.drawPath(fillPath, fillPaint);

            // 绘制主波形线
            Path mainPath = new Path();
            mainPath.moveTo(startX, startY - (data.get(0) * yAxisSize / 5));

            for (int i = 1; i < data.size(); i++) {
                float x = startX + (i * xStep);
                float y = startY - (data.get(i) * yAxisSize / 5);
                mainPath.lineTo(x, y);
            }

            canvas.drawPath(mainPath, mainPaint);

            // 绘制数据点(只在数据点较少时显示)
            if (data.size() < 50) {
                Paint pointPaint = new Paint();
                pointPaint.setColor(waveData.getColor());
                pointPaint.setStyle(Paint.Style.FILL);
                pointPaint.setAntiAlias(true);

                for (int i = 0; i < data.size(); i++) {
                    float x = startX + (i * xStep);
                    float y = startY - (data.get(i) * yAxisSize / 5);
                    canvas.drawCircle(x, y, 4, pointPaint);
                    canvas.drawCircle(x, y, 6, glowPaint);
                }
            }
        }
    }

    private void drawLegend(Canvas canvas) {
        float startX = 60 + xAxisSize;
        float startY = getHeight() - 50 - yAxisSize + 30;

        Paint legendBgPaint = new Paint();
        legendBgPaint.setColor(Color.argb(150, 0, 30, 60));
        legendBgPaint.setStyle(Paint.Style.FILL);
        legendBgPaint.setAntiAlias(true);

        // 绘制图例背景
        float legendWidth = 180;
        float legendHeight = waveDataList.size() * 40 + 20;
        canvas.drawRoundRect(startX - 10, startY - 10,
                startX + legendWidth, startY + legendHeight, 10, 10, legendBgPaint);

        for (int i = 0; i < waveDataList.size(); i++) {
            WaveData waveData = waveDataList.get(i);

            Paint rectPaint = new Paint();
            rectPaint.setColor(waveData.getColor());
            rectPaint.setStyle(Paint.Style.FILL);
            rectPaint.setAntiAlias(true);

            // 绘制图例颜色块
            canvas.drawRect(startX, startY + (i * 40),
                    startX + 20, startY + (i * 40) + 20, rectPaint);

            // 绘制图例文字
            canvas.drawText(waveData.getName(), startX + 100, startY + (i * 40) + 15, textPaint);
        }
    }

    private void drawBorder(Canvas canvas) {
        Paint borderPaint = new Paint();
        borderPaint.setColor(Color.argb(100, 0, 255, 255));
        borderPaint.setStrokeWidth(2);
        borderPaint.setStyle(Paint.Style.STROKE);
        borderPaint.setAntiAlias(true);

        // 绘制外边框
        canvas.drawRect(5, 5, getWidth() - 5, getHeight() - 5, borderPaint);

        // 绘制内发光效果
        Paint innerGlowPaint = new Paint();
        innerGlowPaint.setColor(Color.argb(50, 0, 255, 255));
        innerGlowPaint.setStrokeWidth(1);
        innerGlowPaint.setStyle(Paint.Style.STROKE);
        innerGlowPaint.setAntiAlias(true);

        canvas.drawRect(8, 8, getWidth() - 8, getHeight() - 8, innerGlowPaint);
    }

    // 内部类,用于存储波形数据
    private static class WaveData {
        private String name;
        private int color;
        private List<Float> data;

        public WaveData(String name, int color) {
            this.name = name;
            this.color = color;
            this.data = new ArrayList<>();
        }

        public String getName() {
            return name;
        }

        public int getColor() {
            return color;
        }

        public List<Float> getData() {
            return data;
        }

        public void addData(float value) {
            data.add(value);
        }
    }
}

2.MainActivity.java源码

package com.example.wave;

import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.view.WindowManager;
import android.widget.LinearLayout;

import androidx.appcompat.app.AppCompatActivity;

import java.util.Random;

public class MainActivity extends AppCompatActivity {
    private WaveView waveView;
    private Handler handler;
    private Runnable updateRunnable;
    private int time = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

// 全屏模式
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                WindowManager.LayoutParams.FLAG_FULLSCREEN);

        setContentView(R.layout.activity_main);

        // 获取布局容器
        LinearLayout container = findViewById(R.id.container);

        // 创建WaveView实例
        waveView = new WaveView(this);

// 设置科幻风格参数
        waveView.setShowGrid(true);
        waveView.setShowGlowEffect(true);
        waveView.setGridDensity(12);

// 使用科幻风格的颜色
        int[] waveColors = {
                Color.argb(255, 0, 255, 255),   // 青色 - 科幻感
                Color.argb(255, 255, 0, 255),   // 洋红色 - 科幻感
                Color.argb(255, 0, 255, 0)      // 绿色 - 科幻感
        };

        // 设置波形图大小
        waveView.setAxisSize(1600, 500);

        // 添加波形数据系列
        waveView.addWaveData("正弦波", waveColors[0]);
        waveView.addWaveData("余弦波", waveColors[1]);
        waveView.addWaveData("噪声波", waveColors[2]);

        // 将WaveView添加到布局中
        container.addView(waveView);

        // 创建Handler用于定时更新
        handler = new Handler();
        updateRunnable = new Runnable() {
            @Override
            public void run() {
                updateWaveData();
                handler.postDelayed(this, 1000); // 每秒更新一次
            }
        };
    }

    @Override
    protected void onResume() {
        super.onResume();
        // 开始定时更新
        handler.post(updateRunnable);
    }

    @Override
    protected void onPause() {
        super.onPause();
        // 停止定时更新
        handler.removeCallbacks(updateRunnable);
    }

    private void updateWaveData() {
        // 生成正弦波数据
        float sinValue = (float) Math.sin(time * 0.1);
        waveView.addDataToWave("正弦波", sinValue * 2 + 2.5f); // 调整幅度和偏移

        // 生成余弦波数据
        float cosValue = (float) Math.cos(time * 0.1);
        waveView.addDataToWave("余弦波", cosValue * 1.5f + 2.5f); // 调整幅度和偏移

        // 生成带噪声的数据
        Random random = new Random();
        float noiseValue = (float) (Math.sin(time * 0.1) + (random.nextFloat() - 0.5f));
        waveView.addDataToWave("噪声波", noiseValue + 2.5f); // 添加偏移

        time++;
    }
}

3.activity_main.xml源码

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp">

    <TextView
        android:id="@+id/titleTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="多波形显示"
        android:textSize="24sp"
        android:textStyle="bold"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <LinearLayout
        android:id="@+id/container"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:orientation="vertical"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/titleTextView" />

</androidx.constraintlayout.widget.ConstraintLayout>

三、效果图

运行后,程序开始绘制3条曲线图。

安卓心电波形控件(改) 心电波形控件,安卓开发,支持手势操作,具体内容见https://www.jianshu.com/p/db542c48cd41 立即下载

相关推荐

基于android的音频信号分析并显示波形图对比结果

基于android的音频信号分析并显示波形图对比结果,增加滤波功能,短时能量,对比分析

【Android Studio学习】第二篇、APP实现画简易的波形图

【Android Studio学习】第二篇、APP实现画简易的波形图

大屁桃的博客 5947

Android代码-原创自定义控件之-Canvas实时绘制音乐波形图

此项目已废弃

整理了一个 android 上的波形图及柱状图绘制控件

欢迎使用Markdown编辑器写博客本Markdown编辑器使用StackEdit修改而来,用它写博客,将会带来全新的体验哦: Markdown和扩展Markdown简洁的语法 代码块高亮 图片链接和图片上传 LaTex数学公式 UML序列图和流程图 离线写博客 导入导出Markdown文件 丰富的快捷键 快捷键 加粗 Ctrl + B 斜体 Ctrl + I 引用 Ctrl

微风轻拂 7741

Android波形绘制

Android波形绘制,模拟数据的可视化波形,可以在各种应用中使用

Andriod 自定义控件之音频条

今天我们实现一个直接继承于View的全新控件。大家都知道音乐播放器吧,在点击一首歌进行播放时,通常会有一块区域用于显示音频条,我们今天就来学习下,播放器音频条的实现。 首先我们还是先定义一个类,直接继承于View,并重写它的构造方法,并初始化一个画笔,这和上一节是同样的道理。直接贴出代码: public class AudioBar extends View{ private Paint mTextPaint; public AudioBar(Context context) { this(context,null); } public AudioBar(Context context, A

开源 java android app 开发(十三)自定义绘图控件--游戏摇杆

摘要:本文记录了嵌入式开发者临时转战Android应用开发的经历,重点讲解了如何通过Java自定义摇杆控件。文章详细阐述了使用Canvas和Paint类进行绘图的基础知识,并完整展示了摇杆控件的实现过程,包括:1)继承View类创建自定义控件;2)处理触摸事件实现拖动效果;3)添加属性文件支持XML参数配置;4)设计回调接口供外部使用。该摇杆控件可用于机器人/无人机控制等场景,文中还提供了完整的代码实现和效果展示。作为系列教程的第13篇,本文帮助开发者快速掌握Android自定义控件的开发技巧。

ajassi2000的博客 1024

Spring WebFlux响应式编程实战(三):优雅处理异常与高效调试技巧

本文深入探讨了Spring WebFlux响应式编程中的异常处理与调试技巧。文章通过电商订单流处理场景,详细解析了Reactor核心操作符onErrorReturn、onErrorResume和onErrorMap的实战应用,并介绍了如何构建@ControllerAdvice和WebExceptionHandler全局异常处理防线。同时,提供了使用.log()操作符、Hooks.onOperatorDebug()及BlockHound进行高效调试的方法,旨在帮助开发者构建健壮、可维护的响应式应用。

weixin_29252171的博客 65

开源 java android app 开发(十六)自定义绘图控件--圆环

本文记录了使用Java开发Android自定义圆环控件DataBoard的详细过程。文章从嵌入式开发转Android开发的背景出发,详细解析了DataBoard控件的实现要点:1.自定义属性定义与初始化;2.视图测量与布局逻辑;3.核心绘制方法实现(包括圆环、渐变边缘和中心文字);4.交互控制功能实现。通过5个关键代码文件(DataBoard.java、MainActivity.java等)完整展示了控件的开发流程,并提供了效果演示说明。该控件支持自定义尺寸、颜色和数值显示,可作为仪表盘等场景的可视化组件。

ajassi2000的博客 1048

一文解释电路中常用电子元器件应用和选型01

电容和二极管是电路中最常用的两个电子元器件,种类和系列众多,这里简单总结一下应用场景和选型方式 电容(Capacitor) 电容的种类(20种以上)和应用非常广泛,但是我经常遇到的无非就是下面这几种: 应用 1.隔直流。 2.旁路(去耦) 3.耦合 4.滤波 5.温度补偿 6.计时 7.调谐 8.整流 9.储能 种类 1. 陶瓷电容 2. 云母电容 3. 铝电解电容 4. 钽

weixin_43904073的博客 2236

手把手教你用QEMU虚拟化华为路由器固件(附完整网络配置指南)

本文提供了一份详尽的QEMU虚拟化华为路由器固件的实战指南。从固件解包、网络桥接配置到QEMU虚拟机启动,手把手教你搭建完整的嵌入式设备沙箱环境,特别适合安全研究、逆向分析和学习嵌入式系统工作原理。

tgb3456789的博客 937

开源 java android app 开发(十五)自定义绘图控件--仪表盘

本文记录了使用Java开发Android自定义仪表盘控件的学习过程。主要实现了一个可动画化显示数值的DashboardView组件,包含表盘绘制、刻度标记、指针动画等功能。文章详细分析了控件的代码结构,包括属性定义、绘制逻辑、动画实现等核心部分,并提供了完整的源码文件(自定义View、主Activity、布局及资源文件)。该控件支持XML属性配置和动态参数调整,演示了随机数值更新时的平滑指针动画效果。作为嵌入式开发人员临时学习Android开发的实战记录,文章还附有相关技术博客的系列链接。

ajassi2000的博客 1211

融合ZILN损失与深度学习的LTV预测:从概率分布到精准营销决策

本文探讨了融合ZILN损失与深度学习的LTV预测方法,通过深度概率模型精准预测用户生命周期价值(LTV),优化营销决策。文章详细解析了ZILN损失函数的优势、模型架构设计要点及工程化落地实践,帮助企业在预算分配、用户分层等场景实现数据驱动的精准营销。

weixin_29093169的博客 308

开源 java android app 开发(九)后台之线程和服务

本文记录了Java开发Android App学习经历,重点讲解了后台程序的实现。主要内容包括: 线程与通讯 - 使用Thread、Handler和Looper实现后台线程与主线程通信,通过示例展示了进度条更新功能 服务Service - 详细介绍了Service的创建、绑定及使用方法,包括: Service生命周期管理 Activity与Service的绑定通信 后台任务处理机制 文章提供了完整的代码示例,涵盖XML布局文件、Service实现和Activity交互,适合Android初学者学习后台开发技术

ajassi2000的博客 835

海康visionmaster开发笔记10-集成HALCON第三方算子到VM工具箱的方法

海康visionmaster开发笔记10-HALCON第三方算子到VM工具箱的方法

视觉人机器视觉的博客 4347

AMD SDP 优快云 系列文章设计

摘要 本文档规划了7篇AMD SDP协议解析系列文章的技术博客设计方案。基于《Scalable Data Port (SDP) Specification》1.5.0版本(122页),面向SoC/NoC领域的RTL设计与验证工程师,构建从基础概念到验证实现的知识体系。 系列文章严格遵循协议规范边界,采用五类证据标签区分内容来源(规范原文、勘误、验证推导等),禁止补充未定义的实现细节。包含端口控制、事务传输、一致性协议等核心内容,最终落实到验证平台设计与覆盖率模型。 每篇文章采用统一模板:明确规范依据、问题导

元直的博客 160

开源 java android app 开发(七)通讯之Tcp和Http

本文记录了作者从嵌入式软件开发转向Android应用开发的经历,重点介绍了使用Java进行Android开发的学习过程。文章详细描述了开发环境的搭建、工程文件结构、GUI界面布局、文件和数据库存储、多媒体使用等关键步骤,并特别强调了网络通讯协议(如TCP和HTTP)在Android应用中的实际应用。作者通过代码示例展示了如何实现TCP客户端与服务器的通信以及HTTP请求的处理,包括权限配置、界面设计和代码实现。此外,文章还探讨了Android网络安全配置的相关问题,并提供了解决方案。通过这些内容,作者希望记

ajassi2000的博客 1307

(2-4-01)目标检测与分割:地面提取(1)

请看下面的例子,生成了虚拟 LiDAR 点云数据,然后使用 DBSCAN 聚类方法进行 LiDAR 地面提取,并根据聚类结果可视化地面和非地面点云。首先生成虚拟 LiDAR 点云数据,然后基于设定的高程阈值进行地面提取,并通过 Open3D 可视化地面和非地面点云。地面提取是LiDAR(激光雷达)数据处理中的一个重要步骤,其目标是从点云数据中分离出地面点,即地面表面上的点。LiDAR点云数据通常包含地面点和非地面点(例如建筑物、树木等),地面提取的目标是从这些点中准确识别和提取地面点。

码农三叔 699

开源 java android app 开发(八)通讯之Mqtt和Ble

本文记录了作者从嵌入式软件开发转向Android应用开发的学习经历,重点介绍了使用Java进行Android开发的关键步骤和技术要点。文章详细描述了开发环境的搭建、工程文件结构、GUI界面布局、常用组件、文件与数据库存储、多媒体使用等内容。特别强调了MQTT和BLE通讯协议的实现,包括MQTT服务器的连接、消息的发送与接收,以及BLE设备的搜索与连接。通过代码示例和配置说明,作者分享了实际开发中的经验与技巧,旨在帮助读者快速掌握Android应用开发的核心技术。

ajassi2000的博客 1047

借助AI搞定国社科“研究内容”部分!用Gemini 3 Pro轻松撰写6大模块,让基金本子效率质量更上一层楼

所谓研究整体框架,是对课题研究逻辑结构与技术路径的系统阐述,其核心在于通过研究目标分解、研究内容模块化与研究方法整合,构建一个层次清晰、逻辑严密、科学可行的研究体系。撰写要求:1)分要点、分层次列出主要研究内容,保持各部分之间的逻辑性、层次性与系统性;研究可行性可理解为开展本课题在理论基础、研究方法、研究条件、人员结构、平台支撑及外部协作等方面的可实施程度,可从内部条件与外部条件两个维度进行系统论证。研究背景为:我正在申报xxx年xxx(项目名称),课题名称为:xxx,拟采用的主要研究方法包括:xxx等;

智能AI让学术科研更容易 964

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

开源 java android app 开发(四)GUI界面重要组件

本文记录了作者从嵌入式软件开发转向Android应用开发的学习经历,重点介绍了在Java环境下使用Android Studio开发应用时涉及的三个重要GUI组件:TabLayout、RecyclerView和BottomNavigationView。TabLayout用于分页显示,RecyclerView用于列表显示并支持点击事件,BottomNavigationView用于底部导航栏。文章详细描述了每个组件的实现步骤,包括创建布局文件、活动代码、适配器以及如何在主活动中集成这些组件。通过具体的代码示例和操

ajassi2000的博客 911

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

开源 java android app 开发(十一)调试、发布

本文记录了一个嵌入式开发人员转学Android应用开发的经历,主要介绍了使用手机调试和生成APK安装包的方法。调试部分详细说明了如何开启手机开发者选项和USB调试模式。APK生成部分则讲解了编译流程和输出文件位置。文章还列出了完整的Java Android开发学习系列链接,涵盖环境搭建、GUI设计、数据存储、网络通讯等核心内容,并推荐了相关的C#.NET开发教程。这些技术笔记既是对学习过程的总结,也为其他开发者提供了实用参考。

ajassi2000的博客 682

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

开源 java android app 开发(十八)最新编译器Android Studio 2025.1.3.7

本文记录了从嵌入式开发转向Android应用开发的学习过程,重点讲解了最新版Android Studio 2025.1.3.7的使用经验。主要内容包括:1)创建Java工程时的语言选择注意事项;2)新版gradle面板设置及混淆封装aar的方法;3)aar库的使用步骤。文中特别强调了新版IDE的配置变化,如gradle面板默认不显示封装库栏目等问题,并提供了解决方案。文章还列出了作者完整的Android开发学习系列文章链接,涵盖环境搭建、UI设计、数据存储、网络通信等全方位内容。

ajassi2000的博客 4618

开源 java android app 开发(十)广播机制

本文记录了Android应用开发中广播机制的学习过程,主要包含两部分内容:一是两个APP之间的广播通讯实现,通过自定义广播发送方(APP1)和接收方(APP2)展示了Intent发送、BroadcastReceiver注册以及跨进程消息传递;二是基于Service和广播机制的网络文件下载系统,使用OkHttp实现带进度显示的APK下载功能,通过广播更新UI进度条,并处理文件存储权限和下载管理。文中详细提供了XML布局、Java代码实现及权限配置,涵盖了广播注册/注销、服务绑定、线程间通信等关键技术点,最后展

ajassi2000的博客 949

开源 java android app 开发(六)多媒体使用

本文记录了作者从嵌入式软件开发转向Android应用开发的经历,重点介绍了使用Java进行Android应用开发的学习过程。文章详细描述了开发环境的搭建、工程文件结构、GUI界面布局及常用组件的使用,以及文件和数据库存储的实现。此外,文章还涵盖了多媒体功能的使用,包括访问手机相册、播放音频和视频的具体实现步骤。通过代码示例和详细说明,作者旨在帮助读者快速掌握Android应用开发的关键技术,并提供了相关开发资源的链接以供参考。

ajassi2000的博客 1039
上一篇: linux C 语言开发 (十三) 进程间通讯--信号量
下一篇: 开源 java android app 开发(十五)自定义绘图控件--仪表盘
ajassi2000
博客等级 码龄22年 2033粉丝 191原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值