Android简单的折线图表之SimpleChart

这个类是该图标的主体类,其中实现的是整体折线图的代码,此类需要配合左坐标类来使用.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
package com.geyek.widget;

import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.widget.OverScroller;

import java.util.ArrayList;
import java.util.List;

/**
* Created by LiHuan on 10/09/16.
*/
public final class SimpleChart extends View implements GestureDetector.OnGestureListener {
private float mWidth;
private float mHeight;
private int row = 5;
private int column = 5;
private float maxY = 500;
private float finishMinX = 0;
private float mXlablePadding = 20;

private boolean hasMore; //左侧是否还有更多

private OverScroller mScroller;
private VelocityTracker mVelocityTracker;

/**
* 各种画笔
*/
private Paint mPaint = new Paint(); //通用画笔

private Paint mBackgroundPaint = new Paint(); //专门用来绘制背景的画笔
private int mBackgroundColor = 0; //背景的颜色,默认透明

private Paint mXLablePaint = new Paint(); //X轴上的文字画笔
private float mXLableSize = 16; //X轴上的文字大小,默认16
private int mXLableColor = Color.BLACK; //X轴上的文字的颜色,默认黑色

private Paint mWavePaint = new Paint(); //网格画笔
private int mWaveColor = Color.GRAY; //默认灰色
private float mWaveWidth = 0; //网格宽度,默认0,即1像素

private Paint mLinePaint = new Paint(); //默认的折线画笔
private int mLineColor = Color.RED; //默认红色
private float mLineWidth = 0; //折线粗细,默认0,即1像素

private Paint mPointPaint = new Paint(); //圆点画笔
private float mPointRadius = 3;
private int mPointColor = Color.BLACK; //默认圆点颜色,黑色

private Paint mHintPaint = new Paint(); //圆点旁边的文本
private float mHintSize = 16; //默认字体大小16
private int mHintColor = Color.BLACK; //默认黑色

private List & lt;
ChartPoint & gt;
chartPointList = new ArrayList & lt;
ChartPoint & gt;
();

public SimpleChart(Context context) {
this(context, null);
}

public SimpleChart(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}

public SimpleChart(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setClickable(true);
initPaint();
mScroller = new OverScroller(context);
}

private void initPaint() {
mBackgroundPaint.setColor(mBackgroundColor);
mBackgroundPaint.setAntiAlias(true);

mXLablePaint.setColor(mXLableColor);
mXLablePaint.setTextSize(mXLableSize);
mXLablePaint.setAntiAlias(true);

mWavePaint.setColor(mWaveColor);
mWavePaint.setStrokeWidth(mWaveWidth);
mWavePaint.setAntiAlias(true);

mLinePaint.setColor(mLineColor);
mLinePaint.setStrokeWidth(mLineWidth);
mLinePaint.setAntiAlias(true);

mPointPaint.setColor(mPointColor);
mPointPaint.setAntiAlias(true);

mHintPaint.setColor(mHintColor);
mHintPaint.setTextSize(mHintSize);
mHintPaint.setAntiAlias(true);

mPaint.setColor(Color.BLACK);
mPaint.setStrokeWidth(2);
mPaint.setAntiAlias(true);

}

/**
* 设置图标显示的最大值,即Y轴能够显示的最大值
*/
public void setMaxY(float maxY) {
if (maxY & lt; 0) maxY = 0;
this.maxY = maxY;
}

/**
* 设置图表背景的颜色
* @param color
*/
public void setBackgroundColor(int color) {
mBackgroundColor = color;
mBackgroundPaint.setColor(color);
}

/**
* 设置X轴的文本离X轴的距离
* @param padding
*/
public void setXlablePadding(float padding) {
mXlablePadding = padding;
}

/**
* X轴文本的字体大小
* @param size
*/
public void setXLableSize(float size) {
mXLableSize = size;
mXLablePaint.setTextSize(size);
}

/**
* X轴文本的颜色
* @param color
*/
public void setXLableColor(int color) {
mXLableColor = color;
mXLablePaint.setColor(color);
}

/**
* 网格线条的颜色
* @param width
*/
public void setWaveWidth(float width) {
mWaveWidth = width;
mWavePaint.setStrokeWidth(width);
}

/**
* 折线图线条的粗细
* @param width
*/
public void setLineWidth(float width) {
mLineWidth = width;
mLinePaint.setStrokeWidth(width);
}

/**
* 折线图线条的颜色
* @param color
*/
public void setLineColor(int color) {
mLineColor = color;
mLinePaint.setColor(color);
}

/**
* 设置网格的颜色
* @param color
*/
public void setWaveColor(int color) {
mWaveColor = color;
mWavePaint.setColor(color);
}

/**
* 设置点的颜色,如果ChartPoint中没有颜色或为0则使用这个颜色.
* @param color
*/
public void setPointColor(int color) {
mPointColor = color;
mPointPaint.setColor(color);
}

/**
* 圆点的半径
* @param radius
*/
public void setPointRadius(int radius) {
if (radius & lt; 0) radius = 0;
mPointRadius = radius;
}
public float getPointRadius() {
return mPointRadius;
}

/**
* 折线图中节点的标签字体大小
* @param size
*/
public void setHintSize(float size) {
mHintSize = size;
mHintPaint.setTextSize(size);
}

/**
* 折线图中节点的标签字体颜色
* @param color
*/
public void setHintColor(int color) {
mHintColor = color;
mHintPaint.setColor(color);
}

/**
* X轴显示的单元格数量
* @param column
*/
public void setColumn(int column) {
if (column & lt; = 0) column = 1;
this.column = column;
}

/**
* Y轴显示的单元格数量
* @param row
*/
public void setRow(int row) {
if (row & lt; = 0) row = 1;
this.row = row;
}

/**
* 获取Y轴能够显示的最大值
* @return
*/
public float getMaxY() {
return maxY;
}

/**
* 获取Y轴的单元格数量
* @return
*/
public int getRow() {
return row;
}

/**
* 获取表格实际高度(除了X轴文本及其Padding后的高度)
* @return
*/
public float getChartHeight() {
return mHeight - mXLableSize / 3 * 4 - mXlablePadding;
}

/**
* 获取X轴文本的字体大小
* @return
*/
public float getXLableSize() {
return mXLableSize;
}

/**
* 获取X轴文本与X轴的距离
* @return
*/
public float getXlablePadding() {
return mXlablePadding;
}

/**
* 测量控件
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@
Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mWidth = getMeasuredWidth();
mHeight = getMeasuredHeight();
}

/**
* 布局控件
* @param changed
* @param left
* @param top
* @param right
* @param bottom
*/
@
Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
}

