爲了增強對自定義 View 的認知以及開發能力,我計劃這段時間陸續來完成幾個難度從易到難的自定義 View,並簡單的寫幾篇博客來進行介紹,全部的代碼也都會開源,也但願讀者能給個 star 哈 GitHub 地址:github.com/leavesC/Cus… 也能夠下載 Apk 來體驗下:www.pgyer.com/CustomViewjava
先看下效果圖:git
假設每一個扇形所表明的數據的數據都是 float 類型的,這些數據須要由外部傳入給 View,View 內部再來根據數據總量來計算各項數據的佔比,各個扇形的角度就是以此來決定github
爲了簡單起見,各個扇形的顏色值由 View 內部來決定,外部只需傳入數據大小便可,將此概念抽象爲 PercentageModel 類canvas
/** * 做者:leavesC * 時間:2019/4/10 14:28 * 描述: */
public class PercentageModel {
private float value;
private float angle;
private int color;
}
複製代碼
public PercentageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initPaint();
}
private void initPaint() {
paint = new Paint();
paint.setStyle(Paint.Style.FILL);
paint.setAntiAlias(true);
paint.setDither(true);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int defaultSize = dp2px(DEFAULT_SIZE);
int width = getSize(widthMeasureSpec, defaultSize);
int height = getSize(heightMeasureSpec, defaultSize);
width = height = Math.min(width, height);
setMeasuredDimension(width, height);
Log.e(TAG, "onMeasure");
}
複製代碼
外部傳入的數據只包含數據量 value 這個參數而已,所以還須要在 View 內部計算數據佔比,併爲數據項按照順序賦予顏色值。爲了不精度損失,還須要在最後判斷佔比總和是否就是 360 度,不是的話則須要將損失值賦予最後一項數據ide
private List<PercentageModel> percentageModelList;
private static final int[] COLORS = {0xff2f7e76, 0xff1ff749, 0xfff42872, 0xff4643f4, 0xe51581da, 0xff8527e4, 0xfff1b00d, 0xff26020f};
public void setData(List<PercentageModel> percentageModelList) {
this.percentageModelList = percentageModelList;
initData(percentageModelList);
invalidate();
}
private void initData(List<PercentageModel> percentageModelList) {
if (percentageModelList == null || percentageModelList.size() == 0) {
return;
}
float sumValue = 0;
for (int i = 0; i < percentageModelList.size(); i++) {
PercentageModel percentageModel = percentageModelList.get(i);
sumValue += percentageModel.getValue();
percentageModel.setColor(COLORS[i % COLORS.length]);
}
float sumAngle = 0;
for (PercentageModel percentageModel : percentageModelList) {
float per = percentageModel.getValue() / sumValue;
percentageModel.setAngle(per * 360);
sumAngle += percentageModel.getAngle();
}
//計算百分比時可能有一些精度損失,此處須要判斷是否須要把差值補回來
if (sumAngle < 360) {
for (PercentageModel percentageModel : percentageModelList) {
if (percentageModel.getAngle() != 0) {
percentageModel.setAngle(360 - sumAngle + percentageModel.getAngle());
break;
}
}
}
}
複製代碼
private RectF rect = new RectF();
@Override
protected void onDraw(Canvas canvas) {
if (percentageModelList == null || percentageModelList.size() == 0) {
return;
}
float currentStartAngle = startAngle;
canvas.translate(getWidth() / 2, getHeight() / 2);
float r = (float) (Math.min(getWidth(), getHeight()) / 2 * 0.95);
rect.left = -r;
rect.top = -r;
rect.right = r;
rect.bottom = r;
for (PercentageModel percentageModel : percentageModelList) {
paint.setColor(percentageModel.getColor());
canvas.drawArc(rect, currentStartAngle, percentageModel.getAngle(), true, paint);
currentStartAngle += percentageModel.getAngle();
}
}
複製代碼
經過全局變量 startAngle 來指定第一個扇形的起始角度,並將其 set 方法開放給外部,並在 set 方法內主動刷新 Viewthis
private float startAngle;
public void setStartAngle(float startAngle) {
this.startAngle = startAngle;
invalidate();
}
複製代碼