前言:这应该算正经的第一次写博客,之前应该一直都算是码农,虽然能够把拿来的代码修改为自己期望的效果,但是总感觉这样子下去技术得不到突破,下定决心开始记录自己的学习路程。
端游或者手游中的技能冷却倒计时大家都见过吧
今天我们就来实现一下,先来看一下最终的效果图。源码在文章末尾
新建ColdDownTimer类
对应MainActivity.java如下
package com.lxp.android.colddowntimer;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import java.text.DecimalFormat;
public class MainActivity extends AppCompatActivity {
private TextView countTimeTv;
private ColdDownTimer coldDownTimer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
countTimeTv = (TextView) findViewById(R.id.countDownTime_tv);
coldDownTimer = (ColdDownTimer) findViewById(R.id.coldDownTimer);
coldDownTimer.setWaitHint("充能中...");// 冷却中提示
coldDownTimer.setCountTime(10);// 总冷却时间
// coldDownTimer.setCurCountTime(5);// 设置当前冷却时间
coldDownTimer.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
coldDownTimer.startCountdown();
}
});
coldDownTimer.setOnCountDownTimeListener(new ColdDownTimer.CountDownTimeListener() {
@Override
public void getCurCountDownTime(int time) {
if (time > 0) {
countTimeTv.setText(Utils.transferTime(time));
countTimeTv.setVisibility(View.VISIBLE);
} else {
countTimeTv.setVisibility(View.GONE);
}
}
@Override
public void countDownFinish(){
countTimeTv.setVisibility(View.GONE);
}
});
}
}
主要就是申明控件,设置一些方法和监听类。
对应main_activity.xml
很简单的布局,就是用FrameLayout把自定义控件和显示时间包起来,这里有用到自定义布局属性,所要做的就是要在values中的attrs文件定义一下我们控件的布局属性,没有attrs文件的话,new一个就好了。相信大部分小伙伴都明白
ColdDownTimer.java
package com.lxp.android.colddowntimer;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.animation.LinearInterpolator;
import android.widget.ImageView;
import android.widget.Toast;
/**
* 冷却时间倒计时
*
* @author by LXP
*/
public class ColdDownTimer extends ImageView {
private Context mContext;
private ScaleType SCALE_TYPE = ScaleType.CENTER_CROP;
private Bitmap.Config BITMAP_CONFIG = Bitmap.Config.ARGB_8888;
private int COLORDRAWABLE_DIMENSION = 1;
private RectF mDrawableRect = new RectF();
private Matrix mShaderMatrix = new Matrix();
private Paint mBitmapPaint = new Paint();
private Paint mArcPaint = new Paint();
private Bitmap mBitmap;
private BitmapShader mBitmapShader;
private int mBitmapWidth;
private int mBitmapHeight;
private float mDrawableRadius;
private boolean mReady;
private boolean mSetupPending;
/**
* 冷却中提示
*/
private String waitHint;
/**
* 已过时间百分比
*/
private float timePercent;
/**
* 当前冷却时间
*/
private int curCountDownTime;
/**
* 总冷却时间(默认5秒)
*/
private int countdownTime = 5;
/**
* 动画持续时间
*/
private int animDuration;
private boolean isCountOver = true;
private CountDownTimeListener countDownTimeListener;
private Handler handler = new Handler();
public ColdDownTimer(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ColdDownTimer(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
super.setScaleType(SCALE_TYPE);
this.mContext = context;
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.ColdDownTimer, defStyle, 0);
waitHint = a.getString(R.styleable.ColdDownTimer_waitHint);
a.recycle();
mReady = true;
if (mSetupPending) {
setup();
mSetupPending = false;
}
}
@Override
public ScaleType getScaleType() {
return SCALE_TYPE;
}
public boolean isCountDownOver() {
return isCountOver;
}
/**
* 设置当前冷却时间
*
* @param time
* @throws Exception
*/
public void setCurCountTime(int time) {
curCountDownTime = animDuration = time;
if (curCountDownTime > countdownTime) {
throw new IllegalArgumentException(Utils.getFunctionName(mContext, "当前冷却时间大于总冷却时间"));
}
}
/**
* 设置总冷却时间
*
* @param time
* @throws Exception
*/
public void setCountTime(int time) {
countdownTime = time;
if (curCountDownTime > countdownTime) {
throw new IllegalArgumentException(Utils.getFunctionName(mContext, "当前冷却时间大于总冷却时间"));
}
}
/**
* 设置冷却中提示
*
* @param waitHint
*/
public void setWaitHint(String waitHint) {
this.waitHint = waitHint;
}
/**
* 设置冷却监听器
*
* @param countDownTimeListener
*/
public void setOnCountDownTimeListener(
CountDownTimeListener countDownTimeListener) {
this.countDownTimeListener = countDownTimeListener;
}
@Override
protected void onDraw(Canvas canvas) {
if (getDrawable() == null) {
return;
}
canvas.drawCircle(getWidth() / 2, getHeight() / 2, mDrawableRadius,
mBitmapPaint);
if (!isCountOver) {
canvas.drawArc(mDrawableRect, 270, -(360 * (1 - timePercent)),
true, mArcPaint);
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
setup();
}
@Override
public void setImageBitmap(Bitmap bm) {
super.setImageBitmap(bm);
mBitmap = bm;
setup();
}
@Override
public void setImageDrawable(Drawable drawable) {
super.setImageDrawable(drawable);
mBitmap = getBitmapFromDrawable(drawable);
setup();
}
@Override
public void setImageResource(int resId) {
super.setImageResource(resId);
mBitmap = getBitmapFromDrawable(getDrawable());
setup();
}
private Bitmap getBitmapFromDrawable(Drawable drawable) {
if (drawable == null) {
return null;
}
if (drawable instanceof BitmapDrawable) {
return ((BitmapDrawable) drawable).getBitmap();
}
try {
Bitmap bitmap;
if (drawable instanceof ColorDrawable) {
bitmap = Bitmap.createBitmap(COLORDRAWABLE_DIMENSION,
COLORDRAWABLE_DIMENSION, BITMAP_CONFIG);
} else {
bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
drawable.getIntrinsicHeight(), BITMAP_CONFIG);
}
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
} catch (OutOfMemoryError e) {
return null;
}
}
private void setup() {
if (!mReady) {
mSetupPending = true;
return;
}
if (mBitmap == null) {
return;
}
mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP,
Shader.TileMode.CLAMP);
mBitmapPaint.setAntiAlias(true);
mBitmapPaint.setShader(mBitmapShader);
mArcPaint.setAntiAlias(true);
mArcPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mArcPaint.setAlpha(122);
mBitmapHeight = mBitmap.getHeight();
mBitmapWidth = mBitmap.getWidth();
mDrawableRect.set(0, 0, getWidth(), getHeight());
mDrawableRadius = Math.min(mDrawableRect.height() / 2,
mDrawableRect.width() / 2);
updateShaderMatrix();
invalidate();
}
private void updateShaderMatrix() {
float scale;
mShaderMatrix.set(null);
if (mBitmapWidth * mDrawableRect.height() > mDrawableRect.width()
* mBitmapHeight) {
scale = mDrawableRect.height() / (float) mBitmapHeight;
} else {
scale = mDrawableRect.width() / (float) mBitmapWidth;
}
mShaderMatrix.setScale(scale, scale);
mBitmapShader.setLocalMatrix(mShaderMatrix);
}
/**
* 开始倒计时
*/
public void startCountdown() {
if (!isCountOver) {
Toast.makeText(mContext, waitHint, Toast.LENGTH_SHORT).show();
} else {
if (curCountDownTime == 0) {
curCountDownTime = animDuration = countdownTime;
}
isCountOver = false;
ValueAnimator valueA = getValueAnimator();
valueA.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
timePercent = (Float) animation.getAnimatedValue();
invalidate();
}
});
valueA.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
if (curCountDownTime > 0) {
isCountOver = false;
} else {
if (null != countDownTimeListener) {
countDownTimeListener.countDownFinish();
}
curCountDownTime = animDuration;
isCountOver = true;
handler.removeCallbacks(runnable);
}
}
});
valueA.start();
handler.post(runnable);
}
}
/**
* 获取值动画
*
* @return
*/
private ValueAnimator getValueAnimator() {
ValueAnimator valueAnimator = ValueAnimator.ofFloat(
(1 - (float) curCountDownTime / countdownTime), 1.F);
valueAnimator.setDuration(animDuration * 1000);
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.setRepeatCount(0);
return valueAnimator;
}
Runnable runnable = new Runnable() {
@Override
public void run() {
if (null != countDownTimeListener) {
countDownTimeListener.getCurCountDownTime(curCountDownTime);
}
curCountDownTime--;
handler.postDelayed(this, 1000);
}
};
public interface CountDownTimeListener {
/**
* 获取当前冷却时间
* @param time
*/
void getCurCountDownTime(int time);
/**
* 冷却计时结束
*/
void countDownFinish();
}
}
大家都知道,自定义控件基本都要重写onDraw方法,我们来看看onDraw中的方法
@Override
protected void onDraw(Canvas canvas) {
if (getDrawable() == null) {
return;
}
canvas.drawCircle(getWidth() / 2, getHeight() / 2, mDrawableRadius,
mBitmapPaint);
if (!isCountOver) {
canvas.drawArc(mDrawableRect, 270, -(360 * (1 - timePercent)),
true, mArcPaint);
}
}
用mBitmapPaint来画圆,圆心的坐标分别是getWidth()/2和getHeight()/2,半径mDrawableRadius
判断isCountOver倒计时是否结束,来确定是否画弧线。
画弧线的画笔mArcPaint配置
mArcPaint.setAntiAlias(true);//设置抗锯齿效果
mArcPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mArcPaint.setAlpha(122);
抗锯齿最好设置为true,不然冷却动画边缘会有锯齿效果。
注意:这里的画笔的style要设置为Paint.Style.FILL_AND_STROKE或者Paint.Style.FILL,不能设置成Paint.Style.STROKE
不明白的小伙伴可以去了解一下Paint类,这里有传送门
接下来就可以开始倒计时了
/**
* 开始倒计时
*/
public void startCountdown() {
if (!isCountOver) {
Toast.makeText(mContext, waitHint, Toast.LENGTH_SHORT).show();
} else {
if (curCountDownTime == 0) {
curCountDownTime = animDuration = countdownTime;
}
isCountOver = false;
ValueAnimator valueA = getValueAnimator();
valueA.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
timePercent = (Float) animation.getAnimatedValue();
invalidate();
}
});
valueA.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
if (curCountDownTime > 0) {
isCountOver = false;
} else {
if (null != countDownTimeListener) {
countDownTimeListener.countDownFinish();
}
curCountDownTime = animDuration;
isCountOver = true;
handler.removeCallbacks(runnable);
}
}
});
valueA.start();
handler.post(runnable);
}
}
其中
if (curCountDownTime == 0) {
curCountDownTime = animDuration = countdownTime;
}
这里是为了判断是否设置了当前冷却时间,如果没设置的话,把总冷却时间赋值给当前冷却时间。
通过getValueAnimator()获取ValueAnimator来达到倒计时的动画效果ValueAnimator基本使用
/**
* 获取值动画
*
* @return
*/
private ValueAnimator getValueAnimator() {
ValueAnimator valueAnimator = ValueAnimator.ofFloat(
(1 - (float) curCountDownTime / countdownTime), 1.F);
valueAnimator.setDuration(animDuration * 1000);
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.setRepeatCount(0);
return valueAnimator;
}
这里动画值的范围设置为(1-(float)curCountDownTime/countdownTime,1),curCountDownTime等于countdownTime,取值范围从0到1,插值器使用new LinearInterpolator()均匀变化。
最后,还需要一个Runnable来启动倒计时
Runnable runnable = new Runnable() {
@Override
public void run() {
if (null != countDownTimeListener) {
countDownTimeListener.getCurCountDownTime(curCountDownTime);
}
curCountDownTime--;
handler.postDelayed(this, 1000);
}
};
最后的最后就是设置一个监听接口CountDownTimeListener把我们需要的参数放出去
public interface CountDownTimeListener {
/**
* 获取当前冷却时间
* @param time
*/
void getCurCountDownTime(int time);
/**
* 冷却计时结束
*/
void countDownFinish();
}
coldDownTimer = (ColdDownTimer) findViewById(R.id.coldDownTimer);
coldDownTimer.setWaitHint("充能中...");// 冷却中提示
coldDownTimer.setCountTime(10);// 总冷却时间
// coldDownTimer.setCurCountTime(5);// 设置当前冷却时间
coldDownTimer.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
coldDownTimer.startCountdown();
}
});
coldDownTimer.setOnCountDownTimeListener(new ColdDownTimer.CountDownTimeListener() {
@Override
public void getCurCountDownTime(int time) {
if (time > 0) {
countTimeTv.setText(Utils.transferTime(time));
countTimeTv.setVisibility(View.VISIBLE);
} else {
countTimeTv.setVisibility(View.GONE);
}
}
@Override
public void countDownFinish(){
countTimeTv.setVisibility(View.GONE);
}
});
如果不想从设置的总冷却时间开始计时,可以配置
coldDownTimer.setCurCountTime(5);// 设置当前冷却时间
好啦,这样我们的自定义圆形图片冷却倒计时就做好了,非常简单的一个小例子。写博客没经验,感觉描述的还是比较乱,就当练手了
源码传送地址