2017-04-17 100 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)或其他方法轻松地使用它。

相关问题