一种实现极简番茄时钟的思路
概述
最近跟着扔物线的自定义View教程重新复习了一波基础,但是API这种东西如果不用很容易就忘了,趁大脑还没触发GC之前,最好的记忆方式就是撸个Demo出来。iOS上有一款个人很喜欢的简约TODO应用叫极简待办,其中它的番茄时钟交互很适合用来练手。
分析
先看下iOS的效果图长啥样
功能
- 一个默认黑色的圆,一个灰色的圆,随着倒计时减少,灰色圆的弧度越来越大
- 手指在圆圈内滑动可以调整时间
心路历程
一步步来,先画一个黑圆以及显示时间文本,传入一个时间值后,可以实现倒计时功能。实现倒计时需要用到CountDownTimer,因此只要在该类的onTick中不断重绘就行了
public void start(){
new CountDownTimer(time * 1000 + 1000, 1000) {
@Override
public void onTick(long millisUntilFinished) {
time = (time * 1000 - 1000) / 1000;
textTime = time + "";
invalidate();
}
@Override
public void onFinish() {
}
}.start();
}
通过setTime方法提前设置一分钟,运行后可以看到界面开始倒计时,
接着,开始倒计时后,画灰色的圆弧,这里需要用到动画ValueAnimator,通过ValueAnimator的getAnimatedValue()方法获取实时的动画值,计算灰色圆弧扫过的区域,修改start()方法如下:
public void start(){
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1.0f);
valueAnimator.setDuration(time * 1000);
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
sweepVelocity = (float) animation.getAnimatedValue();
invalidate();
}
});
valueAnimator.start();
new CountDownTimer(time * 1000 + 1000, 1000) {
@Override
public void onTick(long millisUntilFinished) {
time = (time * 1000 - 1000) / 1000;
textTime = time + "";
invalidate();
}
@Override
public void onFinish() {
}
}.start();
}
onDraw中:
mRectF.set(centerX - radius, centerY - radius, centerX + radius, centerY + radius);
canvas.drawArc(mRectF, START_ANGLE, 360 * sweepVelocity, false, mPaint);
效果如下:
时间显示的有点挫,格式化下:
private String formatCountdownTime(int countdownTime) {
StringBuilder sb = new StringBuilder();
int minute = countdownTime / 60;
int second = countdownTime - 60 * minute;
if (minute < 10) {
sb.append("0" + minute + ":");
} else {
sb.append(minute + ":");
}
if (second < 10) {
sb.append("0" + second);
} else {
sb.append(second);
}
return sb.toString();
}
接下来就是实现上下滑动调整时间了,思路如下:
先确定一个最大值,这里取60分钟,我们可以获取到手指按下和滑动的坐标,如果这两个点的纵坐标差值刚好等于直径,那么显示的时间取最大值,其余的就在0-60的区间内,在滑动过程中执行重绘。
重写onTouchEvent方法:
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
touchX = x;
touchY = y;
break;
case MotionEvent.ACTION_MOVE:
offsetX = x - touchX;
offsetY = y - touchY;
time = (int) (offsetY / 2 / radius * MAX_TIME);
if (time <= 0) {
time = 0;
}
textTime = formatTime(time);
countdownTime = time * 60;
invalidate();
break;
}
return true;
}
运行效果如下:
时间是可以调整了,但是当前没有对滑动范围限制,看到最大时间超出60分钟,处理的思路很简单,就是判断点是否在圆内,那怎么判断触摸点是否在圆内呢?平面内两点间距离公式和半径比较即可。
private boolean isContained(float x, float y) {
if (Math.sqrt((x - centerX) * (x - centerX) + (y - centerY) * (y - centerY)) > radius) {
return false;
} else {
return true;
}
}
最终效果: