最近项目中需要用到折线图,发现了一款比较好用的折线图hellocharts,做了炫酷的动画以及折线图,柱状图,混合图都有涉及到,非常的棒,在此推荐一下。

但是使用过程中碰到这样的需求:

①:不同数据点可以显示不同的颜色

②:还要有基准线

这可把我难坏了,看了下hellocharts的源码后,没有看到提供设置不同数据点的颜色以及基准线的接口(可能也是我看得不仔细吧)算了,不管这些了,项目马上要上线了,得赶紧做出来,主要的是去实现功能满足需求,当然了,满足了需求之后还得调整界面以及样式。话不多说,我们开始吧~  Σ(っ°Д°;)っ

来看看我做的效果吧:

   

接下来结合代码为大家分析一下大致流程(今天我就不直接放代码了,我要一步一步的介绍一下我的流程 ヾ(゚∀゚ゞ)   )(可爱的源码君依然是在文章最后):

一、首先将坐标轴X轴Y轴绘制出来

①、大部分的Y轴都用来展示数字类型的数据,X轴可数字类型也可文字类型,这就好办了,定义两个List,一个存Y轴的数字,一个存X轴的字符(数字的话可以转成字符串)

定义一个AxisValue类,用来存放轴上单个点的数据:

/*** Created by fySpring* Date : 2017/8/10* To do :x或y轴上的值*/public class AxisValue {private float value;  //值private String label;  //文字public AxisValue(float value) {setValue(value);}public AxisValue(float value, String label) {this.value = value;this.label = label;}public AxisValue(AxisValue axisValue) {this.value = axisValue.value;this.label = axisValue.label;}public float getValue() {return value;}public AxisValue setValue(float value) {this.value = value;return this;}public AxisValue setLabel(String label) {this.label = label;return this;}public String getLabel() {return label;}
}

定义一个Axis类,用来存放坐标轴的数据list,即我们的轴。在这个类中我们设置了一些属性,包括是否显示刻度线、刻度线的颜色、轴上文字的颜色、文字的大小、以及坐标值的List。这里得说一下spacingNum,当X轴上的坐标很多的情况下,全部显示很明显是不可能的,设置spacingNum后,即每隔spacingNum个点显示一个,由于X轴的数据可以为字符串,所以目前只针对X轴有效。

/*** Created by fySpring* Date : 2017/8/10* To do :x或y轴*/public class Axis {public static final int DEFAULT_TEXT_SIZE_SP = 12;/*** X轴间隔,每隔多少显示一个值,默认为1*/private int spacingNum = 1;/*** 是否显示轴上的刻度线*/private boolean hasLines = false;/*** 值的颜色*/private int textColor = Color.LTGRAY;/*** 值的字体大小*/private int textSize = DEFAULT_TEXT_SIZE_SP;/*** 刻度线的颜色*/private int lineColor = Color.LTGRAY;/*** 坐标值list*/private List<AxisValue> values = new ArrayList<>();public Axis() {}public Axis(List<AxisValue> values) {this.values = values;}public int getSpacingNum() {return spacingNum;}public void setSpacingNum(int spacingNum) {this.spacingNum = spacingNum;}public boolean isHasLines() {return hasLines;}public Axis setHasLines(boolean hasLines) {this.hasLines = hasLines;return this;}public int getTextColor() {return textColor;}public void setTextColor(int textColor) {this.textColor = textColor;}public int getTextSize() {return textSize;}public void setTextSize(int textSize) {this.textSize = textSize;}public int getLineColor() {return lineColor;}public void setLineColor(int lineColor) {this.lineColor = lineColor;}public List<AxisValue> getValues() {return values;}public void setValues(List<AxisValue> values) {this.values = values;}
}

②、关于轴相关的类已经建好,接下来就是绘制轴上的数据。绘制Y轴的数据需要注意一点,由于数据的宽度不一样,我们需要保证数据在Y轴能够右对齐,要先遍历每一个数据,然后测量其宽度,取出最大的宽度,为了保证高度上能够均分,需要预先算出X轴上文字的高度,这样就能保证X轴和Y轴上下都能留出一定的距离给数据展示。

设置Y轴的数据:

