2017-04-17 96 views
4

我正在使用庫MPAndroidChart,但它沒有我想要的所有功能。MPAndroidChart渲染器如何工作以及如何編寫自定義渲染器?

我聽說可以通過編寫自定義渲染器來實現我想要的功能。

我已經看了MPAndroidChart GitHub回購中的source code for the renderers,但我無法理解所涉及的概念。

MPAndroidChart渲染器如何工作?

什麼是編寫自定義渲染器的高級過程?

注意:對於SO發佈的的許多問題,解決方案是實現某種自定義渲染器。如果沒有指南,對這些問題的評論「你可以通過寫一個自定義渲染器來解決這個問題」是不滿意的。編寫一個包含完整解決方案的答案可能非常耗時。編寫自定義渲染器沒有現存的指南,希望這個問題可以作爲提問者能夠幫助自己的工具,如果不是重複的目標。雖然我在這裏嘗試了自己的答案,但歡迎其他答覆和更正和評論。

回答

6

認識觀和Canvas

首先,應該研究從Android官方文檔Canvas and Drawables Guide。尤其值得注意的是,LineChart,BarChart等是View的子類,它們通過覆蓋View超類的onDraw(Canvas c)回調來顯示它們自己。還要注意「畫布」的定義:

畫布對你來說是作爲你的圖形繪製的實際表面的僞裝或界面 - 它包含你所有的「繪製」調用。

當您使用渲染器時,您將處理在畫布上繪製線條等的功能。

畫布圖表上

點被指定爲x和y的值相對於所述圖表上的單位對所述圖表上的值和像素之間的翻譯。例如,在下面的圖表中,第一欄的中心位於x = 0。第一欄的y值爲52.28

an MPAndroidChart barchart

這顯然不對應於畫布上的像素座標。在畫布上,畫布上的x = 0將是最左邊的像素,它們顯然是空白的。同樣,由於像素枚舉從頂部開始爲y = 0,因此柱形的頂端顯然不是52.28(圖表上的y值)。如果我們使用開發人員選項/指針位置,我們可以看到第一個小節的提示大約爲x = 165y = 1150

A Transformer負責將圖表值轉換爲像素(屏幕上)座標,反之亦然。渲染器中的一種常見模式是使用圖表值(更易於理解)執行計算,然後在最後使用變換器將變換應用於屏幕上。

查看端口和邊界

視圖端口是一個窗口,即,在圖表上的有界區域。視圖端口用於確定用戶當前可以看到哪部分圖表。每個圖表都有一個ViewPortHandler,它封裝了與視圖端口相關的功能。我們可以使用ViewPortHandler#isInBoundsLeft(float x)isInBoundsRight(float x)來確定用戶當前可以看到哪些x值。

在上圖所示的圖表中,BarChart「知道」爲6或更高,但由於它們超出界限而不在當前視口中,因此不會呈現6和向上。因此,x值05在當前視口內。

ChartAnimator

ChartAnimator提供要被施加到該圖表的額外轉化。通常這是一個簡單的乘法。例如,假設我們需要一個動畫,其中圖表的點從底部開始並逐漸上升到正確的y值超過1秒。動畫師將提供一個phaseY,這是一個簡單的標量,從0.000開始,時間爲0ms,並且逐漸上升到1.000,1000ms

現在

我們理解所涉及的基本概念的渲染代碼的例子,讓我們來看看一些代碼LineChartRenderer

protected void drawHorizontalBezier(ILineDataSet dataSet) { 

    float phaseY = mAnimator.getPhaseY(); 

    Transformer trans = mChart.getTransformer(dataSet.getAxisDependency()); 

    mXBounds.set(mChart, dataSet); 

    cubicPath.reset(); 

    if (mXBounds.range >= 1) { 

     Entry prev = dataSet.getEntryForIndex(mXBounds.min); 
     Entry cur = prev; 

     // let the spline start 
     cubicPath.moveTo(cur.getX(), cur.getY() * phaseY); 

     for (int j = mXBounds.min + 1; j <= mXBounds.range + mXBounds.min; j++) { 

      prev = cur; 
      cur = dataSet.getEntryForIndex(j); 

      final float cpx = (prev.getX()) 
        + (cur.getX() - prev.getX())/2.0f; 

      cubicPath.cubicTo(
        cpx, prev.getY() * phaseY, 
        cpx, cur.getY() * phaseY, 
        cur.getX(), cur.getY() * phaseY); 
     } 
    } 

    // if filled is enabled, close the path 
    if (dataSet.isDrawFilledEnabled()) { 

     cubicFillPath.reset(); 
     cubicFillPath.addPath(cubicPath); 
     // create a new path, this is bad for performance 
     drawCubicFill(mBitmapCanvas, dataSet, cubicFillPath, trans, mXBounds); 
    } 

    mRenderPaint.setColor(dataSet.getColor()); 

    mRenderPaint.setStyle(Paint.Style.STROKE); 

    trans.pathValueToPixel(cubicPath); 

    mBitmapCanvas.drawPath(cubicPath, mRenderPaint); 

    mRenderPaint.setPathEffect(null); 
} 

for循環之前的前幾行是設置爲渲染器循環。請注意,我們從ChartAnimator(變換器)獲得phaseY,並計算視圖端口邊界。

for循環的基本含義是「對於位於視口左右邊界內的每個點」。呈現無法看到的x值沒有意義。

在循環中,我們使用dataSet.getEntryForIndex(j)獲取當前條目的x值和y值,並在該條和前一條目之間創建一條路徑。請注意路徑全部乘以phaseY的動畫。

最後,之後已計算出的路徑的變換被施加trans.pathValueToPixel(cubicPath);和路徑被渲染到畫布mBitmapCanvas.drawPath(cubicPath, mRenderPaint);

編寫自定義渲染

的第一步是選擇正確類子類。請注意包com.github.mikephil.charting.renderer中的類 ,包括XAxisRendererLineChartRenderer等。一旦創建了子類,就可以簡單地覆蓋相應的方法。根據上面的示例代碼,我們將覆蓋void drawHorizontalBezier(ILineDataSet dataSet)而不調用super(以便不會調用渲染階段兩次)並將其替換爲我們想要的功能。如果你這樣做是正確的,重寫的方法至少應該有點像你覆蓋方法:

  1. 變壓器,動畫師獲得一個手柄,和界限
  2. 通過可見性×循環 - 值(將x值是視口範圍內的是)
  3. 準備個圖表呈現值
  4. 轉化點爲像素的畫布
  5. 上的使用Canvas類方法繪製在畫布上

您應該研究Canvas classdrawBitmap等)中的方法,以查看允許您在渲染器循環中執行哪些操作。

如果您需要覆蓋的方法未公開,您可能必須繼承像LineRadarRenderer這樣的基礎渲染器才能實現所需的功能。

一旦你設計了你想要的渲染器子類,你可以用Chart#setRenderer(DataRenderer renderer)BarLineChartBase#setXAxisRenderer(XAxisRenderer renderer)或其他方法輕鬆地使用它。

相關問題