在项目中需要用到折线图,但是某些部分需要自己自定义,这里记录一下改动点以及如何改动,希望有需要的朋友也能知道大概怎么改。
需要注意的是这里不讲基本用法,百度一下就有很多写的很好的api讲解了
1 多marker
在项目中,当有多折线的情况下,手指移动,只会聚焦到最近的点,然后显示一个marker在头顶上。
而在我们的项目中,需要显示多个marker。如手指在x轴数字4上假设有4条线就有4个marker。
原lib是在Chart这个类中的drawMarkers方法中绘制marker
protected void drawMarkers(Canvas canvas) {
// if there is no marker view or drawing marker is disabled
if (mMarker == null || !isDrawMarkersEnabled() || !valuesToHighlight())
return;
for (int i = 0; i < mIndicesToHighlight.length; i++) {
Highlight highlight = mIndicesToHighlight[i];
IDataSet set = mData.getDataSetByIndex(highlight.getDataSetIndex());
Entry e = mData.getEntryForHighlight(mIndicesToHighlight[i]);
int entryIndex = set.getEntryIndex(e);
// make sure entry not null
if (e == null || entryIndex > set.getEntryCount() * mAnimator.getPhaseX())
continue;
float[] pos = getMarkerPosition(highlight);
// check bounds
if (!mViewPortHandler.isInBounds(pos[0], pos[1]))
continue;
// callbacks to update the content
mMarker.refreshContent(e, highlight);
// draw the marker
mMarker.draw(canvas, pos[0], pos[1]);
}
}
可以看到,是根据pos0和1去定位在哪个位置。
于是我们继承Chart,并重写方法。
在我们项目中,marker需要放置到最高的位置。因此y轴是0,然后为了与x轴对其,会将x减去marker width的一半,你们看代码就懂了
/**
* draws all MarkerViews on the highlighted positions
*/
@Override
protected void drawMarkers(Canvas canvas) {
//super.drawMarkers(canvas);
// if there is no marker view or drawing marker is disabled
if (!isDrawMarkersEnabled() || !valuesToHighlight())
return;
for (int i = 0; i < mIndicesToHighlight.length; i++) {
Highlight highlight = mIndicesToHighlight[i];
IDataSet set = mData.getDataSetByIndex(highlight.getDataSetIndex());
Entry e = mData.getEntryForHighlight(mIndicesToHighlight[i]);
int entryIndex = set.getEntryIndex(e);
// make sure entry not null
if (e == null || entryIndex > set.getEntryCount() * mAnimator.getPhaseX())
continue;
float[] pos = getMarkerPosition(highlight);
// check bounds
if (!mViewPortHandler.isInBounds(pos[0], pos[1]))
continue;
//设置数据按照黑黄绿的顺序
Entry blackEntry = null;
Entry yellowEntry = null;
Entry greenEntry = null;
for (int j = 0; j < mData.getDataSetCount(); j++) {
ILineDataSet dataSetByIndex = mData.getDataSetByIndex(j);
List<Entry> entriesForXValue = dataSetByIndex.getEntriesForXValue(e.getX());
if (entriesForXValue.size() > 0) {
Entry entry = entriesForXValue.get(0);
if (j == 0) {
blackEntry = entry;
} else if (j == 1) {
yellowEntry = entry;
} else if (j == 2) {
greenEntry = entry;
}
}
}
int height = 0;
int marginTop = 20;
// callbacks to update the content
if (blackEntry != null) {
mBlackMarkView.refreshContent(blackEntry, highlight);
int width = mBlackMarkView.getWidth();
float xLeft = pos[0] - width / 2;
if (xLeft < mViewPortHandler.contentLeft()) {
xLeft = mViewPortHandler.contentLeft();
}
if (xLeft + width > mViewPortHandler.contentRight()) {
xLeft = mViewPortHandler.contentRight() - width;
}
// draw the marker
mBlackMarkView.draw(canvas, xLeft, 0);
height = mBlackMarkView.getHeight() + marginTop;
}
if (yellowEntry != null) {
mYellowMarkView.refreshContent(yellowEntry, highlight);
int width = mYellowMarkView.getWidth();
float xLeft = pos[0] - width / 2;
if (xLeft < mViewPortHandler.contentLeft()) {
xLeft = mViewPortHandler.contentLeft();
}
if (xLeft + width > mViewPortHandler.contentRight()) {
xLeft = mViewPortHandler.contentRight() - width;
}
// draw the marker
mYellowMarkView.draw(canvas, xLeft, height);
height += mYellowMarkView.getHeight() + marginTop;
}
if (greenEntry != null) {
mGreenMarkView.refreshContent(greenEntry, highlight);
int width = mGreenMarkView.getWidth();
float xLeft = pos[0] - width / 2;
if (xLeft < mViewPortHandler.contentLeft()) {
xLeft = mViewPortHandler.contentLeft();
}
if (xLeft + width > mViewPortHandler.contentRight()) {
xLeft = mViewPortHandler.contentRight() - width;
}
// draw the marker
mGreenMarkView.draw(canvas, xLeft, height);
}
}
在这里我默认会有三条线,因此我会循环找到012的entry,然后分别绘制marker。
2 marker触及边缘的时候会看不见
参照上面的源代码,可以知道我用了一个mViewPortHandler。在这个地方mViewPortHandler是管理xy轴内部的top left bottom right的,因此引用这个判断边界动态设置xy即可。
if (greenEntry != null) {
mGreenMarkView.refreshContent(greenEntry, highlight);
int width = mGreenMarkView.getWidth();
float xLeft = pos[0] - width / 2;
if (xLeft < mViewPortHandler.contentLeft()) {
xLeft = mViewPortHandler.contentLeft();
}
if (xLeft + width > mViewPortHandler.contentRight()) {
xLeft = mViewPortHandler.contentRight() - width;
}
// draw the marker
mGreenMarkView.draw(canvas, xLeft, height);
}
3 hightlight默认有横竖两轴,我只想要x/y轴
这个官方api有
设置dataset的时候设置即可
set1.setDrawHorizontalHighlightIndicator(false);
4 绘制桌面背景
这个倒是不难
忽略即可
继承Chart后重写onDraw
@Override
protected void onDraw(Canvas canvas) {
if (mViewPortHandler != null) {
canvas.drawBitmap(mBitmap, mViewPortHandler.contentRight() - mBitmap.getWidth() - 10, mViewPortHandler.contentTop() + 10, mPaint);
}
super.onDraw(canvas);
}
5当离得很远的时候,触发识别别的轴
假设我的手在xy 14 0的地方,而有一条轴线的点在14 1000,而这时候离得比较远,而13 144这个点离得比你近,这时候系统会选择13 144这个点作为你的highlight。
这时候重写init方法
并重新设置setHighlighter
setHighlighter(new ChartHighlighter(this) {
@Override
protected Highlight getHighlightForX(float xVal, float x, float y) {
// return super.getHighlightForX(xVal, x, y);
List<Highlight> closestValues = getHighlightsAtXValue(xVal, x, y);
if (closestValues.isEmpty()) {
return null;
}
// float leftAxisMinDist = getMinimumDistance(closestValues, y, YAxis.AxisDependency.LEFT);
// float rightAxisMinDist = getMinimumDistance(closestValues, y, YAxis.AxisDependency.RIGHT);
//
// YAxis.AxisDependency axis = leftAxisMinDist < rightAxisMinDist ? YAxis.AxisDependency.LEFT : YAxis.AxisDependency.RIGHT;
//
// Highlight detail = getClosestHighlightByPixel(closestValues, x, y, axis, mChart.getMaxHighlightDistance());
int i = 0;
int targetIdx = 0;
float smallestVal = 100;
for (Highlight highlight : closestValues) {
float abs = Math.abs(xVal - highlight.getX());
if (abs < smallestVal) {
smallestVal = abs;
targetIdx = i;
}
i++;
}
Highlight highlight = closestValues.get(targetIdx);
return highlight;
}
});
可以看到原始情况下会调用getClosestHighlightByPixel这个方法。
而按照我的需求,我在x轴只要有这个点,就必须选中,因此我重写并按照我的逻辑选择返回即可。
6 setSpaceTop,setSpaceBottom无效
在项目中我只能项目显示3条线,并且上下留有空隙,为了好看。
而如果设置y轴的最大值最小值并强制设置只有三个点的话,默认会最底点会占据x轴,最高点就画在最高,太丑了
如上图箭头,会默认这样画。
但是spaceTop/Bottom无效。
这时候我看到了limitLine这个东西。因此我打算用limitLine替换y轴虚线
首先设置最大值最小值的时候,再加一个数,使得上下留有空隙
然后设置limitline,发现这文字不能画到y轴左边
于是开始自定义
并在init方法中重新设置setRendererLeftYAxis方法
setRendererLeftYAxis(new YAxisRenderer(mViewPortHandler, mAxisLeft, mLeftAxisTransformer) {
@Override
public void renderLimitLines(Canvas c) {
//super.renderLimitLines(c);
List<LimitLine> limitLines = mYAxis.getLimitLines();
if (limitLines == null || limitLines.size() <= 0)
return;
float[] pts = mRenderLimitLinesBuffer;
pts[0] = 0;
pts[1] = 0;
Path limitLinePath = mRenderLimitLines;
limitLinePath.reset();
for (int i = 0; i < limitLines.size(); i++) {
LimitLine l = limitLines.get(i);
if (!l.isEnabled())
continue;
int clipRestoreCount = c.save();
mLimitLineClippingRect.set(mViewPortHandler.getContentRect());
mLimitLineClippingRect.inset(0.f, -l.getLineWidth());
c.clipRect(mLimitLineClippingRect);
mLimitLinePaint.setStyle(Paint.Style.STROKE);
mLimitLinePaint.setColor(l.getLineColor());
mLimitLinePaint.setStrokeWidth(l.getLineWidth());
mLimitLinePaint.setPathEffect(l.getDashPathEffect());
pts[1] = l.getLimit();
mTrans.pointValuesToPixel(pts);
limitLinePath.moveTo(mViewPortHandler.contentLeft(), pts[1]);
limitLinePath.lineTo(mViewPortHandler.contentRight(), pts[1]);
c.drawPath(limitLinePath, mLimitLinePaint);
limitLinePath.reset();
// c.drawLines(pts, mLimitLinePaint);
String label = l.getLabel();
// if drawing the limit-value label is enabled
if (label != null && !label.equals("")) {
mLimitLinePaint.setStyle(l.getTextStyle());
mLimitLinePaint.setPathEffect(null);
mLimitLinePaint.setColor(l.getTextColor());
mLimitLinePaint.setTypeface(l.getTypeface());
mLimitLinePaint.setStrokeWidth(0.5f);
mLimitLinePaint.setTextSize(l.getTextSize());
final float labelLineHeight = Utils.calcTextHeight(mLimitLinePaint, label);
float xOffset = Utils.convertDpToPixel(4f) + l.getXOffset();
float yOffset = l.getLineWidth() + labelLineHeight + l.getYOffset();
final LimitLine.LimitLabelPosition position = l.getLabelPosition();
// if (position == LimitLine.LimitLabelPosition.RIGHT_TOP) {
//
// mLimitLinePaint.setTextAlign(Paint.Align.RIGHT);
// c.drawText(label,
// mViewPortHandler.contentRight() - xOffset,
// pts[1] - yOffset + labelLineHeight, mLimitLinePaint);
//
// } else if (position == LimitLine.LimitLabelPosition.RIGHT_BOTTOM) {
//
// mLimitLinePaint.setTextAlign(Paint.Align.RIGHT);
// c.drawText(label,
// mViewPortHandler.contentRight() - xOffset,
// pts[1] + yOffset, mLimitLinePaint);
//
// } else if (position == LimitLine.LimitLabelPosition.LEFT_TOP) {
//
// mLimitLinePaint.setTextAlign(Paint.Align.LEFT);
// c.drawText(label,
// mViewPortHandler.contentLeft() + xOffset,
// pts[1] - yOffset + labelLineHeight, mLimitLinePaint);
//
// } else {
//
// mLimitLinePaint.setTextAlign(Paint.Align.LEFT);
// c.drawText(label,
// mViewPortHandler.offsetLeft() + xOffset,
// pts[1] + yOffset, mLimitLinePaint);
// }
c.restoreToCount(clipRestoreCount);
clipRestoreCount = c.save();
Rect rect = new Rect();
mLimitLinePaint.getTextBounds(label, 0, label.length(), rect);
mLimitLinePaint.setTextAlign(Paint.Align.LEFT);
c.drawText(label,
mViewPortHandler.contentLeft() - xOffset - rect.width(),
pts[1] + rect.height() / 2, mLimitLinePaint);
}
c.restoreToCount(clipRestoreCount);
}
}
});
由于上面设置了clipRect方法,所以我们画不出去。因此我们有模有样,先save一下,限制就接触了
github:https://github.com/Lemniscate317/MPAndroidChartCustomize