效果预览
目前项目中用到了一个倒计时控件, 觉的还不错. 所以分享出来. 有需要的同学可以直接拿去用. 废话不多说, 先看看效果:
分析需求
实现一个自定义控件, 先分析控件的初始状态和构成.
-
初始状态:
分析这个控件发现这个控件是一个整体 不是分散 控制的view... (⊙o⊙)…(什么是分散控制的view.常见的比如说滑动开关之类的) 既然如此 可知这个view应该是继承View的而不是ViewGroup.
- 控制状态
这个自定义view需要根据时间倒计时. 外层有个圈圈显示进度 内层有文本数字显示倒计时.倒计时就需要计时器 我用的是CountDownTimer来实现倒计时功能的.
代码实现
- 第一步: 继承View,重写构造方法. 初始化一些参数.如下:
public LoopView(Context context) {
this(context, null);
}
public LoopView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public LoopView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mDisplayMetrics = getContext().getResources().getDisplayMetrics();
t = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, t, mDisplayMetrics);
//环的画笔
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
float strokeWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4, mDisplayMetrics);
mPaint.setStrokeWidth(strokeWidth);//圆圈的线条粗细
//文本画笔
mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mTextPaint.setColor(Color.WHITE);
//默认总时间
mM = String.valueOf(mTotalTime / 1000 / 60);
mS = "00";
setClickable(true);
}
- 第二步: 构造方法走完后就走 onMeasure onDraw方法了 这时候我们就要重写这些方法了.
1.onMeasure 方法中要拿到控件的宽高信息 方便后面绘制的时候用到. 控件的范围采用矩形来约束
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mMWidth = getMeasuredWidth();
mHeight = getMeasuredHeight();
int mHigh = mMWidth;
//范围
mRectF = new RectF(t, t, mMWidth - t, mHigh - t);
}
2.onDraw方法中就要绘制了. 控件在倒计时的时候外层有变化. 里面也有变化. 这些变化都应该是更具剩余时间百分比来的. 而背景 剩余时间 这些元素没有发生变化
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//整体背景
mPaint.setStyle(Paint.Style.FILL); //绘制图形的描边
mPaint.setColor(getResources().getColor(R.color.black_27));
canvas.drawArc(mRectF, -90, 360, false, mPaint);
//外环 背景绿色
mPaint.setStyle(Paint.Style.STROKE); //绘制图形的描边
mPaint.setColor(getResources().getColor(R.color.blue_86));
canvas.drawArc(mRectF, -90, 360, false, mPaint);
//剩余时间 文字
String s = "剩余时间";
float ts = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 10, mDisplayMetrics);
mTextPaint.setTextSize(ts);
mTextPaint.setColor(getResources().getColor(R.color.text_color_99));
float x = mTextPaint.measureText(s);
canvas.drawText(s, mMWidth / 2 - x / 2, (float) mHeight * 0.4f, mTextPaint);
// 动态改变的部分
//内环 背景灰色
mPaint.setColor(getResources().getColor(R.color.gray_c1));
mPaint.setStrokeCap(Paint.Cap.SQUARE);
float swap = mPersent * 360;
canvas.drawArc(mRectF, -90, swap, false, mPaint);
// 绘制进度文案显示
String text = mM + ":" + mS;
float textSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 21, mDisplayMetrics);
float textWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 7, mDisplayMetrics);
mTextPaint.setStrokeWidth(textWidth);
mTextPaint.setTextSize(textSize);
mTextPaint.setColor(getResources().getColor(R.color.text_color_ff));
mTextPaint.setStyle(Paint.Style.FILL);
float width = mTextPaint.measureText(text);
canvas.drawText(text, mMWidth / 2 - width / 2, (float) mHeight * 0.65f, mTextPaint);
}
- 第三步: 控件的倒计时运行. 使用计时器 然后一秒钟回调一次onDraw方法 就能达到效果
/**
* 开始倒计时
*/
public void starTimecount() {
if (mCountDownTimer != null) {
mCountDownTimer.cancel();
}
mRemineTime = mRemineTime == -1 ? mTotalTime : mRemineTime;
mCountDownTimer = new CountDownTimer(mRemineTime, 1000) {
@Override
public void onTick(long millisUntilFinished) {
mPersent = (mTotalTime - millisUntilFinished) / (float)mTotalTime;
Log.i("zmin.............", ".persent..." + mPersent);
changeForTimeText((int) millisUntilFinished / 1000);
invalidate();
}
@Override
public void onFinish() {
mPersent = 1;
changeForTimeText(0);
invalidate();
if (mOnTimeCountListener != null) {
mOnTimeCountListener.finish();
}
}
};
mCountDownTimer.start();
}
- 第四步: 实现了界面. 还需要让控件可以被外部控制. 一个倒计时控件 需要被设置总时间 和剩余时间.
/**
* 设置总时间时间
*
* @param second 秒
*/
public void setTotalTime(int second) {
mTotalTime = second * 1000;
changeForTimeText(second);
}
/**
* 设置倒计时时间
*
* @param second 剩余时间单位 秒
*/
public void setRemineTime(int second) {
if (mCountDownTimer != null) {
mCountDownTimer.cancel();
mCountDownTimer = null;
}
if (second > mTotalTime / 1000) {
try {
throw new Exception("the time your set is less than total time, please reset it ");
} catch (Exception e) {
e.printStackTrace();
}
return;
}
mRemineTime = second * 1000;
changeForTimeText(second);
starTimecount();
}
总结
实现这个自定义控件主要分析其中的逻辑. 找到总时间、剩余时间、控件外圈进度条和倒计时时间文本之间的关系 然后一步步写。 应该还是比较简单的~~
Demo地址: https://github.com/zmin666/LoopView
如果对你有帮助 欢迎star ~~