之前那几遍都是为了展示实现思路的,并不是真正的图实现。看过的人大致都知道接下来怎么做了,只不过是测量下折线图然后设置合理的大小。
这个下面本人实现的折线图是测试数据的图,随机点测试
package sam.android.utils.widget; import java.util.List; /** * 折线信息 */ 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; } }
/** * 折线点 */ 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; } }
package sam.android.utils.widget; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.util.AttributeSet; import android.view.View; import java.util.ArrayList; import java.util.List; /** * 绘制折线图详情 */ 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 = 45; /** * 折线图距离左边或右边多少距离,默认为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 = heightSize > configHeigh ? heightSize : (int) configHeigh; } else if (MeasureSpec.UNSPECIFIED == heightMode) { heightSize = (int) configHeigh; } if (MeasureSpec.AT_MOST == widthMode) { widthSize = widthSize > configWith ? widthSize : (int) 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(widthSize, 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()/2, originY + charToCoordinateAxisDistance + rect1.height()*2, 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 <= realXAxisLen - arrowToTextDistance; i += xUnit, j++) { int o = 0; //标志物的高度 if (0 == j % 10) { o = 20; 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); } else if (0 == j % 5) { 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); o = 15; } else o = 10; canvas.drawLine(originX + i, originY, originX + i, originY - o, oPaint); } for (int i = (int) yUnit, j = 1; i <= realYAxisLen - arrowToTextDistance; i += yUnit, j++) { int o = 0; //标志物的高度 if (0 == j % 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); o = 20; } else if (0 == j % 5) { 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); o = 15; } else o = 10; 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); } }
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <ScrollView android:layout_width="match_parent" android:layout_height="match_parent"> <HorizontalScrollView android:layout_width="match_parent" android:layout_height="match_parent"> <sam.android.utils.widget.LineChartView android:id="@+id/lineChartView" android:layout_width="match_parent" android:layout_height="match_parent" /> </HorizontalScrollView> </ScrollView> </LinearLayout> 如果折线点太大看会看不到,所以添加了滚动条
public class MainActivity extends AppCompatActivity { private LineChartView lineChartView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); 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(10)+1;//随机设置实际值 y += random.nextInt(10)+1; } LineChartPoint point = new LineChartPoint(); point.setxValue(x); point.setyValue(y); pointList.add(point);//添加折线点 } info.setPoints(pointList);//将对应的折线点添加到对应的折线信息上 lineChartView.addLineCharInfo(info); } lineChartView.update();//刷新界面 } }
待更新7