• Android自定义圆弧进度条(半圆进度条) 圆弧渐变色进度条带指示 圆弧宽高可自由修改


    首先我们来看下效果图

    圆弧高度可以自定义,说明,只有高度设置为宽度的二分之一时,才是半圆,否则就是半圆的一部分,即圆弧。

    不只是圆弧是自定的,图中的文字“2”的控件也是自定义的, 下面也会给出源码。

    不多说,直接上源码:

    圆弧进度条控件:

    1. /**
    2. * Created by yfx on 2022/10/11 17:32
    3. *
    4. */
    5. public class CircleBarView extends View {
    6. private Paint rPaint;//绘制矩形的画笔
    7. private Paint progressPaint;//绘制圆弧的画笔
    8. private Paint anchorPaint,anchorBgPaint;//锚点
    9. private float anchorRadius,anchorBgRadius;
    10. private int anchorColor,anchorBgColor;
    11. private CircleBarAnim anim;
    12. private Paint bgPaint;//绘制背景圆弧的画笔
    13. private float progress;//可以更新的进度条数值
    14. private float maxProgress;//进度条最大值
    15. private float progressSweepAngle;//进度条圆弧扫过的角度
    16. private int startAngle;//背景圆弧的起始角度
    17. private float sweepAngle;//背景圆弧扫过的角度
    18. private RectF mRectF;//绘制圆弧的矩形区域
    19. private float barWidth;//圆弧进度条宽度
    20. private int defaultSize;//自定义View默认的宽高
    21. private int[] progressColors;//进度条圆弧颜色
    22. private int bgColor;//背景圆弧颜色
    23. private SweepGradient sweepGradient;//进度条颜色使用渐变色
    24. private LinearGradient linearGradient;
    25. private float circleWidth,circleHeight;
    26. private float xDiff;
    27. public CircleBarView(Context context, AttributeSet attrs) {
    28. super(context, attrs);
    29. init(context,attrs);
    30. }
    31. private void init(Context context,AttributeSet attrs) {
    32. TypedArray typedArray=context.obtainStyledAttributes(attrs, R.styleable.CircleBarView);
    33. int color1 = typedArray.getColor(R.styleable.CircleBarView_progress_color1,Color.RED);
    34. int color2 = typedArray.getColor(R.styleable.CircleBarView_progress_color2,color1);
    35. int color3 = typedArray.getColor(R.styleable.CircleBarView_progress_color3,color2);//写法巧妙
    36. progressColors=new int[]{color1,color1,color2,color3};//必须得4个,
    37. bgColor = typedArray.getColor(R.styleable.CircleBarView_bg_color,Color.GRAY);//默认为灰色
    38. circleWidth = typedArray.getDimension(R.styleable.CircleBarView_circle_width,dip2px(context,200));//默认为0
    39. circleHeight = typedArray.getDimension(R.styleable.CircleBarView_circle_height,dip2px(context,80));//默认为360
    40. barWidth = typedArray.getDimension(R.styleable.CircleBarView_bar_width,dip2px(context,10));//默认为10dp
    41. anchorRadius = typedArray.getDimension(R.styleable.CircleBarView_anchor_radius,dip2px(context,5));
    42. anchorBgRadius = typedArray.getDimension(R.styleable.CircleBarView_anchor_bg_radius,dip2px(context,8.5f));
    43. anchorColor = typedArray.getColor(R.styleable.CircleBarView_anchor_color,Color.RED);
    44. anchorBgColor = typedArray.getColor(R.styleable.CircleBarView_anchor_bg_color,Color.GREEN);
    45. maxProgress = typedArray.getInt(R.styleable.CircleBarView_progress_max,100);
    46. typedArray.recycle();//typedArray用完之后需要回收,防止内存泄漏
    47. rPaint=new Paint();
    48. rPaint.setStyle(Paint.Style.STROKE);//只描边,不填充
    49. rPaint.setColor(Color.RED);
    50. progressPaint=new Paint();
    51. progressPaint.setStyle(Paint.Style.STROKE);//只描边,不填充
    52. progressPaint.setStrokeWidth(barWidth);
    53. progressPaint.setAntiAlias(true);//设置抗锯齿
    54. progressPaint.setStrokeCap(Paint.Cap.ROUND);
    55. //锚点
    56. anchorPaint=new Paint();
    57. anchorPaint.setAntiAlias(true);//设置抗锯齿
    58. anchorPaint.setColor(anchorColor);
    59. //锚点背景
    60. anchorBgPaint=new Paint();
    61. anchorBgPaint.setAntiAlias(true);//设置抗锯齿
    62. int a=Color.alpha(anchorBgColor);
    63. int r=Color.red(anchorBgColor);
    64. int g=Color.green(anchorBgColor);
    65. int b=Color.blue(anchorBgColor);
    66. LogSuperUtil.i("circle_bar","a="+a+",r="+r+",g="+g+",b="+b);
    67. anchorBgPaint.setColor(Color.rgb(r,g,b));
    68. anchorBgPaint.setAlpha(a);
    69. /*raw
    70. anchorBgPaint.setColor(anchorBgColor);
    71. anchorBgPaint.setAlpha(185);*/
    72. anim=new CircleBarAnim();
    73. bgPaint=new Paint();
    74. bgPaint.setStyle(Paint.Style.STROKE);
    75. bgPaint.setColor(bgColor);
    76. bgPaint.setStrokeWidth(barWidth);
    77. bgPaint.setAntiAlias(true);
    78. bgPaint.setStrokeCap(Paint.Cap.ROUND);
    79. mRectF=new RectF();
    80. //
    81. defaultSize=dip2px(context,200);
    82. }
    83. @Override
    84. protected void onDraw(Canvas canvas) {
    85. super.onDraw(canvas);
    86. //进度条的总轮廓
    87. canvas.drawArc(mRectF,startAngle,sweepAngle,false,bgPaint);
    88. float outCircleRadius=mRectF.width()/2;//带宽度的进度条的中线半径
    89. if(sweepGradient==null) {
    90. float centerX=(mRectF.left+mRectF.right)/2;
    91. float centerY=(mRectF.top+mRectF.bottom)/2;
    92. //
    93. int rotateDiff=30;//为了解决圆弧开头出的圆角颜色不正常的问题,30度够用了吧
    94. int rotateDegree=startAngle-rotateDiff;
    95. float sweepEndPosition=(rotateDiff+sweepAngle*progress/maxProgress*1.0f)/360;
    96. LogSuperUtil.i("circle_bar","startAngle="+startAngle+",sweepEndPosition="+sweepEndPosition);
    97. float[] positions={0,rotateDiff*1.0f/360,sweepEndPosition,1};
    98. sweepGradient = new SweepGradient(centerX,centerY,progressColors,positions);
    99. Matrix rotateMatrix = new Matrix();
    100. //设置渐变色
    101. rotateMatrix.setRotate(rotateDegree, centerX, centerY);//180表示从左侧开始往上渐变
    102. sweepGradient.setLocalMatrix(rotateMatrix);//sweepGradient默认是从0度方向开始渐变的,所以要逆转一下。
    103. progressPaint.setShader(sweepGradient);
    104. }
    105. /*
    106. if(linearGradient==null) {
    107. linearGradient = new LinearGradient(0, 0, outCircleRadius*2, 0, progressColors,
    108. null, LinearGradient.TileMode.CLAMP);
    109. progressPaint.setShader(linearGradient);
    110. }*/
    111. //进度条
    112. canvas.drawArc(mRectF,startAngle,progressSweepAngle,false,progressPaint);
    113. float anchorAngle=progressSweepAngle+(startAngle-180);
    114. float anchorCoordRedius=outCircleRadius-barWidth/2;
    115. double cosValue=Math.cos(Math.toRadians(anchorAngle));
    116. double sinValue=Math.sin(Math.toRadians(anchorAngle));
    117. float diffByAnchor=anchorBgRadius-barWidth/2;//x和y的偏移正好都是这个
    118. //float cx=(float)(outCircleRadius-cosValue*anchorCoordRedius+diffByAnchor);
    119. float cx=(float)(outCircleRadius-cosValue*outCircleRadius+anchorBgRadius);
    120. //float cy=(float)(outCircleRadius-sinValue*anchorCoordRedius+diffByAnchor);
    121. float cy=(float)(outCircleRadius-sinValue*outCircleRadius+anchorBgRadius);
    122. canvas.drawCircle(cx-xDiff,cy,anchorRadius,anchorPaint);//锚点小圆
    123. canvas.drawCircle(cx-xDiff,cy,anchorBgRadius,anchorBgPaint);//锚点背景大圆
    124. }
    125. @Override
    126. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    127. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    128. //int height=measureSize(defaultSize,heightMeasureSpec);
    129. //int width=measureSize(defaultSize,widthMeasureSpec);
    130. //startAngle是计算出来的,因为弦的这种,没法通过观察直接得出圆弧的起始角度
    131. //通过数学计算得知x
    132. //x是圆心(圆弧所在的圆的圆心)到弦(也就是圆弧的底部)的垂直距离
    133. float x=(circleWidth*circleWidth-4*circleHeight*circleHeight)/(8*circleHeight);
    134. //画圆弧用到的圆的半径
    135. float r=x+circleHeight;
    136. //int r=(int)(circleWidth/2/Math.cos(Math.toRadians(startAngle-180)));
    137. //弦长/2/半径=cos(startAngle-180)
    138. //(startAngle-180)
    139. double asin=Math.asin(circleWidth/2/r);
    140. //LogSuperUtil.i("circle_bar","asin="+asin);
    141. int a=(int)Math.toDegrees(asin);
    142. //LogSuperUtil.i("circle_bar","a="+a);
    143. startAngle=180+(90-a);
    144. sweepAngle=2*a;
    145. //int min=Math.max(width,height);//获取View最短边的长度
    146. int width=(int)(Math.sqrt(Math.pow(r+anchorBgRadius,2)-Math.pow(x,2))*2+0.5f+anchorBgRadius);//圆弧的宽度
    147. width=(int)(2*(circleWidth/2+anchorBgRadius)+0.5f);
    148. int height=(int)(circleHeight+anchorBgRadius+0.5f+anchorBgRadius);
    149. //height=width;
    150. xDiff=r-circleWidth/2;//画布是从最左边开始画圆且显示的,不偏移的话,显示的效果不对。
    151. LogSuperUtil.i("circle_bar","xDiff="+xDiff);
    152. setMeasuredDimension(width,height);
    153. if(Math.min(width,height)>=barWidth*2) {//这里简单限制了圆弧的最大宽度
    154. //1.mRectF决定着圆画在哪个框框里面
    155. //2.mRectF在当前View中,但不是完全占据当前View
    156. //3.弧度的粗细画的时候是均分到圆半径线的两侧的
    157. //4.之所以矩形设置的四个点有偏移,是因为有弧度宽度。
    158. //因为锚点也有宽度,点再次做调整.
    159. //mRectF.set(barWidth/2,barWidth/2,min-barWidth/2,min-barWidth/2);
    160. float diffByAnchor=anchorBgRadius-barWidth/2;//受锚点影响需要修正的偏移
    161. //是整个圆的矩形,不是弧形区域所在的矩形
    162. mRectF.set(barWidth/2+diffByAnchor-xDiff,barWidth/2+diffByAnchor,barWidth/2+diffByAnchor+2*r-xDiff,barWidth/2+diffByAnchor+2*r);
    163. }
    164. }
    165. private int measureSize(int defaultSize,int measureSpec) {
    166. int result=defaultSize;
    167. int specMode= MeasureSpec.getMode(measureSpec);
    168. int specSize= MeasureSpec.getSize(measureSpec);
    169. if(specMode== MeasureSpec.EXACTLY) {
    170. LogSuperUtil.i("circle_bar","=EXACTLY");
    171. result=specSize;
    172. }else if(specMode== MeasureSpec.AT_MOST) {
    173. LogSuperUtil.i("circle_bar","=AT_MOST");
    174. result=Math.min(result,specSize);
    175. }else {
    176. LogSuperUtil.i("circle_bar","=defaultSize");
    177. }
    178. return result;
    179. }
    180. private TextView textView;
    181. private OnAnimationListener onAnimationListener;
    182. public class CircleBarAnim extends Animation {
    183. public CircleBarAnim() {
    184. }
    185. @Override
    186. protected void applyTransformation(float interpolatedTime, Transformation t) {
    187. super.applyTransformation(interpolatedTime, t);
    188. progressSweepAngle=interpolatedTime*sweepAngle*progress/maxProgress;//这里计算进度条的比例
    189. postInvalidate();
    190. if(textView!=null&&onAnimationListener!=null) {
    191. textView.setText(onAnimationListener.howToChangeText(interpolatedTime,progress,maxProgress));
    192. }
    193. }
    194. }
    195. /**
    196. * 设置显示文字的TextView
    197. * @param textView
    198. */
    199. public void setTextView(TextView textView) {
    200. this.textView=textView;
    201. }
    202. /*
    203. * circleBarView.setOnAnimationListener(new CircleBarView.OnAnimationListener() {
    204. @Override
    205. public String howToChangeText(float interpolatedTime, float progressNum, float maxNum) {
    206. DecimalFormat decimalFormat=new DecimalFormat("0.00");
    207. String s = decimalFormat.format(interpolatedTime * progressNum / maxNum * 100) + "%";
    208. return s;
    209. }
    210. });*/
    211. public interface OnAnimationListener {
    212. /**
    213. * 如何处理要显示的文字内容
    214. * @param interpolatedTime 从0渐变成1,到1时结束动画
    215. * @param progressNum 进度条数值
    216. * @param maxNum 进度条最大值
    217. * @return
    218. */
    219. String howToChangeText(float interpolatedTime, float progressNum, float maxNum);
    220. }
    221. //写个方法给外部调用,用来设置动画时间
    222. public void setProgress(float progress,int time) {
    223. anim.setDuration(time);
    224. this.startAnimation(anim);
    225. this.progress=progress;
    226. }
    227. public static int dip2px(Context context,float dpValue) {
    228. float scale=context.getResources().getDisplayMetrics().density;
    229. return (int)(dpValue*scale+0.5f);
    230. }
    231. public static int px2dip(Context context,float pxValue) {
    232. float scale=context.getResources().getDisplayMetrics().density;
    233. return (int)(pxValue/scale+0.5f);
    234. }
    235. /**
    236. * //extraDistance比较小时,TextView离圆弧很近,是正常,因为TextView是从其自身的左上角开始渲染。
    237. * @param sweepAnglePercent
    238. * @param extraDistance
    239. * @return
    240. */
    241. public float getAngleX(float sweepAnglePercent,int extraDistance) {
    242. float outCircleRadius=mRectF.width()/2+barWidth/2;//带宽度的进度条的外圆半径
    243. float diffByAnchor=anchorBgRadius-barWidth/2;//x和y的偏移正好都是这个
    244. float x=(float)(outCircleRadius+diffByAnchor-Math.cos(Math.toRadians(startAngle-180+sweepAnglePercent*sweepAngle))*(outCircleRadius+dip2px(getContext(),extraDistance))-xDiff);
    245. return x;
    246. }
    247. private static final String TAG="circle_bar";
    248. /**
    249. * 在当前View中的y坐标
    250. * (注意,TextView渲染的时候,这个坐标是作为TextView控件的左上角进行渲染。
    251. * 所以,extraDistance小的话,显示的结果TextView可能还在当前View的内部呢,但TextView的左上角是在圆外部的。
    252. * @param sweepAnglePercent
    253. * @param extraDistance 从外圆算的往外扩展的距离,不是从锚点背景边缘往外扩展。
    254. * @return
    255. */
    256. public float getAngleY(float sweepAnglePercent,int extraDistance) {
    257. float outCircleRadius=mRectF.width()/2+barWidth/2;//带宽度的进度条的外圆半径
    258. float diffByAnchor=anchorBgRadius-barWidth/2;//x和y的偏移正好都是这个
    259. float coordA=startAngle-180+sweepAnglePercent*sweepAngle;
    260. LogSuperUtil.i("circle_bar","coordA="+coordA);
    261. float coordY=(float)Math.sin(Math.toRadians(coordA))*(outCircleRadius+dip2px(getContext(),extraDistance));
    262. LogSuperUtil.i("circle_bar","coordY="+coordY);
    263. float tempR=outCircleRadius+diffByAnchor;
    264. LogSuperUtil.i("circle_bar","tempR="+tempR+",="+circleWidth);//CircleWidth是圆弧宽度
    265. LogSuperUtil.i("circle_bar","mRectF.width()/2="+mRectF.width()/2);
    266. float y=(float) (tempR-coordY);
    267. return y;
    268. }
    269. }

    然后,是那个背景是圆(圆有渐变色和内间距)的文字控件的源码:

    1. /**
    2. * 表示用户等级的文字,可以设置背景色、圆环色、圆弧与背景的内间距
    3. * Created by yfx on 2022/10/20 16:36
    4. */
    5. public class CircleBarTextView extends AppCompatTextView {
    6. private Paint mPaintBg;
    7. private Paint mPaintRing;
    8. private float mRadiusBg;
    9. private float mRadiusRing;
    10. private float mRingStrokeWidth;
    11. private float mPaddingRing2Bg;
    12. private boolean mIsBgShow;
    13. private boolean mIsRingShow;
    14. private int[] ringColors;//进度条圆弧颜色
    15. private int[] bgColors;//
    16. private LinearGradient linearGradientRing;
    17. private LinearGradient linearGradientBg;
    18. public CircleBarTextView(Context context, @Nullable AttributeSet attrs) {
    19. super(context, attrs);
    20. init(context,attrs);
    21. }
    22. private void init(Context context,AttributeSet attrs) {
    23. TypedArray typedArray=context.obtainStyledAttributes(attrs, R.styleable.CircleTextView);
    24. int ringColor1 = typedArray.getColor(R.styleable.CircleTextView_ringColor1,Color.RED);
    25. int ringColor2 = typedArray.getColor(R.styleable.CircleTextView_ringColor2,ringColor1);
    26. //int color3 = typedArray.getColor(R.styleable.CircleTextView_ringColor3,color2);//写法巧妙
    27. ringColors=new int[]{ringColor1,ringColor2};
    28. int bgColor1 = typedArray.getColor(R.styleable.CircleTextView_bgColor1,Color.RED);
    29. int bgColor2 = typedArray.getColor(R.styleable.CircleTextView_bgColor2,bgColor1);
    30. //int color3 = typedArray.getColor(R.styleable.CircleTextView_ringColor3,color2);//写法巧妙
    31. bgColors=new int[]{bgColor1,bgColor2};
    32. mPaddingRing2Bg = typedArray.getDimension(R.styleable.CircleTextView_paddingRing2Bg,0);//写法巧妙
    33. mRingStrokeWidth = typedArray.getDimension(R.styleable.CircleTextView_ringStrokeWidth,0);//写法巧妙
    34. //
    35. mPaintBg=new Paint();
    36. mPaintBg.setColor(bgColor1);
    37. mPaintBg.setStyle(Paint.Style.FILL);
    38. mPaintBg.setAntiAlias(true);
    39. //
    40. mPaintRing=new Paint();
    41. mPaintBg.setColor(ringColor1);
    42. mPaintRing.setStyle(Paint.Style.STROKE);
    43. mPaintRing.setStrokeWidth(mRingStrokeWidth);
    44. mPaintRing.setAntiAlias(true);
    45. }
    46. @Override
    47. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    48. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    49. /*
    50. int width=mRadiusRing*2;
    51. int height=mRadiusRing*2;
    52. setMeasuredDimension(width,height);*/
    53. }
    54. @Override
    55. protected void onDraw(Canvas canvas) {
    56. float cx=getWidth()/2;
    57. float cy=getWidth()/2;
    58. mRadiusRing=getWidth()/2-mRingStrokeWidth/2;
    59. mRadiusBg=mRadiusRing-mPaddingRing2Bg-mRingStrokeWidth/2;
    60. //渐变色设置
    61. if(linearGradientRing==null) {
    62. float r=mRadiusRing+mRingStrokeWidth/2;
    63. float x0=(float)(r-r/Math.sqrt(2));
    64. float y0=(float)(r+r/Math.sqrt(2));
    65. float x1=(float)(r+r/Math.sqrt(2));
    66. float y1=(float)(r-r/Math.sqrt(2));
    67. linearGradientRing = new LinearGradient(x0, y0, x1, y1, ringColors,
    68. null, LinearGradient.TileMode.CLAMP);
    69. mPaintRing.setShader(linearGradientRing);
    70. }
    71. if(linearGradientBg==null) {
    72. float r=mRadiusRing+mRingStrokeWidth/2;//还是根据最大圆来计算
    73. float x0=r-mRingStrokeWidth-mPaddingRing2Bg;
    74. float y0=r;
    75. float x1=r+mRadiusBg;
    76. float y1=r;
    77. //左下到右上的线性渐变
    78. linearGradientBg = new LinearGradient(x0, y0, x1, y1, bgColors,
    79. null, LinearGradient.TileMode.CLAMP);
    80. mPaintBg.setShader(linearGradientBg);
    81. }
    82. if(mIsBgShow) {
    83. canvas.drawCircle(cx,cy,mRadiusBg,mPaintBg);
    84. }
    85. if(mIsRingShow) {
    86. canvas.drawCircle(cx,cy,mRadiusRing,mPaintRing);
    87. }
    88. //canvas.drawtext
    89. super.onDraw(canvas);
    90. }
    91. public static int dip2px(Context context,float dpValue) {
    92. float scale=context.getResources().getDisplayMetrics().density;
    93. return (int)(dpValue*scale+0.5f);
    94. }
    95. public void setBgShow(boolean isShow) {
    96. mIsBgShow=isShow;
    97. postInvalidate();
    98. }
    99. public void setRingShow(boolean isShow) {
    100. mIsRingShow=isShow;
    101. postInvalidate();
    102. }
    103. }

    自定义属性

    
        
        
        
        
        
        
        
        
        
        
        
        
    
    
        
        
        
        
        
        
    

    文字的位置需要处理,使用时有几个细节需要注意,我们下篇再具体介绍。

    紧急联系请家伟信ye1061424091

  • 相关阅读:
    单例模式 创建型模式之一
    JSX基本使用
    计算机网络——计算机网络体系结构(4/4)-计算机网络体系结构中的专用术语(实体、协议、服务,三次握手‘三报文握手’、数据包术语)
    【深入浅出 Yarn 架构与实现】5-3 Yarn 调度器资源抢占模型
    linux系统iptables的操作
    手把手带你申请软著!助你提高通过率!!!
    如何使用Scrapy提取和处理数据
    ASP.NET Core 项目部署
    nginx系列第三篇:nginx平台相关源码自动生成流程
    Mysql数据库
  • 原文地址:https://blog.csdn.net/yeziyfx/article/details/127553212