
3.2 基于Canvas和CustomPaint绘制表盘
有了前面理论知识的铺垫,接下来正式开始拟物时钟的开发实战。首先创建一个新的工程,并修改lib/main.dart的代码与3.1.1小节中的初始代码相同。
本节进行表盘的绘制,表盘采用单独的组件进行封装。在lib下创建components目录,用来容纳项目中的组件,在components下创建ClockPanel.dart。
表盘由3部分组成,分别是外表盘、内表盘及表盘刻度。
3.2.1 使用Container绘制外表盘
外表盘基于Container组件实现,通过decoration设置形状为原型,并通过多个BoxShadow设置新拟物的阴影效果。


其中,ClockPanel的根布局是一个Stack组件,是一种层级布局组件,外表盘位于最底层,刻度位于最上层。在Stack的children属性中,各个部分的布局都通过函数封装起来,以提高代码的清晰性和可读性。getOuterPanel方法即绘制外表盘。ClockPanel接受一个size属性,表示表盘的大小,在getOuterPanel中以size作为Container容器的大小。
回到lib/main.dart中对MyHomePage进行修改。将Scaffold的body组件替换为Stack,用于逐层摆放表盘与指针。同时将ClockPanel作为Stack的第一个元素。在build方法中,首先通过MediaQuery获取到屏幕的宽高,并封装为Size对象传入ClockPanel。这里对宽高均乘以0.9的因子,在屏幕两侧留取余量,使其显示更加美观。具体代码如下:


运行代码,显示效果如图3-16所示,可以看出新拟物效果非常赏心悦目。

图3-16 外表盘运行效果
3.2.2 使用Container绘制内表盘
外表盘通过Container的BoxShadow营造出凸起效果,内表盘则要营造出凹陷的效果。内表盘同样也基于Container实现,主要通过BoxDecoration的RadialGradient来设置渐变色。具体实现如下:



其中,getInnerPanel为内表盘绘制方法,内表盘由两个Container通过Stack叠加而成。每个Container分别带有一个渐变效果。两个Container的长和宽再次乘以0.9的因子,作为表框的宽度。
运行代码效果如图3-17所示,可以看出表盘已经基本成型。

图3-17 内表盘运行效果
3.2.3 使用CustomPaint绘制表盘刻度
完成表盘之后,接下来进行表盘刻度的绘制。有了刻度能够更加精确地显示时间。刻度通过CustomPaint组件绘制而成。
在ClockPanel中继续创建一个getScale方法,用于绘制表盘刻度,代码实现如下:

其中,ClockScalePainter类包含了刻度的具体绘制方法,接下来创建这个类。将ClockScale-Painter类的代码写在ClockPanel类之后即可。在ClockScalePainter的paint方法中,首先创建一个Paint画笔,指定线条的颜色与粗细。之后通过canvas的drawLine方法进行画线操作,根据12、3、6、9点的位置坐标进行画线。代码实现如下:


接下来,在ClockPanel类的build方法中,将getScale方法添加进去:

再次运行代码,显示效果如图3-18所示。

图3-18 表盘添加刻度后效果
至此,表盘部分开发完成。