查看原文
其他

手把手教你撸出一个圆形加载进度条

2017-08-17 BraveJoy 终端研发部

前言介绍

手撸一个精美的环形进度条,一步一步教你学会自定义View

BraveJoy的博客地址:

http://www.jianshu.com/p/be71f6ffe512

正文

最近很长一段时间都在撸自定义View,说实话,在成为大牛的路上,这一块是必走之路,而作为菜鸡的我必须把它啃下来,俗话说不会自定义View的大牛不是好的程序猿,所以呢,强撸吧!!!

先来看一张漂亮妹子的图片,额呸呸呸,不对,是来看一张效果图图片(手动滑稽)。

看到这样的一个效果,该如何去实现呢?下面我就一步一步的把她撸出来!

思路

1.自定义属性:文字的颜色和字体大小,圆弧的颜色和宽度,一开始加载进度的位置,等等;
2.画出需要的效果:画圆弧,画字体,使用画笔paint在canvas上绘制;
3.设置进度,重新绘制;
4.接口回调。

好的,接下来我们就按照这个思路一步一步的撸:

1.自定义我们需要的属性:

在values文件夹下新建文件attrs.xml

<declare-styleable name="CircleProgressView">        <!--画笔宽度-->        <attr name="progress_paint_width" format="dimension" />        <!--画笔颜色-->        <attr name="progress_paint_color" format="color" />        <!--字体颜色-->        <attr name="progress_text_color" format="color" />        <!--字体尺寸-->        <attr name="progress_text_size" format="dimension" />        <!--加载进度的开始位置-->        <attr name="location" format="enum">            <enum name="left" value="1" />            <enum name="top" value="2" />            <enum name="right" value="3" />            <enum name="bottom" value="4" />        </attr>    </declare-styleable>

然后在自定义View中获取并设置这些属性: 首先,来声明我们的属性类型:

private int mCurrent;//当前进度
private Paint mPaintOut;
private Paint mPaintCurrent;
private Paint mPaintText;
private float mPaintWidth;//画笔宽度
private int mPaintColor = Color.RED;//画笔颜色
private int mTextColor = Color.BLACK;//字体颜色
private float mTextSize;//字体大小
private int location;//从哪个位置开始
private float startAngle;//开始角度

获取自定义属性值:

TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.CircleProgressView); location = array.getInt(R.styleable.CircleProgressView_location, 1); mPaintWidth = array.getDimension(R.styleable.CircleProgressView_progress_paint_width, dip2px(context, 4));//默认4dpmPaintColor = array.getColor(R.styleable.CircleProgressView_progress_paint_color, mPaintColor); mTextSize = array.getDimension(R.styleable.CircleProgressView_progress_text_size, dip2px(context, 18));//默认18spmTextColor = array.getColor(R.styleable.CircleProgressView_progress_text_color, mTextColor);array.recycle();

初始化和赋值:

//画笔->背景圆弧 mPaintOut = new Paint();
mPaintOut.setAntiAlias(true);
mPaintOut.setStrokeWidth(mPaintWidth);
mPaintOut.setStyle(Paint.Style.STROKE);
mPaintOut.setColor(Color.GRAY);
mPaintOut.setStrokeCap(Paint.Cap.ROUND);//画笔->进度圆弧 mPaintCurrent = new Paint();
mPaintCurrent.setAntiAlias(true);
mPaintCurrent.setStrokeWidth(mPaintWidth);
mPaintCurrent.setStyle(Paint.Style.STROKE);
mPaintCurrent.setColor(mPaintColor);
mPaintCurrent.setStrokeCap(Paint.Cap.ROUND);//画笔->绘制字体 mPaintText = new Paint();
mPaintText.setAntiAlias(true);
mPaintText.setStyle(Paint.Style.FILL);
mPaintText.setColor(mTextColor);
mPaintText.setTextSize(mTextSize);

2.画出需要的效果:画圆弧,画字体,使用画笔paint在canvas上绘制:

注意:绘制操作是在onDraw(Canvas canvas)方法中。

第一步:绘制背景灰色圆弧

我们使用cancas的drawArc()方法,来了解一下这个方法是什么意思:

public void drawArc(@NonNull RectF oval, float startAngle, float sweepAngle, boolean useCenter, @NonNull Paint paint){}oval  // 绘制范围

startAngle  // 开始角度

sweepAngle  // 扫过角度

useCenter   // 是否使用中心

至于这些都是什么意思,我讲一堆未必能懂,怎么办? 

假设是这样:canvas.drawArc(rectF, 45, 90, false, mPaintOut);代表:从45°开始,扫过90°范围,方向是顺时针方向绘制,至于那个false是什么意思,暂时先不管,传true一般画扇形图才能用到。

来看看草图:

好的,一目了然,如果我们要画一个圆圈怎么办呢,很简单,扫过的范围是360°就OK了。起点在哪个位置就无所谓了。看到这里有些人就要问了,画圆为什么不使用canvas.drawCircle()方法,这是因为后面要画的圆弧使用的也是drawArc()方法,所以为了易懂我们都用这个吧(手动大笑)。

//绘制背景圆弧,因为画笔有一定的宽度,所有画圆弧的范围要比View本身的大小稍微小一些,不然画笔画出来的东西会显示不完整 RectF rectF = new RectF(mPaintWidth / 2, mPaintWidth / 2, getWidth() - mPaintWidth / 2, getHeight() - mPaintWidth / 2); canvas.drawArc(rectF, 0, 360, false, mPaintOut);

