首頁 > 軟體

Android RecyclerLineChart實現圖表繪製教學

2022-12-09 14:02:15

正文

本篇介紹線性圖示RecyclerLineChart 的繪製,對於圖表的公共部分X、Y軸,背景Board等的繪製先前章節已經有過介紹,這裡不再重複;以及高亮選中頂部的poupWindow基本的繪製邏輯跟BarChart類似,可參照之前章節。所以針對LineChart,這裡只介紹主體圖表的繪製邏輯,以及線性表底部的drawFillColor填充。

首先介紹主體圖表的邏輯,與BarChart不同之處在於,BartChart的每個Item的繪製比較獨立,而LineChart對於當前的Item,需要找到PreItem或者NextItem中的Y的點進行drawLine, 相比而言稍顯複雜一些。圖表的中間位置的Line還比較容易繪製,圖表左右邊界是LineChart繪製最難的地方。

整個的繪製邏輯第一章節有介紹在Render類中,這裡的話是LineChartRender的drawLineChartWithoutPoint 方法裡,這個方法程式碼比較長,分段介紹:

private <T extends BarEntry> void drawLineChartWithoutPoint(Canvas canvas, RecyclerView parent, YAxis mYAxis) {
  final float parentRightBoard = parent.getWidth() - parent.getPaddingRight();
  final float parentLeft = parent.getPaddingLeft();
  BaseBarChartAdapter adapter = (BaseBarChartAdapter) parent.getAdapter();
  List<T> entryList = adapter.getEntries();
  final int childCount = parent.getChildCount();

  int adapterPosition;
  for (int i = 0; i < childCount; i++) {
    View child = parent.getChildAt(i);
    T barEntry = (T) child.getTag();
    if (barEntry.getY() == 0) {
      continue;
    }
    adapterPosition = parent.getChildAdapterPosition(child);
    RectF rectF = ChartComputeUtil.getBarChartRectF(child, parent,
                                                    mYAxis, mLineChartAttrs, barEntry);
    PointF pointF2 = new PointF((rectF.left + rectF.right) / 2, rectF.top);
          // 這裡還有好多繪製邏輯程式碼
            }//end for        
}// end  drawLineChartWithoutPoint

整個繪製繪製依次遍歷 Adapter中當前展現的點,總共childcount 個,遍歷的當前點位pointF2, 以pointF2 為基準接下來會涉及找 pointF0,pointF1, 這兩在pointF2左邊(假設存在的情況下);pointF3, pointF4, 這兩個點在PointFd2的右邊,之所以要找左右各兩個點是處理邊界情況。

正常情況下繪製邏輯

連線pointF1、pointF2。

