自定义view实现炫酷loading progress控件

实现效果如下图:

实现原理:

通过属性动画和canvas结合实现效果;

首先在attrs中自定义属性:

proogress_start_color:圆环渐变开始颜色;

progress_end_color:圆环渐变结束颜色;

progress_width:环的宽度;

radius:圆环半径;

  <declare-styleable name="MdStyleProgress">
        <attr name="progress_start_color" format="color"/>
        <attr name="progress_end_color" format="color"/>
        <attr name="progress_width" format="dimension"/>
        <attr name="radius" format="dimension"/>
    </declare-styleable>

开始自定义View:

首先在自定义View中获取定义的属性值:

//获取自定义属性
        TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.MdStyleProgress);
        int indexCount = typedArray.getIndexCount();
        for (int i = 0; i < indexCount; i++) {
            int attr = typedArray.getIndex(i);
            switch (attr) {
                case R.styleable.MdStyleProgress_progress_start_color:
                    mProgressStartColor = typedArray.getColor(attr, PROGRESS_START_COLOR);
                    break;
                    case R.styleable.MdStyleProgress_progress_end_color:
                    mProgressEndColor = typedArray.getColor(attr, PROGRESS_END_COLOR);
                    break;
                case R.styleable.MdStyleProgress_progress_width:
                    mProgressWidth = (int) typedArray.getDimension(attr, mProgressWidth);
                    break;
                case R.styleable.MdStyleProgress_radius:
                    mRadius = (int) typedArray.getDimension(attr, mRadius);
                    break;
            }
        }
        //回收TypedArray对象
        typedArray.recycle();

根据获取到的值配置画笔:

private void setPaint() {
        progressPaint = new Paint();
        progressPaint.setAntiAlias(true);
        progressPaint.setDither(true);
        progressPaint.setStyle(Paint.Style.STROKE);
        progressPaint.setStrokeWidth(mProgressWidth);
        progressPaint.setStrokeCap(Paint.Cap.ROUND);

        //设置渐变色
        LinearGradient linearGradient = new LinearGradient(0, 0, mRadius*2, mRadius*2, mProgressStartColor, mProgressEndColor, Shader.TileMode.CLAMP);
        progressPaint.setShader(linearGradient);
    }

将画笔设置抗锯齿,并且设置线冒为圆形,而后通过LinearGradient为画笔设置了从左上到右下的渐变色,色值是从自定义属性中取得的值;

确定圆环的范围:

mRectF = new RectF(mProgressWidth / 2, mProgressWidth / 2, (mRadius) * 2 + mProgressWidth / 2, (mRadius) * 2 + mProgressWidth / 2);

我们以整个控件的中心点为中心,mProgressWidth / 2开始到(mRadius) * 2 + mProgressWidth / 2为整个圆的显示区域;实际圆所占用的区域长宽便是(mRadius) * 2;

然后我们重写onMeasure方法,测量view的宽高,并处理padding:

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthSize = getPaddingLeft() + mProgressWidth + mRadius * 2 + getPaddingRight();
        widthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY);

        int heightSize = getPaddingTop() + mProgressWidth + mRadius * 2 + getPaddingBottom();
        heightMeasureSpec = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY);
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

代码中我们把上下左右的padding距离分别加在了view的宽高中;

最后重写onDraw,画出圆环:

 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        canvas.save();
        canvas.translate(getPaddingLeft(), getPaddingTop());
        canvas.drawArc(mRectF, startAngle, sweepAngle, false, progressPaint);
        canvas.restore();
    }

代码中我们留出了上边和左边的padding距离,而后通过drawArc画出扇形,至此扇形圆环已经画出来了;

让圆环动起来:

圆环的动画效果是:

1.圆环逆时针逐渐伸出,伸出到40%的长度后停止(从0度逆时针伸出);

2.圆环开始旋转,直到头部到达开始的位置(0度);

3.圆环尾部收缩,直到圆环长度收缩完毕;

4.进入下一次循环;

我们采用属性动画的方式让圆环动起来,分析动画:

圆环需要旋转一圈,并且最后需要收缩尾部,所以我们定义动画的长度为360+360*0.4=504;

设置动画次数为无限重复;

设置重复方式为从头开始;

设置插值器为线性插值器;

设置动画时间为1200ms;

定义动画:

private void initAnimator() {
        mAnimator = new ValueAnimator();
        mAnimator = ValueAnimator.ofInt(0, 504);  //504是360 (转一圈)+ 360*0.4(收缩尾巴)
        mAnimator.setRepeatCount(ValueAnimator.INFINITE);
        mAnimator.setDuration(1200);
        mAnimator.setRepeatMode(ValueAnimator.RESTART);
        mAnimator.setInterpolator(new LinearInterpolator());
        // 设置动画的回调
        mAnimatorUpdateListener = new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int value = (int) animation.getAnimatedValue();
                //开始弧线逐渐增长
                if (value < 144) {
                    startAngle = INITI_ALANGLE - value;
                    //ui备注说为了动画平滑
                    if (value < 5) {
                        sweepAngle = 5;
                    } else {
                        sweepAngle = value;
                    }
                }
                //到了40%长度
                else if (value >= 144 && value < 360) {
                    sweepAngle = 144;
                    startAngle = INITI_ALANGLE - value;
                }
                //收缩尾巴
                else if (value > 360) {
                    if (sweepAngle > 5) {
                        sweepAngle = 144 - (value - 360);
                    }
                }
                Log.d("MDProgress", "start:" + startAngle + " value:" + value);
                invalidate();
            }
        };
    }

我们通过设置ValueAnimator.AnimatorUpdateListener监听动画的每一帧回调,从而在这里来改变画布:

在动画值小于360*0.4=144时,我们让sweepAngle = value;,让扇形圆环慢慢增长;

到了144时,我们改变扇形圆环的开始角度startAngle ,让圆环旋转起来;

转到360度时,再次改变sweepAngle,让扇形圆环 逐渐缩短,完成整个动画过程;

对外暴露方法,便于调用:

    public void startAnimator() {
        mAnimator.addUpdateListener(mAnimatorUpdateListener);
        mAnimator.start();
    }

    public void stopAnimator() {
        mAnimator.removeAllUpdateListeners();
        mAnimator.end();
    }

使用方式:

xml中:

设置自定义属性命名空间:

xmlns:app="http://schemas.android.com/apk/res-auto"

定义布局:

 <view.MdStyleProgress
                android:id="@+id/progress"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_below="@id/tv_content"
                android:layout_marginTop="48dp"
                android:layout_centerHorizontal="true"
                app:progress_width="3dp"
                app:radius="15dp" />

Activity中开始动画:

progress.startAnimator();

源码地址

发布了11 篇原创文章 · 获赞 1 · 访问量 3912

猜你喜欢

转载自blog.csdn.net/qin19930929/article/details/82786621