自定义图表ChartView

ÕâÀïʵÏÖµÄChartViewÖ»ÊÇÒ»¸ö±È½Ï¼òµ¥µÄÀý×Ó£¬Õë¶Ô¹«Ë¾µÄÐèÇ󿪷¢µÄ£¬ÒÔºóÓÐʱ¼äµÄ»°»áÔÚgithubÉÏÃæ½øÐÐά»¤~

ÏÈÉÏЧ¹ûͼ°É£¬ÔÚÄ£ÄâÆ÷ÉÏÃæÏÔʾµÄ²»ÊǺܺã¬ÕÛÏßÏßÌõ¾â³Ý±È½ÏÃ÷ÏÔ¶øÇÒÑÕÉ«²»ÇåÎú£¬Õæ»úÉÏÃæ¾ÍºÃÁ˺ܶࡣ

ChartViewDemo.gif

Õæ»úÉÏÃæÊÇÕâÑùµÄ(ºìÃ×):

blob.png

Êý¾ÝÊǼ¸×éËæ»úÊý(¿´ÆðÀ´»¹ÊÇÓеã³óÈ»¶øÎÒÒѾ­ÀÁµÃÔÙÈ¥½ØͼÁË¡­)

OK£¬²»¹ÜÔõôÑù£¬·´Õý¾Í³¤³ÉÕâÑùÁË£¬ÓÚÊÇÎÒÃÇÏÈÀ´·ÖÎöÒ»ÏÂÕâ¸öͼ±íÓ¦¸ÃÔõô»­¡£

¿ÉÄܺܶàÈË¿´¼ûÕâÑùµÄÒ»¸öͼ±í£¬µÚÒ»Ïë·¨¾ÍÊÇÈ¥githubÉÏÃæÕÒһЩÏֳɵÄlibrary£¬ÕâÑùʵ¼ÊÉÏÎÒÊDz»ÍƼöµÄ¡£ÎªÊ²Ã´ËµÄØ£¬ÄãȥʹÓÃÁ˱ðÈ˵ĿªÔ´¿â£¬Ê×ÏȾʹæÔÚÒ»¸öÄÜ·ñÂú×ãÄãµÄÏîĿʵ¼ÊÐèÇóµÄÎÊÌ⣬Æä¶þÕâÖÖÖ±½ÓÄÃÀ´ÓÃÈ´²»Ë¼¿¼µÄÐÐΪÊǷdz£×è°­×ÔÉíµÄѧϰ½ø²½µÄ£¬·²Ê¶¼ÊÇÄÃÀ´Ö÷Ò壬ÔõôÄÜÌá¸ß×Ô¼ºÄØ£¿
µ±È»ÎÒÕâô˵²»ÊÇ˵¿ªÔ´¿â²»ºÃ£¬Ö»ÊÇÎÒÃÇÔÚʹÓÃ֮ǰ£¬¾¡¿ÉÄܵĶàÏëÏ룬ÔÚʹÓÃÓÅÐãµÄlibraryʱҲ¾¡Á¿³é³öʱ¼äȥѧϰËüµÄÉè¼Æ˼ÏëºÍģʽ£¬ÎÒÃDz»¿ÉÄÜѧ»áËùÓеĶ«Î÷£¬µ«ÊÇÕâ²¢²»ÊÇ˵¿ÉÒÔ²»Ñ§~

»Øµ½ÕýÌ⣬Ê×ÏÈÄØ¿´µ½Õâ¸öͼ±í£¬ËüÊÇÓÉ4ÌõºáÏß¡¢1ÌõÊúÏß¡¢×Ý×ø±ê·½ÏòµÄÎı¾¡¢ºá×ø±ê·½ÏòµÄÎı¾¡¢¾ØÐÎ(Öù״ͼ)¡¢Ô²»·ÒÔ¼°ÕÛÏß×é³ÉµÄ¡£
ÄÇô»æÖƵÄʱºò£¬ÎÒÃÇ¿ÉÒÔÏÈ´Ó×Ý×ø±êÎı¾¿ªÊ¼»æÖÆ£¬ÕâÀï»áÉæ¼°µ½Ò»¸ö¼ÆËãÎÄ×ָ߶ȵÄÎÊÌ⣬ÕâÀï¼òµ¥µÄ½éÉÜÒ»ÏÂFontMetricsµÄ¼¸¸ö³ÉÔ±±äÁ¿£¬·Ö±ðÊÇtop ascent descent bottomleading:

