博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android -- 自定义view实现keep欢迎页倒计时效果
阅读量:6982 次
发布时间:2019-06-27

本文共 16479 字,大约阅读时间需要 54 分钟。

1,最近打开keep的app的时候,发现它的欢迎页面的倒计时效果还不错,所以打算自己来写写,然后就有了这篇文章。

2,还是老规矩,先看一下我们今天实现的效果

   相较于我们常见的倒计时,这次实现的效果是多了外面圆环的不断减少,这也是我们这次自定义view的有意思的一点。

   知道了效果我们先来效果分析一波,首先是一个倒计时效果,计时的时候上面的圆弧不断的减少,里面的文字也不断的变化,在视觉上的改变就大致为这两部分,但是实际上我们的ui是由三部分来构成的:里面的实心圆、外面的圆弧、里面的文字。知道了我们ui的组成,我们就来开撸开撸。

  在开撸之前我们还是回顾一下我们简单的自定义view的基本流程

/** * 自定义View的几个步骤 * 1,自定义View属性 * 2,在View中获得我们的自定义的属性 * 3,重写onMeasure * 4,重写onDraw * 5,重写onLayout(这个决定view放置在哪儿) */

 ①、确定自定义属性

   我们根据上面的基本步骤,我们知道首先我们根据效果图先来确定我们这次的自定义属性,这里我简单的分析了一下,主要添加了八个自定义属性,分别是里面实心圆的半径和颜色、圆弧的颜色和半径、里面文字的大小和颜色、总倒计时时间的长度、圆弧减少的方向(分为顺时针和逆时针),所以首先在res/values目录下创建attrs.xml文件,添加以下属性:(这里如果有对自定义属性不太了解的同学可以去了解我以前写过的,可以更加深刻的理解)

  ②、获取自定义属性、初始化一些属性

  首先创建CircleTimerView类,继承自View类

