美文网首页
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