废话少说先上图。(支持Y轴放大缩小)
40~125一个颜色 125~147一个颜色 147~188一个颜色 188以上是一个颜色。自己测试随便订的范围,无实际意义。
开始撸代码、首先继承MPAndroidChart里的LineChart类
public class MyLineChart extends LineChart {
public MyLineChart(Context context) {
super(context);
}
public MyLineChart(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyLineChart(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void init() {
super.init();
//mRenderer 渲染器 设置为自己定义的渲染器
mRenderer = new MyLineChartRenderer(this, mAnimator, mViewPortHandler);
}
@Override
public LineData getLineData() {
return mData;
}
@Override
protected void onDetachedFromWindow() {
// releases the bitmap in the renderer to avoid oom error
if (mRenderer != null && mRenderer instanceof MyLineChartRenderer) {
((MyLineChartRenderer) mRenderer).releaseBitmap();
}
super.onDetachedFromWindow();
}
}
然后继承折线图的渲染器LineChartRenderer类进行重写。代码如下
public class MyLineChartRenderer extends LineChartRenderer {
private Paint mHighlightCirclePaint;
private boolean isHeart;
float[] pos; //颜色区间的位置
int[] colors; //区间的颜色
int[] range; //区间范围的值 如果不需要判断高亮小圆点的值 可以删除
private ViewPortHandler viewPortHandler;
public MyLineChartRenderer(LineDataProvider chart, ChartAnimator animator,
ViewPortHandler viewPortHandler) {
super(chart, animator, viewPortHandler);
mChart = chart;
mCirclePaintInner = new Paint(Paint.ANTI_ALIAS_FLAG);
mCirclePaintInner.setStyle(Paint.Style.FILL);
mCirclePaintInner.setColor(Color.WHITE);
mHighlightCirclePaint = new Paint();
this.viewPortHandler = viewPortHandler;
}
private float[] mLineBuffer = new float[4];
@Override
protected void drawLinear(Canvas c, ILineDataSet dataSet) {
int entryCount = dataSet.getEntryCount();
final boolean isDrawSteppedEnabled = dataSet.isDrawSteppedEnabled();
final int pointsPerEntryPair = isDrawSteppedEnabled ? 4 : 2;
Transformer trans = mChart.getTransformer(dataSet.getAxisDependency());
float phaseY = mAnimator.getPhaseY();
mRenderPaint.setStyle(Paint.Style.STROKE);
Canvas canvas = null;
// if the data-set is dashed, draw on bitmap-canvas
if (dataSet.isDashedLineEnabled()) {
canvas = mBitmapCanvas;
} else {
canvas = c;
}
mXBounds.set(mChart, dataSet);
// if drawing filled is enabled
if (dataSet.isDrawFilledEnabled() && entryCount > 0) {
drawLinearFill(c, dataSet, trans, mXBounds);
}
// more than 1 color
if (dataSet.getColors().size() > 1) {
if (mLineBuffer.length <= pointsPerEntryPair * 2)
mLineBuffer = new float[pointsPerEntryPair * 4];
for (int j = mXBounds.min; j <= mXBounds.range + mXBounds.min; j++) {
Entry e = dataSet.getEntryForIndex(j);
if (e == null) continue;
if (e.getY() == 0) continue;
mLineBuffer[0] = e.getX();
mLineBuffer[1] = e.getY() * phaseY;
if (j < mXBounds.max) {
e = dataSet.getEntryForIndex(j + 1);
if (e == null) break;
if (e.getY() == 0) break;
if (isDrawSteppedEnabled) {
mLineBuffer[2] = e.getX();
mLineBuffer[3] = mLineBuffer[1];
mLineBuffer[4] = mLineBuffer[2];
mLineBuffer[5] = mLineBuffer[3];
mLineBuffer[6] = e.getX();
mLineBuffer[7] = e.getY() * phaseY;
} else {
mLineBuffer[2] = e.getX();
mLineBuffer[3] = e.getY() * phaseY;
}
} else {
mLineBuffer[2] = mLineBuffer[0];
mLineBuffer[3] = mLineBuffer[1];
}
trans.pointValuesToPixel(mLineBuffer);
if (!mViewPortHandler.isInBoundsRight(mLineBuffer[0]))
break;
// make sure the lines don't do shitty things outside
// bounds
if (!mViewPortHandler.isInBoundsLeft(mLineBuffer[2])
|| (!mViewPortHandler.isInBoundsTop(mLineBuffer[1]) && !mViewPortHandler
.isInBoundsBottom(mLineBuffer[3])))
continue;
// get the color that is set for this line-segment
mRenderPaint.setColor(dataSet.getColor(j));
canvas.drawLines(mLineBuffer, 0, pointsPerEntryPair * 2, mRenderPaint);
}
} else { // only one color per dataset
if (mLineBuffer.length < Math.max((entryCount) * pointsPerEntryPair, pointsPerEntryPair) * 2)
mLineBuffer = new float[Math.max((entryCount) * pointsPerEntryPair, pointsPerEntryPair) * 4];
Entry e1, e2;
e1 = dataSet.getEntryForIndex(mXBounds.min);
if (e1 != null) {
int j = 0;
for (int x = mXBounds.min; x <= mXBounds.range + mXBounds.min; x++) {
e1 = dataSet.getEntryForIndex(x == 0 ? 0 : (x - 1));
e2 = dataSet.getEntryForIndex(x);
if (e1.getY() == 0 || e2.getY() == 0) {
continue;
}
mLineBuffer[j++] = e1.getX();
mLineBuffer[j++] = e1.getY() * phaseY;
if (isDrawSteppedEnabled) {
mLineBuffer[j++] = e2.getX();
mLineBuffer[j++] = e1.getY() * phaseY;
mLineBuffer[j++] = e2.getX();
mLineBuffer[j++] = e1.getY() * phaseY;
}
mLineBuffer[j++] = e2.getX();
mLineBuffer[j++] = e2.getY() * phaseY;
}
if (j > 0) {
trans.pointValuesToPixel(mLineBuffer);
final int size = Math.max((mXBounds.range + 1) * pointsPerEntryPair, pointsPerEntryPair) * 2;
mRenderPaint.setColor(dataSet.getColor());
if (isHeart) {
LinearGradient liner = new LinearGradient(0, mViewPortHandler.getContentRect().top,
0, mViewPortHandler.getContentRect().bottom, colors, pos, Shader.TileMode.CLAMP)
liner.setLocalMatrix(viewPortHandler.getMatrixTouch())
mRenderPaint.setShader(liner);
}
canvas.drawLines(mLineBuffer, 0, size, mRenderPaint);
}
}
}
mRenderPaint.setPathEffect(null);
}
/**
* cache for the circle bitmaps of all datasets
*/
private HashMap<IDataSet, DataSetImageCache> mImageCaches = new HashMap<>();
private float[] mCirclesBuffer = new float[2];
@Override
protected void drawCircles(Canvas c) {
mRenderPaint.setStyle(Paint.Style.FILL);
float phaseY = mAnimator.getPhaseY();
mCirclesBuffer[0] = 0;
mCirclesBuffer[1] = 0;
List<ILineDataSet> dataSets = mChart.getLineData().getDataSets();
for (int i = 0; i < dataSets.size(); i++) {
ILineDataSet dataSet = dataSets.get(i);
if (!dataSet.isVisible() || !dataSet.isDrawCirclesEnabled() ||
dataSet.getEntryCount() == 0)
continue;
mCirclePaintInner.setColor(dataSet.getCircleHoleColor());
Transformer trans = mChart.getTransformer(dataSet.getAxisDependency());
mXBounds.set(mChart, dataSet);
float circleRadius = dataSet.getCircleRadius();
float circleHoleRadius = dataSet.getCircleHoleRadius();
boolean drawCircleHole = dataSet.isDrawCircleHoleEnabled() &&
circleHoleRadius < circleRadius &&
circleHoleRadius > 0.f;
boolean drawTransparentCircleHole = drawCircleHole &&
dataSet.getCircleHoleColor() == ColorTemplate.COLOR_NONE;
DataSetImageCache imageCache;
if (mImageCaches.containsKey(dataSet)) {
imageCache = mImageCaches.get(dataSet);
} else {
imageCache = new DataSetImageCache();
mImageCaches.put(dataSet, imageCache);
}
boolean changeRequired = imageCache.init(dataSet);
// only fill the cache with new bitmaps if a change is required
if (changeRequired) {
imageCache.fill(dataSet, drawCircleHole, drawTransparentCircleHole);
}
int boundsRangeCount = mXBounds.range + mXBounds.min;
for (int j = mXBounds.min; j <= boundsRangeCount; j++) {
Entry e = dataSet.getEntryForIndex(j);
if (e == null) break;
if (e.getY() == 0) continue;
mCirclesBuffer[0] = e.getX();
mCirclesBuffer[1] = e.getY() * phaseY;
trans.pointValuesToPixel(mCirclesBuffer);
if (!mViewPortHandler.isInBoundsRight(mCirclesBuffer[0]))
break;
if (!mViewPortHandler.isInBoundsLeft(mCirclesBuffer[0]) ||
!mViewPortHandler.isInBoundsY(mCirclesBuffer[1]))
continue;
Bitmap circleBitmap = imageCache.getBitmap(j);
if (circleBitmap != null) {
c.drawBitmap(circleBitmap, mCirclesBuffer[0] - circleRadius, mCirclesBuffer[1] - circleRadius, null);
}
}
}
}
private class DataSetImageCache {
private Path mCirclePathBuffer = new Path();
private Bitmap[] circleBitmaps;
/**
* Sets up the cache, returns true if a change of cache was required.
*
* @param set
* @return
*/
protected boolean init(ILineDataSet set) {
int size = set.getCircleColorCount();
boolean changeRequired = false;
if (circleBitmaps == null) {
circleBitmaps = new Bitmap[size];
changeRequired = true;
} else if (circleBitmaps.length != size) {
circleBitmaps = new Bitmap[size];
changeRequired = true;
}
return changeRequired;
}
/**
* Fills the cache with bitmaps for the given dataset.
*
* @param set
* @param drawCircleHole
* @param drawTransparentCircleHole
*/
protected void fill(ILineDataSet set, boolean drawCircleHole, boolean drawTransparentCircleHole) {
int colorCount = set.getCircleColorCount();
float circleRadius = set.getCircleRadius();
float circleHoleRadius = set.getCircleHoleRadius();
for (int i = 0; i < colorCount; i++) {
Bitmap.Config conf = Bitmap.Config.ARGB_4444;
Bitmap circleBitmap = Bitmap.createBitmap((int) (circleRadius * 2.1), (int) (circleRadius * 2.1), conf);
Canvas canvas = new Canvas(circleBitmap);
circleBitmaps[i] = circleBitmap;
mRenderPaint.setColor(set.getCircleColor(i));
if (drawTransparentCircleHole) {
// Begin path for circle with hole
mCirclePathBuffer.reset();
mCirclePathBuffer.addCircle(
circleRadius,
circleRadius,
circleRadius,
Path.Direction.CW);
// Cut hole in path
mCirclePathBuffer.addCircle(
circleRadius,
circleRadius,
circleHoleRadius,
Path.Direction.CCW);
// Fill in-between
canvas.drawPath(mCirclePathBuffer, mRenderPaint);
} else {
canvas.drawCircle(
circleRadius,
circleRadius,
circleRadius,
mRenderPaint);
if (drawCircleHole) {
canvas.drawCircle(
circleRadius,
circleRadius,
circleHoleRadius,
mCirclePaintInner);
}
}
}
}
/**
* Returns the cached Bitmap at the given index.
*
* @param index
* @return
*/
protected Bitmap getBitmap(int index) {
return circleBitmaps[index % circleBitmaps.length];
}
}
/***
* 对高亮的值进行显示小圆点 如果没有此需要可删除
* */
@Override
public void drawHighlighted(Canvas c, Highlight[] indices) {
super.drawHighlighted(c, indices);
float phaseY = mAnimator.getPhaseY();
ILineDataSet lineData = mChart.getLineData().getDataSetByIndex(0);
Transformer trans = mChart.getTransformer(lineData.getAxisDependency());
mCirclesBuffer[0] = 0;
mCirclesBuffer[1] = 0;
for (Highlight high : indices) {
Entry e = lineData.getEntryForXValue(high.getX(), high.getY());
mCirclesBuffer[0] = e.getX();
mCirclesBuffer[1] = e.getY() * phaseY;
trans.pointValuesToPixel(mCirclesBuffer);
mHighlightCirclePaint.setColor(lineData.getHighLightColor());
//根据不同的区间显示小圆点的颜色
if (isHeart) {
if (e.getY() >= range[0]) {
mHighlightCirclePaint.setColor(colors[0]);
} else if (e.getY() < range[0] && e.getY() >= range[1]) {
mHighlightCirclePaint.setColor(colors[2]);
} else if (e.getY() >= range[2] && e.getY() < range[1]) {
mHighlightCirclePaint.setColor(colors[4]);
} else {
mHighlightCirclePaint.setColor(colors[6]);
}
}
c.drawCircle(mCirclesBuffer[0], mCirclesBuffer[1], 10, mHighlightCirclePaint);
mHighlightCirclePaint.setColor(Color.WHITE);
c.drawCircle(mCirclesBuffer[0], mCirclesBuffer[1], 5, mHighlightCirclePaint);
}
}
@Override
public 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);
if (isHeart) {
LinearGradient liner = new LinearGradient(0,mViewPortHandler.getContentRect().top,0,mViewPortHandler.getContentRect().bottom, colors, pos, Shader.TileMode.CLAMP)
liner.setLocalMatrix(viewPortHandler.getMatrixTouch())
mRenderPaint.setShader(liner);
}
mBitmapCanvas.drawPath(cubicPath, mRenderPaint);
mRenderPaint.setPathEffect(null);
}
/***
* @param isHeart true 开启分区间显示的颜色
* @param medium 不同层级的判断条件
* @param colors 不同区间的颜色值,从上到下的颜色 我这里是3个值 那么分成四段 colors数组长度就为4
* */
public void setHeartLine(boolean isHeart, int medium, int larger, int limit, int[] colors) {
this.isHeart = isHeart;
range = new int[3];
range[0] = limit;
range[1] = larger;
range[2] = medium;
float[] pos = new float[4];
float Ymax = ((LineChart) mChart).getAxisLeft().getAxisMaximum();
float Ymin = ((LineChart) mChart).getAxisLeft().getAxisMinimum();
pos[0] = (Ymax - limit) / (Ymax - Ymin);
pos[1] = (limit - larger) / (Ymax - Ymin) + pos[0];
pos[2] = (larger - medium) / (Ymax - Ymin) + pos[1];
pos[3] = 1f;
this.pos = new float[pos.length * 2];
this.colors = new int[colors.length * 2];
int index = 0;
for (int i = 0; i < pos.length; i++) {
this.colors[index] = colors[i];
this.colors[index + 1] = colors[i];
if (i == 0) {
this.pos[index] = 0f;
this.pos[index + 1] = pos[i];
} else {
this.pos[index] = pos[i - 1];
this.pos[index + 1] = pos[i];
}
index += 2;
}
}
}
开始使用,布局中引入
<你的类路径.MyLineChart
android:id="@+id/line_chart"
android:layout_width="match_parent"
android:layout_height="200dp"/>
然后设置chart的XY轴样式等等。设置值之前需要设置你的区间颜色等等信息代码如下
if (line_chart.getRenderer() instanceof MyLineChartRenderer) {
MyLineChartRenderer renderer = (MyLineChartRenderer) line_chart.getRenderer();
int medium = 125;
int larger = 147;
int limit = 188;
int[] colors = new int[4];
colors[0] = Color.parseColor("#fa7069");
colors[1] = Color.parseColor("#faa369");
colors[2] = Color.parseColor("#facd69");
colors[3] = Color.parseColor("#d3d8dc");
renderer.setHeartLine(true, medium,larger,limit, colors);
}
实现思路:
LinearGradient线性渐变类。对Y轴不同区间占比进行计算,设置渐变色的位置pos数组。进行渐变。两个相同的颜色渐变不会有任何变化。所以设置4个颜色,实际设置进去的渐变色有8个。
核心代码(之前代码不支持Y轴放大缩小,现在支持Y轴放大和缩小)
if (isHeart) {
LinearGradient liner = new LinearGradient(0, mViewPortHandler.getContentRect().top,0, mViewPortHandler.getContentRect().bottom, colors, pos, Shader.TileMode.CLAMP)
liner.setLocalMatrix(viewPortHandler.getMatrixTouch())
mRenderPaint.setShader(liner);
}
Y轴支持放大缩小原理是获取到chart的根据收拾放大的触摸矩阵(Matrix)
Matrix自行百度,我就不过多解释了。
public void setHeartLine(boolean isHeart, int medium, int larger, int limit, int[] colors) {
this.isHeart = isHeart;
range = new int[3];
range[0] = limit;
range[1] = larger;
range[2] = medium;
float[] pos = new float[4];
float Ymax = ((LineChart) mChart).getAxisLeft().getAxisMaximum();
float Ymin = ((LineChart) mChart).getAxisLeft().getAxisMinimum();
pos[0] = (Ymax - limit) / (Ymax - Ymin);
pos[1] = (limit - larger) / (Ymax - Ymin) + pos[0];
pos[2] = (larger - medium) / (Ymax - Ymin) + pos[1];
pos[3] = 1f;
this.pos = new float[pos.length * 2];
this.colors = new int[colors.length * 2];
int index = 0;
for (int i = 0; i < pos.length; i++) {
this.colors[index] = colors[i];
this.colors[index + 1] = colors[i];
if (i == 0) {
this.pos[index] = 0f;
this.pos[index + 1] = pos[i];
} else {
this.pos[index] = pos[i - 1];
this.pos[index + 1] = pos[i];
}
index += 2;
}
}
最终效果:
三条红线是LimitLine分界线 删除即可