MPAndroidChart 3.0在項目中需要用到自定義的地方 4 繪製桌面背景

在項目中需要用到折線圖,但是某些部分需要自己自定義,這裏記錄一下改動點以及如何改動,希望有需要的朋友也能知道大概怎麼改。

需要注意的是這裏不講基本用法,百度一下就有很多寫的很好的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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章