项目需要在启动页加上倒计时的功能,所以自定义了一个倒计时的View,下面的是具体的分析
1、自定义View的基础
一般情况下,自定义View可以有三种方式,
第一种:就是继承View或者ViewGroup来自己从头开始实现,
第二种:就是继承系统已经实现了特定功能的View或者ViewGroup,例如TextView,ImageView,LinearLayout等等,这样做的原因和好处就是,可以继承部分功能,在此基础上再进行自己需要的扩展
第三种:就是利用布局将一些View进行特定的组合来组成一个复合的组件
2、倒计时View的具体实现
因为本文的组件是使用第一种方式进行实现的,所以下面就是结合代码对第一种的实现方式进行分析,代码如下
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CountDownView">
<attr name="arc_circle_color" format="color"/>
<attr name="in_circle_color" format="color"/>
<attr name="txt_time_color" format="color"/>
</declare-styleable>
</resources>
上面的代码是自定义的属性,它放在values/attr.xml文件中,当然这个attr.xml文件需要我们自己创建,它的主要目的就是使我们可以在xml文件中进行属性的设置,如果自己实现的自定义View中没有自定义的属性,则这个可以忽略
public class CountDownView extends View {
//绘制内圆的画笔对象
private Paint mInCirclePaint = new Paint();
//绘制文字的画笔对象
private Paint mTxtPaint = new Paint();
//绘制圆弧的画笔对象
private Paint mArcPaint = new Paint();
//计时类
private Timer mTimer = null;
//外部圆当前绘制的弧度
private int currentAngle = 360;
//外部圆最终绘制的弧度
private int progress = 0;
//当前的描述,这里默认为4秒,不可修改
private int currentMillon = 4;
//外部圆的背景颜色
private int arcCircleColor;
//内部圆的背景颜色
private int inCircleColor;
//文字的颜色
private int txtTimeColor;
..............
}
以上是需要用到的属性,代码中都有详细的注释了
public class CountDownView extends View {
.......
public CountDownView(Context context) {
this(context, null);
}
public CountDownView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CountDownView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CountDownView,
defStyleAttr, 0);
arcCircleColor = a.getColor(R.styleable.CountDownView_arc_circle_color, Color.RED);
inCircleColor = a.getColor(R.styleable.CountDownView_in_circle_color, Color.parseColor
("#FFB7B6B6"));
txtTimeColor = a.getColor(R.styleable.CountDownView_txt_time_color, Color.WHITE);
a.recycle();
init();
}
private void init() {
mTimer = new Timer();
}
......
}
接下来是三个构造函数,这里是最先被初始化的地方,所以在这里我们要把在attr.xml文件中的自定义属性取出来,并且设置默认值
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width = 0;
int height = 0;
if (widthMode == MeasureSpec.EXACTLY) {
width = widthSize;
} else {
width = (int) ViewUtils.dp2px(50);
}
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
} else {
if (getLayoutParams().height == WindowManager.LayoutParams.WRAP_CONTENT) {
height = (int) ViewUtils.dp2px(50);
} else {
height = heightSize;
}
}
if (width <= height) {
setMeasuredDimension(width, width);
} else {
setMeasuredDimension(height, height);
}
}
接下来就是自定义View中比较重要的一个方法,这个方法的主要作用就是测量,它的两个传入的参数分别是由父布局测量后传递下来的测量宽度和测量高度,这个测量宽度包括了宽度的大小和测量模式,而测量高度也是一样。这个方法的主要作用就是测量View的大小,如果说自定义View是画画的话,那么这个方法就是先测量出一块画布的大小,以便我们后续在这个画布上进行作画
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//获取屏幕的宽度
int width = getMeasuredWidth();
//获取屏幕的高度
int height = getMeasuredHeight();
//绘制内部的圆
mInCirclePaint.setStyle(Paint.Style.FILL);
mInCirclePaint.setAntiAlias(true);
mInCirclePaint.setColor(inCircleColor);
canvas.drawCircle(width / 2, height / 2, width / 2 - 10, mInCirclePaint);
//绘制文字
int mTxtSize = (int) ViewUtils.dp2px(14);
mTxtPaint.setTextSize(mTxtSize);
Rect mBound = new Rect();
mTxtPaint.getTextBounds(String.valueOf(currentMillon), 0, String.valueOf(currentMillon)
.length(), mBound);
mTxtPaint.setColor(txtTimeColor);
canvas.drawText(String.valueOf(currentMillon), width / 2 - mBound.width() / 2, height / 2
+ mBound.height() / 2, mTxtPaint);
// 绘制圆弧
mArcPaint.setStrokeWidth(10);
mArcPaint.setStyle(Paint.Style.STROKE);
mArcPaint.setAntiAlias(true);
mArcPaint.setColor(arcCircleColor);
RectF rect = new RectF(10, 10, width - 10, height - 10);
canvas.drawArc(rect, -90, currentAngle, false, mArcPaint);
}
这个方法是自定义View中的另一个比较重要的方法,它的主要作用就是绘制具体的内容,在这个方法里面,我们会持有画布的对象,因此我们可以利用系统提供的api来绘制出你想要的图形,上面的代码就绘制了一个圆,文字,圆弧。那这一步就相当于作画中的在画布上画画了。
3、最后一步
经过前面的代码,我们可以绘制出倒计时View的大致的样子,但是怎样实现倒计时呢,代码如下:
/**
* 动画开始
*/
public void start() {
mTimer.schedule(new TimerTask() {
@Override
public void run() {
postInvalidate();
if (currentAngle <= progress) {
//到这里,自动执行的任务已经结束,在这里我们可以定义回调接口来进行特定的处理
mTimer.cancel();
} else {
currentAngle -= 5;
}
if (currentAngle % 90 == 0 && currentMillon > 0) {
currentMillon--;
}
}
}, 50, 50);
}
以上的代码就是倒计时之行的关键,它主要是这样的,在onDraw方法中先在外部绘制出一个圆满的圆形,但是绘制的弧度(currentAngle)是一个变量,我们在定时任务中不断将这个变量进行自减,并且进行View的重绘(通过postInvalidate()方法),这样就可以实现圆形图案缩减的动画了。另外,上面代码中的秒数的计算只是我粗略的计算,并不算准确,有兴趣可以实现更佳精确的计算方法