自定義帶節點點選的折線統計圖
阿新 • • 發佈:2018-12-17
最近專案中要加一個折線統計圖,要求每個節點可以點選並且可以實現展示資料,我就自己繪製了一個統計圖:
下面是自定義的方法:
public class TestLineChartView extends View { private boolean noTitle; private float marginLeft;// 左邊距 private float marginRight;// 右邊距 private float marginTop;// 上邊距 private float marginBottom;//下邊距 private float widthInterval;// 單位寬 private float heightInterval;// 單位高 private float viewHeight = 0;// 控制元件高度 private float viewWidth = 0;// 控制元件寬度 private List<Double> values;// 折線值 private List<String> dates;// 日期 private List<String> stringValues = new ArrayList<String>();//折線值String private double maxV;// 最大值 private double minV;// 最小值 private float valueInterval;// 每格畫素的value值 private List<String> numbers;// 縱座標 private int pointNum = 0;// 選中的是哪一個點 private float textSize = 0;// 字型大小 private float titleSize; private float xSize; private float ySize; private int fillCircleRadio = 14;// 實心圓的半徑 private int strokeCircleRadio = 16;//空心圓半徑 private int lineSize = 10; private int fillPointSize = 5;//實心圓 private int strokePointSize = 10;//空心圓粗細 private TextPaint FontPaint; private Context mContext; // 構造方法 public TestLineChartView(Context context) { this(context, null, 0); this.mContext = context; } public TestLineChartView(Context context, AttributeSet attrs) { this(context, attrs, 0); this.mContext = context; } public TestLineChartView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.mContext = context; } /** * 設定折線的值 * * @param values 折線值 * @param dates 日期(七天) */ public void setData(List<Double> values, List<String> dates, boolean noTitle) { if (values == null || values.size() == 0 || dates == null || dates.size() == 0) { return; } // 儲存傳入的引數 this.values = values; this.dates = dates; this.noTitle = noTitle; reInitData(); // 重繪 this.invalidate(); } // 設定字型大小 public void setTextSize(int textSize, int titleSize, int xSize, int ySize) { this.textSize = textSize; this.titleSize = titleSize; this.xSize = xSize; this.ySize = ySize; } @Override protected void onDraw(Canvas canvas) { clearCanvas(canvas);// 清空畫布 if (values == null || values.size() == 0 || dates == null || dates.size() == 0) { return; } initData(); if (!noTitle) { drawTitle(canvas);// 畫"近七天收益趨勢" } else { marginTop = marginBottom; } drewTable(canvas);// 畫表格虛線 drawX(canvas);// 畫日期 drawY(canvas);// 畫縱座標數字 drawDetail(canvas);// 畫詳情資訊(折線,圓點,圓角矩形) setClickable(true);// 設定控制元件為可點選 } private void reInitData() { maxV = Collections.max(values); //最大值 minV = Collections.min(values); //最小值 maxV = (((int) (maxV - minV - 0.001d)) / (dates.size() - 1) + 1) * (dates.size() - 1) + minV; //得到最長數,和最短數 for (double d : values) { String str = String.valueOf(d); stringValues.add(str); } pointNum = (dates.size() - 1); // 計算出縱座標顯示的值 numbers = new ArrayList<>(); for (int i = 0; i < values.size(); i++) { if (maxV == minV) { numbers.add(String.format("%.0f", 2 * maxV - (i) * maxV / (dates.size() - 1))); } else { numbers.add(String.valueOf((int) ((maxV - minV) * (values.size() - 1 - i) / (values.size() - 1) + minV))); } } lineSize = DisplayUtil.dip2px(mContext, 2f);//折線粗細 fillCircleRadio = DisplayUtil.dip2px(mContext, 3.5f);//實心圓半徑 strokeCircleRadio = DisplayUtil.dip2px(mContext, 4.25f);//空心圓半徑 fillPointSize = DisplayUtil.dip2px(mContext, 1.5f);//實心圓粗細 strokePointSize = DisplayUtil.dip2px(mContext, 2f);//空心圓粗細 } private void initData() { viewWidth = getMeasuredWidth(); viewHeight = getMeasuredHeight(); // 計算出字型大小 if (textSize == 0 || titleSize == 0 || xSize == 0 || ySize == 0) { textSize = viewWidth / 36;// 8.88 titleSize = textSize; xSize = textSize * 2 / 3; ySize = textSize * 2 / 3; } if (null == numbers || numbers.size() == 0) { return; } // 計算出左邊距 marginLeft = viewWidth / 30 + getTextWidth(ySize, numbers.get(0)) + strokeCircleRadio + strokePointSize; // 計算出右邊距 marginRight = viewWidth / 12; // 計算出上邊距 marginTop = getTextHeight(titleSize, "近七天收益趨勢") * 4; //計算出下邊距 marginBottom = viewHeight / 10; // 計算出單位寬 widthInterval = (viewWidth - marginLeft - marginRight) / (dates.size() - 1); // 計算出單位高 heightInterval = (viewHeight - marginTop - marginBottom) / (values.size() - 1); // 計算出每格畫素的value值 valueInterval = ((float) (maxV - minV)) / ((values.size() - 1) * heightInterval); } // 清空畫布 private void clearCanvas(Canvas canvas) { canvas.drawARGB(0, 0, 0, 0); } // 畫"近七天收益趨勢" private void drawTitle(Canvas canvas) { Paint paint = new Paint(); paint.setColor(getResources().getColor(R.color.colorSendName8)); paint.setTextSize(titleSize); paint.setStrokeWidth(1); paint.setAntiAlias(true); paint.setStyle(Paint.Style.FILL); float textHeight = getTextHeight(titleSize, "近七天收益趨勢"); textHeight = marginTop - textHeight * 5 / 2; canvas.drawText("近七天收益趨勢", getTextWidth(titleSize, "----"), textHeight, paint); } // 畫表格虛線 private void drewTable(Canvas canvas) { Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); paint.setColor(getResources().getColor(R.color.app_default_stroke_color)); paint.setStrokeWidth(4); paint.setStyle(Paint.Style.FILL_AND_STROKE); //畫虛線的 // PathEffect effects = new DashPathEffect(new float[]{2, 6}, 0); // paint.setPathEffect(effects); // 橫線 for (int i = 0; i < values.size(); i++) { Path path = new Path(); path.moveTo(marginLeft, marginTop + i * heightInterval); path.lineTo(marginLeft + (dates.size() - 1) * widthInterval, marginTop + i * heightInterval); canvas.drawPath(path, paint); } // 豎線 for (int i = 0; i < dates.size(); i++) { Path path = new Path(); path.moveTo(marginLeft + i * widthInterval, marginTop + (values.size() - 1) * heightInterval); path.lineTo(marginLeft + i * widthInterval, marginTop + (values.size() - 1) * heightInterval + (values.size() - 1)); canvas.drawPath(path, paint); } } private void drawDetail(Canvas canvas) { if (values == null || values.size() == 0 || textSize == 0) { return; } drawFoldLine(canvas);// 畫折線 drawCircle(canvas);// 畫圓圈 drawRoundRect(canvas);// 畫圓角矩形 } // 畫折線 private void drawFoldLine(Canvas canvas) { Paint paintFoldLine = new Paint(); paintFoldLine.setColor(getResources().getColor(R.color.colorSendName8)); paintFoldLine.setStrokeWidth(lineSize);// 資料線寬度(粗細) paintFoldLine.setAntiAlias(true); paintFoldLine.setStyle(Paint.Style.STROKE); Path pathFoldLine = new Path(); pathFoldLine.moveTo(marginLeft, marginTop + Float.parseFloat((maxV - values.get(0)) + "") / valueInterval); for (int i = 1; i < dates.size(); i++) { pathFoldLine.lineTo(marginLeft + i * widthInterval, marginTop + Float.parseFloat((maxV - values.get(i)) + "") / valueInterval); } canvas.drawPath(pathFoldLine, paintFoldLine); } // 畫圓圈 private void drawCircle(Canvas canvas) { Paint fillPaint = new Paint(); fillPaint.setColor(getResources().getColor(R.color.colorSendName8)); fillPaint.setStrokeWidth(fillPointSize); fillPaint.setAntiAlias(true); fillPaint.setStyle(Paint.Style.FILL); Paint fillPaintWhite = new Paint(); fillPaintWhite.setColor(Color.WHITE); fillPaintWhite.setStrokeWidth(strokePointSize); fillPaintWhite.setAntiAlias(true); fillPaintWhite.setStyle(Paint.Style.FILL); Paint paintStroke = new Paint(); paintStroke.setColor(getResources().getColor(R.color.colorSendName8)); paintStroke.setStrokeWidth(strokePointSize); paintStroke.setAntiAlias(true); paintStroke.setStyle(Paint.Style.STROKE); for (int i = 0; i < dates.size(); i++) { float sPointWidth = marginLeft + i * widthInterval; float sPointHeight = marginTop + Float.parseFloat((maxV - values.get(i)) + "") / valueInterval; if (i != pointNum) { canvas.drawCircle(sPointWidth, sPointHeight, fillCircleRadio, fillPaint); } else { canvas.drawCircle(sPointWidth, sPointHeight, strokeCircleRadio, fillPaintWhite); canvas.drawCircle(sPointWidth, sPointHeight, strokeCircleRadio, paintStroke); } } } // 畫圓角矩形 private void drawRoundRect(Canvas canvas) { // 畫圓角矩形 Paint paint = new Paint(); paint.setStyle(Paint.Style.FILL);// 充滿 paint.setColor(getResources().getColor(R.color.colorSendName2)); paint.setAntiAlias(true);// 設定畫筆的鋸齒效果 // 畫文字 Paint paintText = new Paint(); paintText.setStyle(Paint.Style.FILL);// 充滿 paintText.setColor(Color.WHITE); paintText.setTextSize(textSize); float textWidth = getTextWidth(textSize, stringValues.get(pointNum)); float textHeight = getTextHeight(textSize, stringValues.get(pointNum)); float left; float leftText; float right; float top; float bottom; float skewing = strokeCircleRadio + strokePointSize / 2; if (mContext == null) { return; } float skewingWidth = DisplayUtil.dip2px(mContext, 5f); if (pointNum == 0) { left = marginLeft - skewing; right = marginLeft + textWidth + 2 * skewingWidth - skewing; } else if (pointNum == (dates.size() - 1)) { left = marginLeft + pointNum * widthInterval - textWidth - 2 * skewingWidth + skewing; right = marginLeft + pointNum * widthInterval + skewing; } else { left = marginLeft + pointNum * widthInterval - textWidth * 1 / 2 - skewingWidth; right = marginLeft + pointNum * widthInterval + textWidth * 1 / 2 + skewingWidth; } leftText = left + skewingWidth; top = marginTop + Float.parseFloat(maxV - values.get(pointNum) + "") / valueInterval - textHeight * 7 / 2; bottom = marginTop + Float.parseFloat(maxV - values.get(pointNum) + "") / valueInterval - textHeight * 3 / 2; RectF oval3 = new RectF(left, top, right, bottom);// 設定個新的長方形 canvas.drawRoundRect(oval3, DisplayUtil.dip2px(mContext, 7.7f), DisplayUtil.dip2px(mContext, 5.5f), paint);// 第二個引數是x半徑,第三個引數是y半徑 canvas.drawText(stringValues.get(pointNum), leftText, top + textHeight * 3 / 2, paintText); } // 畫日期(橫座標) private void drawX(Canvas canvas) { Paint paint = new Paint(); paint.setColor(getResources().getColor(R.color.color_666666)); paint.setTextSize(xSize); paint.setStrokeWidth(1); paint.setAntiAlias(true); paint.setStyle(Paint.Style.FILL);// 數字字型的樣式 (粗細/實體或空心什麼的...) float textHeight = getTextHeight(xSize, dates.get(0)); float height = marginTop + (dates.size() - 1) * heightInterval + textHeight; for (int i = 0; i < dates.size(); i++) { Path path = new Path(); path.moveTo(marginLeft + i * widthInterval - getTextWidth(xSize, dates.get(i)) / 2, height + textHeight * 2 / 2);// 只用於移動移動畫筆。 path.lineTo(marginLeft + i * widthInterval + getTextWidth(xSize, dates.get(i)) / 2, height + textHeight * 2 / 2);// 用於進行直線繪製。 canvas.drawTextOnPath(dates.get(i), path, 0, 0, paint); } } // 畫縱座標數字 private void drawY(Canvas canvas) { if (numbers == null || numbers.size() == 0 || ySize == 0) { return; } Paint paint = new Paint(); paint.setColor(getResources().getColor(R.color.color_666666)); paint.setTextSize(ySize); paint.setStrokeWidth(1); paint.setAntiAlias(true); paint.setStyle(Paint.Style.FILL);// 數字字型的樣式 (粗細/實體或空心什麼的...) float width = marginLeft - getTextWidth(ySize, numbers.get(0)) - strokeCircleRadio - strokePointSize; float height = marginTop + getTextHeight(ySize, numbers.get(0)) / 2; for (int i = 0; i < numbers.size(); i++) { canvas.drawText(numbers.get(i), width, i * heightInterval + height, paint); } } // 點選事件 @Override public boolean onTouchEvent(MotionEvent event) { if (null != values && values.size() != 0) { if (event.getAction() == MotionEvent.ACTION_UP) { // 得到座標 float fx = event.getX(); float fy = event.getY(); for (int i = 0; i < values.size(); i++) { float width = marginLeft + i * widthInterval; float height = marginTop + Float.parseFloat((maxV - values.get(i)) + "") / valueInterval; if (fx > (width - widthInterval / 2) && fx < (width + widthInterval / 2)) { if (fy > (height - heightInterval) && fy < (height + heightInterval)) { pointNum = i; this.invalidate(); } } } } } return super.onTouchEvent(event); } private float getTextWidth(float Size, String text) { if (FontPaint == null) { FontPaint = new TextPaint(); } FontPaint.setTextSize(Size); return FontPaint.measureText(text); } private int getTextHeight(float Size, String text) { if (FontPaint == null) { FontPaint = new TextPaint(); } FontPaint.setTextSize(Size); Rect bounds = new Rect(); FontPaint.getTextBounds(text, 0, text.length(), bounds); return bounds.bottom + bounds.height(); } }
然後可以看到有點選事件可以點選每個節點
下面給貼出呼叫方法:
dateList.add("05-01"); dateList.add("05-02"); dateList.add("05-03"); dateList.add("05-04"); dateList.add("05-05"); dateList.add("05-06"); dateList.add("05-07"); earnList.add(12.33); earnList.add(31.33); earnList.add(16.28); earnList.add(60.12); earnList.add(22.44); earnList.add(52.21); earnList.add(67.66); data_table = findViewById(R.id.data_table); // 折線圖 chartView = (TestLineChartView) findViewById(R.id.chartView); chartView.setTextSize(25, 25, 25, 25); chartView.setData(earnList,dateList,true); data_table.setAdapter(new TableAdapter() { @Override public int getColumnCount() { return contentList.size(); } @Override public String[] getColumnContent(int position) { return contentList.get(position).toArray(); } });
這也是借鑑別人的然後做了個小修改,如有冒犯告知刪帖