当前位置:   article > 正文

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

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. }

自定义属性

<declare-styleable name="CircleBarView">
    <attr name="progress_color1" format="reference|color"></attr>
    <attr name="progress_color2" format="reference|color"></attr>
    <attr name="progress_color3" format="reference|color"></attr>
    <attr name="bg_color" format="color"></attr>
    <attr name="bar_width" format="dimension"></attr>
    <attr name="circle_width" format="dimension"></attr><!--背景圆弧宽度 -->
    <attr name="circle_height" format="dimension"></attr><!--背景圆弧高度 -->
    <attr name="anchor_radius" format="dimension"></attr>
    <attr name="anchor_bg_radius" format="dimension"></attr>
    <attr name="anchor_color" format="reference|color"></attr>
    <attr name="anchor_bg_color" format="reference|color"></attr>
    <attr name="progress_max" format="integer"></attr>
</declare-styleable>
<declare-styleable name="CircleTextView">
    <attr name="paddingRing2Bg" format="dimension"></attr>
    <attr name="ringStrokeWidth" format="dimension"></attr>
    <attr name="ringColor1" format="reference|color"></attr>
    <attr name="ringColor2" format="reference|color"></attr>
    <attr name="bgColor1" format="reference|color"></attr>
    <attr name="bgColor2" format="reference|color"></attr>
</declare-styleable>

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

紧急联系请家伟信ye1061424091

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/474178
推荐阅读
相关标签
  

闽ICP备14008679号