如图:
实现步骤:
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 - 1){
mPaint.setColor(Color.RED);
}else{
mPaint.setColor(Color.LTGRAY);
}
mCanvas.drawLine(hStep * i, 0, hStep * i, height,