Flutter CustomBarChartWidget

Flutter-CustomBarChartWidget

1、Scene

We hold our mobile phone everywhere and almost everytime,naturally a application which can show our usage detail is born.Like this:

  • Daily
    Flutter CustomBarChartWidget_第1张图片
  • Weekly
    Flutter CustomBarChartWidget_第2张图片
  • Monthly
    Flutter CustomBarChartWidget_第3张图片
    -Yearly
    Flutter CustomBarChartWidget_第4张图片
    One of the most vital work is to accomplish the bar chart,and the charts_flutter library is taken into my consideration firstly,and this link seems meet my requirement,but the x axis’s value was limited to date,you can not self-design the x-axis value and y-axis value,so we need to deliver requirement in detail.

2、Requirement

The custom bar chart should be highly self-design,for instance,the x-axis value and y-axis value,the y-axis value’s position,which can be placed at left or right in bar chart,what’s more, the bar’s color and style,how many lines,lines’s color,each lines gap and so on ,which can be set by other widget.

3、Skeleton

Brief skeleton below:
Flutter CustomBarChartWidget_第5张图片
Parent view is a stack,its children can be stacked from bottom to top,this children are bars,lines,y-units,x-units.
Each children can be placed by point exactly,and factors which related to points can be decided by other widgets,including gap between each lines,the x-units number,y-units number which must equal to lines’s.
With the help of CustomPaint class,we can accomplish this canvas by paint method.

4、CustomPainter

CustomPainter,the interface provides a paint method,that we can self-design what we want to draw.
Then we define a BarChartPaint extends CustomPainter class,and make repaint return true,let canvas redraw while new is coming.

5、Coding

Coding is working than words.

(1)Define the critical parameters

  • Draw type
enum DRAW_TYPE { TYPE_LINE, TYPE_X, TYPE_Y, TYPE_BAR }

Four items decide whole chart appearance.

(2) Label

Including x label and y label

class Label {
  String label;
  int color;

  Label(this.label, this.color);
}

It decide label’s color and value.

(3) LinePosition

class LinePosition {
  double x1;
  double y1;

  double x2;
  double y2;

  LinePosition(this.x1, this.y1, this.x2, this.y2);
}

From start to end.

(4) LabelPointPosition

class LabelPointPosition {
  final double x;
  final double y;
  final double value;

  LabelPointPosition(this.x, this.y, this.value);
}

Including label position and value.

(5) LabelPointPosition

enum LabelPosition { POSITION_LEFT, POSITION_RIGHT }

Y-label can be placed at left or right.

(6) BarStyle

class BarStyle {
  final double radius;
  final int color;
  final double barWidth;

  // ignore: invalid_required_param
  const BarStyle(
      @required this.radius, @required this.color, @required this.barWidth);
}

Bar contains radius for four angle and its width and color can be set.

(7)ChartData

class ChartData {
  double yData;
  double xData;

  ChartData(this.xData, this.yData);
}

chart data decides bar’s height and y label value.

(8)ChartData

class Margin {
  final double left;
  final double right;
  final double top;
  final double bottom;

  const Margin(this.left, this.right, this.top, this.bottom);
}

Charts’s margin from all sides is determined.

(9)Start Drawing

The parent widget is a stack,and each children is drew with given parameters.

return GestureDetector(
  child: Container(
    height: widget.lineMargin.top * (widget.lineNum - 1) +
        widget.margin.top +
        widget.margin.bottom +
        widget.heightOffset,
    width: actualWidth,
    color: Colors.white,
    margin: EdgeInsets.only(
        left: widget.margin.left,
        top: widget.margin.top,
        right: widget.margin.right,
        bottom: widget.margin.bottom),
    child: Stack(
      children: drawSkeleton(),
    ),
  ),
  onTap: () {
    widget.onTap("clicked");
  },
);

Container here do a convenience to margin,background,size.And GestureDetector wrap them to respond to tap.

  • drawLine
    line position parameters init below:
_linePosition.clear();
Margin lineMargin = widget.lineMargin;
for (int index = 0; index < widget.lineNum; index++) {
  LinePosition tempPosition = new LinePosition(
      lineMargin.left,
      lineMargin.top * index,
      width - lineMargin.right,
      lineMargin.bottom * index);
  _linePosition.add(tempPosition);
}

if (_linePosition == null ||
    _linePosition.length == 0 ||
    _linePosition.length != widget.yLabels.length) {
  return Text(
    "data init fail",
    style: TextStyle(color: Colors.red),
  );
}

From init method,we can find that line positions are determined by margin,left and right margin control gap from edge of screen,top and bottom margin control distance with each line from top to bottom.

then we can draw line:

for (LinePosition linePosition in _linePosition) {
  if (widget.yLabelPosition == LabelPosition.POSITION_LEFT) {
    linePosition.x1 = linePosition.x1 + widget.margin.left;
    linePosition.x2 = linePosition.x2 - widget.margin.right;
  } else {
    linePosition.x1 = linePosition.x1 + widget.margin.left;
    linePosition.x2 = linePosition.x2 - widget.margin.right;
  }
  LinePosition curLinePosition = new LinePosition(
      linePosition.x1, linePosition.y1, linePosition.x2, linePosition.y2);
  barChartPaint = new BarChartPaint(
      widget, linePaint, curLinePosition, DRAW_TYPE.TYPE_LINE, null, 0);
  xWidgets.add(CustomPaint(
    painter: barChartPaint,
    willChange: true,
  ));
}

Because BarChartPaint extends CustomPainter,and when it commit a new object of BarChartPaint with painter parameter,it will start draw line from start point to end.

Draw method is easy:

if (DRAW_TYPE.TYPE_LINE == _drawType) {
//      print("x1 = ${_linePosition.x1}" + ",y1 = ${_linePosition.y1}");
  if (widget.yLabelPosition == LabelPosition.POSITION_LEFT) {
    canvas.drawLine(Offset(_linePosition.x1, _linePosition.y1),
        Offset(_linePosition.x2, _linePosition.y2), _paint);
  } else {
    canvas.drawLine(Offset(_linePosition.x1, _linePosition.y1),
        Offset(_linePosition.x2, _linePosition.y2), _paint);
  }
}
  • draw y label
int positionSize = _linePosition.length;
int yLabelSize = widget.yLabels.length;
if (positionSize == yLabelSize) {
  for (int index = yLabelSize - 1; index >= 0; index--) {
    LinePosition curLinePosition = _linePosition[positionSize - index - 1];
    LabelPointPosition labelPointPosition;
    Label yLabel = widget.yLabels[index];
    if (widget.yLabelPosition == LabelPosition.POSITION_LEFT) {
      curLinePosition.x1 = curLinePosition.x1 + widget.margin.left;
      labelPointPosition = new LabelPointPosition(curLinePosition.x1,
          curLinePosition.y1, double.parse(yLabel.label));
      curLinePosition.y1 = curLinePosition.y1 - 7.0;
    } else {
      curLinePosition.x2 = curLinePosition.x2 - widget.margin.right;
      labelPointPosition = new LabelPointPosition(curLinePosition.x2,
          curLinePosition.y2, double.parse(yLabel.label));
      curLinePosition.y2 = curLinePosition.y2 - 7.0;
    }
    widget.yLabelPositions.add(labelPointPosition);
    LinePosition tempLinePosition = new LinePosition(curLinePosition.x1,
        curLinePosition.y1, curLinePosition.x2, curLinePosition.y2);
    barChartPaint = new BarChartPaint(widget, linePaint, tempLinePosition,
        DRAW_TYPE.TYPE_Y, yLabel, yLabelSize);
    xWidgets.add(CustomPaint(
      painter: barChartPaint,
      willChange: true,
    ));
  }
}

Y label accompanies line,it locates left or right of line,and can keep a little distance from start or end of line.
Starting draw y label:

if (DRAW_TYPE.TYPE_Y == _drawType) {
  TextSpan span = new TextSpan(
      style: new TextStyle(color: Color(label.color)),
      text: label.label + widget.yLabelUnit);
  TextPainter tp = new TextPainter(
      text: span,
      textAlign: TextAlign.left,
      textDirection: TextDirection.ltr);
  tp.layout();

  if (widget.yLabelPosition == LabelPosition.POSITION_LEFT) {
    tp.paint(canvas, new Offset(_linePosition.x1, _linePosition.y1));
  } else {
    tp.paint(canvas, new Offset(_linePosition.x2, _linePosition.y2));
  }
}
  • draw x label
int xLabelSize = widget.xLabels.length;
LinePosition lastLinePosition = _linePosition[positionSize - 1];
double xLabelGap = (actualWidth - 18) / xLabelSize;
double initPosition = widget.margin.left;
double yPosition = lastLinePosition.y1;
double xLabelValue = 0;
for (int index = 0; index < xLabelSize; index++) {
  Label xLabel = widget.xLabels[index];
  double xPosition =
      lastLinePosition.x1 + initPosition + index * (xLabelGap);
  LinePosition labelPosition = new LinePosition(xPosition, yPosition, 0, 0);
  barChartPaint = new BarChartPaint(widget, linePaint, labelPosition,
      DRAW_TYPE.TYPE_X, xLabel, xLabelSize);
  if (widget.isMultiData) {
    xLabelValue = double.parse(xLabel.label);
  } else {
    xLabelValue = (index + 1).toDouble();
  }
  LabelPointPosition labelPointPosition =
      new LabelPointPosition(xPosition, yPosition, xLabelValue);
  widget.xLabelPositions.add(labelPointPosition);
  var paint = CustomPaint(
    painter: barChartPaint,
    willChange: true,
  );
  xWidgets.add(paint);
}

X label’s x-axis start position is line’s,and y-axis position is the last y-axis position of line’s.The distance between each x-label is based on the screen width decrease margin and then divide x-label size.The critical parameter is isMultiData,which means whether the x-label is coordinated with value,if true,the bar can be draw between two x-labels,or one x-label by one bar.
Draw x label:

if (DRAW_TYPE.TYPE_X == _drawType) {
  TextSpan span = new TextSpan(
      style: new TextStyle(color: Color(label.color)),
      text: label.label + widget.xLabelUnit);
  TextPainter tp = new TextPainter(
      text: span,
      textAlign: TextAlign.left,
      textDirection: TextDirection.ltr);
  tp.layout();
  tp.paint(canvas, new Offset(_linePosition.x1, _linePosition.y1));
}
  • draw data
if (widget.data != null) {
  barChartPaint = new BarChartPaint(
      widget, barPaint, null, DRAW_TYPE.TYPE_BAR, null, null);
  barChartPaint.barData = widget.data;
  barChartPaint.initDrawData();
  xWidgets.add(CustomPaint(
    painter: barChartPaint,
    willChange: true,
  ));
}

Before drawing bar,we need to invoke initDrawData first,because of the bar position is uncertain.

void initDrawData() {
    xLabelPositions = widget.xLabelPositions;
    yLabelPositions = widget.yLabelPositions;
    if (xLabelPositions != null && xLabelPositions.length > 0) {
      xLabelSize = xLabelPositions.length;
      if (widget.isMultiData) {
        xLabelMaxValue = xLabelPositions[xLabelSize - 1].value;
        xAverageDistance =
            (xLabelPositions[xLabelSize - 1].x - xLabelPositions[0].x) /
                xLabelMaxValue;
        firstXLabelX = xLabelPositions[0].x;
      }
    }
    if (yLabelPositions != null &&
        yLabelPositions.length > 0 &&
        _curData != null &&
        _curData.length > 0) {
      dataSize = _curData.length;
      yLabelSize = yLabelPositions.length;
      yLabelMaxValue = yLabelPositions[0].value;
      yAverageDistance =
          (yLabelPositions[yLabelSize - 1].y - yLabelPositions[0].y) /
              yLabelMaxValue;
      lastYLabelY = yLabelPositions[yLabelSize - 1].y;
    }
}

Pixel Distance coordinate with actual data value is vital,which decide where the bar is. xAverageDistance and yAverageDistance are born before bars to be drew.

xAverageDistance = (x end position - x start position) / x label with max value

yAverageDistance = (y start position - y end position) / y label with max value

Beginning draw data:

if (DRAW_TYPE.TYPE_BAR == _drawType && _curData != null) {
  bool barStyleValid =
      widget.barStyle != null && widget.barStyle.length == dataSize;
  for (int index = 0; index < dataSize; index++) {
    ChartData chartData = _curData[index];
    double yData = chartData.yData;
    if (yData > yLabelMaxValue) {
      break;
    }
    double dataY = 0;
    for (int yIndex = 0; yIndex < yLabelSize; yIndex++) {
      double value = yLabelPositions[yLabelSize - yIndex - 1].value;
      double yLabelY = yLabelPositions[yLabelSize - yIndex - 1].y;
//            print("value = $value" + ",yData = $yData ,yLabelY = $yLabelY");
      if (yData == 0 && value == 0) {
        dataY = lastYLabelY;
        break;
      }
      if (yData <= value) {
        dataY = lastYLabelY - yData * yAverageDistance;
//            print("value = $value" + ",yData = $yData ,yLabelY = $yLabelY");
        break;
      }
    }
    double dataX = 0;
    if (widget.isMultiData) {
      dataX = getXPosition(chartData);
    } else {
      if (index >= xLabelSize) {
        break;
      }
      double xValue = 0;
      for (LabelPointPosition tempXLabelPosition in xLabelPositions) {
        if (tempXLabelPosition.value == chartData.xData) {
          dataX = tempXLabelPosition.x;
          xValue = tempXLabelPosition.value;
          break;
        }
      }
      String valueStr = xValue.toInt().toString();
      int valueLength = valueStr.length;
      double offset = valueLength % 2 == 0
          ? 2 * valueLength.toDouble()
          : valueLength * 3 / 2.0;
      dataX += offset;
    }
    if (dataX < 0) {
      break;
    }
    print("dataX = $dataX" +
        ",dataY = $dataY" +
        ",yData = $yData ,lastYLabelY = $lastYLabelY ,yAverageDistance = $yAverageDistance");
    if (barStyleValid) {
      RRect rRect = new RRect.fromLTRBR(
          dataX,
          dataY,
          dataX + widget.barStyle[index].barWidth,
          lastYLabelY,
          Radius.circular(widget.barStyle[index].radius));
      _paint.color = Color(widget.barStyle[index].color);
      canvas.drawRRect(rRect, _paint);
    } else {
      RRect rRect = new RRect.fromLTRBR(
          dataX,
          dataY,
          dataX + widget.barStyle[0].barWidth,
          lastYLabelY,
          Radius.circular(widget.barStyle[0].radius));
      _paint.color = Color(widget.barStyle[0].color);
      canvas.drawRRect(rRect, _paint);
    }
  }
}

Multi data mode seems complex,

double getXPosition(ChartData chartData) {
    double xData = chartData.xData;
    double dataX = -1;
    for (int xIndex = 0; xIndex < xLabelSize; xIndex++) {
      double value = xLabelPositions[xIndex].value;
      double xLabelX = xLabelPositions[xIndex].x;
    //            print("value = $value" + ",yData = $yData ,yLabelY = $yLabelY");
      if (xData <= value) {
        String valueStr = value.toInt().toString();
        int valueLength = valueStr.length;
        double offset = valueLength % 2 == 0
            ? 2 * valueLength.toDouble()
            : valueLength * 3 / 2.0;
        dataX = firstXLabelX + xData * xAverageDistance + offset;
    //        print("value = $value" + "yLabelY = $xLabelX");
        break;
      }
    }
    return dataX;
}

the codes are a little bit long,the main idea is to clarify how to get bar’s x and y position.If it is not multi mode,the bar’s x-point equal to x-label’s x point the y-point equal to y-average-distance-per-value multi value.And getXPosition method apply to multi mode.

6、Packge Interface

const CustomBarChart({
    Key key,
    this.lineColor = 0x7FA1A1A1,
    this.lineWidth = 1,
    this.lineNum = 3,
    @required this.xLabels,
    @required this.yLabels,
    @required this.data,
    this.onTap,
    this.xLabelUnit = "",
    this.yLabelUnit = "",
    this.yLabelPosition = LabelPosition.POSITION_RIGHT,
    this.barStyle = const [BarStyle(0, 0xFF35CABE, 6)],
    this.xLabelColor = 0xFFBCBCBC,
    this.yLabelColor = 0xFFBCBCBC,
    this.margin = const Margin(0, 0, 0, 0),
    this.isMultiData = false,
    this.lineMargin = const Margin(16, 32, 50, 50),
});

All parameters in this constructor can be customized,and other requirements such as chart data should be above bar,each bar can be touched with different color and so on.

7、How to use