public class CircleTimerView extends View {    private Context context ;    //里面实心圆颜色    private int mSolidCircleColor ;    //里面圆的半径    private int mSolidCircleRadius;    //外面圆弧的颜色    private int mEmptyCircleColor ;    //外面圆弧的半径(可以使用画笔的宽度来实现)    private int mEmptyCircleRadius ;    //文字大小    private int mTextSize ;    //文字颜色    private int mTextColor ;    //文字    private String mText ;    //绘制的方向    private int mDrawOrientation;    //圆弧绘制的速度    private int mSpeed;    //圆的画笔    private Paint mPaintCircle ;    //圆弧的画笔    private Paint mPaintArc ;    //绘制文字的画笔    private Paint mPaintText;    //时长    private int mTimeLength ;    //默认值    private int defaultSolidCircleColor ;    private int defaultEmptyCircleColor ;    private int defaultSolidCircleRadius ;    private int defaultEmptyCircleRadius ;    private int defaultTextColor ;    private int defaultTextSize ;    private int defaultTimeLength ;    private int defaultDrawOritation ;    //当前扇形的角度    private int startProgress ;    private int endProgress ;    private float currProgress ;    //动画集合    private AnimatorSet set ;    //回调    private OnCountDownFinish onCountDownFinish ;    public CircleTimerView(Context context) {        this(context,null);    }    public CircleTimerView(Context context, @Nullable AttributeSet attrs) {        this(context, attrs,0);    }    public CircleTimerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        this.context = context ;        //初始化默认值        defaultSolidCircleColor = getResources().getColor(R.color.colorPrimary);        defaultEmptyCircleColor = getResources().getColor(R.color.colorAccent);        defaultTextColor = getResources().getColor(R.color.colorYellow);        defaultSolidCircleRadius = (int) getResources().getDimension(R.dimen.dimen_20);        defaultEmptyCircleRadius = (int) getResources().getDimension(R.dimen.dimen_25);        defaultTextSize = (int) getResources().getDimension(R.dimen.dimen_16);        defaultTimeLength = 3 ;        defaultDrawOritation = 1 ;        //获取自定义属性        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleTimerView);        mSolidCircleColor = a.getColor(R.styleable.CircleTimerView_solid_circle_color,defaultSolidCircleColor);        mSolidCircleRadius = a.getDimensionPixelOffset(R.styleable.CircleTimerView_solid_circle_radius ,defaultSolidCircleRadius);        mEmptyCircleColor = a.getColor(R.styleable.CircleTimerView_empty_circle_color,defaultEmptyCircleColor);        mEmptyCircleRadius = a.getDimensionPixelOffset(R.styleable.CircleTimerView_empty_circle_radius ,defaultEmptyCircleRadius);        mTextColor = a.getColor(R.styleable.CircleTimerView_circle_text_color,defaultTextColor);        mTextSize = a.getDimensionPixelOffset(R.styleable.CircleTimerView_circle_text_size ,defaultTextSize);        mDrawOrientation = a.getInt(R.styleable.CircleTimerView_circle_draw_orientation,defaultDrawOritation);        mTimeLength = a.getInt(R.styleable.CircleTimerView_time_length ,defaultTimeLength);        a.recycle();        init();    }    private void init() {        //初始化画笔        mPaintCircle = new Paint();        mPaintCircle.setStyle(Paint.Style.FILL);        mPaintCircle.setAntiAlias(true);        mPaintCircle.setColor(mSolidCircleColor);        mPaintArc = new Paint();        mPaintArc.setStyle(Paint.Style.STROKE);        mPaintArc.setAntiAlias(true);        mPaintArc.setColor(mEmptyCircleColor);        mPaintArc.setStrokeWidth(mEmptyCircleRadius - mSolidCircleRadius);        mPaintText = new Paint();        mPaintText.setStyle(Paint.Style.STROKE);        mPaintText.setAntiAlias(true);        mPaintText.setTextSize(mTextSize);        mPaintText.setColor(mTextColor);        mText= mTimeLength +"" ;        if(defaultDrawOritation == 1){            startProgress = 360 ;            endProgress = 0 ;        }else {            startProgress = 0 ;            endProgress = 360 ;        }        currProgress = startProgress ;    }

  这里我在构造函数里面先初始化一些默认的值,然后获取自定义属性,然后再初始化三个画笔,分别代表:实心圆、圆弧、Text的画笔(这个很好理解),然后根据顺时针和逆时针来初始化开始角度和结束角度,很简单就不在过多的废话了。

  ③、重写onMeasure方法

  这里由于我们的效果很简单,基本上就是一个正方形,所以这里我是以外面圆弧的半径当这个view 的宽高的,就没去判断match_parent、wrap_content之类的情况,代码如下:

@Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        //设置宽高        setMeasuredDimension(mEmptyCircleRadius*2,mEmptyCircleRadius*2);    }

  ④,重写onDraw方法

  这也是我们自定义view关键,首先我们绘制圆弧和文字很简单,绘制圆弧的话可能有些同学没有接触过,这里我以前,大家可以去看看,我们这里要用的知识点 都是一样的,所以就不再废话

  

@Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        //绘制背景圆        canvas.drawCircle(mEmptyCircleRadius,mEmptyCircleRadius,mSolidCircleRadius,mPaintCircle);        //绘制圆弧        RectF oval = new RectF((mEmptyCircleRadius - mSolidCircleRadius)/2, (mEmptyCircleRadius - mSolidCircleRadius)/2                , mEmptyCircleRadius + (mEmptyCircleRadius - mSolidCircleRadius)/2+mSolidCircleRadius, mEmptyCircleRadius + (mEmptyCircleRadius - mSolidCircleRadius)/2+mSolidCircleRadius); // 用于定义的圆弧的形状和大小的界限        canvas.drawArc(oval, -90, currProgress, false, mPaintArc); // 根据进度画圆弧        //绘制文字        Rect mBound = new Rect();        mPaintText.getTextBounds(mText, 0, mText.length(), mBound);        canvas.drawText(mText, getWidth() / 2 - mBound.width() / 2, getHeight() / 2 + mBound.height() / 2, mPaintText);    }

  在这个时候,我们就可以来看一下我们自定义view的效果了,将我们currProgress先写死成270,来看看我们的效果,这里注意一项在使用我们的自定义属性的时候,记得在布局文件中添加我们自定义空间。运行效果如下:

  可以看到这里我们的效果基本上试出来了,关键是怎么让它动起来,这里我们的第一反应是handle或者timer来实现一个倒计时,一开始阿呆哥哥也是使用timer来实现的,不过发现由于ui的改变中是有两个不同速率的view在改变:圆弧的不断减小、textView字体的逐渐变小,所以这里使用一个timer无法实现,得用两个,如果用两个就不怎么软件工程了,所以这里打算使用动画来实现,具体代码如下:

/**     * 通过外部开关控制     */    public void start(){               ValueAnimator animator1 = ValueAnimator.ofFloat(startProgress,endProgress);        animator1.setInterpolator(new LinearInterpolator());        animator1.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {            @Override            public void onAnimationUpdate(ValueAnimator valueAnimator) {                currProgress = (float) valueAnimator.getAnimatedValue();                invalidate();            }        });        ValueAnimator animator2 = ValueAnimator.ofInt(mTimeLength,0);        animator2.setInterpolator(new LinearInterpolator());        animator2.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {            @Override            public void onAnimationUpdate(ValueAnimator valueAnimator) {                mTimeLength = (int) valueAnimator.getAnimatedValue();                if (mTimeLength == 0)                    return;                mText =mTimeLength+ "";            }        });        set = new AnimatorSet();        set.playTogether(animator1,animator2);        set.setDuration(mTimeLength * 1000);        set.start();        set.addListener(new Animator.AnimatorListener() {            @Override            public void onAnimationStart(Animator animator) {            }            @Override            public void onAnimationEnd(Animator animator) {                if (onCountDownFinish != null){                    onCountDownFinish.onFinish();                }            }            @Override            public void onAnimationCancel(Animator animator) {            }            @Override            public void onAnimationRepeat(Animator animator) {            }        });    }

  很简单,就是两个ValueAnimator,监听值的改变,然后再最后完成的动画的时候使用接口回调,通知宿主完成ToDo操作,所以到这里我们基本上完全实现了我们的view 的自定义,CircleTimerView的完整代码如下:

