龙空技术网

Android自定义控件 倒计时

码仔 241

前言:

此时小伙伴们对“app时间控件不显示分钟”大概比较重视,看官们都需要知道一些“app时间控件不显示分钟”的相关文章。那么小编在网络上搜集了一些有关“app时间控件不显示分钟””的相关资讯,希望朋友们能喜欢,兄弟们快快来了解一下吧!

点击上方蓝字关注公众号

码个蛋第273次推文

倒数5个数~

作者:容华谢后

博客:

文章目录

写在前面

实现

初始化一些数据

定义控件的大小

绘制刻度盘

绘制刻度盘

绘制定时进度条

绘制时间

滑动事件

写在最后

GitHub传送门()

写在前面

本篇文章实现了一个简单的倒计时控件,主要运用了画布的操作,滑动角度计算等知识点,非常适合自定义控件的初学者进行学习,看下效果图:

1

实现

初始化一些数据

public class CountdownView extends View {

// 控件宽

private int width;

// 控件高

private int height;

// 刻度盘半径

private int dialRadius;

// 小时刻度高

private float hourScaleHeight = dp2px(6);

// 分钟刻度高

private float minuteScaleHeight = dp2px(4);

// 定时进度条宽

private float arcWidth = dp2px(6);

// 时间-分

private int time = 0;

// 刻度盘画笔

private Paint dialPaint;

// 时间画笔

private Paint timePaint;

// 是否移动

private boolean isMove;

// 当前旋转的角度

private float rotateAngle;

// 当前的角度

private float currentAngle;

// 时间改变监听

private OnCountdownListener onCountdownListener;

public CountdownView(Context context) {

this(context, );

}

public CountdownView(Context context, AttributeSet attrs) {

this(context, attrs, 0);

}

public CountdownView(Context context, AttributeSet attrs, int defStyleAttr) {

super(context, attrs, defStyleAttr);

init;

}

private void init {

// 刻度盘画笔

dialPaint = new Paint;

dialPaint.setAntiAlias(true);

dialPaint.setColor(Color.parseColor("#94C5FF"));

dialPaint.setStyle(Paint.Style.STROKE);

dialPaint.setStrokeCap(Paint.Cap.ROUND);

// 时间画笔

timePaint = new Paint;

timePaint.setAntiAlias(true);

timePaint.setColor(Color.parseColor("#94C5FF"));

timePaint.setTextSize(sp2px(33));

timePaint.setStyle(Paint.Style.STROKE);

}

...

}

定义控件的大小

@Override

protected void onSizeChanged(int w, int h, int oldw, int oldh) {

super.onSizeChanged(w, h, oldw, oldh);

// 控件宽、高

width = height = Math.min(h, w);

// 刻度盘半径

dialRadius = (int) (width / 2 - dp2px(10));

}

绘制刻度盘

/**

* 绘制刻度盘

*

* @param canvas 画布

*/

private void drawDial(Canvas canvas) {

// 绘制外层圆盘

dialPaint.setStrokeWidth(dp2px(2));

canvas.drawCircle(width / 2, height / 2, dialRadius, dialPaint);

// 将坐标原点移到控件中心

canvas.translate(getWidth / 2, getHeight / 2);

canvas.save;

// 绘制小时刻度

for (int i = 0; i < 12; i++) {

// 定时时间为0时正常绘制小时刻度

// 小时刻度没有被定时进度条覆盖时正常绘制小时刻度

if (time == 0 || i > time / 5) {

canvas.drawLine(0, -dialRadius, 0, -dialRadius + hourScaleHeight, dialPaint);

}

// 360 / 12 = 30;

canvas.rotate(30);

}

// 绘制分钟刻度

dialPaint.setStrokeWidth(dp2px(1));

for (int i = 0; i < 60; i++) {

// 小时刻度位置不绘制分钟刻度

// 分钟刻度没有被定时进度条覆盖时正常绘制分钟刻度

if (i % 5 != 0 && i > time) {

canvas.drawLine(0, -dialRadius, 0, -dialRadius + minuteScaleHeight, dialPaint);

}

// 360 / 60 = 6;

canvas.rotate(6);

}

}

首先绘制一个圆,然后把坐标原点移动到控件中心,原点移动到控件中心后向上为负值,接着绘制小时刻度,一共有12个刻度,time的单位为分钟,要注意如果刻度被定时进度条覆盖就不再绘制,绘制分钟刻度同理,代码中已经写了很全的注释,不再多说,看下效果:

绘制定时进度条

/**

* 绘制定时进度条

*

* @param canvas 画布

*/

private void drawArc(Canvas canvas) {

if (time > 0) {

// 绘制起始标志

dialPaint.setStrokeWidth(dp2px(3));

canvas.drawLine(0, -dialRadius - hourScaleHeight, 0, -dialRadius + hourScaleHeight, dialPaint);

// 取消直线圆角设置

dialPaint.setStrokeCap(Paint.Cap.BUTT);

// 绘制圆弧

float arcWidth = dp2px(6);

for (int i = 0; i <= time * 6; i++) {

canvas.drawLine(0, -dialRadius - arcWidth / 2, 0, -dialRadius + arcWidth / 2, dialPaint);

// 最后一次不旋转画布

if (i != time * 6) {

canvas.rotate(1);

}

}

// 绘制结束标志

dialPaint.setStrokeCap(Paint.Cap.ROUND);

canvas.drawLine(0, -dialRadius - hourScaleHeight, 0, -dialRadius + hourScaleHeight, dialPaint);

}

}