Widget _getSportsDayBarChartView(
      List sportsTypeDetailList) {
    var barStyles = [BarStyle(2, 0xFFFF7404, 4)];
    var chartList = _getChartData(sportsTypeDetailList);
    return CustomBarChart(
      xLabels: BarChartCommon.getXLabels(0),
      yLabels: BarChartCommon.getYLabel(chartList, ChartYLabelType.TYPE_STEP),
      data: chartList,
      lineNum: 3,
      yLabelUnit: "",
      isMultiData: true,
      yLabelPosition: LabelPosition.POSITION_RIGHT,
      barStyle: barStyles,
      lineMargin: Margin(16, 32, 64, 64),
      onTap: (value) {
        Navigator.push(
          context,
          MaterialPageRoute(
            builder: (context) => SingleDaySportsListView(
                sportsTypeDetailList: sportsTypeDetailList),
          ),
        );
      },
    );
  }

8、flutter codes

import 'package:flutter/material.dart';
import 'dart:ui';

///CustomBarChart
class CustomBarChart extends StatefulWidget {
  final int lineColor;
  final double lineWidth;
  final int lineNum;

  final List

9、java codes

package com.example.view;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.View;

import com.qiku.healthguard.R;
import com.qiku.healthguard.bean.ChartDataBean;
import com.qiku.healthguard.sport.common.util.SDKUtils;
import com.qiku.healthguard.util.SystemDataUtil;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Created by chuck chan-iri on 2017/5/19.
 */

public class CustomBarChartView extends View {
    public final static int TYPE_TIME = 0;
    public final static int TYPE_WEEK = 1;
    public final static int TYPE_MONTH = 2;
    public final static int TYPE_YEAR = 3;

    private static DisplayMetrics mMetrics;
    float yMaxData, yDataForAveragePosition;
    private String TAG = "HG-CustomBarChartView";
    private float offsetLeft = 18;
    private float offsetRight = 18;
    private float offsetBottom = 20;
    private float leftOffsetInPixel = 0;
    private float bottomOffsetInPixel = 0;
    private int width = 0;
    private int height = 0;
    private int positionLeft = 0;
    private int positionRight = 0;
    private int positionTop = 0;
    private int positionBottom = 0;
    private float xGap, yGap, xStartPosition, xEndPosition, yStartPosition,
            yEndPosition, yBaseStartPosition, yBaseEndPosition, rectGap;
    private Paint xPaint = null;
    private Paint yPaint = null;
    private Paint linePaint = null;
    private Paint rectPaint = null;
    private List xItem;
    private int xType = -1;
    private List yItem;
    private int lineColor = R.color.color_A1;
    private int labelColor = R.color.color_BC;
    private List dataList;
    private RectF drawRectF;
    private float rectWidth = 4f;
    private int rectColor = R.color.color_35;
    private Map pointMap = new HashMap<>();
    private boolean isMultiData = false;
    private String[] WEEK_X_ARRAY = null;
    private String yLabelUnit = "h";
    //draw x label only for empty data set
    private boolean isDrawXLabelOnly = false;

    public CustomBarChartView(Context context) {
        this(context, null);
    }

    public CustomBarChartView(Context context, AttributeSet attrs) {
        super(context, attrs);
        Resources res = context.getResources();
        mMetrics = res.getDisplayMetrics();
        xPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        float xLabelSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
                10, mMetrics);
        xPaint.setTextSize(xLabelSize);
        xPaint.setColor(getResources().getColor(labelColor));
        yPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        float yLabelSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
                10, mMetrics);
        yPaint.setTextSize(yLabelSize);
        yPaint.setColor(getResources().getColor(labelColor));
        linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        linePaint.setColor(getResources().getColor(lineColor));
        rectPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        rectPaint.setColor(getResources().getColor(rectColor));
        leftOffsetInPixel = dp2Pixel(offsetLeft);
        bottomOffsetInPixel = dp2Pixel(offsetBottom);
        rectWidth = dp2Pixel(rectWidth);

