基于Flutter手把手教你实现一个日期选择(日历形式)

2023-11-14 20:26:27 浏览数 (2)

今天的主题是,在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腾讯技术创作特训营第三期有奖征文,组队打卡瓜分大奖!

0 人点赞