赞
踩
首先我们来看下效果图
圆弧高度可以自定义,说明,只有高度设置为宽度的二分之一时,才是半圆,否则就是半圆的一部分,即圆弧。
不只是圆弧是自定的,图中的文字“2”的控件也是自定义的, 下面也会给出源码。
不多说,直接上源码:
圆弧进度条控件:
- /**
- * Created by yfx on 2022/10/11 17:32
- *
- */
- public class CircleBarView extends View {
- private Paint rPaint;//绘制矩形的画笔
- private Paint progressPaint;//绘制圆弧的画笔
- private Paint anchorPaint,anchorBgPaint;//锚点
- private float anchorRadius,anchorBgRadius;
- private int anchorColor,anchorBgColor;
- private CircleBarAnim anim;
- private Paint bgPaint;//绘制背景圆弧的画笔
- private float progress;//可以更新的进度条数值
- private float maxProgress;//进度条最大值
- private float progressSweepAngle;//进度条圆弧扫过的角度
- private int startAngle;//背景圆弧的起始角度
- private float sweepAngle;//背景圆弧扫过的角度
- private RectF mRectF;//绘制圆弧的矩形区域
- private float barWidth;//圆弧进度条宽度
- private int defaultSize;//自定义View默认的宽高
- private int[] progressColors;//进度条圆弧颜色
- private int bgColor;//背景圆弧颜色
- private SweepGradient sweepGradient;//进度条颜色使用渐变色
- private LinearGradient linearGradient;
- private float circleWidth,circleHeight;
- private float xDiff;
- public CircleBarView(Context context, AttributeSet attrs) {
- super(context, attrs);
- init(context,attrs);
- }
- private void init(Context context,AttributeSet attrs) {
- TypedArray typedArray=context.obtainStyledAttributes(attrs, R.styleable.CircleBarView);
- int color1 = typedArray.getColor(R.styleable.CircleBarView_progress_color1,Color.RED);
- int color2 = typedArray.getColor(R.styleable.CircleBarView_progress_color2,color1);
- int color3 = typedArray.getColor(R.styleable.CircleBarView_progress_color3,color2);//写法巧妙
- progressColors=new int[]{color1,color1,color2,color3};//必须得4个,
- bgColor = typedArray.getColor(R.styleable.CircleBarView_bg_color,Color.GRAY);//默认为灰色
- circleWidth = typedArray.getDimension(R.styleable.CircleBarView_circle_width,dip2px(context,200));//默认为0
- circleHeight = typedArray.getDimension(R.styleable.CircleBarView_circle_height,dip2px(context,80));//默认为360
- barWidth = typedArray.getDimension(R.styleable.CircleBarView_bar_width,dip2px(context,10));//默认为10dp
- anchorRadius = typedArray.getDimension(R.styleable.CircleBarView_anchor_radius,dip2px(context,5));
- anchorBgRadius = typedArray.getDimension(R.styleable.CircleBarView_anchor_bg_radius,dip2px(context,8.5f));
- anchorColor = typedArray.getColor(R.styleable.CircleBarView_anchor_color,Color.RED);
- anchorBgColor = typedArray.getColor(R.styleable.CircleBarView_anchor_bg_color,Color.GREEN);
- maxProgress = typedArray.getInt(R.styleable.CircleBarView_progress_max,100);
- typedArray.recycle();//typedArray用完之后需要回收,防止内存泄漏
-
- rPaint=new Paint();
- rPaint.setStyle(Paint.Style.STROKE);//只描边,不填充
- rPaint.setColor(Color.RED);
-
- progressPaint=new Paint();
- progressPaint.setStyle(Paint.Style.STROKE);//只描边,不填充
-
- progressPaint.setStrokeWidth(barWidth);
- progressPaint.setAntiAlias(true);//设置抗锯齿
- progressPaint.setStrokeCap(Paint.Cap.ROUND);
-
- //锚点
- anchorPaint=new Paint();
- anchorPaint.setAntiAlias(true);//设置抗锯齿
- anchorPaint.setColor(anchorColor);
- //锚点背景
- anchorBgPaint=new Paint();
- anchorBgPaint.setAntiAlias(true);//设置抗锯齿
-
- int a=Color.alpha(anchorBgColor);
- int r=Color.red(anchorBgColor);
- int g=Color.green(anchorBgColor);
- int b=Color.blue(anchorBgColor);
- LogSuperUtil.i("circle_bar","a="+a+",r="+r+",g="+g+",b="+b);
- anchorBgPaint.setColor(Color.rgb(r,g,b));
- anchorBgPaint.setAlpha(a);
- /*raw
- anchorBgPaint.setColor(anchorBgColor);
- anchorBgPaint.setAlpha(185);*/
-
- anim=new CircleBarAnim();
- bgPaint=new Paint();
- bgPaint.setStyle(Paint.Style.STROKE);
- bgPaint.setColor(bgColor);
- bgPaint.setStrokeWidth(barWidth);
- bgPaint.setAntiAlias(true);
- bgPaint.setStrokeCap(Paint.Cap.ROUND);
- mRectF=new RectF();
-
- //
- defaultSize=dip2px(context,200);
- }
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
- //进度条的总轮廓
- canvas.drawArc(mRectF,startAngle,sweepAngle,false,bgPaint);
- float outCircleRadius=mRectF.width()/2;//带宽度的进度条的中线半径
-
- if(sweepGradient==null) {
- float centerX=(mRectF.left+mRectF.right)/2;
- float centerY=(mRectF.top+mRectF.bottom)/2;
- //
- int rotateDiff=30;//为了解决圆弧开头出的圆角颜色不正常的问题,30度够用了吧
- int rotateDegree=startAngle-rotateDiff;
- float sweepEndPosition=(rotateDiff+sweepAngle*progress/maxProgress*1.0f)/360;
- LogSuperUtil.i("circle_bar","startAngle="+startAngle+",sweepEndPosition="+sweepEndPosition);
-
- float[] positions={0,rotateDiff*1.0f/360,sweepEndPosition,1};
- sweepGradient = new SweepGradient(centerX,centerY,progressColors,positions);
- Matrix rotateMatrix = new Matrix();
- //设置渐变色
- rotateMatrix.setRotate(rotateDegree, centerX, centerY);//180表示从左侧开始往上渐变
- sweepGradient.setLocalMatrix(rotateMatrix);//sweepGradient默认是从0度方向开始渐变的,所以要逆转一下。
- progressPaint.setShader(sweepGradient);
- }
- /*
- if(linearGradient==null) {
- linearGradient = new LinearGradient(0, 0, outCircleRadius*2, 0, progressColors,
- null, LinearGradient.TileMode.CLAMP);
- progressPaint.setShader(linearGradient);
- }*/
- //进度条
- canvas.drawArc(mRectF,startAngle,progressSweepAngle,false,progressPaint);
- float anchorAngle=progressSweepAngle+(startAngle-180);
-
- float anchorCoordRedius=outCircleRadius-barWidth/2;
- double cosValue=Math.cos(Math.toRadians(anchorAngle));
- double sinValue=Math.sin(Math.toRadians(anchorAngle));
-
- float diffByAnchor=anchorBgRadius-barWidth/2;//x和y的偏移正好都是这个
- //float cx=(float)(outCircleRadius-cosValue*anchorCoordRedius+diffByAnchor);
- float cx=(float)(outCircleRadius-cosValue*outCircleRadius+anchorBgRadius);
- //float cy=(float)(outCircleRadius-sinValue*anchorCoordRedius+diffByAnchor);
- float cy=(float)(outCircleRadius-sinValue*outCircleRadius+anchorBgRadius);
- canvas.drawCircle(cx-xDiff,cy,anchorRadius,anchorPaint);//锚点小圆
- canvas.drawCircle(cx-xDiff,cy,anchorBgRadius,anchorBgPaint);//锚点背景大圆
- }
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- //int height=measureSize(defaultSize,heightMeasureSpec);
- //int width=measureSize(defaultSize,widthMeasureSpec);
- //startAngle是计算出来的,因为弦的这种,没法通过观察直接得出圆弧的起始角度
- //通过数学计算得知x
- //x是圆心(圆弧所在的圆的圆心)到弦(也就是圆弧的底部)的垂直距离
- float x=(circleWidth*circleWidth-4*circleHeight*circleHeight)/(8*circleHeight);
- //画圆弧用到的圆的半径
- float r=x+circleHeight;
- //int r=(int)(circleWidth/2/Math.cos(Math.toRadians(startAngle-180)));
- //弦长/2/半径=cos(startAngle-180)
- //(startAngle-180)
- double asin=Math.asin(circleWidth/2/r);
- //LogSuperUtil.i("circle_bar","asin="+asin);
- int a=(int)Math.toDegrees(asin);
- //LogSuperUtil.i("circle_bar","a="+a);
- startAngle=180+(90-a);
- sweepAngle=2*a;
- //int min=Math.max(width,height);//获取View最短边的长度
- int width=(int)(Math.sqrt(Math.pow(r+anchorBgRadius,2)-Math.pow(x,2))*2+0.5f+anchorBgRadius);//圆弧的宽度
- width=(int)(2*(circleWidth/2+anchorBgRadius)+0.5f);
- int height=(int)(circleHeight+anchorBgRadius+0.5f+anchorBgRadius);
- //height=width;
- xDiff=r-circleWidth/2;//画布是从最左边开始画圆且显示的,不偏移的话,显示的效果不对。
- LogSuperUtil.i("circle_bar","xDiff="+xDiff);
- setMeasuredDimension(width,height);
- if(Math.min(width,height)>=barWidth*2) {//这里简单限制了圆弧的最大宽度
- //1.mRectF决定着圆画在哪个框框里面
- //2.mRectF在当前View中,但不是完全占据当前View
- //3.弧度的粗细画的时候是均分到圆半径线的两侧的
- //4.之所以矩形设置的四个点有偏移,是因为有弧度宽度。
- //因为锚点也有宽度,点再次做调整.
- //mRectF.set(barWidth/2,barWidth/2,min-barWidth/2,min-barWidth/2);
- float diffByAnchor=anchorBgRadius-barWidth/2;//受锚点影响需要修正的偏移
- //是整个圆的矩形,不是弧形区域所在的矩形
- mRectF.set(barWidth/2+diffByAnchor-xDiff,barWidth/2+diffByAnchor,barWidth/2+diffByAnchor+2*r-xDiff,barWidth/2+diffByAnchor+2*r);
- }
- }
- private int measureSize(int defaultSize,int measureSpec) {
- int result=defaultSize;
- int specMode= MeasureSpec.getMode(measureSpec);
- int specSize= MeasureSpec.getSize(measureSpec);
- if(specMode== MeasureSpec.EXACTLY) {
- LogSuperUtil.i("circle_bar","=EXACTLY");
- result=specSize;
- }else if(specMode== MeasureSpec.AT_MOST) {
- LogSuperUtil.i("circle_bar","=AT_MOST");
- result=Math.min(result,specSize);
- }else {
- LogSuperUtil.i("circle_bar","=defaultSize");
- }
- return result;
- }
- private TextView textView;
- private OnAnimationListener onAnimationListener;
- public class CircleBarAnim extends Animation {
- public CircleBarAnim() {
-
- }
-
- @Override
- protected void applyTransformation(float interpolatedTime, Transformation t) {
- super.applyTransformation(interpolatedTime, t);
- progressSweepAngle=interpolatedTime*sweepAngle*progress/maxProgress;//这里计算进度条的比例
- postInvalidate();
- if(textView!=null&&onAnimationListener!=null) {
- textView.setText(onAnimationListener.howToChangeText(interpolatedTime,progress,maxProgress));
- }
- }
- }
-
- /**
- * 设置显示文字的TextView
- * @param textView
- */
- public void setTextView(TextView textView) {
- this.textView=textView;
- }
- /*
- * circleBarView.setOnAnimationListener(new CircleBarView.OnAnimationListener() {
- @Override
- public String howToChangeText(float interpolatedTime, float progressNum, float maxNum) {
- DecimalFormat decimalFormat=new DecimalFormat("0.00");
- String s = decimalFormat.format(interpolatedTime * progressNum / maxNum * 100) + "%";
- return s;
- }
- });*/
-
-
- public interface OnAnimationListener {
- /**
- * 如何处理要显示的文字内容
- * @param interpolatedTime 从0渐变成1,到1时结束动画
- * @param progressNum 进度条数值
- * @param maxNum 进度条最大值
- * @return
- */
- String howToChangeText(float interpolatedTime, float progressNum, float maxNum);
- }
- //写个方法给外部调用,用来设置动画时间
- public void setProgress(float progress,int time) {
- anim.setDuration(time);
- this.startAnimation(anim);
- this.progress=progress;
- }
- public static int dip2px(Context context,float dpValue) {
- float scale=context.getResources().getDisplayMetrics().density;
- return (int)(dpValue*scale+0.5f);
- }
- public static int px2dip(Context context,float pxValue) {
- float scale=context.getResources().getDisplayMetrics().density;
- return (int)(pxValue/scale+0.5f);
- }
-
- /**
- * //extraDistance比较小时,TextView离圆弧很近,是正常,因为TextView是从其自身的左上角开始渲染。
- * @param sweepAnglePercent
- * @param extraDistance
- * @return
- */
- public float getAngleX(float sweepAnglePercent,int extraDistance) {
- float outCircleRadius=mRectF.width()/2+barWidth/2;//带宽度的进度条的外圆半径
- float diffByAnchor=anchorBgRadius-barWidth/2;//x和y的偏移正好都是这个
- float x=(float)(outCircleRadius+diffByAnchor-Math.cos(Math.toRadians(startAngle-180+sweepAnglePercent*sweepAngle))*(outCircleRadius+dip2px(getContext(),extraDistance))-xDiff);
- return x;
- }
- private static final String TAG="circle_bar";
- /**
- * 在当前View中的y坐标
- * (注意,TextView渲染的时候,这个坐标是作为TextView控件的左上角进行渲染。
- * 所以,extraDistance小的话,显示的结果TextView可能还在当前View的内部呢,但TextView的左上角是在圆外部的。
- * @param sweepAnglePercent
- * @param extraDistance 从外圆算的往外扩展的距离,不是从锚点背景边缘往外扩展。
- * @return
- */
- public float getAngleY(float sweepAnglePercent,int extraDistance) {
- float outCircleRadius=mRectF.width()/2+barWidth/2;//带宽度的进度条的外圆半径
- float diffByAnchor=anchorBgRadius-barWidth/2;//x和y的偏移正好都是这个
- float coordA=startAngle-180+sweepAnglePercent*sweepAngle;
- LogSuperUtil.i("circle_bar","coordA="+coordA);
- float coordY=(float)Math.sin(Math.toRadians(coordA))*(outCircleRadius+dip2px(getContext(),extraDistance));
- LogSuperUtil.i("circle_bar","coordY="+coordY);
- float tempR=outCircleRadius+diffByAnchor;
- LogSuperUtil.i("circle_bar","tempR="+tempR+",="+circleWidth);//CircleWidth是圆弧宽度
- LogSuperUtil.i("circle_bar","mRectF.width()/2="+mRectF.width()/2);
- float y=(float) (tempR-coordY);
- return y;
- }
- }
然后,是那个背景是圆(圆有渐变色和内间距)的文字控件的源码:
- /**
- * 表示用户等级的文字,可以设置背景色、圆环色、圆弧与背景的内间距
- * Created by yfx on 2022/10/20 16:36
- */
- public class CircleBarTextView extends AppCompatTextView {
- private Paint mPaintBg;
- private Paint mPaintRing;
- private float mRadiusBg;
- private float mRadiusRing;
- private float mRingStrokeWidth;
- private float mPaddingRing2Bg;
- private boolean mIsBgShow;
- private boolean mIsRingShow;
- private int[] ringColors;//进度条圆弧颜色
- private int[] bgColors;//
- private LinearGradient linearGradientRing;
- private LinearGradient linearGradientBg;
- public CircleBarTextView(Context context, @Nullable AttributeSet attrs) {
- super(context, attrs);
- init(context,attrs);
- }
- private void init(Context context,AttributeSet attrs) {
- TypedArray typedArray=context.obtainStyledAttributes(attrs, R.styleable.CircleTextView);
- int ringColor1 = typedArray.getColor(R.styleable.CircleTextView_ringColor1,Color.RED);
- int ringColor2 = typedArray.getColor(R.styleable.CircleTextView_ringColor2,ringColor1);
- //int color3 = typedArray.getColor(R.styleable.CircleTextView_ringColor3,color2);//写法巧妙
- ringColors=new int[]{ringColor1,ringColor2};
- int bgColor1 = typedArray.getColor(R.styleable.CircleTextView_bgColor1,Color.RED);
- int bgColor2 = typedArray.getColor(R.styleable.CircleTextView_bgColor2,bgColor1);
- //int color3 = typedArray.getColor(R.styleable.CircleTextView_ringColor3,color2);//写法巧妙
- bgColors=new int[]{bgColor1,bgColor2};
- mPaddingRing2Bg = typedArray.getDimension(R.styleable.CircleTextView_paddingRing2Bg,0);//写法巧妙
- mRingStrokeWidth = typedArray.getDimension(R.styleable.CircleTextView_ringStrokeWidth,0);//写法巧妙
- //
- mPaintBg=new Paint();
- mPaintBg.setColor(bgColor1);
- mPaintBg.setStyle(Paint.Style.FILL);
- mPaintBg.setAntiAlias(true);
- //
- mPaintRing=new Paint();
- mPaintBg.setColor(ringColor1);
- mPaintRing.setStyle(Paint.Style.STROKE);
- mPaintRing.setStrokeWidth(mRingStrokeWidth);
- mPaintRing.setAntiAlias(true);
-
-
- }
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- /*
- int width=mRadiusRing*2;
- int height=mRadiusRing*2;
- setMeasuredDimension(width,height);*/
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- float cx=getWidth()/2;
- float cy=getWidth()/2;
- mRadiusRing=getWidth()/2-mRingStrokeWidth/2;
- mRadiusBg=mRadiusRing-mPaddingRing2Bg-mRingStrokeWidth/2;
- //渐变色设置
- if(linearGradientRing==null) {
- float r=mRadiusRing+mRingStrokeWidth/2;
- float x0=(float)(r-r/Math.sqrt(2));
- float y0=(float)(r+r/Math.sqrt(2));
- float x1=(float)(r+r/Math.sqrt(2));
- float y1=(float)(r-r/Math.sqrt(2));
- linearGradientRing = new LinearGradient(x0, y0, x1, y1, ringColors,
- null, LinearGradient.TileMode.CLAMP);
- mPaintRing.setShader(linearGradientRing);
- }
-
- if(linearGradientBg==null) {
- float r=mRadiusRing+mRingStrokeWidth/2;//还是根据最大圆来计算
- float x0=r-mRingStrokeWidth-mPaddingRing2Bg;
- float y0=r;
- float x1=r+mRadiusBg;
- float y1=r;
- //左下到右上的线性渐变
- linearGradientBg = new LinearGradient(x0, y0, x1, y1, bgColors,
- null, LinearGradient.TileMode.CLAMP);
- mPaintBg.setShader(linearGradientBg);
- }
- if(mIsBgShow) {
- canvas.drawCircle(cx,cy,mRadiusBg,mPaintBg);
- }
-
- if(mIsRingShow) {
- canvas.drawCircle(cx,cy,mRadiusRing,mPaintRing);
- }
- //canvas.drawtext
- super.onDraw(canvas);
- }
- public static int dip2px(Context context,float dpValue) {
- float scale=context.getResources().getDisplayMetrics().density;
- return (int)(dpValue*scale+0.5f);
- }
- public void setBgShow(boolean isShow) {
- mIsBgShow=isShow;
- postInvalidate();
- }
- public void setRingShow(boolean isShow) {
- mIsRingShow=isShow;
- postInvalidate();
- }
- }
<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
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。