2011-10-09 50 views
6

[UPDATE] 爲了總結這個問題,我使用以下兩種方法實現了我的圖形(請參閱下文)。 drawCurve()收到Canvasfloat的數組。該數組已被正確填充(時間戳由數組中的值索引承擔)並從0.0到1.0變化。該陣列被髮送到prepareWindowArray(),其以循環方式從位置windowStart獲取windowSize值的數組的塊。Android中的自定義動態圖形

GraphView和數據提供者(藍牙設備)使用的數組是相同的。中間的類確保GraphView不讀取藍牙設備正在寫入的數據。由於GraphView始終通過陣列循環並在每次迭代時重新繪製,它將根據藍牙設備寫入的數據進行更新,並且通過強制藍牙設備的寫入頻率達到Graph的刷新頻率,我獲得了平滑我的信號動畫。

GraphViewinvalidate()方法由Activity,其運行一個Timer刷新圖形在每個x毫秒調用。圖形刷新的頻率是動態設置的,以便它適應來自藍牙設備的數據流(指定其信息包頭中的信號頻率)。

在我在下面寫的答案(答案部分)中找到我的GraphView的完整代碼。如果你們發現錯誤或優化方法,請告訴我;這將不勝感激!

/** 
* Read a buffer array of size greater than "windowSize" and create a window array out of it. 
* A curve is then drawn from this array using "windowSize" points, from left 
* to right. 
* @param canvas is a Canvas object on which the curve will be drawn. Ensure the canvas is the 
* later drawn object at its position or you will not see your curve. 
* @param data is a float array of length > windowSize. The floats must range between 0.0 and 1.0. 
* A value of 0.0 will be drawn at the bottom of the graph, while a value of 1.0 will be drawn at 
* the top of the graph. The range is not tested, so you must ensure to pass proper values, or your 
* graph will look terrible. 
*  0.0 : draw at the bottom of the graph 
*  0.5 : draw in the middle of the graph 
*  1.0 : draw at the top of the graph 
*/ 
private void drawCurve(Canvas canvas, float[] data){ 

    // Create a reference value to determine the stepping between each points to be drawn 
    float incrementX = (mRightSide-mLeftSide)/(float) windowSize; 

    float incrementY = (mBottomSide - mTopSide); 

    // Prepare the array for the graph 
    float[] source = prepareWindowArray(data); 

    // Prepare the curve Path 
    curve = new Path(); 
    // Move at the first point. 
    curve.moveTo(mLeftSide, source[0]*incrementY); 
    // Draw the remaining points of the curve 
    for(int i = 1; i < windowSize; i++){ 
     curve.lineTo(mLeftSide + (i*incrementX), source[i] * incrementY); 
    } 

    canvas.drawPath(curve, curvePaint); 

} 

prepareWindowArray()方法實現陣列的圓形行爲:

/** 
* Extract a window array from the data array, and reposition the windowStart 
* index for next iteration 
* @param data the array of data from which we get the window 
* @return an array of float that represent the window 
*/ 
private float[] prepareWindowArray(float[] data){ 
    // Prepare the source array for the graph. 
    float[] source = new float[windowSize]; 

    // Copy the window from the data array into the source array 
    for(int i = 0; i < windowSize; i++){ 
     if(windowStart+i < data.length)       // If the windows holds within the data array 
      source[i] = data[windowStart + i];     // Simply copy the value in the source array 
     else{             // If the window goes beyond the data array 
      source[i] = data[(windowStart + 1)%data.length]; // Loop at the beginning of the data array and copy from there 
     } 
    } 
    // Reposition the buffer index 
    windowStart = windowStart + windowSize; 
    // If the index is beyond the end of the array 
    if(windowStart >= data.length){ 
     windowStart = windowStart % data.length; 
    } 

    return source; 
} 

[/ UPDATE]

我正在一個應用程序,在從藍牙設備讀取的數據固定利率。每當我有新的數據時,我都希望它們被繪製在右側的圖上,並將圖的其餘部分實時轉換到左側。基本上,就像示波器一樣。

所以我用xy軸做了一個自定義視圖,標題和單位。要做到這一點,我只需在視圖畫布上繪製這些東西。現在我想繪製曲線。我設法使用這種方法從已填充的陣列中繪製出一條靜態曲線:

