项目下载地址:https://github.com/Sam474850601/ChartView
效果图如下:
xml代码
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:orientation="vertical" android:layout_height="match_parent"> <TextView android:layout_width="match_parent" android:text="折线图" android:layout_height="?attr/actionBarSize" android:background="#009900" android:textSize="18sp" android:textColor="#ffffff" android:gravity="center" android:textStyle="bold" /> <ScrollView android:layout_width="wrap_content" android:layout_height="wrap_content"> <HorizontalScrollView android:layout_width="wrap_content" android:layout_height="wrap_content"> <sam.android.utils.widget.LineChartView xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/lineChartView" android:layout_width="wrap_content" android:layout_height="wrap_content" app:chartValueTextSize="12sp" app:minHeight="100dp" app:minWith="100dp" app:xMaxValue="15" app:xUnit="16dp" app:xName="@string/xName" app:yName="@string/yName" app:yChartMargin="20dp" app:yMaxValue="12" app:yUnit="20dp" /> </HorizontalScrollView> </ScrollView> </LinearLayout>
<resources> <string name="xName">数目(个/月)</string> <string name="yName">月份</string> </resources>
运行代码图:
运行完整代码:
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="LineChartView"> <!-- 视图x轴距离边缘的距离--> <attr name="xChartMargin" format="dimension"/> <!-- 视图y轴距离边缘的距离--> <attr name="yChartMargin" format="dimension"/> <!-- 单位文字大小--> <attr name="chartValueTextSize" format="dimension"/> <!-- 每x轴单位需要多少px距离--> <attr name="xUnit" format="dimension"/> <!-- 每y轴单位需要多少px距离--> <attr name="yUnit" format="dimension"/> <!-- x轴峰值是多少单位--> <attr name="xMaxValue" format="float"/> <!-- y轴峰值是多少单位--> <attr name="yMaxValue" format="float"/> <!-- 折线图最少宽度是多少--> <attr name="minWith" format="dimension"/> <!-- 折线图最少高度是多少--> <attr name="minHeight" format="dimension"/> <!-- x 轴名字 --> <attr name="xName" format="string"/> <!-- y 轴名字 --> <attr name="yName" format="string"/> </declare-styleable> </resources>
content_main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:orientation="vertical" android:layout_height="match_parent"> <TextView android:layout_width="match_parent" android:text="折线图" android:layout_height="?attr/actionBarSize" android:background="#009900" android:textSize="18sp" android:textColor="#ffffff" android:gravity="center" android:textStyle="bold" /> <ScrollView android:layout_width="wrap_content" android:layout_height="wrap_content"> <HorizontalScrollView android:layout_width="wrap_content" android:layout_height="wrap_content"> <sam.android.utils.widget.LineChartView xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/lineChartView" android:layout_width="wrap_content" android:layout_height="wrap_content" app:chartValueTextSize="12sp" app:minHeight="100dp" app:minWith="100dp" app:xMaxValue="15" app:xUnit="16dp" app:xName="@string/xName" app:yName="@string/yName" app:yChartMargin="20dp" app:yMaxValue="12" app:yUnit="20dp" /> </HorizontalScrollView> </ScrollView> </LinearLayout>
<resources> <string name="xName">数目(个/月)</string> <string name="yName">月份</string> </resources>
strings.xml
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.content_main); LineChartView lineChartView = (LineChartView) findViewById(R.id.lineChartView); Random random = new Random(); int[] colors ={Color.RED, Color.BLACK, Color.BLUE, Color.CYAN, Color.GREEN}; for(int i = 0; i < 5; i ++) { LineChartInfo info = new LineChartInfo(); info.setColor(colors[i]); int x =0; int y =0; List<LineChartPoint> pointList = new ArrayList<>(); for(int j =0 ; j < 4; j ++) { if(0 != j) { x += random.nextInt(3)+1;//随机设置实际值 y += random.nextInt(3)+1; } LineChartPoint point = new LineChartPoint(); point.setxValue(x); point.setyValue(y); pointList.add(point);//添加折线点 } info.setPoints(pointList);//将对应的折线点添加到对应的折线信息上 lineChartView.addLineCharInfo(info); } lineChartView.update();//刷新界面 } }
/** * 折线信息 * @author Sam */ public class LineChartInfo { /** * 折线名字 */ private String name; /** * 折线颜色 */ private int color; /** * 折线所有的折线点 */ private List<LineChartPoint> points; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getColor() { return color; } public void setColor(int color) { this.color = color; } public List<LineChartPoint> getPoints() { return points; } public void setPoints(List<LineChartPoint> points) { this.points = points; } }
/** * 折线点 * @author Sam */ public class LineChartPoint { /** * y坐标轴的实际值 */ private float yValue; /** * x坐标轴的实际值 */ private float xValue; public float getxValue() { return xValue; } public void setxValue(float xValue) { this.xValue = xValue; } public float getyValue() { return yValue; } public void setyValue(float yValue) { this.yValue = yValue; } }
/** * 绘制折线图详情 * @author Sam */ public class LineChartView extends View { /* ** * x 坐标最大值的文字长度 */ private int maxXChartValueLen = 0; /** * y 坐标最大值的文字长度 */ private int maxYChartValueLen = 0; /** * x轴每一单位多少px, 默认为2px/单位 */ private float xUnit = 16; /** * y轴每一单位多少px,默认为10px/单位 */ private float yUnit = 16; /** * x 轴最大单位值(非x位置值,其x位置值等于maxXValue*xUnit), 默认为200个单位 */ private float maxXValue = 30; /** * y 轴最大单位值 (非y位置值,其y位置值等于maxYValue*yUnit).默认为300个单位 */ private float maxYValue = 12; /** * 折线图距离左边或右边多少距离,默认为100px */ private float chartViewMarginX = 100; /** * 折线图距离上班边或下边多少距离, ,默认为100px */ private float chartViewMarginY = 100; /** * 折线图x坐标名字 */ private String xName = "x"; /** * 折线图y坐标名字 */ private String yName = "y"; /** * 坐标轴字体大小,默认32px */ private float coordinateValueTextSize = 32; /** * 坐标轴文字到坐标轴距离 */ private float charToCoordinateAxisDistance = 15; /** * 箭头到最大单位值的距离 */ private float arrowToTextDistance = 100; /** * 表示设置的视图最小宽度 */ private float configWith = 480; /** * 表示设置的视图最小高度 */ private float configHeigh = 800; private int lineNamesHeight; /** * y轴最大的c长度 */ float realYAxisLen; /** * x轴最大的长度 */ float realXAxisLen; /** * x坐标轴到底部距离 */ float marginXAxisDistance; /** * y坐标轴到左边距离 */ float marginYAxisDistance; @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); for(LineChartInfo lineChartInfo : lineChartInfos) { List<LineChartPoint> points = lineChartInfo.getPoints(); for(LineChartPoint point : points) { maxXValue = Math.max(maxXValue, point.getxValue()); maxYValue = Math.max(maxYValue, point.getyValue()); } } if (MeasureSpec.AT_MOST == heightMode) { heightSize = (int) Math.min(heightSize, configHeigh); } else if (MeasureSpec.UNSPECIFIED == heightMode) { heightSize = (int) configHeigh; } if (MeasureSpec.AT_MOST == widthMode) { widthSize = (int) Math.min(widthSize, configWith); } else if (MeasureSpec.UNSPECIFIED == widthMode) { widthSize = (int) configWith; } //x轴名字 CharObj CharObj xNameCharObj = getCharObj(xName, coordinateValueTextSize); //y轴名字 CharObj CharObj yNameCharObj = getCharObj(yName, coordinateValueTextSize); // x 轴最大单位值 CharObj CharObj maxXValueCharObj = getCharObj(maxXValue + "", coordinateValueTextSize); // y 轴最大单位值 CharObj CharObj maxYValueCharObj = getCharObj(maxYValue + "", coordinateValueTextSize); //计算x坐标轴到底部距离 , 文字到坐标轴距离+ 文字高度+marginY+上方名字长度 marginXAxisDistance = charToCoordinateAxisDistance + (xNameCharObj.heightLen > maxXValueCharObj.heightLen ? xNameCharObj.heightLen : maxXValueCharObj.heightLen) + chartViewMarginY + lineNamesHeight; //计算y坐标轴到左边距离 , 文字到坐标轴距离+ 文字宽度+marginX marginYAxisDistance = charToCoordinateAxisDistance + (yNameCharObj.withLen > maxYValueCharObj.withLen ? yNameCharObj.withLen : maxYValueCharObj.withLen) + chartViewMarginX; float maxYValuePX = maxYValue * yUnit; //最大y轴的单位值的实际位置 //显示视图需要的高度 int needHeight = (int) (configHeigh > maxYValuePX ? configHeigh : maxYValuePX ) + (int) (arrowToTextDistance+marginXAxisDistance * 2); float maxXValuePX = maxXValue * xUnit;//最大的x轴单位值的实际位置 //显示视图需要的宽度 int needWidth = (int) (configWith > maxXValuePX ? configWith : maxXValuePX ) + (int)(arrowToTextDistance+marginYAxisDistance * 2); heightSize = heightSize > needHeight ? heightSize : needHeight; widthSize = widthSize > needWidth ? widthSize : needWidth; realXAxisLen = widthSize - marginYAxisDistance * 2; realYAxisLen = heightSize - marginXAxisDistance * 2; super.setMeasuredDimension((int) (widthSize+xNameCharObj.withLen), heightSize); } private float originY; private float originX; /** * 绘制坐标轴 */ private void drawAxis(Canvas canvas) { //计算原点y坐标 originY = marginXAxisDistance + realYAxisLen; //计算原点x坐标相对手机的实际位置 = 文字到y坐标轴距离+ 文字的宽度 originX = marginYAxisDistance; Paint paint = new Paint(); paint.setStrokeWidth(5); paint.setTextSize(coordinateValueTextSize); canvas.drawLine(originX, originY, originX, marginXAxisDistance, paint);//画出y轴 canvas.drawLine(originX, originY, originX + realXAxisLen, originY, paint);//画出x轴 canvas.drawLine(originX, marginXAxisDistance, originX - 12, marginXAxisDistance + 12, paint);//画出y轴左半箭头 canvas.drawLine(originX, marginXAxisDistance, originX + 12, marginXAxisDistance + 12, paint);//画出y轴右半箭头 canvas.drawLine(originX + realXAxisLen, originY, originX + realXAxisLen - 12, originY + 12, paint);//画出x轴上半箭头 canvas.drawLine(originX + realXAxisLen, originY, originX + realXAxisLen - 12, originY - 12, paint);//画出x轴下半箭头 Rect rect1 = new Rect(); paint.setTextSize(coordinateValueTextSize+12); paint.getTextBounds(xName, 0, xName.length(), rect1); canvas.drawText(xName, originX + realXAxisLen-rect1.width()/4, originY + charToCoordinateAxisDistance + rect1.height(), paint); paint.getTextBounds(yName, 0, yName.length(), rect1); canvas.drawText(yName, originX +charToCoordinateAxisDistance+rect1.width()/2, marginXAxisDistance+ rect1.height()/2, paint); Paint oPaint = new Paint(); oPaint.setStrokeWidth(2); for (int i = (int) xUnit, j = 1; i/xUnit <= maxXValue; i += xUnit, j++) { int o = 0; //标志物的高度 if (0 == j % 10) { o = 20; } else if (0 == j % 5) { o = 15; } else o = 10; String value = "" + j; Rect rect = new Rect(); paint.setTextSize(coordinateValueTextSize); paint.getTextBounds(value, 0, value.length(), rect); canvas.drawText("" + j, originX + i-rect.width()/2, originY+charToCoordinateAxisDistance+rect.height(), paint); canvas.drawLine(originX + i, originY, originX + i, originY - o, oPaint); } for (int i = (int) yUnit, j = 1; i/yUnit <= maxYValue; i += yUnit, j++) { int o = 0; //标志物的高度 if (0 == j % 10) { o = 20; } else if (0 == j % 5) { o = 15; } else o = 10; String value = "" + j; Rect rect = new Rect(); paint.setTextSize(coordinateValueTextSize); paint.getTextBounds(value, 0, value.length(), rect); canvas.drawText(value,originX-charToCoordinateAxisDistance-rect.width(), originY - i+rect.height()/2, paint); canvas.drawLine(originX, originY - i, originX + o, originY - i, oPaint);//画y标志物 } } private List<LineChartInfo> lineChartInfos = new ArrayList<LineChartInfo>(); public void addLineCharInfo(LineChartInfo info) { if(null != info) lineChartInfos.add(info); } //刷新界面 public void update() { invalidate(); } private void drawLineChartInfo(Canvas canvas) { if(lineChartInfos.isEmpty()) return; for(LineChartInfo lineChartInfo : lineChartInfos) { List<LineChartPoint> points = lineChartInfo.getPoints(); if(null == points || points.isEmpty()) continue; Paint linePaint = new Paint(); linePaint.setStrokeWidth(5); linePaint.setColor(lineChartInfo.getColor()); Paint pointPaint = new Paint(); pointPaint.setStrokeWidth(10); for(int i = 0; i < points.size()-1; i ++) { LineChartPoint point = points.get(i); LineChartPoint point2 = points.get(i+1); int x = (int) (originX+point.getxValue()*xUnit); int y = (int) (originY-point.getyValue()*yUnit); int x1 = (int) (originX+point2.getxValue()*xUnit); int y1 = (int) (originY-point2.getyValue()*yUnit); canvas.drawLine( x,y,x1,y1,linePaint); canvas.drawPoint(x1, y1, pointPaint); } } } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); drawAxis(canvas); drawLineChartInfo(canvas); } /** * 获取文字会之后的CharObj * * @param value 文字 * @param textSize 文字大小 * @return CharObj */ private CharObj getCharObj(String value, float textSize) { Paint paint = new Paint(); paint.setTextSize(textSize); Rect rect = new Rect(); paint.getTextBounds(value, 0, value.length(), rect); return new CharObj(rect.width(), rect.height()); } /** * 记录绘制文字后,其文字高度,宽度。 */ class CharObj { float withLen;//测量文字的实际宽度 float heightLen;//测量文字的实际高度 CharObj(float withLen, float heightLen) { this.withLen = withLen; this.heightLen = heightLen; } } public LineChartView(Context context) { super(context); } public LineChartView(Context context, AttributeSet attrs) { super(context, attrs); TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.LineChartView); xUnit = typedArray.getDimension( R.styleable.LineChartView_xUnit, xUnit); yUnit = typedArray.getDimension( R.styleable.LineChartView_yUnit, yUnit); chartViewMarginX = typedArray.getDimension( R.styleable.LineChartView_xChartMargin, marginXAxisDistance); chartViewMarginY = typedArray.getDimension( R.styleable.LineChartView_yChartMargin, marginYAxisDistance); xName = typedArray.getString( R.styleable.LineChartView_xName); xName = null == xName?"x":xName; yName = typedArray.getString( R.styleable.LineChartView_yName); yName = null == yName?"y":yName; maxXValue = typedArray.getFloat(R.styleable.LineChartView_xMaxValue, maxXValue); maxYValue = typedArray.getFloat(R.styleable.LineChartView_yMaxValue, maxYValue); coordinateValueTextSize = typedArray.getDimension(R.styleable.LineChartView_chartValueTextSize,coordinateValueTextSize ); configWith = typedArray.getDimension(R.styleable.LineChartView_minWith,configWith ); configHeigh = typedArray.getDimension(R.styleable.LineChartView_minHeight,configHeigh ); } }