Android_自定义倒计时View


2016年08月01日新的一周开始了,一篇自定义倒计时View开启了这周的篇章…


国际惯例,效果图如下;
这里写图片描述

带阴影带指引点的倒计时View,不要被这下过吓到,分析一下,难点其实就是那个白色小圆圈的位置,其他的都是我们之前自定义view中用到的知识,甚至还没有第一篇自定义button逻辑复杂,


看下我们自己实现的效果,和UI做个简单的对比——

这里写图片描述
这里写图片描述
这里写图片描述

简单的对比一下,是不是有几分相似,哈哈哈哈–下面我们就手把手来实现这个倒计时的View,而且我们的进度还是渐变的,这个也就是paint的一个属性很简单的


超一麻袋,来个GIF看下动起来的效果

这里写图片描述


老规矩,分析需求,实现步骤也就那么回事,里面的坑我会用大字标出来

  1. 自定义属性分析
    <declare-styleable name="ATProgressView">
        <attr name="outer_layer_solide_color" format="color"/>
        <attr name="outer_layer_stroke_color" format="color"/>
        <attr name="outer_layer_stroke_width" format="dimension"/>
        <attr name="midlle_layer_bg_color" format="color"/>
        <attr name="midlle_layer_progress_color" format="color"/>
        <attr name="midlle_layer_progress_width" format="dimension"/>
        <attr name="midlle_layer_small_circle_solide_color" format="color"/>
        <attr name="midlle_layer_small_circle_stroke_color" format="color"/>
        <attr name="midlle_layer_small_circle_size" format="dimension"/>
        <attr name="midlle_layer_small_circle_stroke_width" format="dimension"/>
        <attr name="shadow_layer_color" format="color"/>
        <attr name="shadow_layer_inner_color" format="color"/>
        <attr name="inner_text_size" format="dimension"/>
        <attr name="inner_text_color" format="color"/>
    </declare-styleable>

从名字和效果图分析我至少需要三层的的颜色,加上文字的颜色,描边的颜色等,
看起来很多属性,其实这些都是方便我们配置的,不要嫌麻烦,

  1. 获取自定义属性,

    这都是要写吐的代码了,这里就不相信说了,直接粘贴出来,给大家复习下

    public ATProgressView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ATProgressView, defStyleAttr, R.style.def_progress_style);
        int indexCount = typedArray.getIndexCount();
        for (int i = 0; i < indexCount; i++) {
            int attr = typedArray.getIndex(i);
            switch (attr) {
                case R.styleable.ATProgressView_outer_layer_solide_color:
                    outLayerSolideColor = typedArray.getColor(attr, Color.BLACK);
                    break;
                case R.styleable.ATProgressView_outer_layer_stroke_color:
                    outLayerStrokeColor = typedArray.getColor(attr, Color.BLACK);
                    break;
              //省略其他属性......套路都一样
        }
        typedArray.recycle();
        initData();
    }
  1. 确定View的尺寸,
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        int widthSize;
        int heightSize;

        if (widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED) {
            widthSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEF_VIEW_SIZE, getResources().getDisplayMetrics());
            widthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY);
        }

        if (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED) {
            heightSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEF_VIEW_SIZE, getResources().getDisplayMetrics());
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY);
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

4.根据效果图,提供各个图层之间的比例

 private static final float OUTER_LAYER_LARGE_SCALE = 58 / 62.F;
    private static final float MIDDLE_LAYER_LARGE_SCALE = 51 / 62.F;
    private static final float SHADOW_LAYER_LARGE_SCALE = 40 / 62.F;
    private static final float SMALL_CIRCLE_DEGREE_OFFSET = 0.7F;

    private static final int DEF_VIEW_SIZE = 250;

计算比例的时候尽量安装UI给px 尺寸进行计算,如果UI没标注,用Mac自带的图像打开用鼠标大概的测量一下,这样你最后写出的 view 不会因为不同的尺寸而不成比例,**

注意尽可能的用比例来做,不要用偏移量

**

5.最后一步,就是绘制,

分析下我们这个view我们需要绘制的东西有三层,底层,进度层,文字层,
其中进度层还有一个烦人的小圆圈.


**Android的知识点涉及,绘制圆,绘制扇形,绘制阴影,Java基础知识 倒计时的实现,Android属性动画知识,还有就是 

初中数学Sin和Cos的知识以及球圆上任一点的坐标和坐标系象限的知识

**


我们都是有精液的Android开发,以上的知识基本都能搞定,困扰的我的就是那个初中数学的知识,悄悄的告诉你们我也谷歌了这些公式,


ok,我们开始一点点绘制

  • 绘制底层和阴影
  /**
     * 绘制外层的大圆圈和描边
     *
     * @param canvas 画布
     */
    private void drawOuterLayerCircles(Canvas canvas) {
        // 采用阴影绘制
        outLayerSolidePaint.setShadowLayer(10, 2, 2, shadowLayerColor);
        //设置阴影图层
        setLayerType(LAYER_TYPE_SOFTWARE, null);
        canvas.drawCircle(circleCenterPoint.x, circleCenterPoint.y, mOuterLayerSolideSize / 2, outLayerSolidePaint);
    }
  • 绘制中间层 的进度和小圆点

,代码量略大,数学公式来了,高能预警

    /**
     * 绘制中间进度的背景和进度
     *
     * @param canvas 画布
     */
    private void drawMiddleProgressLayer(Canvas canvas) {

        float ddx = (viewSize - mMiddleLayerSize) / 2;
        //外切圆的坐标计算,绘制的扇形在矩形内的外切圆,注意画笔的宽度
        RectF oval = new RectF(ddx + midlleLayerProgressWidth, ddx + midlleLayerProgressWidth, viewSize - ddx - midlleLayerProgressWidth, viewSize - ddx - midlleLayerProgressWidth);

        canvas.drawCircle(circleCenterPoint.x, circleCenterPoint.y, mMiddleLayerSize / 2 - midlleLayerProgressWidth, progressBgPaint);
        // 注意扫过的扇形的范围(进度)要和绘制的小圆点保持一致,所以我们需要从-90度开始
        canvas.drawArc(oval, -90, 360 * drgeePercent, false, progressPaint);

        // 由于前面绘制了一个小圆,所以我们弧度的角度不能用于计算圆的坐标,我们需要大概的加上那么一两度来计算,
        // 由于android坐标系的问题以及角度在不同象限内的问题,所以我们需要计算几种情况
        // 0-90,90-180 ,180-270,270-360
        float animDegree = 360 * drgeePercent + SMALL_CIRCLE_DEGREE_OFFSET;
        float xiaoyuanDegree;
        float xiaoYuanX = 0, xiaoYuanY = 0;
        int tempD = (int) animDegree;
        if (tempD >= 0 && tempD < 90) {
            // 第一象限内,sin和cons正常
            xiaoyuanDegree = animDegree;
            float hudu = (float) Math.abs(Math.PI * xiaoyuanDegree / 180);
            float sinAX = (float) Math.abs(Math.sin(hudu) * (mMiddleLayerSize / 2 + midlleLayerProgressWidth / 4));
            float cosAY = (float) Math.abs(Math.cos(hudu) * (mMiddleLayerSize / 2 + midlleLayerProgressWidth / 4));

            xiaoYuanX = (viewSize - 2 * midlleLayerProgressWidth - 2 * sinAX) / 2 + 2 * sinAX;
            xiaoYuanY = viewSize / 2 + midlleLayerProgressWidth - cosAY;
        } else if (tempD >= 90 && tempD < 180) {
            // 第二象限内,sin和cos互换
            xiaoyuanDegree = animDegree - 90;
            float hudu = (float) Math.abs(Math.PI * xiaoyuanDegree / 180);
            float sinAX = (float) Math.abs(Math.cos(hudu) * (mMiddleLayerSize / 2 + midlleLayerProgressWidth / 4));
            float cosAY = (float) Math.abs(Math.sin(hudu) * (mMiddleLayerSize / 2 + midlleLayerProgressWidth / 4));

            xiaoYuanX = (viewSize - 2 * midlleLayerProgressWidth - 2 * sinAX) / 2 + 2 * sinAX;
            xiaoYuanY = viewSize / 2 + cosAY - midlleLayerProgressWidth;

        } else if (tempD >= 180 && tempD < 270) {
            // 第三象限,sin和cos正常,但是x和y的坐标计算方法发生改变
            xiaoyuanDegree = animDegree - 180;
            float hudu = (float) Math.abs(Math.PI * xiaoyuanDegree / 180);
            float sinAX = (float) Math.abs(Math.sin(hudu) * (mMiddleLayerSize / 2 - midlleLayerProgressWidth));
            float cosAY = (float) Math.abs(Math.cos(hudu) * (mMiddleLayerSize / 2));

            xiaoYuanX = viewSize / 2 - sinAX;
            xiaoYuanY = viewSize / 2 + cosAY - midlleLayerProgressWidth;

        } else if (tempD >= 270 && tempD < 360) {
            // 第四象限内,sin和cos互换,但是x和y的坐标也发生了改变
            xiaoyuanDegree = animDegree - 270;
            float hudu = (float) Math.abs(Math.PI * xiaoyuanDegree / 180);
            float sinAX = (float) Math.abs(Math.cos(hudu) * (mMiddleLayerSize / 2 + midlleLayerProgressWidth / 4));
            float cosAY = (float) Math.abs(Math.sin(hudu) * (mMiddleLayerSize / 2 - midlleLayerProgressWidth));

            xiaoYuanX = viewSize / 2 - sinAX + midlleLayerProgressWidth;
            xiaoYuanY = viewSize / 2 - cosAY;
        }

        canvas.drawCircle(xiaoYuanX, xiaoYuanY, smallCircleSize / 2, smallCirclePaint);
        canvas.drawCircle(xiaoYuanX, xiaoYuanY, (smallCircleSize - smallCircleStrokeWidth) / 2, smallCircleInnerPaint);
    }
  • 终于跳过了上面的计算,下面的就是剩下绘制文字的知识了so easy

我们先计算出来文字的宽度和高度然后计算出来文字的绘制坐标即可

 /**
     * 绘制倒计时的描述显示
     *
     * @param progressDesc 描述
     * @param canvas       画布
     */
    private void drawProgressText(String progressDesc, Canvas canvas) {
        Point textPointInView = getTextPointInView(progressDesc);
        if (null == textPointInView) return;
        canvas.drawText(progressDesc, textPointInView.x, textPointInView.y, mTextDescPaint);
    }

    private Point getTextPointInView(String textDesc) {
        if (null == textDesc) return null;
        Point point = new Point();
        int textW = (viewSize - (int) mTextDescPaint.measureText(textDesc)) / 2;
        Paint.FontMetrics fm = mTextDescPaint.getFontMetrics();
        int textH = (int) Math.ceil(fm.descent - fm.top);
        point.set(textW, viewSize / 2 + textH / 2 - 20);
        return point;
    }


    /**
     * 绘制阴影圆圈
     *
     * @param canvas 画布
     */
    private void drawShadowCircle(Canvas canvas) {
        mShadowLayerInnerPaint.setShadowLayer(10, 2, 2, shadowLayerColor);
        //设置阴影图层
        setLayerType(LAYER_TYPE_SOFTWARE, null);
        mShadowLayerInnerPaint.setColor(shadowLayerInnerColor);
        canvas.drawCircle(circleCenterPoint.x, circleCenterPoint.y, mShadowLayerSize / 2, mShadowLayerInnerPaint);
    }

到此我们的Android绘制API全部用完了,就用了一个绘制扇形和圆形的方法,

下面我们看下Java知识,倒计时的方法;


    private void startCountDownTaskByRxAndroid() {
    // 每隔一秒发送一次事件
        Observable.interval(0, 1, TimeUnit.SECONDS)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<Long>() {
                    @Override
                    public void onCompleted() {
                        countdownTime = 0;
                    }

                    @Override
                    public void onError(Throwable e) {
                    }

                    @Override
                    public void onNext(Long aLong) {
                    // 每隔一秒,我们设定的事件就减少一秒
                        if (countdownTime < -1) {
                            this.unsubscribe();
                        }
                        --countdownTime;
                        // 简单的逻辑判断如果预设事件小于0那么我们修改文案,
                        if (countdownTime < 0) {
                            mTextDescPaint.setTextSize(innerTextSize / 2);
                            progressDesc = "时间到";
                            onCompleted();
                            return;
                        } else {
                            mTextDescPaint.setTextSize(innerTextSize);
                            progressDesc = countdownTime + "″";
                        }
                        // 刷新view 
                        invalidate();
                    }
                });
    }

最后就是一个动画的实现,我们可以理解成进度条在CountDown时间内正好从0-360走完,
那么这个用属性动画就行了

    public void startCountdown(final OnCountDownFinishListener countDownFinishListener) {
        setClickable(false);
        final ValueAnimator valA = getValA(countdownTime * 1000);
        valA.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
            // 计算当前的角度,并且实时的刷新View,这样进度就动起来了
                drgeePercent = Float.valueOf(valA.getAnimatedValue().toString());
                invalidate();
            }
        });
        valA.start();
        valA.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
            //提供一个对外公开的接口,这样我们的View在倒计时结束后,就能通知UI干活了.....
                if (null != countDownFinishListener) {
                    countDownFinishListener.countDownFinished();
                }
                super.onAnimationEnd(animation);
                if (countdownTime > 0) {
                    setClickable(true);
                } else {

                    setClickable(false);
                }
            }
        });
        startCountDownTaskByRxAndroid();
    }

到此,自定义倒计时View结束,下面我把自定义View的全部代码放到下面,有意向的可以放到AS里面运行一下,

public class ATProgressView extends View {
    private static final float OUTER_LAYER_LARGE_SCALE = 58 / 62.F;
    private static final float MIDDLE_LAYER_LARGE_SCALE = 51 / 62.F;
    private static final float SHADOW_LAYER_LARGE_SCALE = 40 / 62.F;
    private static final float SMALL_CIRCLE_DEGREE_OFFSET = 0.7F;

    private static final int DEF_VIEW_SIZE = 250;
    private static final float TEST_DEGREE = 70.F;

    private int outLayerSolideColor;
    private int outLayerStrokeColor;
    private int outLayerStrokeWidth;
    private int midlleLayerProgressColor;
    private int midlleLayerProgressWidth;
    private int midlleLayerBgColor;
    private int smallCircleSolideColor;
    private int smallCircleStrokeColor;
    private int smallCircleSize;
    private int smallCircleStrokeWidth;
    private int shadowLayerColor;
    private int shadowLayerInnerColor;
    private int innerTextSize;
    private int innerTextColor;

    private Point circleCenterPoint;

    private int viewSize;
    private Paint outLayerStrokePaint;
    private Paint outLayerSolidePaint;
    private Paint progressBgPaint;
    private Paint progressPaint;
    private Paint smallCirclePaint;
    private Paint smallCircleInnerPaint;
    private Paint mShadowLayerInnerPaint;
    private Paint mTextDescPaint;

    private float mOuterLayerLargeSize;
    private float mOuterLayerSolideSize;
    private float mMiddleLayerSize;
    private float mShadowLayerSize;
    private String progressDesc;
    private float drgeePercent;
    private int countdownTime;

    public ATProgressView(Context context) {
        this(context, null);
    }