public void drawCurve(Canvas canvas){ 

    int left = getPaddingLeft(); 
    int bottom = getHeight()-getPaddingTop(); 
    int middle = (bottom-10)/2 - 10; 

    curvePaint = new Paint(); 
    curvePaint.setColor(Color.GREEN); 
    curvePaint.setStrokeWidth(1f); 
    curvePaint.setDither(true); 
    curvePaint.setStyle(Paint.Style.STROKE); 
    curvePaint.setStrokeJoin(Paint.Join.ROUND); 
    curvePaint.setStrokeCap(Paint.Cap.ROUND); 
    curvePaint.setPathEffect(new CornerPathEffect(10)); 
    curvePaint.setAntiAlias(true); 

    mCurve = new Path(); 
    mCurve.moveTo(left, middle); 
    for(int i = 0; i < mData[0].length; i++) 
     mCurve.lineTo(left + ((float)mData[0][i] * 5), middle-((float)mData[1][i] * 20)); 


    canvas.drawPath(mCurve, curvePaint); 
} 

它給了我類似這樣的東西。

My custom GraphView

還有事情要解決我的圖形(副軸不能正常縮放),但這些都是細節,我可以稍後再修正。

現在我想要改變這個靜態圖(它接收一個非動態矩陣的值),並且每隔40ms重新繪製一條曲線,將舊數據向左推並將新數據繪製到右側,所以我可以實時查看藍牙設備提供的信息。

我知道有一些圖形包已經存在了,但我對這些東西有點小事,我想通過自己實現這個圖來實踐。此外,我的大部分GraphView類都已完成,曲線部分除外。

第二個問題,我想知道我應該如何將新值發送給圖。我應該使用像FIFO堆棧這樣的東西,還是可以用簡單的雙倍矩陣來實現我想要的功能?

在旁註上,底部的4個字段已經動態更新。那麼,他們有點僞裝「動態」,他們一次又一次地通過同一個雙重矩陣,他們實際上並沒有採取新的價值。

謝謝你的時間!如果有關於我的問題的內容不清楚,請告訴我,我會通過更多細節進行更新。

+0

你能給所有項目鏈接嗎?這看起來很有趣! –

回答

7

正如我的問題中提到的,這裏是我設計的課程來解決我的問題。

/** 
* A View implementation that displays a scatter graph with 
* automatic unit scaling. 
* 
* Call the <i>setupGraph()</i> method to modify the graph's 
* properties. 
* @author Antoine Grondin 
* 
*/ 

public class GraphView extends View { 

    ////////////////////////////////////////////////////////////////// 
    // Configuration 
    ////////////////////////////////////////////////////////////////// 

    // Set to true to impose the graph properties 
    private static final boolean TEST = false; 

    // Scale configuration 
    private float minX = 0;   // When TEST is true, these values are used to 
    private float maxX = 50;  // Draw the graph 
    private float minY = 0; 
    private float maxY = 100; 

    private String titleText = "A Graph..."; 
    private String xUnitText = "s"; 
    private String yUnitText = "Volts"; 

    // Debugging variables 
    private boolean D = true; 
    private String TAG = "GraphView"; 

    ////////////////////////////////////////////////////////////////// 
    // Member fields 
    ////////////////////////////////////////////////////////////////// 

    // Represent the borders of the View 
    private int mTopSide = 0; 
    private int mLeftSide = 0; 
    private int mRightSide = 0; 
    private int mBottomSide = 0; 
    private int mMiddleX = 0; 
    // Size of a DensityIndependentPixel 
    private float mDips = 0; 

    // Hold the position of the axis in regard to the range of values 
    private int positionOfX = 0; 
    private int positionOfY = 0; 

    // Index for the graph array window, and size of the window 
    private int windowStart = 0; 
    private int windowSize = 128; 
    private float[] dataSource; 

    // Painting tools 
    private Paint xAxisPaint; 
    private Paint yAxisPaint; 
    private Paint tickPaint; 
    private Paint curvePaint; 
    private Paint backgroundPaint; 

