Android自定义View:进度条+冒泡文本
简介
最近看到有这样的需求:显示进度条,描述文本显示在进度条的刻度上面。正好练练手,回顾下自定义View知识。
分析
通过上图,我们可以看到,该UI显示了文本,而文本显示在一张图片中,有一个默认的进度条和根据实际进度显示的进度条。我们可以将其拆分成4个组成部分:
(1)图片,作为文本的背景图,这个背景图应该伸缩不失真,建议用.9图;
(2)文本,有颜色和大小,显示在图片上,居中显示;
(3)默认进度条,有颜色和宽高;
(4)真正的进度条:有颜色和进度值、宽高。它比较特殊的是当进度不等于100%时,左边是圆弧,右边是直线型(没有圆角)。
代码
首先在构造函数初始化画笔,在onSizeChanged中确定控件的宽高,设置画笔的颜色,然后在onDraw中开始绘制内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 | 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); } |
绘制图片
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | /** * 绘制文本背景框 * @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); } |
绘制文本
1 2 3 4 5 6 7 8 9 10 11 12 | /** * 绘制文本 * @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); } |
绘制默认进度条
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | /** * 绘制背景矩形圆角 * @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对,表示矩形的一个角,分别为左上,右上,右下,左下(顺时针)。
绘制进度条
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | /** * 绘制进度矩形圆角(只有左上和左下是圆角) * @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,从而实现左边圆弧,右边直线的圆角矩形效果。
开放的接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | /** * 设置是否矩形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时,文本和文本背景图如何显示;进度显示是静态的,如何动态实现显示呢等等。我们可以根据具体的需求去控制,我就不一一描述了。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步