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次數據返回的情況下,示波器沒有延時,更高採樣率沒有試驗,具體可以根據自己需求去探索

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章