MPAndroidChart 3.0在项目中需要用到自定义的地方

在项目中需要用到折线图,但是某些部分需要自己自定义,这里记录一下改动点以及如何改动,希望有需要的朋友也能知道大概怎么改。

需要注意的是这里不讲基本用法,百度一下就有很多写的很好的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。

image.png

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);
            }
image.png

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轴,最高点就画在最高,太丑了


image.png

如上图箭头,会默认这样画。

但是spaceTop/Bottom无效。

这时候我看到了limitLine这个东西。因此我打算用limitLine替换y轴虚线

image.png

首先设置最大值最小值的时候,再加一个数,使得上下留有空隙

然后设置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一下,限制就接触了


image.png

github:https://github.com/Lemniscate317/MPAndroidChartCustomize

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容