    public ATProgressView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ATProgressView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ATProgressView, defStyleAttr, R.style.def_progress_style);
        int indexCount = typedArray.getIndexCount();
        for (int i = 0; i < indexCount; i++) {
            int attr = typedArray.getIndex(i);
            switch (attr) {
                case R.styleable.ATProgressView_outer_layer_solide_color:
                    outLayerSolideColor = typedArray.getColor(attr, Color.BLACK);
                    break;
                case R.styleable.ATProgressView_outer_layer_stroke_color:
                    outLayerStrokeColor = typedArray.getColor(attr, Color.BLACK);
                    break;
                case R.styleable.ATProgressView_outer_layer_stroke_width:
                    outLayerStrokeWidth = typedArray.getDimensionPixelOffset(attr, 0);
                    break;
                case R.styleable.ATProgressView_midlle_layer_progress_color:
                    midlleLayerProgressColor = typedArray.getColor(attr, Color.BLACK);
                    break;
                case R.styleable.ATProgressView_midlle_layer_progress_width:
                    midlleLayerProgressWidth = typedArray.getDimensionPixelOffset(attr, 0);
                    break;
                case R.styleable.ATProgressView_midlle_layer_bg_color:
                    midlleLayerBgColor = typedArray.getColor(attr, Color.BLACK);
                    break;
                case R.styleable.ATProgressView_midlle_layer_small_circle_solide_color:
                    smallCircleSolideColor = typedArray.getColor(attr, Color.BLACK);
                    break;
                case R.styleable.ATProgressView_midlle_layer_small_circle_stroke_color:
                    smallCircleStrokeColor = typedArray.getColor(attr, Color.BLACK);
                    break;
                case R.styleable.ATProgressView_midlle_layer_small_circle_size:
                    smallCircleSize = typedArray.getDimensionPixelSize(attr, 0);
                    break;
                case R.styleable.ATProgressView_midlle_layer_small_circle_stroke_width:
                    smallCircleStrokeWidth = typedArray.getDimensionPixelOffset(attr, 0);
                    break;
                case R.styleable.ATProgressView_shadow_layer_color:
                    shadowLayerColor = typedArray.getColor(attr, Color.BLACK);
                    break;
                case R.styleable.ATProgressView_shadow_layer_inner_color:
                    shadowLayerInnerColor = typedArray.getColor(attr, Color.BLACK);
                    break;
                case R.styleable.ATProgressView_inner_text_size:
                    innerTextSize = typedArray.getDimensionPixelSize(attr, 0);
                    break;
                case R.styleable.ATProgressView_inner_text_color:
                    innerTextColor = typedArray.getColor(attr, Color.BLACK);
                    break;
            }
        }
        typedArray.recycle();
        initData();
    }

    private void initData() {
        outLayerStrokePaint = creatPaint(outLayerStrokeColor, 0, Paint.Style.FILL, 0);
        outLayerSolidePaint = creatPaint(outLayerSolideColor, 0, Paint.Style.FILL, 0);
        progressBgPaint = creatPaint(midlleLayerBgColor, 0, Paint.Style.STROKE, midlleLayerProgressWidth);
        progressPaint = creatPaint(midlleLayerProgressColor, 0, Paint.Style.STROKE, midlleLayerProgressWidth);
        smallCirclePaint = creatPaint(smallCircleStrokeColor, 0, Paint.Style.FILL, 0);
        smallCircleInnerPaint = creatPaint(smallCircleSolideColor, 0, Paint.Style.FILL, 0);
        mShadowLayerInnerPaint = creatPaint(shadowLayerInnerColor, 0, Paint.Style.FILL, 0);
        mTextDescPaint = creatPaint(innerTextColor, innerTextSize, Paint.Style.FILL, 0);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        int widthSize;
        int heightSize;

        if (widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED) {
            widthSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEF_VIEW_SIZE, getResources().getDisplayMetrics());
            widthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY);
        }

        if (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED) {
            heightSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEF_VIEW_SIZE, getResources().getDisplayMetrics());
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY);
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        viewSize = w - h >= 0 ? h : w;
        circleCenterPoint = new Point(viewSize / 2, viewSize / 2);
        mOuterLayerLargeSize = viewSize * OUTER_LAYER_LARGE_SCALE;
        mOuterLayerSolideSize = mOuterLayerLargeSize - 2 * outLayerStrokeWidth;

        mMiddleLayerSize = viewSize * MIDDLE_LAYER_LARGE_SCALE;
        mShadowLayerSize = viewSize * SHADOW_LAYER_LARGE_SCALE;

    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        drawOuterLayerCircles(canvas);
        drawMiddleProgressLayer(canvas);
        drawShadowCircle(canvas);
        drawProgressText(progressDesc, canvas);
    }

    /**
     * 绘制倒计时的描述显示
     *
     * @param progressDesc 描述
     * @param canvas       画布
     */
    private void drawProgressText(String progressDesc, Canvas canvas) {
        Point textPointInView = getTextPointInView(progressDesc);
        if (null == textPointInView) return;
        canvas.drawText(progressDesc, textPointInView.x, textPointInView.y, mTextDescPaint);
    }

    private Point getTextPointInView(String textDesc) {
        if (null == textDesc) return null;
        Point point = new Point();
        int textW = (viewSize - (int) mTextDescPaint.measureText(textDesc)) / 2;
        Paint.FontMetrics fm = mTextDescPaint.getFontMetrics();
        int textH = (int) Math.ceil(fm.descent - fm.top);
        point.set(textW, viewSize / 2 + textH / 2 - 20);
        return point;
    }


    /**
     * 绘制阴影圆圈
     *
     * @param canvas 画布
     */
    private void drawShadowCircle(Canvas canvas) {
        mShadowLayerInnerPaint.setShadowLayer(10, 2, 2, shadowLayerColor);
        //设置阴影图层
        setLayerType(LAYER_TYPE_SOFTWARE, null);
        mShadowLayerInnerPaint.setColor(shadowLayerInnerColor);
        canvas.drawCircle(circleCenterPoint.x, circleCenterPoint.y, mShadowLayerSize / 2, mShadowLayerInnerPaint);
    }

    /**
     * 绘制中间进度的背景和进度
     *
     * @param canvas 画布
     */
    private void drawMiddleProgressLayer(Canvas canvas) {

        float ddx = (viewSize - mMiddleLayerSize) / 2;
        //外切圆的坐标计算,绘制的扇形在矩形内的外切圆,注意画笔的宽度
        RectF oval = new RectF(ddx + midlleLayerProgressWidth, ddx + midlleLayerProgressWidth, viewSize - ddx - midlleLayerProgressWidth, viewSize - ddx - midlleLayerProgressWidth);

        canvas.drawCircle(circleCenterPoint.x, circleCenterPoint.y, mMiddleLayerSize / 2 - midlleLayerProgressWidth, progressBgPaint);
        // 注意扫过的扇形的范围(进度)要和绘制的小圆点保持一致,所以我们需要从-90度开始
        canvas.drawArc(oval, -90, 360 * drgeePercent, false, progressPaint);

        // 由于前面绘制了一个小圆,所以我们弧度的角度不能用于计算圆的坐标,我们需要大概的加上那么一两度来计算,
        // 由于android坐标系的问题以及角度在不同象限内的问题,所以我们需要计算几种情况
        // 0-90,90-180 ,180-270,270-360
        float animDegree = 360 * drgeePercent + SMALL_CIRCLE_DEGREE_OFFSET;
        float xiaoyuanDegree;
        float xiaoYuanX = 0, xiaoYuanY = 0;
        int tempD = (int) animDegree;
        if (tempD >= 0 && tempD < 90) {
            // 第一象限内,sin和cons正常
            xiaoyuanDegree = animDegree;
            float hudu = (float) Math.abs(Math.PI * xiaoyuanDegree / 180);
            float sinAX = (float) Math.abs(Math.sin(hudu) * (mMiddleLayerSize / 2 + midlleLayerProgressWidth / 4));
            float cosAY = (float) Math.abs(Math.cos(hudu) * (mMiddleLayerSize / 2 + midlleLayerProgressWidth / 4));

            xiaoYuanX = (viewSize - 2 * midlleLayerProgressWidth - 2 * sinAX) / 2 + 2 * sinAX;
            xiaoYuanY = viewSize / 2 + midlleLayerProgressWidth - cosAY;
        } else if (tempD >= 90 && tempD < 180) {
            // 第二象限内,sin和cos互换
            xiaoyuanDegree = animDegree - 90;
            float hudu = (float) Math.abs(Math.PI * xiaoyuanDegree / 180);
            float sinAX = (float) Math.abs(Math.cos(hudu) * (mMiddleLayerSize / 2 + midlleLayerProgressWidth / 4));
            float cosAY = (float) Math.abs(Math.sin(hudu) * (mMiddleLayerSize / 2 + midlleLayerProgressWidth / 4));

            xiaoYuanX = (viewSize - 2 * midlleLayerProgressWidth - 2 * sinAX) / 2 + 2 * sinAX;
            xiaoYuanY = viewSize / 2 + cosAY - midlleLayerProgressWidth;

        } else if (tempD >= 180 && tempD < 270) {
            // 第三象限,sin和cos正常,但是x和y的坐标计算方法发生改变
            xiaoyuanDegree = animDegree - 180;
            float hudu = (float) Math.abs(Math.PI * xiaoyuanDegree / 180);
            float sinAX = (float) Math.abs(Math.sin(hudu) * (mMiddleLayerSize / 2 - midlleLayerProgressWidth));
            float cosAY = (float) Math.abs(Math.cos(hudu) * (mMiddleLayerSize / 2));

            xiaoYuanX = viewSize / 2 - sinAX;
            xiaoYuanY = viewSize / 2 + cosAY - midlleLayerProgressWidth;

        } else if (tempD >= 270 && tempD < 360) {
            // 第四象限内,sin和cos互换,但是x和y的坐标也发生了改变
            xiaoyuanDegree = animDegree - 270;
            float hudu = (float) Math.abs(Math.PI * xiaoyuanDegree / 180);
            float sinAX = (float) Math.abs(Math.cos(hudu) * (mMiddleLayerSize / 2 + midlleLayerProgressWidth / 4));
            float cosAY = (float) Math.abs(Math.sin(hudu) * (mMiddleLayerSize / 2 - midlleLayerProgressWidth));

            xiaoYuanX = viewSize / 2 - sinAX + midlleLayerProgressWidth;
            xiaoYuanY = viewSize / 2 - cosAY;
        }

        canvas.drawCircle(xiaoYuanX, xiaoYuanY, smallCircleSize / 2, smallCirclePaint);
        canvas.drawCircle(xiaoYuanX, xiaoYuanY, (smallCircleSize - smallCircleStrokeWidth) / 2, smallCircleInnerPaint);
    }

    /**
     * 绘制外层的大圆圈和描边
     *
     * @param canvas 画布
     */
    private void drawOuterLayerCircles(Canvas canvas) {
        // 采用阴影绘制
        outLayerSolidePaint.setShadowLayer(10, 2, 2, shadowLayerColor);
        //设置阴影图层
        setLayerType(LAYER_TYPE_SOFTWARE, null);
        canvas.drawCircle(circleCenterPoint.x, circleCenterPoint.y, mOuterLayerSolideSize / 2, outLayerSolidePaint);
    }

    /**
     * 初始化画笔
     *
     * @param paintColor 画笔颜色
     * @param textSize   文字大小
     * @param style      画笔风格
     * @param lineWidth  画笔宽度
     * @return 画笔
     */
    private Paint creatPaint(int paintColor, int textSize, Paint.Style style, int lineWidth) {
        Paint paint = new Paint();
        paint.setColor(paintColor);
        paint.setAntiAlias(true);
        paint.setStrokeWidth(lineWidth);
        paint.setDither(true);
        paint.setTextSize(textSize);
        paint.setStyle(style);
        paint.setStrokeCap(Paint.Cap.ROUND);
        paint.setStrokeJoin(Paint.Join.ROUND);
        return paint;
    }

    private ValueAnimator getValA(long countdownTime) {
        ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1.F);
        valueAnimator.setDuration(countdownTime);
        valueAnimator.setInterpolator(new LinearInterpolator());
        valueAnimator.setRepeatCount(0);
        return valueAnimator;
    }

    /**
     * 开始倒计时任务
     */
    public void startCountdown(final OnCountDownFinishListener countDownFinishListener) {
        setClickable(false);
        final ValueAnimator valA = getValA(countdownTime * 1000);
        valA.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                drgeePercent = Float.valueOf(valA.getAnimatedValue().toString());
                invalidate();
            }
        });
        valA.start();
        valA.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                if (null != countDownFinishListener) {
                    countDownFinishListener.countDownFinished();
                }
                super.onAnimationEnd(animation);
                if (countdownTime > 0) {
                    setClickable(true);
                } else {

                    setClickable(false);
                }
            }
        });
        startCountDownTaskByRxAndroid();
    }

    public void setCountdownTime(int countdownTime) {
        this.countdownTime = countdownTime;
        progressDesc = countdownTime + "″";
    }

    private void startCountDownTaskByRxAndroid() {
        Observable.interval(0, 1, TimeUnit.SECONDS)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<Long>() {
                    @Override
                    public void onCompleted() {
                        countdownTime = 0;
                    }

                    @Override
                    public void onError(Throwable e) {
                    }

                    @Override
                    public void onNext(Long aLong) {
                        if (countdownTime < -1) {
                            this.unsubscribe();
                        }
                        --countdownTime;
                        if (countdownTime < 0) {
                            mTextDescPaint.setTextSize(innerTextSize / 2);
                            progressDesc = "时间到";
                            onCompleted();
                            return;
                        } else {
                            mTextDescPaint.setTextSize(innerTextSize);
                            progressDesc = countdownTime + "″";
                        }
                        invalidate();
                    }
                });
    }


    public interface OnCountDownFinishListener {
        void countDownFinished();
    }
}

最后源代码的链接如下,希望大家多多fork和star,github地址如下https://github.com/GuoFeilong/ATLoginButton_New


荆轲刺秦王 The END

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值