Android開發自定義控制元件實現一個足球積分榜RankBar

王世暉發表於2016-05-11

為了實現一個如下圖紅色方框所示的控制元件,系統自帶控制元件並不能滿足要求,因此需要繼承View重新畫一個這樣的控制元件

分析此控制元件發現分為3部分,中間的一列橫線和左右兩個標籤

中間的部分好繪製,通過迴圈呼叫canvas的drawLine方法即可

然後分析左右兩邊的兩個標籤,因為左右兩個是一樣的,因此只分析左邊的

外圍形狀是一個圓環被拉出來了一個三角形,這個三角形是等邊三角形,等邊三角形的上邊頂點設為tt,下邊頂點設為bb,右邊頂點設為rr

tt和bb與圓心相連構成一個角度,這個角度是30度

根據上邊已知條件,使用三角函式運算可以計算出等邊三角形的邊長

圓弧加上等邊三角形的兩條邊構成了一個閉合路徑,因此使用canvas的drawPath繪製路徑

路徑的起點設為等邊三角形的右頂點rr,根據三角函式關係可以計算出等邊三角形下頂點bb的座標

從bb開始計算圓弧路徑,起始角度為15度,掃過角度為360度減去30度

之後把路徑閉合即可,呼叫path的close()方法

圓弧位置的確定需要一個外接正方形,已知等邊三角形的三個頂點可以很方便求出圓的最右邊點的座標

以圓的最右邊座標計算外接正方形的左上頂點座標和右下頂點座標

最後繪製文字即可



測試效果如下:


測試程式碼:

final Random random=new Random();
final RankBar  bar=(RankBar)findViewById(R.id.rank_bar);
bar.setRanks(19,29);
bar.setOnClickListener(new View.OnClickListener(){
    @Override
    public void onClick(View v){
        bar.setRanks(random.nextInt(29)+1,random.nextInt(29)+1);
    }
});
完整程式碼如下:

public class RankBar extends View {
    private Context context;
    /*左右bar的顏色*/
    private int mColorLeft, mColorRight;
    /*左右bar的數值*/
    private int mRankLeft, mRankRight;
    private TypedValue typedValue;
    /*畫圖形的畫筆*/
    private Paint paintDraw = new Paint();
    /*文字畫筆*/
    private Paint paintText = new Paint();
    private Path path = new Path();
    /*等分*/
    private final  int HEIGHT_DEGREE = 40, WIDTH_DEGREES = 31;
    public RankBar(Context context) {
        super(context);
        this.context=context;
        init();
    }

    public RankBar(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context=context;
        init();
    }

