为了实现一个如下图红色方框所示的控件,系统自带控件并不能满足要求,因此需要继承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(); } }