美文网首页
自定义View教你撸出一个圆形加载进度条

自定义View教你撸出一个圆形加载进度条

作者: wayDevelop | 来源:发表于2018-09-27 14:03 被阅读0次

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

    image.png
    先总结下自定义View的步骤:
    1、自定义View的属性
    
    2、在View的构造方法中获得我们自定义的属性
    
    3、重写onMeasure
    
    4、重写onDraw
    

    思路

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

    自定义我们需要的属性:

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

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
    
        <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>
    
    </resources>
    
    

    接下就从最基本的代码开始
    在自定义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;//开始角度
    
    

    初始化构造函数 并获取attr里面的值

    public CircleProgressView(Context context) {
            this(context, null);
        }
    
        public CircleProgressView(Context context, @Nullable AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public CircleProgressView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);    
             //获取attr里面的值
            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));//默认4dp
            mPaintColor = array.getColor(R.styleable.CircleProgressView_progress_paint_color, mPaintColor);
            mTextSize = array.getDimension(R.styleable.CircleProgressView_progress_text_size, dip2px(context, 18));//默认18sp
            mTextColor = 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);
    
            if (location == 1) {//默认从左侧开始
                startAngle = -180;
            } else if (location == 2) {
                startAngle = -90;
            } else if (location == 3) {
                startAngle = 0;
            } else if (location == 4) {
                startAngle = 90;
            }
        }
    
    

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

    注意:绘制操作是在onDraw(Canvas canvas)方法中。
    第一步:绘制背景灰色圆弧:

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


    image.png

    好的,一目了然,如果我们要画一个圆圈怎么办呢,很简单,扫过的范围是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了。很简单的一个公式:画个草图吧!

    image.png

    那么代码就很容易写了。

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

    第三步:绘制文字

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


    image.png

    说白了就是文字左下角的那个点的坐标。
    而我们要把文字画在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到100
    ValueAnimator 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中回调接口就可以了。

    完整代码

    package demo.dianping.com.demo.widget;
    
    import android.content.Context;
    import android.content.res.TypedArray;
    import android.graphics.Canvas;
    import android.graphics.Color;
    import android.graphics.Paint;
    import android.graphics.RectF;
    import android.support.annotation.Nullable;
    import android.util.AttributeSet;
    import android.view.View;
    
    import demo.dianping.com.demo.R;
    
    /**
     * Created by wangwei on 2018/9/27.
     */
    
    public class CircleProgressView extends 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;//开始角度
    
        private OnLoadingCompleteListener mLoadingCompleteListener;
    
        public CircleProgressView(Context context) {
            this(context, null);
        }
    
        public CircleProgressView(Context context, @Nullable AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public CircleProgressView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            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));//默认4dp
            mPaintColor = array.getColor(R.styleable.CircleProgressView_progress_paint_color, mPaintColor);
            mTextSize = array.getDimension(R.styleable.CircleProgressView_progress_text_size, dip2px(context, 18));//默认18sp
            mTextColor = 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);
    
            if (location == 1) {//默认从左侧开始
                startAngle = -180;
            } else if (location == 2) {
                startAngle = -90;
            } else if (location == 3) {
                startAngle = 0;
            } else if (location == 4) {
                startAngle = 90;
            }
        }
    
        @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);
        }
    
        // 然后调用onDraw 进行绘制 看看效果:
        @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();
            }
        }
    
        /**
         * 获取当前进度值
         *
         * @return
         */
        public int getmCurrent() {
            return mCurrent;
        }
    
        /**
         * 设置当前进度并重新绘制界面
         *
         * @param mCurrent
         */
        public void setmCurrent(int mCurrent) {
            this.mCurrent = mCurrent;
            invalidate();
        }
    
        public void setOnLoadingCompleteListener(OnLoadingCompleteListener loadingCompleteListener) {
            this.mLoadingCompleteListener = loadingCompleteListener;
        }
    
        public interface OnLoadingCompleteListener {
            void complete();
        }
    
        /**
         * 根据手机的分辨率从 dp 的单位 转成为 px(像素)
         */
        public static int dip2px(Context context, float dpValue) {
            final float scale = context.getResources().getDisplayMetrics().density;
            return (int) (dpValue * scale + 0.5f);
        }
    
    }
    
    
    

    相关文章

      网友评论

          本文标题:自定义View教你撸出一个圆形加载进度条

          本文链接:https://www.haomeiwen.com/subject/kufeoftx.html