    private TextPaint unitTextPaint; 
    private TextPaint titleTextPaint; 

    // Object to be drawn 

    private Path curve; 
    private Bitmap background; 

    /////////////////////////////////////////////////////////////////////////////// 
    // Constructors 
    /////////////////////////////////////////////////////////////////////////////// 

    public GraphView(Context context) { 
     super(context); 
     init(); 
    } 

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

    public GraphView(Context context, AttributeSet attrs, int defStyle){ 
     super(context, attrs, defStyle); 
     init(); 
    } 

    /////////////////////////////////////////////////////////////////////////////// 
    // Configuration methods 
    /////////////////////////////////////////////////////////////////////////////// 

    public void setupGraph(String title, String nameOfX, float min_X, float max_X, String nameOfY, float min_Y, float max_Y){ 
     if(!TEST){ 
      titleText = title; 
      xUnitText = nameOfX; 
      yUnitText = nameOfY; 
      minX = min_X; 
      maxX = max_X; 
      minY = min_Y; 
      maxY = max_Y; 
     } 
    } 

    /** 
    * Set the array this GraphView is to work with. 
    * @param data is a float array of length > windowSize. The floats must range between 0.0 and 1.0. 
    * A value of 0.0 will be drawn at the bottom of the graph, while a value of 1.0 will be drawn at 
    * the top of the graph. The range is not tested, so you must ensure to pass proper values, or your 
    * graph will look terrible. 
    *  0.0 : draw at the bottom of the graph 
    *  0.5 : draw in the middle of the graph 
    *  1.0 : draw at the top of the graph 
    */ 
    public void setDataSource(float[] data){ 
     this.dataSource = data; 
    } 

    /////////////////////////////////////////////////////////////////////////////// 
    // Initialization methods 
    /////////////////////////////////////////////////////////////////////////////// 

    private void init(){ 
     initDrawingTools(); 
    } 

    private void initConstants(){ 
     mDips = getResources().getDisplayMetrics().density; 
     mTopSide = (int) (getTop() + 10*mDips); 
     mLeftSide = (int) (getLeft() + 10*mDips); 
     mRightSide = (int) (getMeasuredWidth() - 10*mDips); 
     mBottomSide = (int) (getMeasuredHeight() - 10*mDips); 
     mMiddleX = (mRightSide - mLeftSide)/2 + mLeftSide; 
    } 

    private void initWindowSetting() throws IllegalArgumentException { 

     // Don't do anything if the given values make no sense 
     if(maxX < minX || maxY < minY || 
       maxX == minX || maxY == minY){ 
      throw new IllegalArgumentException("Max and min values make no sense"); 
     } 
     // Transform the values in scanable items 
     float[][] maxAndMin = new float[][]{ 
       {minX, maxX}, 
       {minY, maxY}}; 
     int[] positions = new int[]{positionOfY, positionOfX}; 

     // Place the X and Y axis in regard to the given max and min 
     for(int i = 0; i<2; i++){ 
      if(maxAndMin[i][0] < 0f){ 
       if(maxAndMin[i][1] < 0f){ 
        positions[i] = (int) maxAndMin[i][0]; 
       } else{ 
        positions[i] = 0; 
       } 
      } else if (maxAndMin[i][0] > 0f){ 
       positions[i] = (int) maxAndMin[i][0]; 
      } else { 
       positions[i] = 0; 
      } 
     } 

     // Put the values back in their right place 
     minX = maxAndMin[0][0]; 
     maxX = maxAndMin[0][1]; 
     minY = maxAndMin[1][0]; 
     maxY = maxAndMin[1][1]; 

     positionOfY = mLeftSide + (int) (((positions[0] - minX)/(maxX-minX))*(mRightSide - mLeftSide));  
     positionOfX = mBottomSide - (int) (((positions[1] - minY)/(maxY-minY))*(mBottomSide - mTopSide)); 
    } 