package com.ysten.circletimerdown.view;import android.animation.Animator;import android.animation.AnimatorSet;import android.animation.ValueAnimator;import android.content.Context;import android.content.res.TypedArray;import android.graphics.Canvas;import android.graphics.Paint;import android.graphics.Rect;import android.graphics.RectF;import android.support.annotation.Nullable;import android.util.AttributeSet;import android.view.View;import android.view.animation.AnimationSet;import android.view.animation.LinearInterpolator;import com.ysten.circletimerdown.R;import java.util.Timer;import java.util.TimerTask;/** * author : wangjitao * e-mail : 543441727@qq.com * time   : 2017/08/14 * desc   : * version: 1.0 */public class CircleTimerView extends View {    private Context context ;    //里面实心圆颜色    private int mSolidCircleColor ;    //里面圆的半径    private int mSolidCircleRadius;    //外面圆弧的颜色    private int mEmptyCircleColor ;    //外面圆弧的半径(可以使用画笔的宽度来实现)    private int mEmptyCircleRadius ;    //文字大小    private int mTextSize ;    //文字颜色    private int mTextColor ;    //文字    private String mText ;    //绘制的方向    private int mDrawOrientation;    //圆弧绘制的速度    private int mSpeed;    //圆的画笔    private Paint mPaintCircle ;    //圆弧的画笔    private Paint mPaintArc ;    //绘制文字的画笔    private Paint mPaintText;    //时长    private int mTimeLength ;    //默认值    private int defaultSolidCircleColor ;    private int defaultEmptyCircleColor ;    private int defaultSolidCircleRadius ;    private int defaultEmptyCircleRadius ;    private int defaultTextColor ;    private int defaultTextSize ;    private int defaultTimeLength ;    private int defaultDrawOritation ;    //当前扇形的角度    private int startProgress ;    private int endProgress ;    private float currProgress ;    //动画集合    private AnimatorSet set ;    //回调    private OnCountDownFinish onCountDownFinish ;    public CircleTimerView(Context context) {        this(context,null);    }    public CircleTimerView(Context context, @Nullable AttributeSet attrs) {        this(context, attrs,0);    }    public CircleTimerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        this.context = context ;        //初始化默认值        defaultSolidCircleColor = getResources().getColor(R.color.colorPrimary);        defaultEmptyCircleColor = getResources().getColor(R.color.colorAccent);        defaultTextColor = getResources().getColor(R.color.colorYellow);        defaultSolidCircleRadius = (int) getResources().getDimension(R.dimen.dimen_20);        defaultEmptyCircleRadius = (int) getResources().getDimension(R.dimen.dimen_25);        defaultTextSize = (int) getResources().getDimension(R.dimen.dimen_16);        defaultTimeLength = 3 ;        defaultDrawOritation = 1 ;        //获取自定义属性        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleTimerView);        mSolidCircleColor = a.getColor(R.styleable.CircleTimerView_solid_circle_color,defaultSolidCircleColor);        mSolidCircleRadius = a.getDimensionPixelOffset(R.styleable.CircleTimerView_solid_circle_radius ,defaultSolidCircleRadius);        mEmptyCircleColor = a.getColor(R.styleable.CircleTimerView_empty_circle_color,defaultEmptyCircleColor);        mEmptyCircleRadius = a.getDimensionPixelOffset(R.styleable.CircleTimerView_empty_circle_radius ,defaultEmptyCircleRadius);        mTextColor = a.getColor(R.styleable.CircleTimerView_circle_text_color,defaultTextColor);        mTextSize = a.getDimensionPixelOffset(R.styleable.CircleTimerView_circle_text_size ,defaultTextSize);        mDrawOrientation = a.getInt(R.styleable.CircleTimerView_circle_draw_orientation,defaultDrawOritation);        mTimeLength = a.getInt(R.styleable.CircleTimerView_time_length ,defaultTimeLength);        a.recycle();        init();    }    private void init() {        //初始化画笔        mPaintCircle = new Paint();        mPaintCircle.setStyle(Paint.Style.FILL);        mPaintCircle.setAntiAlias(true);        mPaintCircle.setColor(mSolidCircleColor);        mPaintArc = new Paint();        mPaintArc.setStyle(Paint.Style.STROKE);        mPaintArc.setAntiAlias(true);        mPaintArc.setColor(mEmptyCircleColor);        mPaintArc.setStrokeWidth(mEmptyCircleRadius - mSolidCircleRadius);        mPaintText = new Paint();        mPaintText.setStyle(Paint.Style.STROKE);        mPaintText.setAntiAlias(true);        mPaintText.setTextSize(mTextSize);        mPaintText.setColor(mTextColor);        mText= mTimeLength +"" ;        if(defaultDrawOritation == 1){            startProgress = 360 ;            endProgress = 0 ;        }else {            startProgress = 0 ;            endProgress = 360 ;        }        currProgress = startProgress ;    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        //设置宽高        setMeasuredDimension(mEmptyCircleRadius*2,mEmptyCircleRadius*2);    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        //绘制背景圆        canvas.drawCircle(mEmptyCircleRadius,mEmptyCircleRadius,mSolidCircleRadius,mPaintCircle);        //绘制圆弧        RectF oval = new RectF((mEmptyCircleRadius - mSolidCircleRadius)/2, (mEmptyCircleRadius - mSolidCircleRadius)/2                , mEmptyCircleRadius + (mEmptyCircleRadius - mSolidCircleRadius)/2+mSolidCircleRadius, mEmptyCircleRadius + (mEmptyCircleRadius - mSolidCircleRadius)/2+mSolidCircleRadius); // 用于定义的圆弧的形状和大小的界限        canvas.drawArc(oval, -90, currProgress, false, mPaintArc); // 根据进度画圆弧        //绘制文字        Rect mBound = new Rect();        mPaintText.getTextBounds(mText, 0, mText.length(), mBound);        canvas.drawText(mText, getWidth() / 2 - mBound.width() / 2, getHeight() / 2 + mBound.height() / 2, mPaintText);    }    public OnCountDownFinish getOnCountDownFinish() {        return onCountDownFinish;    }    public void setOnCountDownFinish(OnCountDownFinish onCountDownFinish) {        this.onCountDownFinish = onCountDownFinish;    }    /**     * 通过外部开关控制     */    public void start(){        ValueAnimator animator1 = ValueAnimator.ofFloat(startProgress,endProgress);        animator1.setInterpolator(new LinearInterpolator());        animator1.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {            @Override            public void onAnimationUpdate(ValueAnimator valueAnimator) {                currProgress = (float) valueAnimator.getAnimatedValue();                invalidate();            }        });        ValueAnimator animator2 = ValueAnimator.ofInt(mTimeLength,0);        animator2.setInterpolator(new LinearInterpolator());        animator2.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {            @Override            public void onAnimationUpdate(ValueAnimator valueAnimator) {                mTimeLength = (int) valueAnimator.getAnimatedValue();                if (mTimeLength == 0)                    return;                mText =mTimeLength+ "";            }        });        set = new AnimatorSet();        set.playTogether(animator1,animator2);        set.setDuration(mTimeLength * 1000);        set.start();        set.addListener(new Animator.AnimatorListener() {            @Override            public void onAnimationStart(Animator animator) {            }            @Override            public void onAnimationEnd(Animator animator) {                if (onCountDownFinish != null){                    onCountDownFinish.onFinish();                }            }            @Override            public void onAnimationCancel(Animator animator) {            }            @Override            public void onAnimationRepeat(Animator animator) {            }        });    }    public void cancelAnim(){        if(set != null)        set.pause();    }    public interface OnCountDownFinish{        void onFinish();    }}

  最后实现的效果如下:

    Github,有需要源码的同学可以去下载一下。

转载于:https://www.cnblogs.com/wjtaigwh/p/7359114.html

你可能感兴趣的文章
如何使用ITEXTSHARP将HTML代码字符串写进PDF
查看>>
Oracle SQL CPU占用高
查看>>
mongodb简介与增删该查
查看>>
Maya 2015 中英文切换
查看>>
C语言的字符串分割
查看>>
Arduino可穿戴开发入门教程Windows平台下安装Arduino IDE
查看>>
BpBinder 转换为 BpCameraService 流程
查看>>
李洪强经典面试题150-设计模式
查看>>
使用maven的profile切换项目各环境的参数
查看>>
XML基础知识
查看>>
如何用C#写一个简单的Login窗口
查看>>
Sharepoint学习笔记—习题系列--70-576习题解析 -(Q141-Q143)
查看>>
sudo配置文件/etc/sudoers格式
查看>>
【Django】Django 如何使用 Django设置的日志?
查看>>
span设置padding无效
查看>>
RGB、HSB、HSL 互相转换算法
查看>>
【转】外企高管们的“中年危机”
查看>>
HeapAlloc 和 GlobalAlloc 以及 VirtualAlloc 三者之间的关系(转)
查看>>
你一定不知道IDE里的Tomcat是怎么工作的
查看>>
Typescript 2+迷你书 :从入门到不放弃
查看>>