//获取数据中的X轴和Y轴Axis xAxis = data.getAxisX();Axis yAxis = data.getAxisY();//设置轴的颜色和字体大小paint.setColor(xAxis.getTextColor());paint.setTextSize(sp2px(xAxis.getTextSize()));//从X轴的数据中取出一个来测量底部需要的高度String firstStr = "测试";paint.getTextBounds(firstStr, 0, firstStr.length(), fontRect);// X轴的起始值,表示距离底部的Y的值int bottomY = getHeight() - fontRect.height() * 2 - paddingLeft;//Y轴上每一项的高度,用来均分,一定要用float来计算除法,切记!!!!float itemHeight = (float) bottomY / yAxis.getValues().size();//遍历Y轴的数据,取出占用宽度最大的数值,保持Y轴的数据能够右对齐widthList.clear();int maxWidth = 0;for (int i = 0; i < yAxis.getValues().size(); i++) {String contentStr = yAxis.getValues().get(i).getLabel();//测量字体宽度,取最大宽度paint.getTextBounds(contentStr, 0, contentStr.length(), fontRect);widthList.add(fontRect.width());if (fontRect.width() > maxWidth) maxWidth = fontRect.width();}//Y轴的起始值,表示距离左边的X的值int leftX = maxWidth + paddingLeft * 2;//绘制Y轴上的值及刻度for (int i = 0; i < yAxis.getValues().size(); i++) {String contentStr = yAxis.getValues().get(i).getLabel();float curY = itemHeight * (yAxis.getValues().size() - i);//绘制值canvas.drawText(contentStr, paddingLeft + maxWidth - widthList.get(i), curY + fontRect.height() / 2, paint);//绘制刻度线,0的时候不需要画刻度if (i != 0) {if (yAxis.isHasLines())canvas.drawLine(leftX, curY, getWidth() - paddingRight, curY, paint);elsecanvas.drawLine(leftX, curY, leftX + scaleHeight, curY, paint);}}

