我正在参加中秋创意投稿大赛,详情请看
时钟这个东西很奇妙,总能当做创意实现的入口,比如这个大神用compose实现了一个小创意时钟
看下效果
习惯把Compose这种组合声明的开发方式叫热插拔,节省了大量代码,简化了开发,尤其Modifier这个神奇的东西,熟悉了解决一切样式的问题。
看了下他的源码,很简单,百十行左右,不多说,用Flutter仿一份。或者你觉得简单直接看GitHub欢迎批评指正
本案例使用的环境是:
Flutter (Channel stable, 2.2.3, on macOS 11.0.1 20B29 darwin-arm, locale zh-Hans-CN)
• Flutter version 2.2.3 at /Users/moon/Documents/sdk/flutter
• Framework revision f4abaa0735 (2 months ago), 2021-07-01 12:46:11 -0700
• Engine revision 241c87ad80
• Dart version 2.13.4
• Pub download mirror https://pub.flutter-io.cn
• Flutter download mirror https://storage.flutter-io.cn
复制代码
1 构建一个代表时间的小格子 ,一个最普通的Container 里包含一个text
_Number() {
return Container(
width: 40,
height: 40,
child: Text(
"5",
style: TextStyle(color: Colors.white, fontSize: 16),
),
);
}
复制代码
通过看上边的gif图就能设计出来,小格子是有状态的,是当前的时间点数字的话背景色改变,同时也可以让这个数字的字体变大一点,区别于其他数字,而且他会在所在的列里位于屏幕的中部。
所以先简单封装下这个小格子。
_Number(int number, bool isActive) {
var backgroundColor;
var numberSize;
if (isActive) {
backgroundColor = Colors.black;
numberSize = 18.0;
} else {
backgroundColor = Colors.deepPurpleAccent;
numberSize = 16.0;
}
return Container(
alignment: Alignment.center,
width: _normalSize,
height: _normalSize,
child: Text(
"$number",
style: TextStyle(color: Colors.white, fontSize: numberSize),
),
);
复制代码
2 构建一列小格子
既然是几列数字条构成的一个时钟,所以想下大概是绘制出一条就可以了,其他的几个传不同的数字和状态就可以,
Compose里用column,Flutter里同样有这个东西,当然用listview 或其他的容器都可以。
child: Column(
children: [
_Number(1 , false,),
_Number(2 , false,),
_Number(3 , false,),
_Number(4 , false,),
_Number(5 , true,),
],
))
复制代码
为了美观可以切个圆角,这点compose的modifier就体现出了优势, 可以直接clip column,但Flutter却没那么强大,只能根据位置对每个小格子单独处理了。
if (number == 0) {
borderRadius = BorderRadius.only(
topLeft: Radius.circular(15), topRight: Radius.circular(15));
} else if (number == (totalSize - 1)) {
borderRadius = BorderRadius.only(
bottomLeft: Radius.circular(15), bottomRight: Radius.circular(15));
} else {
borderRadius = BorderRadius.all(Radius.zero);
}
复制代码
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: borderRadius,
),
复制代码
或许还有其简单方式设置边角,请朋友提示。
3 构建多列小格子
同理搞出六列格子,代表双位的时分秒,可以用row 包裹六个 column ,设置外层container的子widget都居中,接受当前时间作为状态,突出显示就可以了。
比如现在是21:49:15
这样就画出了一个单独的静态时间刻度。
4 关于Align
这个图好像不太对,一种乱糟糟的感觉,需要能一眼看出当前时间才行,即把所有时间对齐在一条线上。
这方面Compose 发挥了优势, modifier的 offset 可以轻松实现位移。
val mid = (range.last - range.first) / 2f
val offset = 40.dp * (mid - current)
Modifier .offset(y = offset)
复制代码
flutter 里貌似没这么方便了,怎么实现呢,我使用了Align组件,简单说明下,
以下摘自官方。
Align
组件可以调整子组件的位置,并且可以根据子组件的宽高来确定自身的的宽高,定义如下:
Align({
Key key,
this.alignment = Alignment.center,
this.widthFactor,
this.heightFactor,
Widget child,
})
复制代码
alignment
: 需要一个AlignmentGeometry
类型的值,表示子组件在父组件中的起始位置。AlignmentGeometry
是一个抽象类,它有两个常用的子类:Alignment
和FractionalOffset
。widthFactor
和heightFactor
是用于确定Align
组件本身宽高的属性;它们是两个缩放因子,会分别乘以子元素的宽、高,最终的结果就是Align
组件的宽高。如果值为null
,则组件的宽高将会占用尽可能多的空间。
举例如下
Container(
height: 120.0,
width: 120.0,
color: Colors.blue[50],
child: Align(
alignment: Alignment.topRight,
child: FlutterLogo(
size: 60,
),
),
)
复制代码
我显式指定了Container
的宽、高都为120。如果我不显式指定宽高,而通过同时指定widthFactor
和heightFactor
为2也是可以达到同样的效果:
Align(
widthFactor: 2,
heightFactor: 2,
alignment: Alignment.topRight,
child: FlutterLogo(
size: 60,
),
),
复制代码
5 对齐时间线
分析,Align实际上就是把原高度扩大为之前的 heightFactor 倍数,而它的子widget仍然处于Align的顶部,而当前时间点位于column顶部的距离是已知的,所以要让当前时间点的数字定位于 Align的中间 ,需要补齐(或截掉)当前时间点的格子下边的空间。
为了示意明确,我给总体背景和 Align 背景都加了颜色。
第一列: 小时首位,只有三个格子, 当前是2, 2位于第三个,所以需要给下边补齐两个格子,
第二列:小时的末位,共有10个格子,当前数字也是2 ,也位于第三个,为了让上下相等,需要截掉4以下的数字,让2位于中间位置。
以此类推。
这下就可以发现规律了。
高度的系数因子为 :
(current * 2 + 1)/numbers.length
复制代码
numbers.length 是列的长度。
因此, 每列的函数可以写为
_columnNumber(List<int> numbers, int current) {
List<Widget> list = [];
numbers.forEach((e) {
list.add(_Number(e, e == current, numbers.length));
});
return Container(
color: Colors.white,
child: Align(
alignment: Alignment.topCenter,
widthFactor: 1,
heightFactor: (current *2 + 1)/numbers.length,
child: Container(
height: numbers.length * _normalSize,
margin: EdgeInsets.only(left: 5, right: 5),
child: Column(
children: list,
)),
),
);
}
复制代码
至此一个静态时间钟就绘制完成。
6 动起来
获取当前时间,开启计时器,每秒刷新一次就可以实现动态效果了
这个比较简单,不再复述了。
总结下:
Compose 和 Flutter、SwiftUI都是声明式UI,良好的设计已经确定了是发展趋势, 同时上手简单,更能把精力专注在业务上。
小demo只写了一个晚上,比较仓促,不少细节待完善,比如动画略生硬,代码细节不够好 , 感兴趣的朋友纠正补充。
其实我们最缺乏的就是创意。
周末愉快