Android自定义示波器如何绘制高采样率图片

Android自定义示波器如何绘制高采样率图片

Android中自定义示波器通过展示高采样率的数据(数据来源串口)

前言

特以此博客记录高采样率波形绘制中遇到的坑,首先Android帧率以及刷新率相关的概念可以自行百度,博主遇到的情况是串口采样率255的情况下,在使用串口数据会出现波形图绘制卡顿,有严重延时,开始一直怀疑是示波器的问题换了N种实现方式还是卡顿,直到最后才找到原因,是串口数据读取的性能瓶颈,读取串口缓冲区数据频率过快会造成比较严重的数据延时

自定义的示波器

/**
 *该示波器满足的需求为X轴向绘制1000点
 *Y轴向绘制1500点
 *同时绘制五路波形数据
 *具体绘制点数可以根据自己需求配置TOTLE_X 与TOTLE_Y轴字段
 */
 public class LineChartSurfaceFourView extends SurfaceView implements SurfaceHolder.Callback {

    private static final String TAG = "LineChartSurfaceView";

    private static final float TOTLE_X = 1000;//X轴描点数
    private static final float TOTLE_Y = 1500;//Y轴描点数

    private float totalWidth = 800;//默认宽度
    private float totalHeight = 190;//默认高度
    private float xValue = totalWidth / TOTLE_X;//X缩放比
    private float yValue;//Y缩放比
    private float density = getResources().getDisplayMetrics().density;//屏幕分辨率
    private int mSpaceHeight = 10;//间隔线高度
    private int oneHeight;//单个示波器高度
    private boolean isCreate = false;//view是否创建
    private static DataFilter dataFilter1 = new DataFilter();//滤波器,不需要滤波器可以直接去掉
    private static DataFilter dataFilter2 = new DataFilter();//滤波器
    private static DataFilter dataFilter3 = new DataFilter();//滤波器
    private static DataFilter dataFilter4 = new DataFilter();//滤波器
    private static DataFilter dataFilter5 = new DataFilter();//滤波器
    /**
     * 曲线画笔
     */
    private Paint linePaint;
    /**
     * 矩形画笔
     */
    private Paint rectPaint;
    /**
     * 路径数据
     */
    private List<PointF> line1Data = Collections.synchronizedList(new ArrayList<>());//示波器1数据源
    private List<PointF> line2Data = Collections.synchronizedList(new ArrayList<>());//示波器2数据源
    private List<PointF> line3Data = Collections.synchronizedList(new ArrayList<>());//示波器3数据源
    private List<PointF> line4Data = Collections.synchronizedList(new ArrayList<>());//示波器4数据源
    private List<PointF> line5Data = Collections.synchronizedList(new ArrayList<>());//示波器5数据源

    /**
     * 构造函数
     *
     * @param context 上下文对象
     */
    public LineChartSurfaceFourView(Context context) {
        this(context, null);
    }

    /**
     * Instantiates a new Line chart surface four view.
     *
     * @param context the context
     * @param attrs   the attrs
     */
    public LineChartSurfaceFourView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView();
        dataFilter1.init();
        dataFilter2.init();
        dataFilter3.init();
        dataFilter4.init();
        dataFilter5.init();
    }

    private Rect rect1 = new Rect();//矩形绘制数据
    private Rect rect2 = new Rect();//矩形绘制数据
    private Rect rect3 = new Rect();//矩形绘制数据
    private Rect rect4 = new Rect();//矩形绘制数据
    private Rect rect5 = new Rect();//矩形绘制数据


    /**
     * 自定义初始化View
     */
    public void initView() {
        // 曲线画笔
        linePaint = new Paint();
        linePaint.setStrokeWidth(1);
        linePaint.setStyle(Style.STROKE);
        linePaint.setAntiAlias(true);
        rectPaint = new Paint();
        rectPaint.setStrokeWidth(1);
        rectPaint.setColor(getResources().getColor(R.color.common_black));
        rectPaint.setStyle(Style.STROKE);
        rectPaint.setAntiAlias(true);
        getHolder().addCallback(this);
    }


    /**
     * The Surface Create callback.
     */
    SurfaceCreateCallback mCallback;

    /**
     * Sets callback.
     *
     * @param callback the callback
     */
    public void setCallback(SurfaceCreateCallback callback) {
        mCallback = callback;
    }

    /**
     * 绘制曲线数据
     */
    private void drawLineData(Canvas canvas) {
        Path path = new Path();
        for (int i = 0; i < line1Data.size(); i++) {
            PointF startPoint = line1Data.get(i);
            if (i == 0) {
                path.moveTo(startPoint.x, startPoint.y);
            } else {
                path.lineTo(startPoint.x, startPoint.y);
            }
        }
        for (int i = 0; i < line2Data.size(); i++) {
            PointF startPoint = line2Data.get(i);
            if (i == 0) {
                path.moveTo(startPoint.x, startPoint.y);
            } else {
                path.lineTo(startPoint.x, startPoint.y);
            }
        }
        for (int i = 0; i < line3Data.size(); i++) {
            PointF startPoint = line3Data.get(i);
            if (i == 0) {
                path.moveTo(startPoint.x, startPoint.y);
            } else {
                path.lineTo(startPoint.x, startPoint.y);
            }
        }
        for (int i = 0; i < line4Data.size(); i++) {
            PointF startPoint = line4Data.get(i);
            if (i == 0) {
                path.moveTo(startPoint.x, startPoint.y);
            } else {
                path.lineTo(startPoint.x, startPoint.y);
            }
        }
        for (int i = 0; i < line5Data.size(); i++) {
            PointF startPoint = line5Data.get(i);
            if (i == 0) {
                path.moveTo(startPoint.x, startPoint.y);
            } else {
                path.lineTo(startPoint.x, startPoint.y);
            }
        }
        canvas.drawPath(path, linePaint);
    }

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

    /**
     * 计算矩形框位置
     * Calc rect.
     */
    public void calcRect() {
        rect1.top = 0;
        rect1.left = 0;
        rect1.right = (int) totalWidth;
        rect1.bottom = oneHeight;

        rect2.top = oneHeight + mSpaceHeight;
        rect2.left = 0;
        rect2.right = (int) totalWidth;
        rect2.bottom = oneHeight * 2 + mSpaceHeight;

        rect3.top = (oneHeight + mSpaceHeight) * 2;
        rect3.left = 0;
        rect3.right = (int) totalWidth;
        rect3.bottom = (oneHeight + mSpaceHeight) * 2 + oneHeight;

        rect4.top = (oneHeight + mSpaceHeight) * 3;
        rect4.left = 0;
        rect4.right = (int) totalWidth;
        rect4.bottom = (oneHeight + mSpaceHeight) * 3 + oneHeight;

        rect5.top = (oneHeight + mSpaceHeight) * 4;
        rect5.left = 0;
        rect5.right = (int) totalWidth;
        rect5.bottom = (oneHeight + mSpaceHeight) * 4 + oneHeight;

    }

    /**
     * Draw rect.
     * 调用该函数可以实现边框,canvas的调用函数就能实现比如填充颜色绘制等,具体自行发挥
     * @param canvas the canvas
     */
    public void drawRect(Canvas canvas) {
        canvas.drawRect(rect1, rectPaint);
        canvas.drawRect(rect2, rectPaint);
        canvas.drawRect(rect3, rectPaint);
        canvas.drawRect(rect4, rectPaint);
        canvas.drawRect(rect5, rectPaint);
    }

    /**
     * 提供给外部调用的单点更新数据
     *
     * @param data the data
     */
    public void updateUi(PulseData data) {
        if (!isCreate) {
            return;
        }
        if (line1Data.size() >= TOTLE_X) {
            line1Data.clear();
            line2Data.clear();
            line3Data.clear();
            line4Data.clear();
            line5Data.clear();
            float y1 = (rect1.top + oneHeight - Math.min(dataFilter1.filter(data.CunShang), TOTLE_Y) * yValue);
            float y2 = (rect2.top + oneHeight - Math.min(dataFilter2.filter(data.Cun), TOTLE_Y) * yValue);
            float y3 = (rect3.top + oneHeight - Math.min(dataFilter3.filter(data.Guan), TOTLE_Y) * yValue);
            float y4 = (rect4.top + oneHeight - Math.min(dataFilter4.filter(data.Chi), TOTLE_Y) * yValue);
            float y5 = (rect5.top + oneHeight - Math.min(dataFilter5.filter(data.ChiXia), TOTLE_Y) * yValue);
            line1Data.add(new PointF(0, y1));
            line2Data.add(new PointF(0, y2));
            line3Data.add(new PointF(0, y3));
            line4Data.add(new PointF(0, y4));
            line5Data.add(new PointF(0, y5));
        } else {
            float y1 = (rect1.top + oneHeight - Math.min(dataFilter1.filter(data.CunShang), TOTLE_Y) * yValue);
            float y2 = (rect2.top + oneHeight - Math.min(dataFilter2.filter(data.Cun), TOTLE_Y) * yValue);
            float y3 = (rect3.top + oneHeight - Math.min(dataFilter3.filter(data.Guan), TOTLE_Y) * yValue);
            float y4 = (rect4.top + oneHeight - Math.min(dataFilter4.filter(data.Chi), TOTLE_Y) * yValue);
            float y5 = (rect5.top + oneHeight - Math.min(dataFilter5.filter(data.ChiXia), TOTLE_Y) * yValue);
            float x1 = line1Data.size() * xValue;
            line1Data.add(new PointF(x1, y1));
            line2Data.add(new PointF(x1, y2));
            line3Data.add(new PointF(x1, y3));
            line4Data.add(new PointF(x1, y4));
            line5Data.add(new PointF(x1, y5));
        }
        reDraw();
    }

    /**
     * 提供给外部调用的多点更新数据
     *
     * @param datas the datas
     */
    public void updateUi(List<PulseData> datas) {
        if (!isCreate) {
            return;
        }
        if (line1Data.size() >= TOTLE_X) {
            line1Data.clear();
            line2Data.clear();
            line3Data.clear();
            line4Data.clear();
            line5Data.clear();
            updateUi(datas);
        } else {
            for (int i = 0; i < datas.size(); i++) {
                PulseData data = datas.get(i);
                float y1 = (rect1.top + oneHeight - Math.min(dataFilter1.filter(data.CunShang), TOTLE_Y) * yValue);
                float y2 = (rect2.top + oneHeight - Math.min(dataFilter2.filter(data.Cun), TOTLE_Y) * yValue);
                float y3 = (rect3.top + oneHeight - Math.min(dataFilter3.filter(data.Guan), TOTLE_Y) * yValue);
                float y4 = (rect4.top + oneHeight - Math.min(dataFilter4.filter(data.Chi), TOTLE_Y) * yValue);
                float y5 = (rect5.top + oneHeight - Math.min(dataFilter5.filter(data.ChiXia), TOTLE_Y) * yValue);
                float x1 = line1Data.size() * xValue;
                line1Data.add(new PointF(x1, y1));
                line2Data.add(new PointF(x1, y2));
                line3Data.add(new PointF(x1, y3));
                line4Data.add(new PointF(x1, y4));
                line5Data.add(new PointF(x1, y5));
            }
        }
        reDraw();
    }


    /**
     * 重绘画布
     */
    private void reDraw() {
        Canvas canvas = getHolder().lockCanvas();
        try {
            if (canvas != null) {
                canvas.drawColor(Color.parseColor("#F2F2F2"));
                drawLineData(canvas);
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                getHolder().unlockCanvasAndPost(canvas);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }


    /**
     * 清空绘制曲线
     */
    public void cleanDrawLine() {
        line1Data.clear();
        line2Data.clear();
        line3Data.clear();
        line4Data.clear();
        line5Data.clear();
        postInvalidate();
    }

    /**
     * view的大小测量
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(measureWidth(widthMeasureSpec),
                measureHeight(heightMeasureSpec));

    }

    /**
     * view的高度测量
     */
    private int measureHeight(int measureSpec) {
        int result;
        int mode = MeasureSpec.getMode(measureSpec);
        int size = MeasureSpec.getSize(measureSpec);

        if (mode == MeasureSpec.EXACTLY) {
            result = size;
        } else {
            result = 75;
            if (mode == MeasureSpec.AT_MOST) {
                result = Math.min(result, size);
            }
        }
        totalHeight = result;
        int totalSpaceHeight = mSpaceHeight * 4;
        oneHeight = (int) ((totalHeight - totalSpaceHeight) / 5);
        yValue = ((float) oneHeight) / TOTLE_Y;
        calcRect();
        return size;
    }

    /**
     * view的宽度测量
     */
    private int measureWidth(int measureSpec) {
        int result;
        int mode = MeasureSpec.getMode(measureSpec);
        int size = MeasureSpec.getSize(measureSpec);

        if (mode == MeasureSpec.EXACTLY) {
            result = size;
        } else {
            result = 75;//根据自己的需要更改
            if (mode == MeasureSpec.AT_MOST) {
                result = Math.min(result, size);
            }
        }
        totalWidth = result;
        xValue = totalWidth / TOTLE_X;
        Log.e("wl", "总高:" + totalHeight + "xValue:" + xValue);
        return result;

    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        Canvas canvas = holder.lockCanvas();
        canvas.drawColor(Color.parseColor("#F2F2F2"));
        holder.unlockCanvasAndPost(canvas);
        if (mCallback != null)
            mCallback.surfaceCreated(holder);
        isCreate = true;
    }

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

    }

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

    /**
     * The interface Surface create callback.
     */
    public interface SurfaceCreateCallback {
        /**
         * Surface created.
         *
         * @param holder the holder
         */
        void surfaceCreated(SurfaceHolder holder);
    }
}

代码实际比较简单,博主拿手机测试实际1S绘制数据完全能达到1000点。
关于结合串口后卡顿的问题,涉及项目底层通讯的原因无法贴出代码,但是问题点就是拿到串口inputstream后,一般是开线程去循环读取,如果单次读取buff比较小,而下位机抛数据速度比你读取速度快的话,就会造成数据延时,具体的表现就是绘制数据比实际数据会延时几秒,对于这个问题,博主是采取单次加大读取数据,加大读取间隔,目前通过该方式,在下位机1S255次数据返回的情况下,示波器没有延时,更高采样率没有试验,具体可以根据自己需求去探索

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值