    public RankBar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.context=context;
        init();
    }


    private void init() {
        /*資料初始化,沒有設定資料時候的預設資料*/
        mColorLeft = Color.rgb(95, 112, 72);
        mColorRight = Color.rgb(69, 91, 136);
        mRankLeft = 1;
        mRankRight = 1;
        typedValue=new TypedValue();
        context.getTheme().resolveAttribute(R.attr.title_bar,typedValue,true);

    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        float totalWidth = getWidth();
        float totalHeight = getHeight();
        
        paintDraw.reset();
        paintDraw.setAntiAlias(true);
        paintText.reset();
        paintText.setAntiAlias(true);

        /*把總寬度平均分為WIDTH_DEGREES份,中間短橫線的長度佔一份*/
        float lineLength = totalWidth / WIDTH_DEGREES;
        /*中間一列一共有30個短橫線刻度,記錄每個短橫線刻度的垂直方向的座標位置,方便繪製*/
        float[] lineYs = new float[30];
        for (int i = HEIGHT_DEGREE / 2 - 14; i <= HEIGHT_DEGREE / 2 + 15; i++) {
            lineYs[i - (HEIGHT_DEGREE / 2 - 14)] = totalHeight * i / HEIGHT_DEGREE;
        }

        /*設定畫中間水平線的畫筆*/
        paintDraw.setColor(getResources().getColor(typedValue.resourceId));
        paintDraw.setAlpha(50);
        /*設定畫筆的寬度*/
        paintDraw.setStrokeWidth(totalHeight / HEIGHT_DEGREE /3);
        for (int i = 0; i < lineYs.length; i++) {
            /*迴圈繪製中間的短橫線刻度*/
            canvas.drawLine(totalWidth / 2 - lineLength, lineYs[i], totalWidth / 2 + lineLength, lineYs[i], paintDraw);
        }

        /*氣泡的半徑*/
        float radius =  totalHeight *2.5f/ HEIGHT_DEGREE;
        
        /*繪製左邊標籤*/
        path.reset();
        /*moveTo:Set the beginning of the next contour to the point (x,y).*/
        /*計算等邊三角形右邊頂點的座標rr*/
        float triangleRightVertexX=totalWidth / 2 - lineLength / 2;
        float triangleRightVertexY=lineYs[mRankLeft - 1];
        /*路徑起點移動到等邊三角形右邊頂點rr,以此頂點rr畫線*/
        path.moveTo(triangleRightVertexX, triangleRightVertexY);
        /*計算等邊三角形的邊長,等邊三角形上下兩個頂點和圓相交於tt,bb,tt、bb與圓心夾角30度*/
        float triangleLength= radius*(float)Math.sin(15*2*Math.PI/360)*2;
        /*計算bb座標,為繪製弧形的起點*/
        float arcStartPointX=triangleRightVertexX-triangleLength*(float)Math.cos(30*2*Math.PI/360);
        float arcStartPointY=triangleRightVertexY+triangleLength*(float)Math.sin(30*2*Math.PI/360);
        /*畫等邊三角形的一條邊,rr-bb*/
        path.lineTo(arcStartPointX,arcStartPointY);

        /*arcTo 用於繪製弧線(實際是擷取圓或橢圓的一部分)。
        mPath.arcTo(ovalRectF, startAngle, sweepAngle) , ovalRectF為橢圓的矩形,
        startAngle 為開始角度,sweepAngle 為結束角度。*/
        /*利用三角函式計算圓最右邊點的座標,利用該座標值和半徑構建圓的外接正方形然後畫圓*/
        float circleRightPointX=arcStartPointX+(radius-radius*(float)Math.cos(15*2*Math.PI/360));
        float circleRightPointY=triangleRightVertexY;
        /*圓弧路徑,起始角度0度在3點鐘方向,因此弧形起始角度15度,掃過角度360-30度*/
        path.arcTo(new RectF(  circleRightPointX-2*radius,
                                circleRightPointY-radius,
                                circleRightPointX,
                                circleRightPointY+radius),
                    15, 360 - 30);
        /*閉合路徑*/
        path.close();
        paintDraw.setStyle(Paint.Style.FILL);
        paintDraw.setColor(mColorLeft);
        /*將閉合路徑繪製在畫布上*/
        canvas.drawPath(path, paintDraw);
        /*繪製外邊白色邊框*/
        paintDraw.setColor(Color.WHITE);
        paintDraw.setAlpha(90);
        paintDraw.setStyle(Paint.Style.STROKE);
        paintDraw.setStrokeWidth(3);
        canvas.drawPath(path, paintDraw);
        /*準備繪製文字*/
        paintText.setColor(Color.WHITE);
        paintText.setTextSize(radius * 3 / 5);
        /*根據資料位數來確定偏移量*/
        float number_offset;
        /*只有一位數字*/
        if (mRankLeft <10) {
            number_offset=radius/2;
            /*兩位數字*/
        } else {
            number_offset=radius*3 / 4;
        }
        /*圓心x座標*/
        float circleX=arcStartPointX-radius;
        /*繪製#號*/
        canvas.drawText("#", circleX - number_offset, lineYs[mRankLeft - 1] + radius / 4, paintText);
        float offset = paintText.measureText("#");
        /*繪製數字*/
        paintText.setTextSize(radius);
        canvas.drawText("" + mRankLeft, circleX - number_offset + offset, lineYs[mRankLeft - 1] + radius / 3, paintText);

        /*繪製右邊部分*/
        path.reset();
        /*三角形左邊頂點座標*/
        float triangleLeftVertexX=totalWidth / 2 + lineLength / 2;
        float triangleLeftVertexY=lineYs[mRankRight - 1];
        /*等邊三角形左邊頂點作為路徑起始點*/
        path.moveTo(triangleLeftVertexX, triangleLeftVertexY);
        /*等邊三角形上邊頂點和圓的焦點作為圓弧路徑的起點*/
        arcStartPointX=triangleLeftVertexX+triangleLength*(float)Math.cos(30*2*Math.PI/360);
        arcStartPointY=triangleLeftVertexY-triangleLength*(float)Math.sin(30*2*Math.PI/360);
        path.lineTo(arcStartPointX, arcStartPointY);
        /*利用三角函式計算圓最左邊點的座標,利用該座標值和半徑構建圓的外接正方形然後做圓弧路徑*/
        float circleLeftPointX=arcStartPointX-(radius-radius*(float)Math.cos(15*2*Math.PI/360));
        float circleLeftPointY=triangleLeftVertexY;
        /*圓弧路徑*/
        path.arcTo(new RectF(  circleLeftPointX,
                                circleLeftPointY - radius,
                                circleLeftPointX+2*radius,
                                circleLeftPointY+radius),
                180+15, 360 - 30);
        /*閉合路徑*/
        path.close();
        /*繪製路徑*/
        paintDraw.setStyle(Paint.Style.FILL);
        paintDraw.setColor(mColorRight);
        canvas.drawPath(path, paintDraw);
        /*繪製路徑外圍白色邊框*/
        paintDraw.setColor(Color.WHITE);
        paintDraw.setAlpha(90);
        paintDraw.setStyle(Paint.Style.STROKE);
        paintDraw.setStrokeWidth(3);
        canvas.drawPath(path, paintDraw);
        /*開始繪製文字*/
        paintText.setColor(Color.WHITE);
        paintText.setTextSize(radius * 3 / 5);
        if (mRankRight <10) {
            number_offset=radius/2;
        } else {
            number_offset=radius*3 / 4;
        }
        circleX=circleLeftPointX+radius;
        canvas.drawText("#", circleX  - number_offset, lineYs[mRankRight - 1] + radius / 4, paintText);
        offset = paintText.measureText("#");
        paintText.setTextSize(radius);
        canvas.drawText("" + mRankRight, circleX  - number_offset + offset, lineYs[mRankRight - 1] + radius / 4, paintText);
    }
    
    public void setRanks(int rank1,int rank2) {
        if (rank1<1||rank1>30||rank2<1||rank2>30)
            throw new IllegalArgumentException("排名引數只能設定成1到30的整數");
        this.mRankLeft =rank1;
        this.mRankRight =rank2;
        invalidate();
    }
}



相關文章