自定义一个简单的折线图,足够展示万条数据,(千条以上数据在1070的屏幕宽度上 只会是一片茫茫,颜色会挤到一块儿。。。。)
简单说一下实现思路,
1、获取当前View在当前屏幕中的位置(Android中屏幕的左上角为整个屏幕坐标的原点往右 、往下递增,我们自己的坐标系是符合数学逻辑的二维坐标系,原点在整个折线图的左下角)
2、首先计算坐标点(比如,我们自己的坐标系的Y轴长度为画布的高度canasWidth减去50[我们默认距顶50],我们把获取到 的高度数值定为原点的Y轴坐标,X的走向与Android原生一致, 所以想让原点距离屏幕左边多远 ,就设置所多大的值为原点在屏幕的X坐标,记得留出足够的空间,需要去绘制Y轴刻度)
3、然后先绘制出X轴、Y轴,并根据数据的长度计算Y、X轴刻度(有十条数据,X轴长度除以10就是X周刻度,Y轴同理,根据 所有数据里的最大数值计算Y轴刻度)
4、别忘了绘制刻度数据,位置的计算在上一步中有相同之处,可以获取上边的值使用
5、绘制折线与虚线(X轴刻度)使用drawLines去绘制,会节省资源,虚线也要用这个去绘制,若使用(drawLine)绘制,会很慢
以下为自定义View核心类,在java代码中添加,目前没有做在布局中直接使用,另:提供demo供大家参考(点击此处),若有问题欢迎添加QQ :1017726485.共同讨论
csdn链接 Android自定义折线图
import android.app.Activity;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.support.annotation.ColorRes;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.LinearLayout;
import com.jjf.customchartline.R;
import com.jjf.customchartline.utils.Arith;
import com.jjf.customchartline.been.chartline.ChartResultData;
import com.jjf.customchartline.been.chartline.LineBeen;
import com.jjf.customchartline.been.chartline.LinePain;
import java.math.BigDecimal;
import java.util.ArrayList;
/**
* @author: jjf
* @date: 2018/4/19
* @describe:
*/
public abstract class MYChartLine extends View {
private float canasWidth; //画布宽
private float canasHeight;
private float XPoint; //定义原点
private float YPoint;
private double XScale; //刻度间距
private int YScale;
private int topDim = 30;//距离顶部
private int surplusHeight = 50;//Y轴超出最大刻度距离(计算Y轴时总长减去超出距离)
private int Ylenth = 6;//Y轴刻度数量
private int surplusWhith = 50;//X轴超出最大刻度距离
private String TAG = "MYChartLine";
private Context context;
//容器宽高
private int viewGroupWidth;
private int viewGroupHeight;
//虚线间隙
int dottedspace = 3;//虚线间隙
int dottedLength = 10;//虚线长度
//坐标轴宽度
private int axisWidth = 1;
//坐标轴颜色
private @ColorRes
int axisColor = R.color.zuobiao;//#a6a6a6
Resources resources;
int color;//
int colorBlue;
public int getYlenth() {
return Ylenth;
}
public void setYlenth(int ylenth) {
Ylenth = ylenth;
}
//坐标轴线的宽度
public void setAxisWidth(int axisWidth) {
this.axisWidth = axisWidth;
}
//轴颜色
public void setAxisColor(@ColorRes int axisColor) {
this.axisColor = axisColor;
color = context.getResources().getColor(axisColor);
}
boolean isRefresh = true;// false 刷新数据 true 添加数据
Activity activity;
public MYChartLine(Context context, ViewGroup viewGroup) {
super(context);
this.context = context;
if (context instanceof Activity) {
activity = (Activity) context;
}
resources = context.getResources();
//获取屏幕宽(自适应屏幕宽度)
WindowManager wm = ((Activity) context).getWindowManager();
viewGroupWidth = wm.getDefaultDisplay().getWidth();
// 取父控件高度(为了在布局中可以设置高度,不获取屏幕)
LinearLayout.LayoutParams linearParams = (LinearLayout.LayoutParams) viewGroup
.getLayoutParams();
viewGroupHeight = linearParams.height;
//画布宽高
canasWidth = viewGroupWidth - 10;
canasHeight = viewGroupHeight;
//原点坐标
XPoint = canasWidth / 12.0F;
YPoint = canasHeight - surplusHeight;
color = context.getResources().getColor(axisColor);
colorBlue = context.getResources().getColor(R.color.zhexian_blue);
//初始化画笔
//标记线(随手势移动)
paintYTag = new Paint();
paintYTag.setColor(colorBlue);
paintYTag.setStrokeWidth(2);
paintYTag.setAntiAlias(true);
paintYTag.setStyle(Paint.Style.STROKE);//设置画直线格式
paintYTag.setPathEffect(new DashPathEffect(new float[]{dottedLength, dottedspace}, 0));//画虚线关键代码
//轴画笔
paintYX = new Paint();
paintYX.setColor(color);
paintYX.setTextSize(30);
paintYX.setStrokeWidth(axisWidth);
paintYX.setAntiAlias(true);
//垂直虚线
paintDottenY = new Paint();
paintDottenY.setColor(color);
paintDottenY.setTextSize(30);
paintDottenY.setStrokeWidth(axisWidth);
paintDottenY.setAntiAlias(true);
paintDottenY.setStyle(Paint.Style.STROKE);//设置画直线格式
paintDottenY.setPathEffect(new DashPathEffect(new float[]{5, 3}, 1));//画虚线关键代码
//X轴数据
paintXText = new Paint();
paintXText.setColor(color);
paintXText.setTextSize(30);
paintXText.setAntiAlias(true);
YScale = (int) ((canasHeight - canasHeight / Ylenth) / Ylenth);//Y轴刻度间距
effectiveLenthX = (int) (canasWidth - XPoint - surplusWhith);
}
/**
* 桌布
*/
private Canvas canvas;
//Y轴
Paint paintYX;
//Y轴数据
Paint paintYText;
//X轴数据
Paint paintXText;
//垂直虚线
Paint paintDottenY;
//与Y轴平行的、可移动标记线
Paint paintYTag;
//标记线
Path tagPath = new Path();
//清除画笔
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
this.canvas = canvas;
//标记线(随手势移动)
canvas.drawPath(tagPath, paintYTag);
//轴画笔
//Y轴
canvas.drawLine(XPoint, topDim, XPoint, YPoint, paintYX);
//X轴
canvas.drawLine(XPoint, YPoint, canasWidth, YPoint, paintYX);
//X轴数据
YScale = (int) ((canasHeight - canasHeight / Ylenth) / Ylenth);//Y轴刻度间距
this.onMDraw();
}
//画笔集合 记录每一条折线 //VALUE==折线与折线标注字体
ArrayList<Paint> painLines = new ArrayList<>();
//是否设置Y轴数据为10、100、1000倍数
private boolean isYDataToMultiple = true;
private LinePain maxLenthlinePain;//折线图最长的折线
/**
* 控制Y轴数据为10、100、1000的倍数(只有Y轴当前数据大与10倍才会抹去尾数 如 12 不会变成10 112会变成110,1234会变成1200,12345会变成12000)
*/
public void setYDataToMultiple(boolean isYDataToMultiple) {
this.isYDataToMultiple = isYDataToMultiple;
}
int xuLineNum = 0; //虚线个数、、X轴分割区数
LinePain[] dataList;//所有条线的数据
double maxValue = 0;
/**
* 添加折线
* isRefresh true 刷新数据 全部重新绘制
* false 添加数据 增加画笔
*/
public void addLine(LinePain... linePain) {
System.out.println("开始---");
//onDraw 方法会运行两次,这里控制一段代码 只运行一次
if (isRefresh) {
chartResultData.setDatasY(chartYList);
dataList = new LinePain[linePain.length];
}
try {
//虚线个数、、X轴分割区数
xuLineNum = 0;
//所有Y轴数据中最大一条数据值--用最大值计算Y轴刻度平均值
maxValue = 0;
painLines.clear();
//初始化、创建数据
createData(linePain);
//四舍五入求最大位数的整数
//返回数据
chartResultData.setDataX(maxLenthlinePain.getLineBeens().get(maxLenthlinePain.getLineBeens().size() - 1).getX() + "");
//单位像素 代表的数据大小(Y轴)
//Y轴实际单位长度(对应数据)
double heightY = Arith.div(maxY - minY, maxValue, 10).doubleValue();
//X轴刻度间距
if (xuLineNum > 1) {
XScale = ( canasWidth - XPoint - surplusWhith)/(xuLineNum-1);
} else {
XScale = 1;
}
System.out.println("刻画Y轴---");
//画 Y轴数据
drawYData(linePain);
System.out.println("刻画Y轴结束---");
//计算日期间隔(最多显示7个日期,计算相邻两个日期中间间隔的单位个数)
int inDex = xuLineNum / 6;
System.out.println("计算虚线,折线开始---");
//折线位置
ArrayList<float[]> lineschart = new ArrayList<>();
//计算虚线有多少截
int dottedNum = (int) ((YPoint - topDim) / (dottedspace + dottedLength));
//(每一条虚线的每一截都要计算出来)虚线位置
float[] dottedLine = new float[xuLineNum * dottedNum * 4];
//虚线个数--代表单条折线数据长度
for (int j = 0; j < xuLineNum; j++) {
int X = (int) (XPoint + j * XScale); //X轴刻度位置
//折线上数值 以及X轴刻度
if (xuLineNum > 6) {
if (j % inDex == 0) {
//X轴坐标
canvas.drawText(maxLenthlinePain.getLineBeens().get(j).getX() + "",
(int) (XPoint + (j * XScale) - surplusWhith),
YPoint + 30, paintXText);
}
} else {
//X轴坐标
canvas.drawText(maxLenthlinePain.getLineBeens().get(j).getX() + "",
(int) (XPoint + (j * XScale) - maxLenthlinePain.getLineBeens().get(j).getX().length() * 8),
YPoint + 30, paintXText);
}
//每条虚线的每段虚线节点
double dottedPosition = 0;
if (j < xuLineNum - 1) {
//计算每条虚线的每一节
for (int i = 0; i < dottedNum; i++) {
//Y轴平行虚线
dottedLine[j * dottedNum * 4 + i * 4] = (float) (X + XScale);
dottedLine[j * dottedNum * 4 + i * 4 + 1] = (float) (topDim + dottedPosition);
dottedLine[j * dottedNum * 4 + i * 4 + 2] = (float) (X + XScale);
dottedLine[j * dottedNum * 4 + i * 4 + 3] = (float) (topDim + dottedPosition + dottedLength);
dottedPosition += (dottedLength + dottedspace);
}
}
for (int c = 0; c < linePain.length; c++) {
if (lineschart.size() < c + 1) {//添加一条折线的容器(有几条折线,会添加几次)
lineschart.add(new float[xuLineNum * 4]);
}
// 折线Y点位置
double startY = 0;
//当前折线长度是否到达 j 脚标(折线长度不一定相等 对比是否达到最长折线)
if (linePain[c].getLineBeens().size() > j) {
// 折线Y点位置
startY = YPoint - Arith.mul(heightY + "", linePain[c].getLineBeens().get(j).getY() + "");
//折线上数值 以及X轴刻度
if (xuLineNum > 6) {
if (j % inDex == 0) {
//查看本折线是否展示连接点值,true显示折线上的值
if (linePain[c].isShowPrompt()) {
canvas.drawText(linePain[c].getLineBeens().get(j).getY() + "", X - 10, (float) (startY - 20), painLines.get(c));
}
}
} else {
if (linePain[c].isShowPrompt()) {
//显示线上的值
canvas.drawText(linePain[c].getLineBeens().get(j).getY() + "", X - 15, (float) (startY - 20), painLines.get(c));
}
}
//折线(与虚线)在倒数第二次已经完成绘制----折线数据计算
if (j < linePain[c].getLineBeens().size() - 1) {
//实际高度
double z1 = Arith.mul(heightY + "", linePain[c].getLineBeens().get(j + 1).getY() + "");
lineschart.get(c)[j * 4] = X;
lineschart.get(c)[j * 4 + 1] = (float) startY;
lineschart.get(c)[j * 4 + 2] = (float) (XPoint + (j + 1) * XScale);
lineschart.get(c)[j * 4 + 3] = YPoint - ((float) z1);
}
}
}
}
System.out.println("渲染虚线,折线开始---");
//画虚线
canvas.drawLines(dottedLine, paintDottenY);
// 折线
for (int i = 0; i < painLines.size(); i++) {
canvas.drawLines(lineschart.get(i), painLines.get(i));
}
System.out.println("渲染虚线,折线结束---" + System.currentTimeMillis());
this.postInvalidate();
isRefresh = false;
} catch (Exception e) {
Log.i(TAG, "---------" + e.toString());
e.printStackTrace();
}
}
private int lastX = 0;//记录标记线的位置
private int lastY = 0;
private int startYLineX = 0;//Y轴的X坐标
private int effectiveLenthY = 0;//Y轴有效长度
private int effectiveLenthX = 0;//X轴有效长度
@Override
public boolean onTouchEvent(MotionEvent event) {
//获取到手指处的横坐标和纵坐标
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
try {
getParent().requestDisallowInterceptTouchEvent(false);
} catch (Exception e) {
}
case MotionEvent.ACTION_MOVE:
System.out.println("effectiveLenthX=="+effectiveLenthX);
if ((x > XPoint && x < effectiveLenthX + XPoint) && (y < YPoint && y > topDim)) {
lastX = x;
tagPath.reset();
tagPath.moveTo(lastX, topDim - 5);
tagPath.lineTo(lastX, YPoint);
// postInvalidate();
//
setToData(x);
}
break;
case MotionEvent.ACTION_UP:
try {
getParent().requestDisallowInterceptTouchEvent(true);
} catch (Exception e) {
}
break;
}
return true;
}
//计算集合中最大值与最小值
//最大值
public double ArrayListMax(ArrayList<LineBeen> sampleList) {
try {
double maxDevation = 0.0;
int totalCount = sampleList.size();
if (totalCount >= 1) {
double max = strToDouble(sampleList.get(0).getY());
for (int i = 0; i < totalCount; i++) {
double temp = strToDouble(sampleList.get(i).getY());
if (temp > max) {
max = temp;
}
}
maxDevation = max;
}
return maxDevation;
} catch (Exception ex) {
throw ex;
}
}
//调用本类的其他 需要实现本方法、在本方法去调用
public abstract void onMDraw();
private int minY = 0;
private int maxY = 0;
//监听手势 返回触摸到位置的值
private ChartResultData chartResultData = new ChartResultData();
//所有折线的Y值 添加到 chartResultData 中
ArrayList<String> chartYList = new ArrayList();
/**
* \
* <p>
* 返回手势选中数据
*
* @return
*/
public abstract ChartResultData setData(ChartResultData chartResultData);
/**
* 初始化数据
* 创建、计算相关变量
*/
private void createData(LinePain... linePain) throws IllegalAccessException {
int x = 0;
for (int i = 0; i < linePain.length; i++) {
dataList[i] = linePain[i];
Paint paint = new Paint();
paint.setStrokeWidth(linePain[i].getLineWidth());
paint.setAntiAlias(true);
paint.setColor(resources.getColor(linePain[i].getColorId()));
paint.setTextSize(linePain[i].getNumberPromptSize());
if (linePain[i].getLineBeens() != null && linePain[i].getLineBeens().size() > 0) {
//找出最多数据的折线
if (xuLineNum < linePain[i].getLineBeens().size()) {
xuLineNum = linePain[i].getLineBeens().size();
}
//Y轴最大刻度值
if (maxValue < ArrayListMax(linePain[i].getLineBeens())) {
maxValue = ArrayListMax(linePain[i].getLineBeens());
}
int size = linePain[i].getLineBeens().size();
if (x < size) {//找出最长的一条折线(多条折线不一定一样长短)
x = size;
maxLenthlinePain = linePain[i];
}
if (maxLenthlinePain == null) {
maxLenthlinePain = linePain[0];
}
} else {
return;
}
painLines.add(paint);
}
if (maxValue > 1000) {
maxValue = Arith.div(maxValue, (double) 1000, 3).setScale(0, BigDecimal.ROUND_HALF_UP).doubleValue() * 1000;
} else if (maxValue > 100) {
maxValue = Arith.div(maxValue, (double) 100, 3).setScale(0, BigDecimal.ROUND_HALF_UP).doubleValue() * 100;
} else if (maxValue > 10) {
maxValue = Arith.div(maxValue, (double) 10, 3).setScale(0, BigDecimal.ROUND_HALF_UP).doubleValue() * 10;
} else if (maxValue < 5) {
maxValue = 5;
}
}
public double strToDouble(String str) {
return Double.parseDouble(str);
}
/**
* 刻画Y轴数据
*/
public void drawYData(LinePain... linePain) {
//Y轴数据
if (paintYText == null) {
paintYText = new Paint();
}
paintYText.setColor(color);
paintYText.setTextSize(30);
paintYText.setAntiAlias(true);
String strY;
//Y轴一个刻度 等于的数据大小
int v1 = (int) (new BigDecimal(maxValue / (Ylenth - 1)).setScale(0, BigDecimal.ROUND_HALF_UP).doubleValue());
//Y轴刻度数据
for (int i = 0; i < Ylenth; i++) {
int i1 = (int) (maxValue - i * v1);
if (i1 < 0) {
i1 = 0;
}
/**
*使数据为10、100、1000的倍数---纯属为了数据好看,
* 可以通过 {@link com.jjf.customchartline.view.MYChartLine#setYDataToMultiple(boolean)} 设置
*/
if (isYDataToMultiple) {
if (i1 > 1000) {
strY = i1 / 100 * 100 + "";
} else if (i1 > 100) {
strY = i1 / 10 * 10 + "";
} else {
strY = i1 + "";
}
} else {
strY = i1 + "";
}
// Y轴文字
int heigY = i * YScale + (linePain[0].getNumberPromptSize() / 2) + topDim + surplusHeight;
if (i < Ylenth - 1) {
float v = XPoint - strY.length() * (linePain[0].getNumberPromptSize() / 2) - 10;
if (v < 0) {
v = 0;
}
canvas.drawText(strY, v, heigY, paintYText);
}
if (i == 0) {
minY = heigY;
} else if (i == Ylenth - 1) {
maxY = heigY;
}
}
}
//根据手势位置返回数据到页面
public void setToData(int x) {
try {
double mx = XScale / 2;
x = (int) (x + mx);
int div = (int) Arith.div((x - XPoint), XScale == 0 ? 1 : XScale, 2).doubleValue();
chartResultData.getDatasY().clear();
for (int j = 0; j < dataList.length; j++) {
if (dataList[j].getLineBeens().size() > div) {
chartResultData.setDataX(dataList[j].getLineBeens().get(div).getX());
chartResultData.getDatasY().add(dataList[j].getLineBeens().get(div).getY() + "");
} else {
chartResultData.getDatasY().add("0");
}
}
setData(chartResultData);
// MYChartLine.this.postInvalidate();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}