背景和问题
项目中使用到了大量图表,于是采用了 MPAndroidChart 作为解决方案。不得不说,这个库非常的强大,项目中大部分需求都可以直接使用项目提供的方法解决,但是也有一些需求需要自己的定制,比如本文主题:柱状图圆角问题。
解决方案
为了更好的修改源码实现项目需求,我采用导入模块的方式引入项目,详情不表。
我们的设计小姐姐非常的细,设计的柱状图是这样的,底部直角,顶部圆角。
最开始我找到了这个圆角方案:github.com/WW-Digital/…
这里面的代码可以解决四个角全是圆角的需求,核心是修改绘制方法,将canvas的绘制方法从 drawRect 变成了 drawRoundRect。如果需求是四个角都是圆角的话可以采用此方案,代码修改量不大。
对我来说,问题是没办法只让上面绘制圆角,所以此方案不能满足我的需求。继续尝试别的方案,在这个问题下找到了局部圆角的解决方案:stackoverflow.com/questions/3… 按照@duc tan 提供的代码,顺利的解决了局部圆角的需求,核心是将 Rect 转为 Path,并通过 drawPath 绘制。通过修改下面这个方法参数就可以任意定制圆角。
roundRect(RectF rect, float rx, float ry, boolean tl, boolean tr, boolean br, boolean bl)
事情看起来解决了,然而高亮的时候还是方的啊,这肯定不行,于是我又重写了drawHighlighted
方法,将选中高亮的柱状图也做成局部圆角,最终解决了这个问题,代码如下:
@Override
public void drawHighlighted(Canvas c, Highlight[] indices) {
BarData barData = mChart.getBarData();
for (Highlight high : indices) {
IBarDataSet set = barData.getDataSetByIndex(high.getDataSetIndex());
if (set == null || !set.isHighlightEnabled()) {
continue;
}
BarEntry e = set.getEntryForXValue(high.getX(), high.getY());
if (!isInBoundsX(e, set)) {
continue;
}
Transformer trans = mChart.getTransformer(set.getAxisDependency());
mHighlightPaint.setColor(set.getHighLightColor());
mHighlightPaint.setAlpha(set.getHighLightAlpha());
boolean isStack = high.getStackIndex() >= 0 && e.isStacked();
final float y1;
final float y2;
if (isStack) {
if (mChart.isHighlightFullBarEnabled()) {
y1 = e.getPositiveSum();
y2 = -e.getNegativeSum();
} else {
Range range = e.getRanges()[high.getStackIndex()];
y1 = range.from;
y2 = range.to;
}
} else {
y1 = e.getY();
y2 = 0.f;
}
prepareBarHighlight(e.getX(), y1, y2, barData.getBarWidth() / 2f, trans);
setHighlightDrawPos(high, mBarRect);
Path path2 = roundRect(new RectF(mBarRect.left, mBarRect.top, mBarRect.right,
mBarRect.bottom), mRadius, mRadius, true, true, false, false);
c.drawPath(path2, mHighlightPaint);
}
}
最后贴一下终极方案渲染器类代码和使用方法。
public class CustomBarChartRender extends BarChartRenderer {
private final RectF mBarShadowRectBuffer = new RectF();
private int mRadius;
public CustomBarChartRender(BarDataProvider chart, ChartAnimator animator, ViewPortHandler viewPortHandler) {
super(chart, animator, viewPortHandler);
}
public void setRadius(int mRadius) {
this.mRadius = mRadius;
}
@Override
protected void drawDataSet(Canvas c, IBarDataSet dataSet, int index) {
Transformer trans = mChart.getTransformer(dataSet.getAxisDependency());
mBarBorderPaint.setColor(dataSet.getBarBorderColor());
mBarBorderPaint.setStrokeWidth(Utils.convertDpToPixel(dataSet.getBarBorderWidth()));
mShadowPaint.setColor(dataSet.getBarShadowColor());
boolean drawBorder = dataSet.getBarBorderWidth() > 0f;
float phaseX = mAnimator.getPhaseX();
float phaseY = mAnimator.getPhaseY();
if (mChart.isDrawBarShadowEnabled()) {
mShadowPaint.setColor(dataSet.getBarShadowColor());
BarData barData = mChart.getBarData();
float barWidth = barData.getBarWidth();
float barWidthHalf = barWidth / 2.0f;
float x;
int i = 0;
double count = Math.min(Math.ceil((int) (double) ((float) dataSet.getEntryCount() * phaseX)), dataSet.getEntryCount());
while (i < count) {
BarEntry e = dataSet.getEntryForIndex(i);
x = e.getX();
mBarShadowRectBuffer.left = x - barWidthHalf;
mBarShadowRectBuffer.right = x + barWidthHalf;
trans.rectValueToPixel(mBarShadowRectBuffer);
if (!mViewPortHandler.isInBoundsLeft(mBarShadowRectBuffer.right)) {
i++;
continue;
}
if (!mViewPortHandler.isInBoundsRight(mBarShadowRectBuffer.left)) {
break;
}
mBarShadowRectBuffer.top = mViewPortHandler.contentTop();
mBarShadowRectBuffer.bottom = mViewPortHandler.contentBottom();
c.drawRoundRect(mBarRect, mRadius, mRadius, mShadowPaint);
i++;
}
}
// initialize the buffer
BarBuffer buffer = mBarBuffers[index];
buffer.setPhases(phaseX, phaseY);
buffer.setDataSet(index);
buffer.setInverted(mChart.isInverted(dataSet.getAxisDependency()));
buffer.setBarWidth(mChart.getBarData().getBarWidth());
buffer.feed(dataSet);
trans.pointValuesToPixel(buffer.buffer);
boolean isSingleColor = dataSet.getColors().size() == 1;
if (isSingleColor) {
mRenderPaint.setColor(dataSet.getColor());
}
int j = 0;
while (j < buffer.size()) {
if (!mViewPortHandler.isInBoundsLeft(buffer.buffer[j + 2])) {
j += 4;
continue;
}
if (!mViewPortHandler.isInBoundsRight(buffer.buffer[j])) {
break;
}
if (!isSingleColor) {
// Set the color for the currently drawn value. If the index
// is out of bounds, reuse colors.
mRenderPaint.setColor(dataSet.getColor(j / 4));
}
Path path2 = roundRect(new RectF(buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2],
buffer.buffer[j + 3]), mRadius, mRadius, true, true, false, false);
c.drawPath(path2, mRenderPaint);
if (drawBorder) {
Path path = roundRect(new RectF(buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2],
buffer.buffer[j + 3]), mRadius, mRadius, true, true, false, false);
c.drawPath(path, mBarBorderPaint);
}
j += 4;
}
}
@Override
public void drawHighlighted(Canvas c, Highlight[] indices) {
BarData barData = mChart.getBarData();
for (Highlight high : indices) {
IBarDataSet set = barData.getDataSetByIndex(high.getDataSetIndex());
if (set == null || !set.isHighlightEnabled()) {
continue;
}
BarEntry e = set.getEntryForXValue(high.getX(), high.getY());
if (!isInBoundsX(e, set)) {
continue;
}
Transformer trans = mChart.getTransformer(set.getAxisDependency());
mHighlightPaint.setColor(set.getHighLightColor());
mHighlightPaint.setAlpha(set.getHighLightAlpha());
boolean isStack = high.getStackIndex() >= 0 && e.isStacked();
final float y1;
final float y2;
if (isStack) {
if (mChart.isHighlightFullBarEnabled()) {
y1 = e.getPositiveSum();
y2 = -e.getNegativeSum();
} else {
Range range = e.getRanges()[high.getStackIndex()];
y1 = range.from;
y2 = range.to;
}
} else {
y1 = e.getY();
y2 = 0.f;
}
prepareBarHighlight(e.getX(), y1, y2, barData.getBarWidth() / 2f, trans);
setHighlightDrawPos(high, mBarRect);
Path path2 = roundRect(new RectF(mBarRect.left, mBarRect.top, mBarRect.right,
mBarRect.bottom), mRadius, mRadius, true, true, false, false);
c.drawPath(path2, mHighlightPaint);
}
}
private Path roundRect(RectF rect, float rx, float ry, boolean tl, boolean tr, boolean br, boolean bl) {
float top = rect.top;
float left = rect.left;
float right = rect.right;
float bottom = rect.bottom;
Path path = new Path();
if (rx < 0) {
rx = 0;
}
if (ry < 0) {
ry = 0;
}
float width = right - left;
float height = bottom - top;
if (rx > width / 2) {
rx = width / 2;
}
if (ry > height / 2) {
ry = height / 2;
}
float widthMinusCorners = (width - (2 * rx));
float heightMinusCorners = (height - (2 * ry));
path.moveTo(right, top + ry);
if (tr) {
//top-right corner
path.rQuadTo(0, -ry, -rx, -ry);
} else {
path.rLineTo(0, -ry);
path.rLineTo(-rx, 0);
}
path.rLineTo(-widthMinusCorners, 0);
if (tl) {
//top-left corner
path.rQuadTo(-rx, 0, -rx, ry);
} else {
path.rLineTo(-rx, 0);
path.rLineTo(0, ry);
}
path.rLineTo(0, heightMinusCorners);
if (bl) {
//bottom-left corner
path.rQuadTo(0, ry, rx, ry);
} else {
path.rLineTo(0, ry);
path.rLineTo(rx, 0);
}
path.rLineTo(widthMinusCorners, 0);
if (br) {
//bottom-right corner
path.rQuadTo(rx, 0, rx, -ry);
} else {
path.rLineTo(rx, 0);
path.rLineTo(0, -ry);
}
path.rLineTo(0, -heightMinusCorners);
path.close();//Given close, last lineto can be removed.
return path;
}
}
使用方法(Kotlin):
val barChartRender = CustomBarChartRender(chart,chart.animator,chart.viewPortHandler)
barChartRender.setRadius(20)
barChart.renderer = barChartRender
实现的效果真机截图如下: