├── .DS_Store ├── .github └── workflows │ └── dart.yml ├── README.md ├── r_calendar ├── .github │ └── workflows │ │ └── publish.yaml ├── .gitignore ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── README.md ├── README_ZH.md ├── example │ ├── .gitignore │ ├── .metadata │ ├── README.md │ ├── lib │ │ ├── en.arb │ │ ├── l10n │ │ │ ├── en.dart │ │ │ ├── s.dart │ │ │ └── zh.dart │ │ ├── main.dart │ │ └── zh.arb │ ├── pubspec.lock │ ├── pubspec.yaml │ └── test │ │ └── widget_test.dart ├── lib │ ├── r_calendar.dart │ └── src │ │ ├── r_calendar_controller.dart │ │ ├── r_calendar_custom_widget.dart │ │ ├── r_calendar_extension.dart │ │ ├── r_calendar_utils.dart │ │ └── r_calendar_widget_item.dart ├── pubspec.lock ├── pubspec.yaml └── test │ └── r_calendar_test.dart └── screen ├── .DS_Store ├── s1.png ├── s2.png ├── s3.png └── s4.png /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhymelph/r_calendar/a2a33d772336025552a6205252049aeec71cb94d/.DS_Store -------------------------------------------------------------------------------- /.github/workflows/dart.yml: -------------------------------------------------------------------------------- 1 | 2 | name: Publish to Pub.dev 3 | 4 | # 流程触发时机,当有标签创建时触发,如 v1.0.0。当然也可以选择别的触发时机,如 push,release 等 5 | on: create 6 | 7 | jobs: 8 | publishing: 9 | runs-on: ubuntu-latest 10 | steps: 11 | # 拉取仓库代码 12 | - name: "Checkout" 13 | uses: actions/checkout@v2 14 | - name: "cd rcalendar" 15 | run: cd r_calendar 16 | # 发布插件 17 | - name: Dart and Flutter Package Publisher 18 | uses: k-paxian/dart-package-publisher@v1.2 19 | with: 20 | # 设置发布插件需要的 Token 21 | accessToken: ${{ secrets.OAUTH_ACCESS_TOKEN }} 22 | refreshToken: ${{ secrets.OAUTH_REFRESH_TOKEN }} 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # r_calendar 2 | [![pub package](https://img.shields.io/pub/v/r_calendar.svg)](https://pub.dartlang.org/packages/r_calendar) 3 | 4 | 📅📆Flutter日历插件,支持自定义日历,月视图/周视图切花、点击拦截、单选(切换月自动选)、多选(散选/聚选) 5 | 6 | - [✔] 月视图/周视图切换 7 | - [✔] 自定义日历 8 | - [✔] 点击拦截 9 | - [✔] 单选,切换月/周自动选 10 | - [✔] 多选,散选/聚选 11 | 12 | |
单选 |
周视图 |
多选 |
多选 | 13 | | :---: | :---: | :---: | :---: | 14 | 15 | -------------------------------------------------------------------------------- /r_calendar/.github/workflows/publish.yaml: -------------------------------------------------------------------------------- 1 | 2 | name: Publish to Pub.dev 3 | 4 | # 流程触发时机,当有标签创建时触发,如 v1.0.0。当然也可以选择别的触发时机,如 push,release 等 5 | on: create 6 | 7 | jobs: 8 | publishing: 9 | runs-on: ubuntu-latest 10 | steps: 11 | # 拉取仓库代码 12 | - name: "Checkout" 13 | uses: actions/checkout@v2 14 | # 发布插件 15 | - name: Dart and Flutter Package Publisher 16 | uses: k-paxian/dart-package-publisher@v1.2 17 | with: 18 | # 设置发布插件需要的 Token 19 | accessToken: ${{ secrets.OAUTH_ACCESS_TOKEN }} 20 | refreshToken: ${{ secrets.OAUTH_REFRESH_TOKEN }} -------------------------------------------------------------------------------- /r_calendar/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | lib/generated/ 13 | res/ 14 | # IntelliJ related 15 | *.iml 16 | *.ipr 17 | *.iws 18 | .idea/ 19 | 20 | # The .vscode folder contains launch configuration and tasks you configure in 21 | # VS Code which you may wish to be included in version control, so this line 22 | # is commented out by default. 23 | #.vscode/ 24 | 25 | # Flutter/Dart/Pub related 26 | **/doc/api/ 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .packages 31 | .pub-cache/ 32 | .pub/ 33 | build/ 34 | 35 | # Android related 36 | **/android/**/gradle-wrapper.jar 37 | **/android/.gradle 38 | **/android/captures/ 39 | **/android/gradlew 40 | **/android/gradlew.bat 41 | **/android/local.properties 42 | **/android/**/GeneratedPluginRegistrant.java 43 | 44 | # iOS/XCode related 45 | **/ios/**/*.mode1v3 46 | **/ios/**/*.mode2v3 47 | **/ios/**/*.moved-aside 48 | **/ios/**/*.pbxuser 49 | **/ios/**/*.perspectivev3 50 | **/ios/**/*sync/ 51 | **/ios/**/.sconsign.dblite 52 | **/ios/**/.tags* 53 | **/ios/**/.vagrant/ 54 | **/ios/**/DerivedData/ 55 | **/ios/**/Icon? 56 | **/ios/**/Pods/ 57 | **/ios/**/.symlinks/ 58 | **/ios/**/profile 59 | **/ios/**/xcuserdata 60 | **/ios/.generated/ 61 | **/ios/Flutter/App.framework 62 | **/ios/Flutter/Flutter.framework 63 | **/ios/Flutter/Flutter.podspec 64 | **/ios/Flutter/Generated.xcconfig 65 | **/ios/Flutter/app.flx 66 | **/ios/Flutter/app.zip 67 | **/ios/Flutter/flutter_assets/ 68 | **/ios/Flutter/flutter_export_environment.sh 69 | **/ios/ServiceDefinitions.json 70 | **/ios/Runner/GeneratedPluginRegistrant.* 71 | 72 | # Exceptions to above rules. 73 | !**/ios/**/default.mode1v3 74 | !**/ios/**/default.mode2v3 75 | !**/ios/**/default.pbxuser 76 | !**/ios/**/default.perspectivev3 77 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 78 | -------------------------------------------------------------------------------- /r_calendar/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 27321ebbad34b0a3fafe99fac037102196d655ff 8 | channel: stable 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /r_calendar/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [0.1.12] - update intl package. 2 | 3 | ## [0.1.11] - null safety. 4 | 5 | ## [0.1.10-nullsafety] - chinese lunar year. 6 | 7 | ## [0.1.9-nullsafety] - null safety 8 | 9 | ## [0.1.8] - Fix the bugs in week select bug. 10 | 11 | ## [0.1.7] - Fix the bugs in aggregate selection 12 | 13 | ## [0.1.6] - add expand data in controller 14 | 15 | ## [0.1.5] - format 16 | 17 | ## [0.1.4] - add week view . 18 | 19 | ## [0.1.3] - remove buildMonthYear 、left and right , add build top widget. 20 | 21 | ## [0.1.2] - add single and multiple selected to packages. 22 | 23 | ## [0.1.1] - fix listener data not change and add monthDate to controller. 24 | 25 | ## [0.1.0] - release 26 | 27 | -------------------------------------------------------------------------------- /r_calendar/LICENSE: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The rhyme_lph Authors. All rights reserved. 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are 5 | // met: 6 | // 7 | // * Redistributions of source code must retain the above copyright 8 | // notice, this list of conditions and the following disclaimer. 9 | // * Redistributions in binary form must reproduce the above 10 | // copyright notice, this list of conditions and the following disclaimer 11 | // in the documentation and/or other materials provided with the 12 | // distribution. 13 | // * Neither the name of rhyme_lph authors. nor the names of its 14 | // contributors may be used to endorse or promote products derived from 15 | // this software without specific prior written permission. 16 | // 17 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /r_calendar/README.md: -------------------------------------------------------------------------------- 1 | # r_calendar 2 | 3 | [![pub package](https://img.shields.io/pub/v/r_calendar.svg)](https://pub.dartlang.org/packages/r_calendar) 4 | 5 | A new Flutter package about calendar,you can use this design the calendar and support single or multiple selected. 6 | 7 | 8 | ## [中文点此](README_ZH.md) 9 | 10 | ## 1.Getting Started. 11 | 12 | - use plugin: 13 | add this code in `pubspec.yaml` 14 | ```yaml 15 | dependencies: 16 | r_calendar: last version 17 | ``` 18 | - add the packages to your file. 19 | ```dart 20 | import 'package:r_calendar/r_calendar.dart'; 21 | 22 | ``` 23 | - radio select 24 | ```dart 25 | /// 26 | /// [selectedDate] will default selected one 27 | /// [isAutoSelect] when the page view change will change month to auto select same day. 28 | RCalendarController controller= RCalendarController.single( 29 | selectedDate: DateTime.now(), 30 | isAutoSelect: true,); 31 | ``` 32 | - check select 33 | ```dart 34 | /// 35 | /// [selectedDates] will default selected more. 36 | /// [isDispersion] will selected continuity dateTime. 37 | RCalendarController controller = RCalendarController.multiple( 38 | selectedDates: [ 39 | DateTime(2019, 12, 1), 40 | DateTime(2019, 12, 2), 41 | DateTime(2019, 12, 3), 42 | ], 43 | isDispersion: true); 44 | ``` 45 | - value change listener 46 | ```dart 47 | /// 48 | /// addListener to observe value change 49 | RCalendarController controller = RCalendarController.multiple(...) 50 | addListener((){ 51 | // controller.isMultiple 52 | 53 | // single selected 54 | // controller.isAutoSelect 55 | // controller.selectedDate; 56 | 57 | // multiple selected 58 | // controller.selectedDates; 59 | // controller.isDispersion; 60 | 61 | // mode month/week 62 | // controller.mode 63 | }); 64 | ``` 65 | - custom your widget 66 | ```dart 67 | 68 | class MyRCalendarCustomWidget extends RCalendarCustomWidget { 69 | 70 | // If you want to change first day is Monday ,you can change [MaterialLocalizations.firstDayOfWeekIndex] 71 | // SUM MON TUE WED THU FRI SUT 72 | //build your week header 73 | @override 74 | List buildWeekListWidget(BuildContext context,MaterialLocalizations localizations){...}; 75 | 76 | // 1 2 3 4 5 6 7 77 | //build normal dateTime 78 | @override 79 | Widget buildDateTime(BuildContext context, DateTime time, List types){...}; 80 | 81 | // < 2019 year 11 month > 82 | //build year and month and left 、 right Indicator 83 | @override 84 | Widget buildTopWidget(BuildContext context, RCalendarController controller){...}; 85 | 86 | 87 | //is unable will not have tap events 88 | @override 89 | bool isUnable(DateTime time, bool isSameMonth){...}; 90 | 91 | //Click intercept. When it returns true to intercept, the selected date will not be changed 92 | @override 93 | FutureOr clickInterceptor(BuildContext context, DateTime dateTime){...}; 94 | 95 | //Height of child view 96 | @override 97 | double get childHeight=>{...}; 98 | } 99 | ``` 100 | 101 | ## 2.Use this 102 | ```dart 103 | import 'package:flutter/material.dart'; 104 | import 'package:r_calendar/r_calendar.dart'; 105 | 106 | void main() => runApp(MyApp()); 107 | 108 | class MyApp extends StatelessWidget { 109 | @override 110 | Widget build(BuildContext context) { 111 | return MaterialApp( 112 | title: 'Flutter Demo', 113 | home: MyHomePage(), 114 | ); 115 | } 116 | } 117 | 118 | class MyHomePage extends StatefulWidget { 119 | @override 120 | _MyHomePageState createState() => _MyHomePageState(); 121 | } 122 | class _MyHomePageState extends State { 123 | RCalendarController controller; 124 | 125 | @override 126 | void initState() { 127 | super.initState(); 128 | controller = RCalendarController.multiple(selectedDates: [ 129 | DateTime(2019, 12, 1), 130 | DateTime(2019, 12, 2), 131 | DateTime(2019, 12, 3), 132 | ]); 133 | // controller = RCalendarController.single(selectedDate: DateTime.now(),isAutoSelect: true); 134 | } 135 | 136 | @override 137 | void dispose() { 138 | controller.dispose(); 139 | super.dispose(); 140 | } 141 | 142 | @override 143 | Widget build(BuildContext context) { 144 | return Scaffold( 145 | body: RCalendarWidget( 146 | controller: controller, 147 | customWidget: DefaultRCalendarCustomWidget(), 148 | firstDate: DateTime(1970, 1, 1), 149 | lastDate: DateTime(2055, 12, 31), 150 | ), 151 | ); 152 | } 153 | } 154 | ``` 155 | ## 3.Expand Data(update by v0.1.6 ) 156 | ```dart 157 | /// initial 158 | RCalendarController> controller = RCalendarController.single( 159 | initialData: [ 160 | DateTime.now(), 161 | DateTime.now().add(Duration(days: 1)), 162 | DateTime.now().add(Duration(days: 2)), 163 | ] 164 | ); 165 | 166 | /// if you want to update expand data ,use it. 167 | controller.data = [....]; 168 | 169 | /// you can use it get the controller in RCalendarCustomWidget 170 | 171 | class MyRCalendarCustomWidget extends RCalendarCustomWidget { 172 | ///... 173 | @override 174 | Widget buildDateTime( 175 | BuildContext context, DateTime time, List types) { 176 | 177 | // new 178 | RCalendarController> controller =RCalendarMarker.of(context).notifier; 179 | // new 180 | 181 | //... 182 | } 183 | ///... 184 | ``` -------------------------------------------------------------------------------- /r_calendar/README_ZH.md: -------------------------------------------------------------------------------- 1 | ## r_calendar 2 | [![pub package](https://img.shields.io/pub/v/r_calendar.svg)](https://pub.dartlang.org/packages/r_calendar) 3 | 4 | 一个日历插件,支持自定义日历,月视图/周视图切花、点击拦截、单选(切换月自动选)、多选(散选/聚选) 5 | 6 | ## 1.如何使用. 7 | 8 | - `pubspec.yaml`文件添加依赖 9 | 10 | ```yaml 11 | dependencies: 12 | r_calendar: last version 13 | ``` 14 | - 导入包 15 | 16 | ```dart 17 | import 'package:r_calendar/r_calendar.dart'; 18 | 19 | ``` 20 | - 单选控制器初始化 21 | ```dart 22 | /// 23 | /// [selectedDate] 默认选中的那天 24 | /// [isAutoSelect] 当月份改变时,是否自动选中对应的月份的同一天 25 | 26 | RCalendarController controller= RCalendarController.single( 27 | selectedDate: DateTime.now(), 28 | isAutoSelect: true,); 29 | ``` 30 | - 多选控制器初始化 31 | ```dart 32 | /// 33 | /// [selectedDates] 默认选中的日期数组 34 | /// [isDispersion] 是否散选,否则为连续选中 35 | 36 | RCalendarController controller = RCalendarController.multiple( 37 | selectedDates: [ 38 | DateTime(2019, 12, 1), 39 | DateTime(2019, 12, 2), 40 | DateTime(2019, 12, 3), 41 | ], 42 | isDispersion: true); 43 | ``` 44 | - 周视图/月视图(默认月视图) 45 | ```dart 46 | /// 47 | /// [mode] 模式 48 | /// - RCalendarMode.week 周视图模式 49 | /// - RCalendarMode.month 月视图模式 50 | 51 | RCalendarController controller = RCalendarController.single( 52 | mode:RCalendarMode.week); 53 | ``` 54 | - 数据变化监听 55 | ```dart 56 | /// 添加监听器观察值的变化 57 | RCalendarController controller = RCalendarController.multiple(...) 58 | ..addListener((){ 59 | // 是否为多选 60 | // controller.isMultiple 61 | 62 | // 单选下 63 | // 当月份改变时,是否自动选中对应的月份的同一天 64 | // controller.isAutoSelect 65 | // 当前选中的日期 66 | // controller.selectedDate; 67 | 68 | // 多选 69 | // 是否散选,否则为连续选中 70 | // controller.isDispersion; 71 | // 当前选中的日期列表 72 | // controller.selectedDates; 73 | 74 | // 周视图/月视图 75 | // controller.mode 76 | }); 77 | ``` 78 | - 自定义日历 79 | ```dart 80 | 81 | class MyRCalendarCustomWidget extends RCalendarCustomWidget { 82 | // 如果你想设置第一天是星期一,请更改MaterialLocalizations 的firstDayOfWeekIndex 83 | // 日 一 二 三 四 五 六 84 | //构建头部 85 | @override 86 | List buildWeekListWidget(BuildContext context,MaterialLocalizations localizations){...}; 87 | 88 | // 1 2 3 4 5 6 7 89 | //构建普通的日期 90 | @override 91 | Widget buildDateTime(BuildContext context,DateTime time, List types){...}; 92 | 93 | // < 2019年 11月 > 94 | //构建年份和月份 左指示器、右指示器,返回null就没有 95 | @override 96 | Widget buildTopWidget(BuildContext context,RCalendarController controller){...}; 97 | 98 | //是否不可用,不可用时,无点击事件 99 | @override 100 | bool isUnable(DateTime time, bool isSameMonth){...}; 101 | 102 | //点击拦截,当返回true时进行拦截,就不会改变选中日期 103 | @override 104 | FutureOr clickInterceptor(BuildContext context,DateTime dateTime){...}; 105 | 106 | //子view的高度 107 | @override 108 | double get childHeight=>{...}; 109 | } 110 | ``` 111 | 112 | ## 2.使用它. 113 | 114 | ```dart 115 | import 'package:flutter/material.dart'; 116 | import 'package:r_calendar/r_calendar.dart'; 117 | 118 | void main() => runApp(MyApp()); 119 | class MyApp extends StatelessWidget { 120 | @override 121 | Widget build(BuildContext context) { 122 | return MaterialApp( 123 | title: 'Flutter Demo', 124 | theme: ThemeData( 125 | primarySwatch: Colors.blue, 126 | ), 127 | home: MyHomePage(title: 'Flutter Demo Home Page'), 128 | ); 129 | } 130 | } 131 | 132 | class MyHomePage extends StatefulWidget { 133 | @override 134 | _MyHomePageState createState() => _MyHomePageState(); 135 | } 136 | class _MyHomePageState extends State { 137 | RCalendarController controller; 138 | 139 | @override 140 | void initState() { 141 | super.initState(); 142 | controller = RCalendarController.multiple(selectedDates: [ 143 | DateTime(2019, 12, 1), 144 | DateTime(2019, 12, 2), 145 | DateTime(2019, 12, 3), 146 | ]); 147 | // controller = RCalendarController.single(selectedDate: DateTime.now(),isAutoSelect: true); 148 | } 149 | 150 | @override 151 | void dispose() { 152 | controller.dispose(); 153 | super.dispose(); 154 | } 155 | 156 | @override 157 | Widget build(BuildContext context) { 158 | return Scaffold( 159 | body: RCalendarWidget( 160 | controller: controller, 161 | customWidget: DefaultRCalendarCustomWidget(), 162 | firstDate: DateTime(1970, 1, 1), //当前日历的最小日期 163 | lastDate: DateTime(2055, 12, 31),//当前日历的最大日期 164 | ), 165 | ); 166 | } 167 | } 168 | ``` 169 | ## 3.自定义数据(请更新版本到 v0.1.6) 170 | 用户可以通过将数据设置到controller里面,然后再从`RCalendarCustomWidget`里获取 171 | ```dart 172 | /// 初始化控制器,我这里是设置自定义数据的类型为List,当然,你可以设置成自己需要的类型 173 | /// 在构造方法中新增了一个`initialData`参数,用于初始化你的自定义数据 174 | RCalendarController> controller = RCalendarController.single( 175 | initialData: [ 176 | DateTime.now(), 177 | DateTime.now().add(Duration(days: 1)), 178 | DateTime.now().add(Duration(days: 2)), 179 | ] 180 | ); 181 | 182 | /// 如果你想更改自定义数据,请使用下面的例子,无需setState 183 | controller.data = [....]; 184 | 185 | /// 你可以在RCalendarCustomWidget自定义类中通过context获取对应的controller.然后根据自定义数据进行显示判断 186 | 187 | class MyRCalendarCustomWidget extends RCalendarCustomWidget { 188 | ///... 189 | @override 190 | Widget buildDateTime( 191 | BuildContext context, DateTime time, List types) { 192 | 193 | // new 194 | RCalendarController> controller =RCalendarMarker.of(context).notifier; 195 | // new 196 | 197 | //... 198 | } 199 | ///... 200 | ``` -------------------------------------------------------------------------------- /r_calendar/example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | 33 | # Web related 34 | lib/generated_plugin_registrant.dart 35 | 36 | # Exceptions to above rules. 37 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 38 | -------------------------------------------------------------------------------- /r_calendar/example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 27321ebbad34b0a3fafe99fac037102196d655ff 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /r_calendar/example/README.md: -------------------------------------------------------------------------------- 1 | # r_calendar_example 2 | 3 | ## English Language 4 | 5 | ### 1.RCalendarController 6 | 7 | value controller 8 | 9 | ```dart 10 | import 'package:r_calendar/r_calendar.dart'; 11 | 12 | /// radio select 13 | /// 14 | /// [selectedDate] will default selected one 15 | /// [isAutoSelect] when the page view change will change month to auto select same day. 16 | 17 | RCalendarController controller= RCalendarController.single( 18 | selectedDate: DateTime.now(), 19 | isAutoSelect: true,); 20 | 21 | 22 | /// check select 23 | /// 24 | /// [selectedDates] will default selected more. 25 | /// [isDispersion] will selected continuity dateTime. 26 | RCalendarController controller = RCalendarController.multiple( 27 | selectedDates: [ 28 | DateTime(2019, 12, 1), 29 | DateTime(2019, 12, 2), 30 | DateTime(2019, 12, 3), 31 | ], 32 | isDispersion: true); 33 | 34 | /// addListener to observe value change 35 | RCalendarController controller = RCalendarController.multiple(...) 36 | addListener((){ 37 | // controller.isMultiple 38 | 39 | // single selected 40 | // controller.isAutoSelect 41 | // controller.selectedDate; 42 | 43 | // multiple selected 44 | // controller.selectedDates; 45 | // controller.isDispersion; 46 | }); 47 | ``` 48 | 49 | ### 2.RCalendarCustomWidget 50 | 51 | custom your widget 52 | 53 | ```dart 54 | 55 | class MyRCalendarCustomWidget extends RCalendarCustomWidget { 56 | 57 | // If you want to change first day is Monday ,you can change [MaterialLocalizations.firstDayOfWeekIndex] 58 | // SUM MON TUE WED THU FRI SUT 59 | //build your week header 60 | @override 61 | List buildWeekListWidget(MaterialLocalizations localizations){...}; 62 | 63 | // 1 2 3 4 5 6 7 64 | //build normal dateTime 65 | @override 66 | Widget buildDateTime(DateTime time, List types){...}; 67 | 68 | // < 2019 year 11 month > 69 | //build year and month and left 、 right Indicator 70 | @override 71 | Widget buildTopWidget(RCalendarController controller){...}; 72 | 73 | //is unable will not have tap events 74 | @override 75 | bool isUnable(DateTime time, bool isSameMonth){...}; 76 | 77 | //Click intercept. When it returns true to intercept, the selected date will not be changed 78 | @override 79 | FutureOr clickInterceptor(DateTime dateTime){...}; 80 | 81 | //Height of child view 82 | @override 83 | double get childHeight=>{...}; 84 | } 85 | 86 | ``` 87 | ### 3.Use the widget. 88 | 89 | ```dart 90 | import 'package:flutter/material.dart'; 91 | import 'package:r_calendar/r_calendar.dart'; 92 | 93 | void main() => runApp(MyApp()); 94 | class MyApp extends StatelessWidget { 95 | @override 96 | Widget build(BuildContext context) { 97 | return MaterialApp( 98 | title: 'Flutter Demo', 99 | theme: ThemeData( 100 | primarySwatch: Colors.blue, 101 | ), 102 | home: MyHomePage(title: 'Flutter Demo Home Page'), 103 | ); 104 | } 105 | } 106 | 107 | class MyHomePage extends StatefulWidget { 108 | @override 109 | _MyHomePageState createState() => _MyHomePageState(); 110 | } 111 | class _MyHomePageState extends State { 112 | RCalendarController controller; 113 | 114 | @override 115 | void initState() { 116 | super.initState(); 117 | controller = RCalendarController.multiple(selectedDates: [ 118 | DateTime(2019, 12, 1), 119 | DateTime(2019, 12, 2), 120 | DateTime(2019, 12, 3), 121 | ]); 122 | // controller = RCalendarController.single(selectedDate: DateTime.now(),isAutoSelect: true); 123 | } 124 | 125 | @override 126 | void dispose() { 127 | controller.dispose(); 128 | super.dispose(); 129 | } 130 | 131 | @override 132 | Widget build(BuildContext context) { 133 | return Scaffold( 134 | body: RCalendarWidget( 135 | controller: controller, 136 | customWidget: DefaultRCalendarCustomWidget(), 137 | firstDate: DateTime(1970, 1, 1), 138 | lastDate: DateTime(2055, 12, 31), 139 | ), 140 | ); 141 | } 142 | } 143 | 144 | ``` 145 | 146 | ## Chinese Language 147 | 148 | ### 1.RCalendarController 149 | 150 | 值的控制器 151 | 152 | ```dart 153 | import 'package:r_calendar/r_calendar.dart'; 154 | 155 | /// 单选 156 | /// 157 | /// [selectedDate] 默认选中的那天 158 | /// [isAutoSelect] 当月份改变时,是否自动选中对应的月份的同一天 159 | 160 | RCalendarController controller= RCalendarController.single( 161 | selectedDate: DateTime.now(), 162 | isAutoSelect: true,); 163 | 164 | 165 | /// 多选 166 | /// 167 | /// [selectedDates] 默认选中的日期数组 168 | /// [isDispersion] 是否散选,否则为连续选中 169 | 170 | RCalendarController controller = RCalendarController.multiple( 171 | selectedDates: [ 172 | DateTime(2019, 12, 1), 173 | DateTime(2019, 12, 2), 174 | DateTime(2019, 12, 3), 175 | ], 176 | isDispersion: true); 177 | 178 | /// 添加监听器观察值的变化 179 | RCalendarController controller = RCalendarController.multiple(...) 180 | addListener((){ 181 | // 是否为多选 182 | // controller.isMultiple 183 | 184 | // 单选下 185 | // 当月份改变时,是否自动选中对应的月份的同一天 186 | // controller.isAutoSelect 187 | // 当前选中的日期 188 | // controller.selectedDate; 189 | 190 | // 多选 191 | // 是否散选,否则为连续选中 192 | // controller.isDispersion; 193 | // 当前选中的日期列表 194 | // controller.selectedDates; 195 | }); 196 | ``` 197 | 198 | ### 2.RCalendarCustomWidget 199 | 200 | 自定义 日历小部件 201 | 202 | ```dart 203 | 204 | class MyRCalendarCustomWidget extends RCalendarCustomWidget { 205 | // 如果你想设置第一天是星期一,请更改MaterialLocalizations 的firstDayOfWeekIndex 206 | // 日 一 二 三 四 五 六 207 | //构建头部 208 | @override 209 | List buildWeekListWidget(MaterialLocalizations localizations){...}; 210 | 211 | // 1 2 3 4 5 6 7 212 | //构建普通的日期 213 | @override 214 | Widget buildDateTime(DateTime time, List types){...}; 215 | 216 | // < 2019年 11月 > 217 | //构建年份和月份 左指示器、右指示器,返回null就没有 218 | @override 219 | Widget buildTopWidget(RCalendarController controller){...}; 220 | 221 | //是否不可用,不可用时,无点击事件 222 | @override 223 | bool isUnable(DateTime time, bool isSameMonth){...}; 224 | 225 | //点击拦截,当返回true时进行拦截,就不会改变选中日期 226 | @override 227 | FutureOr clickInterceptor(DateTime dateTime){...}; 228 | 229 | //子view的高度 230 | @override 231 | double get childHeight=>{...}; 232 | } 233 | 234 | ``` 235 | 236 | ## 3.使用小部件 237 | 238 | ### 3.Use the widget. 239 | 240 | ```dart 241 | import 'package:flutter/material.dart'; 242 | import 'package:r_calendar/r_calendar.dart'; 243 | 244 | void main() => runApp(MyApp()); 245 | class MyApp extends StatelessWidget { 246 | @override 247 | Widget build(BuildContext context) { 248 | return MaterialApp( 249 | title: 'Flutter Demo', 250 | theme: ThemeData( 251 | primarySwatch: Colors.blue, 252 | ), 253 | home: MyHomePage(title: 'Flutter Demo Home Page'), 254 | ); 255 | } 256 | } 257 | 258 | class MyHomePage extends StatefulWidget { 259 | @override 260 | _MyHomePageState createState() => _MyHomePageState(); 261 | } 262 | class _MyHomePageState extends State { 263 | RCalendarController controller; 264 | 265 | @override 266 | void initState() { 267 | super.initState(); 268 | controller = RCalendarController.multiple(selectedDates: [ 269 | DateTime(2019, 12, 1), 270 | DateTime(2019, 12, 2), 271 | DateTime(2019, 12, 3), 272 | ]); 273 | // controller = RCalendarController.single(selectedDate: DateTime.now(),isAutoSelect: true); 274 | } 275 | 276 | @override 277 | void dispose() { 278 | controller.dispose(); 279 | super.dispose(); 280 | } 281 | 282 | @override 283 | Widget build(BuildContext context) { 284 | return Scaffold( 285 | body: RCalendarWidget( 286 | controller: controller, 287 | customWidget: DefaultRCalendarCustomWidget(), 288 | firstDate: DateTime(1970, 1, 1), //当前日历的最小日期 289 | lastDate: DateTime(2055, 12, 31),//当前日历的最大日期 290 | ), 291 | ); 292 | } 293 | } 294 | 295 | ``` -------------------------------------------------------------------------------- /r_calendar/example/lib/en.arb: -------------------------------------------------------------------------------- 1 | { 2 | "more":"more", 3 | "single":"single", 4 | "alone":"alone", 5 | "together":"together", 6 | "add":"add", 7 | "reduce":"reduce", 8 | "automatic":"automatic", 9 | "manual":"manual", 10 | "month":"month", 11 | "week":"week" 12 | } -------------------------------------------------------------------------------- /r_calendar/example/lib/l10n/en.dart: -------------------------------------------------------------------------------- 1 | part of s; 2 | 3 | // Generated by flutter_l10n. Do not edit it. 4 | class $en extends S { 5 | const $en(); 6 | 7 | @override 8 | TextDirection get textDirection => TextDirection.ltr; 9 | 10 | @override 11 | String get more => r'more'; 12 | 13 | @override 14 | String get single => r'single'; 15 | 16 | @override 17 | String get alone => r'alone'; 18 | 19 | @override 20 | String get together => r'together'; 21 | 22 | @override 23 | String get add => r'add'; 24 | 25 | @override 26 | String get reduce => r'reduce'; 27 | 28 | @override 29 | String get automatic => r'automatic'; 30 | 31 | @override 32 | String get manual => r'manual'; 33 | 34 | @override 35 | String get month => r'month'; 36 | 37 | @override 38 | String get week => r'week'; 39 | } 40 | -------------------------------------------------------------------------------- /r_calendar/example/lib/l10n/s.dart: -------------------------------------------------------------------------------- 1 | library s; 2 | 3 | import 'package:flutter/foundation.dart'; 4 | import 'package:flutter/widgets.dart'; 5 | 6 | part 'zh.dart'; 7 | part 'en.dart'; 8 | 9 | // Generated by flutter_l10n. Do not edit it. 10 | class S implements WidgetsLocalizations { 11 | const S(); 12 | 13 | @override 14 | TextDirection get textDirection => TextDirection.ltr; 15 | 16 | static S of(BuildContext context) => Localizations.of(context, S)!; 17 | 18 | static const LocalizationsDelegate delegate = _Delegate(); 19 | 20 | String get more => r'多选'; 21 | 22 | String get single => r'单选'; 23 | 24 | String get alone => r'单独'; 25 | 26 | String get together => r'一起'; 27 | 28 | String get add => r'加'; 29 | 30 | String get reduce => r'减'; 31 | 32 | String get automatic => r'自动'; 33 | 34 | String get manual => r'手动'; 35 | 36 | String get month => r'月'; 37 | 38 | String get week => r'周'; 39 | 40 | @override 41 | String get reorderItemDown => 'down'; 42 | 43 | @override 44 | String get reorderItemLeft => 'left'; 45 | 46 | @override 47 | String get reorderItemRight => 'right'; 48 | 49 | @override 50 | String get reorderItemToEnd => 'end'; 51 | 52 | @override 53 | String get reorderItemToStart => 'start'; 54 | 55 | @override 56 | String get reorderItemUp => 'up'; 57 | } 58 | 59 | class _Delegate extends LocalizationsDelegate { 60 | const _Delegate(); 61 | 62 | @override 63 | Future load(Locale locale) { 64 | String tag = locale.countryCode == null || locale.countryCode!.isEmpty 65 | ? locale.languageCode 66 | : locale.toString(); 67 | switch (tag) { 68 | case 'zh': 69 | return SynchronousFuture(const $zh()); 70 | case 'en': 71 | return SynchronousFuture(const $en()); 72 | } 73 | return SynchronousFuture(const S()); 74 | } 75 | 76 | @override 77 | bool isSupported(Locale locale) => true; 78 | 79 | @override 80 | bool shouldReload(_Delegate old) => false; 81 | } 82 | -------------------------------------------------------------------------------- /r_calendar/example/lib/l10n/zh.dart: -------------------------------------------------------------------------------- 1 | part of s; 2 | 3 | // Generated by flutter_l10n. Do not edit it. 4 | class $zh extends S { 5 | const $zh(); 6 | 7 | @override 8 | TextDirection get textDirection => TextDirection.ltr; 9 | 10 | @override 11 | String get more => r'多选'; 12 | 13 | @override 14 | String get single => r'单选'; 15 | 16 | @override 17 | String get alone => r'单独'; 18 | 19 | @override 20 | String get together => r'一起'; 21 | 22 | @override 23 | String get add => r'加'; 24 | 25 | @override 26 | String get reduce => r'减'; 27 | 28 | @override 29 | String get automatic => r'自动'; 30 | 31 | @override 32 | String get manual => r'手动'; 33 | 34 | @override 35 | String get month => r'月'; 36 | 37 | @override 38 | String get week => r'周'; 39 | } 40 | -------------------------------------------------------------------------------- /r_calendar/example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_localizations/flutter_localizations.dart'; 5 | import 'package:r_calendar/r_calendar.dart'; 6 | import 'package:r_calendar_example/l10n/s.dart'; 7 | import 'package:intl/intl.dart'; 8 | 9 | void main() => runApp(MyApp()); 10 | 11 | class MyApp extends StatefulWidget { 12 | @override 13 | _MyAppState createState() => _MyAppState(); 14 | } 15 | 16 | class _MyAppState extends State { 17 | @override 18 | void initState() { 19 | super.initState(); 20 | } 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | return MaterialApp( 25 | title: 'Flutter Demo', 26 | theme: ThemeData( 27 | primarySwatch: Colors.blue, 28 | ), 29 | localizationsDelegates: [ 30 | S.delegate, 31 | GlobalMaterialLocalizations.delegate, 32 | GlobalWidgetsLocalizations.delegate 33 | ], 34 | supportedLocales: [Locale('zh', ''), Locale('en', '')], 35 | home: MyHomePage(title: 'Flutter Demo Home Page'), 36 | ); 37 | } 38 | } 39 | 40 | class MyHomePage extends StatefulWidget { 41 | MyHomePage({super. key, required this.title}); 42 | 43 | final String title; 44 | 45 | @override 46 | _MyHomePageState createState() => _MyHomePageState(); 47 | } 48 | 49 | class _MyHomePageState extends State { 50 | late RCalendarController> controller; 51 | 52 | @override 53 | void initState() { 54 | super.initState(); 55 | controller = 56 | // RCalendarController.multiple( 57 | // selectedDates: [ 58 | // DateTime(2019, 12, 1), 59 | // DateTime(2019, 12, 2), 60 | // DateTime(2019, 12, 3), 61 | // ], 62 | // isDispersion: false, 63 | // ) 64 | RCalendarController.single( 65 | isAutoSelect: true, 66 | //mark day 67 | initialData: [ 68 | // DateTime.now(), 69 | // DateTime.now().add(Duration(days: 1)), 70 | // DateTime.now().add(Duration(days: 2)), 71 | ] 72 | // selectedDate: DateTime.now(), 73 | ) 74 | ..addListener(() { 75 | // controller.isMultiple 76 | 77 | // single selected 78 | // controller.isAutoSelect 79 | // controller.selectedDate; 80 | 81 | // multiple selected 82 | // controller.selectedDates; 83 | // controller.isDispersion; 84 | }); 85 | } 86 | 87 | @override 88 | void dispose() { 89 | controller.dispose(); 90 | super.dispose(); 91 | } 92 | 93 | @override 94 | Widget build(BuildContext context) { 95 | return Scaffold( 96 | appBar: AppBar( 97 | title: Text(widget.title), 98 | ), 99 | body: Container( 100 | decoration: BoxDecoration( 101 | color: Colors.grey[200], 102 | ), 103 | margin: EdgeInsets.symmetric( 104 | horizontal: 8, 105 | ), 106 | child: RCalendarWidget( 107 | controller: controller, 108 | customWidget: MyRCalendarCustomWidget(), 109 | firstDate: DateTime(1970, 1, 1), 110 | lastDate: DateTime(2055, 12, 31), 111 | ), 112 | // child: RecordDatePicker( 113 | // selectedDate: DateTime.now(), 114 | // firstDate: DateTime(1970, 1, 1), 115 | // lastDate: DateTime(2055, 12, 31), 116 | // children: List.generate(20, (index)=>ListTile( 117 | // title: Text('data $index'), 118 | // )), 119 | // ), 120 | ), 121 | floatingActionButton: Row( 122 | mainAxisSize: MainAxisSize.min, 123 | children: [ 124 | FloatingActionButton( 125 | onPressed: () { 126 | setState(() { 127 | if (controller.isMonthMode) { 128 | controller.mode = RCalendarMode.week; 129 | } else { 130 | controller.mode = RCalendarMode.month; 131 | } 132 | }); 133 | }, 134 | tooltip: controller.isMonthMode 135 | ? S.of(context).month 136 | : S.of(context).week, 137 | child: Text( 138 | controller.isMonthMode ? S.of(context).month : S.of(context).week, 139 | style: TextStyle( 140 | color: Colors.white, 141 | ), 142 | maxLines: 1, 143 | semanticsLabel: controller.isMonthMode 144 | ? S.of(context).month 145 | : S.of(context).week, 146 | ), 147 | ), 148 | SizedBox( 149 | width: 20, 150 | ), 151 | FloatingActionButton( 152 | onPressed: () { 153 | setState(() { 154 | controller.isMultiple = !controller.isMultiple; 155 | }); 156 | }, 157 | tooltip: controller.isMultiple 158 | ? S.of(context).more 159 | : S.of(context).single, 160 | child: Text( 161 | controller.isMultiple ? S.of(context).more : S.of(context).single, 162 | style: TextStyle( 163 | color: Colors.white, 164 | ), 165 | maxLines: 1, 166 | semanticsLabel: controller.isMultiple 167 | ? S.of(context).more 168 | : S.of(context).single, 169 | ), 170 | ), 171 | SizedBox( 172 | width: 20, 173 | ), 174 | ...controller.isMultiple 175 | ? [ 176 | FloatingActionButton( 177 | onPressed: () { 178 | setState(() { 179 | controller.isDispersion = !controller.isDispersion; 180 | }); 181 | }, 182 | tooltip: controller.isDispersion 183 | ? S.of(context).alone 184 | : S.of(context).together, 185 | child: Text( 186 | controller.isDispersion 187 | ? S.of(context).alone 188 | : S.of(context).together, 189 | style: TextStyle( 190 | color: Colors.white, 191 | ), 192 | maxLines: 1, 193 | semanticsLabel: controller.isDispersion 194 | ? S.of(context).alone 195 | : S.of(context).together, 196 | ), 197 | ), 198 | ] 199 | : [ 200 | FloatingActionButton( 201 | onPressed: () { 202 | setState(() { 203 | controller.isAutoSelect = !controller.isAutoSelect; 204 | }); 205 | }, 206 | tooltip: controller.isAutoSelect 207 | ? S.of(context).automatic 208 | : S.of(context).manual, 209 | child: Text( 210 | controller.isAutoSelect 211 | ? S.of(context).automatic 212 | : S.of(context).manual, 213 | style: TextStyle( 214 | color: Colors.white, 215 | ), 216 | maxLines: 1, 217 | semanticsLabel: controller.isAutoSelect 218 | ? S.of(context).automatic 219 | : S.of(context).manual, 220 | ), 221 | ), 222 | SizedBox( 223 | width: 20, 224 | ), 225 | FloatingActionButton( 226 | onPressed: () { 227 | setState(() { 228 | controller.selectedDate = 229 | controller.selectedDate?.add(Duration(days: 1)); 230 | }); 231 | }, 232 | tooltip: S.of(context).add, 233 | child: Icon(Icons.add), 234 | ), 235 | SizedBox( 236 | width: 20, 237 | ), 238 | FloatingActionButton( 239 | onPressed: () { 240 | setState(() { 241 | controller.selectedDate = controller.selectedDate 242 | ?.subtract(Duration(days: 1)); 243 | }); 244 | }, 245 | tooltip: S.of(context).reduce, 246 | child: Icon(Icons.remove), 247 | ) 248 | ], 249 | ], 250 | ), 251 | ); 252 | } 253 | } 254 | 255 | class MyRCalendarCustomWidget extends RCalendarCustomWidget { 256 | @override 257 | Widget buildDateTime( 258 | BuildContext context, DateTime time, List types) { 259 | // new 260 | RCalendarController> controller = RCalendarMarker.of(context) 261 | ?.notifier as RCalendarController>; 262 | // new 263 | TextStyle? childStyle; 264 | BoxDecoration? decoration; 265 | if (types.contains(RCalendarType.disable) || 266 | types.contains(RCalendarType.differentMonth)) { 267 | childStyle = TextStyle( 268 | color: Colors.grey[400], 269 | fontSize: 18, 270 | ); 271 | decoration = BoxDecoration(); 272 | } 273 | if (types.contains(RCalendarType.normal)) { 274 | childStyle = TextStyle( 275 | color: Colors.black, 276 | fontSize: 18, 277 | ); 278 | decoration = BoxDecoration(); 279 | } 280 | 281 | if (types.contains(RCalendarType.today)) { 282 | childStyle = TextStyle( 283 | color: Colors.blue, 284 | fontSize: 18, 285 | ); 286 | } 287 | 288 | if (types.contains(RCalendarType.selected)) { 289 | childStyle = TextStyle( 290 | color: Colors.white, 291 | fontSize: 18, 292 | ); 293 | decoration = BoxDecoration( 294 | shape: BoxShape.circle, 295 | color: Colors.blue, 296 | ); 297 | } 298 | Widget child = Container( 299 | decoration: decoration, 300 | alignment: Alignment.center, 301 | padding: EdgeInsets.only( 302 | bottom: 10, 303 | ), 304 | child: Text( 305 | time.day.toString(), 306 | style: childStyle, 307 | )); 308 | //get mark day 309 | if (controller.data! 310 | .where((m) => 311 | time.year == m.year && time.month == m.month && time.day == m.day) 312 | .isNotEmpty) { 313 | child = Stack( 314 | children: [ 315 | child, 316 | Positioned( 317 | bottom: 0, 318 | left: 0, 319 | right: 0, 320 | child: Icon( 321 | Icons.star, 322 | size: 14, 323 | color: Colors.grey, 324 | )), 325 | ], 326 | ); 327 | } else { 328 | String festal = time.specialFestival; 329 | if (festal.isEmpty) { 330 | festal = time.traditionFestival; 331 | } 332 | if (festal.isEmpty) { 333 | festal = time.gregorianFestival; 334 | } 335 | if (festal.isEmpty) { 336 | festal = time.numToChinese; 337 | } 338 | if (festal.isNotEmpty) { 339 | child = Stack( 340 | children: [ 341 | child, 342 | Positioned( 343 | bottom: 6, 344 | left: 0, 345 | right: 0, 346 | child: Text( 347 | festal, 348 | textAlign: TextAlign.center, 349 | style: childStyle?.copyWith( 350 | fontSize: 11, 351 | ), 352 | ), 353 | ), 354 | ], 355 | ); 356 | } 357 | } 358 | 359 | return Tooltip( 360 | message: MaterialLocalizations.of(context).formatFullDate(time), 361 | child: child, 362 | ); 363 | } 364 | 365 | @override 366 | List buildWeekListWidget( 367 | BuildContext context, MaterialLocalizations localizations) { 368 | return localizations.narrowWeekdays 369 | .map( 370 | (d) => Expanded( 371 | child: ExcludeSemantics( 372 | child: Container( 373 | height: 60, 374 | alignment: Alignment.center, 375 | child: Text( 376 | d, 377 | style: TextStyle( 378 | color: Colors.black, 379 | fontSize: 18, 380 | fontWeight: FontWeight.w600), 381 | ), 382 | ), 383 | ), 384 | ), 385 | ) 386 | .toList(); 387 | } 388 | 389 | @override 390 | double get childHeight => 50; 391 | 392 | @override 393 | FutureOr clickInterceptor(BuildContext context, DateTime dateTime) { 394 | return false; 395 | } 396 | 397 | @override 398 | bool isUnable(BuildContext context, DateTime time, bool isSameMonth) { 399 | return isSameMonth; 400 | } 401 | 402 | @override 403 | Widget buildTopWidget(BuildContext context, RCalendarController? controller) { 404 | return Row( 405 | mainAxisAlignment: MainAxisAlignment.center, 406 | children: [ 407 | IconButton( 408 | icon: Icon(Icons.chevron_left), 409 | onPressed: () { 410 | controller?.previousPage(); 411 | }, 412 | ), 413 | SizedBox( 414 | width: 16, 415 | ), 416 | Text( 417 | DateFormat('yyyy-MM').format(controller?.displayedMonthDate??DateTime.now()), 418 | style: TextStyle(color: Colors.red, fontSize: 18), 419 | ), 420 | SizedBox( 421 | width: 16, 422 | ), 423 | IconButton( 424 | icon: Icon(Icons.chevron_right), 425 | onPressed: () { 426 | controller?.nextPage(); 427 | }, 428 | ), 429 | ], 430 | ); 431 | } 432 | } 433 | -------------------------------------------------------------------------------- /r_calendar/example/lib/zh.arb: -------------------------------------------------------------------------------- 1 | { 2 | "more":"多选", 3 | "single":"单选", 4 | "alone":"单独", 5 | "together":"一起", 6 | "add":"加", 7 | "reduce":"减", 8 | "automatic":"自动", 9 | "manual":"手动", 10 | "month":"月", 11 | "week":"周" 12 | } -------------------------------------------------------------------------------- /r_calendar/example/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | _fe_analyzer_shared: 5 | dependency: transitive 6 | description: 7 | name: _fe_analyzer_shared 8 | sha256: eb376e9acf6938204f90eb3b1f00b578640d3188b4c8a8ec054f9f479af8d051 9 | url: "https://pub.flutter-io.cn" 10 | source: hosted 11 | version: "64.0.0" 12 | analyzer: 13 | dependency: transitive 14 | description: 15 | name: analyzer 16 | sha256: "69f54f967773f6c26c7dcb13e93d7ccee8b17a641689da39e878d5cf13b06893" 17 | url: "https://pub.flutter-io.cn" 18 | source: hosted 19 | version: "6.2.0" 20 | args: 21 | dependency: transitive 22 | description: 23 | name: args 24 | sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 25 | url: "https://pub.flutter-io.cn" 26 | source: hosted 27 | version: "2.4.2" 28 | async: 29 | dependency: transitive 30 | description: 31 | name: async 32 | sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" 33 | url: "https://pub.flutter-io.cn" 34 | source: hosted 35 | version: "2.11.0" 36 | boolean_selector: 37 | dependency: transitive 38 | description: 39 | name: boolean_selector 40 | sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" 41 | url: "https://pub.flutter-io.cn" 42 | source: hosted 43 | version: "2.1.1" 44 | characters: 45 | dependency: transitive 46 | description: 47 | name: characters 48 | sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" 49 | url: "https://pub.flutter-io.cn" 50 | source: hosted 51 | version: "1.3.0" 52 | clock: 53 | dependency: transitive 54 | description: 55 | name: clock 56 | sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf 57 | url: "https://pub.flutter-io.cn" 58 | source: hosted 59 | version: "1.1.1" 60 | collection: 61 | dependency: transitive 62 | description: 63 | name: collection 64 | sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a 65 | url: "https://pub.flutter-io.cn" 66 | source: hosted 67 | version: "1.18.0" 68 | convert: 69 | dependency: transitive 70 | description: 71 | name: convert 72 | sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" 73 | url: "https://pub.flutter-io.cn" 74 | source: hosted 75 | version: "3.1.1" 76 | crypto: 77 | dependency: transitive 78 | description: 79 | name: crypto 80 | sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab 81 | url: "https://pub.flutter-io.cn" 82 | source: hosted 83 | version: "3.0.3" 84 | cupertino_icons: 85 | dependency: "direct main" 86 | description: 87 | name: cupertino_icons 88 | sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d 89 | url: "https://pub.flutter-io.cn" 90 | source: hosted 91 | version: "1.0.6" 92 | dart_style: 93 | dependency: transitive 94 | description: 95 | name: dart_style 96 | sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9" 97 | url: "https://pub.flutter-io.cn" 98 | source: hosted 99 | version: "2.3.6" 100 | fake_async: 101 | dependency: transitive 102 | description: 103 | name: fake_async 104 | sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" 105 | url: "https://pub.flutter-io.cn" 106 | source: hosted 107 | version: "1.3.1" 108 | file: 109 | dependency: transitive 110 | description: 111 | name: file 112 | sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" 113 | url: "https://pub.flutter-io.cn" 114 | source: hosted 115 | version: "7.0.0" 116 | flutter: 117 | dependency: "direct main" 118 | description: flutter 119 | source: sdk 120 | version: "0.0.0" 121 | flutter_l10n: 122 | dependency: "direct dev" 123 | description: 124 | name: flutter_l10n 125 | sha256: c756871e2c20dcdc89385254069f232159b1621d93f0e76e0a7af4e486c2617c 126 | url: "https://pub.flutter-io.cn" 127 | source: hosted 128 | version: "1.1.2" 129 | flutter_localizations: 130 | dependency: "direct main" 131 | description: flutter 132 | source: sdk 133 | version: "0.0.0" 134 | flutter_test: 135 | dependency: "direct dev" 136 | description: flutter 137 | source: sdk 138 | version: "0.0.0" 139 | glob: 140 | dependency: transitive 141 | description: 142 | name: glob 143 | sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" 144 | url: "https://pub.flutter-io.cn" 145 | source: hosted 146 | version: "2.1.2" 147 | intl: 148 | dependency: "direct main" 149 | description: 150 | name: intl 151 | sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" 152 | url: "https://pub.flutter-io.cn" 153 | source: hosted 154 | version: "0.18.1" 155 | matcher: 156 | dependency: transitive 157 | description: 158 | name: matcher 159 | sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" 160 | url: "https://pub.flutter-io.cn" 161 | source: hosted 162 | version: "0.12.16" 163 | material_color_utilities: 164 | dependency: transitive 165 | description: 166 | name: material_color_utilities 167 | sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" 168 | url: "https://pub.flutter-io.cn" 169 | source: hosted 170 | version: "0.5.0" 171 | meta: 172 | dependency: transitive 173 | description: 174 | name: meta 175 | sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e 176 | url: "https://pub.flutter-io.cn" 177 | source: hosted 178 | version: "1.10.0" 179 | package_config: 180 | dependency: transitive 181 | description: 182 | name: package_config 183 | sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" 184 | url: "https://pub.flutter-io.cn" 185 | source: hosted 186 | version: "2.1.0" 187 | path: 188 | dependency: transitive 189 | description: 190 | name: path 191 | sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" 192 | url: "https://pub.flutter-io.cn" 193 | source: hosted 194 | version: "1.8.3" 195 | pub_semver: 196 | dependency: transitive 197 | description: 198 | name: pub_semver 199 | sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" 200 | url: "https://pub.flutter-io.cn" 201 | source: hosted 202 | version: "2.1.4" 203 | r_calendar: 204 | dependency: "direct main" 205 | description: 206 | path: ".." 207 | relative: true 208 | source: path 209 | version: "0.1.11" 210 | sky_engine: 211 | dependency: transitive 212 | description: flutter 213 | source: sdk 214 | version: "0.0.99" 215 | source_span: 216 | dependency: transitive 217 | description: 218 | name: source_span 219 | sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" 220 | url: "https://pub.flutter-io.cn" 221 | source: hosted 222 | version: "1.10.0" 223 | stack_trace: 224 | dependency: transitive 225 | description: 226 | name: stack_trace 227 | sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" 228 | url: "https://pub.flutter-io.cn" 229 | source: hosted 230 | version: "1.11.1" 231 | stream_channel: 232 | dependency: transitive 233 | description: 234 | name: stream_channel 235 | sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 236 | url: "https://pub.flutter-io.cn" 237 | source: hosted 238 | version: "2.1.2" 239 | string_scanner: 240 | dependency: transitive 241 | description: 242 | name: string_scanner 243 | sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" 244 | url: "https://pub.flutter-io.cn" 245 | source: hosted 246 | version: "1.2.0" 247 | term_glyph: 248 | dependency: transitive 249 | description: 250 | name: term_glyph 251 | sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 252 | url: "https://pub.flutter-io.cn" 253 | source: hosted 254 | version: "1.2.1" 255 | test_api: 256 | dependency: transitive 257 | description: 258 | name: test_api 259 | sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" 260 | url: "https://pub.flutter-io.cn" 261 | source: hosted 262 | version: "0.6.1" 263 | typed_data: 264 | dependency: transitive 265 | description: 266 | name: typed_data 267 | sha256: "53bdf7e979cfbf3e28987552fd72f637e63f3c8724c9e56d9246942dc2fa36ee" 268 | url: "https://pub.flutter-io.cn" 269 | source: hosted 270 | version: "1.3.0" 271 | vector_math: 272 | dependency: transitive 273 | description: 274 | name: vector_math 275 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" 276 | url: "https://pub.flutter-io.cn" 277 | source: hosted 278 | version: "2.1.4" 279 | watcher: 280 | dependency: transitive 281 | description: 282 | name: watcher 283 | sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" 284 | url: "https://pub.flutter-io.cn" 285 | source: hosted 286 | version: "1.1.0" 287 | web: 288 | dependency: transitive 289 | description: 290 | name: web 291 | sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 292 | url: "https://pub.flutter-io.cn" 293 | source: hosted 294 | version: "0.3.0" 295 | yaml: 296 | dependency: transitive 297 | description: 298 | name: yaml 299 | sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" 300 | url: "https://pub.flutter-io.cn" 301 | source: hosted 302 | version: "3.1.2" 303 | sdks: 304 | dart: ">=3.2.3 <4.0.0" 305 | -------------------------------------------------------------------------------- /r_calendar/example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: r_calendar_example 2 | description: A new Flutter application. 3 | 4 | # The following defines the version and build number for your application. 5 | # A version number is three numbers separated by dots, like 1.2.43 6 | # followed by an optional build number separated by a +. 7 | # Both the version and the builder number may be overridden in flutter 8 | # build by specifying --build-name and --build-number, respectively. 9 | # In Android, build-name is used as versionName while build-number used as versionCode. 10 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 11 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 12 | # Read more about iOS versioning at 13 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 14 | version: 1.0.0+1 15 | 16 | environment: 17 | sdk: '>=3.2.3 <4.0.0' 18 | 19 | dependencies: 20 | flutter: 21 | sdk: flutter 22 | flutter_localizations: 23 | sdk: flutter 24 | # The following adds the Cupertino Icons font to your application. 25 | # Use with the CupertinoIcons class for iOS style icons. 26 | cupertino_icons: ^1.0.6 27 | r_calendar: 28 | path: ../ 29 | intl: ^0.18.1 30 | dev_dependencies: 31 | flutter_test: 32 | sdk: flutter 33 | flutter_l10n: ^1.1.2 34 | 35 | # For information on the generic Dart part of this file, see the 36 | # following page: https://dart.dev/tools/pub/pubspec 37 | 38 | # The following section is specific to Flutter. 39 | flutter: 40 | # The following line ensures that the Material Icons font is 41 | # included with your application, so that you can use the icons in 42 | # the material Icons class. 43 | uses-material-design: true 44 | 45 | # To add assets to your application, add an assets section, like this: 46 | # assets: 47 | # - images/a_dot_burr.jpeg 48 | # - images/a_dot_ham.jpeg 49 | 50 | # An image asset can refer to one or more resolution-specific "variants", see 51 | # https://flutter.dev/assets-and-images/#resolution-aware. 52 | 53 | # For details regarding adding assets from package dependencies, see 54 | # https://flutter.dev/assets-and-images/#from-packages 55 | 56 | # To add custom fonts to your application, add a fonts section here, 57 | # in this "flutter" section. Each entry in this list should have a 58 | # "family" key with the font family name, and a "fonts" key with a 59 | # list giving the asset and other descriptors for the font. For 60 | # example: 61 | # fonts: 62 | # - family: Schyler 63 | # fonts: 64 | # - asset: fonts/Schyler-Regular.ttf 65 | # - asset: fonts/Schyler-Italic.ttf 66 | # style: italic 67 | # - family: Trajan Pro 68 | # fonts: 69 | # - asset: fonts/TrajanPro.ttf 70 | # - asset: fonts/TrajanPro_Bold.ttf 71 | # weight: 700 72 | # 73 | # For details regarding fonts from package dependencies, 74 | # see https://flutter.dev/custom-fonts/#from-packages 75 | -------------------------------------------------------------------------------- /r_calendar/example/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:r_calendar_example/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MyApp()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /r_calendar/lib/r_calendar.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The rhyme_lph Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | library r_calendar; 6 | 7 | import 'dart:async'; 8 | 9 | import 'package:flutter/material.dart'; 10 | import 'package:r_calendar/src/r_calendar_utils.dart'; 11 | import 'package:r_calendar/src/r_calendar_widget_item.dart'; 12 | 13 | import 'src/r_calendar_controller.dart'; 14 | import 'src/r_calendar_custom_widget.dart'; 15 | export 'src/r_calendar_controller.dart'; 16 | export 'src/r_calendar_custom_widget.dart'; 17 | export 'src/r_calendar_widget_item.dart'; 18 | export 'src/r_calendar_extension.dart'; 19 | 20 | class RCalendarWidget extends StatefulWidget { 21 | // 最小日期 22 | final DateTime? firstDate; 23 | 24 | // 最大日期 25 | final DateTime? lastDate; 26 | 27 | // 控制器 28 | final RCalendarController? controller; 29 | 30 | //自定义部件 31 | final RCalendarCustomWidget? customWidget; 32 | 33 | const RCalendarWidget( 34 | {Key? key, 35 | this.firstDate, 36 | this.lastDate, 37 | this.controller, 38 | this.customWidget}) 39 | : super(key: key); 40 | 41 | @override 42 | _RCalendarWidgetState createState() => _RCalendarWidgetState(); 43 | } 44 | 45 | class _RCalendarWidgetState extends State { 46 | //今天的日期 47 | DateTime? _toDayDate; 48 | 49 | //用于更新今天 50 | Timer? _timer; 51 | 52 | ///选中日期更改 53 | void _handleDayChanged(DateTime value) { 54 | setState(() { 55 | widget.controller!.updateSelected(value); 56 | }); 57 | } 58 | 59 | /// 更新当前日期 60 | void _updateCurrentDate() { 61 | _toDayDate = DateTime.now(); 62 | final DateTime tomorrow = 63 | DateTime(_toDayDate!.year, _toDayDate!.month, _toDayDate!.day + 1); 64 | Duration timeUntilTomorrow = tomorrow.difference(_toDayDate!); 65 | timeUntilTomorrow += 66 | const Duration(seconds: 1); // so we don't miss it by rounding 67 | _timer?.cancel(); 68 | _timer = Timer(timeUntilTomorrow, () { 69 | setState(() { 70 | _updateCurrentDate(); 71 | }); 72 | }); 73 | } 74 | 75 | ///处理月份页改变 76 | void _handlePageChanged(int page, RCalendarMode mode) { 77 | setState(() { 78 | widget.controller!.updateDisplayedDate(page, mode); 79 | }); 80 | } 81 | 82 | @override 83 | void initState() { 84 | super.initState(); 85 | widget.controller!.initial(context, widget.firstDate!, widget.lastDate); 86 | _updateCurrentDate(); 87 | } 88 | 89 | @override 90 | void didUpdateWidget(RCalendarWidget oldWidget) { 91 | if (widget.controller!.isMultiple == false) { 92 | DateTime? firstDate = widget.controller!.selectedDate; 93 | if (firstDate != null) { 94 | if (firstDate.month != widget.controller!.displayedMonthDate!.month || 95 | firstDate.year != widget.controller!.displayedMonthDate!.year) { 96 | if (widget.controller!.isMonthMode) { 97 | widget.controller!.monthController! 98 | .jumpToPage(widget.controller!.selectedPage); 99 | } else { 100 | widget.controller!.weekController! 101 | .jumpToPage(widget.controller!.selectedPage); 102 | } 103 | } 104 | } 105 | } 106 | 107 | super.didUpdateWidget(oldWidget); 108 | } 109 | 110 | int _getSelectRowCount() { 111 | if (!widget.controller!.isMonthMode) return 1; 112 | int rowCount = 4; 113 | int maxDay = RCalendarUtils.getDaysInMonth( 114 | widget.controller!.displayedMonthDate!.year, 115 | widget.controller!.displayedMonthDate!.month); 116 | int labelCount = maxDay + 117 | RCalendarUtils.computeFirstDayOffset( 118 | widget.controller!.displayedMonthDate!.year, 119 | widget.controller!.displayedMonthDate!.month, 120 | MaterialLocalizations.of(context)); 121 | if (labelCount <= 7 * 4) { 122 | rowCount = 4; 123 | } else if (labelCount <= 7 * 5) { 124 | rowCount = 5; 125 | } else if (labelCount <= 7 * 6) { 126 | rowCount = 6; 127 | } 128 | return rowCount; 129 | } 130 | 131 | @override 132 | Widget build(BuildContext context) { 133 | double maxHeight = 134 | widget.customWidget!.childHeight * _getSelectRowCount() + 1; 135 | //获取星期的第一天 136 | final MaterialLocalizations localizations = 137 | MaterialLocalizations.of(context); 138 | return RCalendarMarker( 139 | customWidget: widget.customWidget, 140 | toDayDate: _toDayDate, 141 | onChanged: _handleDayChanged, 142 | controller: widget.controller!, 143 | child: Column( 144 | mainAxisSize: MainAxisSize.min, 145 | children: [ 146 | widget.customWidget?.buildTopWidget(context, widget.controller) ?? 147 | Container(), 148 | Row( 149 | children: 150 | widget.customWidget!.buildWeekListWidget(context, localizations), 151 | ), 152 | AnimatedContainer( 153 | duration: Duration(milliseconds: 300), 154 | height: maxHeight, 155 | child: IndexedStack( 156 | index: widget.controller!.isMonthMode ? 1 : 0, 157 | children: [ 158 | PageView.builder( 159 | key: ValueKey("week_view"), 160 | controller: widget.controller!.weekController, 161 | itemBuilder: _builderWeekItems, 162 | onPageChanged: (int page) { 163 | _handlePageChanged(page, RCalendarMode.week); 164 | }, 165 | itemCount: widget.controller!.maxWeekPage, 166 | allowImplicitScrolling: true, 167 | ), 168 | PageView.builder( 169 | key: ValueKey("month_view"), 170 | controller: widget.controller!.monthController, 171 | itemBuilder: _builderMonthItems, 172 | onPageChanged: (int page) { 173 | _handlePageChanged(page, RCalendarMode.month); 174 | }, 175 | itemCount: widget.controller!.maxMonthPage, 176 | allowImplicitScrolling: true, 177 | ), 178 | ], 179 | ), 180 | ), 181 | ], 182 | ), 183 | ); 184 | } 185 | 186 | Widget _builderMonthItems(BuildContext context, int index) { 187 | final DateTime month = 188 | RCalendarUtils.addMonthsToMonthDate(widget.firstDate!, index); 189 | return RCalendarMonthItem( 190 | monthDate: month, 191 | ); 192 | } 193 | 194 | Widget _builderWeekItems(BuildContext context, int index) { 195 | DateTime week = RCalendarUtils.addWeeksToWeeksDate( 196 | widget.firstDate!, index, MaterialLocalizations.of(context)); 197 | return AutoKeepAliveWidget( 198 | child: RCalendarWeekItem( 199 | weekDate: week, 200 | ), 201 | ); 202 | } 203 | } 204 | 205 | class AutoKeepAliveWidget extends StatefulWidget { 206 | final Widget? child; 207 | 208 | const AutoKeepAliveWidget({Key? key, this.child}) : super(key: key); 209 | 210 | @override 211 | _AutoKeepAliveWidgetState createState() => _AutoKeepAliveWidgetState(); 212 | } 213 | 214 | class _AutoKeepAliveWidgetState extends State 215 | with AutomaticKeepAliveClientMixin { 216 | @override 217 | Widget build(BuildContext context) { 218 | super.build(context); 219 | return widget.child!; 220 | } 221 | 222 | @override 223 | bool get wantKeepAlive => true; 224 | } 225 | 226 | class RCalendarMarker extends InheritedNotifier { 227 | //部件 228 | final RCalendarCustomWidget? customWidget; 229 | 230 | //今天 231 | final DateTime? toDayDate; 232 | 233 | //当前选中的日期事件 234 | final ValueChanged onChanged; 235 | 236 | const RCalendarMarker({ 237 | required this.onChanged, 238 | required this.toDayDate, 239 | required this.customWidget, 240 | required RCalendarController controller, 241 | required Widget child, 242 | }) : super(notifier: controller, child: child); 243 | 244 | static RCalendarMarker? of(BuildContext context, {bool nullOk = false}) { 245 | final RCalendarMarker? inherited = 246 | context.dependOnInheritedWidgetOfExactType(); 247 | assert(() { 248 | if (nullOk) { 249 | return true; 250 | } 251 | if (inherited == null) { 252 | throw FlutterError( 253 | 'Unable to find a $RCalendarMarker widget in the context.\n' 254 | '$RCalendarMarker.of() was called with a context that does not contain a ' 255 | '$RCalendarMarker widget.\n' 256 | 'No $RCalendarMarker ancestor could be found starting from the context that was ' 257 | 'passed to $RCalendarMarker.of().\n' 258 | 'The context used was:\n' 259 | ' $context'); 260 | } 261 | return true; 262 | }()); 263 | return inherited; 264 | } 265 | 266 | @override 267 | bool updateShouldNotify(RCalendarMarker oldWidget) { 268 | return this.notifier != oldWidget.notifier || 269 | this.toDayDate != toDayDate || 270 | this.customWidget != customWidget || 271 | this.onChanged != onChanged; 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /r_calendar/lib/src/r_calendar_controller.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The rhyme_lph Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | import 'dart:collection'; 5 | 6 | import 'package:flutter/material.dart'; 7 | import 'dart:math' as math; 8 | 9 | import 'package:r_calendar/src/r_calendar_utils.dart'; 10 | 11 | enum RCalendarMode { 12 | week, 13 | month, 14 | } 15 | 16 | /// you can use [RCalendarMarker.of(context).notifier] get it. 17 | class RCalendarController extends ChangeNotifier { 18 | RCalendarController.single( 19 | {RCalendarMode? mode, 20 | DateTime? selectedDate, 21 | bool? isAutoSelect, 22 | T? initialData}) 23 | : _selectedDates = selectedDate != null 24 | ? SplayTreeSet.of([selectedDate]) 25 | : SplayTreeSet(), 26 | _isMultiple = false, 27 | _isDispersion = true, 28 | _isAutoSelect = isAutoSelect ?? true, 29 | _data = initialData, 30 | _mode = mode ?? RCalendarMode.month; 31 | 32 | RCalendarController.multiple( 33 | {RCalendarMode? mode, 34 | required List selectedDates, 35 | bool? isDispersion, 36 | T? initialData}) 37 | : _isMultiple = true, 38 | _isDispersion = isDispersion ?? true, 39 | _isAutoSelect = false, 40 | _selectedDates = SplayTreeSet.of(selectedDates), 41 | _data = initialData, 42 | _mode = mode ?? RCalendarMode.month; 43 | 44 | //当前显示的月份 45 | DateTime? displayedMonthDate; 46 | 47 | // 月视图控制器 48 | PageController? monthController; 49 | 50 | // 星期视图控制器 51 | PageController? weekController; 52 | 53 | late DateTime firstDate; 54 | 55 | DateTime? lastDate; 56 | 57 | //多选 58 | SplayTreeSet _selectedDates; 59 | 60 | List get selectedDates { 61 | // _selectedDates.sort(_sortSelectDates); 62 | return _selectedDates.toList(); 63 | } 64 | 65 | set selectedDates(List selectedDates) { 66 | _selectedDates = SplayTreeSet.of(selectedDates); 67 | } 68 | 69 | //单选选中的日期 70 | DateTime? get selectedDate => 71 | _selectedDates.isEmpty ? null : _selectedDates.first; 72 | 73 | set selectedDate(DateTime? selectedDate) { 74 | _selectedDates.remove(_selectedDates.first); 75 | _selectedDates.add(selectedDate); 76 | } 77 | 78 | //单选是否当切换月份时自动选 79 | bool _isAutoSelect; 80 | 81 | bool get isAutoSelect => _isAutoSelect; 82 | 83 | set isAutoSelect(bool isAutoSelect) { 84 | this._isAutoSelect = isAutoSelect; 85 | notifyListeners(); 86 | } 87 | 88 | //是否为散选 89 | bool _isDispersion; 90 | 91 | bool get isDispersion => _isDispersion; 92 | 93 | set isDispersion(bool isDispersion) { 94 | _isDispersion = isDispersion; 95 | if (_selectedDates.length > 1 && !isDispersion) { 96 | DateTime first = _selectedDates.first!; 97 | DateTime end = _selectedDates.last!; 98 | Duration duration = end.difference(first); 99 | _selectedDates.clear(); 100 | _selectedDates.addAll(List.generate( 101 | duration.inDays, (int index) => first.add(Duration(days: index))) 102 | .toList() 103 | ..add(end)); 104 | } 105 | notifyListeners(); 106 | } 107 | 108 | //是否为多选 109 | bool _isMultiple; 110 | 111 | bool get isMultiple => _isMultiple; 112 | 113 | set isMultiple(bool isMultiple) { 114 | this._isMultiple = isMultiple; 115 | if (_selectedDates.length > 1 && 116 | isMultiple == false) { 117 | DateTime? firstDateTime = _selectedDates.first; 118 | _selectedDates.clear(); 119 | _selectedDates.add(firstDateTime); 120 | } 121 | notifyListeners(); 122 | } 123 | 124 | //日历模式,支持周视图,月视图 125 | RCalendarMode _mode; 126 | 127 | RCalendarMode get mode => _mode; 128 | 129 | set mode(RCalendarMode mode) { 130 | if (_mode != mode) { 131 | _mode = mode; 132 | jumpTo(selectedDate ?? displayedMonthDate); 133 | notifyListeners(); 134 | } 135 | } 136 | 137 | bool get isMonthMode => _mode == RCalendarMode.month; 138 | 139 | //拓展数据 140 | T? _data; 141 | 142 | T? get data => _data; 143 | 144 | set data(T? data) { 145 | _data = data; 146 | notifyListeners(); 147 | } 148 | 149 | MaterialLocalizations? _localizations; 150 | 151 | void initial(BuildContext context, DateTime firstDate, DateTime? endDate) { 152 | this.firstDate = firstDate; 153 | this.lastDate = endDate; 154 | if (isMultiple) { 155 | // ignore: unnecessary_null_comparison 156 | if(_selectedDates == null){ 157 | _selectedDates = SplayTreeSet(); 158 | } 159 | } else { 160 | // ignore: unnecessary_null_comparison 161 | if(_selectedDates == null){ 162 | _selectedDates = SplayTreeSet.of([ 163 | DateTime(DateTime.now().year, DateTime.now().month, DateTime.now().day) 164 | ]); 165 | } 166 | } 167 | displayedMonthDate = selectedDate ?? DateTime.now(); 168 | final int monthPage = 169 | RCalendarUtils.monthDelta(firstDate, displayedMonthDate!); 170 | monthController = PageController(initialPage: monthPage); 171 | _localizations = DefaultMaterialLocalizations(); 172 | 173 | final int weekPage = RCalendarUtils.weekDelta( 174 | firstDate, displayedMonthDate!, _localizations); 175 | weekController = PageController(initialPage: weekPage); 176 | // controller.addListener(() { 177 | // displayMonth = _addMonthsToMonthDate(firstDate, controller.page ~/ 1); 178 | // notifyListeners(); 179 | // }); 180 | } 181 | 182 | //下一页 183 | void nextPage({Duration? duration, Curve? curve}) { 184 | if (isMonthMode) { 185 | monthController?.nextPage( 186 | duration: duration ?? Duration(milliseconds: 300), 187 | curve: curve ?? Curves.linear); 188 | } else { 189 | weekController?.nextPage( 190 | duration: duration ?? Duration(milliseconds: 300), 191 | curve: curve ?? Curves.linear); 192 | } 193 | } 194 | 195 | //上一页 196 | void previousPage({Duration? duration, Curve? curve}) { 197 | if (isMonthMode) { 198 | monthController?.previousPage( 199 | duration: duration ?? Duration(milliseconds: 300), 200 | curve: curve ?? Curves.linear); 201 | } else { 202 | weekController?.previousPage( 203 | duration: duration ?? Duration(milliseconds: 300), 204 | curve: curve ?? Curves.linear); 205 | } 206 | } 207 | 208 | //跳转到DateTime 209 | void jumpTo(DateTime? dateTime) { 210 | if (isMonthMode) { 211 | final int monthPage = RCalendarUtils.monthDelta(firstDate, dateTime!); 212 | if (monthController != null && monthPage != monthController!.page! ~/ 1) { 213 | // print('更新显示的月份-月份(实际):$monthPage , 当前页数:${monthController.page ~/ 1}'); 214 | monthController!.jumpToPage(monthPage); 215 | } 216 | } else { 217 | final int weekPage = 218 | RCalendarUtils.weekDelta(firstDate, dateTime!, _localizations); 219 | if (weekController != null && weekPage != weekController!.page! ~/ 1) { 220 | weekController?.jumpToPage(weekPage); 221 | } 222 | } 223 | } 224 | 225 | /// when you select the dateTime ,will use this method 226 | /// 227 | /// [selectedDate] your select dateTime 228 | /// 229 | void updateSelected(DateTime selectedDate) { 230 | // _selectedDates.sort(_sortSelectDates); 231 | if (isMultiple) { 232 | // multiple select 233 | if (_selectedDates.contains(selectedDate)) { 234 | // your select dateTime in your selectedDates 235 | if (_isDispersion) { 236 | // dispersion select only remove 237 | _selectedDates.remove(selectedDate); 238 | } else { 239 | // //进行移除 240 | // selectedDates.sort((a, b) => 241 | // a.millisecondsSinceEpoch > b.millisecondsSinceEpoch ? 0 : 1); 242 | if (selectedDate != _selectedDates.first && 243 | selectedDate != _selectedDates.last) { 244 | List dateTimes = _selectedDates.toList(); 245 | 246 | final int index = dateTimes.indexOf(selectedDate); 247 | Duration duration1 = selectedDate.difference(_selectedDates.first!); 248 | Duration duration2 = _selectedDates.last!.difference(selectedDate); 249 | if (duration1.inMilliseconds < duration2.inMilliseconds) { 250 | dateTimes.removeRange(index + 1, _selectedDates.length); 251 | } else { 252 | dateTimes.removeRange(0, index); 253 | } 254 | _selectedDates.clear(); 255 | _selectedDates.addAll(dateTimes); 256 | } 257 | } 258 | } else { 259 | if (_isDispersion || _selectedDates.length < 1) { 260 | _selectedDates.add(selectedDate); 261 | } else { 262 | DateTime? first; 263 | DateTime? last; 264 | 265 | if (_selectedDates.length == 1) { 266 | first = _selectedDates.first; 267 | if (first!.isBefore(selectedDate)) { 268 | last = selectedDate; 269 | } else { 270 | last = first; 271 | first = last; 272 | } 273 | } else { 274 | first = _selectedDates.first; 275 | last = _selectedDates.last; 276 | } 277 | 278 | if (first!.isAfter(selectedDate) && 279 | (last!.isAfter(selectedDate) || _selectedDates.length == 1)) { 280 | Duration duration = first.difference(selectedDate); 281 | final List addList = List.generate(duration.inDays, 282 | (int index) => first!.subtract(Duration(days: index))); 283 | addList.add(selectedDate); 284 | addList.forEach((dateTime) { 285 | if (!_selectedDates.contains(dateTime)) { 286 | // _selectedDates.insert(0, dateTime); 287 | _selectedDates..add(dateTime); 288 | } 289 | }); 290 | } else if (first.isBefore(selectedDate) && 291 | (last!.isBefore(selectedDate) || _selectedDates.length == 1)) { 292 | Duration duration = selectedDate 293 | .difference(_selectedDates.length == 1 ? first : last); 294 | final List addList = List.generate( 295 | duration.inDays, 296 | (int index) => _selectedDates.length == 1 297 | ? first!.add(Duration(days: index)) 298 | : last!.add(Duration(days: index))); 299 | addList.add(selectedDate); 300 | addList.forEach((dateTIme) { 301 | if (!_selectedDates.contains(dateTIme)) { 302 | _selectedDates.add(dateTIme); 303 | } 304 | }); 305 | } 306 | } 307 | } 308 | } else { 309 | if (_selectedDates.isEmpty) { 310 | _selectedDates.add(selectedDate); 311 | } else { 312 | _selectedDates.remove(_selectedDates.first); 313 | _selectedDates.add(selectedDate); 314 | } 315 | } 316 | notifyListeners(); 317 | if (!isMonthMode) { 318 | int weekPage = 319 | RCalendarUtils.weekDelta(firstDate, selectedDate, _localizations); 320 | updateDisplayedDate(weekPage, mode); 321 | } 322 | } 323 | 324 | //更新display日期 325 | void updateDisplayedDate(int page, RCalendarMode mode) { 326 | if (mode != _mode) return; 327 | if (isMonthMode) { 328 | // print('更新显示的月份-月份$page'); 329 | displayedMonthDate = RCalendarUtils.addMonthsToMonthDate(firstDate, page); 330 | if (isAutoSelect && isMultiple == false && selectedDate != null) { 331 | int daysInMonth = RCalendarUtils.getDaysInMonth( 332 | displayedMonthDate!.year, displayedMonthDate!.month); 333 | int day = math.min(daysInMonth, this.selectedDate!.day); 334 | this.selectedDate = 335 | DateTime(displayedMonthDate!.year, displayedMonthDate!.month, day); 336 | } 337 | } else { 338 | // print('更新显示的月份-周期,页数$page'); 339 | displayedMonthDate = 340 | RCalendarUtils.addWeeksToWeeksDate(firstDate, page, _localizations); 341 | if (isAutoSelect && isMultiple == false && selectedDate != null) { 342 | bool isBefore = this.selectedDate!.isBefore(displayedMonthDate!); 343 | bool isAfter = this 344 | .selectedDate! 345 | .isAfter(displayedMonthDate!.add(Duration(days: 7))); 346 | if (isBefore || isAfter) { 347 | // this.selectedDate = displayedMonthDate.add(Duration(days: oldIndex)); 348 | this.selectedDate = 349 | this.selectedDate!.add(Duration(days: isBefore ? 7 : -7)); 350 | } 351 | int selectDatePage = 352 | RCalendarUtils.weekDelta(firstDate, selectedDate!, _localizations); 353 | if (displayedMonthDate!.month != selectedDate!.month && 354 | selectDatePage == page) { 355 | //月份不一样 356 | displayedMonthDate = selectedDate; 357 | } 358 | } 359 | } 360 | notifyListeners(); 361 | } 362 | 363 | //最大的周期页数 364 | int get maxWeekPage => 365 | RCalendarUtils.weekDelta(firstDate, lastDate!, _localizations) + 1; 366 | 367 | //最大的月份页数 368 | int get maxMonthPage => RCalendarUtils.monthDelta(firstDate, lastDate!) + 1; 369 | 370 | //选中的页数 371 | int get selectedPage => isMonthMode 372 | ? RCalendarUtils.monthDelta( 373 | firstDate, selectedDate ?? displayedMonthDate!) 374 | : RCalendarUtils.weekDelta( 375 | firstDate, selectedDate ?? displayedMonthDate!, _localizations); 376 | 377 | //当前显示的页面 378 | int get displayedPage => isMonthMode 379 | ? RCalendarUtils.monthDelta(firstDate, displayedMonthDate!) 380 | : RCalendarUtils.weekDelta( 381 | firstDate, displayedMonthDate!, _localizations); 382 | 383 | @override 384 | void dispose() { 385 | monthController?.dispose(); 386 | weekController?.dispose(); 387 | super.dispose(); 388 | } 389 | } 390 | -------------------------------------------------------------------------------- /r_calendar/lib/src/r_calendar_custom_widget.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The rhyme_lph Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | import 'dart:async'; 5 | 6 | import 'package:flutter/material.dart'; 7 | import 'package:intl/intl.dart'; 8 | import 'package:r_calendar/r_calendar.dart'; 9 | 10 | enum RCalendarType { 11 | //正常 12 | normal, 13 | //不可用 14 | disable, 15 | //不是同一个月 16 | differentMonth, 17 | //选中的 18 | selected, 19 | //当天 20 | today, 21 | } 22 | 23 | abstract class RCalendarCustomWidget { 24 | // 如果你想设置第一天是星期一,请更改MaterialLocalizations 的firstDayOfWeekIndex 25 | // 日 一 二 三 四 五 六 26 | //构建头部 27 | List buildWeekListWidget( 28 | BuildContext context, MaterialLocalizations localizations); 29 | 30 | // 1 2 3 4 5 6 7 31 | //构建普通的日期 32 | Widget buildDateTime( 33 | BuildContext context, DateTime time, List types); 34 | 35 | // < 2019年 11月 > 36 | //构建年份和月份,指示器 37 | Widget buildTopWidget(BuildContext context, RCalendarController? controller); 38 | 39 | //是否不可用,不可用时,无点击事件 40 | bool isUnable(BuildContext context, DateTime time, bool isSameMonth); 41 | 42 | //点击拦截,当返回true时进行拦截,就不会改变选中日期 43 | FutureOr clickInterceptor(BuildContext context, DateTime dateTime); 44 | 45 | //子view的高度 46 | double get childHeight; 47 | } 48 | 49 | class DefaultRCalendarCustomWidget extends RCalendarCustomWidget { 50 | @override 51 | Widget buildDateTime( 52 | BuildContext context, DateTime time, List types) { 53 | TextStyle? childStyle; 54 | BoxDecoration? decoration; 55 | 56 | if (types.contains(RCalendarType.disable) || 57 | types.contains(RCalendarType.differentMonth)) { 58 | childStyle = TextStyle( 59 | color: Colors.grey[400], 60 | fontSize: 18, 61 | ); 62 | decoration = BoxDecoration(); 63 | } 64 | if (types.contains(RCalendarType.normal)) { 65 | childStyle = TextStyle( 66 | color: Colors.black, 67 | fontSize: 18, 68 | ); 69 | decoration = BoxDecoration(); 70 | } 71 | 72 | if (types.contains(RCalendarType.today)) { 73 | childStyle = TextStyle( 74 | color: Colors.blue, 75 | fontSize: 18, 76 | ); 77 | } 78 | 79 | if (types.contains(RCalendarType.selected)) { 80 | childStyle = TextStyle( 81 | color: Colors.white, 82 | fontSize: 18, 83 | ); 84 | decoration = BoxDecoration( 85 | shape: BoxShape.circle, 86 | color: Colors.blue, 87 | ); 88 | } 89 | return Tooltip( 90 | message: MaterialLocalizations.of(context).formatFullDate(time), 91 | child: Container( 92 | decoration: decoration, 93 | alignment: Alignment.center, 94 | child: Text( 95 | time.day.toString(), 96 | style: childStyle, 97 | )), 98 | ); 99 | } 100 | 101 | @override 102 | List buildWeekListWidget( 103 | BuildContext context, MaterialLocalizations localizations) { 104 | return localizations.narrowWeekdays 105 | .map( 106 | (d) => Expanded( 107 | child: ExcludeSemantics( 108 | child: Container( 109 | height: 60, 110 | alignment: Alignment.center, 111 | child: Text( 112 | d, 113 | style: TextStyle( 114 | color: Colors.black, 115 | fontSize: 18, 116 | fontWeight: FontWeight.w600), 117 | ), 118 | ), 119 | ), 120 | ), 121 | ) 122 | .toList(); 123 | } 124 | 125 | @override 126 | double get childHeight => 50; 127 | 128 | @override 129 | FutureOr clickInterceptor(BuildContext context, DateTime dateTime) { 130 | return false; 131 | } 132 | 133 | @override 134 | bool isUnable(BuildContext context, DateTime time, bool isSameMonth) { 135 | return isSameMonth; 136 | } 137 | 138 | @override 139 | Widget buildTopWidget(BuildContext context, RCalendarController? controller) { 140 | return Row( 141 | mainAxisAlignment: MainAxisAlignment.center, 142 | children: [ 143 | IconButton( 144 | icon: Icon(Icons.chevron_left), 145 | onPressed: () { 146 | controller!.previousPage(); 147 | }, 148 | ), 149 | SizedBox( 150 | width: 16, 151 | ), 152 | Text( 153 | DateFormat('yyyy-MM').format(controller!.displayedMonthDate!), 154 | style: TextStyle(color: Colors.red, fontSize: 18), 155 | ), 156 | SizedBox( 157 | width: 16, 158 | ), 159 | IconButton( 160 | icon: Icon(Icons.chevron_right), 161 | onPressed: () { 162 | controller.nextPage(); 163 | }, 164 | ), 165 | ], 166 | ); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /r_calendar/lib/src/r_calendar_extension.dart: -------------------------------------------------------------------------------- 1 | extension DateTimeExternal on DateTime { 2 | // 是否为周末 3 | bool get isWeekend => 4 | weekday == DateTime.saturday || weekday == DateTime.sunday; 5 | 6 | // 是否是闰年 7 | bool get isLeapYear => 8 | ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0); 9 | 10 | bool get isCurrentDay => this.difference(DateTime.now()).inDays == 0; 11 | 12 | // 本月有几天 13 | int get daysInMonth { 14 | int count = 0; 15 | //判断大月份 16 | if (month == 1 || 17 | month == 3 || 18 | month == 5 || 19 | month == 7 || 20 | month == 8 || 21 | month == 10 || 22 | month == 12) { 23 | count = 31; 24 | } 25 | //判断小月 26 | if (month == 4 || month == 6 || month == 9 || month == 11) { 27 | count = 30; 28 | } 29 | 30 | //判断平年与闰年 31 | if (month == 2) { 32 | if (isLeapYear) { 33 | count = 29; 34 | } else { 35 | count = 28; 36 | } 37 | } 38 | return count; 39 | } 40 | 41 | // 本月第几周 42 | int get weekInMonth { 43 | DateTime firstDayInMonth = DateTime(year, month, 1); 44 | Duration duration = this.difference(firstDayInMonth); 45 | return duration.inDays ~/ 7 + 1; 46 | } 47 | 48 | // 本周第几天 49 | int get dayInWeek { 50 | DateTime firstDayInMonth = new DateTime( 51 | year, 52 | month, 53 | ); 54 | Duration duration = this.difference(firstDayInMonth); 55 | return duration.inDays ~/ 7 + 1; 56 | } 57 | } 58 | 59 | const List LUNAR_MONTH_DAYS = [ 60 | 1887, 61 | 0x1694, 62 | 0x16aa, 63 | 0x4ad5, 64 | 0xab6, 65 | 0xc4b7, 66 | 0x4ae, 67 | 0xa56, 68 | 0xb52a, 69 | 0x1d2a, 70 | 0xd54, 71 | 0x75aa, 72 | 0x156a, 73 | 0x1096d, 74 | 0x95c, 75 | 0x14ae, 76 | 0xaa4d, 77 | 0x1a4c, 78 | 0x1b2a, 79 | 0x8d55, 80 | 0xad4, 81 | 0x135a, 82 | 0x495d, 83 | 0x95c, 84 | 0xd49b, 85 | 0x149a, 86 | 0x1a4a, 87 | 0xbaa5, 88 | 0x16a8, 89 | 0x1ad4, 90 | 0x52da, 91 | 0x12b6, 92 | 0xe937, 93 | 0x92e, 94 | 0x1496, 95 | 0xb64b, 96 | 0xd4a, 97 | 0xda8, 98 | 0x95b5, 99 | 0x56c, 100 | 0x12ae, 101 | 0x492f, 102 | 0x92e, 103 | 0xcc96, 104 | 0x1a94, 105 | 0x1d4a, 106 | 0xada9, 107 | 0xb5a, 108 | 0x56c, 109 | 0x726e, 110 | 0x125c, 111 | 0xf92d, 112 | 0x192a, 113 | 0x1a94, 114 | 0xdb4a, 115 | 0x16aa, 116 | 0xad4, 117 | 0x955b, 118 | 0x4ba, 119 | 0x125a, 120 | 0x592b, 121 | 0x152a, 122 | 0xf695, 123 | 0xd94, 124 | 0x16aa, 125 | 0xaab5, 126 | 0x9b4, 127 | 0x14b6, 128 | 0x6a57, 129 | 0xa56, 130 | 0x1152a, 131 | 0x1d2a, 132 | 0xd54, 133 | 0xd5aa, 134 | 0x156a, 135 | 0x96c, 136 | 0x94ae, 137 | 0x14ae, 138 | 0xa4c, 139 | 0x7d26, 140 | 0x1b2a, 141 | 0xeb55, 142 | 0xad4, 143 | 0x12da, 144 | 0xa95d, 145 | 0x95a, 146 | 0x149a, 147 | 0x9a4d, 148 | 0x1a4a, 149 | 0x11aa5, 150 | 0x16a8, 151 | 0x16d4, 152 | 0xd2da, 153 | 0x12b6, 154 | 0x936, 155 | 0x9497, 156 | 0x1496, 157 | 0x1564b, 158 | 0xd4a, 159 | 0xda8, 160 | 0xd5b4, 161 | 0x156c, 162 | 0x12ae, 163 | 0xa92f, 164 | 0x92e, 165 | 0xc96, 166 | 0x6d4a, 167 | 0x1d4a, 168 | 0x10d65, 169 | 0xb58, 170 | 0x156c, 171 | 0xb26d, 172 | 0x125c, 173 | 0x192c, 174 | 0x9a95, 175 | 0x1a94, 176 | 0x1b4a, 177 | 0x4b55, 178 | 0xad4, 179 | 0xf55b, 180 | 0x4ba, 181 | 0x125a, 182 | 0xb92b, 183 | 0x152a, 184 | 0x1694, 185 | 0x96aa, 186 | 0x15aa, 187 | 0x12ab5, 188 | 0x974, 189 | 0x14b6, 190 | 0xca57, 191 | 0xa56, 192 | 0x1526, 193 | 0x8e95, 194 | 0xd54, 195 | 0x15aa, 196 | 0x49b5, 197 | 0x96c, 198 | 0xd4ae, 199 | 0x149c, 200 | 0x1a4c, 201 | 0xbd26, 202 | 0x1aa6, 203 | 0xb54, 204 | 0x6d6a, 205 | 0x12da, 206 | 0x1695d, 207 | 0x95a, 208 | 0x149a, 209 | 0xda4b, 210 | 0x1a4a, 211 | 0x1aa4, 212 | 0xbb54, 213 | 0x16b4, 214 | 0xada, 215 | 0x495b, 216 | 0x936, 217 | 0xf497, 218 | 0x1496, 219 | 0x154a, 220 | 0xb6a5, 221 | 0xda4, 222 | 0x15b4, 223 | 0x6ab6, 224 | 0x126e, 225 | 0x1092f, 226 | 0x92e, 227 | 0xc96, 228 | 0xcd4a, 229 | 0x1d4a, 230 | 0xd64, 231 | 0x956c, 232 | 0x155c, 233 | 0x125c, 234 | 0x792e, 235 | 0x192c, 236 | 0xfa95, 237 | 0x1a94, 238 | 0x1b4a, 239 | 0xab55, 240 | 0xad4, 241 | 0x14da, 242 | 0x8a5d, 243 | 0xa5a, 244 | 0x1152b, 245 | 0x152a, 246 | 0x1694, 247 | 0xd6aa, 248 | 0x15aa, 249 | 0xab4, 250 | 0x94ba, 251 | 0x14b6, 252 | 0xa56, 253 | 0x7527, 254 | 0xd26, 255 | 0xee53, 256 | 0xd54, 257 | 0x15aa, 258 | 0xa9b5, 259 | 0x96c, 260 | 0x14ae, 261 | 0x8a4e, 262 | 0x1a4c, 263 | 0x11d26, 264 | 0x1aa4, 265 | 0x1b54, 266 | 0xcd6a, 267 | 0xada, 268 | 0x95c, 269 | 0x949d, 270 | 0x149a, 271 | 0x1a2a, 272 | 0x5b25, 273 | 0x1aa4, 274 | 0xfb52, 275 | 0x16b4, 276 | 0xaba, 277 | 0xa95b, 278 | 0x936, 279 | 0x1496, 280 | 0x9a4b, 281 | 0x154a, 282 | 0x136a5, 283 | 0xda4, 284 | 0x15ac 285 | ]; 286 | 287 | const List SOLAR = [ 288 | 1887, 289 | 0xec04c, 290 | 0xec23f, 291 | 0xec435, 292 | 0xec649, 293 | 0xec83e, 294 | 0xeca51, 295 | 0xecc46, 296 | 0xece3a, 297 | 0xed04d, 298 | 0xed242, 299 | 0xed436, 300 | 0xed64a, 301 | 0xed83f, 302 | 0xeda53, 303 | 0xedc48, 304 | 0xede3d, 305 | 0xee050, 306 | 0xee244, 307 | 0xee439, 308 | 0xee64d, 309 | 0xee842, 310 | 0xeea36, 311 | 0xeec4a, 312 | 0xeee3e, 313 | 0xef052, 314 | 0xef246, 315 | 0xef43a, 316 | 0xef64e, 317 | 0xef843, 318 | 0xefa37, 319 | 0xefc4b, 320 | 0xefe41, 321 | 0xf0054, 322 | 0xf0248, 323 | 0xf043c, 324 | 0xf0650, 325 | 0xf0845, 326 | 0xf0a38, 327 | 0xf0c4d, 328 | 0xf0e42, 329 | 0xf1037, 330 | 0xf124a, 331 | 0xf143e, 332 | 0xf1651, 333 | 0xf1846, 334 | 0xf1a3a, 335 | 0xf1c4e, 336 | 0xf1e44, 337 | 0xf2038, 338 | 0xf224b, 339 | 0xf243f, 340 | 0xf2653, 341 | 0xf2848, 342 | 0xf2a3b, 343 | 0xf2c4f, 344 | 0xf2e45, 345 | 0xf3039, 346 | 0xf324d, 347 | 0xf3442, 348 | 0xf3636, 349 | 0xf384a, 350 | 0xf3a3d, 351 | 0xf3c51, 352 | 0xf3e46, 353 | 0xf403b, 354 | 0xf424e, 355 | 0xf4443, 356 | 0xf4638, 357 | 0xf484c, 358 | 0xf4a3f, 359 | 0xf4c52, 360 | 0xf4e48, 361 | 0xf503c, 362 | 0xf524f, 363 | 0xf5445, 364 | 0xf5639, 365 | 0xf584d, 366 | 0xf5a42, 367 | 0xf5c35, 368 | 0xf5e49, 369 | 0xf603e, 370 | 0xf6251, 371 | 0xf6446, 372 | 0xf663b, 373 | 0xf684f, 374 | 0xf6a43, 375 | 0xf6c37, 376 | 0xf6e4b, 377 | 0xf703f, 378 | 0xf7252, 379 | 0xf7447, 380 | 0xf763c, 381 | 0xf7850, 382 | 0xf7a45, 383 | 0xf7c39, 384 | 0xf7e4d, 385 | 0xf8042, 386 | 0xf8254, 387 | 0xf8449, 388 | 0xf863d, 389 | 0xf8851, 390 | 0xf8a46, 391 | 0xf8c3b, 392 | 0xf8e4f, 393 | 0xf9044, 394 | 0xf9237, 395 | 0xf944a, 396 | 0xf963f, 397 | 0xf9853, 398 | 0xf9a47, 399 | 0xf9c3c, 400 | 0xf9e50, 401 | 0xfa045, 402 | 0xfa238, 403 | 0xfa44c, 404 | 0xfa641, 405 | 0xfa836, 406 | 0xfaa49, 407 | 0xfac3d, 408 | 0xfae52, 409 | 0xfb047, 410 | 0xfb23a, 411 | 0xfb44e, 412 | 0xfb643, 413 | 0xfb837, 414 | 0xfba4a, 415 | 0xfbc3f, 416 | 0xfbe53, 417 | 0xfc048, 418 | 0xfc23c, 419 | 0xfc450, 420 | 0xfc645, 421 | 0xfc839, 422 | 0xfca4c, 423 | 0xfcc41, 424 | 0xfce36, 425 | 0xfd04a, 426 | 0xfd23d, 427 | 0xfd451, 428 | 0xfd646, 429 | 0xfd83a, 430 | 0xfda4d, 431 | 0xfdc43, 432 | 0xfde37, 433 | 0xfe04b, 434 | 0xfe23f, 435 | 0xfe453, 436 | 0xfe648, 437 | 0xfe83c, 438 | 0xfea4f, 439 | 0xfec44, 440 | 0xfee38, 441 | 0xff04c, 442 | 0xff241, 443 | 0xff436, 444 | 0xff64a, 445 | 0xff83e, 446 | 0xffa51, 447 | 0xffc46, 448 | 0xffe3a, 449 | 0x10004e, 450 | 0x100242, 451 | 0x100437, 452 | 0x10064b, 453 | 0x100841, 454 | 0x100a53, 455 | 0x100c48, 456 | 0x100e3c, 457 | 0x10104f, 458 | 0x101244, 459 | 0x101438, 460 | 0x10164c, 461 | 0x101842, 462 | 0x101a35, 463 | 0x101c49, 464 | 0x101e3d, 465 | 0x102051, 466 | 0x102245, 467 | 0x10243a, 468 | 0x10264e, 469 | 0x102843, 470 | 0x102a37, 471 | 0x102c4b, 472 | 0x102e3f, 473 | 0x103053, 474 | 0x103247, 475 | 0x10343b, 476 | 0x10364f, 477 | 0x103845, 478 | 0x103a38, 479 | 0x103c4c, 480 | 0x103e42, 481 | 0x104036, 482 | 0x104249, 483 | 0x10443d, 484 | 0x104651, 485 | 0x104846, 486 | 0x104a3a, 487 | 0x104c4e, 488 | 0x104e43, 489 | 0x105038, 490 | 0x10524a, 491 | 0x10543e, 492 | 0x105652, 493 | 0x105847, 494 | 0x105a3b, 495 | 0x105c4f, 496 | 0x105e45, 497 | 0x106039, 498 | 0x10624c, 499 | 0x106441, 500 | 0x106635, 501 | 0x106849, 502 | 0x106a3d, 503 | 0x106c51, 504 | 0x106e47, 505 | 0x10703c, 506 | 0x10724f, 507 | 0x107444, 508 | 0x107638, 509 | 0x10784c, 510 | 0x107a3f, 511 | 0x107c53, 512 | 0x107e48 513 | ]; 514 | 515 | /** 516 | * 公历节日 517 | */ 518 | const List SOLAR_CALENDAR = [ 519 | "0101元旦", 520 | "0214情人节", 521 | "0308妇女节", 522 | "0312植树节", 523 | "0315消权日", 524 | "0401愚人节", 525 | "0422地球日", 526 | "0501劳动节", 527 | "0504青年节", 528 | "0601儿童节", 529 | "0701建党节", 530 | "0801建军节", 531 | "0910教师节", 532 | "1001国庆节", 533 | "1031万圣节", 534 | "1111光棍节", 535 | "1224平安夜", 536 | "1225圣诞节", 537 | ]; 538 | 539 | /** 540 | * 传统农历节日 541 | */ 542 | const List TRADITION_FESTIVAL_STR = [ 543 | "除夕", 544 | "0101春节", 545 | "0115元宵", 546 | "0505端午", 547 | "0707七夕", 548 | "0815中秋", 549 | "0909重阳", 550 | ]; 551 | 552 | /** 553 | * 特殊节日、母亲节和父亲节,感恩节等 554 | */ 555 | const Map> SPECIAL_FESTIVAL = {}; 556 | 557 | /** 558 | * 特殊节日的数组 559 | */ 560 | const List SPECIAL_FESTIVAL_STR = [ 561 | "母亲节", 562 | "父亲节", 563 | "感恩节", 564 | ]; 565 | 566 | /** 567 | * 用来表示1900年到2099年间农历年份的相关信息,共24位bit的16进制表示,其中: 568 | * 1. 前4位表示该年闰哪个月; 569 | * 2. 5-17位表示农历年份13个月的大小月分布,0表示小,1表示大; 570 | * 3. 最后7位表示农历年首(正月初一)对应的公历日期。 571 | *

