最近项目要求做一个倒计时功能,进入答题界面前开始弹框倒计时3,2,1,倒计时完成弹框消失之后请求数据,答题计时开始10,9,8…3,2,1。
倒计时一直都用CountDownTimer,但是在界面展示进度和播放声音的时候总是有一些误差,开始计时的时候太快了以及执行onFinish()方法延迟,于是自己仿写了一个CountDownUtil用于倒计时操作。
CountDownUtil代码如下
package com.spring.countdowndemo;
import android.os.Handler;
import android.os.Message;
/**
* Created by Administrator on 2018/7/9.
* 自定义倒计时
*/
public class CountDownUtil {
private static final int MSG = 1;
/**
* 倒计时时长已毫秒为单位
*/
private final long mMillisInFuture;
/**
* 每隔多少毫秒记一次时
*/
private final long mCountdownInterval;
/**
* boolean representing if the timer was cancelled
*/
private boolean mCancelled = false;
private long mDuration;//时长 毫秒为单位
public CountDownUtil(long millisInFuture, long countDownInterval, CountDownListener countDownListener) {
mMillisInFuture = millisInFuture;
mCountdownInterval = countDownInterval;
mCountDownListener = countDownListener;
}
/**
* Cancel the countdown.
*/
public synchronized final void cancel() {
mCancelled = true;
mHandler.removeMessages(MSG);
}
/**
* 开始计时
*/
public synchronized final CountDownUtil start() {
mCancelled = false;
mDuration = mMillisInFuture;
if (mMillisInFuture <= 0) {
if (mCountDownListener != null) {
mCountDownListener.onFinish();
}
return this;
}
if (mCountDownListener != null) {
mCountDownListener.onTick(mDuration);
}
mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG), mCountdownInterval);
return this;
}
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (mCancelled) {
return;
}
mDuration -= mCountdownInterval;
if (mDuration <= 0) {
if (mCountDownListener != null) {
mCountDownListener.onFinish();
}
} else {
if (mCountDownListener != null) {
mCountDownListener.onTick(mDuration);
}
mHandler.sendMessageDelayed(obtainMessage(MSG), mCountdownInterval);
}
}
};
private CountDownListener mCountDownListener;
public void setCountDownListener(CountDownListener countDownListener) {
this.mCountDownListener = countDownListener;
}
public interface CountDownListener {
/**
* 倒计时
*/
void onTick(long millisUntilFinished);
/**
* 倒计时完成
*/
void onFinish();
}
}
倒计时弹框代码如下
package com.spring.countdowndemo;
import android.app.Dialog;
import android.content.Context;
import android.support.annotation.NonNull;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.ScaleAnimation;
import android.widget.TextView;
/**
* Created by Administrator on 2018/6/15.
* 倒计时弹框
*/
public class CountDownDialog extends Dialog implements CountDownUtil.CountDownListener {
private static final long DURATION = 3000;
private static final long INTERVAL = 1000;
private TextView tvCountdown;
private CountDownUtil timerCountDown;
private VolumeUtil volumeUtil;
private AlphaAnimation alphaAnimation;
private ScaleAnimation scaleAnimation;
public CountDownDialog(@NonNull Context context) {
this(context, R.style.customDialog);
}
public CountDownDialog(@NonNull Context context, int themeResId) {
super(context, themeResId);
Window window = getWindow();
window.setGravity(Gravity.CENTER); // 此处可以设置dialog显示的位置
WindowManager.LayoutParams params = window.getAttributes();
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
getWindow().setAttributes(params);
setCancelable(false);
setCanceledOnTouchOutside(false);
LayoutInflater factory = LayoutInflater.from(context);
View view = factory.inflate(R.layout.dialog_answer_countdown, null);
setContentView(view);
tvCountdown = findViewById(R.id.tv_countdown);
volumeUtil = new VolumeUtil();
alphaAnimation = new AlphaAnimation(0, 1);
scaleAnimation = new ScaleAnimation(0.5f, 1.5f, 0.5f, 1.5f,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
timerCountDown = new CountDownUtil(DURATION, INTERVAL, this);
timerCountDown.start();
}
@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
volumeUtil.releaseMusic();
if (alphaAnimation != null) {
alphaAnimation.cancel();
alphaAnimation = null;
}
if (scaleAnimation != null) {
scaleAnimation.cancel();
scaleAnimation = null;
}
}
@Override
public void onTick(long millisUntilFinished) {
volumeUtil.playMusic(getContext(), R.raw.audio1);
tvCountdown.setText(millisUntilFinished / INTERVAL + "");
// 设置透明度渐变动画
//设置动画持续时间
alphaAnimation.setDuration(INTERVAL / 2);
tvCountdown.startAnimation(alphaAnimation);
// 设置缩放渐变动画
scaleAnimation.setDuration(INTERVAL / 2);
tvCountdown.startAnimation(scaleAnimation);
}
@Override
public void onFinish() {
volumeUtil.playMusic(getContext(), R.raw.audio2);
dismiss();
}
}
弹框样式
倒计时控件代码如下
package com.spring.countdowndemo;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
/**
* Created by Administrator on 2018/6/11.
* 倒计时控件
*/
public class CountTimeView extends View {
// 画实心圆的画笔
private Paint mCirclePaint;
// 画圆环的画笔
private Paint mRingPaint;
// 画圆环的画笔背景色
private Paint mRingPaintBg;
// 画字体的画笔
private Paint mTextPaint;
// 圆形颜色
private int mCircleColor;
// 圆环颜色
private int mRingColor;
// 圆环背景颜色
private int mRingBgColor;
// 半径
private float mRadius;
// 圆环半径
private float mRingRadius;
//进度圆环半径
private float mProgressRadius;
// 圆环宽度
private float mBordWidth;
//进度圆环宽度
private float mProgressWidth;
// 圆心x坐标
private int mXCenter;
// 圆心y坐标
private int mYCenter;
// 字的长度
private float mTxtWidth;
// 字的高度
private float mTxtHeight;
//文字颜色
private int mTextColor;
//文字大小
private float mTextSize;
// 总进度
private long mTotalProgress = 100;
// 当前进度
private long mProgress = 100;
public CountTimeView(Context context, AttributeSet attrs) {
super(context, attrs);
// 获取自定义的属性
initAttrs(context, attrs);
initVariable();
}
//属性
private void initAttrs(Context context, AttributeSet attrs) {
TypedArray typeArray = context.getTheme().obtainStyledAttributes(attrs,
R.styleable.CountTimeView, 0, 0);
mRadius = typeArray.getDimension(R.styleable.CountTimeView_radius, 80);
mBordWidth = typeArray.getDimension(R.styleable.CountTimeView_bordWidth, 10);
mProgressWidth = typeArray.getDimension(R.styleable.CountTimeView_progressWidth, 10);
mCircleColor = typeArray.getColor(R.styleable.CountTimeView_circleColor, 0xFFFFFFFF);
mRingColor = typeArray.getColor(R.styleable.CountTimeView_ringColor, 0xFFFFFFFF);
mRingBgColor = typeArray.getColor(R.styleable.CountTimeView_ringBgColor, 0xFFFFFFFF);
mTextColor = typeArray.getColor(R.styleable.CountTimeView_text_color, 0xFFFFFFFF);
mTextSize = typeArray.getDimension(R.styleable.CountTimeView_text_size, 12);
mTotalProgress = typeArray.getInteger(R.styleable.CountTimeView_total_progress, 10);
mProgress = typeArray.getInteger(R.styleable.CountTimeView_progress, 10);
mRingRadius = mRadius + mBordWidth / 2;
mProgressRadius = mRadius + mProgressWidth / 2;
}
//初始化画笔
private void initVariable() {
//内圆
mCirclePaint = new Paint();
mCirclePaint.setAntiAlias(true);
mCirclePaint.setColor(mCircleColor);
mCirclePaint.setStyle(Paint.Style.FILL);
//外圆弧背景
mRingPaintBg = new Paint();
mRingPaintBg.setAntiAlias(true);
mRingPaintBg.setColor(mRingBgColor);
mRingPaintBg.setStyle(Paint.Style.STROKE);
mRingPaintBg.setStrokeWidth(mBordWidth);
//外圆弧
mRingPaint = new Paint();
mRingPaint.setAntiAlias(true);
mRingPaint.setColor(mRingColor);
mRingPaint.setStyle(Paint.Style.STROKE);
mRingPaint.setStrokeWidth(mProgressWidth);
mRingPaint.setStrokeCap(Paint.Cap.SQUARE);//设置线冒样式,有圆 有方
//中间字
mTextPaint = new Paint();
mTextPaint.setAntiAlias(true);
mTextPaint.setStyle(Paint.Style.FILL);
mTextPaint.setColor(mTextColor);
mTextPaint.setTextSize(mTextSize);
Paint.FontMetrics fm = mTextPaint.getFontMetrics();
mTxtHeight = (int) Math.ceil(fm.descent - fm.ascent);
}
//画图
@Override
protected void onDraw(Canvas canvas) {
mXCenter = getWidth() / 2;
mYCenter = getHeight() / 2;
//内圆
canvas.drawCircle(mXCenter, mYCenter, mRadius, mCirclePaint);
//外圆弧背景
RectF oval1 = new RectF();
oval1.left = (mXCenter - mRingRadius);
oval1.top = (mYCenter - mRingRadius);
oval1.right = mRingRadius * 2 + (mXCenter - mRingRadius);
oval1.bottom = mRingRadius * 2 + (mYCenter - mRingRadius);
canvas.drawArc(oval1, 0, 360, false, mRingPaintBg); //圆弧所在的椭圆对象、圆弧的起始角度、圆弧的角度、是否显示半径连线
//外圆弧
if (mProgress >= 0) {
RectF oval = new RectF();
oval.left = (mXCenter - mProgressRadius - (mBordWidth - mProgressWidth) / 2);
oval.top = (mYCenter - mProgressRadius - (mBordWidth - mProgressWidth) / 2);
oval.right = mProgressRadius * 2 + (mXCenter - mProgressRadius) + (mBordWidth - mProgressWidth) / 2;
oval.bottom = mProgressRadius * 2 + (mYCenter - mProgressRadius) + (mBordWidth - mProgressWidth) / 2;
canvas.drawArc(oval, -90, ((float) (mTotalProgress - mProgress) / mTotalProgress) * 360, false, mRingPaint); //
//字体
long count = mProgress;
String txt = String.valueOf(count);
mTxtWidth = mTextPaint.measureText(txt, 0, txt.length());
canvas.drawText(txt, mXCenter - mTxtWidth / 2, mYCenter + mTxtHeight / 4, mTextPaint);
}
}
/**
* 设置进度
*
* @param progress
*/
public void setProgress(long totalProgress, long progress) {
mTotalProgress = totalProgress;
mProgress = progress;
postInvalidate();//重绘
}
}
自定义属性如下:
<resources>
<declare-styleable name="CountTimeView">
<attr name="radius" format="dimension"/>
<attr name="bordWidth" format="dimension"/>
<attr name="progressWidth" format="dimension"/>
<attr name="circleColor" format="color"/>
<attr name="ringColor" format="color"/>
<attr name="ringBgColor" format="color"/>
<attr name="text_color" format="color"/>
<attr name="text_size" format="dimension"/>
<attr name="progress" format="integer"/>
<attr name="total_progress" format="integer"/>
declare-styleable>
resources>
声音播放控制
package com.spring.countdowndemo;
import android.content.Context;
import android.media.MediaPlayer;
/**
* Created by Administrator on 2018/7/9.
* 声音播放
*/
public class VolumeUtil {
private MediaPlayer mediaPlayer;
public void playMusic(Context context,int music) {
if (mediaPlayer != null) {
mediaPlayer.reset();
}
mediaPlayer = MediaPlayer.create(context, music);
mediaPlayer.start();
mediaPlayer.setVolume(1f, 1f);
}
public void releaseMusic() {
if (mediaPlayer != null) {
mediaPlayer.stop();
mediaPlayer.release();
mediaPlayer = null;
}
}
}
Activity中使用
package com.spring.countdowndemo;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
public class MainActivity extends AppCompatActivity implements CountDownUtil.CountDownListener {
private static final long DURATION = 10000;
private static final long INTERVAL = 1000;
private CountTimeView viewCount;
private CountDownDialog countDownDialog;
private CountDownUtil countDownUtil;
private VolumeUtil volumeUtil;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
viewCount = findViewById(R.id.view_count);
countDownUtil = new CountDownUtil(DURATION, INTERVAL, this);
volumeUtil = new VolumeUtil();
}
public void startCountDown(View view) {
countDownUtil.start();
}
public void startDialogCountDown(View view) {
countDownDialog = new CountDownDialog(this);
countDownDialog.show();
}
@Override
public void onTick(long millisUntilFinished) {
viewCount.setProgress(DURATION / INTERVAL, (long) (((double) millisUntilFinished / DURATION) * (DURATION / INTERVAL)));
volumeUtil.playMusic(this, R.raw.audio1);
}
@Override
public void onFinish() {
viewCount.setProgress(DURATION / INTERVAL, 0);
volumeUtil.playMusic(this, R.raw.audio2);
}
@Override
protected void onDestroy() {
super.onDestroy();
volumeUtil.releaseMusic();
}
}
项目源码地址https://github.com/qidingquan/CountDownDemo.git