剖析项目名称: CountdownView
剖析原项目地址:https://github.com/iwgang/CountdownView
剖析理由:只知其然而不知其所以然,如此不好。想要快速的进阶,不走寻常路,剖析开源项目,深入理解扩展知识,仅仅这样还不够,还需要如此:左手爱哥的设计模式,右手重构改善既有设计,如此漫长打坐,回过头再看来时的路,书已成山,相信翔哥说的,量变引起质变。
在不久前做的一个商城项目,有抢购活动的需求,活动没开始要倒计时,开始后到结束也要倒计时,虽然当时我捣鼓了一个自定义控件,效果也出来了,但是赶脚该控件写的太复杂了,逻辑调理不清晰,郁闷不已,前两天又发现该开源项目,下定决心学习一下,顺便重新梳理知识。下面上效果图:
CountdownView是一个Android 倒计时控件,使用Canvas绘制,支持多种样式,Android Studio导入:
compile 'com.github.iwgang:countdownview:1.2'
代码调用实例:
CountdownView mCvCountdownView = (CountdownView)findViewById(R.id.cv_countdownViewTest1);
mCvCountdownView.start(995550000);
/**或者自己编写倒计时逻辑,然后调用updateShow来更新UI*/
for (int time=0; time<1000; time++) {
mCvCountdownView.updateShow(time);
}
xxx.xml引用实例:
<cn.iwgang.countdownview.CountdownView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:isHideTimeBackground="true"
app:isShowDay="true"
app:isShowHour="true"
app:isShowMinute="true"
app:isShowSecond="true"
app:isShowMillisecond="true"
app:timeTextColor="#000000"
app:timeTextSize="22sp"
app:isTimeTextBold="true"
app:suffixGravity="bottom"
app:suffixTextColor="#000000"
app:suffixTextSize="12sp"
app:suffixHour="时"
app:suffixMinute="分"
app:suffixSecond="秒"
app:suffixMillisecond="毫秒" />
下面是相关自定义的属性表(属性名称、类型、默认值)
isHideTimeBackground boolean true 隐藏倒计时背景
timeBgColor color #444444 倒计时的背景色
timeBgSize dimension timeSize + 2dp * 4 倒计时背景大小
timeBgRadius dimension 0 倒计时背景的圆角
isShowTimeBgDivisionLine boolean true 倒计时的横向的分割线
timeBgDivisionLineColor color #30FFFFFF 倒计时的横向的分割线颜色
timeBgDivisionLineSize dimension 0.5dp 倒计时的横向的分割线高度
timeTextSize dimension 12sp 倒计时的文字大小
timeTextColor color #000000 倒计时的文字颜色
isTimeTextBold boolean false 倒计时文字所在的边框
isShowDay boolean 自动显示 (天 > 1 显示, = 0 隐藏)
isShowHour boolean 自动显示 (小时 > 1 显示, = 0 隐藏)
isShowMinute boolean true 是否显示分钟
isShowSecond boolean true 是否显示秒
isShowMillisecond boolean false 是否显示毫秒
suffixTextSize dimension 12sp 添加的分号:的大小
suffixTextColor color #000000 添加的分号:的颜色
isSuffixTextBold boolean false 添加的分号:的边框
suffixGravity 'top' or 'center' or 'bottom' 'center' 添加的分号:对齐方式
suffix string ':' 添加的分号:默认值
suffixDay string null 天默认值
suffixHour string null 时默认值
suffixMinute string null 分默认值
suffixSecond string null 秒默认值
suffixMillisecond string null 毫秒默认值
suffixLRMargin dimension left 3dp right 3dp 间距默认左右各3dp
suffixDayLeftMargin dimension 0
suffixDayRightMargin dimension 0
suffixHourLeftMargin dimension 0
suffixHourRightMargin dimension 0
suffixMinuteLeftMargin dimension 0
suffixMinuteRightMargin dimension 0
suffixSecondLeftMargin dimension 0
suffixSecondRightMargin dimension 0
suffixMillisecondLeftMargin dimension 0
由上面一堆自定义属性来看,哎妈呀,好多属性,构造函数的解析好多,真心感觉原作者他好累!!
当我么ListView 或者RecycleView等控件使用倒计时,会出现这个情况一个界面有多个倒计时,那么他的回调函数在Activity、Fragment里面只有一个,这是后我们可以选择添加Tag标签,回调函数onEnd里判断Tag值匹配执行响应函数,个人觉得在Adapter里面使用有个更好的方法,onEnd(CountdownView ,position)把position回调回来就简单多了,这里不细说,先看给控件添加Tag回调实例:
// 第1步,设置tag
mCvCountdownView.setTag(R.id.name, uid);
// 第2步,从回调中的CountdownView取回tag
@Override
public void onEnd(CountdownView cv) {
Object nameTag = cv.getTag(R.id.uid);
if (null != nameTag) {
Log.i(TAG, "name = " + nameTag.toString());
}
}
动态显示/隐藏某些时间 (如:开始显示时、分、秒,后面到指定时间改成分、秒、毫秒)
customTimeShow(boolean isShowDay,
boolean isShowHour,
boolean isShowMinute,
boolean isShowSecond,
boolean isShowMillisecond)
指定间隔时间回调和倒计时结束回调:
setOnCountdownIntervalListener(long interval, OnCountdownIntervalListener onCountdownIntervalListener);
setOnCountdownEndListener(OnCountdownEndListener onCountdownEndListener);
倒计时的抽象类CustomCountDownTimer ,调用其start、stop(同步方法)方法控制倒计时,Handler控制进度,重点在这里:
while (delay < 0) delay += mCountdownInterval;
sendMessageDelayed(obtainMessage(MSG), delay);
倒计时没结束继续发消息while循环直到结束,下面再来看正主CountdownView:
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);
this.mContext = context;
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.CountdownView);
mTimeBgColor = ta.getColor(R.styleable.CountdownView_timeBgColor, 0xFF444444);
mTimeBgRadius = ta.getDimension(R.styleable.CountdownView_timeBgRadius, 0);
//..........................属性解析略...........................
}
}
构造方法调用initPaint初始化画笔操作,两个个人平时不常用属性:setFakeBoldText(true);//true设定,false清除,这里是设置中文仿“粗体”-.setTextAlign(Paint.Align.CENTER);设置文字对齐方式,系统提供了几种,当这些不满足我们需求比如盖章那种类型的就需要Path配合绘制完成,这里不多说
private void initPaint() {
// time text
mTimeTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mTimeTextPaint.setColor(mTimeTextColor);
mTimeTextPaint.setTextAlign(Paint.Align.CENTER);
mTimeTextPaint.setTextSize(mTimeTextSize);
if (isTimeTextBold) {
mTimeTextPaint.setFakeBoldText(true);
}
//..........................更多Paint初始化略...........................
}
initSuffix(true);根据:天时分秒毫秒等是否显示初始化值,initSuffixMargin()初始化边距值,用到了dp转px,sp转px,真心佩服写着控件的主人,耐心真好!!初始化还在继续,测量文字以便于onDraw绘制(有人说这种测量方法有误差,配合mPaint.measureText()就完美了)
Rect rect = new Rect();
mTimeTextPaint.getTextBounds("00", 0, 2, rect);
mTimeTextWidth = rect.width();
mTimeTextHeight = rect.height();
mTimeTextBottom = rect.bottom;
构造函数说完该轮到onMeasure测量函数了
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//根据现实的元素计算总宽度
mContentAllWidth = getAllContentWidth();
//根据现实的元素计算总高度
mContentAllHeight = isHideTimeBackground ? (int) mTimeTextHeight : (int) mTimeBgSize;
//更具传入类型和MeasureSpec获取响应的mode size,计算宽高
mViewWidth = measureSize(1, mContentAllWidth, widthMeasureSpec);
mViewHeight = measureSize(2, mContentAllHeight, heightMeasureSpec);
//测量到控件的宽高后重新设置(setMeasuredDimension方法觉得view视图大小)
setMeasuredDimension(mViewWidth, mViewHeight);
//又开始一些列初始化了,计算这些值好累O(∩_∩)O~
initTimeTextBaselineAndTimeBgTopPadding();
initLeftPaddingSize();
initTimeBgRect();
}
measure测量个人感觉下面这种方式比较喜欢,以前我都全部一股脑的写在了onMeasure里面
private int measureSize(int specType, int contentSize, int measureSpec) {
int result;
//获取测量的模式和Size
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
result = Math.max(contentSize, specSize);
} else {
result = contentSize;
if (specType == 1) {
// 根据传人方式计算宽
result += (getPaddingLeft() + getPaddingRight());
} else {
// 根据传人方式计算高
result += (getPaddingTop() + getPaddingBottom());
}
}
return result;
}
太多的初始化操作了,太多的属性了,我终于发现我和大神的区别了,我的耐心和他们没法比,哎..再来看你onDraw绘制:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
float mHourLeft;
float mMinuteLeft;
float mSecondLeft;
if (isHideTimeBackground) {
// no background
//*****drawText 时分秒等略***
canvas.drawText(formatNum(mDay), mLeftPaddingSize + mDayTimeTextWidth / 2, mTimeTextBaseline, mTimeTextPaint);
//****根据是否显示时分秒等控制是否绘制*******
if (isShowSecond) {
// draw second text
canvas.drawText(formatNum(mSecond), mSecondLeft + mTimeTextWidth / 2, mTimeTextBaseline, mTimeTextPaint);
} else {
// 要绘制背景
if (isShowDay) {
// 绘制带圆角的
canvas.drawRoundRect(mDayBgRectF, mTimeBgRadius, mTimeBgRadius, mTimeTextBgPaint);
绘制横向的分割线
if (isShowTimeBgDivisionLine) {
// draw day background division line
canvas.drawLine(mLeftPaddingSize, mTimeBgDivisionLineYPos, mLeftPaddingSize + mDayTimeBgWidth, mTimeBgDivisionLineYPos, mTimeTextBgDivisionLinePaint);
}
//********************代码绘制太多都略过吧*********************
}
在我们调用countdownView.start方法本质是调用了辅助类的start方法:
/** * start countdown * @param millisecond millisecond */
public void start(long millisecond) {
if (millisecond <= 0) {
return ;
}
if (null != mCustomCountDownTimer) {
mCustomCountDownTimer.stop();
mCustomCountDownTimer = null;
}
long countDownInterval;
if (isShowMillisecond) {
countDownInterval = 10;
updateShow(millisecond);
} else {
countDownInterval = 1000;
}
mCustomCountDownTimer = new CustomCountDownTimer(millisecond, countDownInterval) {
@Override
public void onTick(long millisUntilFinished) {
updateShow(millisUntilFinished);
}
@Override
public void onFinish() {
// countdown end
allShowZero();
// 倒计时结束了回调该函数
if (null != mOnCountdownEndListener) {
mOnCountdownEndListener.onEnd(CountdownView.this);
}
}
};
mCustomCountDownTimer.start();
}
stop同理调用了辅助类的stop方法这里说了,这个开源项目看完,学到了许多,收获最大的是辅助类的运用,把倒计时时间分离到抽象类,自身需要处理的用接口回调,内部实例化辅助类,这样的代码结构清晰简洁,真实我需要的,我当时写这个类型控件全放在一个类里处理,乱糟糟的感觉。
参考资料
http://blog.csdn.net/mapdigit/article/details/7784035
*逗比的一天终于结束了,嘴强王者回家开鲁啦~O(∩_∩)O~*