自定义图表ChartView
ÕâÀïʵÏÖµÄChartViewÖ»ÊÇÒ»¸ö±È½Ï¼òµ¥µÄÀý×Ó£¬Õë¶Ô¹«Ë¾µÄÐèÇ󿪷¢µÄ£¬ÒÔºóÓÐʱ¼äµÄ»°»áÔÚgithubÉÏÃæ½øÐÐά»¤~
ÏÈÉÏЧ¹ûͼ°É£¬ÔÚÄ£ÄâÆ÷ÉÏÃæÏÔʾµÄ²»ÊǺܺã¬ÕÛÏßÏßÌõ¾â³Ý±È½ÏÃ÷ÏÔ¶øÇÒÑÕÉ«²»ÇåÎú£¬Õæ»úÉÏÃæ¾ÍºÃÁ˺ܶࡣ
Õæ»úÉÏÃæÊÇÕâÑùµÄ(ºìÃ×):
Êý¾ÝÊǼ¸×éËæ»úÊý(¿´ÆðÀ´»¹ÊÇÓеã³óÈ»¶øÎÒÒѾÀÁµÃÔÙÈ¥½ØͼÁË¡)
OK£¬²»¹ÜÔõôÑù£¬·´Õý¾Í³¤³ÉÕâÑùÁË£¬ÓÚÊÇÎÒÃÇÏÈÀ´·ÖÎöÒ»ÏÂÕâ¸öͼ±íÓ¦¸ÃÔõô»¡£
¿ÉÄܺܶàÈË¿´¼ûÕâÑùµÄÒ»¸öͼ±í£¬µÚÒ»Ïë·¨¾ÍÊÇÈ¥githubÉÏÃæÕÒһЩÏֳɵÄlibrary£¬ÕâÑùʵ¼ÊÉÏÎÒÊDz»ÍƼöµÄ¡£ÎªÊ²Ã´ËµÄØ£¬ÄãȥʹÓÃÁ˱ðÈ˵ĿªÔ´¿â£¬Ê×ÏȾʹæÔÚÒ»¸öÄÜ·ñÂú×ãÄãµÄÏîĿʵ¼ÊÐèÇóµÄÎÊÌ⣬Æä¶þÕâÖÖÖ±½ÓÄÃÀ´ÓÃÈ´²»Ë¼¿¼µÄÐÐΪÊǷdz£×è°×ÔÉíµÄѧϰ½ø²½µÄ£¬·²Ê¶¼ÊÇÄÃÀ´Ö÷Ò壬ÔõôÄÜÌá¸ß×Ô¼ºÄØ£¿
µ±È»ÎÒÕâô˵²»ÊÇ˵¿ªÔ´¿â²»ºÃ£¬Ö»ÊÇÎÒÃÇÔÚʹÓÃ֮ǰ£¬¾¡¿ÉÄܵĶàÏëÏ룬ÔÚʹÓÃÓÅÐãµÄlibraryʱҲ¾¡Á¿³é³öʱ¼äȥѧϰËüµÄÉè¼Æ˼ÏëºÍģʽ£¬ÎÒÃDz»¿ÉÄÜѧ»áËùÓеĶ«Î÷£¬µ«ÊÇÕâ²¢²»ÊÇ˵¿ÉÒÔ²»Ñ§~
»Øµ½ÕýÌ⣬Ê×ÏÈÄØ¿´µ½Õâ¸öͼ±í£¬ËüÊÇÓÉ4ÌõºáÏß¡¢1ÌõÊúÏß¡¢×Ý×ø±ê·½ÏòµÄÎı¾¡¢ºá×ø±ê·½ÏòµÄÎı¾¡¢¾ØÐÎ(Öù״ͼ)¡¢Ô²»·ÒÔ¼°ÕÛÏß×é³ÉµÄ¡£
ÄÇô»æÖƵÄʱºò£¬ÎÒÃÇ¿ÉÒÔÏÈ´Ó×Ý×ø±êÎı¾¿ªÊ¼»æÖÆ£¬ÕâÀï»áÉæ¼°µ½Ò»¸ö¼ÆËãÎÄ×ָ߶ȵÄÎÊÌ⣬ÕâÀï¼òµ¥µÄ½éÉÜÒ»ÏÂFontMetricsµÄ¼¸¸ö³ÉÔ±±äÁ¿£¬·Ö±ðÊÇtop ascent descent bottomleading:
ʵ¼ÊÉÏ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/