这是一篇博文用来记录自己写的一个倒计时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;