今天的主题是,在flutter里面实现一个日期选择的自定义控件,或者说自定义组件,考虑到这个日期自定义组件的通用性,我们将会采用插件开发开始来做,这样就可以发布到 pub.dev 上,供广大flutter开发者用(虽然别人不一定会用哈,但是我们要对自己有一个小小的要求不是嘛!)
所以,读完本文,你讲学会两个大的知识点:
- 如何在flutter上做一个自定义组件
- 如何开发插件并发布到 pub.dev
因为是操作实战,所以,我会给出完整的实现过程来,首先,我们确定的是需要创建一个自定义组件,并且需要发布到 pub.dev 上,因此,我们首先创建一个插件工程吧,可以参考这里。
代码语言:javascript复制flutter create --template=plugin --platforms=android,ios,linux,macos,windows date_picker
在flutter种创建自定义组件的三种方式介绍
在Flutter中,创建自定义组件(也称为自定义widget)主要有三种方式:通过组合其他组件,自绘和实现RenderObject。
通过组合其他组件:这是创建自定义组件的最基本和最常见的方式。Flutter框架提供了大量的内置组件,如文本、图像、按钮等。你可以通过组合这些内置组件来创建自己的自定义组件。这种方式的优点是简单易用,适用于大多数场景。例如,你可以创建一个包含图像和文本的自定义按钮。
代码语言:javascript复制class CustomButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return FlatButton(
onPressed: () {},
child: Row(
children: <Widget>[
Icon(Icons.add),
Text('Add'),
],
),
);
}
}
自绘:当内置组件无法满足你的需求时,你可以选择自绘。Flutter提供了CustomPaint和Canvas等类,你可以使用这些类来自定义绘制你的组件。这种方式的优点是灵活性高,可以绘制任何你想要的形状和样式。但是,这种方式的复杂度也较高,需要一定的绘图知识。例如,你可以创建一个自定义的进度条。
代码语言:javascript复制class CustomProgressBar extends StatelessWidget {
@override
Widget build(BuildContext context) {
return CustomPaint(
painter: _ProgressBarPainter(),
);
}
}
class _ProgressBarPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
// 绘制进度条
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
实现RenderObject:这是创建自定义组件的最底层方式。Flutter的渲染系统是基于RenderObject的,每个组件都对应一个RenderObject。通过实现自己的RenderObject,你可以完全控制组件的布局和绘制。这种方式的优点是最大的灵活性,但是复杂度也最高,通常只在创建高度自定义的组件或框架时使用。
使用内置组件组合的方式实现一个日期选择器
要实现这个日期选择器,首先我们对需求进行分析之后,提炼出这些功能点
- 需要有一个日历展示视图来讲日期已日历的方式渲染出来
- 需要有一个向左向右的切换按钮方便快速切换到下一个月,上一个月
- 需要有一个label展示当前展示的日历在何年何月
- 简单起见,设置初始化时默认选择的区间开始,区间结束都是当天
- 编写区间选中规则,具体可以看下面的流程图
- 还要考虑选中部分的渲染,既如何标记区分出选中的。
如何渲染出日历展示的日期选择视图
我们定义了一个 MonthView 组件来显示这个视图,其主要的功能就是渲染一个日历视图。其主要的逻辑在这段,在于怎么构造出一个 GridView
代码语言:javascript复制List<Widget> dayTiles = [];
for (int i = 0; i < firstWeekdayOfMonth - 1; i ) {
dayTiles.add(Container()); // empty days to align the first day
}
for (int i = 1; i <= daysInMonth; i ) {
final day = DateTime(month.year, month.month, i);
final isSelected =
(day.isAfter(selectedStartDate.subtract(Duration(days: 1))) &&
day.isBefore(selectedEndDate.add(Duration(days: 1)))) ||
day == selectedStartDate ||
day == selectedEndDate;
BoxDecoration decoration;
if (isSelected) {
decoration = BoxDecoration(
color: Theme.of(context).primaryColor,
shape: BoxShape.circle,
);
} else {
decoration = BoxDecoration();
}
- 这里按照每行7天的方式显示,因为是日历呈现嘛
- 找到本月种周的第一天所在,它前面的补空格展示
- 然后讲剩下的天数都显示出来
- 以及,我们后面要应对的选中的区域着色的逻辑。
这块逻辑做完,我们相当于完成了渲染部分了。
完成日期的选择逻辑
这部分就是按照规则来做,具体的代码很简单,下面也给出了注释
代码语言:javascript复制void _onDateSelected(DateTime selectedDate) {
setState(() {
// 如果没有选中的结束日期,或者选中的开始日期晚于当前选中的日期
if (selectedDate.isBefore(_selectedStartDate)) {
//比最左区间日期还小
_selectedStartDate = selectedDate;
} else if (selectedDate.isAfter(_selectedEndDate)) {
//比有区间日期还大
_selectedEndDate = selectedDate;
} else {
// 处在了区间内,将 selectedDate 与 _lastSelectedDate 比较,小的给到 _selectedStartDate,大的给到 _selectedEndDate
if (selectedDate.isAfter(_lastSelectedDate)) {
_selectedEndDate = selectedDate;
_selectedStartDate = _lastSelectedDate;
} else {
_selectedStartDate = selectedDate;
_selectedEndDate = _lastSelectedDate;
}
}
widget.onDateRangeSelected([_selectedStartDate, _selectedEndDate]);
_lastSelectedDate = selectedDate;
});
}
当然,这部分的逻辑是是作者根据自己的思维模式来写的,可能和主流的日期选择有些差别。其主要的规则是
- 初始化是选中的是当天,类似于用户选择的的起始日期和终止日期是同一天及当天。
- 当用户点击一个日期时,此时判断,如果在起始日期之前,就将起始日期设置为当前选中的日期
- 如果在终止日期之后,就将终止日期设置为当前选中的日期
- 如果在区间内呢?这时候我们记录的最后一次的用户点击日期就发挥作用了,此时对selectedDate和_lastSelectedDate进行比较,小的给到起始日期,大的给到终止日期。。
如何发布
插件开发完毕,剩下的过程是发布了,首先你需要检查下有没有语法问题,使用以下命令来分析你的代码,确保没有任何语法错误:
代码语言:javascript复制flutter analyze
并运行测试:
代码语言:javascript复制flutter test
确保所有测试都通过,并且代码分析没有重要问题,我这里执行实际上是报错了的,但是修复起来也不是难事。
接下来才是真正的进入到发布环节,在发布之前,你需要在pub.dev上创建一个账户。然后,配置你的pubspec.yaml
文件,确保所有的信息都是最新的,包括版本号、描述、作者等。
使用以下命令来发布你的包:
代码语言:javascript复制flutter pub publish
这个命令会再次运行分析器,确保没有问题,并且会提示你确认发布的信息。
可以看看,我们亲手制作的插件,这里可以查看
我正在参与2023腾讯技术创作特训营第三期有奖征文,组队打卡瓜分大奖!