blob.png

ʵ¼ÊÉÏAndroidÎÄ×ֵĻæÖƶ¼ÊÇ´Ó»ùÏß¿ªÊ¼µÄ£¬´Óbaselineµ½×Ö·û×î¸ß´¦µÄ¾àÀëΪascentֵΪ¸º£¬´Óbaselineµ½×Ö·û×îµ×²¿µÄ¾àÀë³Æ֮ΪdescentֵΪÕý£¬leadingÔÚÕâÕÅͼÉÏûÓбíʾ³öÀ´£¬ËüÆäʵÊÇÒ»¸öÐмä¾àÊôÐÔ£¬ÖµÎª´ÓÉÏÒ»ÐеÄdescentµ½¸ÃÐÐ×Ö·ûµÄascentµÄ¾àÀë¡£¿ÉÒÔ¿´³öÀ´£¬topºÍbottomÒª±ÈascentºÍdescentÉÔ΢¡±´ó¡±Ò»µãµã£¬ÊÂʵÉÏ°üÀ¨ººÓïÆ´ÒôÔÚÄÚ£¬»¹ÓÐÂù¶àÓïÑÔÊÇ´øÓÐÒô±êµÄ£¬Õâ¸ö¿Õ϶¾ÍÊÇΪÁËÕâЩÒô±ê×¼±¸µÄ~

ÄÇôÎÒÃǵÄtextHeight¾Í¿ÉÒÔÓÃMath.abs(ascent + descent)À´±íʾ¡£

/**
 * »­×Ý×ø±êÎı¾
 *
 * @param canvas
 */
private void drawOrdinate(Canvas canvas) {
    ordinateSize = ordinateList.size();
    if (0 == ordinateSize) {
        return;
    }
    //¼ÆËãyÖáÎı¾¼ä¾à
    spaceY = (chartViewHeight - xAxisMarginBottom - ordinateSize * ordinateTextHeight) / (ordinateSize - 1);
    //yÖá·½ÏòÎı¾¿í¶È
    ordinateTextWidth = 0;
    yAxisPaint.setTextAlign(Paint.Align.RIGHT);
    for (int i = 1; i < ordinateSize; i++) {
        //Ñ­»·±éÀúÊý×飬»ñµÃ×Ö·ûµÄ×î´ó³¤¶È£¬×÷Ϊ»æÖÆ×Ö·ûµÄÆðµã
        float ordinateTextWidthTemp = yAxisPaint.measureText(ordinateList.get(i));
        if (ordinateTextWidth < ordinateTextWidthTemp) {
            ordinateTextWidth = ordinateTextWidthTemp;
        }
    }
    for (int i = 0; i < ordinateSize; i++){
        canvas.drawText(ordinateList.get(i), ordinateTextWidth, chartViewHeight - i * spaceY - i * ordinateTextHeight - xAxisMarginBottom + 2, yAxisPaint);
    }
}

ÕâÀïÃæµÄxAxisMarginBottom´ú±íµÄÊÇxÖá¾àÀëView×îϲ¿µÄ¾àÀ룬ordinateListÊÇÓÉÓû§´«ÈëµÄ×Ý×ø±êÎı¾µÄ¼¯ºÏ¡£ÎÒÃÇÕâÀïÊÇͨ¹ýÓÃchartViewµÄ¸ß¶È¼õµô(xAxisMarginBottom + ordinateTextHeight / 2)ºóµÃµ½yÖáµÄ¸ß¶È£¬ÔÙÈ¥¼ÆËã³öyÖáÏàÁÚÎı¾µÄ¼ä¾à£¬¼ÆËãµÃµ½¼¯ºÏÖÐÎı¾µÄ×î´ó³¤¶È£¬×îºóÒԸó¤¶ÈΪÆðµã´ÓÓÒÏò×ó»æÖÆÎı¾¡£

