Android开发自定义控件实现一个足球积分榜RankBar

为了实现一个如下图红色方框所示的控件,系统自带控件并不能满足要求,因此需要继承View重新画一个这样的控件

分析此控件发现分为3部分,中间的一列横线和左右两个标签

中间的部分好绘制,通过循环调用canvas的drawLine方法即可

然后分析左右两边的两个标签,因为左右两个是一样的,因此只分析左边的

外围形状是一个圆环被拉出来了一个三角形,这个三角形是等边三角形,等边三角形的上边顶点设为tt,下边顶点设为bb,右边顶点设为rr

tt和bb与圆心相连构成一个角度,这个角度是30度

根据上边已知条件,使用三角函数运算可以计算出等边三角形的边长

圆弧加上等边三角形的两条边构成了一个闭合路径,因此使用canvas的drawPath绘制路径

路径的起点设为等边三角形的右顶点rr,根据三角函数关系可以计算出等边三角形下顶点bb的坐标

从bb开始计算圆弧路径,起始角度为15度,扫过角度为360度减去30度

之后把路径闭合即可,调用path的close()方法

圆弧位置的确定需要一个外接正方形,已知等边三角形的三个顶点可以很方便求出圆的最右边点的坐标

以圆的最右边坐标计算外接正方形的左上顶点坐标和右下顶点坐标

最后绘制文字即可


Android开发自定义控件实现一个足球积分榜RankBar_第1张图片

测试效果如下:

Android开发自定义控件实现一个足球积分榜RankBar_第2张图片

测试代码:

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();
    }
}



你可能感兴趣的:(Android开发自定义控件实现一个足球积分榜RankBar)