    private void initDrawingTools(){ 

     xAxisPaint = new Paint(); 
     xAxisPaint.setColor(0xff888888); 
     xAxisPaint.setStrokeWidth(1f*mDips); 
     xAxisPaint.setAlpha(0xff); 
     xAxisPaint.setAntiAlias(true); 

     yAxisPaint = xAxisPaint; 

     tickPaint = xAxisPaint; 
     tickPaint.setColor(0xffaaaaaa); 

     curvePaint = new Paint(); 
     curvePaint.setColor(0xff00ff00); 
     curvePaint.setStrokeWidth(1f*mDips); 
     curvePaint.setDither(true); 
     curvePaint.setStyle(Paint.Style.STROKE); 
     curvePaint.setStrokeJoin(Paint.Join.ROUND); 
     curvePaint.setStrokeCap(Paint.Cap.ROUND); 
     curvePaint.setPathEffect(new CornerPathEffect(10)); 
     curvePaint.setAntiAlias(true); 

     backgroundPaint = new Paint(); 
     backgroundPaint.setFilterBitmap(true); 

     titleTextPaint = new TextPaint(); 
     titleTextPaint.setAntiAlias(true); 
     titleTextPaint.setColor(0xffffffff); 
     titleTextPaint.setTextAlign(Align.CENTER); 
     titleTextPaint.setTextSize(20f*mDips); 
     titleTextPaint.setTypeface(Typeface.MONOSPACE); 

     unitTextPaint = new TextPaint(); 
     unitTextPaint.setAntiAlias(true); 
     unitTextPaint.setColor(0xff888888); 
     unitTextPaint.setTextAlign(Align.CENTER); 
     unitTextPaint.setTextSize(20f*mDips); 
     unitTextPaint.setTypeface(Typeface.MONOSPACE); 

    } 

