一、背景介绍
我们在项目中,经常会见到圆形进度条,看起来很美观、直观。刚好最近项目中有这样的需求,记录一下,顺便回顾下自定义View的知识。
二、实现思路
自定义View,就是在画布中绘制View,需要重写onDraw方法。该View可以拆分成以下几部分:
1)需要画一个浅绿色的圆
2)需要画一个白色的圆
3)圆圈中有进度数字的显示
4)圆圈中可以自定义顶部和底部不同文案的提示
三、主要方法介绍
1、drawArc:由上图可以看出,该圆需要画出圆弧表示进度,所以选择drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)方法。
1)参数:
oval-用于确定圆弧形状与尺寸的椭圆边界(即椭圆外切矩形)
startAngle-开始角度(以时钟3点钟为0°,逆时针为正方向)
sweepAngle-旋转角度(以时钟3点钟为0°,逆时针为正方向)
useCenter-是否包含圆心
paint-画笔
2)绘制原理
- 当RectF(float left,float top,float right,float bottom)中right-left等于bottom-top时(长=宽),这时画出的就是个圆。
- 以矩形中心为圆心,以3点钟方向为0°,逆时针为正方向,从0°旋转startAngle度,和椭圆相交得到一条直线和一个焦点。
- 从这条直线开始,正方向旋转sweepAngle度,得到另一条直线和焦点,这样就可以得到两个焦点间的圆弧。
2、drawText(String text, float x, float y, Paint paint):文本的绘制方法。
参数:
text-文本
x-该文本的左边与屏幕左边的距离
y-该文本baseline在屏幕上的位置
paint-画笔
需要注意的是,参数y不是表示竖直方向上的位置,而是该文本baseline在屏幕上的位置。
根据官方API说明,Paint的TextAlign属性决定了text相对于起始坐标x的相对位置。默认left,文本从x的右边开始绘制,如果是center,则x坐标在文本的中间。
baseline的介绍参考:http://www.xyczero.com/blog/article/20/
四、画圆
1)画一个背景圆
//1-圆弧的位置:整圆,再绘制进度圆弧 mArcCirclePaint.setColor(mCircleBackgroundColor); mArcCirclePaint.setStrokeWidth(mStrokeWidth); //屏幕宽度 int width = getMeasuredWidth(); RectF rectF = new RectF(); rectF.left = (width-mWidth)/2;//左上角X rectF.top = mWidth*0.1f;//左上角Y rectF.right = (width-mWidth)/2+mWidth;//右上角X rectF.bottom = mWidth*0.9f;//右上角Y if ((rectF.right - rectF.left) > (rectF.bottom- rectF.top)){//正方形矩形,保证画出的圆不会变成椭圆 float space = (rectF.right - rectF.left) - (rectF.bottom- rectF.top); rectF.left += space/2; rectF.right -= space/2; } canvas.drawArc(rectF,270,360,false,mArcCirclePaint);//第2个参数:时钟3点处为0度,逆时针为正方向
2)画进度圆
使用同一个Paint,改变其颜色,在画布上绘制一样大小的圆,只是旋转角度值不一样。
mArcCirclePaint.setColor(mProgressColor); //设置边角为圆 mArcCirclePaint.setStrokeCap(Paint.Cap.ROUND); mArcCirclePaint.setStrokeWidth(mInnerStrokeWidth); canvas.drawArc(rectF,270,mAngleValue,false,mArcCirclePaint);
3)绘制文本
不同文本只是位置不一样,计算好位置就可以绘制出文本了。
//2-文本的位置:居中显示 int centerX = width/2; //计算文本宽度 int textWidth = (int) mTextPaint.measureText(mText, 0, mText.length()); //计算baseline:垂直方向居中 Paint.FontMetricsInt fontMetrics = mTextPaint.getFontMetricsInt(); int baseline = (getMeasuredHeight() - fontMetrics.bottom + fontMetrics.top) / 2 - fontMetrics.top; int textX = centerX-textWidth/2; mTextPaint.setColor(mTextColor); mTextPaint.setTextSize(mTextSize); canvas.drawText(mText,textX,baseline,mTextPaint); if (mTopText != null && !mTopText.equals("")) { textWidth = (int) mTextPaint.measureText(mTopText, 0, mTopText.length()); textX = centerX - textWidth / 2; mTextPaint.setTextSize(mTopTextSize); mTextPaint.setColor(mTopTextColor); canvas.drawText(mTopText, textX, baseline - 20, mTextPaint); } if (mBottomText != null && !mBottomText.equals("")) { textWidth = (int) mTextPaint.measureText(mBottomText, 0, mBottomText.length()); textX = centerX - textWidth / 2; // mTextPaint.reset(); // mTextPaint.setAntiAlias(true); // mTextPaint.setLinearText(true); mTextPaint.setTextSize(mTopTextSize); mTextPaint.setColor(mBottomTextColor); canvas.drawText(mBottomText, textX, baseline + 20, mTextPaint); }
五、总结
其实很多的自定义View都是在画布canvas中画出来的,看着复杂(其实难在位置的计算),但是只要将其拆分成几部分,一一画出再组合就好了。
附上源码:工程demo
package com.example.ViewDemo; import android.content.Context; import android.graphics.*; import android.util.AttributeSet; import android.view.View; /** * 自定义View方式三:重新绘制,继承View * 第一步:画出外圆drawArc * 第二步:画出进度圆drawArc * 第三步:画出文本:中间文本,顶部文本,底部文本drawText * Created by cjy on 17/6/14. */ public class ArcCircleView extends View { private Context mContext; /** * 文本画笔 */ private Paint mTextPaint; /** * 圆弧画笔 */ private Paint mArcCirclePaint; /** * 宽度 */ private float mWidth = 100.0f; /** * 文本 */ private String mText ="0%"; /** * 底部文本 */ private String mTopText =""; /** * 底部文本 */ private String mBottomText =""; /** * 弧度 */ private int mAngleValue = 0; /** * 圆的背景色:默认浅绿色 */ private int mCircleBackgroundColor = 0x4c11af9c; /** * 进度的颜色,默认白色 */ private int mProgressColor = 0xffffffff; /** * 顶部文本的颜色,默认白色 */ private int mTopTextColor = 0xffffffff; /** * 底部文本的颜色,默认白色 */ private int mBottomTextColor = 0xffffffff; /** * 文本的颜色,默认白色 */ private int mTextColor = 0xffffffff; /** * 边宽 */ private int mStrokeWidth = 8; /** * 进度圆边宽 */ private int mInnerStrokeWidth = 7; /** * 文本大小 */ private int mTextSize = 12; /** * 顶部文本大小 */ private int mTopTextSize = 10; public ArcCircleView(Context context) { super(context); init(context); } public ArcCircleView(Context context, AttributeSet attrs) { super(context, attrs); init(context); } public ArcCircleView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(context); } private void init(Context context){ mContext = context; mTextPaint = new Paint(); //设置抗锯齿 mTextPaint.setAntiAlias(true); //使文本看起来更清晰 mTextPaint.setLinearText(true); mArcCirclePaint = new Paint(); mArcCirclePaint.setAntiAlias(true); mArcCirclePaint.setStyle(Paint.Style.STROKE); } public void setWidth(float width){ mWidth = width; invalidate(); } public void setText(String text){ mText = text; invalidate(); } public void setTopText(String text){ mTopText = text; invalidate(); } public void setBottomText(String text){ mBottomText = text; invalidate(); } public void setTextColor(int textColor) { this.mTextColor = mContext.getResources().getColor(textColor); invalidate(); } public void setBottomTextColor(int bottomTextColor) { this.mBottomTextColor = mContext.getResources().getColor(bottomTextColor); invalidate(); } public void setTopTextColor(int topTextColor) { this.mTopTextColor = mContext.getResources().getColor(topTextColor); invalidate(); } public void setProgressColor(int progressColor) { this.mProgressColor = mContext.getResources().getColor(progressColor); invalidate(); } public void setCircleBackgroundColor(int circleBackgroundColor) { this.mCircleBackgroundColor = mContext.getResources().getColor(circleBackgroundColor); invalidate(); } public void setStrokeWidth(int strokeWidth){ this.mStrokeWidth = strokeWidth; invalidate(); } public void setInnerStrokeWidth(int innerStrokeWidth){ this.mInnerStrokeWidth = innerStrokeWidth; invalidate(); } public void setTextSize(int textSize){ this.mTextSize = textSize; invalidate(); } public void setTopTextSize(int topTextSize){ this.mTopTextSize = topTextSize; invalidate(); } /** * 设置进度 * @param progress */ public void setProgress(float progress){ int angleValue = (int) ((progress * 1.0)/100 * 360); if (angleValue != 0 && progress <= 100){ mAngleValue = angleValue; mText = String.valueOf(progress)+"%"; } invalidate(); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //1-圆弧的位置:整圆,再绘制进度圆弧 mArcCirclePaint.setColor(mCircleBackgroundColor); mArcCirclePaint.setStrokeWidth(mStrokeWidth); //屏幕宽度 int width = getMeasuredWidth(); RectF rectF = new RectF(); rectF.left = (width-mWidth)/2;//左上角X rectF.top = mWidth*0.1f;//左上角Y rectF.right = (width-mWidth)/2+mWidth;//右上角X rectF.bottom = mWidth*0.9f;//右上角Y if ((rectF.right - rectF.left) > (rectF.bottom- rectF.top)){//正方形矩形,保证画出的圆不会变成椭圆 float space = (rectF.right - rectF.left) - (rectF.bottom- rectF.top); rectF.left += space/2; rectF.right -= space/2; } canvas.drawArc(rectF,270,360,false,mArcCirclePaint);//第2个参数:时钟3点处为0度,逆时针为正方向 mArcCirclePaint.setColor(mProgressColor); //设置边角为圆 mArcCirclePaint.setStrokeCap(Paint.Cap.ROUND); mArcCirclePaint.setStrokeWidth(mInnerStrokeWidth); canvas.drawArc(rectF,270,mAngleValue,false,mArcCirclePaint); //2-文本的位置:居中显示 int centerX = width/2; //计算文本宽度 int textWidth = (int) mTextPaint.measureText(mText, 0, mText.length()); //计算baseline:垂直方向居中 Paint.FontMetricsInt fontMetrics = mTextPaint.getFontMetricsInt(); int baseline = (getMeasuredHeight() - fontMetrics.bottom + fontMetrics.top) / 2 - fontMetrics.top; int textX = centerX-textWidth/2; mTextPaint.setColor(mTextColor); mTextPaint.setTextSize(mTextSize); canvas.drawText(mText,textX,baseline,mTextPaint); if (mTopText != null && !mTopText.equals("")) { textWidth = (int) mTextPaint.measureText(mTopText, 0, mTopText.length()); textX = centerX - textWidth / 2; mTextPaint.setTextSize(mTopTextSize); mTextPaint.setColor(mTopTextColor); canvas.drawText(mTopText, textX, baseline - 20, mTextPaint); } if (mBottomText != null && !mBottomText.equals("")) { textWidth = (int) mTextPaint.measureText(mBottomText, 0, mBottomText.length()); textX = centerX - textWidth / 2; // mTextPaint.reset(); // mTextPaint.setAntiAlias(true); // mTextPaint.setLinearText(true); mTextPaint.setTextSize(mTopTextSize); mTextPaint.setColor(mBottomTextColor); canvas.drawText(mBottomText, textX, baseline + 20, mTextPaint); } } }