public class MyChartView extends View { private static final int LEFT = 1;//在通用函数中用于区别左边y轴和右边y轴 private static final int RIGHT = 2; private Paint mPaint; private int xNums;//x轴坐标分割点个数 private int yNums;//y轴坐标分割点个数 private String[] xTexts;//x坐标字段 private String[] yBarTexts;//y坐标字段 private String[] yLineTexts;//y坐标字段 private double[] yBarUnits;//y坐标值 private double[] yLineUnits;//y坐标值 private int xStep;//x轴上每一个字段距离 private int yStep;//y轴上每一个虚线距离 private int yBarStepVal;//y轴字段间距值 private int yLineStepVal;//y轴字段间距值 private int width;//获取控件宽度 private int height;//获取控件高度 private boolean isBarPercent = false;//检测yStepVal是不是百分数 private boolean isLinePercent = false;//检测yStepVal是不是百分数 private int canDrawMaxNum = 0;//根据屏幕宽度设置x轴最多可显示的字段数 private float xOffset = 0;//当前的位置 private float startX;//手指按下位置 private int MAX_OFFSET;//移动范围 private float xOffsetThisTime;//现在要移动的值 private boolean isFirstInitDraw = true;//是否第一次画图,使图表初始化在最后数据的位置 private boolean isMoveable = false;//检查是不是需要滑动 public MyChartView(Context context) { super(context); } public MyChartView(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); initDraw(); drawAxis(canvas); drawYAxisText(canvas); drawHLines(canvas); Log.d("xOffset = ", xOffset + ""); if (isMoveable) { //移动绘制画柱状图和折线图区域,移动绘制x轴文字区域 canvas.save(); canvas.clipRect(dp2px(30) + dp2px(3), dp2px(30) + dp2px(1), width - dp2px(30) - dp2px(3), height + dp2px(30), Region.Op.REPLACE); canvas.translate(xOffset, 0); drawBarChart(canvas); drawXAxisText(canvas); drawLineChart(canvas); canvas.restore(); } else { //不用移动时候直接绘制内容 drawBarChart(canvas); drawXAxisText(canvas); drawLineChart(canvas); } } @Override public boolean onTouchEvent(MotionEvent event) { if (isMoveable) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: startX = (int) event.getRawX(); break; case MotionEvent.ACTION_MOVE: float stopX = (int) event.getRawX(); xOffsetThisTime = stopX - startX; xOffset += xOffsetThisTime; startX = stopX; invalidate(); isFirstInitDraw = false; break; case MotionEvent.ACTION_UP: //smooth scroll new Thread(new SmoothScrollThread(xOffsetThisTime)).start(); break; } return true; } else { return false; } } /** * 初始化onDraw函数中需要的一些变量 */ private void initDraw() { mPaint = new Paint(); width = getWidth(); height = getHeight() - dp2px(30); canDrawMaxNum = (width - dp2px(60)) / dp2px(30); Log.d("canDrawMaxNum", canDrawMaxNum + ""); //设置y坐标字段,尽量以整数来分割 setYTexts(yBarUnits, LEFT); setYTexts(yLineUnits, RIGHT); for (int i = 0; i < yLineUnits.length; i++) { Log.d("yLineUnits" + i + " == ", "" + yLineUnits[i]); } //如果字段数比canDrawMaxNum小,则将字段加宽以适应屏幕 if (yBarTexts.length - 1 < canDrawMaxNum) { xStep = (width - dp2px(60)) / (yBarTexts.length - 1); } else { xStep = (width - dp2px(60)) / canDrawMaxNum; } yStep = (height - dp2px(30)) / (yBarTexts.length - 1); MAX_OFFSET = (dp2px(30) + xStep / 2 * (1 + 2 * (xTexts.length - 1)) + dp2px(10) + dp2px(10)) - (dp2px(30) + xStep / 2 - dp2px(10)) - (width - dp2px(60)); if (isFirstInitDraw) { xOffset = -MAX_OFFSET; } } /** * 画坐标轴 * * @param canvas */ private void drawAxis(Canvas canvas) { mPaint.setAntiAlias(true); mPaint.setColor(Color.BLACK); mPaint.setStrokeWidth(1); //画x轴 canvas.drawLine(dp2px(30), height, width - dp2px(30), height, mPaint); //画左边y轴 canvas.drawLine(dp2px(30), height, dp2px(30), dp2px(10), mPaint); //画左边y轴箭头 canvas.drawLine(dp2px(30), dp2px(10), dp2px(27), dp2px(13), mPaint); canvas.drawLine(dp2px(30), dp2px(10), dp2px(33), dp2px(13), mPaint); //画右边y轴 canvas.drawLine(width - dp2px(30), height, width - dp2px(30), dp2px(10), mPaint); //画右边y轴箭头 canvas.drawLine(width - dp2px(30), dp2px(10), width - dp2px(27), dp2px(13), mPaint); canvas.drawLine(width - dp2px(30), dp2px(10), width - dp2px(33), dp2px(13), mPaint); } /** * 画x轴坐标点处的文字 * * @param canvas */ private void drawXAxisText(Canvas canvas) { Log.d("yStep text = ", yStep + ""); mPaint.setAntiAlias(true); mPaint.setTextAlign(Paint.Align.LEFT); mPaint.setTextSize(sp2px(8)); mPaint.setColor(Color.BLACK); mPaint.setStrokeWidth(2); for (int i = 0; i < xTexts.length; i++) { canvas.drawText(xTexts[i], dp2px(30) + xStep / 2 * (1 + 2 * i) - dp2px(10), height + dp2px(10), mPaint); } } /** * 画y轴坐标点处的文字 * * @param canvas */ private void drawYAxisText(Canvas canvas) { Log.d("yStep text = ", yStep + ""); mPaint.setAntiAlias(true); mPaint.setTextAlign(Paint.Align.LEFT); mPaint.setTextSize(sp2px(8)); mPaint.setColor(Color.BLACK); mPaint.setStrokeWidth(2); for (int i = 0; i < yBarTexts.length; i++) { canvas.drawText(yBarTexts[i], dp2px(10), height - (i) * yStep + dp2px(3), mPaint); } for (int i = 0; i < yLineTexts.length; i++) { canvas.drawText(yLineTexts[i], width - dp2px(30) + dp2px(10), height - (i) * yStep + dp2px(3), mPaint); } } /** * 画水平线 * * @param canvas */ private void drawHLines(Canvas canvas) { Log.d("yStep Line= ", yStep + ""); mPaint.setAntiAlias(true); mPaint.setTextAlign(Paint.Align.CENTER); mPaint.setColor(Color.LTGRAY); mPaint.setStrokeWidth(1); for (int i = 0; i < yBarTexts.length - 1; i++) { canvas.drawLine(dp2px(30), height - (i + 1) * yStep, width - dp2px(30), height - (i + 1) * yStep, mPaint); } } /** * 画柱状图 * * @param canvas */ private void drawBarChart(Canvas canvas) { mPaint.setAntiAlias(true); mPaint.setColor(Color.CYAN); mPaint.setStyle(Paint.Style.FILL); mPaint.setStrokeWidth(0); for (int i = 0; i < yNums; i++) { float drawHeight; if (!isBarPercent) { if (!yBarTexts[0].equals("0")) { drawHeight = (float) (height - yStep * ((yBarUnits[i] - yBarStepVal) / yBarStepVal)); } else { drawHeight = (float) (height - yStep * (yBarUnits[i] / yBarStepVal)); } } else { if (!yBarTexts[0].equals("0")) { drawHeight = (float) (height - (float) yStep * ((yBarUnits[i] * 100 - yBarStepVal) / yBarStepVal)); } else { drawHeight = (float) (height - (float) yStep * (yBarUnits[i] * 100 / yBarStepVal)); } } canvas.drawRect(dp2px(30) + xStep / 2 * (1 + 2 * i) - dp2px(10), drawHeight, dp2px(30) + xStep / 2 * (1 + 2 * i) + dp2px(10), height, mPaint); } } /** * 画折线图 * * @param canvas */ private void drawLineChart(Canvas canvas) { mPaint.setAntiAlias(true); mPaint.setStyle(Paint.Style.FILL); mPaint.setStrokeWidth(0); float x = 0, y = 0; for (int i = 0; i < yNums; i++) { float drawHeight; if (!isLinePercent) { if (!yLineTexts[0].equals("0")) { drawHeight = (float) (height - yStep * ((yLineUnits[i] - yLineStepVal) / yLineStepVal)); } else { drawHeight = (float) (height - yStep * (yLineUnits[i] / yLineStepVal)); } } else { if (!yLineTexts[0].equals("0")) { drawHeight = (float) (height - (float) yStep * ((yLineUnits[i] * 100 - yLineStepVal) / yLineStepVal)); } else { drawHeight = (float) (height - (float) yStep * (yLineUnits[i] * 100 / yLineStepVal)); } } mPaint.setColor(Color.MAGENTA); canvas.drawCircle(dp2px(30) + xStep / 2 * (1 + 2 * i), drawHeight, 5, mPaint); if (i > 0) { mPaint.setColor(Color.BLUE); canvas.drawLine(x, y, dp2px(30) + xStep / 2 * (1 + 2 * i), drawHeight, mPaint); } x = dp2px(30) + xStep / 2 * (1 + 2 * i); y = drawHeight; // if (i>0){ // canvas.drawLine(); // } } } /** * 坐标转换,从dp转换为px * * @param value * @return */ private int dp2px(int value) { float v = getContext().getResources().getDisplayMetrics().density; return (int) (v * value + 0.5f); } /** * 坐标转换,从sp转换为px * * @param value * @return */ private int sp2px(int value) { float v = getContext().getResources().getDisplayMetrics().scaledDensity; return (int) (v * value + 0.5f); } /** * 从服务器或其他地方传入数据,由此函数适配 * * @param object 传入数据以Json格式传入,Json格式如下: * { * "chart":{ * "xunits":["xUnit1","xUnit2",...], * "ybarunits":["yUnit1","yUnit2",...], * "ylineunits":["yUnit1","yUnit2",...] * } */ public void setData(JSONObject object) { try { //解析Json数据 JSONObject result = object.getJSONObject("chart"); JSONArray xArray = result.getJSONArray("xunits"); JSONArray yBarArray = result.getJSONArray("ybarunits"); JSONArray yLineArray = result.getJSONArray("ylineunits"); Log.d("barArray = ", yBarArray.toString()); Log.d("lineArray = ", yLineArray.toString()); //部署数据 xNums = yNums = xArray.length(); xTexts = new String[xNums]; xTexts = parseJsonArray2StringArray(xArray); yBarUnits = new double[yNums]; yLineUnits = new double[yNums]; String[] tempBar = parseJsonArray2StringArray(yBarArray); String[] tempLine = parseJsonArray2StringArray(yLineArray); for (int i = 0; i < yNums; i++) { yBarUnits[i] = Double.parseDouble(tempBar[i]); yLineUnits[i] = Double.parseDouble(tempLine[i]); } } catch (JSONException e) { e.printStackTrace(); } } /** * 从服务器或其他地方传入数据,由此函数适配 * * @param xUnits x轴数据,传入字段名称数组 * @param yBarUnits y轴数据,传入柱状图字段值数组 * @param yLineUnits y轴数据,传入折线图字段值数组 */ public void setData(String[] xUnits, String[] yBarUnits, String[] yLineUnits) { //部署数据 xNums = yNums = xUnits.length; xTexts = new String[xNums]; xTexts = xUnits; this.yBarUnits = new double[yNums]; this.yLineUnits = new double[yNums]; for (int i = 0; i < yNums; i++) { this.yBarUnits[i] = Double.parseDouble(yBarUnits[i]); this.yLineUnits[i] = Double.parseDouble(yLineUnits[i]); } } /** * 设置y坐标字段,尽量以整数来分割 * y坐标字段最多设置为x轴每个柱状图之间有间隔 * * @param yUnits * @param which 控制是左边还是右边 */ private void setYTexts(double[] yUnits, int which) { String[] yTempTexts; int yTempStepVal; boolean isTempPercent; if (yUnits.length > canDrawMaxNum) { yTempTexts = new String[canDrawMaxNum + 1]; isMoveable = true; } else { yTempTexts = new String[yUnits.length + 1]; isMoveable = false; } Log.d("yTempTexts.length = ", yTempTexts.length + ""); //获取yUnits的最大值和最小值 double max = 0; double min = yUnits[0]; for (int i = 0; i < yUnits.length; i++) { if (max < yUnits[i]) { max = yUnits[i]; } if (min > yUnits[i]) { min = yUnits[i]; } } Log.d("max, min ", max + ", " + min); //将最大值与最小值的差等分成yUnits.length份,并取相近的整数代替 int times = yUnits.length > canDrawMaxNum ? yUnits.length / canDrawMaxNum : 1; yTempStepVal = (int) (((max - min) / yUnits.length + 1) * times); //判断yTempStepVal是不是小于最小值 while (yTempStepVal * yTempTexts.length < max) { yTempStepVal++; } if (yTempStepVal < min) { //可以显示出最小值,不需要再处理了 if (yTempStepVal >= 2) { isTempPercent = false; Log.d("yTempStepVal = ", yTempStepVal + ""); for (int i = 0; i < yTempTexts.length; i++) { yTempTexts[i] = String.valueOf(yTempStepVal * (i + 1)); } } else { isTempPercent = true; yTempStepVal = (int) (((max - min) / yUnits.length) * 100 + 1); Log.d("yTempStepValP = ", yTempStepVal + ""); for (int i = 0; i < yTempTexts.length; i++) { yTempTexts[i] = yTempStepVal * (i + 1) + "%"; } } Log.d("isTempPercent = ", isTempPercent + ""); } else { //无法显示最小值,则起点从0开始,再重新调整yTempStepVal的值 while (yTempStepVal * (yTempTexts.length - 1) < max) { yTempStepVal++; } if (yTempStepVal >= 2) { isTempPercent = false; Log.d("yTempStepVal = ", yTempStepVal + ""); yTempTexts[0] = String.valueOf(0); for (int i = 1; i < yTempTexts.length; i++) { yTempTexts[i] = String.valueOf(yTempStepVal * i); } } else { isTempPercent = true; yTempStepVal = (int) (((max - min) / yUnits.length) * 100 + 1); Log.d("yTempStepValP = ", yTempStepVal + ""); yTempTexts[0] = String.valueOf(0); for (int i = 1; i < yTempTexts.length; i++) { yTempTexts[i] = yTempStepVal * (i + 1) + "%"; } } Log.d("isTempPercent = ", isTempPercent + ""); } if (which == LEFT) { //左边 yBarTexts = yTempTexts; yBarStepVal = yTempStepVal; isBarPercent = isTempPercent; } if (which == RIGHT) { //右边 yLineTexts = yTempTexts; yLineStepVal = yTempStepVal; isLinePercent = isTempPercent; } } /** * 将Json数组转换成字符串数组以方便操作 * * @param jsonArray * @return * @throws JSONException */ private String[] parseJsonArray2StringArray(JSONArray jsonArray) throws JSONException { String[] array = new String[jsonArray.length()]; for (int i = 0; i < jsonArray.length(); i++) { array[i] = jsonArray.getString(i); } return array; } /** * 自定义线程,用以控制图表移动速度和移动逻辑 */ private class SmoothScrollThread implements Runnable { float xOffsetThisTime; boolean scrolling = true; public SmoothScrollThread(float xOffsetThisTime) { this.xOffsetThisTime = xOffsetThisTime; scrolling = true; } @Override public void run() { while (scrolling) { long start = System.currentTimeMillis(); xOffsetThisTime = (int) (0.9f * xOffsetThisTime); xOffset += xOffsetThisTime; checkXOffset(); postInvalidate(); isFirstInitDraw = false; if (Math.abs(xOffsetThisTime) < 20) { scrolling = false; } long end = System.currentTimeMillis(); if (end - start < 20) { try { Thread.sleep(20 - (end - start)); } catch (InterruptedException e) { e.printStackTrace(); } } } } } /** * 检查移动是否在可移动范围内 */ private void checkXOffset() { if (xOffset > 0) { xOffset = 0; } if (xOffset < -MAX_OFFSET) { xOffset = -MAX_OFFSET; } } }