/**
* 绘制控件
* @param canvas
*/
@
Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);

//判断绘制的方式
int size = chartPointList.size();
if (size & lt; = 0) {
hasMore = false;
//只绘制网格
onlyInitWave(canvas);
} else if (size & lt; = column) {
hasMore = false;
//从右到左绘制
initRight2Left(canvas);
} else {
hasMore = true;
//从右到左绘制
initRight2Left(canvas);
}
}

/**
* 多于column条数据时的绘制方式
* @param canvas
*/
private void initRight2Left(Canvas canvas) {
drawBackground(canvas);

float w = mWidth / column;
float h = mHeight + mPointRadius - (mXLableSize / 3 * 4 + mXlablePadding);
//绘制网格,从右往左绘制
int c = column - chartPointList.size() - 1; //偏差值
for (int i = column; i & gt; = (c & gt; = 0 ? 0 : c); i--) {
finishMinX = i * w;
canvas.drawLine(finishMinX, 0, finishMinX, h, mWavePaint); //画竖线
}

for (int i = 0; i & lt; = row; i++) {
float v = i * h / row + mPointRadius;
canvas.drawLine(finishMinX, v - mPointRadius, mWidth, v - mPointRadius, mWavePaint);
}

float textSpace = mXLableSize / 3 * 4 + mXlablePadding;
float baseY = mHeight - textSpace;

//绘制X轴
canvas.drawLine(finishMinX, mHeight - textSpace + mPointRadius, column * w, mHeight - textSpace + mPointRadius, mPaint);

float preX = 0;
float preY = 0;
//绘制X轴文本
for (int i = column - 1; i & gt; = c + 1; i--) {
ChartPoint chartPoint = chartPointList.get(i - column + chartPointList.size());

if (i & gt; c) canvas.drawText(chartPoint.lableX, i * w - mXLablePaint.measureText(chartPoint.lableX) / 2, mHeight - mXLableSize / 3 * 2 + mPointRadius, mXLablePaint);
else canvas.drawText(chartPoint.lableX, i * w, mHeight - mXLableSize / 3 * 2 + mPointRadius, mXLablePaint);

float x = i * w;
float y = mHeight - (chartPoint.valueY / maxY * baseY + textSpace);
//绘制折线,如果不大于1则不绘制
if (i & lt; column - 1) {
canvas.drawLine(preX, preY + mPointRadius, x, y + mPointRadius, mLinePaint);
}
preX = x;
preY = y;
}

//绘制圆点
for (int i = column - 1; i & gt; = c + 1; i--) {
ChartPoint chartPoint = chartPointList.get(i - column + chartPointList.size());

float x = i * w;
float y = mHeight - (chartPoint.valueY / maxY * baseY + textSpace);

mPointPaint.setColor(chartPoint.pointColor == 0 ? mPointColor : chartPoint.pointColor);
canvas.drawCircle(x, y + mPointRadius, mPointRadius, mPointPaint);
mPointPaint.setColor(mPointColor);
}

//绘制圆点标签
for (int i = column - 1; i & gt; = c + 1; i--) {
ChartPoint chartPoint = chartPointList.get(i - column + chartPointList.size());
mHintPaint.setColor(chartPoint.pointColor == 0 ? mHintColor : chartPoint.pointColor);

float x = i * w;
float y = mHeight - (chartPoint.valueY / maxY * baseY + textSpace);
if (y & lt; mHintSize) canvas.drawText(chartPoint.lableY, x + mPointRadius, y + mHintSize / 3 * 2 + mPointRadius, mHintPaint);
else canvas.drawText(chartPoint.lableY, x + mPointRadius + chartPoint.hintXoffset, y + mHintSize / 3 + chartPoint.hintYoffset + mPointRadius, mHintPaint);
}
}