第二步:绘制当前进度的圆弧

这里有人就发现了,我们还继续按照上面的方式只要传入不同的sweepAngle扫过角度的值不就好了吗?yes,没错。我们只要计算出当前百分比所对应的角度值是多少度就OK了。很简单的一个公式:画个草图吧! 

那么代码就很容易写了。

//绘制当前进度float sweepAngle = 360 * mCurrent / 100; canvas.drawArc(rectF, startAngle, sweepAngle, false, mPaintCurrent);

第三步:绘制文字

先来看看canvas的方法:

// 参数分别为 (文本 基线x 基线y 画笔)canvas.drawText(String text,  float x, float y,Paint paint);

基线是个什么鬼,画个草图吧!

说白了就是文字左下角的那个点的坐标。 而我们要把文字画在View的中心点位置,所以开始撸吧, 来,先求x点坐标=View宽度的一半减去文字宽度的一半,思考一下是不是? y点的坐标=View高度的一半+文字高度的一半。

OK,上代码:

//绘制进度数字 String text = mCurrent + "%"; //获取文字宽度 float textWidth = mPaintText.measureText(text, 0, text.length()); float dx = getWidth() / 2 - textWidth / 2; Paint.FontMetricsInt fontMetricsInt = mPaintText.getFontMetricsInt(); float dy = (fontMetricsInt.bottom - fontMetricsInt.top) / 2 - fontMetricsInt.bottom; float baseLine = getHeight() / 2 + dy; canvas.drawText(text, dx, baseLine, mPaintText);

为什么这么写,说白了都是套路。。。。。。

3.设置进度,重新绘制;

为了让当前进度mCurrent从0~100的增加,我们需要暴露一个方法可以实时的设置mCurrent值然后不断的进行绘制界面。

/** * 设置当前进度并重新绘制界面 * * @param mCurrent */ public void setmCurrent(int mCurrent) {    this.mCurrent = mCurrent;    invalidate(); }

相信很所人都知道只要调用了 invalidate()方法,正常情况下系统就会调用onDraw方法,然后就可以不断的绘制界面了。 这里写个属性动画来达到进度条加载的效果:

//进度条从0到100ValueAnimator animator = ValueAnimator.ofFloat(0, 100); animator.setDuration(4000); animator.setInterpolator(new LinearInterpolator()); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {    @Override    
   public void onAnimationUpdate(ValueAnimator animation)
{        float current = (float) animation.getAnimatedValue();        mCircleProgressView.setmCurrent((int) current);    } }); animator.start();

4.接口回调。

这一步就很简单了,只要监听进度条达到100就是完成了加载,所以我们先来写一个接口。

//声明接口
public interface OnLoadingCompleteListener {        
   void complete(); }
//暴露回调方法 public void setOnLoadingCompleteListener(OnLoadingCompleteListener loadingCompleteListener) {  
this.mLoadingCompleteListener = loadingCompleteListener; }
  //监听  if (mLoadingCompleteListener != null && mCurrent == 100) {       mLoadingCompleteListener.complete();   }

在对应的Activity中回调接口就可以了。

OK,到这里就算全部结束了

部分关键代码

@Override    
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        int width = MeasureSpec.getSize(widthMeasureSpec);        int height = MeasureSpec.getSize(heightMeasureSpec);        int size = width > height ? height : width;        setMeasuredDimension(size, size);    }    @Override    
protected void onDraw(Canvas canvas) {        
       super.onDraw(canvas);        //绘制背景圆弧,因为画笔有一定的宽度,所有画圆弧的范围要比View本身的大小稍微小一些,不然画笔画出来的东西会显示不完整        RectF rectF = new RectF(mPaintWidth / 2, mPaintWidth / 2, getWidth() - mPaintWidth / 2, getHeight() - mPaintWidth / 2);        canvas.drawArc(rectF, 0, 360, false, mPaintOut);        //绘制当前进度        float sweepAngle = 360 * mCurrent / 100;        canvas.drawArc(rectF, startAngle, sweepAngle, false, mPaintCurrent);        //绘制进度数字        String text = mCurrent + "%";        //获取文字宽度        float textWidth = mPaintText.measureText(text, 0, text.length());        
       float dx = getWidth() / 2 - textWidth / 2;        Paint.FontMetricsInt fontMetricsInt = mPaintText.getFontMetricsInt();        
       float dy = (fontMetricsInt.bottom - fontMetricsInt.top) / 2 - fontMetricsInt.bottom;        float baseLine = getHeight() / 2 + dy;        canvas.drawText(text, dx, baseLine, mPaintText);        
       if (mLoadingCompleteListener != null && mCurrent == 100) {            mLoadingCompleteListener.complete();        }    }


博客地址:

http://www.jianshu.com/p/2b5ef5e18fe5

项目地址:

https://github.com/SuperKotlin/CirclrProgress

终端研发部提倡 没有做不到的,只有想不到的

在这里获得的不仅仅是技术!


让心,在阳光下学会舞蹈

让灵魂,在痛苦中学会微笑

—终端研发部—



如果你觉得此文对您有所帮助,欢迎入群 QQ交流群 :232203809   

微信公众号:终端研发部


            

这里学到不仅仅是技术

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存