目录
前言
因为最近项目里面需要记录通话时长,那么就要用到计时器,想着去网上搜一个现成的直接拿过来用呢,结果百度搜到的都是自定义倒计时控件,偶尔有几个是正向计时的,但是看了他们的实现都不太符合我的要求,所以最后只能自己动手写一个了。基本思路就是封装一个自定义的TextView,把计时的功能做到里面去,在需要计时的地方直接在XML里面全类名引用一下这个控件,然后代码里调用几个API就能搞定。既然思路已经确定,那就照着做吧。
一、项目效果展示
没有效果图总感觉自己是在耍流氓,所以还是录了一个,让大家能够更直观的感受一下这个控件是个什么玩意,如下图所示:
二、自定义TextView
关于自定义View相关的知识,这里并不会多讲,一是我们的重点不在这里,二是我本身掌握的也不好,自定义View这块是通往安卓高级工程师的必备技能,所以我还在努力。因此这里也会使用最简单的自定义View来封装这个控件,这里选用的是继承自系统控件TextView,所以这里不用重写onMeasure()方法去测量宽高,系统已经为我们测量好了,这样就极大的减轻了我们的工作量,那么接下来就正式来处理这个自定义View吧(简单级别的)。
2.1 自定义属性
关于自定义属性这个需要根据你自己的业务去考虑需要哪些属性,我这里就简单的写了两个,具体步骤如下:
在values文件夹下面新建一个attrs.xml文件,然后就可以定义我们需要的属性了,我这里只定义了文字大小和颜色:
<declare-styleable name="DigitalTimer">
<attr name="textColor" format="color" />
<attr name="textSize" format="dimension" />
</declare-styleable>
2.2 配置属性
定义一个类DigitalTimer继承自TextView,重写单参(代码中new的时候使用)和双参(布局中引入使用)的构造方法,然后开始配置需要的相关属性,代码如下:
private static final float DEFAULT_TEXT_SIZE = 12;
private static final int DEFAULT_TEXT_COLOR = Color.WHITE;
private float textSize;
private int textColor;
public DigitalTimer(Context context) {
super(context);
init();
}
public DigitalTimer(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DigitalTimer);
textSize = a.getDimension(R.styleable.DigitalTimer_textSize, DEFAULT_TEXT_SIZE);
textColor = a.getColor(R.styleable.DigitalTimer_textColor, DEFAULT_TEXT_COLOR);
a.recycle();
init();
}
//初始化
private void init() {
setTextSize(textSize);
setTextColor(textColor);
setGravity(Gravity.CENTER);
setBackgroundColor(Color.TRANSPARENT);
}
三、实现计时器
3.1 技术选型
这里选用的是rxjava中的interval操作符去实现的,链式调用看着就很舒服,先来了解一下interval操作符:
3.2 代码实现
这里调用interval操作符,构造方法里面分别传入了1,1,TimeUnit.SECONDS,分别表示延时1秒之后,每隔1秒发送一次数据,我们定义一个变量mCurrentSecond,用来记录当前的秒数,初始值为0,之后每次递增1,然后按照时分秒的转换关系进行相应的转换处理,最后把值设置到TextView上面去,具体代码如下:
//开始计时
public void start() {
mDisposable = Observable.interval(1, 1, TimeUnit.SECONDS)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<Long>() {
@Override
public void accept(Long aLong) throws Exception {
mCurrentSecond++;
int hour = mCurrentSecond / 3600;
int minute = (mCurrentSecond % 3600) / 60;
int second = mCurrentSecond % 60;
if (second < 10) { //处理秒
seconds = "0" + second;
} else {
seconds = String.valueOf(second);
}
if (minute < 10) { //处理分
minutes = "0" + minute;
} else {
minutes = String.valueOf(minute);
}
if (hour < 10) { //处理小时
hours = "0" + hour;
} else {
hours = String.valueOf(hour);
}
//设置数据
setText(String.format("%s:%s:%s", hours, minutes, seconds));
}
}, new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
LogUtil.e("自定义计时器数据异常------" + throwable.getMessage());
}
});
}
相应的还封装了停止计时和数据清零的方法,如下所示:
//停止计时
public void stop() {
LogUtil.e("当前计时时长----->" + getText().toString());
if (mDisposable != null) {
mDisposable.dispose();
}
}
//重置数据
@SuppressLint("SetTextI18n")
public void reset() {
mCurrentSecond = 0;
setText("00:00:00");
stop();
}
四、具体使用
4.1 布局引用
首先在布局文件中引用这个控件:
<com.nari.yihui.widget.DigitalTimer
android:id="@+id/timer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:textSize="@dimen/font_size_18"
app:textColor="@color/colorAccent"
android:text="00:00:00"/>
4.2 API调用
然后在对应的界面中调用开始计时、停止计时的方法就OK了:
@OnClick({R.id.button1, R.id.button2, R.id.button3})
public void onClick(View view) {
switch (view.getId()) {
case R.id.button1:
timer.start();
break;
case R.id.button2:
timer.stop();
break;
case R.id.button3:
timer.reset();
break;
}
}
这样就完成了这个控件的封装,是不是还挺简单的,如果发现了什么问题,欢迎给我留言。
附:控件源代码
package com.nari.yihui.widget;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.Gravity;
import android.widget.TextView;
import com.nari.yihui.R;
import com.nari.yihui.utils.LogUtil;
import java.util.concurrent.TimeUnit;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.Schedulers;
/**
* 包名:com.nari.yihui.widget
* 文件名: DigitalTimer
* 创建时间: 2018/7/25 11:29
* 作者: 纪安奇
* 作用:自定义计时器控件
*/
@SuppressWarnings({"RedundantThrows"})
@SuppressLint("AppCompatCustomView")
public class DigitalTimer extends TextView {
private static final float DEFAULT_TEXT_SIZE = 12;
private static final int DEFAULT_TEXT_COLOR = Color.WHITE;
private float textSize;
private int textColor;
private int mCurrentSecond = 0; //当前秒
private String hours, minutes, seconds; //展示的时分秒
private Disposable mDisposable;
public DigitalTimer(Context context) {
super(context);
init();
}
public DigitalTimer(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DigitalTimer);
textSize = a.getDimension(R.styleable.DigitalTimer_textSize, DEFAULT_TEXT_SIZE);
textColor = a.getColor(R.styleable.DigitalTimer_textColor, DEFAULT_TEXT_COLOR);
a.recycle();
init();
}
//初始化
private void init() {
setTextSize(textSize);
setTextColor(textColor);
setGravity(Gravity.CENTER);
setBackgroundColor(Color.TRANSPARENT);
}
//开始计时
public void start() {
mDisposable = Observable.interval(1, 1, TimeUnit.SECONDS)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<Long>() {
@Override
public void accept(Long aLong) throws Exception {
mCurrentSecond++;
int hour = mCurrentSecond / 3600;
int minute = (mCurrentSecond % 3600) / 60;
int second = mCurrentSecond % 60;
if (second < 10) { //处理秒
seconds = "0" + second;
} else {
seconds = String.valueOf(second);
}
if (minute < 10) { //处理分
minutes = "0" + minute;
} else {
minutes = String.valueOf(minute);
}
if (hour < 10) { //处理小时
hours = "0" + hour;
} else {
hours = String.valueOf(hour);
}
//设置数据
setText(String.format("%s:%s:%s", hours, minutes, seconds));
}
}, new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
LogUtil.e("自定义计时器数据异常------" + throwable.getMessage());
}
});
}
//停止计时
public void stop() {
LogUtil.e("当前计时时长----->" + getText().toString());
if (mDisposable != null) {
mDisposable.dispose();
}
}
//重置数据
@SuppressLint("SetTextI18n")
public void reset() {
mCurrentSecond = 0;
setText("00:00:00");
stop();
}
}