偶然有天发现朋友圈有人晒出苹果手机的一个就寝功能,里面一个显示睡眠时间的控件,觉得这个控件非常好看,而且正好看了一系列的自定义控件文章,就模仿这个试一试。
1 先看效果图:完整源码在这里
2.具体实现:
(1)初始化一些画笔
从图中可以分析出,我们需要如下画笔:画外面黑色大圆,画时钟数字,画中间文字,画时钟刻度等等,也就是需要什么画笔new出来就好。
/**
* 初始化画笔:
*/
private void init() {
blackPaint = new Paint();
blackPaint.setColor(Color.parseColor("#171717"));
blackPaint.setStyle(Paint.Style.STROKE);//填充
blackPaint.setStrokeWidth(60);
blackPaint.setAntiAlias(true);
bigPaint = new Paint();
bigPaint.setColor(Color.BLACK);
bigPaint.setStyle(Paint.Style.STROKE);
bigPaint.setStrokeWidth(1);
bigPaint.setAntiAlias(true);
yellowPaint = new Paint();
yellowPaint.setStyle(Paint.Style.STROKE);//填充
yellowPaint.setStrokeWidth(60);
yellowPaint.setAntiAlias(true);
//头部画笔
headTailPaint = new Paint();
headTailPaint.setStyle(Paint.Style.STROKE);//填充
headTailPaint.setColor(Color.parseColor("#ff9801"));
headTailPaint.setStrokeWidth(58);
headTailPaint.setAntiAlias(true);
//尾部画笔
headTailPaint1 = new Paint();
headTailPaint1.setStyle(Paint.Style.STROKE);//填充
headTailPaint1.setColor(Color.parseColor("#ffcb05"));
headTailPaint1.setStrokeWidth(58);
headTailPaint1.setAntiAlias(true);
smallPaint = new Paint();
smallPaint.setColor(Color.BLACK);
smallPaint.setStyle(Paint.Style.STROKE);
smallPaint.setStrokeWidth(52);
smallPaint.setAntiAlias(true);
linePaint1 = new Paint();
linePaint1.setColor(Color.WHITE);
linePaint1.setStyle(Paint.Style.FILL);
linePaint1.setStrokeWidth(5);
linePaint1.setAntiAlias(true);
linePaint2 = new Paint();
linePaint2.setColor(Color.GRAY);
linePaint2.setStyle(Paint.Style.FILL);
linePaint2.setStrokeWidth(2);
linePaint2.setAntiAlias(true);
//画数字:
clockPaint = new Paint();
clockPaint.setColor(Color.parseColor("#8F8F91"));
clockPaint.setTextSize(28);
clockPaint.setTextAlign(Paint.Align.CENTER);
clockPaint.setAntiAlias(true);
textPaint = new Paint();
textPaint.setColor(Color.WHITE);
textPaint.setTextSize(36);
textPaint.setTextAlign(Paint.Align.CENTER);
textPaint.setAntiAlias(true);
}
(2)有了画笔,我们就开始画想要的形状了:
可以把这个控件理解为一个进度控件,就好画多了,所以我这个控件在外部调用需要传递一个progrgess进度
public void setProgress(int progress) {
this.progress = progress;
invalidate();
}
//画中间的文字:将进度转化为文字:2*progress min
int number = 2*progress;
String text = "";
int hour = 0;
int min = 0;
if(number>60){
hour = number/60;
min = number%60;
text= hour+ "小时"+min+"分钟";
}else{
text = number+"分钟";
}
canvas.drawText(text, mWidth / 2, mHeight / 2 + 20, textPaint);
画好文字之后,将画布平移到中心点,为什么这样做呢?因为Android坐标系是从左上角开始的,个人觉得这样某些点的坐标不好计算。而平移之后,就和我们数学当中的坐标系保持一致了,这个大家非常熟悉,计算起来就得心应手了。
平移坐标到中心的代码:
//坐标转移到中心:
canvas.translate(mWidth / 2, mHeight / 2);
坐标转移到中心,画下面的就非常简单了:
//画外面的大圆:
canvas.drawCircle(0, 0, raduis, blackPaint);
画圆上的数字:我这个写得比较复杂一点,是由于实际过程中,有些数字会画得有点偏,没有对准,然后就针对一些具体的数字做了微调,里面看似复杂的(raduis - 80) * Math.cos(Math.toRadians(90))这个其实就是圆弧上坐标的计算:
//画文字:不要旋转
for (int i = 0; i < 12; i++) {
//先从12位置开始算起:
if (i == 0) {
canvas.drawText("12", (float) ((raduis - 80) * Math.cos(Math.toRadians(90))), (float) (-(raduis - 80) * Math.sin(Math.toRadians(90))), clockPaint);
} else if (i <= 3) {
if(i==3){
canvas.drawText(i + "", (float) ((raduis - 80) * Math.cos(Math.toRadians(90-i * 30))+10), (float) (-(raduis - 80) * Math.sin(Math.toRadians(90-i * 30))+10), clockPaint);
}else{
canvas.drawText(i + "", (float) ((raduis - 80) * Math.cos(Math.toRadians(90-i * 30))), (float) (-(raduis - 80) * Math.sin(Math.toRadians(90-i * 30))), clockPaint);
}
} else if (i <= 6) {
int newIndex = i-3;
canvas.drawText(i + "", (float) ((raduis - 80) * Math.cos(Math.toRadians(newIndex * 30))), (float) (((raduis - 80) * Math.sin(Math.toRadians(newIndex * 30))+20)), clockPaint);
} else if (i <= 9) {
int newIndex1 = i-6;
if(i==9){
canvas.drawText(i + "", (float) (-(raduis - 80) * Math.cos(Math.toRadians(90 - newIndex1 * 30))-10), (float) ((raduis - 80) * Math.sin(Math.toRadians(90 - newIndex1 * 30))+10), clockPaint);
}else{
canvas.drawText(i + "", (float) (-(raduis - 80) * Math.cos(Math.toRadians(90 - newIndex1 * 30))), (float) ((raduis - 80) * Math.sin(Math.toRadians(90 - newIndex1 * 30))+20), clockPaint);
}
} else if (i <= 11) {
int newIndex2 = i-9;
canvas.drawText(i + "", (float) (-(raduis - 80) * Math.cos(Math.toRadians(newIndex2 * 30))), (float) (-(raduis - 80) * Math.sin(Math.toRadians(newIndex2* 30))), clockPaint);
}
}
之后是画刻度,这比较简单,每画完一根就旋转下角度就好
//画刻度:
for (int i = 0; i < 48; i++) {
if (i % 4 == 0) {
canvas.drawLine(0, raduis - 20 - 30, 0, raduis - 5 - 30, linePaint1);
} else {
canvas.drawLine(0, raduis - 20 - 30, 0, raduis - 5 - 30, linePaint2);
}
canvas.rotate(7.5f);
}
画总计睡眠时间走过的圆弧:
mRectF = new RectF(-mWidth / 2 + 40, -mWidth / 2 + 40, mWidth / 2 - 40, mWidth / 2 - 40);
//设置渐变色
LinearGradient linearGradient = new LinearGradient((float) (raduis * Math.cos(Math.toRadians(progress / 2))), (float) (-raduis * Math.sin(Math.toRadians(progress / 2))), (float) (raduis * Math.cos(Math.toRadians(progress / 2))), (float) (raduis * Math.sin(Math.toRadians(progress / 2))), Color.parseColor("#ff9801"), Color.parseColor("#ffcb05"), Shader.TileMode.CLAMP);
yellowPaint.setShader(linearGradient);
canvas.drawArc(mRectF, -progress / 2, progress, false, yellowPaint);
首尾处也有2个半圆,这个是为了让进度条头尾处的弧度好看不生硬。
//画首尾2端:头部:尾部,角度转化为弧度
canvas.drawCircle((float) (raduis * Math.cos(Math.toRadians(progress / 2))), (float) (-raduis * Math.sin(Math.toRadians(progress / 2))), 1, headTailPaint);
canvas.drawCircle((float) (raduis * Math.cos(Math.toRadians(progress / 2))), (float) (raduis * Math.sin(Math.toRadians(progress / 2))), 1, headTailPaint1);
最后就是首尾处各有一个黑色小圆:
//画中间套住的两个圆
canvas.drawCircle((float) (raduis * Math.cos(Math.toRadians(progress / 2 - 0.3))), (float) (-raduis * Math.sin(Math.toRadians(progress / 2 - 0.3))), 1, smallPaint);
canvas.drawCircle((float) (raduis * Math.cos(Math.toRadians(progress / 2 - 0.3))), (float) (raduis * Math.sin(Math.toRadians(progress / 2 - 0.3))), 1, smallPaint);
以上画完之后,这个控件自定义就基本完成,但是我们要给它加点动画效果,定义一个方法,不断改变progress的值即可
public void startAnimation(){
mObjectAnimator = ObjectAnimator.ofInt(this, "progress", 0, progress);
mObjectAnimator.setDuration(4000);
mObjectAnimator.start();
}
一个完整的仿ios就寝控件就做好了。
外界调用方法如下:
- xml布局中引入此控件
- 设置progress
- 调用startAnimation