自定義一個簡單的折線圖,足夠展示萬條數據,(千條以上數據在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.共同討論
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();
}
}
}
項目資源地址 https://download.csdn.net/download/jiaojunfeng/10963486
上傳資源的時候 需要的C幣那裏沒法設置。默認是5。今天也沒法改,本想設置最少的那個1幣。。。。。