×Ý×ø±êµÄÎı¾»æÖÆÍê³É£¬¸ÃÂÖµ½×Ý×ø±êÏßÁË£¬Êµ¼ÊÉϷdz£¼òµ¥£¬¾ÍÊÇÒ»Ìõ´ÓÉϵ½ÏµÄÖ±Ïߣ¬ÕâÀïÐèҪעÒâµÄÊÇ×Ý×ø±êµÄ¶¥µãÔÚ×²¿Îı¾µÄÖмäλÖ㬼´Ó¦ÎªchartViewHeight - xAxisMarginBottom - ordinateTextHeight / 2£»

/**
 * »­×Ý×ø±êÏß
 *
 * @param canvas
 */
private void drawOrdinateLine(Canvas canvas) {
    if (ordinateSize == 0) {
        return;
    }
    canvas.drawLine(ordinateTextWidth + 10,
            chartViewHeight - (ordinateSize - 1) * spaceY - (ordinateSize - 1) * ordinateTextHeight - ordinateTextHeight / 2 - xAxisMarginBottom,
            ordinateTextWidth + 10,
            chartViewHeight - xAxisMarginBottom - ordinateTextHeight / 2, yAxisPaint);
}

ÎÒÃǽÓ×ÅÀ´»æÖƺá×ø±êÏߣ¬Ò²¾ÍÊÇÒ»Ìõ´Ó×óµ½ÓÒµÄÖ±Ïß(¿Úºú£¬´ÓÉϵ½ÏÂÃ÷Ã÷ÊÇ4Ìõ£¡)

/**
 * »­ºá×ø±êÏß
 *
 * @param canvas
 */
private void drawAbscissaLine(Canvas canvas) {
    if (ordinateSize == 0) {
        return;
    }
    for (int i = 0; i < ordinateSize; i++) {
        canvas.drawLine(ordinateTextWidth + 10,
                chartViewHeight - i * spaceY - i * ordinateTextHeight - ordinateTextHeight / 2 - xAxisMarginBottom,
                chartViewWidth,
                chartViewHeight - i * spaceY - i * ordinateTextHeight - ordinateTextHeight / 2 - xAxisMarginBottom,
                xAxisPaint);
    }
}

»æÖƺá×ø±êµÄÎı¾Í¬»æÖÆ×Ý×ø±êµÄÎı¾ÀàËÆ¡£

 /**
 * »­ºá×ø±êÎı¾
 *
 * @param canvas
 */
private void drawAbscissa(Canvas canvas) {
    abscissaSize = abscissaList.size();
    if (abscissaSize == 0) {
        return;
    }
    //ºá×ø±êÎı¾¼ä¾à
    spaceX = (chartViewWidth - ordinateTextWidth - 30) / abscissaSize;
    abscissaTextWidth = 0;
    for (int i = 0; i < abscissaSize; i++) {
        canvas.drawText(abscissaList.get(i), ordinateTextWidth + 30 + i * spaceX, chartViewHeight - 15, xAxisPaint);
        float abscissaTextWidthTemp = xAxisPaint.measureText(abscissaList.get(i));
        if (abscissaTextWidth < abscissaTextWidthTemp) {
            abscissaTextWidth = abscissaTextWidthTemp;
        }
    }
}

ÕâÑùÎÒÃǾͰÑxÖáºÍyÖᶼ»æÖƳöÀ´ÁË£¬ÔõôÑù£¬²»ÄÑ°É£¿Æäʵ·Ç³£¼òµ¥¶Ô°É~

ºÃµÄ£¬¼ÌÐø»æÖÆÖù״ͼ¡£Öù״ͼʵ¼ÊÉϾÍÊÇÒ»¸ö¾ØÐΣ¬drawRectµÄ4¸öÊôÐÔ£¬topÊôÐÔÒ»¶¨ÊÇÓû§ÉèÖøøÎÒÃǵģ¬bottom¾ÍÊÇxÖáËùÔÚµÄy×ø±ê¡£Ö»Ê£ÏÂÁ½¸öÁË£¬leftºÍright¡£Õâ¸öleftºÍrightÓ¦¸ÃÈçºÎ¼ÆËãÄØ£¿ÎªÁËÃÀ¹Û£¬ÎÒÃÇÓ¦±£Ö¤Õû¸ö¾ØÐιØÓÚÆäϵÄ×ÖµÄÖеã¶Ô³Æ£¬ÕâÀïÎÒÃÇÉèÖÃÕû¸ö¾ØÐεĿí¶ÈµÈÓÚ×Ö¿í¶ÈµÄÒ»°ë¡£ÓÚÊǾÍÓÐ