如果定时时间大于0则开始绘制定时进度条,重点说下绘制进度,在这里并没有使用绘制圆弧的方法,依然是通过旋转画布的方式绘制的,设置一个15分钟的进度,看下效果:

绘制时间

/**

* 绘制时间

*

* @param canvas 画布

*/

private void drawTime(Canvas canvas) {

canvas.restore;

String timeText = String.format(Locale.CHINA, "%02d", time) + " : 00";

// 获取时间的宽高

float timeWidth = timePaint.measureText(timeText);

float timeHeight = Math.abs(timePaint.ascent + timePaint.descent);

// 居中显示

canvas.drawText(timeText, -timeWidth / 2, timeHeight / 2, timePaint);

}

在控件中心绘制一段文本,重点在于如何获取文本的宽高,宽度直接测量就可以了,高度比较特殊,因为绘制的是数字,所以使用Math.abs(timePaint.ascent + timePaint.descent);这种方式来获取文本高度,先挖个坑,下一篇文章详细讲一下文本的绘制,看下效果:

滑动事件

@Override

public boolean onTouchEvent(MotionEvent event) {

switch (event.getAction) {

case MotionEvent.ACTION_DOWN:

// 按下的角度

currentAngle = calcAngle(event.getX, event.getY);

break;

case MotionEvent.ACTION_MOVE:

// 标记正在移动

isMove = true;

// 移动的角度

float moveAngle = calcAngle(event.getX, event.getY);

// 滑过的角度偏移量

float angleOffset = moveAngle - currentAngle;

// 防止越界

if (angleOffset < -270) {

angleOffset = angleOffset + 360;

} else if (angleOffset > 270) {

angleOffset = angleOffset - 360;

}

currentAngle = moveAngle;

// 计算时间

calcTime(angleOffset);

break;

case MotionEvent.ACTION_CANCEL:

case MotionEvent.ACTION_UP: {

if (isMove && onCountdownListener != ) {

// 回调倒计时改变方法

onCountdownListener.countdown(time);

isMove = false;

}

break;

}

}

return true;

}

通过计算滑过的角度增量来设置当前的定时时间,看下如何来计算当前触摸点的角度:

前方高能,请减速慢行!

/**

* 以刻度盘圆心为坐标圆点,建立坐标系,求出(targetX, targetY)坐标与x轴的夹角

*

* @param targetX x坐标

* @param targetY y坐标

* @return (targetX, targetY)坐标与x轴的夹角

*/

private float calcAngle(float targetX, float targetY) {

// 以刻度盘圆心为坐标圆点

float x = targetX - width / 2;

float y = targetY - height / 2;

// 滑过的弧度

double radian;

if (x != 0) {

float tan = Math.abs(y / x);

if (x > 0) {

if (y >= 0) {

// 第四象限

radian = Math.atan(tan);

} else {

// 第一象限

radian = 2 * Math.PI - Math.atan(tan);

}

} else {

if (y >= 0) {

// 第三象限

radian = Math.PI - Math.atan(tan);

} else {

// 第二象限

radian = Math.PI + Math.atan(tan);

}

}

} else {

if (y > 0) {

// Y轴向下方向

radian = Math.PI / 2;

} else {

// Y轴向上方向

radian = Math.PI + Math.PI / 2;

}

}

// 完整圆的弧度为2π,角度为360度,所以180度等于π弧度

// 弧度 = 角度 / 180 * π

// 角度 = 弧度 / π * 180

return (float) (radian / Math.PI * 180);

}

首先了解下弧度与角度的计算公式:

完整圆的弧度为2π,角度为360度,所以180度等于π弧度

弧度 = 角度 / 180 * π

角度 = 弧度 / π * 180

然后以第一象限的点为例,计算一下触摸点的角度:

// 以刻度盘圆心为坐标圆点

float x = targetX - width / 2;

float y = targetY - height / 2;

// 触摸点与x轴的夹角

float tan = Math.abs(y / x);

// 触摸点的弧度

double radian = 2 * Math.PI - Math.atan(tan);

// 触摸点的角度

double angle = radian / Math.PI * 180;

看图理解:

根据滑过的角度计算当前的定时时间:

/**

* 计算时间

*

* @param angle 增加的角度

*/

private void calcTime(float angle) {

rotateAngle += angle;

if (rotateAngle < 0) {

rotateAngle = 0;

} else if (rotateAngle > 360) {

rotateAngle = 360;

}

time = (int) rotateAngle / 6;

invalidate;

}

最后提供设置倒计时,和监听倒计时状态的方法:

/**

* 设置倒计时

*

* @param minute 分钟

*/

public void setCountdown(int minute) {

if (minute < 0 || minute > 60) {

return;

}

time = minute;

rotateAngle = minute * 6;

invalidate;

}

/**

* 设置倒计时监听

*

* @param onTempChangeListener 倒计时监听接口

*/

public void setOnCountdownListener(OnCountdownListener onCountdownListener) {

this.onCountdownListener = onCountdownListener;

}

/**

* 倒计时监听接口

*/

public interface OnCountdownListener {

/**

* 倒计时

*

* @param temp 时间

*/

void countdown(int time);

}

大功告成,再看下效果:

2

写在最后

源码已经上传到GitHub上了,欢迎Fork,觉得还不错就Start一下吧!

点我下载本文Demo的Apk()

标签: #app时间控件不显示分钟