Android自定义折线图

来源:互联网 发布:gartner 云计算 2017 编辑:程序博客网 时间:2024/05/19 06:15

最终实现效果如图所示,可以支持多个折线的对比效果,当手指滑动到相应区域的时候会显示相应点的折线的数据.

下面开始项目的构建,我们可以将整个效果分为几个部分,第一个部分是x轴和x轴上的文字,第二个部分是y轴和y轴上的文字,第三部分是数据上的线和圆的绘制,第四部分是响应我们的手指触摸事件.

其中X轴和Y轴中的部分可以直接绘制,但是对于关于数据上的每个点,我们可以知道每个点有相同的属性和方法,所以我们应该创建一个类LineViewPointBean,这个类里面应该有这个点在坐标系中的坐标Point类,一个x轴的数值和一个y轴的数值,还有一个这个点属于哪组数据的标识符sort.具体代码如下:

<span style="font-size:18px;">/** * 封装了LineView中数据点的类,每个点有4个属性,分别是他的x和y的值,以及在lineView中的position,还有他属于第几组数据 *  * @author CK *  */class LineViewPointBean {String xData, yData;// x,y的数据PointF position;// 每个点在当前屏幕中的位置int sort;// 每个点属于哪类数据public String getxData() {return xData;}public void setxData(String xData) {this.xData = xData;}public String getyData() {return yData;}public void setyData(String yData) {this.yData = yData;}public PointF getPosition() {return position;}public void setPosition(PointF position) {this.position = position;}public int getSort() {return sort;}public void setSort(int sort) {this.sort = sort;}/** * 每个点绘制自己的方法 *  * @param canvas * @param pointPaint */public void drawPoint(Canvas canvas, Paint pointPaint) {canvas.drawCircle(position.x, position.y, 10, pointPaint);}@Overridepublic String toString() {return "LineViewPointBean [xData=" + xData + ", yData=" + yData+ ", position=" + position + ", sort=" + sort + "]";}}</span>
然后我们看一下这个view应该接收的数据,对于x轴和y轴的数据可以直接设置为一个数组,然后是折线上的数据,因为有实际和目标两组数据,单个list无法满足要求,所以我们使用List<List<String>>来表示折线上的数据,其中外层list的长度代表有多少组数据,内层的list代表每层数据有多少个点.所以我们该view的构造方法应该传入这三个参数及Context(因为继承自View,在View的构造方法中需要用到Context,所以我们也要传入Context并使用super(context)).构造方法如下所示:
<span style="font-size:18px;">/** * @param dataSource *            数据源-最外层list的长度代表每种数据有多少种data,内层list长度代表每种data的长度 * @param yData *            y轴坐标值,分别传入0和最大值 * @param xData *            x轴的坐标值 */public CKLineView(List<List<String>> dataSource, String[] yData,String[] xData, Context context) {super(context);this.dataSource = dataSource;this.yData = yData;this.xData = xData;// 初始化颜色数组LINE_COLOR_DATA = new String[dataSource.size()];TEXT_COLOR_DATA = new String[dataSource.size()];// 初始化画笔paintLineData = new Paint[dataSource.size()];paintTextData = new Paint[dataSource.size()];}</span>
对于这个折线图,我们对每个部分的颜色是可以设置的,所以我们需要设置不同的画笔和颜色,然后x轴及y轴的尺寸,文字的大小
<span style="font-size:18px;">// sizeint MARGIN_SIZE_HORIZONTAL = 60, MARGIN_SIZE_VERTICAL = 60;//折线图的marginfloat TEXT_SIZE_X = 0, TEXT_SIZE_Y = 0, TEXT_SIZE_DATA = 0;//x轴文字,y轴文字,数据文字的大小int LENGTH_X, LENGTN_Y;//x,y轴的长度int SCALE_X, SCALE_Y;//x,y轴每个数据的长度int width, height;//view的宽高// colorString TEXT_COLOR_X, TEXT_COLOR_Y;String LINE_COLOR_X, LINE_COLOR_Y;String TEXT_COLOR_DATA[], LINE_COLOR_DATA[];// positionint xStart = 0, yStart = 0;//x轴和y轴的起始点// paintPaint paintLineX, paintLineY, paintTextX, paintTextY, paintLineData[],paintTextData[];</span>
接下来我们将传入的List<List<String>>的数据,转化为我们的List<List<LineViewBean>>,handleDataSource
<span style="font-size:18px;">void handleDataSource() {data = new ArrayList<List<LineViewPointBean>>();for (int i = 0; i < dataSource.size(); i++) {// 有多少条线List<String> tempDataSource = dataSource.get(i);List<LineViewPointBean> tempData = new ArrayList<LineViewPointBean>();for (int j = 0; j < tempDataSource.size(); j++) {// 每条线有多少点LineViewPointBean tempLineViewPointBean = new LineViewPointBean();tempLineViewPointBean.setxData(j + "");tempLineViewPointBean.setyData(tempDataSource.get(j));tempLineViewPointBean.setSort(i);PointF temPointF = new PointF();// 通过数据值,计算每个点Float x = 0f, y = 0f;x = (float) (xStart + j * SCALE_X);if (Float.parseFloat(yData[1]) == 0) {// 如果最大值和最小值都为0,那么应该做特殊处理.将0的位置设为y轴中间,同时所有的数据都为0y = (float) (yStart - 2 * SCALE_Y);tempLineViewPointBean.setyData("0");} else {y = yStart- SCALE_Y- (Float.parseFloat(tempDataSource.get(j)) / Float.parseFloat(yData[1])) * 2 * SCALE_Y;}temPointF.set(x, y);tempLineViewPointBean.setPosition(temPointF);tempData.add(tempLineViewPointBean);}data.add(tempData);}}</span>

最后我们看一下如何处理触摸事件,当我们手指滑动到某个点时,在ontouchEvent中判断触摸的点在哪个数据的区域内,然后我们就把touchRegion赋值,在draw方法中绘制.

@Overridepublic boolean onTouchEvent(MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_MOVE:for (int i = 0; i < data.get(0).size(); i++) {RectF tempRectF = new RectF((float) (xStart + (i - 0.5)* SCALE_X), 0f, (float) (xStart + (i + 0.5) * SCALE_X),(float) yStart);if (tempRectF.contains(event.getX(), event.getY())) {touchRegion = i;postInvalidate();return true;}}break;case MotionEvent.ACTION_DOWN:for (int i = 0; i < data.get(0).size(); i++) {RectF tempRectF = new RectF((float) (xStart + (i - 0.5)* SCALE_X), 0f, (float) (xStart + (i + 0.5) * SCALE_X),(float) yStart);if (tempRectF.contains(event.getX(), event.getY())) {touchRegion = i;postInvalidate();return true;}}break;}return true;}
最后我们看下这个view的全部代码:
/**折线图 * @author CK * */public class CKLineView extends View {// dataList<List<String>> dataSource;String[] yData;String[] xData;List<List<LineViewPointBean>> data;// sizeint MARGIN_SIZE_HORIZONTAL = 60, MARGIN_SIZE_VERTICAL = 60;//折线图的marginfloat TEXT_SIZE_X = 0, TEXT_SIZE_Y = 0, TEXT_SIZE_DATA = 0;//x轴文字,y轴文字,数据文字的大小int LENGTH_X, LENGTN_Y;//x,y轴的长度int SCALE_X, SCALE_Y;//x,y轴每个数据的长度int width, height;//view的宽高// colorString TEXT_COLOR_X, TEXT_COLOR_Y;String LINE_COLOR_X, LINE_COLOR_Y;String TEXT_COLOR_DATA[], LINE_COLOR_DATA[];// positionint xStart = 0, yStart = 0;//x轴和y轴的起始点// paintPaint paintLineX, paintLineY, paintTextX, paintTextY, paintLineData[],paintTextData[];// touchRegion手指滑动区域int touchRegion = -1;/** * @param dataSource *            数据源-最外层list的长度代表每种数据有多少种data,内层list长度代表每种data的长度 * @param yData *            y轴坐标值,分别传入0和最大值 * @param xData *            x轴的坐标值 */public CKLineView(List<List<String>> dataSource, String[] yData,String[] xData, Context context) {super(context);this.dataSource = dataSource;this.yData = yData;this.xData = xData;// 初始化颜色数组LINE_COLOR_DATA = new String[dataSource.size()];TEXT_COLOR_DATA = new String[dataSource.size()];// 初始化画笔paintLineData = new Paint[dataSource.size()];paintTextData = new Paint[dataSource.size()];}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);// 初始化init();// 开始画线// 画x轴canvas.drawLine(xStart, yStart, xStart+ (width - 2 * MARGIN_SIZE_HORIZONTAL), yStart, paintLineX);// 写x轴文字for (int i = 0; i < xData.length; i++) {if (i % 2 == 0) {float tempWidth = paintTextX.measureText(xData[i]);canvas.drawText(xData[i], xStart + i * SCALE_X - tempWidth / 2,yStart + TEXT_SIZE_X, paintTextX);}}// 画y轴for (int i = 0; i < xData.length; i++) {canvas.drawLine(xStart + i * SCALE_X, yStart, xStart + i * SCALE_X,yStart - (height - 2 * MARGIN_SIZE_VERTICAL), paintLineY);}// 写y轴文字// for (int i = 0; i < yData.length; i++) {// }if (Float.parseFloat(yData[1]) == 0) {// 如果最大值和最小值都为0,那么应该做特殊处理.将0的位置设为y轴中间float tempWidth = paintTextX.measureText(yData[1]);canvas.drawText(yData[1], xStart - tempWidth - 15, yStart - 2* SCALE_Y + TEXT_SIZE_Y / 2, paintTextY);} else {float tempWidth = paintTextX.measureText(yData[0]);canvas.drawText(yData[0], xStart - tempWidth - 15, yStart - SCALE_Y+ TEXT_SIZE_Y / 2, paintTextY);float tempWidth1 = paintTextX.measureText(yData[1]);canvas.drawText(yData[1], xStart - tempWidth1 - 15, yStart - 3* SCALE_Y + TEXT_SIZE_Y / 2, paintTextY);}// 画数据for (int i = 0; i < data.size(); i++) {List<LineViewPointBean> temp = data.get(i);for (int j = 0; j < temp.size(); j++) {LineViewPointBean tempBean = temp.get(j);tempBean.drawPoint(canvas, paintLineData[i]);if (j > 0) {canvas.drawLine(temp.get(j - 1).getPosition().x,temp.get(j - 1).getPosition().y, temp.get(j).getPosition().x,temp.get(j).getPosition().y, paintLineData[i]);}}}// 画选中的点和文字if (touchRegion >= 0) {// 初始化touchRegion为-1,如果有点击到相应区域会对此值进行更改为>=0的数for (int i = 0; i < data.size(); i++) {List<LineViewPointBean> temp = data.get(i);LineViewPointBean tempBean = temp.get(touchRegion);canvas.drawCircle(tempBean.getPosition().x,tempBean.getPosition().y, 13, paintLineData[i]);switch (tempBean.getSort()) {case 0:canvas.drawText(tempBean.getyData(),tempBean.getPosition().x + 5,tempBean.getPosition().y + 1.5f * TEXT_SIZE_DATA,paintTextData[i]);break;case 1:canvas.drawText(tempBean.getyData(),tempBean.getPosition().x + 5,tempBean.getPosition().y - TEXT_SIZE_DATA,paintTextData[i]);break;default:break;}}}}void init() {// 获取控件宽高width = getMeasuredWidth();height = getMeasuredHeight();// 计算xStart,yStartxStart = MARGIN_SIZE_HORIZONTAL;yStart = MARGIN_SIZE_VERTICAL + (height - 2 * MARGIN_SIZE_VERTICAL);// 计算SCALE_X SCALE_YSCALE_X = (width - 2 * MARGIN_SIZE_HORIZONTAL) / (xData.length-1);SCALE_Y = (height - 2 * MARGIN_SIZE_VERTICAL) / (yData.length + 2);// 初始化颜色LINE_COLOR_X = "#99666666";LINE_COLOR_Y = "#99666666";TEXT_COLOR_X = "#99666666";TEXT_COLOR_Y = "#99666666";for (int i = 0; i < TEXT_COLOR_DATA.length; i++) {TEXT_COLOR_DATA[i] = "#99666666";LINE_COLOR_DATA[i] = "#99666666";}// 初始化文字大小TEXT_SIZE_X = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 12,getResources().getDisplayMetrics());TEXT_SIZE_Y = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 12,getResources().getDisplayMetrics());TEXT_SIZE_DATA = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,12, getResources().getDisplayMetrics());// 初始化画笔paintLineX = paintSetColor(LINE_COLOR_X);paintLineY = paintSetColor(LINE_COLOR_Y);paintTextX = paintSetColor(TEXT_COLOR_X);paintTextX.setTextSize(TEXT_SIZE_X);paintTextY = paintSetColor(TEXT_COLOR_Y);paintTextY.setTextSize(TEXT_SIZE_Y);for (int i = 0; i < TEXT_COLOR_DATA.length; i++) {paintLineData[i] = paintSetColor(LINE_COLOR_DATA[i]);paintTextData[i] = paintSetColor(TEXT_COLOR_DATA[i]);paintTextData[i].setTextSize(TEXT_SIZE_DATA);}// 处理dataSourcehandleDataSource();}/** * 初始化画笔的辅助方法. *  * @param paint * @param color */Paint paintSetColor(String color) {Paint paint = new Paint();paint.setStyle(Paint.Style.FILL_AND_STROKE);paint.setAntiAlias(true);// 去锯齿paint.setColor(Color.parseColor(color));return paint;}void handleDataSource() {data = new ArrayList<List<LineViewPointBean>>();for (int i = 0; i < dataSource.size(); i++) {// 有多少条线List<String> tempDataSource = dataSource.get(i);List<LineViewPointBean> tempData = new ArrayList<LineViewPointBean>();for (int j = 0; j < tempDataSource.size(); j++) {// 每条线有多少点LineViewPointBean tempLineViewPointBean = new LineViewPointBean();tempLineViewPointBean.setxData(j + "");tempLineViewPointBean.setyData(tempDataSource.get(j));tempLineViewPointBean.setSort(i);PointF temPointF = new PointF();// 通过数据值,计算每个点Float x = 0f, y = 0f;x = (float) (xStart + j * SCALE_X);if (Float.parseFloat(yData[1]) == 0) {// 如果最大值和最小值都为0,那么应该做特殊处理.将0的位置设为y轴中间,同时所有的数据都为0y = (float) (yStart - 2 * SCALE_Y);tempLineViewPointBean.setyData("0");} else {y = yStart- SCALE_Y- (Float.parseFloat(tempDataSource.get(j)) / Float.parseFloat(yData[1])) * 2 * SCALE_Y;}temPointF.set(x, y);tempLineViewPointBean.setPosition(temPointF);tempData.add(tempLineViewPointBean);}data.add(tempData);}}@Overridepublic boolean onTouchEvent(MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_MOVE:for (int i = 0; i < data.get(0).size(); i++) {RectF tempRectF = new RectF((float) (xStart + (i - 0.5)* SCALE_X), 0f, (float) (xStart + (i + 0.5) * SCALE_X),(float) yStart);if (tempRectF.contains(event.getX(), event.getY())) {touchRegion = i;postInvalidate();return true;}}break;case MotionEvent.ACTION_DOWN:for (int i = 0; i < data.get(0).size(); i++) {RectF tempRectF = new RectF((float) (xStart + (i - 0.5)* SCALE_X), 0f, (float) (xStart + (i + 0.5) * SCALE_X),(float) yStart);if (tempRectF.contains(event.getX(), event.getY())) {touchRegion = i;postInvalidate();return true;}}break;}return true;}/** * 封装了LineView中数据点的类,每个点有4个属性,分别是他的x和y的值,以及在lineView中的position,还有他属于第几组数据 *  * @author CK *  */class LineViewPointBean {String xData, yData;// x,y的数据PointF position;// 每个点在当前屏幕中的位置int sort;// 每个点属于哪类数据public String getxData() {return xData;}public void setxData(String xData) {this.xData = xData;}public String getyData() {return yData;}public void setyData(String yData) {this.yData = yData;}public PointF getPosition() {return position;}public void setPosition(PointF position) {this.position = position;}public int getSort() {return sort;}public void setSort(int sort) {this.sort = sort;}/** * 每个点绘制自己的方法 *  * @param canvas * @param pointPaint */public void drawPoint(Canvas canvas, Paint pointPaint) {canvas.drawCircle(position.x, position.y, 10, pointPaint);}@Overridepublic String toString() {return "LineViewPointBean [xData=" + xData + ", yData=" + yData+ ", position=" + position + ", sort=" + sort + "]";}}}

0 0
原创粉丝点击