float left = ordinateTextWidth + 30 + i * spaceX + xAxisTextSize / 2.5f;
float right = ordinateTextWidth + 30 + i * spaceX + abscissaTextWidth - xAxisTextSize / 2.5f;

i±íʾµ±Ç°»æÖƵÄÊǵڼ¸¸öÖù״ͼ¡£
Õû¸ö·½·¨µÄ´úÂëÈçÏ£º

/**
 * »­Öù״ͼ
 *
 * @param canvas
 */
private void drawHistogram(Canvas canvas) {
    if (abscissaSize == 0) {
        return;
    }
    yAxisHeight = chartViewHeight - xAxisMarginBottom - ordinateTextHeight / 2;
    for (int i = 0; i < abscissaSize; i++) {
        float historgramHeight = chartViewHeight - xAxisMarginBottom - (historgramList.get(i) / 3000f) * yAxisHeight;
        float left = ordinateTextWidth + 30 + i * spaceX + xAxisTextSize / 2.5f;
        float top = historgramHeight;
        float right = ordinateTextWidth + 30 + i * spaceX + abscissaTextWidth - xAxisTextSize / 2.5f;
        float bottom = chartViewHeight - xAxisMarginBottom - ordinateTextHeight / 2;
        canvas.drawRect(left, top, right, bottom, histogramPaint);
    }
}

Ê£ÏÂ×îºóÒ»¸ö£¬ÕÛÏßͼ£¡
ÕÛÏßͼʵ¼ÊÉÏÊÇͨ¹ýÓû§´«µÝ¸øÎÒÃÇÔ²ÐÄ×ø±ê£¬ÎÒÃǽ«ÆäÀ©Õ¹³ÉÒ»¸öÔ²ÐΣ¬²¢×öÏàÁÚÔ²µÄÁ¬Ïß¼´¿É¡£
ÏÈÀ´»­ÕÛÏßÉÏÃæµÄÔ²£º

linePaint.setStrokeWidth(3);
ArrayList<Circle> circleList;
for (int i = 0; i < brokenLineMap.size(); i++) {
    circleList = new ArrayList<>();
    linePaint.setColor(colors\[i\]);
    for (int j = 0; j < abscissaSize; j++) {
        //»ñµÃÐèÒª»­µÄÔ²µÄ×Ý×ø±ê
        float brokenLineYAxis = chartViewHeight - xAxisMarginBottom - (brokenLineMap.get(i).get(j) / yMaxValue) * yAxisHeight;
        canvas.drawCircle(ordinateTextWidth + 30 + j * spaceX + abscissaTextWidth / 2, brokenLineYAxis, 10, linePaint);
        Circle circle = new Circle(ordinateTextWidth + 30 + j * spaceX + abscissaTextWidth / 2, brokenLineYAxis, 10);
        circleList.add(circle);
        circleListMap.put(i, circleList);
    }
}

CircleÊÇÒ»¸öÄÚ²¿Àࣺ

 private class Circle {
    private float x;
    private float y;
    private float r;
    public Circle(float x, float y, float r) {
        this.x = x;
        this.y = y;
        this.r = r;
    }
    public float getX() {
        return x;
    }
    public float getY() {
        return y;
    }
    public float getR() {
        return r;
    }
}

ÉÏÃæÎÒÃÇÔÚ»­ÁËÔ²ÒÔºó½«Æä·Åµ½ÁËÒ»¸öcircleListMapÖУ¬ÕâÊÇΪÁ˼ÌÐø×öÔ²Ö®¼äµÄÁ¬Ïß×¼±¸µÄ¡£

linePaint.setStrokeWidth(1);
for (int i = 0; i < circleListMap.size(); i++) {
    linePaint.setColor(colors\[i\]);
    for (int j = 1; j < circleListMap.get(i).size(); j++) {
        drawLineBetweenCirCLe(canvas, circleListMap.get(i).get(j - 1).getX(),
                circleListMap.get(i).get(j - 1).getY(),
                circleListMap.get(i).get(j - 1).getR(),
                circleListMap.get(i).get(j).getX(),
                circleListMap.get(i).get(j).getY(),
                circleListMap.get(i).get(j).getR());
    }
}