设置X轴上的数据,在这个地方会出现新的问题,为保证文字能够垂直居中,所以我们需要减去测量文字的高度的一半,但是!!!文字的高度不一样的话,就不会显示在中间。比如文字 一   和   日。咦,我为什么要说日。ノ´▽`)ノ♪      后来我想到一种做法,在底部放一个矩形,每个矩形的高度一样,然后将文字设置在矩形的中间。并且,宽度还要减去每个文字的宽度的一半。简直完美!

 //计算X轴要显示的数据个数int xUnit = (int) Math.ceil((double) xAxis.getValues().size() / xAxis.getSpacingNum());//X轴上每一项的宽度,切记,右边一定要记得转floatfloat itemWidth = (float) (getWidth() - leftX - paddingRight) / xUnit;int index = 0;for (int i = 0; i < xAxis.getValues().size(); i += xAxis.getSpacingNum()) {String contentStr = xAxis.getValues().get(i).getLabel();//测量字体宽度paint.getTextBounds(contentStr, 0, contentStr.length(), fontRect);float curX = leftX + index * itemWidth;//绘制坐标轴上的值,减掉字体宽度的一半是为了对齐刻度线//设置一个矩形格子,保证文字能够居中Rect targetRect = new Rect((int) curX - fontRect.width() / 2, bottomY, (int) curX + fontRect.width() / 2, getHeight());canvas.drawText(contentStr, targetRect.left, targetRect.centerY(), paint);//绘制坐标轴刻度线if (i != 0) {if (xAxis.isHasLines())canvas.drawLine(curX, 0, curX, bottomY, paint);elsecanvas.drawLine(curX, bottomY - scaleHeight, curX, bottomY, paint);}index++;}//绘制Y轴的线canvas.drawLine(leftX, 0, leftX, bottomY, paint);//绘制X轴的线canvas.drawLine(leftX, bottomY, getWidth() - paddingRight, bottomY, paint);

二、坐标轴已经绘制好了,我们开始绘制基准线,定义了一个BaseLine类,这个类里主要放了value和color,就不展示出来了,画线那里用到了DashPathEffect类。这个类的作用就是将Path的线段虚线化。构造函数为DashPathEffect(float[] intervals, float offset),其中intervals为虚线的ON和OFF数组,该数组的length必须大于等于2,offset为绘制时的偏移量。

effects = new DashPathEffect(new float[]{5, 5, 5, 5}, 1);
//绘制基准线if (data.getBaseLines().size() != 0) {float maxY = yAxis.getValues().get(yAxis.getValues().size() - 1).getValue();float minY = yAxis.getValues().get(0).getValue();for (BaseLine line : data.getBaseLines()) {path.reset();//基准值只能在最大值和最小值之间if (line.getBaseValue() < maxY && line.getBaseValue() > minY) {String contentStr = String.valueOf(line.getBaseValue());//测量字体宽度paint.getTextBounds(contentStr, 0, contentStr.length(), fontRect);//计算基准线的Y轴的位置float progress = 1 - (line.getBaseValue() / maxY);float curY = (bottomY - itemHeight) * progress + itemHeight;float toX = getWidth() - paddingRight - fontRect.width();paint.setColor(line.getBaseColor());paint.setStyle(Paint.Style.STROKE);paint.setPathEffect(effects);path.moveTo(leftX, curY);path.lineTo(toX, curY);canvas.drawPath(path, paint);paint.setStyle(Paint.Style.FILL);paint.setAntiAlias(true);canvas.drawText(contentStr, toX + paddingRight / 2, curY + fontRect.height() / 2, paint);}}}

三、基准线绘制好了,接下来就是绘制点和线了。这里也得注意,先要绘制线,再绘制点,因为点的颜色会不一样,再绘制线的话会盖在点的上面。我的做法是先将每个点在屏幕的坐标位置根据点的X轴Y轴的值计算出来。

①、首先我们需要一个PointValue类来存储点的位置,这里没什么好说的,看名字就能明白:

/*** Created by fySpring* Date : 2017/8/10* To do :点的坐标值及颜色*/public class PointValue {private float xValue;       //点在X轴上的值private float yValue;       //点在Y轴上的值private float xPosition;    //点在X轴上的位置private float yPosition;    //点在Y轴上的位置private String label;private int pointColor;public PointValue(float xValue, float yValue) {setData(xValue, yValue);}public PointValue(PointValue pointValue) {setData(pointValue.xValue, pointValue.yValue);this.label = pointValue.label;}public PointValue setData(float xValue, float yValue) {this.xValue = xValue;this.yValue = yValue;return this;}public float getxValue() {return xValue;}public void setxValue(float xValue) {this.xValue = xValue;}public float getyValue() {return yValue;}public void setyValue(float yValue) {this.yValue = yValue;}public float getxPosition() {return xPosition;}public void setxPosition(float xPosition) {this.xPosition = xPosition;}public float getyPosition() {return yPosition;}public void setyPosition(float yPosition) {this.yPosition = yPosition;}public String getLabel() {return TextUtils.isEmpty(label) ? String.valueOf(yValue) : label;}public void setLabel(String label) {this.label = label;}public int getPointColor() {return pointColor;}public PointValue setPointColor(int pointColor) {this.pointColor = pointColor;return this;}
}

②、其次我们需要把每个点串起来的Line类,这里开放了几个属性,如,线的颜色,点的颜色,宽度,是否是平滑曲线还是直线,是否显示当前值等,当然了你也可以拿过去定制你需要的属性:

/*** Created by fySpring* Date : 2017/8/10* To do :折线图中每条线的数据*/public class Line {private static final int DEFAULT_LINE_STROKE_WIDTH_DP = 1;private static final int DEFAULT_POINT_RADIUS_DP = 3;/*** 线的颜色,默认红色*/private int lineColor = Color.RED;/*** 圆点的颜色*/private int pointColor;/*** 线的宽度*/private int strokeWidth = DEFAULT_LINE_STROKE_WIDTH_DP;/*** 圆点的半径*/private int pointRadius = DEFAULT_POINT_RADIUS_DP;/*** 是否显示小圆点*/private boolean hasPoints = true;/*** 是否显示值*/private boolean hasLabels = false;/*** 折线是否圆滑*/private boolean isSmooth = false;private List<PointValue> values = new ArrayList<>();public Line(List<PointValue> values) {this.values = values;}public int getLineColor() {return lineColor;}public void setLineColor(int lineColor) {this.lineColor = lineColor;}public int getPointColor() {//如果没有设置则返回线的颜色return pointColor == 0 ? lineColor : pointColor;}public void setPointColor(int pointColor) {this.pointColor = pointColor;}public int getStrokeWidth() {return strokeWidth;}public void setStrokeWidth(int strokeWidth) {this.strokeWidth = strokeWidth;}public int getPointRadius() {return pointRadius;}public void setPointRadius(int pointRadius) {this.pointRadius = pointRadius;}public boolean isHasPoints() {return hasPoints;}public void setHasPoints(boolean hasPoints) {this.hasPoints = hasPoints;}public boolean isHasLabels() {return hasLabels;}public void setHasLabels(boolean hasLabels) {this.hasLabels = hasLabels;}public boolean isSmooth() {return isSmooth;}public void setSmooth(boolean smooth) {isSmooth = smooth;}public List<PointValue> getValues() {return values;}public void setValues(List<PointValue> values) {this.values = values;}
}

③、然后我们需要一个类用来存放我们所有的数据,这样在代码中我们只需要几行代码就能显示出折线图,

/*** Created by fySpring* Date : 2017/8/10* To do :折线图数据类*/public class LineChartData implements ChartData{private static final int DEFAULT_TEXT_SIZE_SP = 12;private Axis axisX;private Axis axisY;private int valueLabelTextColor = Color.WHITE;private int valueLabelTextSize = DEFAULT_TEXT_SIZE_SP;private List<BaseLine> baseLines = new ArrayList<>();private List<Line> lines = new ArrayList<>();public LineChartData(List<Line> lines) {this.lines = lines;}@Overridepublic Axis getAxisX() {return axisX;}@Overridepublic void setAxisX(Axis axisX) {this.axisX = axisX;}@Overridepublic Axis getAxisY() {return axisY;}@Overridepublic void setAxisY(Axis axisY) {this.axisY = axisY;}@Overridepublic int getValueLabelTextColor() {return valueLabelTextColor;}@Overridepublic void setValueLabelsTextColor(int valueLabelTextColor) {this.valueLabelTextColor = valueLabelTextColor;}@Overridepublic int getValueLabelTextSize() {return valueLabelTextSize;}@Overridepublic void setValueLabelTextSize(int valueLabelTextSize) {this.valueLabelTextSize = valueLabelTextSize;}public List<Line> getLines() {return lines;}public List<BaseLine> getBaseLines() {return baseLines;}public void setBaseLines(List<BaseLine> baseLines) {this.baseLines = baseLines;}
}

④、最后再来根据线的个数去绘制点和线,通过Y的最大值来计算点的Y轴的位置

            //计算点在屏幕中的位置if (data.getLines().size() != 0) {float maxY = yAxis.getValues().get(yAxis.getValues().size() - 1).getValue();for (Line line : data.getLines()) {for (PointValue value : line.getValues()) {//根据横纵坐标计算点在屏幕中的位置,如果没有线的话可以在这里画点float xProgress = value.getxValue() / xAxis.getValues().size();float yProgress = 1 - (value.getyValue() / maxY);//点的坐标float centerX = leftX + (getWidth() - leftX - paddingRight) * xProgress;float centerY = (bottomY - itemHeight) * yProgress + itemHeight;value.setxPosition(centerX);value.setyPosition(centerY);}//根据数据值画线与点drawLineAndPoint(canvas, line);}}

最关键的绘制线的地方在这里,由于比较复杂,我就单独封装了一个方法,记得绘制之前将path和paint给reset一下。(这里可得注意点,一不留心就会出问题)

/*** 根据点绘制贝赛尔曲线和点,绘制贝赛尔曲线每次需要三个点** @param canvas* @param line*/private void drawLineAndPoint(Canvas canvas, Line line) {path.reset();paint.reset();paint.setAntiAlias(true);paint.setStyle(Paint.Style.STROKE);paint.setColor(line.getLineColor());paint.setStrokeWidth(dip2px(line.getStrokeWidth()));if (line.isSmooth()) {//绘制圆滑曲线float prePreviousPointX = Float.NaN;float prePreviousPointY = Float.NaN;float previousPointX = Float.NaN;float previousPointY = Float.NaN;float currentPointX = Float.NaN;float currentPointY = Float.NaN;float nextPointX = Float.NaN;float nextPointY = Float.NaN;int valueSize = line.getValues().size();for (int valueIndex = 0; valueIndex < valueSize; valueIndex++) {if (Float.isNaN(currentPointX)) {PointValue linePoint = line.getValues().get(valueIndex);currentPointX = linePoint.getxPosition();currentPointY = linePoint.getyPosition();}if (Float.isNaN(previousPointX)) {if (valueIndex > 0) {PointValue linePoint = line.getValues().get(valueIndex - 1);previousPointX = linePoint.getxPosition();previousPointY = linePoint.getyPosition();} else {previousPointX = currentPointX;previousPointY = currentPointY;}}if (Float.isNaN(prePreviousPointX)) {if (valueIndex > 1) {PointValue linePoint = line.getValues().get(valueIndex - 2);prePreviousPointX = linePoint.getxPosition();prePreviousPointY = linePoint.getyPosition();} else {prePreviousPointX = previousPointX;prePreviousPointY = previousPointY;}}// 设置当前点的下一个点的坐标if (valueIndex < valueSize - 1) {PointValue linePoint = line.getValues().get(valueIndex + 1);nextPointX = linePoint.getxPosition();nextPointY = linePoint.getyPosition();} else {nextPointX = currentPointX;nextPointY = currentPointY;}if (valueIndex == 0) {// 将第一个点设为起始点path.moveTo(currentPointX, currentPointY);} else {// 计算出每个点的控制点final float firstDiffX = (currentPointX - prePreviousPointX);final float firstDiffY = (currentPointY - prePreviousPointY);final float secondDiffX = (nextPointX - previousPointX);final float secondDiffY = (nextPointY - previousPointY);final float firstControlPointX = previousPointX + (LINE_SMOOTHNESS * firstDiffX);final float firstControlPointY = previousPointY + (LINE_SMOOTHNESS * firstDiffY);final float secondControlPointX = currentPointX - (LINE_SMOOTHNESS * secondDiffX);final float secondControlPointY = currentPointY - (LINE_SMOOTHNESS * secondDiffY);path.cubicTo(firstControlPointX, firstControlPointY, secondControlPointX, secondControlPointY,currentPointX, currentPointY);}// 将值后移prePreviousPointX = previousPointX;prePreviousPointY = previousPointY;previousPointX = currentPointX;previousPointY = currentPointY;currentPointX = nextPointX;currentPointY = nextPointY;}} else {//绘制直线for (int i = 0; i < line.getValues().size(); i++) {PointValue value = line.getValues().get(i);//第一个点不需要连接if (i == 0) {path.moveTo(value.getxPosition(), value.getyPosition());} else {path.lineTo(value.getxPosition(), value.getyPosition());}}}canvas.drawPath(path, paint);//如果有点的话就进行画圆点if (line.isHasPoints()) {paint.setTextSize(defaultTextSize);paint.setStyle(Paint.Style.FILL);for (PointValue pointValue : line.getValues()) {if (pointValue.getPointColor() != 0) {paint.setColor(pointValue.getPointColor());} else {paint.setColor(line.getPointColor());}//画圆点canvas.drawCircle(pointValue.getxPosition(), pointValue.getyPosition(), dip2px(line.getPointRadius()), paint);if (line.isHasLabels()) {String contentStr = pointValue.getLabel();//测量字体宽高paint.getTextBounds(contentStr, 0, contentStr.length(), fontRect);//绘制点的顶部数据canvas.drawText(contentStr, pointValue.getxPosition() - fontRect.width() / 2, pointValue.getyPosition() - 20, paint);}}}}

最后再放上完整的LineChartView吧,好方便大家对应着去看:

public class LineChartView extends View {private static final float LINE_SMOOTHNESS = 0.16f;private Context context;private LineChartData data;private int paddingLeft;private int paddingRight;//刻度的高度,只在hasLine为false的情况下显示private int scaleHeight;//绘制虚线private PathEffect effects;private Paint paint;private Path path;//路径private Rect fontRect; //测量字体private List<Integer> widthList;private int defaultTextSize;public LineChartView(Context context) {super(context, null, 0);this.context = context;init();}public LineChartView(Context context, AttributeSet attrs) {super(context, attrs, 0);this.context = context;init();}public LineChartView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);this.context = context;init();}private void init() {paint = new Paint();path = new Path();paint.setAntiAlias(true);fontRect = new Rect();widthList = new ArrayList<>();effects = new DashPathEffect(new float[]{5, 5, 5, 5}, 1);paddingLeft = dip2px(10);paddingRight = dip2px(10);scaleHeight = dip2px(5);defaultTextSize = sp2px(12);}public void setData(LineChartData data) {this.data = data;invalidate();}public LineChartData getData() {return data;}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);if (data != null && data.getAxisY().getValues().size() != 0 && data.getAxisX().getValues().size() != 0) {path.reset();paint.setStrokeWidth(1);paint.setStyle(Paint.Style.FILL);//获取数据中的X轴和Y轴Axis xAxis = data.getAxisX();Axis yAxis = data.getAxisY();//设置轴的颜色和字体大小paint.setColor(xAxis.getTextColor());paint.setTextSize(sp2px(xAxis.getTextSize()));//从X轴的数据中取出一个来测量底部需要的高度String firstStr = "测试";paint.getTextBounds(firstStr, 0, firstStr.length(), fontRect);// X轴的起始值,表示距离底部的Y的值int bottomY = getHeight() - fontRect.height() * 2 - paddingLeft;//Y轴上每一项的高度,用来均分,一定要用float来计算除法,切记!!!!float itemHeight = (float) bottomY / yAxis.getValues().size();//遍历Y轴的数据,取出占用宽度最大的数值,保持Y轴的数据能够右对齐widthList.clear();int maxWidth = 0;for (int i = 0; i < yAxis.getValues().size(); i++) {String contentStr = yAxis.getValues().get(i).getLabel();//测量字体宽度,取最大宽度paint.getTextBounds(contentStr, 0, contentStr.length(), fontRect);widthList.add(fontRect.width());if (fontRect.width() > maxWidth) maxWidth = fontRect.width();}//Y轴的起始值,表示距离左边的X的值int leftX = maxWidth + paddingLeft * 2;//绘制Y轴上的值及刻度for (int i = 0; i < yAxis.getValues().size(); i++) {String contentStr = yAxis.getValues().get(i).getLabel();float curY = itemHeight * (yAxis.getValues().size() - i);//绘制值canvas.drawText(contentStr, paddingLeft + maxWidth - widthList.get(i), curY + fontRect.height() / 2, paint);//绘制刻度线,0的时候不需要画刻度if (i != 0) {if (yAxis.isHasLines())canvas.drawLine(leftX, curY, getWidth() - paddingRight, curY, paint);elsecanvas.drawLine(leftX, curY, leftX + scaleHeight, curY, paint);}}//计算X轴要显示的数据个数int xUnit = (int) Math.ceil((double) xAxis.getValues().size() / xAxis.getSpacingNum());//X轴上每一项的宽度,切记,右边一定要记得转floatfloat itemWidth = (float) (getWidth() - leftX - paddingRight) / xUnit;int index = 0;for (int i = 0; i < xAxis.getValues().size(); i += xAxis.getSpacingNum()) {String contentStr = xAxis.getValues().get(i).getLabel();//测量字体宽度paint.getTextBounds(contentStr, 0, contentStr.length(), fontRect);float curX = leftX + index * itemWidth;//绘制坐标轴上的值,减掉字体宽度的一半是为了对齐刻度线//设置一个矩形格子,保证文字能够居中Rect targetRect = new Rect((int) curX - fontRect.width() / 2, bottomY, (int) curX + fontRect.width() / 2, getHeight());canvas.drawText(contentStr, targetRect.left, targetRect.centerY(), paint);//绘制坐标轴刻度线if (i != 0) {if (xAxis.isHasLines())canvas.drawLine(curX, 0, curX, bottomY, paint);elsecanvas.drawLine(curX, bottomY - scaleHeight, curX, bottomY, paint);}index++;}//绘制Y轴的线canvas.drawLine(leftX, 0, leftX, bottomY, paint);//绘制X轴的线canvas.drawLine(leftX, bottomY, getWidth() - paddingRight, bottomY, paint);//绘制基准线if (data.getBaseLines().size() != 0) {float maxY = yAxis.getValues().get(yAxis.getValues().size() - 1).getValue();float minY = yAxis.getValues().get(0).getValue();for (BaseLine line : data.getBaseLines()) {path.reset();//基准值只能在最大值和最小值之间if (line.getBaseValue() < maxY && line.getBaseValue() > minY) {String contentStr = String.valueOf(line.getBaseValue());//测量字体宽度paint.getTextBounds(contentStr, 0, contentStr.length(), fontRect);//计算基准线的Y轴的位置float progress = 1 - (line.getBaseValue() / maxY);float curY = (bottomY - itemHeight) * progress + itemHeight;float toX = getWidth() - paddingRight - fontRect.width();paint.setColor(line.getBaseColor());paint.setStyle(Paint.Style.STROKE);paint.setPathEffect(effects);path.moveTo(leftX, curY);path.lineTo(toX, curY);canvas.drawPath(path, paint);paint.setStyle(Paint.Style.FILL);paint.setAntiAlias(true);canvas.drawText(contentStr, toX + paddingRight / 2, curY + fontRect.height() / 2, paint);}}}//计算点在屏幕中的位置if (data.getLines().size() != 0) {float maxY = yAxis.getValues().get(yAxis.getValues().size() - 1).getValue();for (Line line : data.getLines()) {for (PointValue value : line.getValues()) {//根据横纵坐标计算点在屏幕中的位置,如果没有线的话可以在这里画点float xProgress = value.getxValue() / xAxis.getValues().size();float yProgress = 1 - (value.getyValue() / maxY);//点的坐标float centerX = leftX + (getWidth() - leftX - paddingRight) * xProgress;float centerY = (bottomY - itemHeight) * yProgress + itemHeight;value.setxPosition(centerX);value.setyPosition(centerY);}//根据数据值画线与点drawLineAndPoint(canvas, line);}}}}/*** 根据点绘制贝赛尔曲线和点,绘制贝赛尔曲线每次需要三个点** @param canvas* @param line*/private void drawLineAndPoint(Canvas canvas, Line line) {path.reset();paint.reset();paint.setAntiAlias(true);paint.setStyle(Paint.Style.STROKE);paint.setColor(line.getLineColor());paint.setStrokeWidth(dip2px(line.getStrokeWidth()));if (line.isSmooth()) {//绘制圆滑曲线float prePreviousPointX = Float.NaN;float prePreviousPointY = Float.NaN;float previousPointX = Float.NaN;float previousPointY = Float.NaN;float currentPointX = Float.NaN;float currentPointY = Float.NaN;float nextPointX = Float.NaN;float nextPointY = Float.NaN;int valueSize = line.getValues().size();for (int valueIndex = 0; valueIndex < valueSize; valueIndex++) {if (Float.isNaN(currentPointX)) {PointValue linePoint = line.getValues().get(valueIndex);currentPointX = linePoint.getxPosition();currentPointY = linePoint.getyPosition();}if (Float.isNaN(previousPointX)) {if (valueIndex > 0) {PointValue linePoint = line.getValues().get(valueIndex - 1);previousPointX = linePoint.getxPosition();previousPointY = linePoint.getyPosition();} else {previousPointX = currentPointX;previousPointY = currentPointY;}}if (Float.isNaN(prePreviousPointX)) {if (valueIndex > 1) {PointValue linePoint = line.getValues().get(valueIndex - 2);prePreviousPointX = linePoint.getxPosition();prePreviousPointY = linePoint.getyPosition();} else {prePreviousPointX = previousPointX;prePreviousPointY = previousPointY;}}// 设置当前点的下一个点的坐标if (valueIndex < valueSize - 1) {PointValue linePoint = line.getValues().get(valueIndex + 1);nextPointX = linePoint.getxPosition();nextPointY = linePoint.getyPosition();} else {nextPointX = currentPointX;nextPointY = currentPointY;}if (valueIndex == 0) {// 将第一个点设为起始点path.moveTo(currentPointX, currentPointY);} else {// 计算出每个点的控制点final float firstDiffX = (currentPointX - prePreviousPointX);final float firstDiffY = (currentPointY - prePreviousPointY);final float secondDiffX = (nextPointX - previousPointX);final float secondDiffY = (nextPointY - previousPointY);final float firstControlPointX = previousPointX + (LINE_SMOOTHNESS * firstDiffX);final float firstControlPointY = previousPointY + (LINE_SMOOTHNESS * firstDiffY);final float secondControlPointX = currentPointX - (LINE_SMOOTHNESS * secondDiffX);final float secondControlPointY = currentPointY - (LINE_SMOOTHNESS * secondDiffY);path.cubicTo(firstControlPointX, firstControlPointY, secondControlPointX, secondControlPointY,currentPointX, currentPointY);}// 将值后移prePreviousPointX = previousPointX;prePreviousPointY = previousPointY;previousPointX = currentPointX;previousPointY = currentPointY;currentPointX = nextPointX;currentPointY = nextPointY;}} else {//绘制直线for (int i = 0; i < line.getValues().size(); i++) {PointValue value = line.getValues().get(i);//第一个点不需要连接if (i == 0) {path.moveTo(value.getxPosition(), value.getyPosition());} else {path.lineTo(value.getxPosition(), value.getyPosition());}}}canvas.drawPath(path, paint);//如果有点的话就进行画圆点if (line.isHasPoints()) {paint.setTextSize(defaultTextSize);paint.setStyle(Paint.Style.FILL);for (PointValue pointValue : line.getValues()) {if (pointValue.getPointColor() != 0) {paint.setColor(pointValue.getPointColor());} else {paint.setColor(line.getPointColor());}//画圆点canvas.drawCircle(pointValue.getxPosition(), pointValue.getyPosition(), dip2px(line.getPointRadius()), paint);if (line.isHasLabels()) {String contentStr = pointValue.getLabel();//测量字体宽高paint.getTextBounds(contentStr, 0, contentStr.length(), fontRect);//绘制点的顶部数据canvas.drawText(contentStr, pointValue.getxPosition() - fontRect.width() / 2, pointValue.getyPosition() - 20, paint);}}}}/*** 根据手机的分辨率从 dp 的单位 转成为 px(像素)*/private int dip2px(float dpValue) {float scale = context.getResources().getDisplayMetrics().density;return (int) (dpValue * scale + 0.5f);}/*** 将sp值转换为px值,保证文字大小不变** @param spValue (DisplayMetrics类中属性scaledDensity)* @return*/public int sp2px(float spValue) {float fontScale = context.getResources().getDisplayMetrics().scaledDensity;return (int) (spValue * fontScale + 0.5f);}}

好了,基本上主要的内容就是以上的这些了。来看看在界面中如何使用:

public class MainActivity extends AppCompatActivity {private String rankColors[] = new String[]{"#98D675", "#8DD0EA", "#F6CE6E", "#FF7077", "#C15667"};private LineChartView chartView;private Button addPointBtn;private List<String> xValueList = new ArrayList<>();private List<PointValue> pointValues = new ArrayList<>();private List<Line> lineList = new ArrayList<>();private Random random = new Random();@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);chartView = (LineChartView) findViewById(R.id.line_chart_view);addPointBtn = (Button) findViewById(R.id.add_new_point_btn);for (int i = 1; i < 16; i++) {xValueList.add("测试"+i);}pointValues.add(new PointValue(0f,15f));lineList = initDataLine(pointValues);chartView.setData(initData(lineList,xValueList));addPointBtn.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {addNewPoint();}});}private void addNewPoint(){float x = random.nextInt(15);float y = random.nextInt(20);int colorInt = random.nextInt(5);//生成随机颜色,随机位置的点在坐标轴上pointValues.add(new PointValue(x,y).setPointColor(Color.parseColor(rankColors[colorInt])));lineList = initDataLine(pointValues);chartView.setData(initData(lineList,xValueList));}/*** 初始化线属性** @return*/private List<Line> initDataLine(List<PointValue> pointValueList) {List<Line> lineList = new ArrayList<>();
    //这里可以设置多条数据线,目前我这里展示只有一条Line bgLine = new Line(pointValueList);bgLine.setSmooth(true);bgLine.setHasLabels(true);bgLine.setPointColor(Color.RED);bgLine.setLineColor(Color.GREEN);bgLine.setHasPoints(true);lineList.add(bgLine);return lineList;}/*** 初始化记录曲线** @return*/private LineChartData initData(List<Line> lines, List<String> xDatas) {LineChartData data = new LineChartData(lines);Axis axisY = new Axis();Axis axisX = new Axis();axisX.setSpacingNum(3);//设置x轴数值List<AxisValue> xValues = new ArrayList<>();for (int i = 0; i < xDatas.size(); i++) {AxisValue value = new AxisValue(i);String label = xDatas.get(i);value.setLabel(label);xValues.add(value);}axisX.setValues(xValues);//设置y轴数值List<AxisValue> yValues = new ArrayList<>();for (int i = 0; i <= 20; i += 5) {AxisValue value = new AxisValue(i);String label = String.valueOf(i);value.setLabel(label);yValues.add(value);}axisY.setValues(yValues);//设置X轴Y轴data.setAxisY(axisY);data.setAxisX(axisX);//设置基准线List<BaseLine> baseLineList = new ArrayList<>();BaseLine baseLine1 = new BaseLine().setBaseValue(17.5f).setBaseColor(Color.RED);BaseLine baseLine2 = new BaseLine().setBaseValue(6.89f).setBaseColor(Color.BLUE);baseLineList.add(baseLine1);baseLineList.add(baseLine2);data.setBaseLines(baseLineList);return data;}
}

好啦,基本上就是这些了,各位老铁觉得不错的话不妨点个赞?   罒ω罒

源码点我点我

Android自定义折线图,可设置基准线,不同点颜色相关推荐

  1. Android 自定义折线图实现教程

    前言: 各位同学大家好,有段时间没有给大家更新文章了,具体多久我也记不清楚了.最近重新复习了一下原生安卓的知识点,写了一个安卓原生自定义折线图的效果,就想着分享给大家.希望帮助到各位学习和工作,那份废 ...

  2. 新手如何画出自定义View(Android——自定义折线图)

    在正式开始之前 我还是打算先说几句废话: 1.本文章是让初学者画自定义View所以不会对代码进行过多的解释 2.为什么不用现有强大的图表框架 列如:Android HelloChart 或者 MPAn ...

  3. android 自定义折线图

    看图: 比较简陋,主要是通过canvas画上去的: package com.example.democurvegraph.view;import java.util.ArrayList; import ...

  4. android自定义折线图可左右滑动,25.Android自定义折线图,可左右滑动

    前言 最近项目需要折线图,自己就手画了一个,主要是锻炼下自己的自定义控件技术.控件没有实现惯性滑动,希望知道的朋友指教下. 效果图 hehe.gif 直接上代码 public class MyLine ...

  5. android自定义曲线控件,Android自定义折线图(可拖动显示)

    废话不多说先上图咯 图一 至于怎么做呢 咱们可以先获取下折线图数据分析一波 { "code": 200, "message": "", &q ...

  6. matplotlib.pyplot常用画图方式函数封装(一)——.plot绘制折线图及设置坐标轴箭头完美解决

    matplotlib.pyplot常用画图方式函数封装(一)--.plot绘制折线图及设置坐标轴箭头完美解决 py.plot常见绘图设置函数封装 绘制函数图像(完美解决坐标轴添加箭头) 绘制折线图 p ...

  7. android自定义弧度按钮,Android 自定义View 绘制六边形设置按钮

    今天逛酷安的时候,发现酷安的设置按钮(截图的右上角),是一个六边形 + 中心圆的图标,所以又是一个自定义View练习对象了.画圆很简单,知道半径即可,而重点就在画出六边形. 酷安截图.png 最终效果 ...

  8. pyqt5,Qchart画折线图,设定多个不同刻度的Y轴,修改自定义折线图的坐标轴,改变折线图的底色

    就是这些自己遇到的需求: 如有不对,请大佬不吝赐教. Qchart设定多个不同刻度的Y轴 修改自定义折线图的坐标轴 改变折线图的底色,底色透明 鼠标悬停事件 import datetime impor ...

  9. Python使用matplotlib函数subplot可视化多个不同颜色的折线图、自定义数据点的形状、自定义折线图的颜色

    Python使用matplotlib函数subplot可视化多个不同颜色的折线图.自定义数据点的形状.自定义折线图的颜色 目录

最新文章

  1. Android实现对图片的缩放、剪切、旋转、存储
  2. I.MX6 WIFI wireless_tools 移植
  3. 密码可见_教你如何批量删除微博、设置仅自己可见、仅好友可见等
  4. Rational Rose 2003 下载、破解及安装方法(图文)
  5. python编程第八讲答案_小甲鱼Python第八讲课后习题
  6. 智能云媒体服务器,智慧广电媒体解决方案
  7. Why React?
  8. java gps 距离计算_Java教程之地图中计算两个GPS坐标点的距离
  9. 编程基础(三)——体系结构
  10. 热点账户高并发解决方案
  11. 如何使用AxureShare+Axure RP 8.0创建团队项目,实现团队协同
  12. GATK / 体细胞短变异检测工具Mutect2的使用
  13. WEB入门.七 CSS布局模型
  14. D365几个功能开发思路
  15. 2021-09-12 Autodesk inventor 技巧整理
  16. python3 类似‘\xe4\xbd\xa0\xe5\xa5\xbd.pdf‘这种str到byte的转换
  17. VNC连接Too many security failures
  18. 如何给女友挑选黑丝?用Python来分析一波哪一款好看
  19. 供水管道损坏的原因及预防修复措施
  20. http://xmuzyq.iteye.com/blog/783218

热门文章

  1. 计算机专业白色简历封面,计算机专业个人简历封面模板图
  2. 竞品分析:小宇宙APP——如何在播客领域站住脚?
  3. TKDragView_iOS开发常用第三方开源框架 持续更新中...
  4. 旋转卡壳简介(POJ2187)(洛谷P1452)
  5. HUAWEI篇 NGFW与AR网关建立GRE over IPSec隧道
  6. 【渝粤教育】电大中专机械制造技术基础作业 题库
  7. python排名差怎么改,2017中国大学排名爬虫代码修改
  8. 解决active样式在ios手机上没有生效的问题
  9. java Bean拷贝忽略空属性
  10. 用卷积神经网络实现笑脸、非笑脸表情识别