美文网首页
2019-01-08

2019-01-08

作者: 遥望星空forward | 来源:发表于2019-01-08 23:34 被阅读0次

    Android自定义view实现圆环倒计时

    好记性不如烂笔头

    先上效果图,如下所示:

    主要实现了倒计时的功能,主要功能包括绘制了内部圆和外部圆,可以让外部圆作为背景,如果不需要的话可以设置外部圆的颜色为透明,倒计时颜色可设置为渐变色,触摸可改变进度,设置顺时针或逆时针方向等功能,下面就详细介绍下这个倒计时的控件的绘制,其它功能可以直奔最下面查看完整的实现,里面的代码都已加详细的注释,相信你能一目了然。

    • view的绘制包括外部圆和内部圆:
    private void init() {
        innerCircle.setStrokeWidth(foregroundProgressWidth); //圆环宽度
        innerCircle.setAntiAlias(true);                //抗锯齿
        innerCircle.setStyle(Paint.Style.STROKE);   //设置图形为空心
        innerCircle.setColor(foregroundProgressColor); //设置颜色
    
        outerCircle.setStrokeWidth(backgroundProgressWidth);
        outerCircle.setAntiAlias(true);
        outerCircle.setColor(backgroundProgressColor);
        outerCircle.setStyle(Paint.Style.STROKE);
    }
    
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        width = getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec);
        height = getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec);
        centerPoint = Math.min(width, height);
        int min = Math.min(width, height);
        setMeasuredDimension(min, min);
        setRadiusRect();
    }
    
    /**
     * 测量,主要是计算获得内外转圈的半径,中心点,位置
     */
    private void setRadiusRect() {
        centerPoint = Math.min(width, height) / 2;
        subtractingValue = (backgroundProgressWidth > foregroundProgressWidth) ? backgroundProgressWidth : foregroundProgressWidth;
        float newSeekWidth = subtractingValue / 2;
        drawRadius = Math.min((width - subtractingValue) / 2, (height - subtractingValue) / 2);
        drawOuterRadius = Math.min((width - newSeekWidth), (height - newSeekWidth));
        rectF.set(subtractingValue / 2, subtractingValue / 2, drawOuterRadius, drawOuterRadius);
    }
    
    @Override
    protected void onDraw(Canvas canvas) {
        //画布旋转90度,根据view的中心点进行旋转,使倒计时的开始位置位于view的顶部
        canvas.rotate(-90, width / 2, height / 2);
        canvas.drawCircle(centerPoint, centerPoint, drawRadius, outerCircle);
        //设置颜色渐变
        SweepGradient sweepGradient = new SweepGradient(centerPoint, centerPoint, doughnutColors, null);
        innerCircle.setShader(sweepGradient);
        //指定矩阵旋转,处理设置两端为圆角时分开后的颜色问题
        Matrix matrix = new Matrix();
        matrix.postRotate(2);
        sweepGradient.setLocalMatrix(matrix);
        canvas.drawArc(rectF, startAngle, sweepAngle, false, innerCircle);
        super.onDraw(canvas);
    }
    
    • 设置可触摸改变进度以及触摸改变进度的主要实现:
    public void enabledTouch(boolean enabled) {
        this.isTouchEnabled = enabled;
        invalidate();
    }
    
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (isTouchEnabled) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    if (onProgressbarChangeListener != null) {
                        onProgressbarChangeListener.onStartTracking(this);
                    }
                    checkForCorrect(event.getX(), event.getY());
                    break;
                case MotionEvent.ACTION_MOVE:
                    if (moveCorrect) {
                        justMove(event.getX(), event.getY());
                    }
                    upgradeProgress(this.progress, true);
    
                    break;
                case MotionEvent.ACTION_UP:
                    if (onProgressbarChangeListener != null) {
                        onProgressbarChangeListener.onStopTracking(this);
                    }
                    moveCorrect = false;
                    break;
            }
            return true;
        }
        return false;
    }
    
    • 至于其它详细过程可以查看完整代码,代码中已经加了详细注释
    package com.beauty.circleprogressbarproject;
    
    import android.animation.ObjectAnimator;
    import android.content.Context;
    import android.content.res.TypedArray;
    import android.graphics.Canvas;
    import android.graphics.Color;
    import android.graphics.Matrix;
    import android.graphics.Paint;
    import android.graphics.RectF;
    import android.graphics.SweepGradient;
    import android.util.AttributeSet;
    import android.view.MotionEvent;
    import android.view.View;
    import android.view.animation.DecelerateInterpolator;
    
    /**
     * Android自定义view实现圆环倒计时
     */
    public class CircleProgressbar extends View {
    
        /*绘制内部圆*/
        private Paint innerCircle = new Paint();
        /*绘制外部圆*/
        private Paint outerCircle = new Paint();
        private RectF rectF = new RectF();
        /*view的大小*/
        private int width, height;
        /*转圈背景和前景宽度*/
        private float backgroundProgressWidth;
        private float foregroundProgressWidth;
        /*转圈背景和前景默认宽度*/
        private final int DEFAULT_FOREGROUND_PROGRESS_WIDTH = 10;
        private final int DEFAULT_BACKGROUND_CIRCLE_WIDTH = 10;
        /*转圈背景和前景默认颜色值*/
        private int DEFAULT_BACKGROUND_PROGRESS_COLOR = Color.GRAY;
        private int DEFAULT_FOREGROUND_PROGRESS_COLOR = Color.RED;
        /*转圈背景和前景颜色值*/
        private int backgroundProgressColor;
        private int foregroundProgressColor;
        /*触摸时是否可移动*/
        private boolean moveCorrect;
        /*顺时针方向*/
        private boolean clockWise;
        /*进度*/
        private float progress = 0;
        private float maxProgress = 100;
        /*起始角度位置*/
        private int startAngle = 0;
        /*旋转角度位置*/
        private float sweepAngle = 0;
        /*绘制圆的中心点*/
        private int centerPoint;
        /*获取转圈背景和前景默认宽度的最大值,为了计算绘制圆的位置*/
        private float subtractingValue;
        /*计算绘制外部圆的半径*/
        private float drawRadius;
        /*计算内部圆的半径*/
        private float drawOuterRadius;
    
        /*是否可触摸改变进度*/
        private boolean isTouchEnabled = false;
        private boolean roundedCorner;
    
        /*圆环进度颜色,可设置渐变*/
        private int[] doughnutColors;
        /*圆环进度监听*/
        private OnProgressbarChangeListener onProgressbarChangeListener;
    
        public CircleProgressbar(Context context) {
            super(context);
            init();
        }
    
        public CircleProgressbar(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public CircleProgressbar(Context context, AttributeSet attrs, int i) {
            super(context, attrs, i);
            TypedArray typeArray = context.obtainStyledAttributes(attrs, R.styleable.CircleProgressbar, 0, 0);
            backgroundProgressWidth = typeArray.getDimension(R.styleable.CircleProgressbar_cpb_backgroundProgressWidth, DEFAULT_BACKGROUND_CIRCLE_WIDTH);
            foregroundProgressWidth = typeArray.getDimension(R.styleable.CircleProgressbar_cpb_foregroundProgressWidth, DEFAULT_FOREGROUND_PROGRESS_WIDTH);
            backgroundProgressColor = typeArray.getColor(R.styleable.CircleProgressbar_cpb_backgroundProgressColor, DEFAULT_BACKGROUND_PROGRESS_COLOR);
            foregroundProgressColor = typeArray.getColor(R.styleable.CircleProgressbar_cpb_foregroundProgressColor, DEFAULT_FOREGROUND_PROGRESS_COLOR);
            this.progress = typeArray.getFloat(R.styleable.CircleProgressbar_cpb_progress, progress);
            this.maxProgress = typeArray.getFloat(R.styleable.CircleProgressbar_cpb_maxProgress, maxProgress);
            this.roundedCorner = typeArray.getBoolean(R.styleable.CircleProgressbar_cpb_roundedCorner, false);
            this.clockWise = typeArray.getBoolean(R.styleable.CircleProgressbar_cpb_clockwise, false);
            this.isTouchEnabled = typeArray.getBoolean(R.styleable.CircleProgressbar_cpb_touchEnabled, false);
            typeArray.recycle();
            // 圆环默认颜色为主题色,可设置为颜色渐变
            doughnutColors = new int[]{foregroundProgressColor, foregroundProgressColor};
            init();
            if (roundedCorner) {
                setRoundedCorner(roundedCorner);
            }
            if (this.progress > 0) {
                setProgress(this.progress);
            }
    
            if (clockWise) {
                setClockwise(clockWise);
            }
    
            if (isTouchEnabled) {
                enabledTouch(isTouchEnabled);
            }
        }
    
        private void init() {
            innerCircle.setStrokeWidth(foregroundProgressWidth); //圆环宽度
            innerCircle.setAntiAlias(true);                //抗锯齿
            innerCircle.setStyle(Paint.Style.STROKE);   //设置图形为空心
            innerCircle.setColor(foregroundProgressColor); //设置颜色
    
            outerCircle.setStrokeWidth(backgroundProgressWidth);
            outerCircle.setAntiAlias(true);
            outerCircle.setColor(backgroundProgressColor);
            outerCircle.setStyle(Paint.Style.STROKE);
        }
    
        /**
         * 倒计时转圈颜色类型
         *
         * @param selectColor 根据各种条件,可变换倒计时圆圈的渐变颜色
         */
        public void setDoughnutColors(int selectColor) {
            if (selectColor == 1) {
                doughnutColors = new int[]{Color.parseColor("#60B4FD"), Color.parseColor("#5C72F2")};
            } else if (selectColor == 2) {
                doughnutColors = new int[]{Color.parseColor("#FCC95C"), Color.parseColor("#FD80A8")};
            } else {
                doughnutColors = new int[]{Color.parseColor("#5DC072"), Color.parseColor("#5DC072")};
            }
            invalidate();
        }
    
        //    @Override
        //    protected void onDraw(Canvas canvas) {
        //        canvas.drawCircle(centerPoint, centerPoint, drawRadius, outerCircle);
        //        canvas.drawArc(rectF, startAngle, sweepAngle, false, innerCircle);
        //        super.onDraw(canvas);
        //    }
    
        @Override
        protected void onDraw(Canvas canvas) {
            //画布旋转90度,根据view的中心点进行旋转,使倒计时的开始位置位于view的顶部
            canvas.rotate(-90, width / 2, height / 2);
            canvas.drawCircle(centerPoint, centerPoint, drawRadius, outerCircle);
            //设置颜色渐变
            SweepGradient sweepGradient = new SweepGradient(centerPoint, centerPoint, doughnutColors, null);
            innerCircle.setShader(sweepGradient);
            //指定矩阵旋转,处理设置两端为圆角时分开后的颜色问题
            Matrix matrix = new Matrix();
            matrix.postRotate(2);
            sweepGradient.setLocalMatrix(matrix);
            canvas.drawArc(rectF, startAngle, sweepAngle, false, innerCircle);
            super.onDraw(canvas);
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            width = getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec);
            height = getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec);
            centerPoint = Math.min(width, height);
            int min = Math.min(width, height);
            setMeasuredDimension(min, min);
            setRadiusRect();
        }
    
        /**
         * 测量,主要是计算获得内外转圈的半径,中心点,位置
         */
        private void setRadiusRect() {
            centerPoint = Math.min(width, height) / 2;
            subtractingValue = (backgroundProgressWidth > foregroundProgressWidth) ? backgroundProgressWidth : foregroundProgressWidth;
            float newSeekWidth = subtractingValue / 2;
            drawRadius = Math.min((width - subtractingValue) / 2, (height - subtractingValue) / 2);
            drawOuterRadius = Math.min((width - newSeekWidth), (height - newSeekWidth));
            rectF.set(subtractingValue / 2, subtractingValue / 2, drawOuterRadius, drawOuterRadius);
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            if (isTouchEnabled) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        if (onProgressbarChangeListener != null) {
                            onProgressbarChangeListener.onStartTracking(this);
                        }
                        checkForCorrect(event.getX(), event.getY());
                        break;
                    case MotionEvent.ACTION_MOVE:
                        if (moveCorrect) {
                            justMove(event.getX(), event.getY());
                        }
                        upgradeProgress(this.progress, true);
    
                        break;
                    case MotionEvent.ACTION_UP:
                        if (onProgressbarChangeListener != null) {
                            onProgressbarChangeListener.onStopTracking(this);
                        }
                        moveCorrect = false;
                        break;
                }
                return true;
            }
            return false;
        }
    
        /**
         * 更新进度
         *
         * @param progress
         * @param b
         */
        private void upgradeProgress(float progress, boolean b) {
            if (progress < 0) {
                if (b) {
                    progress = -progress;
                } else {
                    progress = 0;
                }
            }
            this.progress = (progress <= maxProgress) ? progress : maxProgress;
            sweepAngle = (360 * progress / maxProgress);
    
            if (this.clockWise) {
                if (sweepAngle > 0) {
                    sweepAngle = -sweepAngle;
                }
            }
            if (onProgressbarChangeListener != null) {
                onProgressbarChangeListener.onProgressChanged(this, progress, b);
            }
            invalidate();
        }
    
        /**
         * 触摸时根据坐标获取进度
         *
         * @param x
         * @param y
         */
        private void justMove(float x, float y) {
            if (clockWise) {
                float degree = (float) Math.toDegrees(Math.atan2(x - centerPoint, centerPoint - y));
                if (degree > 0) {
                    degree -= 360;
                }
                sweepAngle = degree;
            } else {
                float degree = (float) Math.toDegrees(Math.atan2(x - centerPoint, centerPoint - y));
                if (degree < 0) {
                    degree += 360;
                }
    
                sweepAngle = degree;
            }
            progress = (sweepAngle * maxProgress / 360);
            invalidate();
        }
    
        /**
         * 设置可触摸点击时检查坐标时是否在规定的范围内,是则获取角度从而计算得到进度值
         *
         * @param x
         * @param y
         */
        private void checkForCorrect(float x, float y) {
            float distance = (float) Math.sqrt(Math.pow((x - centerPoint), 2) + Math.pow((y - centerPoint), 2));
            if (distance < drawOuterRadius / 2 + subtractingValue && distance > drawOuterRadius / 2 - subtractingValue * 2) {
                moveCorrect = true;
                if (clockWise) {
                    float degree = (float) Math.toDegrees(Math.atan2(x - centerPoint, centerPoint - y));
    
                    if (degree > 0) {
                        degree -= 360;
                    }
                    sweepAngle = degree;
                } else {
                    float degree = (float) Math.toDegrees(Math.atan2(x - centerPoint, centerPoint - y));
                    if (degree < 0) {
                        degree += 360;
                    }
    
                    sweepAngle = degree;
                }
                progress = (sweepAngle * maxProgress / 360);
                invalidate();
            }
        }
    
        /**
         * 设置是顺时针还是逆时针
         *
         * @param clockwise
         */
        public void setClockwise(boolean clockwise) {
            this.clockWise = clockwise;
            if (this.clockWise) {
                if (sweepAngle > 0) {
                    sweepAngle = -sweepAngle;
                }
            }
            invalidate();
        }
    
        /**
         * 设置转圈背景的宽度
         *
         * @param width
         */
        public void setBackgroundProgressWidth(int width) {
            this.backgroundProgressWidth = width;
            outerCircle.setStrokeWidth(backgroundProgressWidth);
            requestLayout();
            invalidate();
        }
    
        /**
         * 设置转圈前景的宽度
         *
         * @param width
         */
        public void setForegroundProgressWidth(int width) {
            this.foregroundProgressWidth = width;
            innerCircle.setStrokeWidth(foregroundProgressWidth);
            requestLayout();
            invalidate();
        }
    
        /**
         * 设置转圈背景颜色
         *
         * @param color
         */
        public void setBackgroundProgressColor(int color) {
            this.backgroundProgressColor = color;
            outerCircle.setColor(color);
            requestLayout();
            invalidate();
        }
    
        /**
         * 设置转圈前景颜色
         *
         * @param color
         */
        public void setForegroundProgressColor(int color) {
            this.foregroundProgressColor = color;
            innerCircle.setColor(color);
            requestLayout();
            invalidate();
        }
    
        /**
         * 设置进度的最大值
         *
         * @param maxProgress
         */
        public void setMaxProgress(float maxProgress) {
            this.maxProgress = maxProgress;
        }
    
        /**
         * 返回进度的最大值
         */
        public float getMaxProgress() {
            return maxProgress;
        }
    
        /**
         * 获取当前进度
         *
         * @return
         */
        public float getProgress() {
            return progress;
        }
    
        /**
         * 设置进度
         *
         * @param progress
         */
        public void setProgress(float progress) {
            upgradeProgress(progress, false);
        }
    
        /**
         * 设置进度,可以改变进度的渐变颜色
         *
         * @param progress
         * @param selectColor
         */
        public void setProgress(float progress, int selectColor) {
            setDoughnutColors(selectColor);
            upgradeProgress(progress, false);
        }
    
        /**
         * 设置进度的动画和动画时间
         *
         * @param progress
         * @param duration
         */
        public void setProgressWithAnimation(float progress, int duration) {
            ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(this, "progress", progress);
            objectAnimator.setDuration(duration);
            objectAnimator.setInterpolator(new DecelerateInterpolator());
            objectAnimator.start();
        }
    
        /**
         * 设置可触摸改变进度
         *
         * @param enabled
         */
        public void enabledTouch(boolean enabled) {
            this.isTouchEnabled = enabled;
            invalidate();
        }
    
        /**
         * 设置两端为圆角还是矩形
         *
         * @param roundedCorner
         */
        public void setRoundedCorner(boolean roundedCorner) {
            if (roundedCorner) {
                //线帽,即画的两端是否带有圆角
                innerCircle.setStrokeCap(Paint.Cap.ROUND);
                outerCircle.setStrokeCap(Paint.Cap.ROUND);
            } else {
                // 线帽,即画的两端是否带有矩形
                innerCircle.setStrokeCap(Paint.Cap.SQUARE);
                outerCircle.setStrokeCap(Paint.Cap.SQUARE);
            }
            invalidate();
        }
    
        /**
         * 进度监听
         *
         * @param onProgressbarChangeListener
         */
        public void setOnProgressbarChangeListener(OnProgressbarChangeListener onProgressbarChangeListener) {
            this.onProgressbarChangeListener = onProgressbarChangeListener;
        }
    
        public interface OnProgressbarChangeListener {
            void onProgressChanged(CircleProgressbar circleSeekbar, float progress, boolean fromUser);
            void onStartTracking(CircleProgressbar circleSeekbar);
            void onStopTracking(CircleProgressbar circleSeekbar);
        }
    }
    
    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <!--圆圈进度-->
        <declare-styleable name="CircleProgressbar">
            <attr name="cpb_progress" format="float"/>
            <attr name="cpb_maxProgress" format="float"/>
            <attr name="cpb_backgroundProgressWidth" format="dimension"/>
            <attr name="cpb_foregroundProgressWidth" format="dimension"/>
            <attr name="cpb_backgroundProgressColor" format="color"/>
            <attr name="cpb_foregroundProgressColor" format="color"/>
            <attr name="cpb_roundedCorner" format="boolean"/>
            <attr name="cpb_touchEnabled" format="boolean"/>
            <attr name="cpb_clockwise" format="boolean"/>
        </declare-styleable>
    
    </resources>
    

    如果需要查看效果,可下载运行Github传送门

    相关文章

      网友评论

          本文标题:2019-01-08

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