    /////////////////////////////////////////////////////////////////////////////// 
    // Overridden methods 
    /////////////////////////////////////////////////////////////////////////////// 

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){ 
     super.onMeasure(widthMeasureSpec, heightMeasureSpec); 
    } 

    protected void onSizeChanged(int w, int h, int oldw, int oldh) { 
     regenerateBackground(); 
    } 

    public void onDraw(Canvas canvas){ 
     drawBackground(canvas); 
     if(dataSource != null) 
      drawCurve(canvas, dataSource); 
    } 

    /////////////////////////////////////////////////////////////////////////////// 
    // Drawing methods 
    /////////////////////////////////////////////////////////////////////////////// 

    private void drawX(Canvas canvas){ 
     canvas.drawLine(mLeftSide, positionOfX, mRightSide, positionOfX, xAxisPaint); 
     canvas.drawText(xUnitText, mRightSide - unitTextPaint.measureText(xUnitText)/2, positionOfX - unitTextPaint.getTextSize()/2, unitTextPaint); 
    } 

    private void drawY(Canvas canvas){ 
     canvas.drawLine(positionOfY, mTopSide, positionOfY, mBottomSide, yAxisPaint); 
     canvas.drawText(yUnitText, positionOfY + unitTextPaint.measureText(yUnitText)/2 + 4*mDips, mTopSide + (int) (unitTextPaint.getTextSize()/2), unitTextPaint); 
    } 

    private void drawTick(Canvas canvas){ 
     // No tick at this time 
     // TODO decide how I want to put those ticks, if I want them 
    } 

    private void drawTitle(Canvas canvas){ 
     canvas.drawText(titleText, mMiddleX, mTopSide + (int) (titleTextPaint.getTextSize()/2), titleTextPaint); 
    } 

    /** 
    * Read a buffer array of size greater than "windowSize" and create a window array out of it. 
    * A curve is then drawn from this array using "windowSize" points, from left 
    * to right. 
    * @param canvas is a Canvas object on which the curve will be drawn. Ensure the canvas is the 
    * later drawn object at its position or you will not see your curve. 
    * @param data is a float array of length > windowSize. The floats must range between 0.0 and 1.0. 
    * A value of 0.0 will be drawn at the bottom of the graph, while a value of 1.0 will be drawn at 
    * the top of the graph. The range is not tested, so you must ensure to pass proper values, or your 
    * graph will look terrible. 
    *  0.0 : draw at the bottom of the graph 
    *  0.5 : draw in the middle of the graph 
    *  1.0 : draw at the top of the graph 
    */ 
    private void drawCurve(Canvas canvas, float[] data){ 

     // Create a reference value to determine the stepping between each points to be drawn 
     float incrementX = (mRightSide-mLeftSide)/(float) windowSize; 

     float incrementY = mBottomSide - mTopSide; 

     // Prepare the array for the graph 
     float[] source = prepareWindowArray(data); 

     // Prepare the curve Path 
     curve = new Path(); 
     // Move at the first point. 
     curve.moveTo(mLeftSide, source[0]*incrementY); 
     // Draw the remaining points of the curve 
     for(int i = 1; i < windowSize; i++){ 
      curve.lineTo(mLeftSide + (i*incrementX), source[i] * incrementY); 
     } 

     canvas.drawPath(curve, curvePaint); 
    } 

    /////////////////////////////////////////////////////////////////////////////// 
    // Intimate methods 
    /////////////////////////////////////////////////////////////////////////////// 

    /** 
    * When asked to draw the background, this method will verify if a bitmap of the 
    * background is available. If not, it will regenerate one. Then, it will draw 
    * the background using this bitmap. The use of a bitmap to draw the background 
    * is to avoid unnecessary processing for static parts of the view. 
    */ 
    private void drawBackground(Canvas canvas){ 
     if(background == null){ 
      regenerateBackground(); 
     } 
     canvas.drawBitmap(background, 0, 0, backgroundPaint); 
    } 

    /** 
    * Call this method to force the <i>GraphView</i> to redraw the cache of it's background, 
    * using new properties if you changed them with <i>setupGraph()</i>. 
    */ 
    public void regenerateBackground(){ 
     initConstants(); 
     try{ 
      initWindowSetting(); 
     } catch (IllegalArgumentException e){ 
      Log.e(TAG, "Could not initalize windows.", e); 
      return; 
     } 
     if(background != null){ 
      background.recycle(); 
     } 
     background = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888); 
     Canvas backgroundCanvas = new Canvas(background); 

     drawX(backgroundCanvas); 
     drawY(backgroundCanvas); 
     drawTick(backgroundCanvas); 
     drawTitle(backgroundCanvas); 

    } 

    /** 
    * Extract a window array from the data array, and reposition the windowStart 
    * index for next iteration 
    * @param data the array of data from which we get the window 
    * @return an array of float that represent the window 
    */ 
    private float[] prepareWindowArray(float[] data){ 
     // Prepare the source array for the graph. 
     float[] source = new float[windowSize]; 

     // Copy the window from the data array into the source array 
     for(int i = 0; i < windowSize; i++){ 
      if(windowStart+i < data.length)       // If the windows holds within the data array 
       source[i] = data[windowStart + i];     // Simply copy the value in the source array 
      else{             // If the window goes beyond the data array 
       source[i] = data[(windowStart + 1)%data.length]; // Loop at the beginning of the data array and copy from there 
      } 
     } 
     // Reposition the buffer index 
     windowStart = windowStart + windowSize; 
     // If the index is beyond the end of the array 
     if(windowStart >= data.length){ 
      windowStart = windowStart % data.length; 
     } 

     return source; 
    } 
} 
4

那麼我會開始只是試圖用你有的代碼和真正的代數據重繪它。只有如果這不夠快,你需要嘗試任何幻想像滾動...

如果你需要花哨,我會嘗試像這樣的東西。

我會將圖形的動態部分繪製到您保留在框架之間而不是直接指向罐子的輔助位圖中。我將在另一個位圖中的背景上沒有動態部分,只在重新縮放等時繪製。

在繪製新數據的第二個動態位圖中,首先需要清除要替換的舊數據,然後執行此操作在靜態數據的頂部繪製靜態背景位圖的適當片段,從而清除它並使背景更加美觀和新鮮。然後您只需要繪製新的動態數據。訣竅是你從左到右畫出第二個位圖,然後在最後回到左邊並重新開始。

要從soncodary位圖獲取cancas,將位圖繪製爲兩部分。剛剛添加的舊數據需要繪製到最終畫布的左側,新數據需要直接繪製到右側。

對於發送數據,一個循環緩衝區對於這類數據來說是正常的事情,一旦它離開圖表,你不關心它。

+0

感謝您的想法,它給了我一個良好的感覺,如何解決這個問題。 – AntoineG