AndroidCharts 是一款轻量级的图表显示控件,对比起Android-Charts和AChartEngine来说简单和活泼了很多,适合数据展示不需要太过详细专业的场合,它支持简单且带动画的折线图,柱状图和饼状图。
其中的linechart不支持y坐标显示,我们可以自己修改添加上去,修改后类LineView的代码如下,其中YCOORD_TEXT_LEFT_MARGIN为图标向右的偏移量,用来空出y坐标文字的空间:
package com.nekocode.xuedao.utils;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.NinePatchDrawable;
import android.util.AttributeSet;
import android.view.View;
import java.util.ArrayList;
import com.nekocode.xuedao.R;
/**
* Created by Dacer on 11/4/13.
*/
public class LineView extends View {
private int mViewHeight;
//drawBackground
private boolean autoSetDataOfGird = true;
private boolean autoSetGridWidth = true;
private int dataOfAGird = 10;
private int bottomTextHeight = 0;
private ArrayList<String> bottomTextList;
private ArrayList<Integer> dataList;
private ArrayList<Integer> xCoordinateList = new ArrayList<Integer>();
private ArrayList<Integer> yCoordinateList = new ArrayList<Integer>();
private ArrayList<Dot> drawDotList = new ArrayList<Dot>();;
private Paint bottomTextPaint = new Paint();
private Paint ycoordTextPaint = new Paint();
private int bottomTextDescent;
//popup
private Paint popupTextPaint = new Paint();
private final int bottomTriangleHeight = 12;
//private Dot selectedDot;
private boolean mShowYCoordinate = true;
private int topLineLength = MyUtils.dip2px(getContext(), 12);; // | | 鈫this
//-+-+-
private int sideLineLength = MyUtils.dip2px(getContext(),45)/3*2;// --+--+--+--+--+--+--
// 鈫this 鈫
private int backgroundGridWidth = MyUtils.dip2px(getContext(),45);
//Constants
private final int popupTopPadding = MyUtils.dip2px(getContext(),2);
private final int popupBottomMargin = MyUtils.dip2px(getContext(),5);
private final int bottomTextTopMargin = MyUtils.sp2px(getContext(),5);
private final int bottomLineLength = MyUtils.sp2px(getContext(), 22);
private final int DOT_INNER_CIR_RADIUS = MyUtils.dip2px(getContext(), 2);
private final int DOT_OUTER_CIR_RADIUS = MyUtils.dip2px(getContext(),5);
private final int MIN_TOP_LINE_LENGTH = MyUtils.dip2px(getContext(),12);
private final int MIN_VERTICAL_GRID_NUM = 4;
private final int MIN_HORIZONTAL_GRID_NUM = 1;
private final int BACKGROUND_LINE_COLOR = Color.parseColor("#EEEEEE");
private final int BOTTOM_TEXT_COLOR = Color.parseColor("#9B9A9B");
private final int YCOORD_TEXT_LEFT_MARGIN = MyUtils.dip2px(getContext(), 10);
class YCoordData {
private int y;
private int data;
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public int getData() {
return data;
}
public void setData(int data) {
this.data = data;
}
}
private Runnable animator = new Runnable() {
@Override
public void run() {
boolean needNewFrame = false;
for(Dot dot : drawDotList){
dot.update();
if(!dot.isAtRest()){
needNewFrame = true;
}
}
if (needNewFrame) {
postDelayed(this, 0);
}
invalidate();
}
};
public LineView(Context context){
this(context,null);
}
public LineView(Context context, AttributeSet attrs){
super(context, attrs);
popupTextPaint.setAntiAlias(true);
popupTextPaint.setColor(Color.WHITE);
popupTextPaint.setTextSize(MyUtils.sp2px(getContext(), 13));
popupTextPaint.setStrokeWidth(5);
popupTextPaint.setTextAlign(Paint.Align.CENTER);
bottomTextPaint.setAntiAlias(true);
bottomTextPaint.setTextSize(MyUtils.sp2px(getContext(),12));
bottomTextPaint.setTextAlign(Paint.Align.CENTER);
bottomTextPaint.setStyle(Paint.Style.FILL);
bottomTextPaint.setColor(BOTTOM_TEXT_COLOR);
ycoordTextPaint.setAntiAlias(true);
ycoordTextPaint.setTextSize(MyUtils.sp2px(getContext(),12));
ycoordTextPaint.setTextAlign(Paint.Align.LEFT);
ycoordTextPaint.setStyle(Paint.Style.FILL);
ycoordTextPaint.setColor(BOTTOM_TEXT_COLOR);
}
/**
* dataList will be reset when called is method.
* @param bottomTextList The String ArrayList in the bottom.
*/
public void setBottomTextList(ArrayList<String> bottomTextList){
this.dataList = null;
this.bottomTextList = bottomTextList;
Rect r = new Rect();
int longestWidth = 0;
String longestStr = "";
bottomTextDescent = 0;
for(String s:bottomTextList){
bottomTextPaint.getTextBounds(s,0,s.length(),r);
if(bottomTextHeight<r.height()){
bottomTextHeight = r.height();
}
if(autoSetGridWidth&&(longestWidth<r.width())){
longestWidth = r.width();
longestStr = s;
}
if(bottomTextDescent<(Math.abs(r.bottom))){
bottomTextDescent = Math.abs(r.bottom);
}
}
if(autoSetGridWidth){
if(backgroundGridWidth<longestWidth){
backgroundGridWidth = longestWidth+(int)bottomTextPaint.measureText(longestStr,0,1);
}
if(sideLineLength<longestWidth/2){
sideLineLength = longestWidth/2;
}
}
refreshXCoordinateList(getHorizontalGridNum());
}
/**
*
* @param dataList The Integer ArrayList for showing,
* dataList.size() must < bottomTextList.size()
*/
public void setDataList(ArrayList<Integer> dataList){
this.dataList = dataList;
if(dataList.size() > bottomTextList.size()){
throw new RuntimeException("dacer.LineView error:" +
" dataList.size() > bottomTextList.size() !!!");
}
if(autoSetDataOfGird){
int biggestData = 0;
for(Integer i:dataList){
if(biggestData<i){
biggestData = i;
}
}
dataOfAGird = 1;
while(biggestData/10 > dataOfAGird){
dataOfAGird *= 10;
}
}
refreshAfterDataChanged();
setMinimumWidth(0); // It can help the LineView reset the Width,
// I don't know the better way..
postInvalidate();
}
public void setShowYCoordinate(boolean showYCoordinate) {
mShowYCoordinate = showYCoordinate;
}
private void refreshAfterDataChanged(){
int verticalGridNum = getVerticalGridlNum();
refreshTopLineLength(verticalGridNum);
refreshYCoordinateList(verticalGridNum);
refreshDrawDotList(verticalGridNum);
}
private int getVerticalGridlNum(){
int verticalGridNum = MIN_VERTICAL_GRID_NUM;
if(dataList != null && !dataList.isEmpty()){
for(Integer integer:dataList){
if(verticalGridNum<(integer+1)){
verticalGridNum = integer+1;
}
}
}
return verticalGridNum;
}
private int getHorizontalGridNum(){
int horizontalGridNum = bottomTextList.size()-1;
if(horizontalGridNum<MIN_HORIZONTAL_GRID_NUM){
horizontalGridNum = MIN_HORIZONTAL_GRID_NUM;
}
return horizontalGridNum;
}
private void refreshXCoordinateList(int horizontalGridNum){
xCoordinateList.clear();
for(int i=0;i<(horizontalGridNum+1);i++){
xCoordinateList.add(sideLineLength + backgroundGridWidth*i);
}
}
private void refreshYCoordinateList(int verticalGridNum){
yCoordinateList.clear();
for(int i=0;i<(verticalGridNum+1);i++){
yCoordinateList.add(topLineLength +
((mViewHeight-topLineLength-bottomTextHeight-bottomTextTopMargin-
bottomLineLength-bottomTextDescent)*i/(verticalGridNum)));
}
}
private void refreshDrawDotList(int verticalGridNum){
if(dataList != null && !dataList.isEmpty()){
int drawDotSize = drawDotList.isEmpty()? 0:drawDotList.size();
for(int i=0;i<dataList.size();i++){
int x = xCoordinateList.get(i);
int y = yCoordinateList.get(verticalGridNum - dataList.get(i));
if(i>drawDotSize-1){
drawDotList.add(new Dot(x, 0, x, y, dataList.get(i)));
}else{
drawDotList.set(i, drawDotList.get(i).setTargetData(x,y,dataList.get(i)));
}
}
int temp = drawDotList.size() - dataList.size();
for(int i=0; i<temp; i++){
drawDotList.remove(drawDotList.size()-1);
}
}
removeCallbacks(animator);
post(animator);
}
private void refreshTopLineLength(int verticalGridNum){
// For prevent popup can't be completely showed when backgroundGridHeight is too small.
// But this code not so good.
if((mViewHeight-topLineLength-bottomTextHeight-bottomTextTopMargin)/
(verticalGridNum+2)<getPopupHeight()){
topLineLength = getPopupHeight()+DOT_OUTER_CIR_RADIUS+DOT_INNER_CIR_RADIUS+2;
}else{
topLineLength = MIN_TOP_LINE_LENGTH;
}
}
@Override
protected void onDraw(Canvas canvas) {
drawBackgroundLines(canvas);
drawLines(canvas);
drawDots(canvas);
for(Dot dot : drawDotList){
drawPopup(canvas,
String.valueOf(dot.data),
dot.getPoint());
}
/*
if(showPopup && selectedDot != null){
drawPopup(canvas,
String.valueOf(selectedDot.data),
selectedDot.getPoint());
}*/
}
/**
*
* @param canvas The canvas you need to draw on.
* @param point The Point consists of the x y coordinates from left bottom to right top.
* Like is 3
* 2
* 1
* 0 1 2 3 4 5
*/
private void drawPopup(Canvas canvas,String num, Point point){
boolean singularNum = (num.length() == 1);
int sidePadding = MyUtils.dip2px(getContext(),singularNum? 8:5);
int x = point.x;
if(mShowYCoordinate == true) x += YCOORD_TEXT_LEFT_MARGIN;
int y = point.y-MyUtils.dip2px(getContext(),5);
Rect popupTextRect = new Rect();
popupTextPaint.getTextBounds(num,0,num.length(),popupTextRect);
Rect r = new Rect(x-popupTextRect.width()/2-sidePadding,
y - popupTextRect.height()-bottomTriangleHeight-popupTopPadding*2-popupBottomMargin,
x + popupTextRect.width()/2+sidePadding,
y+popupTopPadding-popupBottomMargin);
Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.drawable.popup_red);
byte chunk[] = bmp.getNinePatchChunk();
NinePatchDrawable popup = new NinePatchDrawable(bmp, chunk, new Rect(), null);
popup.setBounds(r);
popup.draw(canvas);
canvas.drawText(num, x, y-bottomTriangleHeight-popupBottomMargin, popupTextPaint);
}
private int getPopupHeight(){
Rect popupTextRect = new Rect();
popupTextPaint.getTextBounds("9",0,1,popupTextRect);
Rect r = new Rect(-popupTextRect.width()/2,
- popupTextRect.height()-bottomTriangleHeight-popupTopPadding*2-popupBottomMargin,
+ popupTextRect.width()/2,
+popupTopPadding-popupBottomMargin);
return r.height();
}
private void drawDots(Canvas canvas){
Paint bigCirPaint = new Paint();
bigCirPaint.setAntiAlias(true);
bigCirPaint.setColor(Color.parseColor("#FF0033"));
Paint smallCirPaint = new Paint(bigCirPaint);
smallCirPaint.setColor(Color.parseColor("#FFFFFF"));
if(drawDotList!=null && !drawDotList.isEmpty()){
for(Dot dot : drawDotList){
int x = dot.x;
if(mShowYCoordinate == true) x += YCOORD_TEXT_LEFT_MARGIN;
canvas.drawCircle(x,dot.y,DOT_OUTER_CIR_RADIUS,bigCirPaint);
canvas.drawCircle(x,dot.y,DOT_INNER_CIR_RADIUS,smallCirPaint);
}
}
}
private void drawLines(Canvas canvas){
Paint linePaint = new Paint();
linePaint.setAntiAlias(true);
linePaint.setColor(Color.parseColor("#FF0033"));
linePaint.setStrokeWidth(MyUtils.dip2px(getContext(), 2));
for(int i=0; i<drawDotList.size()-1; i++){
int x1 = drawDotList.get(i).x;
int x2 = drawDotList.get(i+1).x;
if(mShowYCoordinate == true) {
x1 += YCOORD_TEXT_LEFT_MARGIN;
x2 += YCOORD_TEXT_LEFT_MARGIN;
}
canvas.drawLine(x1,
drawDotList.get(i).y,
x2,
drawDotList.get(i+1).y,
linePaint);
}
}
private void drawBackgroundLines(Canvas canvas){
Paint paint = new Paint();
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(MyUtils.dip2px(getContext(),1f));
paint.setColor(BACKGROUND_LINE_COLOR);
//draw vertical lines
for(int i=0;i<xCoordinateList.size();i++){
int x = xCoordinateList.get(i);
if(mShowYCoordinate == true) x += YCOORD_TEXT_LEFT_MARGIN;
canvas.drawLine(x, 0, x,
mViewHeight - bottomTextTopMargin - bottomTextHeight-bottomTextDescent,
paint);
}
for(int i=0;i<yCoordinateList.size();i++){
if((yCoordinateList.size()-1-i)%dataOfAGird == 0){
int y = yCoordinateList.get(i);
canvas.drawLine(0, y, getWidth(), y, paint);
if(mShowYCoordinate == true)
canvas.drawText(String.valueOf(yCoordinateList.size()-i-1), 0, y, ycoordTextPaint);
}
}
//draw bottom text
if(bottomTextList != null){
for(int i=0;i<bottomTextList.size();i++){
int x = sideLineLength+backgroundGridWidth*i;
if(mShowYCoordinate == true) x += YCOORD_TEXT_LEFT_MARGIN;
canvas.drawText(bottomTextList.get(i), x,
mViewHeight-bottomTextDescent, bottomTextPaint);
}
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int mViewWidth = measureWidth(widthMeasureSpec);
mViewHeight = measureHeight(heightMeasureSpec);
refreshAfterDataChanged();
setMeasuredDimension(mViewWidth,mViewHeight);
}
private int measureWidth(int measureSpec){
int horizontalGridNum = getHorizontalGridNum();
int preferred = backgroundGridWidth*horizontalGridNum+sideLineLength*2;
return getMeasurement(measureSpec, preferred);
}
private int measureHeight(int measureSpec){
int preferred = 0;
return getMeasurement(measureSpec, preferred);
}
private int getMeasurement(int measureSpec, int preferred){
int specSize = MeasureSpec.getSize(measureSpec);
int measurement;
switch(MeasureSpec.getMode(measureSpec)){
case MeasureSpec.EXACTLY:
measurement = specSize;
break;
case MeasureSpec.AT_MOST:
measurement = Math.min(preferred, specSize);
break;
default:
measurement = preferred;
break;
}
return measurement;
}
/*
@Override
public boolean onTouchEvent(MotionEvent event) {
Point point = new Point();
point.x = (int) event.getX();
point.y = (int) event.getY();
Region r = new Region();
int width = backgroundGridWidth/2;
if(drawDotList != null || !drawDotList.isEmpty()){
for(Dot dot : drawDotList){
r.set(dot.x-width,dot.y-width,dot.x+width,dot.y+width);
if (r.contains(point.x,point.y) && event.getAction() == MotionEvent.ACTION_DOWN){
selectedDot = dot;
}else if (event.getAction() == MotionEvent.ACTION_UP){
if (r.contains(point.x,point.y)){
showPopup = true;
}
}
}
}
if (event.getAction() == MotionEvent.ACTION_DOWN ||
event.getAction() == MotionEvent.ACTION_UP){
postInvalidate();
}
return true;
}
//*/
private int updateSelf(int origin, int target, int velocity){
if (origin < target) {
origin += velocity;
} else if (origin > target){
origin-= velocity;
}
if(Math.abs(target-origin)<velocity){
origin = target;
}
return origin;
}
class Dot{
int x;
int y;
int data;
int targetX;
int targetY;
int velocity = MyUtils.dip2px(getContext(),20);
Dot(int x,int y,int targetX,int targetY,Integer data){
this.x = x;
this.y = y;
setTargetData(targetX, targetY,data);
}
Point getPoint(){
return new Point(x,y);
}
Dot setTargetData(int targetX,int targetY,Integer data){
this.targetX = targetX;
this.targetY = targetY;
this.data = data;
return this;
}
boolean isAtRest(){
return (x==targetX)&&(y==targetY);
}
void update(){
x = updateSelf(x, targetX, velocity);
y = updateSelf(y, targetY, velocity);
}
}
}
修改后我们可以通过增加的setShowYCoordinate方法设置是否显示y坐标文字。