Android 酷炫的3d立體圓柱動畫效果實現

最近在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;
        }
    }

至此就介紹得差不多了,具體的代碼裏面都有註釋,希望喜歡的小夥伴們點個贊,有問題可以留言。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章