當(dāng)項目要做圖標(biāo)功能時痛黎,大家基本就是去引用開源庫來使用了,其中最著名的圖標(biāo)庫是MPAndroidChart了贸桶。但是舅逸,如果能夠自己手動擼出各種圖標(biāo),那是不是很酷呢皇筛?常用圖表類型包括柱狀圖琉历,折線圖,餅狀圖水醋,雷達(dá)圖旗笔,股票圖,這里是做的前三者圖表拄踪。
演示效果:
實現(xiàn)步驟
-
柱狀圖
1.以等差高度畫Y軸文字和橫線
2.畫X軸文字
3.依據(jù)數(shù)值畫不等高度的矩形柱
4.點擊各個柱子顯示頂部數(shù)值
-
折線圖
1.以等差高度畫Y軸文字和橫線
2.畫X軸文字
3.根據(jù)數(shù)值繪制path
-
餅狀圖
1.根據(jù)數(shù)值繪制不同起始角度和弧度的圓環(huán)
2.依據(jù)弧度并利用三角函數(shù)知識繪制各版塊文字蝇恶,并調(diào)節(jié)位置至板塊中心
3.繪制內(nèi)部同心圓環(huán)
繪制圓環(huán)上文字知識點:
圓心為o(x,y),半徑r惶桐,圓上各個弧度點上的坐標(biāo)撮弧,求值公式為
xA = x+rsinα,yA = y-rcosα姚糊,α大小隨著頂點變化而變化贿衍。
代碼實現(xiàn):
-
柱狀圖View
/**
* create by libo
* create on 2020/7/26
* description 柱狀圖View
*/
public class BarChartView extends View {
/** 文字Paint */
private Paint textPaint;
/** Y軸基準(zhǔn)線Paint */
private Paint linePaint;
/** 柱型paint */
private Paint rectPaint;
/** Y軸每單元數(shù)量高度 */
private float unitHeight;
/** Y軸數(shù)據(jù)數(shù)組 */
private int[] unitHeightNum = new int[] {100, 200, 300, 400, 500};
/** 各個階段數(shù)據(jù)數(shù)組 */
private int[] stageNum = new int[] {125, 230, 323, 253, 398, 410};
private String[] stageStr = new String[] {"Jan", "Feb", "Mar", "Apr", "May", "Jun"};
private int[] colors = new int[] {R.color.green, R.color.blue, R.color.yellow, R.color.red};
/** X軸單元寬度 */
private float unitWidth;
/** 橫線左邊距大小 */
private float lineLeftPadding;
/** 柱狀圖左右間距 */
private int rectPadding = 12;
/** 每個柱子集合 */
private ArrayList<Bar> bars = new ArrayList<>();
/** 當(dāng)前顯示值的位置 */
private int showValuePos = -1;
public BarChartView(Context context) {
super(context);
initPaint();
}
public BarChartView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initPaint();
}
private void initPaint() {
textPaint = new Paint();
textPaint.setColor(getResources().getColor(R.color.textcolor));
textPaint.setTextSize(40);
textPaint.setAntiAlias(true);
linePaint = new Paint();
linePaint.setColor(getResources().getColor(R.color.linecolor));
linePaint.setAntiAlias(true);
linePaint.setStyle(Paint.Style.FILL);
linePaint.setStrokeWidth(2);
rectPaint = new Paint();
rectPaint.setAntiAlias(true);
rectPaint.setStyle(Paint.Style.FILL);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawYText(canvas);
drawXText(canvas);
drawBars(canvas);
}
/**
* 繪制Y軸文字及基準(zhǔn)線
*/
private void drawYText(Canvas canvas) {
int top = getHeight() - 80; //給底部文字留下高度
unitHeight = getHeight()/unitHeightNum.length - 20;
for (int num : unitHeightNum) {
Rect rect = new Rect();
String text = num + "萬";
textPaint.getTextBounds(text, 0, text.length(), rect);
canvas.drawText(text, 0, top, textPaint); //畫文字
lineLeftPadding = rect.width() + 20;
canvas.drawLine(lineLeftPadding, top, getWidth(), top, linePaint); //畫橫線
top -= unitHeight;
}
}
/**
* 繪制X軸文字
*/
private void drawXText(Canvas canvas) {
float left = lineLeftPadding;
unitWidth = getWidth()/stageNum.length - 20;
Bar bar;
for (int i=0;i<stageNum.length;i++) {
canvas.drawText(stageStr[i], left + unitWidth/4, getHeight()-20, textPaint); //畫文字
float top = getHeight() - (float)stageNum[i]/100*unitHeight;
int color = getResources().getColor(colors[i%colors.length]);
bar = new Bar(stageNum[i], left+rectPadding, top, left+unitWidth-rectPadding, getHeight()- 80, color);
bars.add(bar);
left += unitWidth;
}
}
/**
* 繪制柱形
*/
private void drawBars(Canvas canvas) {
//畫矩形,并左右設(shè)置間距
//根據(jù)該項數(shù)值獲取實際的柱形高度
//Y軸每格單元高度為100數(shù)值
for (int i=0;i<bars.size();i++) {
Bar bar = bars.get(i);
rectPaint.setColor(bar.color);
canvas.drawRect(bar.left, bar.top, bars.get(i).right, bar.bootom, rectPaint);
//繪制柱形上數(shù)值
if (showValuePos == i) {
String value = String.valueOf(bar.value);
Rect rect = new Rect();
textPaint.getTextBounds(value, 0, value.length(), rect);
float textLeft = bar.left + (bar.right-bar.left-rect.width())/2; //計算使文字在柱形居中位置
canvas.drawText(value, textLeft, bar.top-20, textPaint); //繪制柱頂部數(shù)值
}
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
for (int i=0;i<bars.size();i++) {
if (event.getX() > bars.get(i).left && event.getX() < bars.get(i).right) {
//按下事件在當(dāng)前柱形內(nèi)
showValuePos = i;
invalidate();
}
}
}
return true;
}
/**
* 柱形類
*/
class Bar {
private int value;
private float left;
private float top;
private float right;
private float bootom;
private int color;
public Bar(int value, float left, float top, float right, float bootom, int color) {
this.value = value;
this.left = left;
this.top = top;
this.right = right;
this.bootom = bootom;
this.color = color;
}
}
}
-
折線圖View
/**
* create by libo
* create on 2020/7/26
* description 折線圖View
*/
public class LineChartView extends View {
/** 文字Paint */
private Paint textPaint;
/** 基準(zhǔn)線Paint */
private Paint linePaint;
/** 折線paint */
private Paint charLinePaint;
/** Y軸每單元數(shù)量高度 */
private float unitHeight;
/** Y軸數(shù)據(jù)數(shù)組 */
private int[] unitHeightNum = new int[] {0, 20, 40, 60, 80, 100};
private String[] stageStr = new String[] {"Jan", "Feb", "Mar", "Apr", "May", "Jun"};
/** 橫線左邊距大小 */
private float lineLeftPadding;
/** X軸單元寬度 */
private float unitWidth;
/** 各個階段數(shù)據(jù)數(shù)組 */
private int[] stageNum = new int[] {56, 40, 82, 74, 60, 92};
private Path linePath;
public LineChartView(Context context) {
super(context);
initPaint();
}
public LineChartView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initPaint();
}
private void initPaint() {
textPaint = new Paint();
textPaint.setColor(getResources().getColor(R.color.textcolor));
textPaint.setTextSize(40);
textPaint.setAntiAlias(true);
linePaint = new Paint();
linePaint.setColor(getResources().getColor(R.color.linecolor));
linePaint.setAntiAlias(true);
linePaint.setStyle(Paint.Style.STROKE);
linePaint.setStrokeWidth(2);
charLinePaint = new Paint();
charLinePaint.setColor(getResources().getColor(R.color.orange));
charLinePaint.setAntiAlias(true);
charLinePaint.setStyle(Paint.Style.FILL);
linePath = new Path();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawYText(canvas);
drawXText(canvas);
drawLinePath(canvas);
}
/**
* 繪制Y軸文字及基準(zhǔn)線
*/
private void drawYText(Canvas canvas) {
int top = getHeight() - 80; //給底部文字留下高度
unitHeight = getHeight()/unitHeightNum.length - 20;
Rect rect = new Rect();
String longText = unitHeightNum[unitHeightNum.length-1]+"萬"; //以最長文字對齊
textPaint.getTextBounds(longText, 0, longText.length(), rect);
for (int num : unitHeightNum) {
canvas.drawText(num + "萬", 0, top, textPaint); //畫文字
lineLeftPadding = rect.width() + 20;
canvas.drawLine(lineLeftPadding, top, getWidth(), top, linePaint); //畫橫線
top -= unitHeight;
}
}
/**
* 繪制X軸文字
*/
private void drawXText(Canvas canvas) {
float left = lineLeftPadding;
unitWidth = getWidth()/stageNum.length - 20;
for (int i=0;i<stageNum.length;i++) {
canvas.drawText(stageStr[i], left + unitWidth/4, getHeight()-20, textPaint); //畫文字
canvas.drawLine(left, getHeight()-80, left, 80, linePaint);
left += unitWidth;
}
}
/**
* 繪制折線
*/
private void drawLinePath(Canvas canvas) {
float left = lineLeftPadding;
for (int i=0;i<stageNum.length;i++) {
float topX = left + unitWidth/2;
float topY = getHeight() - (float)stageNum[i]/20*unitHeight;
if (i == 0) {
linePath.moveTo(topX, topY);
} else {
linePath.lineTo(topX, topY);
}
charLinePaint.setStyle(Paint.Style.FILL);
canvas.drawCircle(topX, topY, 10, charLinePaint); //繪制拐點小圓
left += unitWidth;
}
charLinePaint.setStyle(Paint.Style.STROKE);
charLinePaint.setStrokeWidth(4);
canvas.drawPath(linePath, charLinePaint);
}
}
-
餅狀圖View
/**
* create by libo
* create on 2020/7/26
* description 餅狀圖
*/
public class PieChartView extends View {
private Paint piePaint;
private Paint innerPiePaint;
/** 圓環(huán)寬度 */
private int ringWidth;
private int[] colors = new int[] {R.color.green, R.color.blue, R.color.yellow, R.color.red, R.color.orange};
private int[] values = new int[] {21, 12, 30, 23, 14};
private String[] titles = new String[] {"醫(yī)院", "學(xué)校", "酒店", "商場", "商業(yè)建筑"};
private Paint textPaint;
private Paint centerTextPaint;
private String title = "設(shè)施占比";
public PieChartView(Context context) {
super(context);
init();
}
public PieChartView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
piePaint = new Paint();
piePaint.setAntiAlias(true);
piePaint.setStyle(Paint.Style.STROKE);
piePaint.setAlpha(190);
innerPiePaint = new Paint();
innerPiePaint.setAntiAlias(true);
innerPiePaint.setStyle(Paint.Style.STROKE);
textPaint = new Paint();
textPaint.setAntiAlias(true);
textPaint.setColor(getResources().getColor(R.color.white));
textPaint.setTextSize(45);
centerTextPaint = new Paint();
centerTextPaint.setAntiAlias(true);
centerTextPaint.setColor(getResources().getColor(R.color.colorPrimary));
centerTextPaint.setTextSize(55);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawRing(canvas);
drawText(canvas);
drawCenterText(canvas);
}
/**
* 畫分塊圓環(huán)
*/
private void drawRing(Canvas canvas) {
ringWidth = getMeasuredWidth()/4;
piePaint.setStrokeWidth(ringWidth);
innerPiePaint.setStrokeWidth(60);
RectF rectF = new RectF(ringWidth/2, ringWidth/2, getWidth()-ringWidth/2, getHeight()-ringWidth/2);
RectF innerRectF = new RectF(ringWidth, ringWidth, getWidth()-ringWidth, getHeight()-ringWidth);
int startAngle = -90; //12點鐘方向起始
for (int i=0;i<values.length;i++) {
float sweepAngle = (float) values[i]/100*360;
piePaint.setColor(getResources().getColor(colors[i]));
innerPiePaint.setColor(getResources().getColor(colors[i]));
canvas.drawArc(rectF, startAngle, sweepAngle, false, piePaint);
innerPiePaint.setAlpha(120);
canvas.drawArc(innerRectF, startAngle, sweepAngle, false, innerPiePaint);
startAngle += sweepAngle;
}
}
/**
* 畫每分塊文字
*/
private void drawText(Canvas canvas) {
int startAngle = 0; //12點鐘方向起始
int radius = getWidth()/2-ringWidth/2;
for (int i=0;i<titles.length;i++) {
float sweepAngle = (float) values[i]/100*360;
double angle = Math.toRadians(startAngle + sweepAngle/2);
float x = (float) (getWidth()/2 + radius*Math.sin(angle));
float y = (float) (getHeight()/2 - radius*Math.cos(angle));
//橫坐標(biāo)需要全部左移文字寬度的一半
Rect rect = new Rect();
textPaint.getTextBounds(titles[i], 0, titles[i].length(), rect);
canvas.drawText(titles[i], x-rect.width()/2, y, textPaint);
startAngle += sweepAngle;
}
}
/**
* 中心文字
*/
private void drawCenterText(Canvas canvas) {
Rect rect = new Rect();
centerTextPaint.getTextBounds(title, 0, title.length(), rect);
canvas.drawText(title, getWidth()/2-rect.width()/2, getHeight()/2+rect.height()/2, centerTextPaint);
}
}