572 | * 以2014年的数据0x955ABF为例说明: 573 | * 1001 0101 0101 1010 1011 1111 574 | * 闰九月 农历正月初一对应公历1月31号 575 | */ 576 | const List LUNAR_INFO = [ 577 | 0x84B6BF, 578 | /*1900*/ 579 | 0x04AE53, 580 | 0x0A5748, 581 | 0x5526BD, 582 | 0x0D2650, 583 | 0x0D9544, 584 | 0x46AAB9, 585 | 0x056A4D, 586 | 0x09AD42, 587 | 0x24AEB6, 588 | 0x04AE4A, 589 | /*1901-1910*/ 590 | 0x6A4DBE, 591 | 0x0A4D52, 592 | 0x0D2546, 593 | 0x5D52BA, 594 | 0x0B544E, 595 | 0x0D6A43, 596 | 0x296D37, 597 | 0x095B4B, 598 | 0x749BC1, 599 | 0x049754, 600 | /*1911-1920*/ 601 | 0x0A4B48, 602 | 0x5B25BC, 603 | 0x06A550, 604 | 0x06D445, 605 | 0x4ADAB8, 606 | 0x02B64D, 607 | 0x095742, 608 | 0x2497B7, 609 | 0x04974A, 610 | 0x664B3E, 611 | /*1921-1930*/ 612 | 0x0D4A51, 613 | 0x0EA546, 614 | 0x56D4BA, 615 | 0x05AD4E, 616 | 0x02B644, 617 | 0x393738, 618 | 0x092E4B, 619 | 0x7C96BF, 620 | 0x0C9553, 621 | 0x0D4A48, 622 | /*1931-1940*/ 623 | 0x6DA53B, 624 | 0x0B554F, 625 | 0x056A45, 626 | 0x4AADB9, 627 | 0x025D4D, 628 | 0x092D42, 629 | 0x2C95B6, 630 | 0x0A954A, 631 | 0x7B4ABD, 632 | 0x06CA51, 633 | /*1941-1950*/ 634 | 0x0B5546, 635 | 0x555ABB, 636 | 0x04DA4E, 637 | 0x0A5B43, 638 | 0x352BB8, 639 | 0x052B4C, 640 | 0x8A953F, 641 | 0x0E9552, 642 | 0x06AA48, 643 | 0x6AD53C, 644 | /*1951-1960*/ 645 | 0x0AB54F, 646 | 0x04B645, 647 | 0x4A5739, 648 | 0x0A574D, 649 | 0x052642, 650 | 0x3E9335, 651 | 0x0D9549, 652 | 0x75AABE, 653 | 0x056A51, 654 | 0x096D46, 655 | /*1961-1970*/ 656 | 0x54AEBB, 657 | 0x04AD4F, 658 | 0x0A4D43, 659 | 0x4D26B7, 660 | 0x0D254B, 661 | 0x8D52BF, 662 | 0x0B5452, 663 | 0x0B6A47, 664 | 0x696D3C, 665 | 0x095B50, 666 | /*1971-1980*/ 667 | 0x049B45, 668 | 0x4A4BB9, 669 | 0x0A4B4D, 670 | 0xAB25C2, 671 | 0x06A554, 672 | 0x06D449, 673 | 0x6ADA3D, 674 | 0x0AB651, 675 | 0x095746, 676 | 0x5497BB, 677 | /*1981-1990*/ 678 | 0x04974F, 679 | 0x064B44, 680 | 0x36A537, 681 | 0x0EA54A, 682 | 0x86B2BF, 683 | 0x05AC53, 684 | 0x0AB647, 685 | 0x5936BC, 686 | 0x092E50, 687 | 0x0C9645, 688 | /*1991-2000*/ 689 | 0x4D4AB8, 690 | 0x0D4A4C, 691 | 0x0DA541, 692 | 0x25AAB6, 693 | 0x056A49, 694 | 0x7AADBD, 695 | 0x025D52, 696 | 0x092D47, 697 | 0x5C95BA, 698 | 0x0A954E, 699 | /*2001-2010*/ 700 | 0x0B4A43, 701 | 0x4B5537, 702 | 0x0AD54A, 703 | 0x955ABF, 704 | 0x04BA53, 705 | 0x0A5B48, 706 | 0x652BBC, 707 | 0x052B50, 708 | 0x0A9345, 709 | 0x474AB9, 710 | /*2011-2020*/ 711 | 0x06AA4C, 712 | 0x0AD541, 713 | 0x24DAB6, 714 | 0x04B64A, 715 | 0x6a573D, 716 | 0x0A4E51, 717 | 0x0D2646, 718 | 0x5E933A, 719 | 0x0D534D, 720 | 0x05AA43, 721 | /*2021-2030*/ 722 | 0x36B537, 723 | 0x096D4B, 724 | 0xB4AEBF, 725 | 0x04AD53, 726 | 0x0A4D48, 727 | 0x6D25BC, 728 | 0x0D254F, 729 | 0x0D5244, 730 | 0x5DAA38, 731 | 0x0B5A4C, 732 | /*2031-2040*/ 733 | 0x056D41, 734 | 0x24ADB6, 735 | 0x049B4A, 736 | 0x7A4BBE, 737 | 0x0A4B51, 738 | 0x0AA546, 739 | 0x5B52BA, 740 | 0x06D24E, 741 | 0x0ADA42, 742 | 0x355B37, 743 | /*2041-2050*/ 744 | 0x09374B, 745 | 0x8497C1, 746 | 0x049753, 747 | 0x064B48, 748 | 0x66A53C, 749 | 0x0EA54F, 750 | 0x06AA44, 751 | 0x4AB638, 752 | 0x0AAE4C, 753 | 0x092E42, 754 | /*2051-2060*/ 755 | 0x3C9735, 756 | 0x0C9649, 757 | 0x7D4ABD, 758 | 0x0D4A51, 759 | 0x0DA545, 760 | 0x55AABA, 761 | 0x056A4E, 762 | 0x0A6D43, 763 | 0x452EB7, 764 | 0x052D4B, 765 | /*2061-2070*/ 766 | 0x8A95BF, 767 | 0x0A9553, 768 | 0x0B4A47, 769 | 0x6B553B, 770 | 0x0AD54F, 771 | 0x055A45, 772 | 0x4A5D38, 773 | 0x0A5B4C, 774 | 0x052B42, 775 | 0x3A93B6, 776 | /*2071-2080*/ 777 | 0x069349, 778 | 0x7729BD, 779 | 0x06AA51, 780 | 0x0AD546, 781 | 0x54DABA, 782 | 0x04B64E, 783 | 0x0A5743, 784 | 0x452738, 785 | 0x0D264A, 786 | 0x8E933E, 787 | /*2081-2090*/ 788 | 0x0D5252, 789 | 0x0DAA47, 790 | 0x66B53B, 791 | 0x056D4F, 792 | 0x04AE45, 793 | 0x4A4EB9, 794 | 0x0A4D4C, 795 | 0x0D1541, 796 | 0x2D92B5 /*2091-2099*/ 797 | ]; 798 | 799 | /** 800 | * 一周七天 801 | */ 802 | const List WEEK_LIST = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"]; 803 | 804 | /** 805 | * 农历的月份 806 | */ 807 | const List LUNAR_MONTH_TEXT = [ 808 | "春节", 809 | "二月", 810 | "三月", 811 | "四月", 812 | "五月", 813 | "六月", 814 | "七月", 815 | "八月", 816 | "九月", 817 | "十月", 818 | "冬月", 819 | "腊月", 820 | ]; 821 | 822 | /** 823 | * 农历的日期 824 | */ 825 | const List LUNAR_DAY_TEXT = [ 826 | "初一", 827 | "初二", 828 | "初三", 829 | "初四", 830 | "初五", 831 | "初六", 832 | "初七", 833 | "初八", 834 | "初九", 835 | "初十", 836 | "十一", 837 | "十二", 838 | "十三", 839 | "十四", 840 | "十五", 841 | "十六", 842 | "十七", 843 | "十八", 844 | "十九", 845 | "二十", 846 | "廿一", 847 | "廿二", 848 | "廿三", 849 | "廿四", 850 | "廿五", 851 | "廿六", 852 | "廿七", 853 | "廿八", 854 | "廿九", 855 | "三十" 856 | ]; 857 | 858 | /** 859 | * 24节气 860 | */ 861 | const List SOLAR_TERMS = [ 862 | "春分", 863 | "清明", 864 | "谷雨", 865 | "立夏", 866 | "小满", 867 | "芒种", 868 | "夏至", 869 | "小暑", 870 | "大暑", 871 | "立秋", 872 | "处暑", 873 | "白露", 874 | "秋分", 875 | "寒露", 876 | "霜降", 877 | "立冬", 878 | "小雪", 879 | "大雪", 880 | "冬至", 881 | "小寒", 882 | "大寒", 883 | "立春", 884 | "雨水", 885 | "惊蛰", 886 | ]; 887 | 888 | //农历扩展 889 | extension LunerDateTimeExternal on DateTime { 890 | int get lunarDaysInMonth { 891 | if ((LUNAR_INFO[this.year - 1900] & (0x100000 >> this.month)) == 0) 892 | return 29; 893 | else 894 | return 30; 895 | } 896 | 897 | int get lunarYear => _solarToLunar()[0]; 898 | 899 | int get lunarMonth => _solarToLunar()[1]; 900 | 901 | int get lunarDay => _solarToLunar()[2]; 902 | 903 | int? get leapMonth => _solarToLunar()[3] == 1 ? lunarMonth : null; 904 | 905 | // 公历节日 906 | String get gregorianFestival { 907 | String text = string; 908 | String solar = ""; 909 | for (String aMSolarCalendar in SOLAR_CALENDAR) { 910 | if (aMSolarCalendar.contains(text)) { 911 | solar = aMSolarCalendar.replaceAll(text, ""); 912 | break; 913 | } 914 | } 915 | return solar; 916 | } 917 | 918 | //农历传统节日 919 | String get traditionFestival { 920 | if (lunarMonth == 12) { 921 | int count = lunarDaysInMonth; 922 | if (lunarDay == count) { 923 | return TRADITION_FESTIVAL_STR[0]; //除夕 924 | } 925 | } 926 | String text = lunarString; 927 | String festivalStr = ""; 928 | for (String festival in TRADITION_FESTIVAL_STR) { 929 | if (festival.contains(text)) { 930 | festivalStr = festival.replaceAll(text, ""); 931 | break; 932 | } 933 | } 934 | return festivalStr; 935 | } 936 | 937 | List get specialFestivals { 938 | List festivals = List.generate(3, (index) => ''); 939 | DateTime dateTime = new DateTime(year, 5, 1); 940 | //母亲节 941 | int week = (dateTime.weekday + 1) % 8; 942 | int startDiff = 7 - week + 1; 943 | if (startDiff == 7) { 944 | festivals[0] = 945 | _dateToString(year, 5, startDiff + 1) + SPECIAL_FESTIVAL_STR[0]; 946 | } else { 947 | festivals[0] = 948 | _dateToString(year, 5, startDiff + 7 + 1) + SPECIAL_FESTIVAL_STR[0]; 949 | } 950 | 951 | //父亲节 952 | dateTime = new DateTime(year, 6, 1); 953 | 954 | week = (dateTime.weekday + 1) % 8; 955 | startDiff = 7 - week + 1; 956 | if (startDiff == 7) { 957 | festivals[1] = 958 | _dateToString(year, 6, startDiff + 7 + 1) + SPECIAL_FESTIVAL_STR[1]; 959 | } else { 960 | festivals[1] = _dateToString(year, 6, startDiff + 7 + 7 + 1) + 961 | SPECIAL_FESTIVAL_STR[1]; 962 | } 963 | 964 | //感恩节 965 | dateTime = new DateTime(year, 11, 1); 966 | week = (dateTime.weekday + 1) % 8; 967 | 968 | startDiff = 7 - week + 1; 969 | if (startDiff <= 2) { 970 | festivals[2] = 971 | _dateToString(year, 11, startDiff + 21 + 5) + SPECIAL_FESTIVAL_STR[2]; 972 | } else { 973 | festivals[2] = 974 | _dateToString(year, 11, startDiff + 14 + 5) + SPECIAL_FESTIVAL_STR[2]; 975 | } 976 | return festivals; 977 | } 978 | 979 | String _dateToString(int year, int month, int day) { 980 | String _getString(int month, int day) { 981 | return (month >= 10 ? month.toString() : "0$month") + 982 | (day >= 10 ? day.toString() : "0$day"); 983 | } 984 | 985 | return "$year" + _getString(month, day); 986 | } 987 | 988 | String get specialFestival { 989 | List specialFestival = specialFestivals; 990 | String text = "$year" + string; 991 | String solar = ""; 992 | for (String special in specialFestival) { 993 | if (special.contains(text)) { 994 | solar = special.replaceAll(text, ""); 995 | break; 996 | } 997 | } 998 | return solar; 999 | } 1000 | 1001 | String get numToChinese { 1002 | if (lunarDay == 1) { 1003 | return numToChineseMonth; 1004 | } 1005 | return LUNAR_DAY_TEXT[lunarDay - 1]; 1006 | } 1007 | 1008 | String get numToChineseMonth { 1009 | if (leapMonth == 1) { 1010 | return "闰" + LUNAR_MONTH_TEXT[lunarMonth - 1]; 1011 | } 1012 | return LUNAR_MONTH_TEXT[lunarMonth - 1]; 1013 | } 1014 | 1015 | String get string { 1016 | return _getString(year, month, day); 1017 | } 1018 | 1019 | String get lunarString { 1020 | return _getString(lunarYear, lunarMonth, lunarDay); 1021 | } 1022 | 1023 | String _getString(int year, int month, int day) { 1024 | return (month >= 10 ? month.toString() : "0$month") + 1025 | (day >= 10 ? day.toString() : "0$day"); 1026 | } 1027 | 1028 | List _solarToLunar() { 1029 | List lunarInt = List.generate(4, (index) => 0); 1030 | int index = year - SOLAR[0]; 1031 | int data = (year << 9) | (month << 5) | (day); 1032 | int solar11; 1033 | if (SOLAR[index] > data) { 1034 | index--; 1035 | } 1036 | solar11 = SOLAR[index]; 1037 | int y = _getBitInt(solar11, 12, 9); 1038 | int m = _getBitInt(solar11, 4, 5); 1039 | int d = _getBitInt(solar11, 5, 0); 1040 | int offset = _solarToInt(year, month, day) - _solarToInt(y, m, d); 1041 | 1042 | int days = LUNAR_MONTH_DAYS[index]; 1043 | int leap = _getBitInt(days, 4, 13); 1044 | 1045 | int lunarY = index + SOLAR[0]; 1046 | int lunarM = 1; 1047 | int lunarD; 1048 | offset += 1; 1049 | 1050 | for (int i = 0; i < 13; i++) { 1051 | int dm = _getBitInt(days, 1, 12 - i) == 1 ? 30 : 29; 1052 | if (offset > dm) { 1053 | lunarM++; 1054 | offset -= dm; 1055 | } else { 1056 | break; 1057 | } 1058 | } 1059 | 1060 | lunarD = offset; 1061 | lunarInt[0] = lunarY; 1062 | lunarInt[1] = lunarM; 1063 | lunarInt[3] = 0; 1064 | 1065 | if (leap != 0 && lunarM > leap) { 1066 | lunarInt[1] = lunarM - 1; 1067 | if (lunarM == leap + 1) { 1068 | lunarInt[3] = 1; 1069 | } 1070 | } 1071 | lunarInt[2] = lunarD; 1072 | return lunarInt; 1073 | } 1074 | 1075 | int _getBitInt(int data, int length, int shift) { 1076 | return (data & (((1 << length) - 1) << shift)) >> shift; 1077 | } 1078 | 1079 | int _solarToInt(int y, int m, int d) { 1080 | m = (m + 9) % 12; 1081 | y = y - m ~/ 10; 1082 | return (365 * y + 1083 | y ~/ 4 - 1084 | y ~/ 100 + 1085 | y ~/ 400 + 1086 | (m * 306 + 5) ~/ 10 + 1087 | (d - 1)); 1088 | } 1089 | } 1090 | -------------------------------------------------------------------------------- /r_calendar/lib/src/r_calendar_utils.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The rhyme_lph Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | import 'package:flutter/material.dart'; 5 | 6 | ///一个月有多少天 7 | const List _daysInMonth = [ 8 | 31, 9 | -1, 10 | 31, 11 | 30, 12 | 31, 13 | 30, 14 | 31, 15 | 31, 16 | 30, 17 | 31, 18 | 30, 19 | 31 20 | ]; 21 | 22 | class RCalendarUtils { 23 | /// 获取一个月有多少天 24 | static int getDaysInMonth(int year, int month) { 25 | if (month == DateTime.february) { 26 | final bool isLeapYear = 27 | (year % 4 == 0) && (year % 100 != 0) || (year % 400 == 0); 28 | if (isLeapYear) return 29; 29 | return 28; 30 | } 31 | return _daysInMonth[month - 1]; 32 | } 33 | 34 | /// 获取第一天的偏移量,星期一 35 | static int computeFirstDayOffset( 36 | int year, int month, MaterialLocalizations? localizations) { 37 | // 0-based day of week, with 0 representing Monday. 38 | final int weekdayFromMonday = DateTime(year, month).weekday - 1; 39 | // 0-based day of week, with 0 representing Sunday. 40 | final int firstDayOfWeekFromSunday = 41 | localizations?.firstDayOfWeekIndex ?? 0; 42 | // firstDayOfWeekFromSunday recomputed to be Monday-based 43 | final int firstDayOfWeekFromMonday = (firstDayOfWeekFromSunday - 1) % 7; 44 | // Number of days between the first day of week appearing on the calendar, 45 | // and the day corresponding to the 1-st of the month. 46 | return (weekdayFromMonday - firstDayOfWeekFromMonday) % 7; 47 | } 48 | 49 | /// 月份添加和减少 50 | static DateTime addMonthsToMonthDate(DateTime monthDate, int monthsToAdd) { 51 | return DateTime( 52 | monthDate.year + monthsToAdd ~/ 12, monthDate.month + monthsToAdd % 12); 53 | } 54 | 55 | /// 星期添加和减少 56 | static DateTime addWeeksToWeeksDate( 57 | DateTime weekDate, int weeksToAdd, MaterialLocalizations? localizations) { 58 | final firstDayOffset = 59 | computeFirstDayOffset(weekDate.year, weekDate.month, localizations); 60 | DateTime weekFirstDate = weekDate.subtract(Duration(days: firstDayOffset)); 61 | return weekFirstDate.add(Duration(days: weeksToAdd * DateTime.daysPerWeek)); 62 | } 63 | 64 | /// 月份总页数 65 | static int monthDelta(DateTime startDate, DateTime endDate) { 66 | return (endDate.year - startDate.year) * 12 + 67 | endDate.month - 68 | startDate.month; 69 | } 70 | 71 | /// 星期总页数 72 | static int weekDelta(DateTime startDate, DateTime endDate, 73 | MaterialLocalizations? localizations) { 74 | final int firstDayOffset = 75 | computeFirstDayOffset(startDate.year, startDate.month, localizations); 76 | Duration diff = DateTime(endDate.year, endDate.month, endDate.day) 77 | .difference(DateTime(startDate.year, startDate.month, startDate.day)); 78 | int days = diff.inDays + firstDayOffset + 1; 79 | return (days / 7).ceil() - 1; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /r_calendar/lib/src/r_calendar_widget_item.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The rhyme_lph Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter/rendering.dart'; 6 | import 'package:r_calendar/r_calendar.dart'; 7 | import 'package:r_calendar/src/r_calendar_utils.dart'; 8 | 9 | class RCalendarMonthItem extends StatelessWidget { 10 | final DateTime? monthDate; 11 | 12 | const RCalendarMonthItem({Key? key, this.monthDate}) : super(key: key); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | RCalendarMarker data = RCalendarMarker.of(context)!; 17 | RCalendarController? controller = data.notifier; 18 | //获取星期的第一天 19 | final MaterialLocalizations localizations = 20 | MaterialLocalizations.of(context); 21 | final int year = monthDate!.year; 22 | final int month = monthDate!.month; 23 | final int dayInMonth = RCalendarUtils.getDaysInMonth(year, month); 24 | //第一天的偏移 25 | final int firstDayOffset = 26 | RCalendarUtils.computeFirstDayOffset(year, month, localizations); 27 | 28 | final List labels = []; 29 | 30 | for (int i = 0; true; i += 1) { 31 | final int day = i - firstDayOffset + 1; 32 | 33 | if (day > dayInMonth && i % 7 == 0) break; // 大于当月最大日期和可以除以7 34 | if (day < 1) { 35 | //小于当月的日期 36 | List types = [RCalendarType.differentMonth]; 37 | final DateTime dayToBuild = 38 | DateTime(year, month, 1).subtract(Duration(days: (day * -1) + 1)); 39 | 40 | final bool disabled = dayToBuild.isAfter(controller!.lastDate!) || 41 | dayToBuild.isBefore(controller.firstDate) || 42 | (data.customWidget != null && 43 | !data.customWidget!.isUnable(context, dayToBuild, false)); 44 | if (disabled) { 45 | types.add(RCalendarType.disable); 46 | } 47 | 48 | if (!disabled) { 49 | labels.add(GestureDetector( 50 | behavior: HitTestBehavior.opaque, 51 | onTap: () async { 52 | if (await data.customWidget! 53 | .clickInterceptor(context, dayToBuild)) { 54 | return; 55 | } 56 | data.onChanged(dayToBuild); 57 | }, 58 | child: data.customWidget!.buildDateTime(context, dayToBuild, types), 59 | )); 60 | } else { 61 | labels 62 | .add(data.customWidget!.buildDateTime(context, dayToBuild, types)); 63 | } 64 | } else if (day > dayInMonth) { 65 | //大于当月的日期 66 | List types = [RCalendarType.differentMonth]; 67 | final DateTime dayToBuild = DateTime(year, month, dayInMonth) 68 | .add(Duration(days: day - dayInMonth)); 69 | 70 | final bool disabled = dayToBuild.isAfter(controller!.lastDate!) || 71 | dayToBuild.isBefore(controller.firstDate) || 72 | (data.customWidget != null && 73 | !data.customWidget!.isUnable(context, dayToBuild, false)); 74 | if (disabled) { 75 | types.add(RCalendarType.disable); 76 | } 77 | 78 | if (!disabled) { 79 | labels.add(GestureDetector( 80 | behavior: HitTestBehavior.opaque, 81 | onTap: () async { 82 | if (await data.customWidget! 83 | .clickInterceptor(context, dayToBuild)) { 84 | return; 85 | } 86 | data.onChanged(dayToBuild); 87 | }, 88 | child: data.customWidget!.buildDateTime(context, dayToBuild, types), 89 | )); 90 | } else { 91 | labels 92 | .add(data.customWidget!.buildDateTime(context, dayToBuild, types)); 93 | } 94 | } else { 95 | List types = [RCalendarType.disable]; 96 | final DateTime dayToBuild = DateTime(year, month, day); 97 | final bool disabled = dayToBuild.isAfter(controller!.lastDate!) || 98 | dayToBuild.isBefore(controller.firstDate) || 99 | (data.customWidget != null && 100 | !data.customWidget!.isUnable(context, dayToBuild, true)); 101 | if (disabled) { 102 | types.add(RCalendarType.disable); 103 | } 104 | bool isSelectedDay = false; 105 | try { 106 | isSelectedDay = controller.selectedDates 107 | .where((selectedDate) => 108 | selectedDate!.year == year && 109 | selectedDate.month == month && 110 | selectedDate.day == day) 111 | .length != 112 | 0; 113 | } catch (_) {} 114 | if (isSelectedDay) { 115 | types.add(RCalendarType.selected); 116 | } 117 | final bool isToday = data.toDayDate!.year == year && 118 | data.toDayDate!.month == month && 119 | data.toDayDate!.day == day; 120 | if (isToday) { 121 | types.add(RCalendarType.today); 122 | } 123 | if (!disabled && !isSelectedDay && !isToday) { 124 | types.add(RCalendarType.normal); 125 | } 126 | 127 | if (!disabled) { 128 | labels.add(GestureDetector( 129 | behavior: HitTestBehavior.opaque, 130 | onTap: () async { 131 | if (await data.customWidget! 132 | .clickInterceptor(context, dayToBuild)) { 133 | return; 134 | } 135 | data.onChanged(dayToBuild); 136 | }, 137 | child: Semantics( 138 | label: 139 | '${localizations.formatDecimal(day)}, ${localizations.formatFullDate(dayToBuild)}', 140 | selected: isSelectedDay, 141 | excludeSemantics: true, 142 | child: 143 | data.customWidget!.buildDateTime(context, dayToBuild, types), 144 | ), 145 | )); 146 | } else { 147 | labels.add(ExcludeSemantics( 148 | child: data.customWidget!.buildDateTime(context, dayToBuild, types), 149 | )); 150 | } 151 | } 152 | } 153 | 154 | return GridView.custom( 155 | key: ValueKey(month), 156 | physics: NeverScrollableScrollPhysics(), 157 | padding: EdgeInsets.zero, 158 | gridDelegate: _DayPickerGridDelegate(data.customWidget?.childHeight ?? 42), 159 | childrenDelegate: 160 | SliverChildListDelegate(labels, addRepaintBoundaries: false), 161 | ); 162 | } 163 | } 164 | 165 | class RCalendarWeekItem extends StatelessWidget { 166 | final DateTime? weekDate; 167 | 168 | const RCalendarWeekItem({Key? key, this.weekDate}) : super(key: key); 169 | 170 | @override 171 | Widget build(BuildContext context) { 172 | RCalendarMarker data = RCalendarMarker.of(context)!; 173 | RCalendarController? controller = data.notifier; 174 | 175 | final List labels = []; 176 | for (int i = 0; i < 7; i += 1) { 177 | final DateTime dayToBuild = 178 | DateTime(weekDate!.year, weekDate!.month, weekDate!.day + i); 179 | final int year = dayToBuild.year; 180 | final int month = dayToBuild.month; 181 | final int day = dayToBuild.day; 182 | List types = []; 183 | final bool disabled = dayToBuild.isAfter(controller!.lastDate!) || 184 | dayToBuild.isBefore(controller.firstDate) || 185 | (data.customWidget != null && 186 | !data.customWidget!.isUnable(context, dayToBuild, true)); 187 | if (disabled) { 188 | types.add(RCalendarType.disable); 189 | } 190 | bool isSelectedDay = false; 191 | try { 192 | isSelectedDay = controller.selectedDates 193 | .where((selectedDate) => 194 | selectedDate!.year == year && 195 | selectedDate.month == month && 196 | selectedDate.day == day) 197 | .length != 198 | 0; 199 | } catch (_) {} 200 | if (isSelectedDay) { 201 | types.add(RCalendarType.selected); 202 | } 203 | final bool isToday = data.toDayDate!.year == year && 204 | data.toDayDate!.month == month && 205 | data.toDayDate!.day == day; 206 | if (isToday) { 207 | types.add(RCalendarType.today); 208 | } 209 | if (!disabled && !isSelectedDay && !isToday) { 210 | types.add(RCalendarType.normal); 211 | } 212 | if (!disabled) { 213 | labels.add(GestureDetector( 214 | behavior: HitTestBehavior.opaque, 215 | onTap: () async { 216 | if (await data.customWidget!.clickInterceptor(context, dayToBuild)) { 217 | return; 218 | } 219 | data.onChanged(dayToBuild); 220 | }, 221 | child: data.customWidget!.buildDateTime(context, dayToBuild, types), 222 | )); 223 | } else { 224 | labels.add(data.customWidget!.buildDateTime(context, dayToBuild, types)); 225 | } 226 | } 227 | return GridView.custom( 228 | key: ValueKey(weekDate!.toIso8601String()), 229 | physics: NeverScrollableScrollPhysics(), 230 | padding: EdgeInsets.zero, 231 | gridDelegate: _DayPickerGridDelegate(data.customWidget?.childHeight ?? 42), 232 | childrenDelegate: 233 | SliverChildListDelegate(labels, addRepaintBoundaries: true), 234 | ); 235 | } 236 | } 237 | 238 | class _DayPickerGridDelegate extends SliverGridDelegate { 239 | final double childHeight; 240 | 241 | const _DayPickerGridDelegate(this.childHeight); 242 | 243 | @override 244 | SliverGridLayout getLayout(SliverConstraints constraints) { 245 | const int columnCount = DateTime.daysPerWeek; 246 | final double tileWidth = constraints.crossAxisExtent / columnCount; 247 | return SliverGridRegularTileLayout( 248 | crossAxisCount: columnCount, 249 | mainAxisStride: childHeight, 250 | crossAxisStride: tileWidth, 251 | childMainAxisExtent: childHeight, 252 | childCrossAxisExtent: tileWidth, 253 | reverseCrossAxis: axisDirectionIsReversed(constraints.crossAxisDirection), 254 | ); 255 | } 256 | 257 | @override 258 | bool shouldRelayout(_DayPickerGridDelegate oldDelegate) => false; 259 | } 260 | -------------------------------------------------------------------------------- /r_calendar/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" 9 | url: "https://pub.dev" 10 | source: hosted 11 | version: "2.11.0" 12 | boolean_selector: 13 | dependency: transitive 14 | description: 15 | name: boolean_selector 16 | sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" 17 | url: "https://pub.dev" 18 | source: hosted 19 | version: "2.1.1" 20 | characters: 21 | dependency: transitive 22 | description: 23 | name: characters 24 | sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" 25 | url: "https://pub.dev" 26 | source: hosted 27 | version: "1.3.0" 28 | clock: 29 | dependency: transitive 30 | description: 31 | name: clock 32 | sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf 33 | url: "https://pub.dev" 34 | source: hosted 35 | version: "1.1.1" 36 | collection: 37 | dependency: transitive 38 | description: 39 | name: collection 40 | sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a 41 | url: "https://pub.dev" 42 | source: hosted 43 | version: "1.18.0" 44 | fake_async: 45 | dependency: transitive 46 | description: 47 | name: fake_async 48 | sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" 49 | url: "https://pub.dev" 50 | source: hosted 51 | version: "1.3.1" 52 | flutter: 53 | dependency: "direct main" 54 | description: flutter 55 | source: sdk 56 | version: "0.0.0" 57 | flutter_test: 58 | dependency: "direct dev" 59 | description: flutter 60 | source: sdk 61 | version: "0.0.0" 62 | intl: 63 | dependency: "direct main" 64 | description: 65 | name: intl 66 | sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" 67 | url: "https://pub.dev" 68 | source: hosted 69 | version: "0.18.1" 70 | leak_tracker: 71 | dependency: transitive 72 | description: 73 | name: leak_tracker 74 | sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" 75 | url: "https://pub.dev" 76 | source: hosted 77 | version: "10.0.0" 78 | leak_tracker_flutter_testing: 79 | dependency: transitive 80 | description: 81 | name: leak_tracker_flutter_testing 82 | sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 83 | url: "https://pub.dev" 84 | source: hosted 85 | version: "2.0.1" 86 | leak_tracker_testing: 87 | dependency: transitive 88 | description: 89 | name: leak_tracker_testing 90 | sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 91 | url: "https://pub.dev" 92 | source: hosted 93 | version: "2.0.1" 94 | matcher: 95 | dependency: transitive 96 | description: 97 | name: matcher 98 | sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb 99 | url: "https://pub.dev" 100 | source: hosted 101 | version: "0.12.16+1" 102 | material_color_utilities: 103 | dependency: transitive 104 | description: 105 | name: material_color_utilities 106 | sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" 107 | url: "https://pub.dev" 108 | source: hosted 109 | version: "0.8.0" 110 | meta: 111 | dependency: transitive 112 | description: 113 | name: meta 114 | sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 115 | url: "https://pub.dev" 116 | source: hosted 117 | version: "1.11.0" 118 | path: 119 | dependency: transitive 120 | description: 121 | name: path 122 | sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" 123 | url: "https://pub.dev" 124 | source: hosted 125 | version: "1.9.0" 126 | sky_engine: 127 | dependency: transitive 128 | description: flutter 129 | source: sdk 130 | version: "0.0.99" 131 | source_span: 132 | dependency: transitive 133 | description: 134 | name: source_span 135 | sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" 136 | url: "https://pub.dev" 137 | source: hosted 138 | version: "1.10.0" 139 | stack_trace: 140 | dependency: transitive 141 | description: 142 | name: stack_trace 143 | sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" 144 | url: "https://pub.dev" 145 | source: hosted 146 | version: "1.11.1" 147 | stream_channel: 148 | dependency: transitive 149 | description: 150 | name: stream_channel 151 | sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 152 | url: "https://pub.dev" 153 | source: hosted 154 | version: "2.1.2" 155 | string_scanner: 156 | dependency: transitive 157 | description: 158 | name: string_scanner 159 | sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" 160 | url: "https://pub.dev" 161 | source: hosted 162 | version: "1.2.0" 163 | term_glyph: 164 | dependency: transitive 165 | description: 166 | name: term_glyph 167 | sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 168 | url: "https://pub.dev" 169 | source: hosted 170 | version: "1.2.1" 171 | test_api: 172 | dependency: transitive 173 | description: 174 | name: test_api 175 | sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" 176 | url: "https://pub.dev" 177 | source: hosted 178 | version: "0.6.1" 179 | vector_math: 180 | dependency: transitive 181 | description: 182 | name: vector_math 183 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" 184 | url: "https://pub.dev" 185 | source: hosted 186 | version: "2.1.4" 187 | vm_service: 188 | dependency: transitive 189 | description: 190 | name: vm_service 191 | sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 192 | url: "https://pub.dev" 193 | source: hosted 194 | version: "13.0.0" 195 | sdks: 196 | dart: ">=3.2.3 <4.0.0" 197 | -------------------------------------------------------------------------------- /r_calendar/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: r_calendar 2 | description: A new Flutter package about calendar,you can use this design the calendar and support single or multiple selected. 3 | version: 0.1.12 4 | homepage: https://github.com/rhymelph/r_calendar 5 | 6 | environment: 7 | sdk: '>=3.2.3 <4.0.0' 8 | 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | intl: ^0.18.1 13 | dev_dependencies: 14 | flutter_test: 15 | sdk: flutter 16 | 17 | # For information on the generic Dart part of this file, see the 18 | # following page: https://dart.dev/tools/pub/pubspec 19 | 20 | # The following section is specific to Flutter. 21 | flutter: 22 | 23 | # To add assets to your package, add an assets section, like this: 24 | # assets: 25 | # - images/a_dot_burr.jpeg 26 | # - images/a_dot_ham.jpeg 27 | # 28 | # For details regarding assets in packages, see 29 | # https://flutter.dev/assets-and-images/#from-packages 30 | # 31 | # An image asset can refer to one or more resolution-specific "variants", see 32 | # https://flutter.dev/assets-and-images/#resolution-aware. 33 | 34 | # To add custom fonts to your package, add a fonts section here, 35 | # in this "flutter" section. Each entry in this list should have a 36 | # "family" key with the font family name, and a "fonts" key with a 37 | # list giving the asset and other descriptors for the font. For 38 | # example: 39 | # fonts: 40 | # - family: Schyler 41 | # fonts: 42 | # - asset: fonts/Schyler-Regular.ttf 43 | # - asset: fonts/Schyler-Italic.ttf 44 | # style: italic 45 | # - family: Trajan Pro 46 | # fonts: 47 | # - asset: fonts/TrajanPro.ttf 48 | # - asset: fonts/TrajanPro_Bold.ttf 49 | # weight: 700 50 | # 51 | # For details regarding fonts in packages, see 52 | # https://flutter.dev/custom-fonts/#from-packages 53 | -------------------------------------------------------------------------------- /r_calendar/test/r_calendar_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | 3 | void main() { 4 | test('adds one to input values', () { 5 | // final calculator = Calculator(); 6 | // expect(calculator.addOne(2), 3); 7 | // expect(calculator.addOne(-7), -6); 8 | // expect(calculator.addOne(0), 1); 9 | // expect(() => calculator.addOne(null), throwsNoSuchMethodError); 10 | print(DateTime.now().weekday); 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /screen/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhymelph/r_calendar/a2a33d772336025552a6205252049aeec71cb94d/screen/.DS_Store -------------------------------------------------------------------------------- /screen/s1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhymelph/r_calendar/a2a33d772336025552a6205252049aeec71cb94d/screen/s1.png -------------------------------------------------------------------------------- /screen/s2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhymelph/r_calendar/a2a33d772336025552a6205252049aeec71cb94d/screen/s2.png -------------------------------------------------------------------------------- /screen/s3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhymelph/r_calendar/a2a33d772336025552a6205252049aeec71cb94d/screen/s3.png -------------------------------------------------------------------------------- /screen/s4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rhymelph/r_calendar/a2a33d772336025552a6205252049aeec71cb94d/screen/s4.png --------------------------------------------------------------------------------