【Android View】寫一個蛛網評分控制元件

panpf發表於2019-04-15

上週專案中要用到一個蛛網評分控制元件,於是就先上Github搜,搜了半天沒搜著(也可能是我搜的關鍵詞不對),那隻好自己寫一個了,就叫SpiderWebScoreView

先放一張最終效果圖:

sample.png

先整理一下需求:

  • 要能支援任意個角以及任意層級
  • 不管是多少個角都要能自動左右對稱
  • 蛛網圖形外圈的文字要能自定義(甚至是圖片)

然後整理一下一個蛛網評分控制元件的實現流程:

  • 首先要根據角的個數以及層數畫一個蜘蛛網
  • 然後根據最大分值和所有分值畫一個分數圖形
  • 最後在蜘蛛網的外圈把文案畫出來

我們第一步來畫蜘蛛網

先整理一下畫蜘蛛網需要的知識點:

  • 一層一層的多邊形要用Path畫
  • 從中心出發到每個角的線用drawLine
  • 根據角度計算圓上的座標

接下來看一下怎麼根據角度計算圓上的座標,直接上程式碼

float centerX = 100f;  // 已知圓心X座標為100
float centerY = 100f;  // 已知圓心Y座標為100
float radius = 100f;  // 已知半徑為100
float angle = 45f;  // 已知角度為45°

double radians = Math.toRadians(angle);  // 計算出弧度
float x = (float) (centerX + Math.sin(radians) * radius);  // 計算出X座標
float y = (float) (centerY - Math.cos(radians) * radius);  // 計算出Y座標
複製程式碼

既然上述技術問題都解決了,就開始畫吧

private int angleCount = 5; // 整個蛛網有幾個角
private int hierarchyCount = 5;  // 整個蛛網分多少層(例如最大分數是10分,分5層,那麼每層就代表2分)
private int lineColor = 0xFF000000; // 蛛網線條的顏色
private float lineWidth = -1; // 蛛網線條的寬度

private float centerX;    // 中心點X座標
private float centerY;    // 中心點Y座標
private float radius; // 整個蛛網圖的半徑
private Paint linePaint;
private Path path;

....

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    reset();
}

private void reset(){
    if(angleCount != 0 && hierarchyCount != 0){
        int viewWidth = getWidth();
        int viewHeight = getHeight();
        centerX = viewWidth / 2;
        centerY = viewHeight / 2;
        radius = Math.min(viewWidth, viewHeight) / 2;
    }
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    drawAllHierarchy(canvas);
    drawAllLine(canvas);
}

/**
 * 繪製所有的層
 * @param canvas Canvas
 */
private void drawAllHierarchy(Canvas canvas) {
    float averageRadius = radius / hierarchyCount;
    for(int w = 0; w < hierarchyCount; w++){
        drawHierarchyByRadius(canvas, averageRadius *(w+1));
    }
}

/**
 * 根據半徑繪製一層
 * @param canvas Canvas
 * @param currentRadius 當前半徑
 */
private void drawHierarchyByRadius(Canvas canvas, float currentRadius) {
    path.reset();

    float nextAngle;
    float nextRadians;
    float nextPointX;
    float nextPointY;
    float averageAngle = 360 / angleCount;
    float offsetAngle = averageAngle > 0 && angleCount % 2 == 0 ? averageAngle / 2 : 0;
    for (int position = 0; position < angleCount; position++) {
        nextAngle = offsetAngle + (position * averageAngle);
        nextRadians = (float) Math.toRadians(nextAngle);
        nextPointX = (float) (centerX + Math.sin(nextRadians) * currentRadius);
        nextPointY = (float) (centerY - Math.cos(nextRadians) * currentRadius);

        if(position == 0){
            path.moveTo(nextPointX, nextPointY);
        }else{
            path.lineTo(nextPointX, nextPointY);
        }
    }

    path.close();
    canvas.drawPath(path, linePaint);
}

/**
 * 繪製所有的線
 * @param canvas Canvas
 */