/**
* 绘制背景
* @param canvas
*/
private void drawBackground(Canvas canvas) {

float h = (mHeight - mXLableSize / 3 * 4 - mXlablePadding + mPointRadius) / row;
for (int i = row; i & gt; 0; i--) {
if (i % 2 == 1) {
canvas.drawRect(finishMinX, (i - 1) * h, mWidth, i * h, mBackgroundPaint);
}
}

}

private void onlyInitWave(Canvas canvas) {
//绘制背景
drawBackground(canvas);

float textSpace = mXLableSize / 3 * 4 + mXlablePadding; //文本占用的空间
//先绘制纵线,即X轴固定,Y轴变化
float w = mWidth / column; //计算出x轴每个单元格的距离
for (int i = 0; i & lt; = column; i++) { //可以<column,也可以<=column,这个无所谓。
//绘制线条
canvas.drawLine(i * w, 0 + mPointRadius, i * w, mHeight - textSpace + mPointRadius, mWavePaint);
}
//绘制纵线
float h = (mHeight - textSpace) / row;
for (int i = 0; i & lt; = row; i++) {
canvas.drawLine(0, i * h + mPointRadius, mWidth, i * h + mPointRadius, mWavePaint);
}

//绘制x轴
canvas.drawLine(0, h * row + mPointRadius, mWidth, h * row + mPointRadius, mPaint);
}

float nowX = 0;
float downX = 0; //点击下去的位置
float nowPosition = 0;

@
Override
public boolean dispatchTouchEvent(MotionEvent event) {
if (!hasMore) return false;
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mScroller.abortAnimation();
downX = event.getRawX();
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
nowX = event.getRawX() - downX;
nowPosition -= nowX;
if (finishMinX & gt; nowPosition) {
nowPosition = finishMinX;
scrollTo((int) finishMinX, 0);
if (mStatusListener != null) {
mStatusListener.status(LEFT);
}
getParent().requestDisallowInterceptTouchEvent(true);
} else if (0 & lt; nowPosition) {
nowPosition = 0;
scrollTo(0, 0);
if (mStatusListener != null) {
mStatusListener.status(RIGHT);
}
getParent().requestDisallowInterceptTouchEvent(true);
} else {
scrollBy((int) - nowX, 0);
if (mStatusListener != null) {
mStatusListener.status(CENTER);
}
getParent().requestDisallowInterceptTouchEvent(true);
}
downX = event.getRawX();
//return true;
break;
case MotionEvent.ACTION_UP:
mVelocityTracker.computeCurrentVelocity(1000);
float xVelocity = mVelocityTracker.getXVelocity();
//if (downX > event.getRawX()) {
nowX = event.getRawX() - downX;
nowPosition -= nowX;
mScroller.fling((int) nowPosition, 0, -(int) xVelocity, 0, (int) finishMinX, 0, 0, 0);
/*} else {
mScroller.fling((int) nowPosition, 0, (int) xVelocity, 0, (int) finishMinX, 0, 0, 0);
}*/
break;
}
return super.dispatchTouchEvent(event);
}

@
Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), 0);
//invalidate();
}
nowPosition = mScroller.getCurrX();
super.computeScroll();
}

public void add(ChartPoint chartPoint) {
if (chartPoint == null) return;
chartPointList.add(0, chartPoint);
invalidate();
}

public void addAll(List & lt; ChartPoint & gt; chartPointList) {
if (chartPointList == null) return;
this.chartPointList.addAll(0, chartPointList);
invalidate();
}

public void clear() {
chartPointList.clear();
scrollTo(0, 0);
invalidate();

}

public final static int LEFT = -1;
public final static int CENTER = 0;
public final static int RIGHT = 1;

@
Override
public boolean onDown(MotionEvent e) {
return false;
}

@
Override
public void onShowPress(MotionEvent e) {

}

@
Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}

@
Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
return false;
}

@
Override
public void onLongPress(MotionEvent e) {

}

@
Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
return false;
}

public interface StatusListener {
public void status(int status);
}
public StatusListener mStatusListener;
public void setStatusListener(StatusListener l) {
mStatusListener = l;
}
}