美文网首页Android开发Android开发Android开发经验谈
Android自定义View之自定义加载进度条(二)

Android自定义View之自定义加载进度条(二)

作者: AntDream | 来源:发表于2018-09-24 19:17 被阅读48次

    本文首发于公众号“AntDream”,欢迎微信搜索“AntDream”或扫描文章底部二维码关注,和我一起每天进步一点点

    自定义加载进度条

    上次我们已经把实线和虚线都绘制好了,这次我们就主要来解决更新的问题:

    • 怎么随着时间的推移逐渐地绘制进度条
    • 怎么在绘制的过程中加速进度条的绘制

    首先我们来解决第一个问题,也就是随着时间更新我们的进度。

    其实原理也很简单,上次我们已经提到用Canvas的drawArc方法来绘制进度条,只需要改变绘制的角度就可以。所以我们可以利用时间差值器,在一段时间内让角度从0度变化到360度,也就是完整的一圈了。

    更新进度

    1. 首先我们要设置一个范围,这个范围初始值为0,表示初始状态;最大值为100,表示全部绘制完成
    float mCurrentValue = 0.0F;
    float mMaxValue = 100.0F;
    
    1. 有了范围以后,我们就可以计算需要绘制的角度了
    float degrees = 360.0F / this.mMaxValue * this.mCurrentValue;
    
    1. 然后我们就可以绘制我们的进度条啦
    canvas.drawArc(this.mCircleBounds, (float) this.mStartAngle, degrees, false, this.mBarPaint);
    

    这里的mStartAngle表示起始角度,因为我们的进度条一般都是从最上面开始,所以这里是270度

    1. 这样子的话我们就只需要改变我们的mCurrentValue值就行了。
    //线性时间差值器
    TimeInterpolator mInterpolator = new LinearInterpolator();
    

    随着时间计算mCurrentValue的值

    float t = (float)((double)(System.currentTimeMillis() - this.mAnimationStartTime) / circleView.mAnimationDuration);
    t = t > 1.0F ? 1.0F : t;
    float interpolatedRatio = this.mInterpolator.getInterpolation(t);
    //这里的mValueTo表示我们的最大的范围,也就是100,而mValueFrom用于暂存当前的进度
    circleView.mCurrentValue = circleView.mValueFrom + (circleView.mValueTo - circleView.mValueFrom) * interpolatedRatio;
    

    通过以上的几步我们基本就解决了随着时间刷新我们的进度条的问题。

    需要注意的是更新我们的进度条最好要通过Handler来更新,因为外面调用者并不一定是在UI线程发起更新我们UI的调用的。

    public class AnimationHandler extends Handler{
    
        private long mAnimationStartTime;
        private TimeInterpolator mInterpolator = new LinearInterpolator();
        
        //这里要用弱引用,防止内存泄漏
        private final WeakReference<DashCircleProgressView> mCircleViewWeakReference;
        
        public AnimationHandler(DashCircleProgressView dashCircleProgressView) {
            super(dashCircleProgressView.getContext().getMainLooper());
            this.mCircleViewWeakReference = new WeakReference(dashCircleProgressView);
        }
        
        public void setValueInterpolator(TimeInterpolator interpolator) {
            this.mInterpolator = interpolator;
        }
        
         @Override
        public void handleMessage(Message msg) {
        
            ...
            case ANIMATING:
                switch (msgType) {
                    case SET_VALUE:
                        this.setValue(msg, circleProgressView);
                        break;
                    case SET_VALUE_ANIMATED:
                        break;
                    case TICK:
                        if (this.calcNextAnimationValue(circleProgressView)) {
                            circleProgressView.mAnimationState = AnimationState.IDLE;
                            circleProgressView.mCurrentValue = circleProgressView.mValueTo;
                        }
    
                        this.sendEmptyMessageDelayed(AnimationMsg.TICK.ordinal(), (long)circleProgressView.mFrameDelayMillis - (SystemClock.uptimeMillis() - this.mFrameStartTime));
                        //刷新
                        circleProgressView.invalidate();
                        break;
                    default:
                        break;
                }
                break;
        }
        
        //判断是否还能更新,并根据差值器更新mCurrentValue值
        private boolean calcNextAnimationValue(DashCircleProgressView circleView) {
            float t = (float)((double)(System.currentTimeMillis() - this.mAnimationStartTime) / circleView.mAnimationDuration);
            t = t > 1.0F ? 1.0F : t;
            float interpolatedRatio = this.mInterpolator.getInterpolation(t);
            circleView.mCurrentValue = circleView.mValueFrom + (circleView.mValueTo - circleView.mValueFrom) * interpolatedRatio;
            return t >= 1.0F;
        }
        
        ...
    }
    

    需要注意的是,我们对于UI的引用采用了弱引用,为的是防止内存泄漏。具体的原因可以查看文章:Android内存优化之内存泄漏

    最后我们还需要对外提供初始设置和启动更新进度的接口

    //valueTo表示更新的目标值;animationDuration表示更新的时间
    public void setValueAnimated(float _valueTo, long _animationDuration) {
        this.setValueAnimated(this.mCurrentValue, _valueTo, _animationDuration);
    }
    
    //最终内部是通过Handler来更新我们的UI
    public void setValueAnimated(float _valueFrom, float _valueTo, long _animationDuration) {
        this.mAnimationDuration = (double) _animationDuration;
        Message msg = new Message();
        msg.what = AnimationMsg.SET_VALUE_ANIMATED.ordinal();
        msg.obj = new float[]{_valueFrom, _valueTo};
        this.mAnimationHandler.sendMessage(msg);
    }
    
    //设置进度
    public void setValue(float _value) {
        Message msg = new Message();
        msg.what = AnimationMsg.SET_VALUE.ordinal();
        msg.obj = new float[]{_value, _value};
        this.mAnimationHandler.sendMessage(msg);
    }
    

    如何加快我们的进度条

    其实上面提供的接口已经给出了答案,我们可以通过缩短更新的时间来达到加快动画的效果。比如原来是设置10秒钟加载完进度,进度加载到10%的时候,发现需要加快进度,这个时候我们就可以设置进度加载的时间为2秒钟,这样看起来就加快了。

    //通过这2个接口的设置,我们就将剩下的进度在2秒钟内加载完毕
    circleView.setValue(circleView.getCurrentValue());
    circleView.setValueAnimated(circleView.getCurrentValue(), 100, 2000);
    

    总结

    通过2篇文章,简单梳理了自定义一个加载进度条的过程。其实自定义View最重要的是理清思路,然后一步步去实现,最后才是慢慢地优化和尝试复杂的自定义View。

    这里面涉及到的知识点主要是:

    • View的绘制原理
    • 消息机制
    • 内存泄漏
    • 时间差值器的使用
    • 基本的Paint和Canvas的使用

    希望能帮到大家


                           欢迎关注我的微信公众号,和我一起每天进步一点点!
    
    AntDream

    相关文章

      网友评论

        本文标题:Android自定义View之自定义加载进度条(二)

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