if (i < childCount - 1) {//這裡的LayoutManager設定的reverse倒敘,所以i+1在i的左邊i對應的是pointF2
  View pointF1Child = parent.getChildAt(i + 1);
  T barEntryLeft = (T) pointF1Child.getTag();
  //這裡的RectF跟之前的Barchart類似,為ItemView中除去space後所佔的RectF區域,其中pointF1的X為RectF的X軸方向的中心。
  RectF rectFLeft = ChartComputeUtil.getBarChartRectF(pointF1Child, parent, mYAxis,   mLineChartAttrs, barEntryLeft);
  //找到PointF1
  PointF pointF1 = new PointF((rectFLeft.left + rectFLeft.right) / 2, rectFLeft.top);
  //parentLeft為左邊界, parentRightBoard 為Chart的右邊界
  if (pointF1.x >= parentLeft && pointF2.x <= parentRightBoard) {
    float[] pointsOut = new float[]{pointF1.x, pointF1.y, pointF2.x, pointF2.y};
    drawChartLine(canvas, pointsOut);//繪製正常情況下的Line。
    drawFill(parent, mLineChartAttrs, canvas, pointF1, pointF2, rectF.bottom);
    //其它邊界繪製邏輯。 
}//end if

左邊界繪製

以上的情況是pointF1.x 在Chart內,見圖, 黃色為當前的PointF1, 紫色為PointF2, 上面程式碼李drawLine繪製的是PointF1跟PointF2之前的連線。

繼續看上面的那個圖,要繪製PointF1到Chart左邊邊界的線段,需要繼續找到PointF0,然後用PointF0、PointF1與Chart相交得到上圖黑色圈裡的點,記為pointFIntercept, drawLine(pointFIntercept, PointF1)

if (pointF1Child.getLeft() < parentLeft) {//左邊界,處理pointF1值顯示出來了的情況。
  if (adapterPosition + 2 < entryList.size()) {
  float x = pointF1.x - pointF1Child.getWidth();
  T barEntry0 = entryList.get(adapterPosition + 2);
  float y = ChartComputeUtil.getYPosition(barEntry0, parent, mYAxis, mLineChartAttrs);
  PointF pointF0 = new PointF(x, y);
    //PointF0、PointF1 跟Chart的交點pointFIntercept
  PointF pointFIntercept = ChartComputeUtil.getInterceptPointF(pointF0, pointF1, parentLeft);
  float[] points = new float[]{pointFIntercept.x, pointFIntercept.y, pointF1.x, pointF1.y};
  drawChartLine(canvas, points);
  drawFill(parent, mLineChartAttrs, canvas, pointFIntercept, pointF1, rectF.bottom);
   }
}

上面是 pointF1.x >= parentLeft,在左邊界內的情況,當pointF1.x < parentLeft時,rectLeft 出來一小部分的情況,如下圖所示:紫色為當前的PointF2點

這時需要求PointF1、PointF2跟Chart相交的點pointF, 然後drawLine(pointF, PointF2)即可, 見程式碼:

if (pointF1.x < parentLeft && pointF1Child.getRight() >= parentLeft) {//左邊界,處理pointF1值沒有顯示出來
  PointF pointF = ChartComputeUtil.getInterceptPointF(pointF1, pointF2, parentLeft);
  float[] points = new float[]{pointF.x, pointF.y, pointF2.x, pointF2.y};
  drawChartLine(canvas, points);
  drawFill(parent, mLineChartAttrs, canvas, pointF, pointF2, rectF.bottom);
}

右邊界繪製

處理完左邊界的繪製,右邊界的繪製跟左邊界大致一樣,PointF2 往右兩個點PointF3, PointF4; 注意這裡RecyclerView的LayoutManager為reverse, 所以當 PointF2對應的下標為i時, PointF3對應的為i-1, PointF4為i-2.

然後就是分情況討論PointF3.x 是否在Chart範圍內,跟parentRightBorad比較即可。

看PointF3.x 在 Chart範圍內的情況,如圖:紫色為PointF2點,黃色為PonitF3點,黑色為PointF3,PointF4跟Chart的交點,這裡只需要繪製PointF3跟交點之間的Line;PointF2、PointF3之間的Line 在當黃色點遍歷到i時,紫色點位PointF1,所以這裡不需要重複繪製了。

程式碼邏輯

if (pointF3.x < parentRightBoard) {//pointF3 在界內。
  if (adapterPosition - 2 > 0) {
  float xInner = pointF3.x + child.getWidth();
  T barEntry4 = entryList.get(adapterPosition - 2);
  float yInner = ChartComputeUtil.getYPosition(barEntry4, parent, mYAxis, mLineChartAttrs);
  PointF pointF4 = new PointF(xInner, yInner);//找到PointF4.
  PointF pointFInterceptInner = ChartComputeUtil.getInterceptPointF(pointF3, pointF4, parentRightBoard);
  float[] pointsInner = new float[]{pointF3.x, pointF3.y, pointFInterceptInner.x, pointFInterceptInner.y};
  drawChartLine(canvas, pointsInner);
  drawFill(parent, mLineChartAttrs, canvas, pointF3, pointFInterceptInner, rectF.bottom);
  }
}

最後就是 pointF3.x >parentRightBoard的情況,見圖:紫色為PointF2, 黃色為 PointF2、PointF3跟Chart的交點:

程式碼邏輯如下:

if (pointF3.x > parentRightBoard) {//在Chart之外。
  PointF pointFIntercept = ChartComputeUtil.getInterceptPointF(pointF2, pointF3, parentRightBoard);
  float[] points = new float[]{pointF2.x, pointF2.y, pointFIntercept.x, pointFIntercept.y};
  drawChartLine(canvas, points);
  drawFill(parent, mLineChartAttrs, canvas, pointFIntercept, pointF2, rectF.bottom);
} 

以上的邊界處理中涉及到的工具類方法求相交點,簡單的數學公司帶入:

public static PointF getInterceptPointF(PointF pointF1, PointF pointF2, float x) {
    float width = Math.abs(pointF1.x - pointF2.x);
    float height = Math.abs(pointF1.y - pointF2.y);
    float interceptWidth = Math.abs(pointF1.x - x);
    float interceptHeight = interceptWidth * 1.0f / width * height;
    float y;
    if (pointF2.y < pointF1.y) {
      y = pointF1.y - interceptHeight;
    } else {
      y = pointF1.y + interceptHeight;
    }
    return new PointF(x, y);
}

見以上圖表中的紅色半透明的FillColor的繪製,每次drawLine()緊跟著就是drawFill(), 以下是drawFill的邏輯,跟X軸構建一個path,然後drawPath 即可:

private void drawFill(RecyclerView parent, LineChartAttrs mBarChartAttrs, Canvas canvas, PointF pointF, PointF pointF1, float bottom) {
        if (mBarChartAttrs.enableLineFill) {
            float yBottom = parent.getBottom() - parent.getPaddingBottom();
            float yTop = parent.getTop() + parent.getPaddingTop();
            LinearGradient mLinearGradient = new LinearGradient(
                    0,
                    yBottom,
                    0,
                    yTop,
                    new int[]{
                            mBarChartAttrs.lineShaderBeginColor, mBarChartAttrs.lineShaderEndColor},
                    null,
                    Shader.TileMode.CLAMP
            );
            mLineFillPaint.setShader(mLinearGradient);
            Path path = ChartComputeUtil.createColorRectPath(pointF, pointF1, bottom);
            LineChartDrawable drawable = new LineChartDrawable(mLineFillPaint, path);
            drawable.draw(canvas);
        }
    }

設定了一個Color的Linear漸變從bottom到top.

至此,RecyclerLineChart的主體圖表繪製邏輯介紹完畢。還有部分的細節,當前Point帶圓圈,以及邊界圓圈的繪製等,選中圓圈的處理等多處細節,讀者想了解的,可以GitHub上下載看原始碼demo, 連線在本專欄的第一篇裡有連結。

以上就是Android RecyclerLineChart實現圖表繪製教學的詳細內容,更多關於Android RecyclerLineChart圖表繪製的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com