最近在drrible上看到一個超酷炫的效果,立體圓柱緩慢上升:https://dribbble.com/shots/7077455-Spending-analytics
然後準備實現一波,做之前在網上找了很久,並沒有相似的效果,所以自己做了一個,已經上傳到我的代碼庫裏:
https://github.com/jiangzhengnan/NguiLib
歡迎小夥伴們的start或者requests
下面簡單說一下實現過程:
1.首先要講傳入的數據數組進行排序,因爲是2d平面模擬3d,所以弧形的繪製要由內到外才不會出現錯位的情況:
//計算出繪製順序
private void computationOrder() {
ArrayList<Entry> tempOrderList = new ArrayList<>();
leftAngle = 0;
rightAngle = 0;
for (int i = 0; i < max; i++) {
Collections.sort(mEntries, new MyCompare());
Entry tempEntry = mEntries.get(0);
float halfAngle = tempEntry.percent / 2;
/*
1.算法,先繪製最大的一塊,居中
2.剩下的 優先記錄左邊和右邊的座標值
3.優先添加不會溢出的
*/
if (i == 0) {
leftAngle = 270f - halfAngle;
rightAngle = 270f + halfAngle;
if (rightAngle >= 360) {
rightAngle -= 360f;
}
tempEntry.tag = CENTER;
tempEntry.startAngle = leftAngle;
mEntries.remove(0);
} else {
//1.找到當前小的邊
if (getDistanceToCenter(leftAngle, LEFT) > getDistanceToCenter(rightAngle, RIGHT)) {
//左邊比右邊大,取右邊
tempEntry = getNextAngle(getDistanceToCenter(rightAngle, RIGHT));
tempEntry.tag = RIGHT;
rightAngle += tempEntry.percent;
if (rightAngle >= 360) {
rightAngle -= 360f;
}
tempEntry.startAngle = rightAngle;
} else {
//右邊比左邊大,取左邊
tempEntry = getNextAngle(getDistanceToCenter(leftAngle, LEFT));
tempEntry.tag = LEFT;
leftAngle -= tempEntry.percent;
tempEntry.startAngle = leftAngle;
}
}
tempOrderList.add(tempEntry);
}
mEntrySourceList = tempOrderList;
}
2.然後是繪製部分,首先根據排列的順序進行繪製,依次繪製各個弧形段,然後畫輪廓線:
private void drawCylinder(Canvas canvas, Entry tempEntry, int thickness,boolean ifChangeThick) {
mainPaint.setStyle(Paint.Style.FILL);
//繪製各個弧度
int perThickness =ifChangeThick? (int) ((tempEntry.percent / 360f) * thickness * (max * 0.5f)) : thickness;
float drawTempStartAngle = 0f;
RectF tempRectF;
float lineStartX = 0f;
float lineStartY = 0f;
float lineEndX = 0f;
float lineEndY = 0f;
float oX = centerX;
float oY = (area2DHeight + area3DHight) / 2;
float R = centerX;
//y軸2d3d縮放比例
float bilv = ((float) (area3DHight - area2DHeight)) / ((float) area2DHeight);
for (int j = 0; j <= perThickness; j++) {
tempRectF = new RectF(0, area2DHeight - j, area2DWidth, area3DHight - j);
switch (tempEntry.tag) {
case CENTER:
drawTempStartAngle = tempEntry.startAngle;
lineStartX = oX;
lineEndX = oX;
lineStartY = (area2DHeight + area3DHight) / 2;
lineEndY = oY - j;
break;
case LEFT:
drawTempStartAngle = tempEntry.startAngle;
/*
左邊夾角tempAngle 0< tempAngle < 180
90 <drawTempStartAngle <270
*/
if (drawTempStartAngle <= 180) {
//startAngle 90-180
lineStartX = oX - (float) (R * Math.sin(Math.toRadians(drawTempStartAngle - 90f)));
lineEndX = lineStartX;
lineStartY = oY + (float) (bilv * R * Math.cos(Math.toRadians(drawTempStartAngle - 90f)));
lineEndY = lineStartY - j;
} else {
//startAngle 180-270
lineStartX = oX - (float) (R * Math.cos(Math.toRadians(drawTempStartAngle - 180f)));
lineEndX = lineStartX;
lineStartY = oY - (float) (bilv * R * Math.sin(Math.toRadians(drawTempStartAngle - 180f)));
lineEndY = lineStartY - j;
}
break;
case RIGHT:
drawTempStartAngle = tempEntry.startAngle - tempEntry.percent;
/*
右邊夾角tempAngle 0< tempAngle < 180
90 <drawTempStartAngle <270
*/
if (drawTempStartAngle <= 360) {
//startAngle 270-360
lineStartX = oX + (float) (R * Math.cos(Math.toRadians(360f - drawTempStartAngle - tempEntry.percent)));
lineEndX = lineStartX;
lineStartY = oY - (float) (bilv * R * Math.sin(Math.toRadians(360f - drawTempStartAngle - tempEntry.percent)));
lineEndY = lineStartY - j;
} else {
//startAngle 0-90
lineStartX = oX + (float) (R * Math.cos(Math.toRadians(drawTempStartAngle)));
lineEndX = lineStartX;
lineStartY = oY - (float) (bilv * R * Math.sin(Math.toRadians(drawTempStartAngle)));
lineEndY = lineStartY - j;
}
break;
}
//弧形
mainPaint.setColor(tempEntry.color);
canvas.drawArc(tempRectF, drawTempStartAngle, tempEntry.percent, true, mainPaint);
if (j == perThickness) {
drawTopLine(canvas, tempRectF, drawTempStartAngle, tempEntry.percent);
}
//豎直線
mainPaint.setColor(Color.WHITE);
if (tempEntry.tag == CENTER) {
canvas.drawLine(lineStartX, lineStartY,
lineEndX, lineEndY,
mainPaint);
//還需要畫左右兩邊的豎直線
float xOffset = (float) (R * Math.sin(Math.toRadians(tempEntry.percent / 2)));
float yOffset = (float) (bilv * R * Math.cos(Math.toRadians(tempEntry.percent / 2)));
canvas.drawLine(
oX - xOffset,
oY - yOffset,
oX - xOffset,
oY - yOffset - j,
mainPaint);
canvas.drawLine(
oX + xOffset,
oY - yOffset,
oX + xOffset,
oY - yOffset - j,
mainPaint);
} else {
canvas.drawLine(lineStartX, lineStartY,
lineEndX, lineEndY,
mainPaint);
}
}
}
3.兩種不同的動畫效果在ondraw裏面分別進行判斷:
@SuppressLint("DrawAllocation")
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
switch (ANIM_STATE) {
case ANIM_STATE_ALL:
for (int i = 0; i < mEntrySourceList.size(); i++) {
Entry tempEntry = mEntrySourceList.get(i);
drawCylinder(canvas, tempEntry, thickness,true);
}
break;
case ANIM_STATE_SINGLE:
for (int i = 0; i < mEntrySourceList.size(); i++) {
Entry tempEntry = mEntrySourceList.get(i);
drawCylinder(canvas, tempEntry, 1,false);
}
for (int i = 0; i < singleAnimIndex; i++) {
if (i < mEntrySourceList.size()) {
Entry tempEntry = mEntrySourceList.get(i);
int tempThickNess = singleAnimValue - (i + 1) * 100;
LogUtils.INSTANCE.d("tempThickNess: " + tempThickNess);
tempThickNess = (int) ((tempEntry.percent / 360f) * tempThickNess * (max * 0.33f));
drawCylinder(canvas, tempEntry, tempThickNess,false);
}
}
break;
case ANIM_STATE_CHANGGE:
break;
}
}
至此就介紹得差不多了,具體的代碼裏面都有註釋,希望喜歡的小夥伴們點個贊,有問題可以留言。