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"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="10dp">

        <com.example.myapplication333.OscDrawViewQueue
            android:id="@+id/oscview"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>

        <Button
            android:id="@+id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="停止"/>

    </RelativeLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

MainActivity.java

package com.example.myapplication333;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.View;
import android.widget.Button;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    private Button m_btnStop;
    private OscDrawViewQueue m_oscDrawView;
    private boolean m_bEndThread = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findView();
        init();
    }

    private void findView() {
        m_oscDrawView = this.findViewById(R.id.oscview);
        m_btnStop = this.findViewById(R.id.button);
    }

    private void init() {
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                initOsc();
            }
        }, 3000);
    }

    private void initOsc() {
        //设置上下限
        try {
            m_oscDrawView.setLinesLimit(1000, 0, 400, 0);
            m_oscDrawView.setXaxisLength(10000);
            m_oscDrawView.startOsc();
        } catch (Exception e) {
            e.printStackTrace();
        }

        m_btnStop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                m_oscDrawView.stopOsc();
            }
        });

        //填充数据
        new Thread(new Runnable() {
            @Override
            public void run() {
                int x = 0;
                while(!m_bEndThread){
                    for (int i = 0; i < 8; i++) {
                        float y = (int)(100 * Math.sin(2 * (++x) * Math.PI / 180) + 400);
                        m_oscDrawView.setLine1Data(y);
                        m_oscDrawView.setLine2Data(y);
                    }
                    try {
                        Thread.sleep(4);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        m_bEndThread = true;
    }

}

项目清单文件AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.myapplication333">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity"
            android:screenOrientation="landscape">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

波形封装文件(V1-V7,逐渐优化):

V1、OscDrawView.java

package com.example.myapplication333;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

import java.util.LinkedList;
import java.util.Queue;

/**
 * 波形绘制view
 */
public class OscDrawView extends SurfaceView implements SurfaceHolder.Callback, Runnable {

    //控制句柄
    private SurfaceHolder mSurfaceHolder;
    //绘图的Canvas
    private Canvas mCanvas;
    //子线程标志位
    private boolean mIsDrawing;
    //画笔
    private Paint mPaint;
    //通道一路径
    private Path mLinePath1;
    //通道二路径
    private Path mLinePath2;
    //X轴、时间轴跨度,默认50ms
    private int m_iXaxisLength;
    //X轴、时间轴步长,点与点之间的像素
    private float m_fXaxisStep;
    //示波通道数据长度
    private int m_iLineDatasLength;
    //通道一数据
    private LinkedList<Float> mLineDatas1 = new LinkedList<>();
    //通道二数据
    private LinkedList<Float> mLineDatas2 = new LinkedList<>();
    //通道一缓存数据
    private LinkedList<Float> mLineBuffer1 = new LinkedList<>();
    //通道二缓存数据
    private LinkedList<Float> mLineBuffer2 = new LinkedList<>();
    //通道一波形上限、下限/通道二上下限
    private int[] miArrLineLimit = new int[OSC_CHANNEL_MAX_NUM * 2];
    //通道一/通道二的Y轴步长
    private float[] m_fArrYaxisSteps = new float[OSC_CHANNEL_MAX_NUM];
    //网格线个数
    private final static int GRID_LINE_COUNTS = 5;
    //示波通道最大个数,最大为2个
    private final static int OSC_CHANNEL_MAX_NUM = 2;
    //数据采样频率,1ms采样点数,2个点/1ms
    private final static int OSC_SAMPLE_RATE = 2;
    //缓存数据时间长度,单位min,默认3min
    private final static int OSC_BUFFER_TIME_LENGTH = 3;
    //单通道波形缓存最大数据量(3min*60*1000ms*2个点)
    private final static int OSC_BUFFER_DATA_LENGTH = OSC_BUFFER_TIME_LENGTH * 60 * 1000 * OSC_SAMPLE_RATE;

    public OscDrawView(Context context) {
        this(context, null);
    }

    public OscDrawView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public OscDrawView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView();
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        mIsDrawing = true;
        //开启绘制子线程
        new Thread(this).start();
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        mIsDrawing = false;
    }

    @Override
    public void run() {
        while (mIsDrawing){
            long start = System.currentTimeMillis();
            drawChart();
            long end = System.currentTimeMillis();
            if (end - start < 200) {
                try {
                    Thread.sleep(200 - (end - start));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            Log.e("draw: ", "end-start: " + (end - start));
        }
    }

    /**
     * 初始化View
     */
    private void initView(){
        mSurfaceHolder = getHolder();
        mSurfaceHolder.addCallback(this);
        setFocusable(true);
        setKeepScreenOn(true);
        setFocusableInTouchMode(true);
        //初始化画笔
        mPaint = new Paint();
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setAntiAlias(true);
        mPaint.setStrokeWidth(3);
        //波形路径
        mLinePath1 = new Path();
        mLinePath2 = new Path();
    }

    /**
     * 绘图逻辑
     */
    private void drawChart() {
        try {
            //获得canvas对象
            mCanvas = mSurfaceHolder.lockCanvas();
            //绘制背景
            drawBackground();
            //绘制波形
            drawOscLines();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if (mCanvas != null){
                //释放canvas对象并提交画布
                mSurfaceHolder.unlockCanvasAndPost(mCanvas);
            }
        }
    }

    /**
     * 绘制背景
     */
    private void drawBackground(){
        int width = getWidth();
        int height = getHeight();
        int vStep = (int)(height / (GRID_LINE_COUNTS - 1.0));
        int hStep = (int)(width / (GRID_LINE_COUNTS - 1.0));
        //绘制白色背景
        mCanvas.drawColor(Color.WHITE);
        //绘制横向网格线
        for (int i = 0; i < GRID_LINE_COUNTS; i++) {
            if(i == 0 || i == GRID_LINE_COUNTS - 1){
                mPaint.setColor(Color.BLACK);
            }else{
                mPaint.setColor(Color.LTGRAY);
            }
            mCanvas.drawLine(0, vStep * i, width, vStep * i, mPaint);
        }
        //绘制纵向网格线
        for (int i = 0; i < GRID_LINE_COUNTS; i++) {
            if(i == 0){
                mPaint.setColor(Color.BLUE);
            }else if(i == GRID_LINE_COUNTS - 1){
                mPaint.setColor(Color.RED);
            }else{
                mPaint.setColor(Color.LTGRAY);
            }
            mCanvas.drawLine(hStep * i, 0, hStep * i, height, mPaint);
        }
    }

    /**
     * 绘图波形
     */
    private void drawOscLines(){
        long start = System.currentTimeMillis();

        mLinePath1.reset();
        mLinePath2.reset();
        LinkedList<Float> data1 = (LinkedList<Float>) mLineDatas1.clone();
        LinkedList<Float> data2 = (LinkedList<Float>) mLineDatas2.clone();
        int length1 = data1.size();
        int length2 = data2.size();
        if(length2 > 0){
            for (int i = 0; i < length1; i++) {
                //时间轴
                float x = getWidth() - (m_fXaxisStep * i);
                //通道赋值
                if(i == 0){
                    //通道一
                    mLinePath1.moveTo(x, (miArrLineLimit[0] - data1.poll()) * m_fArrYaxisSteps[0]);
                    //通道二
                    mLinePath2.moveTo(x, (miArrLineLimit[2] - data2.poll()) * m_fArrYaxisSteps[1]);
                }else{
                    //通道一
                    mLinePath1.lineTo(x, (miArrLineLimit[0] - data1.poll()) * m_fArrYaxisSteps[0]);
                    //通道二
                    mLinePath2.lineTo(x, (miArrLineLimit[2] - data2.poll()) * m_fArrYaxisSteps[1]);
                }
            }
        }else{
            for (int i = 0; i < length1; i++) {
                //时间轴
                float x = getWidth() - (m_fXaxisStep * i);
                //通道赋值
                if(i == 0){
                    //通道一
                    mLinePath1.moveTo(x, (miArrLineLimit[0] - data1.poll()) * m_fArrYaxisSteps[0]);
                }else{
                    //通道一
                    mLinePath1.lineTo(x, (miArrLineLimit[0] - data1.poll()) * m_fArrYaxisSteps[0]);
                }
            }
        }
        //绘制路径
        //通道一
        mPaint.setColor(Color.BLUE);
        mCanvas.drawPath(mLinePath1, mPaint);
        //通道二
        mPaint.setColor(Color.RED);
        mCanvas.drawPath(mLinePath2, mPaint);

        long end = System.currentTimeMillis();
        Log.e("drawline: ", "end-start: " + (end - start));
    }

    /**
     * 设置波形上下限
     * @param line_1_max 通道一上限
     * @param line_1_min 通道一下限
     * @param line_2_max 通道二上限
     * @param line_2_min 通道二下限
     */
    public void setLinesLimit(int line_1_max, int line_1_min, int line_2_max, int line_2_min) throws Exception {
        if(line_1_max - line_1_min <= 0 || line_2_max - line_2_min <= 0){
            throw new Exception("line limit set error value");
        }
        miArrLineLimit[0] = line_1_max;
        miArrLineLimit[1] = line_1_min;
        miArrLineLimit[2] = line_2_max;
        miArrLineLimit[3] = line_2_min;
        //计算Y轴点与像素的对应关系
        m_fArrYaxisSteps[0] = getHeight() / ((float)(line_1_max - line_1_min));
        m_fArrYaxisSteps[1] = getHeight() / ((float)(line_2_max - line_2_min));
    }

    /**
     * 设置时间轴跨度,单位ms
     * @param millisecond
     */
    public void setXaxisLength(int millisecond){
        if(millisecond < 50){
            m_iXaxisLength = 50;
        }else if(millisecond > 5*1000){
            m_iXaxisLength = 5*1000;
        }else{
            m_iXaxisLength = millisecond;
        }
        m_iLineDatasLength = m_iXaxisLength * OSC_SAMPLE_RATE;
        //计算X轴点与点之间像素跨度
        m_fXaxisStep = getWidth() / ((float) m_iLineDatasLength);

        Log.e("m_fXaxisStep: ", getWidth() + " / " + m_iLineDatasLength + " = " + m_fXaxisStep);
    }

    /**
     * 写入通道一数据
     * @param value
     */
    public synchronized void setLine1Data(Float value){
        mLineBuffer1.addFirst(value);
        if(mLineBuffer1.size() > OSC_BUFFER_DATA_LENGTH){
            mLineBuffer1.removeLast();
        }
        mLineDatas1.addFirst(value);
        for (int i = mLineDatas1.size(); i > m_iLineDatasLength; i--) {
            mLineDatas1.removeLast();
        }
    }

    /**
     * 写入通道二数据
     * @param value
     */
    public synchronized void setLine2Data(Float value){
        mLineBuffer2.addFirst(value);
        if(mLineBuffer2.size() > OSC_BUFFER_DATA_LENGTH){
            mLineBuffer2.removeLast();
        }
        mLineDatas2.addFirst(value);
        for (int i = mLineDatas2.size(); i > m_iLineDatasLength; i--) {
            mLineDatas2.removeLast();
        }
    }

    /**
     * 开始示波
     */
    public void startOsc(){
        mLineDatas1.clear();
        mLineBuffer1.clear();
        mLineDatas2.clear();
        mLineBuffer2.clear();
        //发送示波命令
    }

    /**
     * 停止示波
     */
    public void stopOsc(){
        //发送停止示波命令
    }

    /**
     * 定义操作接口
     */
    public interface OscHandler{
        void startOsc();
        void stopOsc();
    }


}

运行时,链表克隆报错,LinkedList删除元素时要用迭代。

V2、OscDrawViewQueue.java

package com.example.myapplication333;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.Queue;

/**
 * 波形绘制view
 */
public class OscDrawViewQueue extends SurfaceView implements SurfaceHolder.Callback, Runnable {

    //控制句柄
    private SurfaceHolder mSurfaceHolder;
    //绘图的Canvas
    private Canvas mCanvas;
    //子线程标志位
    private boolean mIsDrawing;
    //画笔
    private Paint mPaint;
    //通道一路径
    private Path mLinePath1;
    //通道二路径
    private Path mLinePath2;
    //X轴、时间轴跨度,默认50ms
    private int m_iXaxisLength;
    //X轴、时间轴步长,点与点之间的像素
    private float m_fXaxisStep;
    //示波通道数据长度
    private int m_iLineDatasLength;
    //示波中、停止示波
    private boolean m_bOscTaskDoing;
    //通道一数据
    private Queue<Float> mLineDatas1 = new LinkedList<>();
    //通道二数据
    private Queue<Float> mLineDatas2 = new LinkedList<>();
    //通道一屏幕数据
    private Queue<Float> mScreenDatas1 = new LinkedList<>();
    //通道二屏幕数据
    private Queue<Float> mScreenDatas2 = new LinkedList<>();
    //通道一缓存数据
    private Queue<Float> mLineBuffer1 = new LinkedList<>();
    //通道二缓存数据
    private Queue<Float> mLineBuffer2 = new LinkedList<>();
    //通道一波形上限、下限/通道二上下限
    private int[] miArrLineLimit = new int[OSC_CHANNEL_MAX_NUM * 2];
    //通道一/通道二的Y轴步长
    private float[] m_fArrYaxisSteps = new float[OSC_CHANNEL_MAX_NUM];
    //网格线个数
    private final static int GRID_LINE_COUNTS = 5;
    //屏幕刷新频率,毫秒,默认100ms
    private final static int SCREEN_REFRESH_RATE = 95;
    //示波通道最大个数,最大为2个
    private final static int OSC_CHANNEL_MAX_NUM = 2;
    //数据采样频率,1ms采样点数,2个点/1ms
    private final static int OSC_SAMPLE_RATE = 2;
    //缓存数据时间长度,单位min,默认3min
    private final static int OSC_BUFFER_TIME_LENGTH = 3;
    //单通道波形缓存最大数据量(3min*60*1000ms*2个点)
    private final static int OSC_BUFFER_DATA_LENGTH = OSC_BUFFER_TIME_LENGTH * 60 * 1000 * OSC_SAMPLE_RATE;
    //屏幕刷新时更新的数据长度
    private final static int SCREEN_REFRESH_NEW_DATA_LENGTH = SCREEN_REFRESH_RATE * OSC_SAMPLE_RATE;

    public OscDrawViewQueue(Context context) {
        this(context, null);
    }

    public OscDrawViewQueue(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public OscDrawViewQueue(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView();
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        mIsDrawing = true;
        //开启绘制子线程
        new Thread(this).start();
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        mIsDrawing = false;
    }

    @Override
    public void run() {
        while (mIsDrawing){
            long start = System.currentTimeMillis();
            drawChart();
            long end = System.currentTimeMillis();
            if (end - start < SCREEN_REFRESH_RATE) {
                try {
                    Thread.sleep(SCREEN_REFRESH_RATE - (end - start));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            Log.e("draw: ", "end-start: " + (end - start));
        }
    }

    /**
     * 初始化View
     */
    private void initView(){
        mSurfaceHolder = getHolder();
        mSurfaceHolder.addCallback(this);
        setFocusable(true);
        setKeepScreenOn(true);
        setFocusableInTouchMode(true);
        //初始化画笔
        mPaint = new Paint();
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setAntiAlias(true);
        mPaint.setStrokeWidth(3);
        //波形路径
        mLinePath1 = new Path();
        mLinePath2 = new Path();
    }

    /**
     * 绘图逻辑
     */
    private void drawChart() {
        try {
            //获得canvas对象
            mCanvas = mSurfaceHolder.lockCanvas();
            //绘制背景
            drawBackground();
            //绘制波形
            if(m_bOscTaskDoing){
                //绘制实时波形
                drawOscLines();
            }else{
                //绘制历史波形
                drawHistory();
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if (mCanvas != null){
                //释放canvas对象并提交画布
                mSurfaceHolder.unlockCanvasAndPost(mCanvas);
            }
        }
    }

    /**
     * 绘制背景
     */
    private void drawBackground(){
        int width = getWidth();
        int height = getHeight();
        int vStep = (int)(height / (GRID_LINE_COUNTS - 1.0));
        int hStep = (int)(width / (GRID_LINE_COUNTS - 1.0));
        //绘制白色背景
        mCanvas.drawColor(Color.WHITE);
        //绘制横向网格线
        for (int i = 0; i < GRID_LINE_COUNTS; i++) {
            if(i == 0 || i == GRID_LINE_COUNTS - 1){
                mPaint.setColor(Color.BLACK);
            }else{
                mPaint.setColor(Color.LTGRAY);
            }
            mCanvas.drawLine(0, vStep * i, width, vStep * i, mPaint);
        }
        //绘制纵向网格线
        for (int i = 0; i < GRID_LINE_COUNTS; i++) {
            if(i == 0){
                mPaint.setColor(Color.BLUE);
            }else if(i == GRID_LINE_COUNTS -
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值