private void drawAllLine(Canvas canvas){
    float nextAngle;
    float nextRadians;
    float nextPointX;
    float nextPointY;
    float averageAngle = 360 / angleCount;
    float offsetAngle = averageAngle > 0 && angleCount % 2 == 0 ? averageAngle / 2 : 0;
    for(int position = 0; position < angleCount; position++){
        nextAngle = offsetAngle + (position * averageAngle);
        nextRadians = (float) Math.toRadians(nextAngle);
        nextPointX = (float) (centerX + Math.sin(nextRadians) * radius);
        nextPointY = (float) (centerY - Math.cos(nextRadians) * radius);

        canvas.drawLine(centerX, centerY, nextPointX, nextPointY, linePaint);
    }
}
複製程式碼

詳解:

  • onSizeChanged的時候會根據view的寬高計算圓心座標和半徑
  • 先根據層數計算每層的半徑
  • 接下來再根據角個數計算出每個角的度數
  • 有了角度和半徑後就可以根據上面的演算法算出每個點的座標了
  • 座標都算出來了就直接用Path連線,然後繪製即可

這裡面需要額外注意的就是offsetAngle的計算,offsetAngle用來偏移整個圖形,讓整個圖形左右對稱,演算法就是對於角個數是奇數的不用偏移,是偶數的偏移平均角度除以2即可

第二步來畫分數圖形

和畫蜘蛛網所用到的知識點一樣,只不過半徑要根據分值和最大分值來算,如下:

private float maxScore = 10f;   // 最大分數
private float[] scores;  // 分數列表
private int scoreColor = 0x80F65801; // 分數圖形的顏色
private int scoreStrokeColor = 0xFFF65801; // 分數圖形描邊的顏色
private float scoreStrokeWidth = -1; // 分數圖形描邊的寬度
private boolean disableScoreStroke; // 禁用分數圖形的描邊
private Paint scorePaint;
private Paint scoreStrokePaint;

...

/**
 * 繪製分數圖形
 * @param canvas Canvas
 */
private void drawScore(Canvas canvas){
    if(scores == null || scores.length <= 0){
        return;
    }

    path.reset();

    float nextAngle;
    float nextRadians;
    float nextPointX;
    float nextPointY;
    float currentRadius;
    float averageAngle = 360 / angleCount;
    float offsetAngle = averageAngle > 0 && angleCount % 2 == 0 ? averageAngle / 2 : 0;
    for (int position = 0; position < angleCount; position++) {
        currentRadius = (scores[position] / maxScore) * radius;
        nextAngle = offsetAngle + (position * averageAngle);
        nextRadians = (float) Math.toRadians(nextAngle);
        nextPointX = (float) (centerX + Math.sin(nextRadians) * currentRadius);
        nextPointY = (float) (centerY - Math.cos(nextRadians) * currentRadius);

        if(position == 0){
            path.moveTo(nextPointX, nextPointY);
        }else{
            path.lineTo(nextPointX, nextPointY);
        }
    }

    path.close();
    canvas.drawPath(path, scorePaint);

    // 繪製描邊
    if(!disableScoreStroke){
        if(scoreStrokePaint == null){
            scoreStrokePaint = new Paint();
            scoreStrokePaint.setColor(scoreStrokeColor);
            scoreStrokePaint.setStyle(Paint.Style.STROKE);
            scoreStrokePaint.setAntiAlias(true);
            if(scoreStrokeWidth > 0){
                scoreStrokePaint.setStrokeWidth(scoreStrokeWidth);
            }
        }
        canvas.drawPath(path, scoreStrokePaint);
    }
}
複製程式碼

第三步畫外面那圈文字

到了這裡有些猶難辦了,考慮到外面這圈文字變數比較大,不同的APP會有不同的需求,比如有的需求是一行純文字,有的是兩行純文字但兩行的樣式不一樣,更甚者可能是是文字加圖片。

要是直接往Canvas上寫文字是絕對滿足不了這樣的需求的,既然我要做一個開源的控制元件那就必須要解決這樣的問題,要不然別人是用不了的

想到這裡腦海裡立馬就有解決方案了,那就是自定義一個圓形的Layout,就叫CircularLayout,把CircularLayout放在SpiderWebScoreView上面,用的時候直接往CircularLayout裡面新增View即可,這樣想要什麼樣式就加什麼樣的View即可

具體的自定義CircularLayout的細節就不再贅述了,用到的知識點跟SpiderWebScoreView一樣

最後放上蛛網評分控制元件的Github地址 github.com/xiaopansky/… ,歡迎大家訪問使用

相關文章