Flutter时间段选择控件的界面优化过程
需求描述 Flutter默认提供的时间段选择组建调用如下:
1 2 3 4 5 6 7 8 9 10 11 DateTimeRange? pickedRange = await showDateRangePicker( context: context, initialDateRange: DateTimeRange( start: DateTime.now().subtract(Duration(days: 30)), end: DateTime.now()), helpText: "选择领料日期段", cancelText: '取消', saveText: '确认', firstDate: DateTime.now().subtract(Duration(days: 365)), lastDate: DateTime.now() );
从效果图来看并不让人满意,需要修改的地方有: 1.背景 2.helpText字体大小 3.跨年的情况下未完全显示的时间段,即时间段字体大小
修改过程 按照原生开发的思路,首先直接找构造属性,没有再通过获取实例动态修改,但是flutter的view是Widget,其呈现和和更新机制和原生不同,先按照一般思路往下操作
一般思路 直接在构造方法中查看是否有配置属性就可以了,通过属性修改即可,查看showDateRangePicker方法源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 Future<DateTimeRange?> showDateRangePicker({ required BuildContext context, DateTimeRange? initialDateRange, required DateTime firstDate, required DateTime lastDate, DateTime? currentDate, DatePickerEntryMode initialEntryMode = DatePickerEntryMode.calendar, String? helpText, String? cancelText, String? confirmText, String? saveText, String? errorFormatText, String? errorInvalidText, String? errorInvalidRangeText, String? fieldStartHintText, String? fieldEndHintText, String? fieldStartLabelText, String? fieldEndLabelText, Locale? locale, bool useRootNavigator = true, RouteSettings? routeSettings, TextDirection? textDirection, TransitionBuilder? builder, }) async {...//省略
并没有helpText相关的style设置,也就是说并没有属性能够配置,看来该方法不可行
查看源码 当一般方法行不通的时候就需要一点点探究精神了,既然这个Widget的显示中有字体,就一定有字体相关的style属性,同理也一定是在哪里设置了颜色相关配置,那就简单了,直接源码看一下哪里赋值的不就知道了么 直接查找”date_picker.dart”源码类中的 Widget build(BuildContext context) 行,就能找到Widget构建的地方,也就能在这里找到属性的赋值了
先说字体:1313-1336行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 case DatePickerEntryMode.calendarOnly: contents = _CalendarRangePickerDialog( key: _calendarPickerKey, selectedStartDate: _selectedStart.value, selectedEndDate: _selectedEnd.value, firstDate: widget.firstDate, lastDate: widget.lastDate, currentDate: widget.currentDate, onStartDateChanged: _handleStartDateChanged, onEndDateChanged: _handleEndDateChanged, onConfirm: _hasSelectedDateRange ? _handleOk : null, onCancel: _handleCancel, entryModeButton: showEntryModeButton ? IconButton( //编辑按钮图标 icon: const Icon(Icons.edit), padding: EdgeInsets.zero, color: onPrimarySurface, tooltip: localizations.inputDateModeButtonLabel, onPressed: _handleEntryModeToggle, ) : null, confirmText: widget.saveText ?? localizations.saveButtonLabel, helpText: widget.helpText ?? localizations.dateRangePickerHelpText, );
源码这里IconButton就是我们的日期展示后面的编辑按钮,那么前面的部分就有设置字体大小的地方,前面是一个_CalendarRangePickerDialog,进入源码查看一下 1457-1475行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @override Widget build(BuildContext context) { final ThemeData theme = Theme.of(context); final ColorScheme colorScheme = theme.colorScheme; final MaterialLocalizations localizations = MaterialLocalizations.of(context); final Orientation orientation = MediaQuery.of(context).orientation; final TextTheme textTheme = theme.textTheme; final Color headerForeground = colorScheme.brightness == Brightness.light ? colorScheme.onPrimary : colorScheme.onSurface; final Color headerDisabledForeground = headerForeground.withOpacity(0.38); final String startDateText = _formatRangeStartDate(localizations, selectedStartDate, selectedEndDate); final String endDateText = _formatRangeEndDate(localizations, selectedStartDate, selectedEndDate, DateTime.now()); //这下面就是设置开始和结束的时间,那这个headlineStyle就是字体大小了 final TextStyle? headlineStyle = textTheme.headline5; final TextStyle? startDateStyle = headlineStyle?.apply( color: selectedStartDate != null ? headerForeground : headerDisabledForeground, ); final TextStyle? endDateStyle = headlineStyle?.apply( color: selectedEndDate != null ? headerForeground : headerDisabledForeground, ); final TextStyle saveButtonStyle = textTheme.button!.apply( color: onConfirm != null ? headerForeground : headerDisabledForeground, );
成功找到headline的style设置来源于textTheme.headline5,说明这些属性是通过Theme的属性设置的,那是不是给我们的日期选择dialog外面加一个Theme并配置相关属性就可以了呢?是的,就是这么简单,但还需要找到属性的对应关系,也就是说应该配置哪些属性。这里有两个问题 1.怎么包装这个dialog,2.找剩余需要配置的属性
1.怎么包装这个dialog 很显然要拿到这个dialog的widget才行,所以再次后去看showDateRangePicker的源码1007-1023行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 if (locale != null) { dialog = Localizations.override( context: context, locale: locale, child: dialog, ); } return showDialog<DateTimeRange>( context: context, useRootNavigator: useRootNavigator, routeSettings: routeSettings, useSafeArea: false, builder: (BuildContext context) { //这里会判断是否有builder,如果有,则使用builder来封装dialog return builder == null ? dialog : builder(context, dialog); }, );
还记得showDateRangePicker的属性TransitionBuilder? builder吗?是的,可以通过设置builder来获取到该dialog,再包装一层Theme就解决了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 DateTimeRange? pickedRange = await showDateRangePicker( context: context, initialDateRange: DateTimeRange( start: DateTime.now().subtract(Duration(days: 30)), end: DateTime.now()), helpText: "选择领料日期段", cancelText: '取消', saveText: '确认', firstDate: DateTime.now().subtract(Duration(days: 365)), lastDate: DateTime.now() builder: (context, child) => customDatePickTheme(context,child) ); customDatePickTheme(context, child) { ThemeData theme = Theme.of(context); return Theme( data: theme, child: Container( child: child,)); }
2.其他属性的查找 使用上面的方法查找属性在1506-1514行,找到helpText的字体大小由textTheme.overline设置
1 2 3 4 5 6 7 8 9 child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ Text( helpText, style: textTheme.overline!.apply( color: headerForeground, ), ),
查看_HighlightPainter类即选中的时间段颜色在_buildDayItem中2247-2254行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Widget _buildDayItem(BuildContext context, DateTime dayToBuild, int firstDayOffset, int daysInMonth) { final ThemeData theme = Theme.of(context); final ColorScheme colorScheme = theme.colorScheme; final TextTheme textTheme = theme.textTheme; final MaterialLocalizations localizations = MaterialLocalizations.of(context); final TextDirection textDirection = Directionality.of(context); //这里设置选中的颜色 final Color highlightColor = _highlightColor(context); final int day = dayToBuild.day; ...略 //可以看出选中颜色由Theme的colorScheme控制 Color _highlightColor(BuildContext context) { return Theme.of(context).colorScheme.primary.withOpacity(0.12); }
总结 阅读源码并不难,只要根据自己的需求抓主要线索就能很快定位到需要查找的地方,这比引入一个新控件节省更多的时间和学习成本,下次遇到其他修改需求也能更快的找到并修改,最后贴一下DateTimeRange的Theme配置和效果图
1 2 3 4 5 6 7 8 9 10 11 12 DateTimeRange? pickedRange = await showDateRangePicker( context: context, initialDateRange: DateTimeRange( start: DateTime.now().subtract(Duration(days: 30)), end: DateTime.now()), helpText: "选择日期段", cancelText: '取消', saveText: '确认', firstDate: DateTime.now().subtract(Duration(days: 365)), lastDate: DateTime.now() builder: (context, child) => customDatePickTheme(context,child) );
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 //日期选择的主题和字体大小设置 customDatePickTheme(context, child) { ThemeData theme = Theme.of(context); final TextTheme partialTheme = TextTheme( headline4: TextStyle(fontSize: ScreenUtil().setSp(30)), headline5: TextStyle(fontSize: ScreenUtil().setSp(30)), overline: TextStyle(fontSize: ScreenUtil().setSp(36))); theme = theme.copyWith( colorScheme: theme.colorScheme.copyWith(primary: AppBarColors.backgroundColor), textTheme: theme.textTheme.merge(partialTheme), appBarTheme: AppBarTheme( backgroundColor: AppBarColors.backgroundColor, )); return Theme( data: theme, child: Container( child: child, )); }