实现类似Kitkat系统中的Time Picker

Android Kitkat自带的time picker很符合我的审美,看起来挺漂亮,使用起来也确实很方便。这个widget很简单,实现起来没有特别的难度,但是还是需要了解android绘制方面的某些细节才能够做到完美。我们先来看看time picker的样子:



this is test t new test
Time Picker的布局很简单,复杂点在中间表盘的绘制。在已知表盘中心和数字所在内圆的半径后,我们可以利用简单的三角函数的知识,计算每一个数字对应的位置,示意图如下:


红点即为中心坐标(x,y), 角度为a, 那么在红色圆上任一点的坐标可以表示为:

1
2
x1 = x + radius * sin(a);
y1 = y - radius * cos(a);

按照这种计算方式,我们画表盘的代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private void drawNumbers(Canvas canvas, float numberCircleRadius, double centerX, double centerY) {
float innerRadius = numberCircleRadius * mInnerRadiusPercent;
mPaint.setColor(Color.WHITE);
mPaint.setTextSize(numberCircleRadius * mTextSizeMultipler);
mPaint.setTextAlign(Paint.Align.CENTER);
mPaint.setAntiAlias(true);
Paint.FontMetrics metrics = mPaint.getFontMetrics();
double x1 = centerX;
double y1 = centerY;// - (mPaint.descent() + mPaint.ascent()) / 2;

if (minutesStr != null)
for (int i = 0; i < 12; ++i) {
double angle = i / 12.0 * 2 * Math.PI;
double x = x1 + innerRadius * Math.sin(angle);
double y = y1 - innerRadius * Math.cos(angle);
canvas.drawText(minutesStr[i], (float)x, (float)y, mPaint);
}
}

相应得到的绘制结果为:



可以看出来,数字的位置并非我们预期的,出于表盘的中间,而是略微偏上。这与Android绘制Text的方式有关。

关于Text的绘制,我没有看到特别详细的android的文档,但是通过Text Programming Guide for iOS, 我们可以了解到系统对Text的处理(android与ios应该不会有太大区别)。下图可以看出,Text绘制时的一些参数:


可以看到对Text的支持还是比较复杂的,参数很多,我们需要特别注意的为ascent, decent与图中所示的红色的线baseline。我们以如下的简单代码为例:

1
2
mPaint.setTextAlign(Paint.Align.LEFT);
canvas.drawText("j", x, y, mPaint);

在代码段中,我们设置左对齐,因此图中的红点就是我们指定的(x,y)的位置;如果我们设置的是右对齐,那么图中的蓝点就是(x,y)的位置;同样,如果设置中间对齐,那么(x,y)就位于红点与蓝点之间。总的来说,我们给定的位置(x,y)并不是text绘制时的中心位置,而是基于baseline和alignment的位置,文字的整体高度相对于我们指定的位置,有向上的偏差这也就是为什么我们绘制的数字看起来不在表盘中心的原因

我们只需要将数字所在圆的圆心向下移动,来消除字体高度造成的偏差,来解决上面的问题(注意代码中被注释的语句)。

弄清楚了text绘制的方式,以后的问题就比较简单了,具体可以参考代码