自定义View -- 倒计时View

这篇博客介绍了如何在Android中自定义一个倒计时View,详细讲解了从创建构造函数、定义自定义属性、重写`onMeasure`和`onDraw`方法到实现倒计时动画的全过程。通过属性动画实现更优雅的动画效果,未来计划增加更多样式和功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这是一篇博文用来记录自己写的一个倒计时view,以便学习android;希望和大家共同学习进步,如有任何意见,建议或者讨论,可在评论中指出或者联系我Email:pokerwu.work@gmail.com

关于自定义view

这个不需要我多讲了,相信大家页看了很多这些,我采用的是继承View来创建,因为现有的组件都太复杂,考虑到自己对这些还不是很熟悉,现有的组件的代码看起来很恼火的。所以自己就参考了一些博客自己定义一个view来实现倒计时。具体可参考官方文档creating custom views,或者其他博客博主关于自定View的介绍。


开始

创建一个CountDownView extends View

public class CountDownView extends View {
    public CountDownView(Context context) {
        this(context,null);
    }
    public CountDownView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
}

关于构造函数
我们注意到自定view时需要创建构造函数,view在SDKVersion21之后为4个构造函数,官方的指导当中建议我们采用第二个。
* public CountDownView(Context context)构造函数是用于代码中new一个实例对象;
* public CountDownView(Context context, AttributeSet attrs)构造函数用于android从layout文件中inflate一个View对象,AttributeSet中就包含了我们在xml文件中所指定的属性;
* public CountDownView(Context context, AttributeSet attrs, int defStyleAttr)public CountDownView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)两个构造函数,其实差不多是一样的,就是一些属性是通过主题或者是style中获得。
一般来说我们是不考虑后两个构造函数的。可参考其他人写的关于这个四个构造函数的讲解深入理解Android View的构造函数


定义自定义属性

在自定义view过程中我们我定义一些属性来帮我们实现view的特定样式。
res/values下新建一个xml文件,定义我们需要的属性

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="CountDownView">
        <attr name="cd_stroke_color" format="color"/>
        <attr name="cd_number_start_color" format="color"/>
        <attr name="cd_number_end_color" format="color"/>
        <attr name="cd_number" format="integer"/>
        <attr name="cd_delay" format="integer"/>
        <attr name="cd_stroke_width" format="dimension"/>
        <attr name="cd_number_size" format="dimension"/>
    </declare-styleable>
</resources>

然后在构造函数中将者属性的只取出

 TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.CountDownView);
        strokeColor = array.getColor(R.styleable.CountDownView_cd_stroke_color,strokeColor);
        numberStartColor = array.getColor(R.styleable.CountDownView_cd_number_start_color,numberStartColor);
        numberEndColor = array.getColor(R.styleable.CountDownView_cd_number_end_color,numberEndColor);
        number = array.getInt(R.styleable.CountDownView_cd_number, number);
        numberSize = array.getDimension(R.styleable.CountDownView_cd_number_size, DensityUtil.sp2px(context,numberSize));
        delay = array.getInt(R.styleable.CountDownView_cd_delay,delay);
        strokeWidth = array.getDimension(R.styleable.CountDownView_cd_stroke_width, DensityUtil.dip2px(context,strokeWidth));
        array.recycle();

在最后一定要记得array.recycle();


onMeasure