        drawRectF = new RectF();
        setWeekArray(res);
    }

    private void setWeekArray(Resources resource) {
        WEEK_X_ARRAY = new String[]{
                resource.getString(R.string.week_1), resource.getString(R.string.week_2),
                resource.getString(R.string.week_3), resource.getString(R.string.week_4),
                resource.getString(R.string.week_5), resource.getString(R.string.week_6),
                resource.getString(R.string.week_7)};
    }

    public List getData() {
        return dataList;
    }

    public void setData(List list) {
        dataList = list;
    }

    private float dp2Pixel(float dp) {
        return dp * mMetrics.density;
    }

    public void setXLabelData(List xItem) {
        this.xItem = xItem;
    }

    public void setXLabelDataType(int type) {
        xType = type;
    }

    public void setYLabelData(List yItem) {
        this.yItem = yItem;
    }

    public void setRectWidth(int rectWidth) {
        this.rectWidth = dp2Pixel(rectWidth);
    }

    public void setRectColor(int rectColor) {
        this.rectColor = rectColor;
        rectPaint.setColor(rectColor);
    }

    public void setIsMultiData(boolean isMultiData) {
        this.isMultiData = isMultiData;
    }

    public void setyLabelUnit(String yLabelUnit) {
        this.yLabelUnit = yLabelUnit;
    }

    private String getXLabel(String label, int index) {
        if (index > xItem.size()) {
            return "";
        }
        if (xType == TYPE_TIME) {
            if (label.equals("24")) {
                label = "0";
            }
            return label;
        } else if (xType == TYPE_WEEK) {
            return WEEK_X_ARRAY[index];
        } else {
            return xItem.get(index);
        }
    }

    public void setDrawXLabelOnly(boolean drawXLabelOnly) {
        isDrawXLabelOnly = drawXLabelOnly;
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        width = w;
        height = h;
        positionLeft = 0;
        positionTop = 0;
        positionRight = positionLeft + width;
        positionBottom = positionTop + height;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (xItem == null || yItem == null) {
            return;
        }
        pointMap.clear();
        xStartPosition = positionLeft + leftOffsetInPixel;
        xEndPosition = positionRight - leftOffsetInPixel;
        xGap = (xEndPosition - xStartPosition) / (xItem.size());

        yStartPosition = positionBottom - positionTop;
        yEndPosition = 0;
        yBaseStartPosition = yStartPosition - getResources().getDimensionPixelOffset(R.dimen.view_margin_40dp);
        yBaseEndPosition = yEndPosition;

        if (isDrawXLabelOnly) {
            drawX(canvas);
            return;
        }
        if (dataList == null || dataList.size() == 0) {
            return;
        }

        yMaxData = Float.parseFloat(yItem.get(yItem.size() - 1));
        yDataForAveragePosition = (yBaseStartPosition - yBaseEndPosition) / yMaxData;
        yGap = (yBaseStartPosition - yBaseEndPosition) / (yItem.size() - 1);

        float yBaseStartPositionTmp = yBaseStartPosition;
        try {
            for (ChartDataBean mChartDataBean : dataList) {
                if (mChartDataBean.getDataY() <= 0 || mChartDataBean.getResIds() == null || mChartDataBean.getResIds().size() <= 0)
                    continue;
                if (mChartDataBean.getResIds() != null && mChartDataBean.getResIds().size() >= 1) {
                    for (Integer resId : mChartDataBean.getResIds()) {
                        Bitmap bitmap = SystemDataUtil.getBitmap(mContext, resId);
                        if (bitmap == null) {
                            continue;
                        }
                        yBaseStartPositionTmp -= bitmap.getHeight() + SDKUtils.dip2px(mContext, 4);
                    }
                }
                float tmp = (yBaseStartPositionTmp - yBaseEndPosition) / mChartDataBean.getDataY();
                if (yDataForAveragePosition > tmp) yDataForAveragePosition = tmp;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        drawX(canvas);
        drawY(canvas);

        drawData(canvas);
    }

    private void drawX(Canvas canvas) {
        float xBasePosition = 0f;
        int xItemSize = xItem.size();
        if (xType == TYPE_YEAR) {
            xBasePosition = xStartPosition + getResources().getDimensionPixelOffset(R.dimen.view_margin_4dp);
        } else if (xType == TYPE_MONTH || xType == TYPE_WEEK) {
            xBasePosition = xStartPosition + getResources().getDimensionPixelOffset(R.dimen.view_margin_12dp);
        } else {
            xBasePosition = xStartPosition + getResources().getDimensionPixelOffset(R.dimen.view_margin_24dp);
        }
        float yBasePosition = yStartPosition - bottomOffsetInPixel;
        for (int i = 0; i < xItemSize; ++i) {
            String tempXItem = xItem.get(i);
            float x = xBasePosition + xGap * i;
            float y = yBasePosition;
//            Log.d(TAG, "x = " + x + ",y = " + y);
            pointMap.put(tempXItem, new Point((int) x, (int) y));
            tempXItem = getXLabel(tempXItem, i);
            canvas.drawText(tempXItem + "", x, y, xPaint);
        }
    }

    private void drawData(Canvas canvas) {
        int nextKeyValue = -1;
        int previousValue = -1;
        for (int j = 0; j < dataList.size(); ++j) {
            ChartDataBean data = dataList.get(j);
            int dataOne = data.getDataX();
            float dataTwo = data.getDataY();
//            MyLog.d(TAG, "drawData dataOne = " + dataOne + ",dataTwo = " + dataTwo);

            float left = 0f;
            float top = 0f;
            float right = 0f;
            float bottom = 0f;
            boolean canDraw = false;
            int[] dataGap = getProperValueGap(dataOne);
            previousValue = dataGap[0];
            nextKeyValue = dataGap[1];

            Point tempPoint = pointMap.get("" + previousValue);
            if (tempPoint == null) {
                continue;
            }
            int x = tempPoint.x;
            int y = tempPoint.y;
            if (isMultiData) {
                if (dataOne >= previousValue && dataOne <= nextKeyValue) {
                    canDraw = true;
                }
                rectGap = (xGap - (rectWidth * (nextKeyValue - previousValue))) / (nextKeyValue - previousValue);
                left = x + (dataOne - previousValue) * (rectWidth + rectGap);
                top = yBaseStartPosition - yDataForAveragePosition * dataTwo;
                right = left + rectWidth;
                bottom = yBaseStartPosition;
            } else {
                canDraw = true;
                left = x;
                top = yBaseStartPosition - yDataForAveragePosition * dataTwo;
                right = left + rectWidth;
                bottom = yBaseStartPosition;
            }
            if (canDraw && dataTwo >= 0f) {
                drawRectF.set(left, top, right, bottom);
                canvas.drawRoundRect(drawRectF, 12f, 12f, rectPaint);
            }
            try {
                if (data.getResIds() != null && data.getResIds().size() >= 1) {
                    for (Integer resId : data.getResIds()) {
                        Bitmap bitmap = SystemDataUtil.getBitmap(mContext, resId);
                        if (bitmap == null) {
                            continue;
                        }
                        float mBpWidth = bitmap.getWidth() / 2f;
                        top = top - bitmap.getHeight() - SDKUtils.dip2px(mContext, 4);
                        canvas.drawBitmap(bitmap, left + (rectWidth / 2) - mBpWidth, top, null);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private int[] getProperValueGap(int currentData) {
        int nextKeyValue = -1;
        int previousValue = -1;
        int size = xItem.size();
        if (!isMultiData) {
            return new int[]{currentData, currentData};
        }
        for (int i = 0; i < xItem.size(); ++i) {
            String tempLabel = xItem.get(i);
            int nextKey = i + 1;
            int curHour = Integer.parseInt(tempLabel);
            if (nextKey < size) {
                previousValue = curHour;
                nextKeyValue = Integer.parseInt(xItem.get(nextKey));
            } else {
                nextKeyValue = curHour;
                if (currentData == curHour && i == size - 1) {
                    previousValue = Integer.parseInt(xItem.get(i - 1));
                } else {
                    previousValue = Integer.parseInt(xItem.get(nextKey - 1));
                }
            }
            if (currentData >= previousValue && currentData < nextKeyValue) {
                break;
            }
        }
//        MyLog.d(TAG, "getProperValueGap currentData = " + currentData + ",previousValue = " + previousValue + ",nextKeyValue = " + nextKeyValue);
        return new int[]{previousValue, nextKeyValue};
    }

    private void drawY(Canvas canvas) {
        int offset = SDKUtils.dip2px(getContext(), 2);
        for (int i = 0; i < yItem.size(); ++i) {
            String tempYItem = yItem.get(i);
            float yLinePosition = yBaseStartPosition - yGap * i;

            Rect rect = new Rect();
            yPaint.getTextBounds((tempYItem + yLabelUnit), 0, (tempYItem + yLabelUnit).length(), rect);
            int w = rect.width();
            int h = rect.height();

            canvas.drawText(tempYItem + yLabelUnit, xEndPosition - w + offset, yLinePosition + offset + h, yPaint);
//            Log.d(TAG, "drawY x = " + xEndPosition + ",yLinePosition = " + yLinePosition);
            canvas.drawLine(xStartPosition, yLinePosition, xEndPosition, yLinePosition, linePaint);
        }
    }
}

Article will be synced to wechat blog:Android部落格

你可能感兴趣的:(flutter,flutter)