Android自定义View:进度条+冒泡文本
简介
最近看到有这样的需求:显示进度条,描述文本显示在进度条的刻度上面。正好练练手,回顾下自定义View知识。
分析
通过上图,我们可以看到,该UI显示了文本,而文本显示在一张图片中,有一个默认的进度条和根据实际进度显示的进度条。我们可以将其拆分成4个组成部分:
(1)图片,作为文本的背景图,这个背景图应该伸缩不失真,建议用.9图;
(2)文本,有颜色和大小,显示在图片上,居中显示;
(3)默认进度条,有颜色和宽高;
(4)真正的进度条:有颜色和进度值、宽高。它比较特殊的是当进度不等于100%时,左边是圆弧,右边是直线型(没有圆角)。
代码
首先在构造函数初始化画笔,在onSizeChanged中确定控件的宽高,设置画笔的颜色,然后在onDraw中开始绘制内容
private Context mContext; /** * 背景图画笔 */ private Paint mBackgroundPaint; /** * 进度画笔 */ private Paint mProgressPaint; /** * 文本画笔 */ private Paint mTextPaint; /** * 图片画笔 */ private Paint mPicturePaint; /** * 背景矩形颜色 */ private int mBackgroundColor = Color.BLACK; /** * 进度矩形颜色 */ private int mProgressColor = Color.GRAY; /** * 文本颜色 */ private int mTextColor = Color.BLACK; /** * 文本大小 */ private int mTextSize = 28; /** * 控件宽高:控件必须在布局中指定宽高大小 */ private int mWidth; private int mHeight; /** * 进度矩形高度 */ private int mProgressHeight = 10; /** * 图片宽高 */ private int mPicWidth; private int mPicHeight; /** * 矩形4个角的半径坐标,左上,右上,右下,左下(顺时针) */ private float[] mRadiusArr = new float[]{0f,0f,0f,0f,0f,0f,0f,0f}; /** * 进度 */ private float mProgress; /** * 文本 */ private String mText; public ProgressBubbleView(Context context) { super(context); initView(context); } public ProgressBubbleView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); initView(context); } public ProgressBubbleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(context); } private void initView(Context context){ mContext = context; mBackgroundPaint = new Paint(); mBackgroundPaint.setStyle(Paint.Style.FILL); mBackgroundPaint.setStrokeWidth(1); mBackgroundPaint.setAntiAlias(true); mProgressPaint = new Paint(); mProgressPaint.setStyle(Paint.Style.FILL_AND_STROKE); mProgressPaint.setStrokeWidth(2); mProgressPaint.setAntiAlias(true); mTextPaint = new Paint(); mTextPaint.setStyle(Paint.Style.STROKE); mTextPaint.setTextSize(mTextSize); mTextPaint.setAntiAlias(true); mPicturePaint = new Paint(); mPicturePaint.setStyle(Paint.Style.STROKE); mPicturePaint.setAntiAlias(true); setLayerType(LAYER_TYPE_SOFTWARE,null); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mWidth = w; mHeight = h; mBackgroundPaint.setColor(mBackgroundColor); mProgressPaint.setColor(mProgressColor); mTextPaint.setColor(mTextColor); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //绘制文本背景框 drawPicture(canvas); //绘制文本 drawText(canvas); //将原点坐标移到文本之下 canvas.translate(0,mPicHeight-28);//因为图片底部有留白,导致图片与进度条的距离看起来偏大,所以减去一段距离 //绘制背景圆角矩形 drawBackgroud(canvas); //绘制进度矩形圆角 drawProgress(canvas); }
绘制图片
/** * 绘制文本背景框 * @param canvas */ private void drawPicture(Canvas canvas){ if(this.mProgress == 0) return; float percent = this.mProgress / 100 * 1.0f; int width = (int) (percent * mWidth); Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(), R.mipmap.infowindow_bg); mPicWidth = bitmap.getHeight(); mPicHeight = bitmap.getWidth(); int pos = width - mPicWidth/2 - 13; canvas.drawBitmap(bitmap,pos,0,mPicturePaint); }
绘制文本
/** * 绘制文本 * @param canvas */ private void drawText(Canvas canvas){ if(this.mProgress == 0) return; float percent = this.mProgress / 100 * 1.0f; int width = (int) (percent * mWidth); float length = mTextPaint.measureText(mText); canvas.drawText(mText,width-length/2,mPicHeight/3,mTextPaint); }
绘制默认进度条
/** * 绘制背景矩形圆角 * @param canvas */ private void drawBackgroud(Canvas canvas){ RectF rectF = new RectF(); rectF.left = 0; rectF.top = 0; rectF.right = mWidth; rectF.bottom = mProgressHeight; allRoundRadius(); Path path = new Path(); path.addRoundRect(rectF, mRadiusArr,Path.Direction.CW); canvas.drawPath(path,mBackgroundPaint); }
这里利用path类的addRoundRect方法的第2个参数float[] radii来实现矩形四个角是圆角还是直角效果。radii是一个大小为8的数组,2个元素为1对,表示矩形的一个角,分别为左上,右上,右下,左下(顺时针)。
绘制进度条
/** * 绘制进度矩形圆角(只有左上和左下是圆角) * @param canvas */ private void drawProgress(Canvas canvas){ if(this.mProgress == 0) return; float percent = this.mProgress / 100 * 1.0f; int width = (int) (percent * mWidth); RectF rectF = new RectF(); rectF.left = 0; rectF.top = 0; rectF.right = width; rectF.bottom = mProgressHeight; if (this.mProgress < 100){ mRadiusArr[0] = 20; mRadiusArr[1] = 20; mRadiusArr[2] = 0; mRadiusArr[3] = 0; mRadiusArr[4] = 0; mRadiusArr[5] = 0; mRadiusArr[6] = 20; mRadiusArr[7] = 20; }else{ allRoundRadius(); } Path path = new Path(); path.addRoundRect(rectF,mRadiusArr,Path.Direction.CW); canvas.drawPath(path,mProgressPaint); } /** * 4个角都是圆角 */ private void allRoundRadius(){ mRadiusArr[0] = 20; mRadiusArr[1] = 20; mRadiusArr[2] = 20; mRadiusArr[3] = 20; mRadiusArr[4] = 20; mRadiusArr[5] = 20; mRadiusArr[6] = 20; mRadiusArr[7] = 20; }
在drawProgress中,当进度不等于100%时,左边是圆弧,右边是直线型,所以设置左上、左下的圆角半径为20,其他2个角半径为0,从而实现左边圆弧,右边直线的圆角矩形效果。
开放的接口
/** * 设置是否矩形4个角都是圆角 * @param isAllRound */ public void setFourRoundRect(boolean isAllRound){ if (isAllRound){ allRoundRadius(); } invalidate(); } /** * 设置进度值0-100 * @param progress */ public void setProgress(float progress){ this.mProgress = progress; invalidate(); } /** * 设置文本 * @param text */ public void setText(String text){ this.mText = text; invalidate(); } public void setBackgroundColor(int backgroundColor){ this.mBackgroundColor = backgroundColor; invalidate(); } public void setProgressColor(int progressColor){ this.mProgressColor = progressColor; invalidate(); } public void setTextColor(int textColor){ this.mTextColor = textColor; invalidate(); }
效果图
最后
当然,这只是粗略的实现,还是有些问题遗留的。比如进度为0或100时,文本和文本背景图如何显示;进度显示是静态的,如何动态实现显示呢等等。我们可以根据具体的需求去控制,我就不一一描述了。