一.效果图
二.实现原理
1.先绘制背景圆环和代表进度的圆环。
2.绘制两端的小圆
3.使用SweepGradient为圆环添加渐变色
4.绘制阴影
5.绘制中间文字
三.实现的具体过程
1.自定义View的基本过程,自定义属性和获取自定义属性
<declare-styleable name="ProgressBarView"> <attr name="progressbar_color_start" format="color">attr> //圆弧的颜色 <attr name="progressbar_color_end" format="color">attr> <attr name="progressbar_shadow_color" format="color">attr> //阴影的颜色 <attr name="progressbar_end_color" format="color">attr> //圆弧结束时候的颜色 <attr name="progressbar_start_color" format="color">attr> //圆弧起始时候的颜色 <attr name="progressbar_default_color" format="color">attr> //圆弧默认的颜色 <attr name="progressbar_inside_circle_radius" format="dimension"/> //内部圆的半径 <attr name="progressbar_outside_circle_radius" format="dimension"/> //外部圆的半径 <attr name="progressbar_circle_width" format="dimension"/> //默认圆弧和其他圆弧之间的距离 <attr name="progressbar_progress" format="integer"/> //进度条的大小 declare-styleable>
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ProgressBarView); startColor =typedArray.getColor(R.styleable.ProgressBarView_progressbar_start_color, Color.BLACK); endColor=typedArray.getColor(R.styleable.ProgressBarView_progressbar_end_color,Color.WHITE); defaultColor=typedArray.getColor(R.styleable.ProgressBarView_progressbar_default_color,Color.WHITE); startColor=typedArray.getColor(R.styleable.ProgressBarView_progressbar_color_start,Color.WHITE); endColor = typedArray.getColor(R.styleable.ProgressBarView_progressbar_color_end,Color.WHITE); shadowColor = typedArray.getColor(R.styleable.ProgressBarView_progressbar_shadow_color,Color.WHITE); insideCircleRadius=typedArray.getDimension(R.styleable.ProgressBarView_progressbar_inside_circle_radius,50); outsideCircleRadius=typedArray.getDimension(R.styleable.ProgressBarView_progressbar_outside_circle_radius,50); circleWidth = typedArray.getDimension(R.styleable.ProgressBarView_progressbar_circle_width,40); progress = typedArray.getInteger(R.styleable.ProgressBarView_progressbar_progress,350);
绘制圆环的方法主要有两种,一种是通过绘制两个圆,在使用Paint.setXferMode()的方法来绘制,还有一种就是通过绘制Path来绘制圆环。
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) private void drawDefaultCircularArc(Canvas canvas){ defaultCirclePaint.reset(); defaultCirclePaint.setColor(Color.WHITE); int sc=canvas.saveLayer(new RectF(centerX-outsideCircleRadius,centerY-outsideCircleRadius,centerX+outsideCircleRadius,centerY+outsideCircleRadius), defaultCirclePaint); canvas.drawCircle(centerX,centerY,insideCircleRadius, defaultCirclePaint); defaultCirclePaint.setColor(defaultColor); defaultCirclePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT)); canvas.drawCircle(centerX,centerY,outsideCircleRadius, defaultCirclePaint); canvas.restoreToCount(sc); } @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) private void drawTypeCircularArc(Canvas canvas){ circlePaint.setShader(sweepGradient); RectF outRectF = new RectF(centerX-outsideCircleRadius-circleWidth,centerY-outsideCircleRadius-circleWidth, centerX+outsideCircleRadius+circleWidth,centerY+outsideCircleRadius+circleWidth); RectF insideRectF = new RectF(centerX-insideCircleRadius+circleWidth,centerY-insideCircleRadius+circleWidth, centerX+insideCircleRadius-circleWidth,centerY+insideCircleRadius-circleWidth); int sc=canvas.saveLayer(outRectF,circlePaint); canvas.translate(centerX,centerY); canvas.rotate(-90); canvas.translate(-centerX,-centerY); canvas.drawArc(insideRectF,startAngle,progress,true,circlePaint); circlePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT)); canvas.drawArc(outRectF,startAngle,progress, true,circlePaint); circlePaint.setAlpha(0); canvas.drawCircle(centerX,centerY,insideCircleRadius-circleWidth,circlePaint); canvas.restoreToCount(sc); circlePaint.reset();
这里使用的是Paint.setXferMode()的方法,该Mode总共有16种模式。绘制两端的小圆
private void drawSemicircle(Canvas canvas,int startAngle,boolean isClockwise){ canvas.save(); canvas.translate(centerX,centerY); canvas.rotate(-90); canvas.translate(-centerX,-centerY); int sweepAngle= isClockwise? 180:-180; startAngle = isClockwise? startAngle-1:startAngle+1; double anglePi = Math.toRadians(startAngle); float radius = insideCircleRadius+(outsideCircleRadius-insideCircleRadius)/2; float circleRadius = radius-insideCircleRadius+circleWidth; float circleCenterY= (float) (Math.sin(anglePi)*radius+centerY); float circleCenterX = (float) (Math.cos(anglePi)*radius+centerX); RectF rectF = new RectF(circleCenterX-circleRadius,circleCenterY-circleRadius, circleCenterX+circleRadius,circleCenterY+circleRadius); if(!isClockwise){ shadowDrawable.setCircleRectFFirst(rectF); shadowDrawable.setY((float) (Math.sin(anglePi)*insideCircleRadius+centerY)); shadowDrawable.setX((float) (Math.cos(anglePi)*insideCircleRadius+centerX)); }else{ shadowDrawable.setCircleRectFLast(rectF); } canvas.drawArc(rectF,startAngle,sweepAngle,true,circlePaint); canvas.restore(); }
使用SweepGradient为圆环添加渐变色,就设置Paint.setShadow()就好了
if(progress!=360){ startAngle+=10; progress-=10; sweepGradient = new SweepGradient(centerX,centerY,new int[]{startColor,endColor},new float[]{0,progress/360f}); }else{ sweepGradient = new SweepGradient(centerX,centerY,new int[]{startColor,endColor,startColor},new float[]{0,0.8f,1f}); }
在添加进度条的阴影效果,普遍添加阴影效果的方法就有三种,
1. .9图的设置
2.自定义xml,通过不断的叠加
3.使用elevation来设置阴影效果
但在这里这些方法都不行,因为该阴影要根据进度条的变化而变化,而上面这些阴影设置都不能在进行改变。
所以这里使用的方法是 写一个继承于Drawable的类,通过Paint.setShadowLayer()来实现阴影效果
public ShadowDrawable(int shadowColor, int shadowRadius, int offsetX, int offsetY,int startAngle) { this.mOffsetX = offsetX; this.mOffsetY = offsetY; this.mStartAngle = startAngle; mShadowPaint = new Paint(); mShadowPaint.setColor(Color.TRANSPARENT); mShadowPaint.setAntiAlias(true); mShadowPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP)); mShadowPaint.setShadowLayer(shadowRadius, offsetX, offsetY, shadowColor); mPath = new Path(); }
@Override public void draw(@NonNull Canvas canvas) { canvas.save(); canvas.translate(mInsideRectF.centerX(),mInsideRectF.centerY()); canvas.rotate(-90); canvas.translate(-mInsideRectF.centerX(),-mInsideRectF.centerY()); mPath.reset(); if(mProgress!=360){ mShadowPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP)); mPath.moveTo(x,y); mPath.arcTo(mCircleRectFFirst,mStartAngle,-180); mPath.arcTo(mOutsideRectF,mStartAngle,mProgress); mPath.arcTo(mCircleRectFLast,mStartAngle+mProgress,180); mPath.arcTo(mInsideRectF,mStartAngle+mProgress,-1*mProgress); canvas.drawPath(mPath,mShadowPaint); }else{ canvas.drawCircle(mOutsideRectF.centerX(),mOutsideRectF.centerY(),mOutsideRectF.centerX()-mOutsideRectF.left,mShadowPaint); canvas.drawCircle(mInsideRectF.centerX(),mInsideRectF.centerY(),mInsideRectF.centerX()-mInsideRectF.left,mShadowPaint); } canvas.restore(); }
public static void setShadowDrawable(View view, Drawable drawable) { view.setLayerType(View.LAYER_TYPE_SOFTWARE, null); //关闭当前View的硬件加速 ViewCompat.setBackground(view, drawable); }
阴影这里绘制的效果就是使用Path来绘制圆环的。这样之后,只需要在自定义进度条调用setShadowDrawable()方法就好了
最后一步绘制中间的文字,这里主要的就是如何让文字居中绘制
private void drawText(Canvas canvas,String text){ Paint textPaint = new Paint(); textPaint.setStyle(Paint.Style.FILL); textPaint.setColor(Color.BLACK); textPaint.setTextSize(50); Paint.FontMetrics fp = textPaint.getFontMetrics(); float length = textPaint.measureText(text); canvas.drawText(text,centerX-length/2,centerY-fp.top/2-fp.bottom/2,textPaint); }
最后效果就实现了