为了满足wrap_content这样的layout属性,我们需要重写onMeasure函数,因为我的倒计时view主要涉及到其中需要drawText,并且定义了cd_text_size的属性,所以在测量是需要考虑文字的大小,具体代码如下:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int specModeWidth = MeasureSpec.getMode(widthMeasureSpec);
        int specModeHeight = MeasureSpec.getMode(heightMeasureSpec);
        int suggestWidth = MeasureSpec.getSize(widthMeasureSpec);
        int suggestHeight = MeasureSpec.getSize(heightMeasureSpec);

        int measureWidth;
        int measureHeight;
        textRect = new Rect();
        numberPaint.setTextSize(numberSize);
        numberPaint.getTextBounds(String.valueOf(number),0,String.valueOf(number).length(), textRect);
        int textWidth = textRect.width();
        int textHeight = textRect.height();
         //文字和边距有16dp,draw的位置有4dp不draw任何东西
        measureHeight = measureWidth = Math.max(textHeight,textWidth)
                + DensityUtil.dip2px(context,16) + (int)strokeWidth + padding;

        if (specModeWidth == MeasureSpec.AT_MOST && specModeHeight == MeasureSpec.AT_MOST)
            setMeasuredDimension(measureWidth, measureHeight);
        else if (specModeWidth == MeasureSpec.AT_MOST)
            setMeasuredDimension(measureWidth,suggestHeight);
        else if(specModeHeight == MeasureSpec.AT_MOST)
            setMeasuredDimension(suggestWidth,measureHeight);
        else
            setMeasuredDimension(suggestWidth,suggestHeight);
    }

onDraw

自定义View中最重要的一个方法了,我的view要做成什么样完全就看这个方法的实现了,我们的倒计时view需要先绘制一个圆形背景,然后绘制数字,然后在绘制外围的进度条。
其中需要保证文字能够绝对的居中,关于文字绝对居中,可以参考官方FontMetrics或者其他的博客Android字符串进阶之三:字体属性及测量(FontMetrics)

具体draw方法实现如下:

 protected void onDraw(Canvas canvas) {

        strokePaint.setColor(strokeColor);
        strokePaint.setStrokeWidth(strokeWidth);

        numberPaint.setColor(color);

        canvas.drawCircle(centerX,centerY,radius,bgPaint);
        //draw number
        Paint.FontMetrics fontMetrics = numberPaint.getFontMetrics();
        canvas.drawText(String.valueOf(number - (int) progress),centerX,
               centerY - (fontMetrics.bottom -fontMetrics.top)/2 -fontMetrics.top,numberPaint);
        float endAngle = progress/ number * 360.0f;
    //绘制外围进度条
        canvas.drawArc(strokeOval,0,endAngle,false,strokePaint);
    }

动起来

既然是倒计时,那必须要能够动起来,数字要变化,进度条要变化才行,可以用有两种方式:
1. Thread + handler
2. Animator
我采用第二种,更加优雅一些,动画效果也比第一种要柔和。
我使用了属性动画,由于我们主要操作的属性是进度条的进度和倒计时数字的颜色,需要设计两个动画,但是需要同时进行,为了不使代码开启来杂乱,我采用了内部类的方式来做动画,这样便与日后拓展:

public class ViewAnimatorWrap {
        private ObjectAnimator strokeAnimator;
        private ObjectAnimator numberColorAnimator;
        private AnimatorSet animatorSet;
        private CountDownView view;

        public ViewAnimatorWrap (CountDownView view){
            this.view = view;
            strokeAnimator = ObjectAnimator.ofFloat(view,"progress",0,number);
            strokeAnimator.setInterpolator(new LinearInterpolator());

            numberColorAnimator = ObjectAnimator.ofArgb(view,"color",numberStartColor,numberEndColor);
            animatorSet = new AnimatorSet();
            animatorSet.playTogether(strokeAnimator,numberColorAnimator);
            animatorSet.setDuration(number * delay);
            animatorSet.addListener(new Animator.AnimatorListener() {
                @Override
                public void onAnimationStart(Animator animation) {}

                @Override
                public void onAnimationEnd(Animator animation) {
                    if (listener != null)
                        listener.onComplete();
                }

                @Override
                public void onAnimationCancel(Animator animation) {}

                @Override
                public void onAnimationRepeat(Animator animation) {}
            });
        }

        public void star(){
            animatorSet.start();
        }
    }

现在的版本还比较简单,后续要陆续添加更多的样式和功能:
1. 多样的数字支持;
2. 文字动画;
3. 进度条样式;

希望大家在评论中多多给意见
详细代码github.countDown,对您有帮助的可以给个star;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值