ÕâÑùÎÒÃǵÄÕû¸öͼ±í¾Í»æÖÆÍê³ÉÁË£¬½ÓÏÂÀ´´¦ÀíÏÂonTouchEvent()£¬À´ÈÃÖù״ͼÏìÓ¦ÏìÓ¦µÄʼþ£º

@Override
public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            downX = event.getX();
            downY = event.getY();
            inside = isInside(downX, downY);
            if (inside) {
                if (null != listener) {
                    listener.show();
                }
            } else {
                return false;
            }
            break;
        case MotionEvent.ACTION_UP:
            if (inside) {
                if (null != listener) {
                    listener.dismiss();
                }
            }
            break;
    }
    return true;
}

µ±ÊÖÖ¸°´ÏÂʱÅжϸÃÂäµãÊÇ·ñÔÚÖù״ͼÄÚ£¬ÈôÔÚÓÉÓû§½øÐд¦Àí£¬ÊÖָ̧ʱ×öÏìÓ¦µÄ´¦Àí¡£

/**
 * ÅжϵãÊÇ·ñÔÚÖù״ͼÄÚ
 *
 * @param downX
 * @param downY
 * @return
 */
private boolean isInside(float downX, float downY) {
    for (int i = 0; i < abscissaSize; i++) {
        histogramXStart = ordinateTextWidth + 30 + i * spaceX + xAxisTextSize / 3;
        histogramYStart = chartViewHeight - xAxisMarginBottom - (historgramList.get(i) / 3000f) * yAxisHeight;
        histogramXEnd = ordinateTextWidth + 30 + i * spaceX + abscissaTextWidth - xAxisTextSize / 3;
        histogramYEnd = chartViewHeight - xAxisMarginBottom - ordinateTextHeight / 2;
        if (downX >= histogramXStart && downX <= histogramXEnd && downY >= histogramYStart && downY <= histogramYEnd) {
            selectPosition = i;
            return true;
        }
    }
    return false;
}
public interface OnInsideTouchListener {
    void show();
    void dismiss();
}
public void setOnTouchListener(OnInsideTouchListener listener) {
    this.listener = listener;
}

ÕâÒ»¶Î´úÂë¾ÍÖ»Êǽӿڻص÷ºÍÅжϣ¬±È½Ï¼òµ¥¾Í²»×ö½²½âÁË~~

×îºó£¬How to Use?

<declare-styleable name="chartView">
    <attr name="xAxisMarginBottom" format="dimension"/>
    <attr name="xAxisTextSize" format="dimension"/>
    <attr name="yAxisTextSize" format="dimension"/>
    <attr name="histogramShow" format="boolean"/>
    <attr name="brokenLineShow" format="boolean"/>
    <attr name="historgramColor" format="color"/>
</declare-styleable>

ÌṩÒÔÉÏÊôÐÔ£¬»ù±¾¶¼ÊǼûÆäÃûÖªÆäÒâµÄ£¬¼òµ¥À²¡­

ÔÚactivityÖмÓÈëÈçÏ´úÂ룺

chartView.setAbscissa(abscissaList);
chartView.setOrdinate(ordinateList);
chartView.setHistorgramList(historgramList);
chartView.setBrokenLineMap(brokenLineMap);
chartView.onSettingFinished();
chartView.setOnTouchListener(new ChartView.OnInsideTouchListener() {
    @Override
    public void show() {
        Toast.makeText(MainActivity.this, "µ±Ç°°´ÏµÄÊǵÚ" + chartView.getSelectPosition() + "¸ö", Toast.LENGTH_SHORT).show();
    }
    @Override
    public void dismiss() {
    }
});

àÅ¡­×îºóµÄ×îºó£¬ÌùÉÏÏîÄ¿githubµØÖ·

Æäʵд²©¿ÍÒ²ÊÇΪÁËÈÃ×Ô¼ºÁì»áµÄ¸üÉîÒ»µã£¬Ò²Ï£Íû×Ô¼ºÒ²Äܲ»¶Ï½ø²½~

±¾Îijö×Ô£ºhttp://z.sye.space/2015/10/20/ChartView/