├── .gitignore ├── .metadata ├── API.md ├── API_en.md ├── CHANGELOG.md ├── LICENSE ├── README.md ├── README_en.md ├── example ├── .idea │ ├── codeStyles │ │ └── Project.xml │ ├── example.iml │ ├── libraries │ │ ├── Dart_Packages.xml │ │ ├── Dart_SDK.xml │ │ └── Flutter_Plugins.xml │ ├── modules.xml │ └── workspace.xml ├── README.md ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ │ └── com │ │ │ │ │ └── example │ │ │ │ │ └── example │ │ │ │ │ └── MainActivity.java │ │ │ └── res │ │ │ │ ├── drawable │ │ │ │ └── launch_background.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ └── values │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── example_android.iml │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── example.iml ├── example │ └── example.iml ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ └── WorkspaceSettings.xcsettings │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── Runner-Bridging-Header.h ├── lib │ └── main.dart ├── pubspec.lock ├── pubspec.yaml └── test │ └── widget_test.dart ├── flutter_custom_calendar.iml ├── fluttercustomcalendar.iml ├── img.gif ├── lib ├── cache_data.dart ├── calendar_provider.dart ├── configuration.dart ├── constants │ └── constants.dart ├── controller.dart ├── flutter_custom_calendar.dart ├── model │ └── date_model.dart ├── style │ └── style.dart ├── utils │ ├── LogUtil.dart │ ├── date_util.dart │ ├── lunar_util.dart │ ├── math_util.dart │ └── solar_term_util.dart └── widget │ ├── base_day_view.dart │ ├── base_week_bar.dart │ ├── calendar_view.dart │ ├── default_combine_day_view.dart │ ├── default_custom_day_view.dart │ ├── default_week_bar.dart │ ├── month_view.dart │ ├── month_view_pager.dart │ ├── only_one_pointer_widget.dart │ ├── week_view.dart │ └── week_view_pager.dart ├── pubspec.lock ├── pubspec.yaml └── test └── fluttercustomcalendar_test.dart /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea/ 3 | .dart_tool/ 4 | 5 | .packages 6 | .pub/ 7 | 8 | build/ 9 | ios/.generated/ 10 | ios/Flutter/Generated.xcconfig 11 | ios/Runner/GeneratedPluginRegistrant.* 12 | -------------------------------------------------------------------------------- /.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: d42db56b52657d7557664393ac1fe9a734a6b6e7 8 | channel: master 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [1.0.4+0.4] 2 | - 新增三个模式相互且含数据不影响 3 | 4 | ## [1.0.1] 5 | - 新增多选范围功能 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2019, LXD312569496 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > The code owner of the repo has not been actively building Flutter projects, thus the repo has been archived. 2 | > 该项目的作者已经长时间未开发 Flutter 项目,故该项目已被归档。 3 | 4 | ## flutter_custom_calendar 5 | > 本插件是基于[flutter_custom_calendar](https://github.com/fluttercandies/flutter_custom_calendar)做了稍微的修改进行上传的。 6 | 7 | 具体使用方法见[flutter_custom_calendar](https://github.com/ifgyong/flutter_custom_calendar) 8 | 9 | 新增一个选择`mode` 10 | 11 | 支持选择开始和结束,选择范围内的日期,使用方法 12 | 13 | ``` 14 | controller = new CalendarController( 15 | minYear: 2019, 16 | minYearMonth: 1, 17 | maxYear: 2021, 18 | maxYearMonth: 12, 19 | showMode: CalendarConstants.MODE_SHOW_MONTH_AND_WEEK, 20 | selectedDateTimeList: _selectedDate, 21 | selectMode: CalendarSelectedMode.mutltiStartToEndSelect) 22 | ..addOnCalendarSelectListener((dateModel) { 23 | _selectedModels.add(dateModel); 24 | }) 25 | ..addOnCalendarUnSelectListener((dateModel) { 26 | if (_selectedModels.contains(dateModel)) { 27 | _selectedModels.remove(dateModel); 28 | } 29 | }); 30 | 31 | ``` 32 | `CalendarSelectedMode.mutltiStartToEndSelect`这个选择模式会选择开始和结束中间的 默认选择的。 33 | 34 | 35 | ### 安装和使用 36 | 37 | Use this package as a library 38 | 1. Depend on it 39 | Add this to your package's pubspec.yaml file: 40 | 41 | ``` 42 | dependencies: 43 | flutter_custom_calendar: ^1.0.4+0.5 44 | ``` 45 | 46 | 2. Install it 47 | You can install packages from the command line: 48 | 49 | with Flutter: 50 | 51 | ``` 52 | $ flutter pub get 53 | ``` 54 | 55 | Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more. 56 | 57 | 3. Import it 58 | Now in your Dart code, you can use: 59 | 60 | ``` 61 | import 'package:flutter_custom_calendar/flutter_custom_calendar.dart'; 62 | ``` 63 | ### 监听月视图和周视图状态 64 | 65 | ```dart WidgetsBinding.instance.addPostFrameCallback((timeStamp) { 66 | controller.addExpandChangeListener((value) { 67 | /// 添加改变 月视图和 周视图的监听 68 | _isMonthSelected = value; 69 | setState(() {}); 70 | }); 71 | }); 72 | ``` 73 | ### 变更月视图和周视图 74 | > 前提条件是`showModel`是`CalendarConstants.MODE_SHOW_MONTH_AND_WEEK`或者`CalendarConstants.MODE_SHOW_WEEK_AND_MONTH`. 75 | 76 | #### 变更到周视图 77 | ```dart 78 | setState(() { 79 | controller.weekAndMonthViewChange(CalendarConstants.MODE_SHOW_ONLY_WEEK); 80 | }); 81 | ``` 82 | 83 | #### 变更到月视图 84 | ```dart 85 | setState(() {controller.weekAndMonthViewChange(CalendarConstants.MODE_SHOW_ONLY_MONTH); 86 | }); 87 | ``` 88 | 89 | ### 动画演示 90 | ![](img.gif) 91 | ### [查看API](https://github.com/ifgyong/flutter_custom_calendar/blob/master/API.md) 92 | 93 | ### [查看一个例子 如何使用](https://github.com/ifgyong/flutter_custom_calendar/blob/master/example/lib/main.dart) 94 | -------------------------------------------------------------------------------- /README_en.md: -------------------------------------------------------------------------------- 1 | 2 | ## FlutterCalendarWidget 3 | 4 | A calendar widget in flutter,you can design what you want to show! 5 | 6 | Language:English|[中文简体](README.md) 7 | 8 | - [FlutterCalendarWidget](#fluttercalendarwidget) 9 | - [Overview](#overview) 10 | - [Online Demo](#online-demo) 11 | - [Example](#example) 12 | - [Getting Started](#getting-started) 13 | - [2.0 version](#20-version) 14 | - [matters needing attention](#matters-needing-attention) 15 | - [API Documentation](#api-documentation) 16 | 17 | ### Overview 18 | 19 | * Support the Gregorian calendar, lunar calendar, solar terms, traditional festivals and common holidays 20 | * Date range setting, the maximum date range supported by default is 1971.01-2055.12 21 | * Disable date range settings. For example, you can click within a range of dates and gray the dates outside the range 22 | * Support single selection and multiple selection modes, and provide multiple selection of callbacks exceeding the limit and multiple selection of callbacks exceeding the specified range. 23 | * Jump to the specified date. Animation switching is supported by default 24 | * Custom Calendar items, support the way of combining widgets and drawing with canvas 25 | * Customize the weekbar at the top 26 | * According to the actual scene, you can add custom additional data to the item to realize various additional functions. For example, to realize the calendar of progress bar style and various marks of calendar 27 | * Support weekly view display, monthly view and weekly view display and switching linkage 28 | 29 | ### Online Demo 30 | 31 | Calendar supports web Preview:[Click here for preview](https://lxd312569496.github.io/flutter_custom_calendar/#/) 32 | 33 | 34 | ### Example 35 | 36 | 37 | 38 | 39 | 42 | 43 | 46 | 47 | 48 | 49 | 52 | 55 | 58 | 59 | 60 | 61 | 64 | 67 | 70 | 71 | 72 | 73 |
40 | 41 | 44 | 45 |
50 | 51 | 53 | 54 | 56 | 57 |
62 | 63 | 65 | 66 | 68 | 69 |
74 | 75 | 76 | ## Getting Started 77 | 78 | 1.add dependencies into you project pubspec.yaml file: 79 | ``` 80 | flutter_custom_calendar: 81 | git: 82 | url: https://github.com/LXD312569496/flutter_custom_calendar.git 83 | ``` 84 | 85 | 2.import flutter_custom_calendar lib 86 | ``` 87 | import 'package:flutter_custom_calendar/flutter_custom_calendar.dart'; 88 | ``` 89 | 90 | 3.create CalendarViewWidget object,profile CalendarController 91 | ``` 92 | CalendarController controller= new CalendarController( 93 | minYear: 2018, 94 | minYearMonth: 1, 95 | maxYear: 2020, 96 | maxYearMonth: 12, 97 | showMode: CalendarConstants.MODE_SHOW_MONTH_AND_WEEK); 98 | CalendarViewWidget calendar= CalendarViewWidget( 99 | calendarController: controller, 100 | ), 101 | ``` 102 | 103 | 4.operate controller 104 | ``` 105 | controller.toggleExpandStatus();//Switch between month view and week view 106 | ``` 107 | 108 | ``` 109 | controller.previousPage();//Action calendar switch to previous page 110 | ``` 111 | 112 | ``` 113 | controller.nextPage();//Action calendar switch to next page 114 | 115 | ``` 116 | 117 | 118 | ## 2.0 version 119 | Major changes: 120 | * The UI configuration related parameters are moved to the calendar view construction method (the old version is configured in the controller) 121 | * Calendar supports padding and margin attributes, and item size calculation and modification. 122 | * Realize the overall adaptive height of calendar 123 | * The controller provides the method of changing extradatamap, which can dynamically modify the customized data extradatamap at any time. 124 | * It supports the display of monthly view and weekly view, with weekly view as the priority, and mode "show" week "and" month " 125 | * Supports verticalspacing and itemsize properties 126 | 127 | 128 | ## matters needing attention 129 | 130 | * If you use the version before 2.0, you need to move the UI configuration related parameters to the calendar view construction method (the old version is configured in the controller). 131 | * I haven't found any other problems for the time being. If you have any, please let me know. 132 | * If you use this library to make a calendar, you can share the display results with me, and I will paste them on the document for display. 133 | 134 | 135 | 136 | ## API Documentation 137 | 138 | [API Documentation](API.md) 139 | 140 | 141 | -------------------------------------------------------------------------------- /example/.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | xmlns:android 14 | 15 | ^$ 16 | 17 | 18 | 19 |
20 |
21 | 22 | 23 | 24 | xmlns:.* 25 | 26 | ^$ 27 | 28 | 29 | BY_NAME 30 | 31 |
32 |
33 | 34 | 35 | 36 | .*:id 37 | 38 | http://schemas.android.com/apk/res/android 39 | 40 | 41 | 42 |
43 |
44 | 45 | 46 | 47 | .*:name 48 | 49 | http://schemas.android.com/apk/res/android 50 | 51 | 52 | 53 |
54 |
55 | 56 | 57 | 58 | name 59 | 60 | ^$ 61 | 62 | 63 | 64 |
65 |
66 | 67 | 68 | 69 | style 70 | 71 | ^$ 72 | 73 | 74 | 75 |
76 |
77 | 78 | 79 | 80 | .* 81 | 82 | ^$ 83 | 84 | 85 | BY_NAME 86 | 87 |
88 |
89 | 90 | 91 | 92 | .* 93 | 94 | http://schemas.android.com/apk/res/android 95 | 96 | 97 | ANDROID_ATTRIBUTE_ORDER 98 | 99 |
100 |
101 | 102 | 103 | 104 | .* 105 | 106 | .* 107 | 108 | 109 | BY_NAME 110 | 111 |
112 |
113 |
114 |
115 |
116 |
-------------------------------------------------------------------------------- /example/.idea/example.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /example/.idea/libraries/Dart_Packages.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | -------------------------------------------------------------------------------- /example/.idea/libraries/Dart_SDK.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /example/.idea/libraries/Flutter_Plugins.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /example/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/.idea/workspace.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 29 | 30 | 31 | 32 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 1592272073156 43 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # example 2 | 3 | flutter_custom_calendar/example 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) 13 | 14 | For help getting started with Flutter, view our 15 | [online documentation](https://flutter.dev/docs), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 26 | 27 | android { 28 | compileSdkVersion 28 29 | 30 | lintOptions { 31 | disable 'InvalidPackage' 32 | } 33 | 34 | defaultConfig { 35 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 36 | applicationId "com.example.example" 37 | minSdkVersion 16 38 | targetSdkVersion 28 39 | versionCode flutterVersionCode.toInteger() 40 | versionName flutterVersionName 41 | } 42 | 43 | buildTypes { 44 | release { 45 | // TODO: Add your own signing config for the release build. 46 | // Signing with the debug keys for now, so `flutter run --release` works. 47 | signingConfig signingConfigs.debug 48 | } 49 | } 50 | } 51 | 52 | flutter { 53 | source '../..' 54 | } 55 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 8 | 12 | 19 | 23 | 27 | 32 | 36 | 37 | 38 | 39 | 40 | 41 | 43 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/com/example/example/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.example; 2 | 3 | import io.flutter.embedding.android.FlutterActivity; 4 | 5 | public class MainActivity extends FlutterActivity { 6 | } 7 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_custom_calendar/f5f822f5a73a55264f382545ca368ff3b7ca98ac/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_custom_calendar/f5f822f5a73a55264f382545ca368ff3b7ca98ac/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_custom_calendar/f5f822f5a73a55264f382545ca368ff3b7ca98ac/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_custom_calendar/f5f822f5a73a55264f382545ca368ff3b7ca98ac/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_custom_calendar/f5f822f5a73a55264f382545ca368ff3b7ca98ac/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | jcenter() 5 | } 6 | 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:3.5.0' 9 | } 10 | } 11 | 12 | allprojects { 13 | repositories { 14 | google() 15 | jcenter() 16 | } 17 | } 18 | 19 | rootProject.buildDir = '../build' 20 | subprojects { 21 | project.buildDir = "${rootProject.buildDir}/${project.name}" 22 | } 23 | subprojects { 24 | project.evaluationDependsOn(':app') 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /example/android/example_android.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip 7 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /example/example.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /example/example/example.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 11 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 12 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 13 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 14 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 15 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXCopyFilesBuildPhase section */ 19 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 20 | isa = PBXCopyFilesBuildPhase; 21 | buildActionMask = 2147483647; 22 | dstPath = ""; 23 | dstSubfolderSpec = 10; 24 | files = ( 25 | ); 26 | name = "Embed Frameworks"; 27 | runOnlyForDeploymentPostprocessing = 0; 28 | }; 29 | /* End PBXCopyFilesBuildPhase section */ 30 | 31 | /* Begin PBXFileReference section */ 32 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 33 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 34 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 35 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 36 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 37 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 38 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 39 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 40 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 41 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 42 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 43 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 44 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 45 | /* End PBXFileReference section */ 46 | 47 | /* Begin PBXFrameworksBuildPhase section */ 48 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 49 | isa = PBXFrameworksBuildPhase; 50 | buildActionMask = 2147483647; 51 | files = ( 52 | ); 53 | runOnlyForDeploymentPostprocessing = 0; 54 | }; 55 | /* End PBXFrameworksBuildPhase section */ 56 | 57 | /* Begin PBXGroup section */ 58 | 9740EEB11CF90186004384FC /* Flutter */ = { 59 | isa = PBXGroup; 60 | children = ( 61 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 62 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 63 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 64 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 65 | ); 66 | name = Flutter; 67 | sourceTree = ""; 68 | }; 69 | 97C146E51CF9000F007C117D = { 70 | isa = PBXGroup; 71 | children = ( 72 | 9740EEB11CF90186004384FC /* Flutter */, 73 | 97C146F01CF9000F007C117D /* Runner */, 74 | 97C146EF1CF9000F007C117D /* Products */, 75 | ); 76 | sourceTree = ""; 77 | }; 78 | 97C146EF1CF9000F007C117D /* Products */ = { 79 | isa = PBXGroup; 80 | children = ( 81 | 97C146EE1CF9000F007C117D /* Runner.app */, 82 | ); 83 | name = Products; 84 | sourceTree = ""; 85 | }; 86 | 97C146F01CF9000F007C117D /* Runner */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 90 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 91 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 92 | 97C147021CF9000F007C117D /* Info.plist */, 93 | 97C146F11CF9000F007C117D /* Supporting Files */, 94 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 95 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 96 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 97 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 98 | ); 99 | path = Runner; 100 | sourceTree = ""; 101 | }; 102 | 97C146F11CF9000F007C117D /* Supporting Files */ = { 103 | isa = PBXGroup; 104 | children = ( 105 | ); 106 | name = "Supporting Files"; 107 | sourceTree = ""; 108 | }; 109 | /* End PBXGroup section */ 110 | 111 | /* Begin PBXNativeTarget section */ 112 | 97C146ED1CF9000F007C117D /* Runner */ = { 113 | isa = PBXNativeTarget; 114 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 115 | buildPhases = ( 116 | 9740EEB61CF901F6004384FC /* Run Script */, 117 | 97C146EA1CF9000F007C117D /* Sources */, 118 | 97C146EB1CF9000F007C117D /* Frameworks */, 119 | 97C146EC1CF9000F007C117D /* Resources */, 120 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 121 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 122 | ); 123 | buildRules = ( 124 | ); 125 | dependencies = ( 126 | ); 127 | name = Runner; 128 | productName = Runner; 129 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 130 | productType = "com.apple.product-type.application"; 131 | }; 132 | /* End PBXNativeTarget section */ 133 | 134 | /* Begin PBXProject section */ 135 | 97C146E61CF9000F007C117D /* Project object */ = { 136 | isa = PBXProject; 137 | attributes = { 138 | LastUpgradeCheck = 1020; 139 | ORGANIZATIONNAME = ""; 140 | TargetAttributes = { 141 | 97C146ED1CF9000F007C117D = { 142 | CreatedOnToolsVersion = 7.3.1; 143 | LastSwiftMigration = 1100; 144 | }; 145 | }; 146 | }; 147 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 148 | compatibilityVersion = "Xcode 9.3"; 149 | developmentRegion = en; 150 | hasScannedForEncodings = 0; 151 | knownRegions = ( 152 | en, 153 | Base, 154 | ); 155 | mainGroup = 97C146E51CF9000F007C117D; 156 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 157 | projectDirPath = ""; 158 | projectRoot = ""; 159 | targets = ( 160 | 97C146ED1CF9000F007C117D /* Runner */, 161 | ); 162 | }; 163 | /* End PBXProject section */ 164 | 165 | /* Begin PBXResourcesBuildPhase section */ 166 | 97C146EC1CF9000F007C117D /* Resources */ = { 167 | isa = PBXResourcesBuildPhase; 168 | buildActionMask = 2147483647; 169 | files = ( 170 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 171 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 172 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 173 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 174 | ); 175 | runOnlyForDeploymentPostprocessing = 0; 176 | }; 177 | /* End PBXResourcesBuildPhase section */ 178 | 179 | /* Begin PBXShellScriptBuildPhase section */ 180 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 181 | isa = PBXShellScriptBuildPhase; 182 | buildActionMask = 2147483647; 183 | files = ( 184 | ); 185 | inputPaths = ( 186 | ); 187 | name = "Thin Binary"; 188 | outputPaths = ( 189 | ); 190 | runOnlyForDeploymentPostprocessing = 0; 191 | shellPath = /bin/sh; 192 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; 193 | }; 194 | 9740EEB61CF901F6004384FC /* Run Script */ = { 195 | isa = PBXShellScriptBuildPhase; 196 | buildActionMask = 2147483647; 197 | files = ( 198 | ); 199 | inputPaths = ( 200 | ); 201 | name = "Run Script"; 202 | outputPaths = ( 203 | ); 204 | runOnlyForDeploymentPostprocessing = 0; 205 | shellPath = /bin/sh; 206 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 207 | }; 208 | /* End PBXShellScriptBuildPhase section */ 209 | 210 | /* Begin PBXSourcesBuildPhase section */ 211 | 97C146EA1CF9000F007C117D /* Sources */ = { 212 | isa = PBXSourcesBuildPhase; 213 | buildActionMask = 2147483647; 214 | files = ( 215 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 216 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 217 | ); 218 | runOnlyForDeploymentPostprocessing = 0; 219 | }; 220 | /* End PBXSourcesBuildPhase section */ 221 | 222 | /* Begin PBXVariantGroup section */ 223 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 224 | isa = PBXVariantGroup; 225 | children = ( 226 | 97C146FB1CF9000F007C117D /* Base */, 227 | ); 228 | name = Main.storyboard; 229 | sourceTree = ""; 230 | }; 231 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 232 | isa = PBXVariantGroup; 233 | children = ( 234 | 97C147001CF9000F007C117D /* Base */, 235 | ); 236 | name = LaunchScreen.storyboard; 237 | sourceTree = ""; 238 | }; 239 | /* End PBXVariantGroup section */ 240 | 241 | /* Begin XCBuildConfiguration section */ 242 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 243 | isa = XCBuildConfiguration; 244 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 245 | buildSettings = { 246 | ALWAYS_SEARCH_USER_PATHS = NO; 247 | CLANG_ANALYZER_NONNULL = YES; 248 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 249 | CLANG_CXX_LIBRARY = "libc++"; 250 | CLANG_ENABLE_MODULES = YES; 251 | CLANG_ENABLE_OBJC_ARC = YES; 252 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 253 | CLANG_WARN_BOOL_CONVERSION = YES; 254 | CLANG_WARN_COMMA = YES; 255 | CLANG_WARN_CONSTANT_CONVERSION = YES; 256 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 257 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 258 | CLANG_WARN_EMPTY_BODY = YES; 259 | CLANG_WARN_ENUM_CONVERSION = YES; 260 | CLANG_WARN_INFINITE_RECURSION = YES; 261 | CLANG_WARN_INT_CONVERSION = YES; 262 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 263 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 264 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 265 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 266 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 267 | CLANG_WARN_STRICT_PROTOTYPES = YES; 268 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 269 | CLANG_WARN_UNREACHABLE_CODE = YES; 270 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 271 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 272 | COPY_PHASE_STRIP = NO; 273 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 274 | ENABLE_NS_ASSERTIONS = NO; 275 | ENABLE_STRICT_OBJC_MSGSEND = YES; 276 | GCC_C_LANGUAGE_STANDARD = gnu99; 277 | GCC_NO_COMMON_BLOCKS = YES; 278 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 279 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 280 | GCC_WARN_UNDECLARED_SELECTOR = YES; 281 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 282 | GCC_WARN_UNUSED_FUNCTION = YES; 283 | GCC_WARN_UNUSED_VARIABLE = YES; 284 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 285 | MTL_ENABLE_DEBUG_INFO = NO; 286 | SDKROOT = iphoneos; 287 | SUPPORTED_PLATFORMS = iphoneos; 288 | TARGETED_DEVICE_FAMILY = "1,2"; 289 | VALIDATE_PRODUCT = YES; 290 | }; 291 | name = Profile; 292 | }; 293 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 294 | isa = XCBuildConfiguration; 295 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 296 | buildSettings = { 297 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 298 | CLANG_ENABLE_MODULES = YES; 299 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 300 | ENABLE_BITCODE = NO; 301 | FRAMEWORK_SEARCH_PATHS = ( 302 | "$(inherited)", 303 | "$(PROJECT_DIR)/Flutter", 304 | ); 305 | INFOPLIST_FILE = Runner/Info.plist; 306 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 307 | LIBRARY_SEARCH_PATHS = ( 308 | "$(inherited)", 309 | "$(PROJECT_DIR)/Flutter", 310 | ); 311 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example; 312 | PRODUCT_NAME = "$(TARGET_NAME)"; 313 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 314 | SWIFT_VERSION = 5.0; 315 | VERSIONING_SYSTEM = "apple-generic"; 316 | }; 317 | name = Profile; 318 | }; 319 | 97C147031CF9000F007C117D /* Debug */ = { 320 | isa = XCBuildConfiguration; 321 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 322 | buildSettings = { 323 | ALWAYS_SEARCH_USER_PATHS = NO; 324 | CLANG_ANALYZER_NONNULL = YES; 325 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 326 | CLANG_CXX_LIBRARY = "libc++"; 327 | CLANG_ENABLE_MODULES = YES; 328 | CLANG_ENABLE_OBJC_ARC = YES; 329 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 330 | CLANG_WARN_BOOL_CONVERSION = YES; 331 | CLANG_WARN_COMMA = YES; 332 | CLANG_WARN_CONSTANT_CONVERSION = YES; 333 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 334 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 335 | CLANG_WARN_EMPTY_BODY = YES; 336 | CLANG_WARN_ENUM_CONVERSION = YES; 337 | CLANG_WARN_INFINITE_RECURSION = YES; 338 | CLANG_WARN_INT_CONVERSION = YES; 339 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 340 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 341 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 342 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 343 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 344 | CLANG_WARN_STRICT_PROTOTYPES = YES; 345 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 346 | CLANG_WARN_UNREACHABLE_CODE = YES; 347 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 348 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 349 | COPY_PHASE_STRIP = NO; 350 | DEBUG_INFORMATION_FORMAT = dwarf; 351 | ENABLE_STRICT_OBJC_MSGSEND = YES; 352 | ENABLE_TESTABILITY = YES; 353 | GCC_C_LANGUAGE_STANDARD = gnu99; 354 | GCC_DYNAMIC_NO_PIC = NO; 355 | GCC_NO_COMMON_BLOCKS = YES; 356 | GCC_OPTIMIZATION_LEVEL = 0; 357 | GCC_PREPROCESSOR_DEFINITIONS = ( 358 | "DEBUG=1", 359 | "$(inherited)", 360 | ); 361 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 362 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 363 | GCC_WARN_UNDECLARED_SELECTOR = YES; 364 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 365 | GCC_WARN_UNUSED_FUNCTION = YES; 366 | GCC_WARN_UNUSED_VARIABLE = YES; 367 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 368 | MTL_ENABLE_DEBUG_INFO = YES; 369 | ONLY_ACTIVE_ARCH = YES; 370 | SDKROOT = iphoneos; 371 | TARGETED_DEVICE_FAMILY = "1,2"; 372 | }; 373 | name = Debug; 374 | }; 375 | 97C147041CF9000F007C117D /* Release */ = { 376 | isa = XCBuildConfiguration; 377 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 378 | buildSettings = { 379 | ALWAYS_SEARCH_USER_PATHS = NO; 380 | CLANG_ANALYZER_NONNULL = YES; 381 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 382 | CLANG_CXX_LIBRARY = "libc++"; 383 | CLANG_ENABLE_MODULES = YES; 384 | CLANG_ENABLE_OBJC_ARC = YES; 385 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 386 | CLANG_WARN_BOOL_CONVERSION = YES; 387 | CLANG_WARN_COMMA = YES; 388 | CLANG_WARN_CONSTANT_CONVERSION = YES; 389 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 390 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 391 | CLANG_WARN_EMPTY_BODY = YES; 392 | CLANG_WARN_ENUM_CONVERSION = YES; 393 | CLANG_WARN_INFINITE_RECURSION = YES; 394 | CLANG_WARN_INT_CONVERSION = YES; 395 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 396 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 397 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 398 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 399 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 400 | CLANG_WARN_STRICT_PROTOTYPES = YES; 401 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 402 | CLANG_WARN_UNREACHABLE_CODE = YES; 403 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 404 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 405 | COPY_PHASE_STRIP = NO; 406 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 407 | ENABLE_NS_ASSERTIONS = NO; 408 | ENABLE_STRICT_OBJC_MSGSEND = YES; 409 | GCC_C_LANGUAGE_STANDARD = gnu99; 410 | GCC_NO_COMMON_BLOCKS = YES; 411 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 412 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 413 | GCC_WARN_UNDECLARED_SELECTOR = YES; 414 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 415 | GCC_WARN_UNUSED_FUNCTION = YES; 416 | GCC_WARN_UNUSED_VARIABLE = YES; 417 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 418 | MTL_ENABLE_DEBUG_INFO = NO; 419 | SDKROOT = iphoneos; 420 | SUPPORTED_PLATFORMS = iphoneos; 421 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 422 | TARGETED_DEVICE_FAMILY = "1,2"; 423 | VALIDATE_PRODUCT = YES; 424 | }; 425 | name = Release; 426 | }; 427 | 97C147061CF9000F007C117D /* Debug */ = { 428 | isa = XCBuildConfiguration; 429 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 430 | buildSettings = { 431 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 432 | CLANG_ENABLE_MODULES = YES; 433 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 434 | ENABLE_BITCODE = NO; 435 | FRAMEWORK_SEARCH_PATHS = ( 436 | "$(inherited)", 437 | "$(PROJECT_DIR)/Flutter", 438 | ); 439 | INFOPLIST_FILE = Runner/Info.plist; 440 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 441 | LIBRARY_SEARCH_PATHS = ( 442 | "$(inherited)", 443 | "$(PROJECT_DIR)/Flutter", 444 | ); 445 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example; 446 | PRODUCT_NAME = "$(TARGET_NAME)"; 447 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 448 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 449 | SWIFT_VERSION = 5.0; 450 | VERSIONING_SYSTEM = "apple-generic"; 451 | }; 452 | name = Debug; 453 | }; 454 | 97C147071CF9000F007C117D /* Release */ = { 455 | isa = XCBuildConfiguration; 456 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 457 | buildSettings = { 458 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 459 | CLANG_ENABLE_MODULES = YES; 460 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 461 | ENABLE_BITCODE = NO; 462 | FRAMEWORK_SEARCH_PATHS = ( 463 | "$(inherited)", 464 | "$(PROJECT_DIR)/Flutter", 465 | ); 466 | INFOPLIST_FILE = Runner/Info.plist; 467 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 468 | LIBRARY_SEARCH_PATHS = ( 469 | "$(inherited)", 470 | "$(PROJECT_DIR)/Flutter", 471 | ); 472 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example; 473 | PRODUCT_NAME = "$(TARGET_NAME)"; 474 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 475 | SWIFT_VERSION = 5.0; 476 | VERSIONING_SYSTEM = "apple-generic"; 477 | }; 478 | name = Release; 479 | }; 480 | /* End XCBuildConfiguration section */ 481 | 482 | /* Begin XCConfigurationList section */ 483 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 484 | isa = XCConfigurationList; 485 | buildConfigurations = ( 486 | 97C147031CF9000F007C117D /* Debug */, 487 | 97C147041CF9000F007C117D /* Release */, 488 | 249021D3217E4FDB00AE95B9 /* Profile */, 489 | ); 490 | defaultConfigurationIsVisible = 0; 491 | defaultConfigurationName = Release; 492 | }; 493 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 494 | isa = XCConfigurationList; 495 | buildConfigurations = ( 496 | 97C147061CF9000F007C117D /* Debug */, 497 | 97C147071CF9000F007C117D /* Release */, 498 | 249021D4217E4FDB00AE95B9 /* Profile */, 499 | ); 500 | defaultConfigurationIsVisible = 0; 501 | defaultConfigurationName = Release; 502 | }; 503 | /* End XCConfigurationList section */ 504 | }; 505 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 506 | } 507 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_custom_calendar/f5f822f5a73a55264f382545ca368ff3b7ca98ac/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_custom_calendar/f5f822f5a73a55264f382545ca368ff3b7ca98ac/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_custom_calendar/f5f822f5a73a55264f382545ca368ff3b7ca98ac/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_custom_calendar/f5f822f5a73a55264f382545ca368ff3b7ca98ac/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_custom_calendar/f5f822f5a73a55264f382545ca368ff3b7ca98ac/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_custom_calendar/f5f822f5a73a55264f382545ca368ff3b7ca98ac/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_custom_calendar/f5f822f5a73a55264f382545ca368ff3b7ca98ac/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_custom_calendar/f5f822f5a73a55264f382545ca368ff3b7ca98ac/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_custom_calendar/f5f822f5a73a55264f382545ca368ff3b7ca98ac/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_custom_calendar/f5f822f5a73a55264f382545ca368ff3b7ca98ac/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_custom_calendar/f5f822f5a73a55264f382545ca368ff3b7ca98ac/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_custom_calendar/f5f822f5a73a55264f382545ca368ff3b7ca98ac/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_custom_calendar/f5f822f5a73a55264f382545ca368ff3b7ca98ac/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_custom_calendar/f5f822f5a73a55264f382545ca368ff3b7ca98ac/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_custom_calendar/f5f822f5a73a55264f382545ca368ff3b7ca98ac/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_custom_calendar/f5f822f5a73a55264f382545ca368ff3b7ca98ac/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_custom_calendar/f5f822f5a73a55264f382545ca368ff3b7ca98ac/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_custom_calendar/f5f822f5a73a55264f382545ca368ff3b7ca98ac/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | example 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:collection'; 2 | 3 | import 'package:flutter/cupertino.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_custom_calendar/constants/constants.dart'; 6 | import 'package:flutter_custom_calendar/controller.dart'; 7 | import 'package:flutter_custom_calendar/flutter_custom_calendar.dart'; 8 | import 'package:flutter_custom_calendar/utils/LogUtil.dart'; 9 | 10 | void main() { 11 | runApp(MyApp()); 12 | } 13 | 14 | class MyApp extends StatelessWidget { 15 | // This widget is the root of your application. 16 | @override 17 | Widget build(BuildContext context) { 18 | return MaterialApp( 19 | title: 'Flutter Demo', 20 | theme: ThemeData( 21 | primarySwatch: Colors.blue, 22 | visualDensity: VisualDensity.adaptivePlatformDensity, 23 | focusColor: Colors.teal), 24 | home: MyHomePage(title: 'Flutter Demo Home Page'), 25 | ); 26 | } 27 | } 28 | 29 | class MyHomePage extends StatefulWidget { 30 | MyHomePage({Key key, this.title}) : super(key: key); 31 | 32 | final String title; 33 | 34 | @override 35 | _MyHomePageState createState() => _MyHomePageState(); 36 | } 37 | 38 | class _MyHomePageState extends State { 39 | CalendarController controller; 40 | CalendarViewWidget calendar; 41 | HashSet _selectedDate = new HashSet(); 42 | HashSet _selectedModels = new HashSet(); 43 | 44 | GlobalKey _globalKey = new GlobalKey(); 45 | @override 46 | void initState() { 47 | _selectedDate.add(DateTime.now()); 48 | controller = new CalendarController( 49 | minYear: 2019, 50 | minYearMonth: 1, 51 | maxYear: 2021, 52 | maxYearMonth: 12, 53 | showMode: CalendarConstants.MODE_SHOW_WEEK_AND_MONTH, 54 | selectedDateTimeList: _selectedDate, 55 | selectMode: CalendarSelectedMode.singleSelect) 56 | ..addOnCalendarSelectListener((dateModel) { 57 | _selectedModels.add(dateModel); 58 | setState(() { 59 | _selectDate = _selectedModels.toString(); 60 | }); 61 | }) 62 | ..addOnCalendarUnSelectListener((dateModel) { 63 | LogUtil.log(TAG: '_selectedModels', message: _selectedModels.toString()); 64 | LogUtil.log(TAG: 'dateModel', message: dateModel.toString()); 65 | if (_selectedModels.contains(dateModel)) { 66 | _selectedModels.remove(dateModel); 67 | } 68 | setState(() { 69 | _selectDate = ''; 70 | }); 71 | }); 72 | calendar = new CalendarViewWidget( 73 | key: _globalKey, 74 | calendarController: controller, 75 | dayWidgetBuilder: (DateModel model) { 76 | double wd = (MediaQuery.of(context).size.width - 20) / 7; 77 | bool _isSelected = model.isSelected; 78 | if (_isSelected && 79 | CalendarSelectedMode.singleSelect == 80 | controller.calendarConfiguration.selectMode) { 81 | _selectDate = model.toString(); 82 | } 83 | return ClipRRect( 84 | borderRadius: BorderRadius.all(Radius.circular(wd / 2)), 85 | child: Container( 86 | color: _isSelected ? Theme.of(context).focusColor : Colors.white, 87 | alignment: Alignment.center, 88 | child: Column( 89 | crossAxisAlignment: CrossAxisAlignment.center, 90 | mainAxisAlignment: MainAxisAlignment.center, 91 | children: [ 92 | Text( 93 | model.day.toString(), 94 | style: TextStyle( 95 | color: model.isCurrentMonth 96 | ? (_isSelected == false 97 | ? (model.isWeekend 98 | ? Colors.black38 99 | : Colors.black87) 100 | : Colors.white) 101 | : Colors.black38), 102 | ), 103 | // Text(model.lunarDay.toString()), 104 | ], 105 | ), 106 | ), 107 | ); 108 | }, 109 | ); 110 | WidgetsBinding.instance.addPostFrameCallback((timeStamp) { 111 | controller.addExpandChangeListener((value) { 112 | /// 添加改变 月视图和 周视图的监听 113 | _isMonthSelected = value; 114 | setState(() {}); 115 | }); 116 | }); 117 | 118 | super.initState(); 119 | } 120 | 121 | bool _isMonthSelected = false; 122 | 123 | String _selectDate = ''; 124 | @override 125 | Widget build(BuildContext context) { 126 | return Scaffold( 127 | appBar: AppBar( 128 | title: Text(widget.title), 129 | ), 130 | body: CupertinoScrollbar( 131 | child: CustomScrollView( 132 | slivers: [ 133 | _topButtons(), 134 | _topMonths(), 135 | SliverToBoxAdapter( 136 | child: calendar, 137 | ), 138 | SliverToBoxAdapter( 139 | child: Container( 140 | child: Text( 141 | ' $_selectDate ', 142 | style: TextStyle(color: Theme.of(context).focusColor), 143 | ), 144 | ), 145 | ) 146 | ], 147 | ), 148 | ), 149 | ); 150 | } 151 | 152 | Widget _topButtons() { 153 | return SliverToBoxAdapter( 154 | child: Wrap( 155 | direction: Axis.vertical, 156 | crossAxisAlignment: WrapCrossAlignment.start, 157 | children: [ 158 | Text('请选择mode'), 159 | Wrap( 160 | spacing: 10, 161 | runSpacing: 10, 162 | children: [ 163 | FlatButton( 164 | child: Text( 165 | '单选', 166 | style: TextStyle(color: Colors.white), 167 | ), 168 | onPressed: () { 169 | setState(() { 170 | controller.calendarConfiguration.selectMode = 171 | CalendarSelectedMode.singleSelect; 172 | }); 173 | }, 174 | color: controller.calendarConfiguration.selectMode == 175 | CalendarSelectedMode.singleSelect 176 | ? Colors.teal 177 | : Colors.black38, 178 | ), 179 | FlatButton( 180 | child: Text( 181 | '多选', 182 | style: TextStyle(color: Colors.white), 183 | ), 184 | onPressed: () { 185 | setState(() { 186 | controller.calendarConfiguration.selectMode = 187 | CalendarSelectedMode.multiSelect; 188 | }); 189 | }, 190 | color: controller.calendarConfiguration.selectMode == 191 | CalendarSelectedMode.multiSelect 192 | ? Colors.teal 193 | : Colors.black38, 194 | ), 195 | FlatButton( 196 | child: Text( 197 | '多选 选择开始和结束', 198 | style: TextStyle(color: Colors.white), 199 | ), 200 | onPressed: () { 201 | setState(() { 202 | controller.calendarConfiguration.selectMode = 203 | CalendarSelectedMode.mutltiStartToEndSelect; 204 | }); 205 | }, 206 | color: controller.calendarConfiguration.selectMode == 207 | CalendarSelectedMode.mutltiStartToEndSelect 208 | ? Colors.teal 209 | : Colors.black38, 210 | ), 211 | ], 212 | ), 213 | ], 214 | ), 215 | ); 216 | } 217 | 218 | Widget _topMonths() { 219 | return SliverToBoxAdapter( 220 | child: Wrap( 221 | direction: Axis.vertical, 222 | crossAxisAlignment: WrapCrossAlignment.start, 223 | children: [ 224 | Text('月视图和周视图'), 225 | Wrap( 226 | spacing: 10, 227 | runSpacing: 10, 228 | children: [ 229 | FlatButton( 230 | child: Text( 231 | '月视图', 232 | style: TextStyle(color: Colors.white), 233 | ), 234 | onPressed: () { 235 | setState(() { 236 | controller.weekAndMonthViewChange( 237 | CalendarConstants.MODE_SHOW_ONLY_WEEK); 238 | }); 239 | }, 240 | color: _isMonthSelected ? Colors.teal : Colors.black38, 241 | ), 242 | FlatButton( 243 | child: Text( 244 | '周视图', 245 | style: TextStyle(color: Colors.white), 246 | ), 247 | onPressed: () { 248 | setState(() { 249 | controller.weekAndMonthViewChange( 250 | CalendarConstants.MODE_SHOW_ONLY_MONTH); 251 | }); 252 | }, 253 | color: _isMonthSelected == false ? Colors.teal : Colors.black38, 254 | ), 255 | ], 256 | ), 257 | ], 258 | ), 259 | ); 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /example/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 | url: "https://pub.flutter-io.cn" 9 | source: hosted 10 | version: "2.4.2" 11 | boolean_selector: 12 | dependency: transitive 13 | description: 14 | name: boolean_selector 15 | url: "https://pub.flutter-io.cn" 16 | source: hosted 17 | version: "2.0.0" 18 | characters: 19 | dependency: transitive 20 | description: 21 | name: characters 22 | url: "https://pub.flutter-io.cn" 23 | source: hosted 24 | version: "1.0.0" 25 | charcode: 26 | dependency: transitive 27 | description: 28 | name: charcode 29 | url: "https://pub.flutter-io.cn" 30 | source: hosted 31 | version: "1.1.3" 32 | clock: 33 | dependency: transitive 34 | description: 35 | name: clock 36 | url: "https://pub.flutter-io.cn" 37 | source: hosted 38 | version: "1.0.1" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.flutter-io.cn" 44 | source: hosted 45 | version: "1.14.13" 46 | cupertino_icons: 47 | dependency: "direct main" 48 | description: 49 | name: cupertino_icons 50 | url: "https://pub.flutter-io.cn" 51 | source: hosted 52 | version: "0.1.3" 53 | fake_async: 54 | dependency: transitive 55 | description: 56 | name: fake_async 57 | url: "https://pub.flutter-io.cn" 58 | source: hosted 59 | version: "1.1.0" 60 | flutter: 61 | dependency: "direct main" 62 | description: flutter 63 | source: sdk 64 | version: "0.0.0" 65 | flutter_custom_calendar: 66 | dependency: "direct main" 67 | description: 68 | path: ".." 69 | relative: true 70 | source: path 71 | version: "1.0.4+0.5" 72 | flutter_test: 73 | dependency: "direct dev" 74 | description: flutter 75 | source: sdk 76 | version: "0.0.0" 77 | matcher: 78 | dependency: transitive 79 | description: 80 | name: matcher 81 | url: "https://pub.flutter-io.cn" 82 | source: hosted 83 | version: "0.12.8" 84 | meta: 85 | dependency: transitive 86 | description: 87 | name: meta 88 | url: "https://pub.flutter-io.cn" 89 | source: hosted 90 | version: "1.1.8" 91 | nested: 92 | dependency: transitive 93 | description: 94 | name: nested 95 | url: "https://pub.flutter-io.cn" 96 | source: hosted 97 | version: "0.0.4" 98 | path: 99 | dependency: transitive 100 | description: 101 | name: path 102 | url: "https://pub.flutter-io.cn" 103 | source: hosted 104 | version: "1.7.0" 105 | provider: 106 | dependency: transitive 107 | description: 108 | name: provider 109 | url: "https://pub.flutter-io.cn" 110 | source: hosted 111 | version: "4.1.3" 112 | sky_engine: 113 | dependency: transitive 114 | description: flutter 115 | source: sdk 116 | version: "0.0.99" 117 | source_span: 118 | dependency: transitive 119 | description: 120 | name: source_span 121 | url: "https://pub.flutter-io.cn" 122 | source: hosted 123 | version: "1.7.0" 124 | stack_trace: 125 | dependency: transitive 126 | description: 127 | name: stack_trace 128 | url: "https://pub.flutter-io.cn" 129 | source: hosted 130 | version: "1.9.5" 131 | stream_channel: 132 | dependency: transitive 133 | description: 134 | name: stream_channel 135 | url: "https://pub.flutter-io.cn" 136 | source: hosted 137 | version: "2.0.0" 138 | string_scanner: 139 | dependency: transitive 140 | description: 141 | name: string_scanner 142 | url: "https://pub.flutter-io.cn" 143 | source: hosted 144 | version: "1.0.5" 145 | term_glyph: 146 | dependency: transitive 147 | description: 148 | name: term_glyph 149 | url: "https://pub.flutter-io.cn" 150 | source: hosted 151 | version: "1.1.0" 152 | test_api: 153 | dependency: transitive 154 | description: 155 | name: test_api 156 | url: "https://pub.flutter-io.cn" 157 | source: hosted 158 | version: "0.2.17" 159 | typed_data: 160 | dependency: transitive 161 | description: 162 | name: typed_data 163 | url: "https://pub.flutter-io.cn" 164 | source: hosted 165 | version: "1.2.0" 166 | vector_math: 167 | dependency: transitive 168 | description: 169 | name: vector_math 170 | url: "https://pub.flutter-io.cn" 171 | source: hosted 172 | version: "2.0.8" 173 | sdks: 174 | dart: ">=2.9.0-14.0.dev <3.0.0" 175 | flutter: ">=1.16.0" 176 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: example 2 | description: flutter_custom_calendar example 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `pub publish`. This is preferred for private packages. 6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 7 | 8 | # The following defines the version and build number for your application. 9 | # A version number is three numbers separated by dots, like 1.2.43 10 | # followed by an optional build number separated by a +. 11 | # Both the version and the builder number may be overridden in flutter 12 | # build by specifying --build-name and --build-number, respectively. 13 | # In Android, build-name is used as versionName while build-number used as versionCode. 14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 16 | # Read more about iOS versioning at 17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 18 | version: 1.0.0+1 19 | 20 | environment: 21 | sdk: ">=2.7.0 <3.0.0" 22 | 23 | dependencies: 24 | flutter: 25 | sdk: flutter 26 | 27 | 28 | 29 | # The following adds the Cupertino Icons font to your application. 30 | # Use with the CupertinoIcons class for iOS style icons. 31 | cupertino_icons: ^0.1.3 32 | flutter_custom_calendar: 33 | path: ../ 34 | 35 | dev_dependencies: 36 | flutter_test: 37 | sdk: flutter 38 | 39 | # For information on the generic Dart part of this file, see the 40 | # following page: https://dart.dev/tools/pub/pubspec 41 | 42 | # The following section is specific to Flutter. 43 | flutter: 44 | 45 | # The following line ensures that the Material Icons font is 46 | # included with your application, so that you can use the icons in 47 | # the material Icons class. 48 | uses-material-design: true 49 | 50 | # To add assets to your application, add an assets section, like this: 51 | # assets: 52 | # - images/a_dot_burr.jpeg 53 | # - images/a_dot_ham.jpeg 54 | 55 | # An image asset can refer to one or more resolution-specific "variants", see 56 | # https://flutter.dev/assets-and-images/#resolution-aware. 57 | 58 | # For details regarding adding assets from package dependencies, see 59 | # https://flutter.dev/assets-and-images/#from-packages 60 | 61 | # To add custom fonts to your application, add a fonts section here, 62 | # in this "flutter" section. Each entry in this list should have a 63 | # "family" key with the font family name, and a "fonts" key with a 64 | # list giving the asset and other descriptors for the font. For 65 | # example: 66 | # fonts: 67 | # - family: Schyler 68 | # fonts: 69 | # - asset: fonts/Schyler-Regular.ttf 70 | # - asset: fonts/Schyler-Italic.ttf 71 | # style: italic 72 | # - family: Trajan Pro 73 | # fonts: 74 | # - asset: fonts/TrajanPro.ttf 75 | # - asset: fonts/TrajanPro_Bold.ttf 76 | # weight: 700 77 | # 78 | # For details regarding fonts from package dependencies, 79 | # see https://flutter.dev/custom-fonts/#from-packages 80 | -------------------------------------------------------------------------------- /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 | void main() { 12 | // testWidgets('Counter increments smoke test', (WidgetTester tester) async { 13 | // // Build our app and trigger a frame. 14 | // await tester.pumpWidget(MyApp()); 15 | // 16 | // // Verify that our counter starts at 0. 17 | // expect(find.text('0'), findsOneWidget); 18 | // expect(find.text('1'), findsNothing); 19 | // 20 | // // Tap the '+' icon and trigger a frame. 21 | // await tester.tap(find.byIcon(Icons.add)); 22 | // await tester.pump(); 23 | // 24 | // // Verify that our counter has incremented. 25 | // expect(find.text('0'), findsNothing); 26 | // expect(find.text('1'), findsOneWidget); 27 | // }); 28 | } 29 | -------------------------------------------------------------------------------- /flutter_custom_calendar.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /fluttercustomcalendar.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /img.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_custom_calendar/f5f822f5a73a55264f382545ca368ff3b7ca98ac/img.gif -------------------------------------------------------------------------------- /lib/cache_data.dart: -------------------------------------------------------------------------------- 1 | import 'model/date_model.dart'; 2 | 3 | /** 4 | * 保存一些缓存数据,不用再次去计算日子 5 | */ 6 | class CacheData { 7 | //私有构造函数 8 | CacheData._(); 9 | 10 | static CacheData _instance; 11 | 12 | static CacheData get instance => _instance; 13 | 14 | Map> monthListCache = Map(); 15 | 16 | Map> weekListCache = Map(); 17 | 18 | static CacheData getInstance() { 19 | if (_instance == null) { 20 | _instance = new CacheData._(); 21 | } 22 | return _instance; 23 | } 24 | 25 | void clearData() { 26 | monthListCache.clear(); 27 | weekListCache.clear(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/calendar_provider.dart: -------------------------------------------------------------------------------- 1 | import 'dart:collection'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_custom_calendar/cache_data.dart'; 5 | import 'package:flutter_custom_calendar/widget/month_view.dart'; 6 | import 'configuration.dart'; 7 | import 'constants/constants.dart'; 8 | import 'flutter_custom_calendar.dart'; 9 | import 'utils/LogUtil.dart'; 10 | import 'utils/date_util.dart'; 11 | import 'model/date_model.dart'; 12 | 13 | /** 14 | * 引入provider的状态管理,保存一些临时信息 15 | * 16 | * 目前的情况:只需要获取状态,不需要监听rebuild 17 | */ 18 | class CalendarProvider extends ChangeNotifier { 19 | double _totalHeight; //当前月视图的整体高度 20 | HashSet selectedDateList = new HashSet(); //被选中的日期,用于多选 21 | DateModel _selectDateModel; //当前选中的日期,用于单选 22 | ItemContainerState lastClickItemState; 23 | DateModel _lastClickDateModel; 24 | 25 | double get totalHeight => _totalHeight; 26 | 27 | ValueNotifier _generation = 28 | new ValueNotifier(0); //生成的int值,每次变化,都会刷新整个日历。 29 | 30 | ValueNotifier get generation => _generation; 31 | 32 | set generation(ValueNotifier value) { 33 | _generation = value; 34 | } 35 | 36 | set totalHeight(double value) { 37 | _totalHeight = value; 38 | } 39 | 40 | changeTotalHeight(double value) { 41 | _totalHeight = value; 42 | notifyListeners(); 43 | } 44 | 45 | DateModel get lastClickDateModel => 46 | _lastClickDateModel; //保存最后点击的一个日期,用于周视图与月视图之间的切换和同步 47 | 48 | set lastClickDateModel(DateModel value) { 49 | _lastClickDateModel = value; 50 | print("set lastClickDateModel:$lastClickDateModel"); 51 | } 52 | 53 | DateModel get selectDateModel => _selectDateModel; 54 | 55 | set selectDateModel(DateModel value) { 56 | _selectDateModel = value; 57 | LogUtil.log(TAG: this.runtimeType, message: "selectDateModel change:$selectDateModel"); 58 | // notifyListeners(); 59 | } 60 | 61 | //根据lastClickDateModel,去计算需要展示的星期视图的初始index 62 | int get weekPageIndex { 63 | //计算当前星期视图的index 64 | print('计算当前星期视图的index = > lastClickDateModel$lastClickDateModel'); 65 | DateModel dateModel = lastClickDateModel; 66 | DateTime firstWeek = calendarConfiguration.weekList[0].getDateTime(); 67 | int index = 0; 68 | for (int i = 0; i < calendarConfiguration.weekList.length; i++) { 69 | DateTime nextWeek = firstWeek.add(Duration(days: 7)); 70 | if (dateModel.getDateTime().isBefore(nextWeek)) { 71 | index = i; 72 | break; 73 | } else { 74 | firstWeek = nextWeek; 75 | index++; 76 | } 77 | } 78 | 79 | print("lastClickDateModel:$lastClickDateModel,weekPageIndex:$index, totalHeight:$totalHeight"); 80 | return index; 81 | } 82 | 83 | //根据lastClickDateModel,去计算需要展示的月视图的index 84 | int get monthPageIndex { 85 | //计算当前月视图的index 86 | DateModel dateModel = lastClickDateModel; 87 | int index = 0; 88 | for (int i = 0; i < calendarConfiguration.monthList.length - 1; i++) { 89 | DateTime preMonth = calendarConfiguration.monthList[i].getDateTime(); 90 | DateTime nextMonth = calendarConfiguration.monthList[i + 1].getDateTime(); 91 | if (!dateModel.getDateTime().isBefore(preMonth) && 92 | !dateModel.getDateTime().isAfter(nextMonth)) { 93 | index = i; 94 | break; 95 | } else { 96 | index++; 97 | } 98 | } 99 | 100 | print("lastClickDateModel:$lastClickDateModel, monthPageIndex:$index, totalHeight:$totalHeight"); 101 | return index; 102 | } 103 | 104 | ValueNotifier expandStatus; //当前展开状态 105 | 106 | //配置类也放这里吧,这样的话,所有子树,都可以拿到配置的信息 107 | CalendarConfiguration calendarConfiguration; 108 | void weekAndMonthViewChange(int mode) {} 109 | 110 | void initData({ 111 | Set selectedDateList, 112 | DateModel selectDateModel, 113 | CalendarConfiguration calendarConfiguration, 114 | EdgeInsetsGeometry padding, 115 | EdgeInsetsGeometry margin, 116 | double itemSize, 117 | double verticalSpacing, 118 | DayWidgetBuilder dayWidgetBuilder, 119 | WeekBarItemWidgetBuilder weekBarItemWidgetBuilder, 120 | }) { 121 | LogUtil.log(TAG: this.runtimeType, message: "CalendarProvider initData"); 122 | this.calendarConfiguration = calendarConfiguration; 123 | this 124 | .selectedDateList 125 | .addAll(this.calendarConfiguration.defaultSelectedDateList); 126 | this.selectDateModel = this.calendarConfiguration.selectDateModel; 127 | this.calendarConfiguration.padding = padding; 128 | this.calendarConfiguration.margin = margin; 129 | this.calendarConfiguration.itemSize = itemSize; 130 | this.calendarConfiguration.verticalSpacing = verticalSpacing; 131 | this.calendarConfiguration.dayWidgetBuilder = dayWidgetBuilder; 132 | this.calendarConfiguration.weekBarItemWidgetBuilder = weekBarItemWidgetBuilder; 133 | 134 | //lastClickDateModel,默认是选中的item,如果为空的话,默认是当前的时间 135 | this.lastClickDateModel = selectDateModel != null ? selectDateModel : DateModel.fromDateTime(DateTime.now())..isSelected = true; 136 | //初始化展示状态 137 | if (calendarConfiguration.showMode == CalendarConstants.MODE_SHOW_ONLY_WEEK || calendarConfiguration.showMode == CalendarConstants.MODE_SHOW_WEEK_AND_MONTH) { 138 | expandStatus = ValueNotifier(false); 139 | } else { 140 | expandStatus = ValueNotifier(true); 141 | } 142 | //初始化item的大小。如果itemSize为空,默认是宽度/7。网页版的话是高度/7。需要减去padding和margin值 143 | if (calendarConfiguration.itemSize == null) { 144 | MediaQueryData mediaQueryData = 145 | MediaQueryData.fromWindow(WidgetsBinding.instance.window); 146 | if (mediaQueryData.orientation == Orientation.landscape) { 147 | calendarConfiguration.itemSize = (mediaQueryData.size.height - 148 | calendarConfiguration.padding.vertical - 149 | calendarConfiguration.margin.vertical) / 150 | 7; 151 | } else { 152 | calendarConfiguration.itemSize = (mediaQueryData.size.width - 153 | calendarConfiguration.padding.horizontal - 154 | calendarConfiguration.margin.horizontal) / 155 | 7; 156 | } 157 | } else { 158 | //如果指定了itemSize的大小,那就按照itemSize的大小去绘制 159 | } 160 | 161 | ///如果第一个页面展示的是月视图,需要计算下初始化的高度 162 | if (calendarConfiguration.showMode == 163 | CalendarConstants.MODE_SHOW_ONLY_MONTH || 164 | calendarConfiguration.showMode == 165 | CalendarConstants.MODE_SHOW_MONTH_AND_WEEK) { 166 | int lineCount = DateUtil.getMonthViewLineCount( 167 | calendarConfiguration.nowYear, 168 | calendarConfiguration.nowMonth, 169 | calendarConfiguration.offset); 170 | totalHeight = calendarConfiguration.itemSize * (lineCount) + calendarConfiguration.verticalSpacing * (lineCount - 1); 171 | } else { 172 | totalHeight = calendarConfiguration.itemSize; 173 | } 174 | print('totalHeight: $totalHeight'); 175 | } 176 | 177 | //退出的时候,清除数据 178 | void clearData() { 179 | LogUtil.log(TAG: this.runtimeType, message: "CalendarProvider clearData"); 180 | CacheData.getInstance().clearData(); 181 | selectedDateList.clear(); 182 | selectDateModel = null; 183 | calendarConfiguration = null; 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /lib/configuration.dart: -------------------------------------------------------------------------------- 1 | import 'dart:collection'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter/foundation.dart'; 5 | 6 | import 'constants/constants.dart'; 7 | import 'flutter_custom_calendar.dart'; 8 | 9 | import 'model/date_model.dart'; 10 | 11 | /** 12 | * 配置信息类 13 | */ 14 | class CalendarConfiguration { 15 | //默认是单选,可以配置为MODE_SINGLE_SELECT,MODE_MULTI_SELECT 16 | CalendarSelectedMode selectMode; 17 | 18 | //仅展示月视图,仅展示周视图,支持月视图和周视图切换 19 | int showMode; 20 | 21 | //日历显示的最小年份和最大年份 22 | int minYear; 23 | int maxYear; 24 | 25 | //日历显示的最小年份的月份,最大年份的月份 26 | int minYearMonth; 27 | int maxYearMonth; 28 | 29 | //日历显示的当前的年份和月份 30 | int nowYear; 31 | int nowMonth; 32 | // 周视图需要 33 | int nowDay; 34 | 35 | //可操作的范围设置,比如点击选择 36 | int minSelectYear; 37 | int minSelectMonth; 38 | int minSelectDay; 39 | 40 | int maxSelectYear; 41 | int maxSelectMonth; 42 | int maxSelectDay; //注意:不能超过对应月份的总天数 43 | 44 | DateModel selectDateModel; //默认被选中的item,用于单选 45 | HashSet defaultSelectedDateList; //默认被选中的日期set,用于多选 46 | int maxMultiSelectCount; //多选,最多选多少个 47 | Map extraDataMap = new Map(); //自定义额外的数据 48 | 49 | /** 50 | * UI绘制方面的绘制 51 | */ 52 | double itemSize; //默认是屏幕宽度/7 53 | double verticalSpacing; //日历item之间的竖直方向间距,默认10 54 | BoxDecoration boxDecoration; //整体的背景设置 55 | EdgeInsetsGeometry padding; 56 | EdgeInsetsGeometry margin; 57 | 58 | //支持自定义绘制 59 | DayWidgetBuilder dayWidgetBuilder; //创建日历item 60 | WeekBarItemWidgetBuilder weekBarItemWidgetBuilder; //创建顶部的weekbar 61 | 62 | /** 63 | * 监听变化 64 | */ 65 | //各种事件回调 66 | OnMonthChange monthChange; //月份切换事件 (已弃用,交给multiMonthChanges来实现) 67 | OnCalendarSelect calendarSelect; //点击选择事件 68 | OnCalendarSelect unCalendarSelect; //点击选择事件 69 | OnMultiSelectOutOfRange multiSelectOutOfRange; //多选超出指定范围 70 | OnMultiSelectOutOfSize multiSelectOutOfSize; //多选超出限制个数 71 | 72 | ObserverList monthChangeListeners = 73 | ObserverList(); //保存多个月份监听的事件 74 | ObserverList weekChangeListeners = 75 | ObserverList(); //周视图切换 76 | 77 | /** 78 | * 下面的信息不是配置的,是根据配置信息进行计算出来的 79 | */ 80 | List monthList = new List(); //月份list 81 | List weekList = new List(); //星期list 82 | PageController monthController; //月份的controller 83 | PageController weekController; //星期的controller 84 | DateModel minSelectDate; 85 | DateModel maxSelectDate; 86 | 87 | /// 首日偏移量 first day offset 88 | /// first day = (first day of month or week) + offset 89 | final int offset; 90 | 91 | CalendarConfiguration( 92 | {this.selectMode, 93 | this.minYear, 94 | this.maxYear, 95 | this.minYearMonth, 96 | this.maxYearMonth, 97 | this.nowYear, 98 | this.nowMonth, 99 | this.minSelectYear, 100 | this.minSelectMonth, 101 | this.minSelectDay, 102 | this.maxSelectYear, 103 | this.maxSelectMonth, 104 | this.maxSelectDay, 105 | this.defaultSelectedDateList, 106 | this.selectDateModel, 107 | this.maxMultiSelectCount, 108 | this.extraDataMap, 109 | this.monthList, 110 | this.weekList, 111 | this.monthController, 112 | this.weekController, 113 | this.verticalSpacing, 114 | this.itemSize, 115 | this.showMode, 116 | this.padding, 117 | this.margin, 118 | this.offset = 0}); 119 | 120 | @override 121 | String toString() { 122 | return 'CalendarConfiguration{selectMode: $selectMode, minYear: $minYear, maxYear: $maxYear, minYearMonth: $minYearMonth, maxYearMonth: $maxYearMonth, nowYear: $nowYear, nowMonth: $nowMonth, minSelectYear: $minSelectYear, minSelectMonth: $minSelectMonth, minSelectDay: $minSelectDay, maxSelectYear: $maxSelectYear, maxSelectMonth: $maxSelectMonth, maxSelectDay: $maxSelectDay, defaultSelectedDateList: $defaultSelectedDateList, maxMultiSelectCount: $maxMultiSelectCount, extraDataMap: $extraDataMap, monthList: $monthList, weekList: $weekList, monthController: $monthController, weekController: $weekController}'; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /lib/constants/constants.dart: -------------------------------------------------------------------------------- 1 | enum CalendarSelectedMode { singleSelect, multiSelect, mutltiStartToEndSelect } 2 | 3 | class CalendarConstants { 4 | //单选或者多选模式 5 | // 单选 6 | static const int MODE_SINGLE_SELECT = 1; 7 | 8 | /// 多选 9 | 10 | static const int MODE_MULTI_SELECT = 2; 11 | 12 | /// 选择开始和结束 中间的自动选择 13 | 14 | static const int MODE_MULTI_SELECT_START_TO_END = 3; 15 | 16 | //展示模式 17 | static const int MODE_SHOW_ONLY_MONTH = 1; //仅支持月视图 18 | static const int MODE_SHOW_ONLY_WEEK = 2; //仅支持星期视图 19 | static const int MODE_SHOW_WEEK_AND_MONTH = 3; //支持两种视图,先显示周视图 20 | static const int MODE_SHOW_MONTH_AND_WEEK = 4; //支持两种视图,先显示月视图 21 | 22 | /** 23 | * 一周七天 24 | */ 25 | static const List WEEK_LIST = [ 26 | "周一", 27 | "周二", 28 | "周三", 29 | "周四", 30 | "周五", 31 | "周六", 32 | "周日" 33 | ]; 34 | 35 | /** 36 | * 农历的月份 37 | */ 38 | static const List LUNAR_MONTH_TEXT = [ 39 | "春节", 40 | "二月", 41 | "三月", 42 | "四月", 43 | "五月", 44 | "六月", 45 | "七月", 46 | "八月", 47 | "九月", 48 | "十月", 49 | "冬月", 50 | "腊月", 51 | ]; 52 | 53 | /** 54 | * 农历的日期 55 | */ 56 | static const List LUNAR_DAY_TEXT = [ 57 | "初一", 58 | "初二", 59 | "初三", 60 | "初四", 61 | "初五", 62 | "初六", 63 | "初七", 64 | "初八", 65 | "初九", 66 | "初十", 67 | "十一", 68 | "十二", 69 | "十三", 70 | "十四", 71 | "十五", 72 | "十六", 73 | "十七", 74 | "十八", 75 | "十九", 76 | "二十", 77 | "廿一", 78 | "廿二", 79 | "廿三", 80 | "廿四", 81 | "廿五", 82 | "廿六", 83 | "廿七", 84 | "廿八", 85 | "廿九", 86 | "三十" 87 | ]; 88 | } 89 | -------------------------------------------------------------------------------- /lib/controller.dart: -------------------------------------------------------------------------------- 1 | import 'dart:collection'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'calendar_provider.dart'; 5 | import 'configuration.dart'; 6 | import 'constants/constants.dart'; 7 | import 'flutter_custom_calendar.dart'; 8 | import 'utils/LogUtil.dart'; 9 | import 'utils/date_util.dart'; 10 | import 'widget/default_combine_day_view.dart'; 11 | import 'widget/default_custom_day_view.dart'; 12 | import 'widget/default_week_bar.dart'; 13 | 14 | import 'model/date_model.dart'; 15 | 16 | /** 17 | * 利用controller来控制视图 18 | */ 19 | 20 | class CalendarController { 21 | static const Set EMPTY_SET = {}; 22 | static const Map EMPTY_MAP = {}; 23 | static const Duration DEFAULT_DURATION = const Duration(milliseconds: 500); 24 | 25 | CalendarConfiguration calendarConfiguration; 26 | 27 | CalendarProvider calendarProvider = CalendarProvider(); 28 | 29 | /** 30 | * 下面的信息不是配置的 31 | */ 32 | List monthList = new List(); //月份list 33 | List weekList = new List(); //星期list 34 | PageController monthController; //月份的controller 35 | PageController weekController; //星期的controller 36 | 37 | CalendarController( 38 | {CalendarSelectedMode selectMode = CalendarSelectedMode.singleSelect, 39 | int showMode = CalendarConstants.MODE_SHOW_ONLY_MONTH, 40 | int minYear = 1971, 41 | int maxYear = 2055, 42 | int minYearMonth = 1, 43 | int maxYearMonth = 12, 44 | int nowYear, 45 | int nowMonth, 46 | int minSelectYear = 1971, 47 | int minSelectMonth = 1, 48 | int minSelectDay = 1, 49 | int maxSelectYear = 2055, 50 | int maxSelectMonth = 12, 51 | int maxSelectDay = 30, 52 | Set selectedDateTimeList = EMPTY_SET, //多选模式下,默认选中的item列表 53 | DateModel selectDateModel, //单选模式下,默认选中的item 54 | int maxMultiSelectCount = 9999, 55 | Map extraDataMap = EMPTY_MAP, 56 | int offset = 0 // 首日偏移量 57 | }) { 58 | assert(offset >= 0 && offset <= 6); 59 | LogUtil.log(TAG: this.runtimeType, message: "init CalendarConfiguration"); 60 | //如果没有指定当前月份和年份,默认是当年时间 61 | if (nowYear == null) { 62 | nowYear = DateTime.now().year; 63 | } 64 | if (nowMonth == null) { 65 | nowMonth = DateTime.now().month; 66 | } 67 | calendarConfiguration = CalendarConfiguration( 68 | selectMode: selectMode, 69 | showMode: showMode, 70 | minYear: minYear, 71 | maxYear: maxYear, 72 | maxYearMonth: maxYearMonth, 73 | nowYear: nowYear, 74 | nowMonth: nowMonth, 75 | minSelectYear: minSelectYear, 76 | minSelectMonth: minSelectMonth, 77 | minYearMonth: minYearMonth, 78 | minSelectDay: minSelectDay, 79 | maxSelectYear: maxSelectYear, 80 | maxSelectMonth: maxSelectMonth, 81 | extraDataMap: extraDataMap, 82 | maxSelectDay: maxSelectDay, 83 | maxMultiSelectCount: maxMultiSelectCount, 84 | selectDateModel: selectDateModel, 85 | offset: offset); 86 | 87 | calendarConfiguration.defaultSelectedDateList = new HashSet(); 88 | calendarConfiguration.defaultSelectedDateList 89 | .addAll(selectedDateTimeList.map((dateTime) { 90 | return DateModel.fromDateTime(dateTime); 91 | }).toSet()); 92 | //将默认选中的数据,放到provider中 93 | calendarProvider.selectDateModel = selectDateModel; 94 | calendarProvider.selectedDateList = 95 | calendarConfiguration.defaultSelectedDateList; 96 | calendarConfiguration.minSelectDate = DateModel.fromDateTime(DateTime( 97 | calendarConfiguration.minSelectYear, 98 | calendarConfiguration.minSelectMonth, 99 | calendarConfiguration.minSelectDay)); 100 | calendarConfiguration.maxSelectDate = DateModel.fromDateTime(DateTime( 101 | calendarConfiguration.maxSelectYear, 102 | calendarConfiguration.maxSelectMonth, 103 | calendarConfiguration.maxSelectDay)); 104 | 105 | LogUtil.log( 106 | TAG: this.runtimeType, 107 | message: "start:${DateModel.fromDateTime(DateTime( 108 | minYear, 109 | minYearMonth, 110 | ))},end:${DateModel.fromDateTime(DateTime( 111 | maxYear, 112 | maxYearMonth, 113 | ))}"); 114 | _weekAndMonthViewChange(showMode); 115 | } 116 | void _weekAndMonthViewChange( 117 | int showMode, 118 | ) { 119 | int minYear = calendarConfiguration.minYear; 120 | int maxYear = calendarConfiguration.maxYear; 121 | int minYearMonth = calendarConfiguration.minYearMonth; 122 | int maxYearMonth = calendarConfiguration.maxYearMonth; 123 | int nowYear = calendarConfiguration.nowYear; 124 | int nowMonth = calendarConfiguration.nowMonth; 125 | int nowDay = calendarConfiguration.selectDateModel?.day ?? -1; 126 | 127 | if (showMode != CalendarConstants.MODE_SHOW_ONLY_WEEK) { 128 | //初始化pageController,initialPage默认是当前时间对于的页面 129 | int initialPage = 0; 130 | int nowMonthIndex = 0; 131 | monthList.clear(); 132 | for (int i = minYear; i <= maxYear; i++) { 133 | for (int j = 1; j <= 12; j++) { 134 | if (i == minYear && j < minYearMonth) { 135 | continue; 136 | } 137 | if (i == maxYear && j > maxYearMonth) { 138 | continue; 139 | } 140 | DateModel dateModel = new DateModel(); 141 | dateModel.year = i; 142 | dateModel.month = j; 143 | 144 | if (i == nowYear && j == nowMonth) { 145 | initialPage = nowMonthIndex; 146 | } 147 | monthList.add(dateModel); 148 | nowMonthIndex++; 149 | } 150 | } 151 | this.monthController = 152 | new PageController(initialPage: initialPage, keepPage: true); 153 | 154 | LogUtil.log( 155 | TAG: this.runtimeType, 156 | message: 157 | "初始化月份视图的信息:一共有${monthList.length}个月,initialPage为$nowMonthIndex"); 158 | } 159 | 160 | if (showMode != CalendarConstants.MODE_SHOW_ONLY_MONTH) { 161 | //计算一共多少周 162 | //计算方法:第一天是周几,最后一天是周几,中间的天数/7后加上2就是结果了 163 | int initialWeekPage = 0; 164 | weekList.clear(); 165 | //如果没有配置当前时间,设置成当前的时间 166 | if (nowYear == -1 || nowMonth == -1) { 167 | nowYear = DateTime.now().year; 168 | nowMonth = DateTime.now().month; 169 | } 170 | int nowDay = 15; // 默认月中 171 | // 如果设置了 默认选择的时间 就取默认选择的时间天数,否则为当前时间 172 | DateModel currentModel = calendarProvider.selectDateModel ?? calendarProvider.selectedDateList?.toList()[0] ?? DateModel.fromDateTime(DateTime.now()); 173 | if(currentModel != null){ 174 | nowDay = currentModel.day; 175 | } 176 | DateTime nowTime = new DateTime(nowYear, nowMonth, nowDay); 177 | DateTime firstDayOfMonth = DateTime(minYear, minYearMonth, 1); 178 | //计算第一个星期的第一天的日期 179 | DateTime firstWeekDate = firstDayOfMonth.add(Duration(days: -(firstDayOfMonth.weekday - 1))); 180 | 181 | DateTime lastDay = DateTime(maxYear, maxYearMonth, 182 | DateUtil.getMonthDaysCount(maxYear, maxYearMonth)); 183 | int temp = -1; 184 | for (DateTime dateTime = firstWeekDate; 185 | !dateTime.isAfter(lastDay); 186 | dateTime = dateTime.add(Duration(days: 7))) { 187 | DateModel dateModel = DateModel.fromDateTime(dateTime); 188 | weekList.add(dateModel); 189 | // print("nowTime.isBefore(dateTime)"); 190 | // print("$nowTime,,,,$dateTime"); 191 | 192 | if (nowTime.isAfter(dateTime)) { 193 | temp++; 194 | } 195 | } 196 | initialWeekPage = temp; 197 | LogUtil.log( 198 | TAG: this.runtimeType, 199 | message: 200 | "初始化星期视图的信息:一共有${weekList.length}个星期,initialPage为$initialWeekPage"); 201 | this.weekController = new PageController(initialPage: initialWeekPage); 202 | } 203 | calendarConfiguration.monthList = monthList; 204 | calendarConfiguration.weekList = weekList; 205 | calendarConfiguration.monthController = monthController; 206 | calendarConfiguration.weekController = weekController; 207 | calendarProvider.weekAndMonthViewChange(showMode); 208 | } 209 | 210 | void weekAndMonthViewChange( 211 | int showMode, 212 | ) { 213 | calendarProvider.expandStatus.value = 214 | showMode == CalendarConstants.MODE_SHOW_ONLY_WEEK ? true : false; 215 | } 216 | 217 | //周视图切换 218 | void addWeekChangeListener(OnWeekChange listener) { 219 | this.calendarConfiguration.weekChangeListeners.add(listener); 220 | } 221 | 222 | //月份切换监听 223 | void addMonthChangeListener(OnMonthChange listener) { 224 | // this.calendarConfiguration.monthChange = listener; 225 | this.calendarConfiguration.monthChangeListeners.add(listener); 226 | } 227 | 228 | //点击选择监听 229 | void addOnCalendarSelectListener(OnCalendarSelect listener) { 230 | this.calendarConfiguration.calendarSelect = listener; 231 | } 232 | 233 | //点击选择取消监听 234 | void addOnCalendarUnSelectListener(OnCalendarUnSelect listener) { 235 | this.calendarConfiguration.unCalendarSelect = listener; 236 | } 237 | 238 | //多选超出指定范围 239 | void addOnMultiSelectOutOfRangeListener(OnMultiSelectOutOfRange listener) { 240 | this.calendarConfiguration.multiSelectOutOfRange = listener; 241 | } 242 | 243 | //多选超出限制个数 244 | void addOnMultiSelectOutOfSizeListener(OnMultiSelectOutOfSize listener) { 245 | this.calendarConfiguration.multiSelectOutOfSize = listener; 246 | } 247 | 248 | //切换展开状态 249 | void toggleExpandStatus() { 250 | calendarProvider.expandStatus.value = !calendarProvider.expandStatus.value; 251 | LogUtil.log( 252 | TAG: this.runtimeType, 253 | message: "toggleExpandStatus:${calendarProvider.expandStatus.value}"); 254 | } 255 | 256 | //监听展开变化 257 | void addExpandChangeListener(ValueChanged expandChange) { 258 | calendarProvider.expandStatus.addListener(() { 259 | expandChange(calendarProvider.expandStatus.value); 260 | }); 261 | } 262 | 263 | //可以动态修改extraDataMap 264 | void changeExtraData(Map newMap) { 265 | this.calendarConfiguration.extraDataMap = newMap; 266 | this.calendarProvider.generation.value++; 267 | } 268 | 269 | //可以动态修改默认选中的item。 270 | void changeDefaultSelectedDateList(Set defaultSelectedDateList) { 271 | this.calendarConfiguration.defaultSelectedDateList = 272 | defaultSelectedDateList; 273 | this.calendarProvider.generation.value++; 274 | } 275 | 276 | //可以动态修改默认选中的item 277 | void changeDefaultSelectedDateModel(DateModel dateModel) { 278 | this.calendarProvider.selectDateModel = dateModel; 279 | this.calendarProvider.generation.value++; 280 | } 281 | 282 | /** 283 | * 月份或者星期的上一页 284 | */ 285 | Future previousPage() async { 286 | if (calendarProvider.expandStatus.value == true) { 287 | //月视图 288 | int currentIndex = calendarProvider.calendarConfiguration.monthController.page.toInt(); 289 | if (currentIndex == 0) { 290 | return false; 291 | } else { 292 | calendarProvider.calendarConfiguration.monthController.previousPage(duration: DEFAULT_DURATION, curve: Curves.ease); 293 | calendarProvider.calendarConfiguration.monthChangeListeners.forEach((listener) { 294 | listener(monthList[currentIndex - 1].year, monthList[currentIndex - 1].month); 295 | }); 296 | DateModel temp = new DateModel(); 297 | temp.year = monthList[currentIndex].year; 298 | temp.month = monthList[currentIndex].month; 299 | temp.day = monthList[currentIndex].day + 14; 300 | print('298 周视图的变化: $temp'); 301 | calendarProvider.lastClickDateModel = temp; 302 | return true; 303 | } 304 | } else { 305 | //周视图 306 | int currentIndex = calendarProvider.calendarConfiguration.weekController.page.toInt(); 307 | if (currentIndex == 0) { 308 | return false; 309 | } else { 310 | calendarProvider.calendarConfiguration.weekController.previousPage(duration: DEFAULT_DURATION, curve: Curves.ease); 311 | return true; 312 | } 313 | } 314 | } 315 | 316 | /** 317 | * 月份或者星期的下一页 318 | * true:成功 319 | * false:是最后一页 320 | */ 321 | Future nextPage() async { 322 | if (calendarProvider.expandStatus.value == true) { 323 | //月视图 324 | int currentIndex = 325 | calendarProvider.calendarConfiguration.monthController.page.toInt(); 326 | if (monthList.length - 1 == currentIndex) { 327 | return false; 328 | } else { 329 | calendarProvider.calendarConfiguration.monthController 330 | .nextPage(duration: DEFAULT_DURATION, curve: Curves.ease); 331 | calendarProvider.calendarConfiguration.monthChangeListeners 332 | .forEach((listener) { 333 | listener(monthList[currentIndex + 1].year, 334 | monthList[currentIndex + 1].month); 335 | }); 336 | 337 | DateModel temp = new DateModel(); 338 | temp.year = monthList[currentIndex].year; 339 | temp.month = monthList[currentIndex].month; 340 | temp.day = monthList[currentIndex].day + 14; 341 | print('341 周视图的变化: $temp'); 342 | calendarProvider.lastClickDateModel = temp; 343 | return true; 344 | } 345 | } else { 346 | //周视图 347 | int currentIndex = 348 | calendarProvider.calendarConfiguration.weekController.page.toInt(); 349 | if (weekList.length - 1 == currentIndex) { 350 | return false; 351 | } else { 352 | calendarProvider.calendarConfiguration.weekController 353 | .nextPage(duration: DEFAULT_DURATION, curve: Curves.ease); 354 | return true; 355 | } 356 | } 357 | } 358 | 359 | //跳转到指定日期 360 | void moveToCalendar(int year, int month, int day, 361 | {bool needAnimation = false, 362 | Duration duration = const Duration(milliseconds: 500), 363 | Curve curve = Curves.ease}) { 364 | if (calendarProvider.expandStatus.value == true) { 365 | DateModel dateModel = DateModel.fromDateTime(DateTime(year, month, 1)); 366 | //计算目标索引 367 | int targetPage = monthList.indexOf(dateModel); 368 | if (targetPage == -1) { 369 | return; 370 | } 371 | if (calendarProvider.calendarConfiguration.monthController.hasClients == 372 | false) { 373 | return; 374 | } 375 | if (needAnimation) { 376 | calendarProvider.calendarConfiguration.monthController 377 | .animateToPage(targetPage, duration: duration, curve: curve); 378 | } else { 379 | calendarProvider.calendarConfiguration.monthController 380 | .jumpToPage(targetPage); 381 | } 382 | } else { 383 | DateModel dateModel = DateModel.fromDateTime(DateTime(year, month, 1)); 384 | //计算目标索引 385 | int targetPage = 0; 386 | for (int i = 0; i < weekList.length - 1; i++) { 387 | DateModel first = weekList[i]; 388 | DateModel next = weekList[i + 1]; 389 | if (!first.isAfter(dateModel) && next.isAfter(dateModel)) { 390 | targetPage = i; 391 | return; 392 | } 393 | } 394 | if (calendarProvider.calendarConfiguration.weekController.hasClients == 395 | false) { 396 | return; 397 | } 398 | if (needAnimation) { 399 | calendarProvider.calendarConfiguration.weekController 400 | .animateToPage(targetPage, duration: duration, curve: curve); 401 | } else { 402 | calendarProvider.calendarConfiguration.weekController 403 | .jumpToPage(targetPage); 404 | } 405 | } 406 | } 407 | 408 | //切换到下一年 409 | void moveToNextYear( 410 | {bool needAnimation = false, 411 | Duration duration = const Duration(milliseconds: 500), 412 | Curve curve = Curves.ease}) { 413 | DateTime targetDateTime = monthList[calendarProvider 414 | .calendarConfiguration.monthController.page 415 | .toInt() + 416 | 12] 417 | .getDateTime(); 418 | moveToCalendar( 419 | targetDateTime.year, targetDateTime.month, targetDateTime.day, 420 | needAnimation: needAnimation, duration: duration, curve: curve); 421 | } 422 | 423 | //切换到上一年 424 | void moveToPreviousYear( 425 | {bool needAnimation = false, 426 | Duration duration = const Duration(milliseconds: 500), 427 | Curve curve = Curves.ease}) { 428 | DateTime targetDateTime = monthList[calendarProvider 429 | .calendarConfiguration.monthController.page 430 | .toInt() - 431 | 12] 432 | .getDateTime(); 433 | moveToCalendar( 434 | targetDateTime.year, targetDateTime.month, targetDateTime.day, 435 | needAnimation: needAnimation, duration: duration, curve: curve); 436 | } 437 | 438 | //切换到下一个月份, 439 | void moveToNextMonth( 440 | {bool needAnimation = false, 441 | Duration duration = const Duration(milliseconds: 500), 442 | Curve curve = Curves.ease}) { 443 | // 如果当前显示的是周视图的话,需要计算出第一个月的index后,调用weekController 444 | if (calendarProvider.expandStatus.value == false) { 445 | int currentMonth = weekList[calendarProvider 446 | .calendarConfiguration.weekController.page 447 | .toInt()] 448 | .month; 449 | for (int i = calendarProvider.calendarConfiguration.weekController.page 450 | .toInt(); 451 | i < weekList.length; 452 | i++) { 453 | if (weekList[i].month != currentMonth) { 454 | calendarProvider.calendarConfiguration.weekController.jumpToPage(i); 455 | break; 456 | } 457 | } 458 | return; 459 | } 460 | 461 | if ((calendarProvider.calendarConfiguration.monthController.page.toInt() + 462 | 1) >= 463 | monthList.length) { 464 | LogUtil.log(TAG: this.runtimeType, message: "moveToNextMonth:当前是最后一个月份"); 465 | return; 466 | } 467 | DateTime targetDateTime = monthList[calendarProvider 468 | .calendarConfiguration.monthController.page 469 | .toInt() + 470 | 1] 471 | .getDateTime(); 472 | moveToCalendar( 473 | targetDateTime.year, targetDateTime.month, targetDateTime.day, 474 | needAnimation: needAnimation, duration: duration, curve: curve); 475 | } 476 | 477 | //切换到上一个月份 478 | void moveToPreviousMonth( 479 | {bool needAnimation = false, 480 | Duration duration = const Duration(milliseconds: 500), 481 | Curve curve = Curves.ease}) { 482 | // 如果当前显示的是周视图的话,需要计算出第一个月的index后,调用weekController 483 | if (calendarProvider.expandStatus.value == false) { 484 | int currentMonth = weekList[weekController.page.toInt()].month; 485 | for (int i = calendarProvider.calendarConfiguration.weekController.page 486 | .toInt(); 487 | i >= 0; 488 | i--) { 489 | if (weekList[i].month != currentMonth && 490 | weekList[i].isAfter(DateModel.fromDateTime(DateTime( 491 | calendarConfiguration.minYear, 492 | calendarConfiguration.minYearMonth)))) { 493 | calendarProvider.calendarConfiguration.weekController.jumpToPage(i); 494 | break; 495 | } 496 | } 497 | return; 498 | } 499 | 500 | if ((calendarProvider.calendarConfiguration.monthController.page.toInt()) == 501 | 0) { 502 | LogUtil.log( 503 | TAG: this.runtimeType, message: "moveToPreviousMonth:当前是第一个月份"); 504 | return; 505 | } 506 | DateTime targetDateTime = monthList[calendarProvider 507 | .calendarConfiguration.monthController.page 508 | .toInt() - 509 | 1] 510 | .getDateTime(); 511 | moveToCalendar( 512 | targetDateTime.year, targetDateTime.month, targetDateTime.day, 513 | needAnimation: needAnimation, duration: duration, curve: curve); 514 | } 515 | 516 | // 获取当前的月份 517 | DateModel getCurrentMonth() { 518 | return monthList[monthController.page.toInt()]; 519 | } 520 | 521 | //获取被选中的日期,多选 522 | Set getMultiSelectCalendar() { 523 | return calendarProvider.selectedDateList; 524 | } 525 | 526 | //获取被选中的日期,单选 527 | DateModel getSingleSelectCalendar() { 528 | return calendarProvider.selectDateModel; 529 | } 530 | 531 | //清除数据 532 | void clearData() { 533 | monthList.clear(); 534 | weekList.clear(); 535 | calendarProvider.clearData(); 536 | calendarConfiguration.weekChangeListeners = null; 537 | calendarConfiguration.monthChangeListeners = null; 538 | } 539 | } 540 | 541 | /** 542 | * 默认的weekBar 543 | */ 544 | Widget defaultWeekBarWidget() { 545 | return const DefaultWeekBar(); 546 | } 547 | 548 | /** 549 | * 使用canvas绘制item 550 | */ 551 | Widget defaultCustomDayWidget(DateModel dateModel) { 552 | return DefaultCustomDayWidget( 553 | dateModel, 554 | ); 555 | } 556 | 557 | /** 558 | * 使用组合widget的方式构造item 559 | */ 560 | Widget defaultCombineDayWidget(DateModel dateModel) { 561 | return new DefaultCombineDayWidget( 562 | dateModel, 563 | ); 564 | } 565 | 566 | /** 567 | * 判断是否在范围内,不在范围内的话,可以置灰 568 | */ 569 | bool defaultInRange(DateModel dateModel) { 570 | return true; 571 | } 572 | 573 | /** 574 | * 周视图切换 575 | */ 576 | typedef void OnWeekChange(int year, int month); 577 | 578 | /** 579 | * 月份切换事件 580 | */ 581 | typedef void OnMonthChange(int year, int month); 582 | 583 | /** 584 | * 日期选择事件 585 | */ 586 | typedef void OnCalendarSelect(DateModel dateModel); 587 | /** 588 | * 取消选择 589 | */ 590 | typedef void OnCalendarUnSelect(DateModel dateModel); 591 | 592 | /** 593 | * 多选超出指定范围 594 | */ 595 | typedef void OnMultiSelectOutOfRange(); 596 | 597 | /** 598 | * 多选超出限制个数 599 | */ 600 | typedef void OnMultiSelectOutOfSize(); 601 | 602 | /** 603 | * 可以创建自定义样式的item 604 | */ 605 | typedef Widget DayWidgetBuilder(DateModel dateModel); 606 | 607 | /** 608 | * 是否可以点击,外部来进行判断,默认都可以点击 609 | */ 610 | typedef bool CanClick(DateModel dateModel); 611 | 612 | /** 613 | * 可以自定义绘制每个Item,这种扩展性好一点,以后可以提供给外部进行自定义绘制 614 | */ 615 | typedef void DrawDayWidget(DateModel dateModel, Canvas canvas, Size size); 616 | 617 | /** 618 | * 自定义顶部weekBar 619 | */ 620 | typedef Widget WeekBarItemWidgetBuilder(); 621 | -------------------------------------------------------------------------------- /lib/flutter_custom_calendar.dart: -------------------------------------------------------------------------------- 1 | library flutter_custom_calendar; 2 | 3 | export 'controller.dart'; 4 | export 'widget/calendar_view.dart'; 5 | export 'widget/base_day_view.dart'; 6 | export 'widget/base_week_bar.dart'; 7 | export 'constants/constants.dart'; 8 | export 'model/date_model.dart'; 9 | export 'widget/default_combine_day_view.dart'; 10 | export 'widget/default_custom_day_view.dart'; 11 | export 'widget/default_week_bar.dart'; 12 | export 'configuration.dart'; 13 | 14 | export 'configuration.dart'; 15 | export 'calendar_provider.dart'; 16 | export 'constants/constants.dart'; 17 | export 'widget/base_day_view.dart'; 18 | -------------------------------------------------------------------------------- /lib/model/date_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_custom_calendar/utils/date_util.dart'; 2 | import 'package:flutter_custom_calendar/utils/lunar_util.dart'; 3 | 4 | /** 5 | * 日期的实体类 6 | */ 7 | class DateModel { 8 | int year; 9 | int month; 10 | int day = 1; 11 | 12 | List lunar = List(3); 13 | 14 | // List get lunar { 15 | // if (lunar?.isNotEmpty == false) { 16 | // return lunar; 17 | // } 18 | //// return LunarUtil.solarToLunar(year, month, day); 19 | // } 20 | 21 | //农历字符串 22 | String get lunarString { 23 | if (solarTerm.isNotEmpty) { 24 | return solarTerm; 25 | } else if (gregorianFestival.isNotEmpty) { 26 | return gregorianFestival; 27 | } else if (traditionFestival.isNotEmpty) { 28 | return traditionFestival; 29 | } else { 30 | return LunarUtil.numToChinese(lunar[1], lunar[2], lunar[3]); 31 | } 32 | } 33 | 34 | //24节气 35 | String get solarTerm => LunarUtil.getSolarTerm(year, month, day); 36 | 37 | //公历节日 38 | String get gregorianFestival { 39 | String result = LunarUtil.gregorianFestival(month, day); 40 | if (result?.isNotEmpty == true) { 41 | return result; 42 | } 43 | return LunarUtil.getSpecialFestival(year, month, day); 44 | } 45 | 46 | //传统农历节日 47 | String get traditionFestival => 48 | LunarUtil.getTraditionFestival(lunarYear, lunarMonth, lunarDay); 49 | 50 | bool isCurrentMonth; //是否是当前月份 51 | 52 | Object extraData; //自定义的额外数据 53 | 54 | bool isInRange = false; //是否在范围内,比如可以实现在某个范围外,设置置灰的功能 55 | bool isSelected; //是否被选中,用来实现一些标记或者选择功能 56 | bool isCanClick = 57 | true; //todo:是否可点击:设置范围外的日历不可点击,或者可以通过自定义拦截点击事件来设置true或者false 58 | //是否是周末 59 | bool get isWeekend => DateUtil.isWeekend(getDateTime()); 60 | 61 | //是否是闰年 62 | bool get isLeapYear => DateUtil.isLeapYear(year); 63 | 64 | //是否是今天 65 | bool get isCurrentDay => DateUtil.isCurrentDay(year, month, day); 66 | 67 | int get lunarYear => lunar[0]; 68 | 69 | int get lunarMonth => lunar[1]; 70 | 71 | int get lunarDay => lunar[2]; 72 | 73 | @override 74 | String toString() { 75 | return 'DateModel{year: $year, month: $month, day: $day}'; 76 | } //如果是闰月,则返回闰月 77 | 78 | //转化成DateTime格式 79 | DateTime getDateTime() { 80 | return new DateTime(year, month, day); 81 | } 82 | 83 | //根据DateTime创建对应的model,并初始化农历和传统节日等信息 84 | static DateModel fromDateTime(DateTime dateTime) { 85 | DateModel dateModel = new DateModel() 86 | ..year = dateTime.year 87 | ..month = dateTime.month 88 | ..day = dateTime.day; 89 | List lunar = 90 | LunarUtil.solarToLunar(dateModel.year, dateModel.month, dateModel.day); 91 | dateModel.lunar = lunar; 92 | 93 | // 将数据的初始化放到各个get方法里面进行操作,类似懒加载,不然很浪费 94 | // LunarUtil.setupLunarCalendar(dateModel); 95 | return dateModel; 96 | } 97 | 98 | @override 99 | bool operator ==(Object other) => 100 | identical(this, other) || 101 | other is DateModel && 102 | runtimeType == other.runtimeType && 103 | year == other.year && 104 | month == other.month && 105 | day == other.day; 106 | 107 | @override 108 | int get hashCode => year.hashCode ^ month.hashCode ^ day.hashCode; 109 | 110 | //是否是同一天 111 | bool isSameWith(DateModel dateModel) { 112 | return year == dateModel.year && 113 | month == dateModel.month && 114 | day == dateModel.day; 115 | } 116 | 117 | //是否在某天之后 118 | bool isAfter(DateModel dateModel) { 119 | return this.getDateTime().isAfter(dateModel.getDateTime()); 120 | } 121 | 122 | //是否在某天之前 123 | bool isBefore(DateModel dateModel) { 124 | return this.getDateTime().isBefore(dateModel.getDateTime()); 125 | } 126 | } 127 | 128 | class X { 129 | var _y; 130 | 131 | get y => null == _y ? initY() : _y; 132 | 133 | initY() { 134 | //do some computation 135 | _y = "result"; 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /lib/style/style.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | //顶部7天的文案 4 | TextStyle topWeekTextStyle = new TextStyle(fontSize: 12); 5 | 6 | //当前月份的日期的文字 7 | TextStyle currentMonthTextStyle = 8 | new TextStyle(color: Colors.black, fontSize: 16); 9 | 10 | //下一个月或者上一个月的日期的文字 11 | TextStyle preOrNextMonthTextStyle = 12 | new TextStyle(color: Colors.grey, fontSize: 18); 13 | 14 | //农历的字体 15 | TextStyle lunarTextStyle = new TextStyle(color: Colors.grey, fontSize: 12); 16 | 17 | //不是当前月份的日期的文字 18 | TextStyle notCurrentMonthTextStyle = 19 | new TextStyle(color: Colors.grey, fontSize: 16); 20 | 21 | TextStyle currentDayTextStyle = new TextStyle(color: Colors.red, fontSize: 16); 22 | -------------------------------------------------------------------------------- /lib/utils/LogUtil.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/foundation.dart'; 3 | 4 | /** 5 | * 打印日志,外部可以控制日历信息的打印显示,方便调试查错 6 | */ 7 | class LogUtil { 8 | static bool _enableLog = false; //是否可以打印日志 9 | 10 | static set enableLog(bool value) { 11 | _enableLog = value; 12 | } 13 | 14 | /** 15 | * TAG:类名 16 | * message:一般就方法名+自定义信息吧 17 | */ 18 | static void log({@required dynamic TAG, String message = ""}) { 19 | if (_enableLog && kDebugMode) { 20 | debugPrint("flutter_custom_calendar------$TAG------>$message"); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/utils/date_util.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter_custom_calendar/flutter_custom_calendar.dart'; 3 | import 'LogUtil.dart'; 4 | 5 | /** 6 | * 工具类 7 | */ 8 | class DateUtil { 9 | /** 10 | * 判断一个日期是否是周末,即周六日 11 | */ 12 | static bool isWeekend(DateTime dateTime) { 13 | return dateTime.weekday == DateTime.saturday || 14 | dateTime.weekday == DateTime.sunday; 15 | } 16 | 17 | /** 18 | * 获取某年的天数 19 | */ 20 | static int getYearDaysCount(int year) { 21 | if (isLeapYear(year)) { 22 | return 366; 23 | } 24 | return 365; 25 | } 26 | 27 | /** 28 | * 获取某月的天数 29 | * 30 | * @param year 年 31 | * @param month 月 32 | * @return 某月的天数 33 | */ 34 | static int getMonthDaysCount(int year, int month) { 35 | int count = 0; 36 | //判断大月份 37 | if (month == 1 || 38 | month == 3 || 39 | month == 5 || 40 | month == 7 || 41 | month == 8 || 42 | month == 10 || 43 | month == 12) { 44 | count = 31; 45 | } 46 | 47 | //判断小月 48 | if (month == 4 || month == 6 || month == 9 || month == 11) { 49 | count = 30; 50 | } 51 | 52 | //判断平年与闰年 53 | if (month == 2) { 54 | if (isLeapYear(year)) { 55 | count = 29; 56 | } else { 57 | count = 28; 58 | } 59 | } 60 | return count; 61 | } 62 | 63 | /** 64 | * 是否是今天 65 | */ 66 | static bool isCurrentDay(int year, int month, int day) { 67 | DateTime now = DateTime.now(); 68 | return now.year == year && now.month == month && now.day == day; 69 | } 70 | 71 | /** 72 | * 是否是闰年 73 | */ 74 | static bool isLeapYear(int year) { 75 | return ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0); 76 | } 77 | 78 | /** 79 | * 本月的第几周 80 | */ 81 | static int getIndexWeekInMonth(DateTime dateTime) { 82 | DateTime firstdayInMonth = new DateTime(dateTime.year, dateTime.month, 1); 83 | Duration duration = dateTime.difference(firstdayInMonth); 84 | return (duration.inDays / 7).toInt() + 1; 85 | } 86 | 87 | /** 88 | * 本周的第几天 89 | */ 90 | static int getIndexDayInWeek(DateTime dateTime) { 91 | DateTime firstdayInMonth = new DateTime( 92 | dateTime.year, 93 | dateTime.month, 94 | ); 95 | Duration duration = dateTime.difference(firstdayInMonth); 96 | return (duration.inDays / 7).toInt() + 1; 97 | } 98 | 99 | /** 100 | * 本月第一天,是那一周的第几天,从1开始 101 | * @return 获取日期所在月视图对应的起始偏移量 the start diff with MonthView 102 | */ 103 | static int getIndexOfFirstDayInMonth(DateTime dateTime, {int offset = 0}) { 104 | DateTime firstDayOfMonth = new DateTime(dateTime.year, dateTime.month, 1); 105 | 106 | int week = firstDayOfMonth.weekday + offset; 107 | 108 | return week; 109 | } 110 | 111 | static List initCalendarForMonthView( 112 | int year, int month, DateTime currentDate, int weekStart, 113 | {DateModel minSelectDate, 114 | DateModel maxSelectDate, 115 | Map extraDataMap, 116 | int offset = 0}) { 117 | print('initCalendarForMonthView start'); 118 | weekStart = DateTime.monday; 119 | //获取月视图真实偏移量 120 | int mPreDiff = 121 | getIndexOfFirstDayInMonth(new DateTime(year, month), offset: offset); 122 | //获取该月的天数 123 | int monthDayCount = getMonthDaysCount(year, month); 124 | 125 | LogUtil.log( 126 | TAG: "DateUtil", 127 | message: 128 | "initCalendarForMonthView:$year年$month月,有$monthDayCount天,第一天的index为${mPreDiff}"); 129 | 130 | List result = new List(); 131 | 132 | int size = 42; 133 | 134 | DateTime firstDayOfMonth = new DateTime(year, month, 1); 135 | DateTime lastDayOfMonth = new DateTime(year, month, monthDayCount); 136 | 137 | for (int i = 0; i < size; i++) { 138 | DateTime temp; 139 | DateModel dateModel; 140 | if (i < mPreDiff - 1) { 141 | if (i < ((mPreDiff / 7).ceil() - 1) * 7) { 142 | size++; 143 | continue; 144 | } 145 | //这个上一月的几天 146 | temp = firstDayOfMonth.subtract(Duration(days: mPreDiff - i - 1)); 147 | 148 | dateModel = DateModel.fromDateTime(temp); 149 | dateModel.isCurrentMonth = false; 150 | } else if (i >= monthDayCount + (mPreDiff - 1)) { 151 | //这是下一月的几天 152 | temp = lastDayOfMonth 153 | .add(Duration(days: i - mPreDiff - monthDayCount + 2)); 154 | dateModel = DateModel.fromDateTime(temp); 155 | dateModel.isCurrentMonth = false; 156 | } else { 157 | //这个月的 158 | temp = new DateTime(year, month, i - mPreDiff + 2); 159 | dateModel = DateModel.fromDateTime(temp); 160 | dateModel.isCurrentMonth = true; 161 | } 162 | 163 | //判断是否在范围内 164 | if (dateModel.getDateTime().isAfter(minSelectDate.getDateTime()) && 165 | dateModel.getDateTime().isBefore(maxSelectDate.getDateTime())) { 166 | dateModel.isInRange = true; 167 | } else { 168 | dateModel.isInRange = false; 169 | } 170 | //将自定义额外的数据,存储到相应的model中 171 | if (extraDataMap?.isNotEmpty == true) { 172 | if (extraDataMap.containsKey(dateModel)) { 173 | dateModel.extraData = extraDataMap[dateModel]; 174 | } else { 175 | dateModel.extraData = null; 176 | } 177 | } else { 178 | dateModel.extraData = null; 179 | } 180 | 181 | result.add(dateModel); 182 | } 183 | 184 | print('initCalendarForMonthView end'); 185 | 186 | return result; 187 | } 188 | 189 | /** 190 | * 月的行数 191 | */ 192 | static int getMonthViewLineCount(int year, int month, int offset) { 193 | DateTime firstDayOfMonth = new DateTime(year, month, 1); 194 | int monthDayCount = getMonthDaysCount(year, month); 195 | 196 | int preIndex = (firstDayOfMonth.weekday - 1 + offset) % 7; 197 | int lineCount = ((preIndex + monthDayCount) / 7).ceil(); 198 | LogUtil.log( 199 | TAG: "DateUtil", 200 | message: "getMonthViewLineCount:$year年$month月:有$lineCount行"); 201 | 202 | return lineCount; 203 | } 204 | 205 | /** 206 | * 获取本周的7个item 207 | */ 208 | static List initCalendarForWeekView( 209 | int year, int month, DateTime currentDate, int weekStart, 210 | {DateModel minSelectDate, 211 | DateModel maxSelectDate, 212 | Map extraDataMap, 213 | int offset = 0}) { 214 | List items = List(); 215 | 216 | int weekDay = currentDate.weekday + offset; 217 | 218 | //计算本周的第一天 219 | DateTime firstDayOfWeek = currentDate.add(Duration(days: -weekDay)); 220 | 221 | for (int i = 1; i <= 7; i++) { 222 | DateModel dateModel = 223 | DateModel.fromDateTime(firstDayOfWeek.add(Duration(days: i))); 224 | 225 | //判断是否在范围内 226 | if (dateModel.getDateTime().isAfter(minSelectDate.getDateTime()) && 227 | dateModel.getDateTime().isBefore(maxSelectDate.getDateTime())) { 228 | dateModel.isInRange = true; 229 | } else { 230 | dateModel.isInRange = false; 231 | } 232 | if (month == dateModel.month) { 233 | dateModel.isCurrentMonth = true; 234 | } else { 235 | dateModel.isCurrentMonth = false; 236 | } 237 | 238 | //将自定义额外的数据,存储到相应的model中 239 | if (extraDataMap?.isNotEmpty == true) { 240 | if (extraDataMap.containsKey(dateModel)) { 241 | dateModel.extraData = extraDataMap[dateModel]; 242 | } 243 | } 244 | 245 | items.add(dateModel); 246 | } 247 | return items; 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /lib/utils/math_util.dart: -------------------------------------------------------------------------------- 1 | class Math { 2 | static double abs(double num) { 3 | if (num < 0) { 4 | return -num; 5 | } else { 6 | return num; 7 | } 8 | } 9 | } 10 | 11 | class System { 12 | //todo:后面再检查这个方法是否正确 13 | // src:源数组; 14 | // srcPos:源数组要复制的起始位置; 15 | // dest:目的数组; 16 | // destPos:目的数组放置的起始位置; 17 | // length:复制的长度。 18 | static void arraycopy(List src, int srcPos, List dest, 19 | int destPos, int length) { 20 | List.copyRange(dest, destPos, src, srcPos, srcPos+length); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/widget/base_day_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_custom_calendar/model/date_model.dart'; 3 | import 'package:flutter_custom_calendar/flutter_custom_calendar.dart'; 4 | 5 | import '../controller.dart'; 6 | 7 | /** 8 | * 通过canvas自定义item,只需实现相关的方法就可以 9 | */ 10 | abstract class BaseCustomDayWidget extends StatelessWidget { 11 | final DateModel dateModel; 12 | 13 | const BaseCustomDayWidget( 14 | this.dateModel, 15 | ); 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return Container( 20 | child: new CustomPaint( 21 | painter: 22 | //根据isSelected标志获取对应的item 23 | dateModel.isSelected 24 | ? new CustomDayWidgetPainter(dateModel, 25 | drawDayWidget: drawSelected) 26 | : new CustomDayWidgetPainter(dateModel, 27 | drawDayWidget: drawNormal), 28 | ), 29 | ); 30 | } 31 | 32 | void drawNormal(DateModel dateModel, Canvas canvas, Size size); 33 | 34 | void drawSelected(DateModel dateModel, Canvas canvas, Size size); 35 | } 36 | 37 | class CustomDayWidgetPainter extends CustomPainter { 38 | DateModel dateModel; 39 | 40 | DrawDayWidget drawDayWidget; //普通样式是必须的 41 | 42 | CustomDayWidgetPainter(this.dateModel, {this.drawDayWidget}); 43 | 44 | Paint textPaint; 45 | 46 | @override 47 | void paint(Canvas canvas, Size size) { 48 | drawDayWidget(dateModel, canvas, size); 49 | } 50 | 51 | @override 52 | bool shouldRepaint(CustomPainter oldDelegate) { 53 | return true; 54 | } 55 | } 56 | 57 | /** 58 | * 通过组合widget创建item,只需实现相关的方法就可以 59 | */ 60 | abstract class BaseCombineDayWidget extends StatelessWidget { 61 | final DateModel dateModel; 62 | 63 | BaseCombineDayWidget(this.dateModel); 64 | 65 | @override 66 | Widget build(BuildContext context) { 67 | return dateModel.isSelected 68 | ? getSelectedWidget(dateModel) 69 | : getNormalWidget(dateModel); 70 | } 71 | 72 | Widget getNormalWidget(DateModel dateModel); 73 | 74 | Widget getSelectedWidget(DateModel dateModel); 75 | } 76 | -------------------------------------------------------------------------------- /lib/widget/base_week_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /** 4 | * 顶部的固定的周显示 5 | */ 6 | abstract class BaseWeekBar extends StatelessWidget { 7 | const BaseWeekBar({Key key}) : super(key: key); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return Container( 12 | child: new Row( 13 | children: getWeekDayWidget(), 14 | ), 15 | ); 16 | } 17 | 18 | Widget getWeekBarItem(int index); 19 | 20 | List getWeekDayWidget() { 21 | return List.generate(7, (index) { 22 | return getChild(index); 23 | }); 24 | } 25 | 26 | Widget getChild(int index) { 27 | return new Expanded( 28 | child: getWeekBarItem(index), 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/widget/calendar_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_custom_calendar/calendar_provider.dart'; 4 | import 'package:flutter_custom_calendar/constants/constants.dart'; 5 | import 'package:flutter_custom_calendar/controller.dart'; 6 | import 'package:flutter_custom_calendar/utils/LogUtil.dart'; 7 | import 'package:flutter_custom_calendar/utils/date_util.dart'; 8 | import 'package:flutter_custom_calendar/widget/month_view_pager.dart'; 9 | import 'package:flutter_custom_calendar/widget/week_view_pager.dart'; 10 | import 'package:provider/provider.dart'; 11 | 12 | /** 13 | * 暂时默认是周一开始的 14 | */ 15 | 16 | //由于旧的代码关系。。所以现在需要抽出一个StatefulWidget放在StatelessWidget里面 17 | class CalendarViewWidget extends StatefulWidget { 18 | //整体的背景设置 19 | final BoxDecoration boxDecoration; 20 | 21 | //日历的padding和margin 22 | final EdgeInsetsGeometry padding; 23 | final EdgeInsetsGeometry margin; 24 | 25 | //默认是屏幕宽度/7 26 | final double itemSize; 27 | 28 | //日历item之间的竖直方向间距,默认10 29 | final double verticalSpacing; 30 | 31 | //自定义日历item 32 | final DayWidgetBuilder dayWidgetBuilder; 33 | final WeekBarItemWidgetBuilder weekBarItemWidgetBuilder; 34 | 35 | //控制器 36 | final CalendarController calendarController; 37 | 38 | CalendarViewWidget( 39 | {Key key, 40 | this.dayWidgetBuilder = defaultCustomDayWidget, 41 | this.weekBarItemWidgetBuilder = defaultWeekBarWidget, 42 | @required this.calendarController, 43 | this.boxDecoration, 44 | this.padding = EdgeInsets.zero, 45 | this.margin = EdgeInsets.zero, 46 | this.verticalSpacing = 10, 47 | this.itemSize}) 48 | : super(key: key); 49 | 50 | @override 51 | _CalendarViewWidgetState createState() => _CalendarViewWidgetState(); 52 | } 53 | 54 | class _CalendarViewWidgetState extends State { 55 | @override 56 | void initState() { 57 | //初始化一些数据,一些跟状态有关的要放到provider中 58 | widget.calendarController.calendarProvider.initData( 59 | calendarConfiguration: widget.calendarController.calendarConfiguration, 60 | padding: widget.padding, 61 | margin: widget.margin, 62 | itemSize: widget.itemSize, 63 | verticalSpacing: widget.verticalSpacing, 64 | dayWidgetBuilder: widget.dayWidgetBuilder, 65 | weekBarItemWidgetBuilder: widget.weekBarItemWidgetBuilder); 66 | 67 | super.initState(); 68 | } 69 | 70 | @override 71 | void dispose() { 72 | // widget.calendarController.clearData(); 73 | super.dispose(); 74 | } 75 | 76 | @override 77 | Widget build(BuildContext context) { 78 | return ChangeNotifierProvider.value( 79 | value: widget.calendarController.calendarProvider, 80 | child: Container( 81 | //外部可以自定义背景设置 82 | decoration: widget.boxDecoration, 83 | padding: widget.padding, 84 | margin: widget.margin, 85 | //使用const,保证外界的setState不会刷新日历这个widget 86 | child: CalendarContainer(widget.calendarController)), 87 | ); 88 | } 89 | } 90 | 91 | class CalendarContainer extends StatefulWidget { 92 | final CalendarController calendarController; 93 | 94 | const CalendarContainer(this.calendarController); 95 | 96 | @override 97 | CalendarContainerState createState() => CalendarContainerState(); 98 | } 99 | 100 | class CalendarContainerState extends State 101 | with SingleTickerProviderStateMixin { 102 | double itemHeight; 103 | double totalHeight; 104 | 105 | bool expand; 106 | 107 | CalendarProvider calendarProvider; 108 | 109 | List widgets = []; 110 | 111 | int index = 0; 112 | 113 | @override 114 | void initState() { 115 | super.initState(); 116 | calendarProvider = Provider.of(context, listen: false); 117 | expand = calendarProvider.expandStatus.value; 118 | 119 | if (calendarProvider.calendarConfiguration.showMode == 120 | CalendarConstants.MODE_SHOW_ONLY_WEEK) { 121 | widgets.add(const WeekViewPager()); 122 | } else if (calendarProvider.calendarConfiguration.showMode == 123 | CalendarConstants.MODE_SHOW_WEEK_AND_MONTH) { 124 | widgets.add(const MonthViewPager()); 125 | widgets.add(const WeekViewPager()); 126 | index = 1; 127 | } else if (calendarProvider.calendarConfiguration.showMode == 128 | CalendarConstants.MODE_SHOW_MONTH_AND_WEEK) { 129 | widgets.add(const MonthViewPager()); 130 | widgets.add(const WeekViewPager()); 131 | index = 0; 132 | } else { 133 | //默认是只显示月视图 134 | widgets.add(const MonthViewPager()); 135 | } 136 | expand = calendarProvider.expandStatus.value; 137 | 138 | //如果需要视图切换的话,才需要添加监听,不然不需要监听变化 139 | if (calendarProvider.calendarConfiguration.showMode == 140 | CalendarConstants.MODE_SHOW_WEEK_AND_MONTH || 141 | calendarProvider.calendarConfiguration.showMode == 142 | CalendarConstants.MODE_SHOW_MONTH_AND_WEEK) { 143 | calendarProvider.expandStatus.addListener(() { 144 | setState(() { 145 | print( 146 | "calendarProvider.expandStatus.value:${calendarProvider.expandStatus.value}"); 147 | expand = calendarProvider.expandStatus.value; 148 | if (expand) { 149 | index = 0; 150 | //周视图切换到月视图,需要计算下初始化的高度 151 | int lineCount = DateUtil.getMonthViewLineCount( 152 | calendarProvider.calendarConfiguration.nowYear, 153 | calendarProvider.calendarConfiguration.nowMonth, 154 | calendarProvider.calendarConfiguration.offset); 155 | totalHeight = calendarProvider.calendarConfiguration.itemSize * (lineCount) + calendarProvider.calendarConfiguration.verticalSpacing * (lineCount - 1); 156 | calendarProvider.calendarConfiguration.monthController.jumpToPage(calendarProvider.monthPageIndex); 157 | } else { 158 | index = 1; 159 | //月视图切换到周视图 160 | calendarProvider.calendarConfiguration.weekController.jumpToPage(calendarProvider.weekPageIndex); 161 | } 162 | }); 163 | }); 164 | } else { 165 | index = 0; 166 | } 167 | 168 | widget.calendarController.addMonthChangeListener((year, month) { 169 | if (widget.calendarController.calendarProvider.calendarConfiguration 170 | .showMode != 171 | CalendarConstants.MODE_SHOW_ONLY_WEEK) { 172 | //月份切换的时候,如果高度发生变化的话,需要setState使高度整体自适应 173 | int lineCount = DateUtil.getMonthViewLineCount(year, month, widget.calendarController.calendarConfiguration.offset); 174 | double newHeight = itemHeight * (lineCount) + 175 | calendarProvider.calendarConfiguration.verticalSpacing * 176 | (lineCount - 1); 177 | LogUtil.log( 178 | TAG: this.runtimeType, 179 | message: "totalHeight:$totalHeight,newHeight:$newHeight"); 180 | if (totalHeight.toInt() != newHeight.toInt()) { 181 | LogUtil.log(TAG: this.runtimeType, message: "月份视图高度发生变化"); 182 | setState(() { 183 | totalHeight = newHeight; 184 | }); 185 | } 186 | } 187 | }); 188 | 189 | itemHeight = calendarProvider.calendarConfiguration.itemSize; 190 | totalHeight = calendarProvider.totalHeight; 191 | } 192 | 193 | @override 194 | void dispose() { 195 | super.dispose(); 196 | } 197 | 198 | @override 199 | Widget build(BuildContext context) { 200 | LogUtil.log(TAG: this.runtimeType, message: "CalendarContainerState build"); 201 | return Container( 202 | width: itemHeight * 7, 203 | child: new Column( 204 | crossAxisAlignment: CrossAxisAlignment.stretch, 205 | mainAxisSize: MainAxisSize.min, 206 | children: [ 207 | /** 208 | * 利用const,避免每次setState都会刷新到这顶部的view 209 | */ 210 | calendarProvider.calendarConfiguration.weekBarItemWidgetBuilder(), 211 | AnimatedContainer( 212 | duration: Duration(milliseconds: 500), 213 | height: expand ? totalHeight : itemHeight, 214 | child: IndexedStack( 215 | index: index, 216 | children: widgets, 217 | )), 218 | ], 219 | ), 220 | ); 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /lib/widget/default_combine_day_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_custom_calendar/flutter_custom_calendar.dart'; 3 | import 'package:flutter_custom_calendar/style/style.dart'; 4 | 5 | import 'base_day_view.dart'; 6 | 7 | /** 8 | * 默认的利用组合widget的方式构造item 9 | */ 10 | //class DefaultCombineDayWidget extends StatelessWidget { 11 | // DateModel dateModel; 12 | // 13 | // DefaultCombineDayWidget(this.dateModel); 14 | // 15 | // @override 16 | // Widget build(BuildContext context) { 17 | // return Container( 18 | // margin: EdgeInsets.only(top: 5, bottom: 5), 19 | // decoration: dateModel.isSelected 20 | // ? new BoxDecoration(color: Colors.red, shape: BoxShape.circle) 21 | // : null, 22 | // child: new Stack( 23 | // alignment: Alignment.center, 24 | // children: [ 25 | // new Column( 26 | // mainAxisSize: MainAxisSize.max, 27 | // crossAxisAlignment: CrossAxisAlignment.center, 28 | // children: [ 29 | // //公历 30 | // new Expanded( 31 | // child: Center( 32 | // child: new Text( 33 | // dateModel.day.toString(), 34 | // style: currentMonthTextStyle, 35 | // ), 36 | // ), 37 | // ), 38 | // 39 | // //农历 40 | // new Expanded( 41 | // child: Center( 42 | // child: new Text( 43 | // "${dateModel.lunarString}", 44 | // style: lunarTextStyle, 45 | // ), 46 | // ), 47 | // ), 48 | // ], 49 | // ) 50 | // ], 51 | // ), 52 | // ); 53 | // } 54 | //} 55 | 56 | class DefaultCombineDayWidget extends BaseCombineDayWidget { 57 | DefaultCombineDayWidget(DateModel dateModel) : super(dateModel); 58 | 59 | @override 60 | Widget getNormalWidget(DateModel dateModel) { 61 | return Container( 62 | margin: EdgeInsets.all(8), 63 | child: new Stack( 64 | alignment: Alignment.center, 65 | children: [ 66 | new Column( 67 | mainAxisSize: MainAxisSize.max, 68 | crossAxisAlignment: CrossAxisAlignment.center, 69 | children: [ 70 | //公历 71 | new Expanded( 72 | child: Center( 73 | child: new Text( 74 | dateModel.day.toString(), 75 | style: currentMonthTextStyle, 76 | ), 77 | ), 78 | ), 79 | 80 | //农历 81 | new Expanded( 82 | child: Center( 83 | child: new Text( 84 | "${dateModel.lunarString}", 85 | style: lunarTextStyle, 86 | ), 87 | ), 88 | ), 89 | ], 90 | ) 91 | ], 92 | ), 93 | ); 94 | } 95 | 96 | @override 97 | Widget getSelectedWidget(DateModel dateModel) { 98 | return Container( 99 | margin: EdgeInsets.all(8), 100 | foregroundDecoration: 101 | new BoxDecoration(border: Border.all(width: 2, color: Colors.blue)), 102 | child: new Stack( 103 | alignment: Alignment.center, 104 | children: [ 105 | new Column( 106 | mainAxisSize: MainAxisSize.max, 107 | crossAxisAlignment: CrossAxisAlignment.center, 108 | children: [ 109 | //公历 110 | new Expanded( 111 | child: Center( 112 | child: new Text( 113 | dateModel.day.toString(), 114 | style: currentMonthTextStyle, 115 | ), 116 | ), 117 | ), 118 | 119 | //农历 120 | new Expanded( 121 | child: Center( 122 | child: new Text( 123 | "${dateModel.lunarString}", 124 | style: lunarTextStyle, 125 | ), 126 | ), 127 | ), 128 | ], 129 | ) 130 | ], 131 | ), 132 | ); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /lib/widget/default_custom_day_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_custom_calendar/flutter_custom_calendar.dart'; 3 | import 'package:flutter_custom_calendar/style/style.dart'; 4 | 5 | import 'base_day_view.dart'; 6 | 7 | /** 8 | * 这里定义成一个StatelessWidget,状态是外部的父控件传进来参数控制就行,自己不弄state类 9 | */ 10 | 11 | class DefaultCustomDayWidget extends BaseCustomDayWidget { 12 | const DefaultCustomDayWidget(DateModel dateModel) : super(dateModel); 13 | 14 | @override 15 | void drawNormal(DateModel dateModel, Canvas canvas, Size size) { 16 | defaultDrawNormal(dateModel, canvas, size); 17 | } 18 | 19 | @override 20 | void drawSelected(DateModel dateModel, Canvas canvas, Size size) { 21 | defaultDrawSelected(dateModel, canvas, size); 22 | } 23 | } 24 | 25 | //class DefaultCustomDayWidget extends StatelessWidget { 26 | // DateModel dateModel; 27 | // 28 | // DefaultCustomDayWidget(this.dateModel); 29 | // 30 | // @override 31 | // Widget build(BuildContext context) { 32 | // return Container( 33 | // child: new CustomPaint( 34 | // painter: CustomDayWidgetPainter( 35 | // dateModel, 36 | // ), 37 | // ), 38 | // ); 39 | // } 40 | //} 41 | // 42 | //class CustomDayWidgetPainter extends CustomPainter { 43 | // DateModel dateModel; 44 | // 45 | // drawNormal normalDraw; //普通样式是必须的 46 | // drawSelected selectedDraw; 47 | // 48 | // CustomDayWidgetPainter(this.dateModel, 49 | // {this.normalDraw = defaultDrawNormal, 50 | // this.selectedDraw = defaultDrawSelected}); 51 | // 52 | // Paint textPaint; 53 | // 54 | // @override 55 | // void paint(Canvas canvas, Size size) { 56 | //// print("paint:$size"); 57 | // if (dateModel.isSelected) { 58 | // selectedDraw(dateModel, canvas, size); 59 | // } else { 60 | // normalDraw(dateModel, canvas, size); 61 | // } 62 | // } 63 | // 64 | // @override 65 | // bool shouldRepaint(CustomPainter oldDelegate) { 66 | // return true; 67 | // } 68 | //} 69 | 70 | /** 71 | * 默认的样式 72 | */ 73 | void defaultDrawNormal(DateModel dateModel, Canvas canvas, Size size) { 74 | //顶部的文字 75 | TextPainter dayTextPainter = new TextPainter() 76 | ..text = TextSpan( 77 | text: dateModel.day.toString(), 78 | style: dateModel.isCurrentDay 79 | ? currentDayTextStyle 80 | : currentMonthTextStyle) 81 | ..textDirection = TextDirection.ltr 82 | ..textAlign = TextAlign.center; 83 | 84 | dayTextPainter.layout(minWidth: size.width, maxWidth: size.width); 85 | dayTextPainter.paint(canvas, Offset(0, 10)); 86 | 87 | //下面的文字 88 | TextPainter lunarTextPainter = new TextPainter() 89 | ..text = new TextSpan(text: dateModel.lunarString, style: lunarTextStyle) 90 | ..textDirection = TextDirection.ltr 91 | ..textAlign = TextAlign.center; 92 | 93 | lunarTextPainter.layout(minWidth: size.width, maxWidth: size.width); 94 | lunarTextPainter.paint(canvas, Offset(0, size.height / 2)); 95 | } 96 | 97 | /** 98 | * 被选中的样式 99 | */ 100 | void defaultDrawSelected(DateModel dateModel, Canvas canvas, Size size) { 101 | //绘制背景 102 | Paint backGroundPaint = new Paint() 103 | ..color = Colors.blue 104 | ..style = PaintingStyle.stroke 105 | ..strokeWidth = 2; 106 | double padding = 8; 107 | canvas.drawRect( 108 | Rect.fromPoints(Offset(padding, padding), 109 | Offset(size.width - padding, size.height - padding)), 110 | backGroundPaint); 111 | 112 | //顶部的文字 113 | TextPainter dayTextPainter = new TextPainter() 114 | ..text = 115 | TextSpan(text: dateModel.day.toString(), style: currentMonthTextStyle) 116 | ..textDirection = TextDirection.ltr 117 | ..textAlign = TextAlign.center; 118 | 119 | dayTextPainter.layout(minWidth: size.width, maxWidth: size.width); 120 | dayTextPainter.paint(canvas, Offset(0, 10)); 121 | 122 | //下面的文字 123 | TextPainter lunarTextPainter = new TextPainter() 124 | ..text = new TextSpan(text: dateModel.lunarString, style: lunarTextStyle) 125 | ..textDirection = TextDirection.ltr 126 | ..textAlign = TextAlign.center; 127 | 128 | lunarTextPainter.layout(minWidth: size.width, maxWidth: size.width); 129 | lunarTextPainter.paint(canvas, Offset(0, size.height / 2)); 130 | } 131 | -------------------------------------------------------------------------------- /lib/widget/default_week_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_custom_calendar/constants/constants.dart'; 3 | import 'package:flutter_custom_calendar/style/style.dart'; 4 | 5 | import 'base_week_bar.dart'; 6 | 7 | ///** 8 | // * 顶部的固定的周显示 9 | // */ 10 | //class DefaultWeekBar extends StatelessWidget { 11 | // const DefaultWeekBar({Key key}) : super(key: key); 12 | // 13 | // @override 14 | // Widget build(BuildContext context) { 15 | // return Container( 16 | // child: new Row( 17 | // children: getWeekDayWidget(), 18 | // ), 19 | // ); 20 | // } 21 | //} 22 | // 23 | //List getWeekDayWidget() { 24 | // return List.generate(7, (index) { 25 | // return getChild(Constants.WEEK_LIST[index]); 26 | // }); 27 | //} 28 | // 29 | //Widget getChild(String title) { 30 | // return new Expanded( 31 | // child: new Container( 32 | // color: RandomColor.next(), 33 | // height: 40, 34 | // alignment: Alignment.center, 35 | // child: new Text( 36 | // title, 37 | // style: topWeekTextStyle, 38 | // ), 39 | // )); 40 | //} 41 | 42 | class DefaultWeekBar extends BaseWeekBar { 43 | const DefaultWeekBar({Key key}) : super(key: key); 44 | 45 | @override 46 | Widget getWeekBarItem(int index) { 47 | return new Container( 48 | height: 40, 49 | alignment: Alignment.center, 50 | child: new Text( 51 | CalendarConstants.WEEK_LIST[index], 52 | style: topWeekTextStyle, 53 | ), 54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /lib/widget/month_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_custom_calendar/cache_data.dart'; 4 | import 'package:flutter_custom_calendar/configuration.dart'; 5 | import 'package:flutter_custom_calendar/flutter_custom_calendar.dart'; 6 | import 'package:flutter_custom_calendar/utils/LogUtil.dart'; 7 | import 'package:flutter_custom_calendar/utils/date_util.dart'; 8 | import 'package:provider/provider.dart'; 9 | 10 | /** 11 | * 月视图,显示整个月的日子 12 | */ 13 | class MonthView extends StatefulWidget { 14 | final int year; 15 | final int month; 16 | final int day; 17 | 18 | final CalendarConfiguration configuration; 19 | 20 | const MonthView({ 21 | Key key, 22 | @required this.year, 23 | @required this.month, 24 | this.day, 25 | this.configuration, 26 | }) : super(key: key); 27 | 28 | @override 29 | _MonthViewState createState() => _MonthViewState(); 30 | } 31 | 32 | class _MonthViewState extends State 33 | with AutomaticKeepAliveClientMixin { 34 | List items = List(); 35 | 36 | int lineCount; 37 | Map extraDataMap; //自定义额外的数据 38 | 39 | @override 40 | void initState() { 41 | super.initState(); 42 | extraDataMap = widget.configuration.extraDataMap; 43 | DateModel firstDayOfMonth = 44 | DateModel.fromDateTime(DateTime(widget.year, widget.month, 1)); 45 | if (CacheData.getInstance().monthListCache[firstDayOfMonth]?.isNotEmpty == 46 | true) { 47 | LogUtil.log(TAG: this.runtimeType, message: "缓存中有数据"); 48 | items = CacheData.getInstance().monthListCache[firstDayOfMonth]; 49 | } else { 50 | LogUtil.log(TAG: this.runtimeType, message: "缓存中无数据"); 51 | getItems().then((_) { 52 | CacheData.getInstance().monthListCache[firstDayOfMonth] = items; 53 | }); 54 | } 55 | 56 | lineCount = DateUtil.getMonthViewLineCount( 57 | widget.year, widget.month, widget.configuration.offset); 58 | 59 | //第一帧后,添加监听,generation发生变化后,需要刷新整个日历 60 | WidgetsBinding.instance.addPostFrameCallback((callback) { 61 | Provider.of(context, listen: false) 62 | .generation 63 | .addListener(() async { 64 | extraDataMap = widget.configuration.extraDataMap; 65 | await getItems(); 66 | }); 67 | }); 68 | } 69 | 70 | Future getItems() async { 71 | items = await compute(initCalendarForMonthView, { 72 | 'year': widget.year, 73 | 'month': widget.month, 74 | 'minSelectDate': widget.configuration.minSelectDate, 75 | 'maxSelectDate': widget.configuration.maxSelectDate, 76 | 'extraDataMap': extraDataMap, 77 | 'offset': widget.configuration.offset 78 | }); 79 | setState(() {}); 80 | } 81 | 82 | static Future> initCalendarForMonthView(Map map) async { 83 | return DateUtil.initCalendarForMonthView( 84 | map['year'], map['month'], DateTime.now(), DateTime.sunday, 85 | minSelectDate: map['minSelectDate'], 86 | maxSelectDate: map['maxSelectDate'], 87 | extraDataMap: map['extraDataMap'], 88 | offset: map['offset']); 89 | } 90 | 91 | @override 92 | Widget build(BuildContext context) { 93 | super.build(context); 94 | LogUtil.log(TAG: this.runtimeType, message: "_MonthViewState build"); 95 | 96 | CalendarProvider calendarProvider = 97 | Provider.of(context, listen: false); 98 | CalendarConfiguration configuration = 99 | calendarProvider.calendarConfiguration; 100 | 101 | return new GridView.builder( 102 | addAutomaticKeepAlives: true, 103 | padding: EdgeInsets.zero, 104 | physics: const NeverScrollableScrollPhysics(), 105 | gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( 106 | crossAxisCount: 7, mainAxisSpacing: configuration.verticalSpacing), 107 | itemCount: items.isEmpty ? 0 : items.length, 108 | itemBuilder: (context, index) { 109 | DateModel dateModel = items[index]; 110 | //判断是否被选择 111 | switch (configuration.selectMode) { 112 | 113 | /// 多选 114 | case CalendarSelectedMode.multiSelect: 115 | if (calendarProvider.selectedDateList.contains(dateModel)) { 116 | dateModel.isSelected = true; 117 | } else { 118 | dateModel.isSelected = false; 119 | } 120 | break; 121 | 122 | /// 选择开始和结束 中间的自动选择 123 | 124 | case CalendarSelectedMode.mutltiStartToEndSelect: 125 | if (calendarProvider.selectedDateList.contains(dateModel)) { 126 | dateModel.isSelected = true; 127 | } else { 128 | dateModel.isSelected = false; 129 | } 130 | break; 131 | 132 | /// 单选 133 | case CalendarSelectedMode.singleSelect: 134 | if (calendarProvider.selectDateModel == dateModel) { 135 | dateModel.isSelected = true; 136 | } else { 137 | dateModel.isSelected = false; 138 | } 139 | break; 140 | } 141 | 142 | return ItemContainer( 143 | dateModel: dateModel, 144 | key: ObjectKey(dateModel), 145 | clickCall: () { 146 | setState(() {}); 147 | // if (configuration.selectMode == 148 | // CalendarSelectedMode.mutltiStartToEndSelect) 149 | 150 | /// 如果是选择开始和结束则进行刷新日历 151 | }, 152 | //这里使用objectKey,保证可以刷新。原因1:跟flutter的刷新机制有关。原因2:statefulElement持有state。 153 | ); 154 | }); 155 | } 156 | 157 | @override 158 | bool get wantKeepAlive => true; 159 | } 160 | 161 | /** 162 | * 多选模式,包装item,这样的话,就只需要刷新当前点击的item就行了,不需要刷新整个页面 163 | */ 164 | class ItemContainer extends StatefulWidget { 165 | final DateModel dateModel; 166 | 167 | final GestureTapCallback clickCall; 168 | const ItemContainer({Key key, this.dateModel, this.clickCall}) 169 | : super(key: key); 170 | 171 | @override 172 | ItemContainerState createState() => ItemContainerState(); 173 | } 174 | 175 | class ItemContainerState extends State { 176 | DateModel dateModel; 177 | CalendarConfiguration configuration; 178 | CalendarProvider calendarProvider; 179 | 180 | ValueNotifier isSelected; 181 | 182 | @override 183 | void initState() { 184 | super.initState(); 185 | dateModel = widget.dateModel; 186 | isSelected = ValueNotifier(dateModel.isSelected); 187 | } 188 | 189 | /** 190 | * 提供方法给外部,可以调用这个方法进行刷新item 191 | */ 192 | void refreshItem(bool v) { 193 | /** 194 | Exception caught by gesture 195 | The following assertion was thrown while handling a gesture: 196 | setState() called after dispose() 197 | */ 198 | v ??= false; 199 | if (mounted) { 200 | setState(() { 201 | dateModel.isSelected = v; 202 | }); 203 | 204 | if (widget.clickCall != null) { 205 | widget.clickCall(); 206 | } 207 | } 208 | } 209 | 210 | void _notifiCationUnCalendarSelect(DateModel element) { 211 | if (configuration.unCalendarSelect != null) { 212 | configuration.unCalendarSelect(element); 213 | } 214 | } 215 | 216 | void _notifiCationCalendarSelect(DateModel element) { 217 | if (configuration.calendarSelect != null) { 218 | configuration.calendarSelect(element); 219 | } 220 | } 221 | 222 | @override 223 | Widget build(BuildContext context) { 224 | // LogUtil.log(TAG: this.runtimeType, message: "ItemContainerState build"); 225 | calendarProvider = Provider.of(context, listen: false); 226 | configuration = calendarProvider.calendarConfiguration; 227 | 228 | return GestureDetector( 229 | //点击整个item都会触发事件 230 | behavior: HitTestBehavior.opaque, 231 | onTap: () { 232 | LogUtil.log( 233 | TAG: this.runtimeType, 234 | message: "GestureDetector onTap: $dateModel}"); 235 | 236 | //范围外不可点击 237 | if (!dateModel.isInRange) { 238 | //多选回调 239 | if (configuration.selectMode == CalendarSelectedMode.multiSelect) { 240 | configuration.multiSelectOutOfRange(); 241 | } 242 | return; 243 | } 244 | print('244 周视图的变化: $dateModel'); 245 | calendarProvider.lastClickDateModel = dateModel; 246 | 247 | switch (configuration.selectMode) { 248 | //简单多选 249 | case CalendarSelectedMode.multiSelect: 250 | if (calendarProvider.selectedDateList.contains(dateModel)) { 251 | calendarProvider.selectedDateList.remove(dateModel); 252 | _notifiCationUnCalendarSelect(dateModel); 253 | } else { 254 | //多选,判断是否超过限制,超过范围 255 | if (calendarProvider.selectedDateList.length == 256 | configuration.maxMultiSelectCount) { 257 | if (configuration.multiSelectOutOfSize != null) { 258 | configuration.multiSelectOutOfSize(); 259 | } 260 | return; 261 | } 262 | dateModel.isSelected = !dateModel.isSelected; 263 | calendarProvider.selectedDateList.add(dateModel); 264 | } 265 | 266 | //多选也可以弄这些单选的代码 267 | calendarProvider.selectDateModel = dateModel; 268 | break; 269 | 270 | /// 单选 271 | case CalendarSelectedMode.singleSelect: 272 | 273 | /// 加入已经选择了多个 则进行取消操作 274 | calendarProvider.selectedDateList.forEach((element) { 275 | element.isSelected = false; 276 | _notifiCationUnCalendarSelect(element); 277 | }); 278 | calendarProvider.selectedDateList.clear(); 279 | 280 | //单选需要刷新上一个item 281 | if (calendarProvider.lastClickItemState != this) { 282 | calendarProvider.lastClickItemState?.refreshItem(false); 283 | calendarProvider.lastClickItemState = this; 284 | } 285 | if(calendarProvider.selectedDateList.contains(dateModel)){ 286 | // 如果已经选择就执行取消 287 | _notifiCationUnCalendarSelect(calendarProvider.selectDateModel); 288 | dateModel.isSelected = false; 289 | calendarProvider.selectedDateList.clear(); 290 | calendarProvider.selectDateModel = null; 291 | _notifiCationUnCalendarSelect(dateModel); 292 | }else{ 293 | _notifiCationUnCalendarSelect(calendarProvider.selectDateModel); 294 | dateModel.isSelected = true; 295 | calendarProvider.selectDateModel = dateModel; 296 | _notifiCationCalendarSelect(dateModel); 297 | } 298 | 299 | setState(() {}); 300 | 301 | break; 302 | 303 | /// 选择范围 304 | case CalendarSelectedMode.mutltiStartToEndSelect: 305 | if (calendarProvider.selectedDateList.length == 0) { 306 | calendarProvider.selectedDateList.add(dateModel); 307 | } else if (calendarProvider.selectedDateList.length == 1) { 308 | DateModel d2 = calendarProvider.selectedDateList.first; 309 | if (calendarProvider.selectedDateList.contains(dateModel)) { 310 | /// 选择同一个第二次则进行取消 311 | dateModel.isSelected = false; 312 | _notifiCationUnCalendarSelect(dateModel); 313 | setState(() {}); 314 | return; 315 | } 316 | DateTime t1, t2; 317 | if (d2.getDateTime().isAfter(dateModel.getDateTime())) { 318 | t2 = d2.getDateTime(); 319 | t1 = dateModel.getDateTime(); 320 | } else { 321 | t1 = d2.getDateTime(); 322 | t2 = dateModel.getDateTime(); 323 | } 324 | for (; t1.isBefore(t2);) { 325 | calendarProvider.selectedDateList 326 | .add(DateModel.fromDateTime(t1)); 327 | t1 = t1.add(Duration(days: 1)); 328 | } 329 | calendarProvider.selectedDateList.add(DateModel.fromDateTime(t1)); 330 | } else { 331 | /// 加入已经选择了多个 则进行取消操作 332 | calendarProvider.selectedDateList.forEach((element) { 333 | element.isSelected = false; 334 | _notifiCationUnCalendarSelect(element); 335 | }); 336 | 337 | /// 清空删除的 数组 338 | calendarProvider.selectedDateList.clear(); 339 | setState(() {}); 340 | } 341 | 342 | break; 343 | } 344 | 345 | /// 所有数组操作完了 进行通知分发 346 | if (configuration.calendarSelect != null && 347 | calendarProvider.selectedDateList.length > 0) { 348 | calendarProvider.selectedDateList.forEach((element) { 349 | _notifiCationCalendarSelect(element); 350 | }); 351 | } 352 | 353 | refreshItem(!this.dateModel.isSelected); 354 | }, 355 | child: configuration.dayWidgetBuilder(dateModel), 356 | ); 357 | } 358 | 359 | @override 360 | void deactivate() { 361 | // LogUtil.log( 362 | // TAG: this.runtimeType, message: "ItemContainerState deactivate"); 363 | super.deactivate(); 364 | } 365 | 366 | @override 367 | void dispose() { 368 | // LogUtil.log(TAG: this.runtimeType, message: "ItemContainerState dispose"); 369 | super.dispose(); 370 | } 371 | 372 | @override 373 | void didUpdateWidget(ItemContainer oldWidget) { 374 | // LogUtil.log( 375 | // TAG: this.runtimeType, message: "ItemContainerState didUpdateWidget"); 376 | super.didUpdateWidget(oldWidget); 377 | } 378 | } 379 | -------------------------------------------------------------------------------- /lib/widget/month_view_pager.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_custom_calendar/calendar_provider.dart'; 3 | import 'package:flutter_custom_calendar/configuration.dart'; 4 | import 'package:flutter_custom_calendar/flutter_custom_calendar.dart'; 5 | import 'package:flutter_custom_calendar/utils/LogUtil.dart'; 6 | import 'package:flutter_custom_calendar/utils/date_util.dart'; 7 | import 'package:flutter_custom_calendar/widget/month_view.dart'; 8 | 9 | import 'package:provider/provider.dart'; 10 | 11 | class MonthViewPager extends StatefulWidget { 12 | const MonthViewPager({Key key}) : super(key: key); 13 | 14 | @override 15 | _MonthViewPagerState createState() => _MonthViewPagerState(); 16 | } 17 | 18 | class _MonthViewPagerState extends State 19 | with AutomaticKeepAliveClientMixin { 20 | CalendarProvider calendarProvider; 21 | 22 | @override 23 | void initState() { 24 | super.initState(); 25 | LogUtil.log(TAG: this.runtimeType, message: "MonthViewPager initState"); 26 | 27 | calendarProvider = Provider.of(context, listen: false); 28 | 29 | //计算当前月视图的index 30 | DateModel dateModel = calendarProvider.lastClickDateModel; 31 | List monthList = calendarProvider.calendarConfiguration.monthList; 32 | int index = 0; 33 | for (int i = 0; i < monthList.length; i++) { 34 | DateModel firstDayOfMonth = monthList[i]; 35 | DateModel lastDayOfMonth = DateModel.fromDateTime(firstDayOfMonth.getDateTime().add(Duration(days: DateUtil.getMonthDaysCount(firstDayOfMonth.year, firstDayOfMonth.month)))); 36 | 37 | if ((dateModel.isAfter(firstDayOfMonth) || 38 | dateModel.isSameWith(firstDayOfMonth)) && 39 | dateModel.isBefore(lastDayOfMonth)) { 40 | index = i; 41 | break; 42 | } 43 | } 44 | WidgetsBinding.instance.addPostFrameCallback((timeStamp) { 45 | calendarProvider.calendarConfiguration.monthController.jumpToPage(index); 46 | }); 47 | } 48 | 49 | @override 50 | void dispose() { 51 | LogUtil.log(TAG: this.runtimeType, message: "MonthViewPager dispose"); 52 | super.dispose(); 53 | } 54 | 55 | @override 56 | Widget build(BuildContext context) { 57 | super.build(context); 58 | LogUtil.log(TAG: this.runtimeType, message: "MonthViewPager build"); 59 | // 获取到当前的CalendarProvider对象,设置listen为false,不需要刷新 60 | calendarProvider = Provider.of(context, listen: false); 61 | CalendarConfiguration configuration = 62 | calendarProvider.calendarConfiguration; 63 | 64 | return PageView.builder( 65 | onPageChanged: (position) { 66 | if (calendarProvider.expandStatus.value == false) { 67 | return; 68 | } 69 | //月份的变化 70 | DateModel dateModel = configuration.monthList[position]; 71 | configuration.monthChangeListeners.forEach((listener) { 72 | listener(dateModel.year, dateModel.month); 73 | }); 74 | //用来保存临时变量,用于月视图切换到周视图的时候, 75 | if (calendarProvider.lastClickDateModel != null || 76 | calendarProvider.lastClickDateModel.month != dateModel.month) { 77 | DateModel temp = new DateModel(); 78 | temp.year = configuration.monthList[position].year; 79 | temp.month = configuration.monthList[position].month; 80 | temp.day = configuration.monthList[position].day + 14; // 默认月中 81 | // 如果设置了 默认选择的时间 就取默认选择的时间天数,否则为当前时间 82 | DateModel currentModel = calendarProvider.selectDateModel ?? calendarProvider.selectedDateList?.toList()[0]; 83 | if(currentModel != null && temp.month == currentModel.month){ 84 | temp.day = currentModel.day; 85 | } 86 | print('85 周视图的变化: $temp'); 87 | calendarProvider.lastClickDateModel = temp; 88 | } 89 | }, 90 | controller: configuration.monthController, 91 | itemBuilder: (context, index) { 92 | final DateModel dateModel = configuration.monthList[index]; 93 | return new MonthView( 94 | configuration: configuration, 95 | year: dateModel.year, 96 | month: dateModel.month, 97 | ); 98 | }, 99 | itemCount: configuration.monthList.length, 100 | ); 101 | } 102 | 103 | @override 104 | bool get wantKeepAlive => true; 105 | } 106 | -------------------------------------------------------------------------------- /lib/widget/only_one_pointer_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/gestures.dart'; 3 | 4 | /** 5 | * 单点触摸的方案:https://xbuba.com/questions/51712287 6 | */ 7 | class OnlyOnePointerRecognizer extends OneSequenceGestureRecognizer { 8 | int _p = 0; 9 | 10 | @override 11 | void addAllowedPointer(PointerDownEvent event) { 12 | startTrackingPointer(event.pointer); 13 | if (_p == 0) { 14 | resolve(GestureDisposition.rejected); 15 | _p = event.pointer; 16 | } else { 17 | resolve(GestureDisposition.accepted); 18 | } 19 | } 20 | 21 | @override 22 | String get debugDescription => 'only one pointer recognizer'; 23 | 24 | @override 25 | void didStopTrackingLastPointer(int pointer) {} 26 | 27 | @override 28 | void handleEvent(PointerEvent event) { 29 | if (!event.down && event.pointer == _p) { 30 | _p = 0; 31 | } 32 | } 33 | } 34 | 35 | class OnlyOnePointerRecognizerWidget extends StatelessWidget { 36 | final Widget child; 37 | 38 | OnlyOnePointerRecognizerWidget({this.child}); 39 | 40 | @override 41 | Widget build(BuildContext context) { 42 | return RawGestureDetector( 43 | gestures: { 44 | OnlyOnePointerRecognizer: 45 | GestureRecognizerFactoryWithHandlers( 46 | () => OnlyOnePointerRecognizer(), 47 | (OnlyOnePointerRecognizer instance) { 48 | 49 | }, 50 | ), 51 | }, 52 | child: child, 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/widget/week_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:flutter/foundation.dart'; 4 | import 'package:flutter_custom_calendar/configuration.dart'; 5 | import 'package:flutter_custom_calendar/flutter_custom_calendar.dart'; 6 | import 'package:flutter_custom_calendar/utils/date_util.dart'; 7 | import 'package:flutter_custom_calendar/widget/month_view.dart'; 8 | import 'package:provider/provider.dart'; 9 | 10 | /** 11 | * 周视图,只显示本周的日子 12 | */ 13 | class WeekView extends StatefulWidget { 14 | final int year; 15 | final int month; 16 | final DateModel firstDayOfWeek; 17 | final CalendarConfiguration configuration; 18 | 19 | const WeekView( 20 | {@required this.year, 21 | @required this.month, 22 | this.firstDayOfWeek, 23 | this.configuration}); 24 | 25 | @override 26 | _WeekViewState createState() => _WeekViewState(); 27 | } 28 | 29 | class _WeekViewState extends State { 30 | List items; 31 | 32 | Map extraDataMap; //自定义额外的数据 33 | 34 | @override 35 | void initState() { 36 | super.initState(); 37 | extraDataMap = widget.configuration.extraDataMap; 38 | items = DateUtil.initCalendarForWeekView( 39 | widget.year, widget.month, widget.firstDayOfWeek.getDateTime(), 0, 40 | minSelectDate: widget.configuration.minSelectDate, 41 | maxSelectDate: widget.configuration.maxSelectDate, 42 | extraDataMap: extraDataMap, 43 | offset: widget.configuration.offset); 44 | 45 | //第一帧后,添加监听,generation发生变化后,需要刷新整个日历 46 | WidgetsBinding.instance.addPostFrameCallback((callback) { 47 | Provider.of(context, listen: false) 48 | .generation 49 | .addListener(() async { 50 | items = DateUtil.initCalendarForWeekView( 51 | widget.year, widget.month, widget.firstDayOfWeek.getDateTime(), 0, 52 | minSelectDate: widget.configuration.minSelectDate, 53 | maxSelectDate: widget.configuration.maxSelectDate, 54 | extraDataMap: extraDataMap, 55 | offset: widget.configuration.offset); 56 | setState(() {}); 57 | }); 58 | }); 59 | } 60 | 61 | @override 62 | Widget build(BuildContext context) { 63 | CalendarProvider calendarProvider = 64 | Provider.of(context, listen: false); 65 | 66 | CalendarConfiguration configuration = 67 | calendarProvider.calendarConfiguration; 68 | return new GridView.builder( 69 | physics: NeverScrollableScrollPhysics(), 70 | gridDelegate: new SliverGridDelegateWithFixedCrossAxisCount( 71 | crossAxisCount: 7, mainAxisSpacing: 10), 72 | itemCount: 7, 73 | itemBuilder: (context, index) { 74 | DateModel dateModel = items[index]; 75 | //判断是否被选择 76 | switch (configuration.selectMode) { 77 | case CalendarSelectedMode.multiSelect: 78 | if (calendarProvider.selectedDateList.contains(dateModel)) { 79 | dateModel.isSelected = true; 80 | } else { 81 | dateModel.isSelected = false; 82 | } 83 | break; 84 | case CalendarSelectedMode.singleSelect: 85 | if (calendarProvider.selectDateModel == dateModel) { 86 | dateModel.isSelected = true; 87 | } else { 88 | dateModel.isSelected = false; 89 | } 90 | break; 91 | case CalendarSelectedMode.mutltiStartToEndSelect: 92 | if (calendarProvider.selectedDateList.contains(dateModel)) { 93 | dateModel.isSelected = true; 94 | } else { 95 | dateModel.isSelected = false; 96 | } 97 | break; 98 | } 99 | 100 | return ItemContainer( 101 | dateModel: dateModel, 102 | clickCall: () { 103 | setState(() {}); 104 | }); 105 | }); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /lib/widget/week_view_pager.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_custom_calendar/widget/week_view.dart'; 3 | import 'package:provider/provider.dart'; 4 | import 'package:flutter/foundation.dart'; 5 | import 'package:flutter_custom_calendar/configuration.dart'; 6 | import 'package:flutter_custom_calendar/flutter_custom_calendar.dart'; 7 | import 'package:flutter_custom_calendar/utils/LogUtil.dart'; 8 | 9 | class WeekViewPager extends StatefulWidget { 10 | const WeekViewPager({Key key}) : super(key: key); 11 | 12 | @override 13 | _WeekViewPagerState createState() => _WeekViewPagerState(); 14 | } 15 | 16 | class _WeekViewPagerState extends State 17 | with AutomaticKeepAliveClientMixin { 18 | int lastMonth; //保存上一个月份,不然不知道月份发生了变化 19 | CalendarProvider calendarProvider; 20 | 21 | // PageController newPageController; 22 | 23 | @override 24 | void initState() { 25 | super.initState(); 26 | LogUtil.log(TAG: this.runtimeType, message: "WeekViewPager initState"); 27 | 28 | calendarProvider = Provider.of(context, listen: false); 29 | 30 | lastMonth = calendarProvider.lastClickDateModel.month; 31 | } 32 | 33 | @override 34 | void dispose() { 35 | LogUtil.log(TAG: this.runtimeType, message: "WeekViewPager dispose"); 36 | super.dispose(); 37 | } 38 | 39 | @override 40 | Widget build(BuildContext context) { 41 | super.build(context); 42 | LogUtil.log(TAG: this.runtimeType, message: "WeekViewPager build"); 43 | 44 | // 获取到当前的CalendarProvider对象,设置listen为false,不需要刷新 45 | CalendarProvider calendarProvider = 46 | Provider.of(context, listen: false); 47 | CalendarConfiguration configuration = 48 | calendarProvider.calendarConfiguration; 49 | return Container( 50 | height: configuration.itemSize ?? MediaQuery.of(context).size.width / 7, 51 | child: PageView.builder( 52 | onPageChanged: (position) { 53 | if (calendarProvider.expandStatus.value == true) { 54 | return; 55 | } 56 | 57 | LogUtil.log( 58 | TAG: this.runtimeType, 59 | message: 60 | "WeekViewPager PageView onPageChanged,position:$position"); 61 | DateModel firstDayOfWeek = configuration.weekList[position]; 62 | int currentMonth = firstDayOfWeek.month; 63 | // 周视图的变化 64 | configuration.weekChangeListeners.forEach((listener) { 65 | listener(firstDayOfWeek.year, firstDayOfWeek.month); 66 | }); 67 | if (lastMonth != currentMonth) { 68 | LogUtil.log( 69 | TAG: this.runtimeType, 70 | message: 71 | "WeekViewPager PageView monthChange:currentMonth:$currentMonth"); 72 | configuration.monthChangeListeners.forEach((listener) { 73 | listener(firstDayOfWeek.year, firstDayOfWeek.month); 74 | }); 75 | lastMonth = currentMonth; 76 | if (calendarProvider.lastClickDateModel == null || calendarProvider.lastClickDateModel.month != currentMonth) { 77 | DateModel temp = new DateModel(); 78 | temp.year = firstDayOfWeek.year; 79 | temp.month = firstDayOfWeek.month; 80 | temp.day = firstDayOfWeek.day + 14; 81 | print('83 周视图的变化: $temp'); 82 | calendarProvider.lastClickDateModel = temp; 83 | } 84 | } 85 | // calendarProvider.lastClickDateModel = configuration.weekList[position] 86 | // ..day += 4; 87 | }, 88 | controller: calendarProvider.calendarConfiguration.weekController, 89 | itemBuilder: (context, index) { 90 | DateModel dateModel = configuration.weekList[index]; 91 | print('dateModel: $dateModel'); 92 | return new WeekView( 93 | year: dateModel.year, 94 | month: dateModel.month, 95 | firstDayOfWeek: dateModel, 96 | configuration: calendarProvider.calendarConfiguration, 97 | ); 98 | }, 99 | itemCount: configuration.weekList.length, 100 | ), 101 | ); 102 | } 103 | 104 | @override 105 | bool get wantKeepAlive => true; 106 | } 107 | -------------------------------------------------------------------------------- /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 | url: "https://pub.flutter-io.cn" 9 | source: hosted 10 | version: "2.4.2" 11 | boolean_selector: 12 | dependency: transitive 13 | description: 14 | name: boolean_selector 15 | url: "https://pub.flutter-io.cn" 16 | source: hosted 17 | version: "2.0.0" 18 | characters: 19 | dependency: transitive 20 | description: 21 | name: characters 22 | url: "https://pub.flutter-io.cn" 23 | source: hosted 24 | version: "1.0.0" 25 | charcode: 26 | dependency: transitive 27 | description: 28 | name: charcode 29 | url: "https://pub.flutter-io.cn" 30 | source: hosted 31 | version: "1.1.3" 32 | clock: 33 | dependency: transitive 34 | description: 35 | name: clock 36 | url: "https://pub.flutter-io.cn" 37 | source: hosted 38 | version: "1.0.1" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.flutter-io.cn" 44 | source: hosted 45 | version: "1.14.13" 46 | fake_async: 47 | dependency: transitive 48 | description: 49 | name: fake_async 50 | url: "https://pub.flutter-io.cn" 51 | source: hosted 52 | version: "1.1.0" 53 | flutter: 54 | dependency: "direct main" 55 | description: flutter 56 | source: sdk 57 | version: "0.0.0" 58 | flutter_test: 59 | dependency: "direct dev" 60 | description: flutter 61 | source: sdk 62 | version: "0.0.0" 63 | matcher: 64 | dependency: transitive 65 | description: 66 | name: matcher 67 | url: "https://pub.flutter-io.cn" 68 | source: hosted 69 | version: "0.12.8" 70 | meta: 71 | dependency: transitive 72 | description: 73 | name: meta 74 | url: "https://pub.flutter-io.cn" 75 | source: hosted 76 | version: "1.1.8" 77 | nested: 78 | dependency: transitive 79 | description: 80 | name: nested 81 | url: "https://pub.flutter-io.cn" 82 | source: hosted 83 | version: "0.0.4" 84 | path: 85 | dependency: transitive 86 | description: 87 | name: path 88 | url: "https://pub.flutter-io.cn" 89 | source: hosted 90 | version: "1.7.0" 91 | provider: 92 | dependency: "direct main" 93 | description: 94 | name: provider 95 | url: "https://pub.flutter-io.cn" 96 | source: hosted 97 | version: "4.1.3" 98 | sky_engine: 99 | dependency: transitive 100 | description: flutter 101 | source: sdk 102 | version: "0.0.99" 103 | source_span: 104 | dependency: transitive 105 | description: 106 | name: source_span 107 | url: "https://pub.flutter-io.cn" 108 | source: hosted 109 | version: "1.7.0" 110 | stack_trace: 111 | dependency: transitive 112 | description: 113 | name: stack_trace 114 | url: "https://pub.flutter-io.cn" 115 | source: hosted 116 | version: "1.9.5" 117 | stream_channel: 118 | dependency: transitive 119 | description: 120 | name: stream_channel 121 | url: "https://pub.flutter-io.cn" 122 | source: hosted 123 | version: "2.0.0" 124 | string_scanner: 125 | dependency: transitive 126 | description: 127 | name: string_scanner 128 | url: "https://pub.flutter-io.cn" 129 | source: hosted 130 | version: "1.0.5" 131 | term_glyph: 132 | dependency: transitive 133 | description: 134 | name: term_glyph 135 | url: "https://pub.flutter-io.cn" 136 | source: hosted 137 | version: "1.1.0" 138 | test_api: 139 | dependency: transitive 140 | description: 141 | name: test_api 142 | url: "https://pub.flutter-io.cn" 143 | source: hosted 144 | version: "0.2.17" 145 | typed_data: 146 | dependency: transitive 147 | description: 148 | name: typed_data 149 | url: "https://pub.flutter-io.cn" 150 | source: hosted 151 | version: "1.2.0" 152 | vector_math: 153 | dependency: transitive 154 | description: 155 | name: vector_math 156 | url: "https://pub.flutter-io.cn" 157 | source: hosted 158 | version: "2.0.8" 159 | sdks: 160 | dart: ">=2.9.0-14.0.dev <3.0.0" 161 | flutter: ">=1.16.0" 162 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_custom_calendar 2 | description: A calendar control of flutter that supports three selection modes 3 | version: 1.0.4+0.5 4 | homepage: http://www.fgyong.cn 5 | author: fgyong 6 | repository: https://github.com/ifgyong/flutter_custom_calendar 7 | issue_tracker: https://github.com/ifgyong/flutter_custom_calendar/issues 8 | environment: 9 | sdk: ">=2.7.0 <3.0.0" 10 | 11 | dependencies: 12 | flutter: 13 | sdk: flutter 14 | 15 | provider: ^4.1.3 16 | dev_dependencies: 17 | flutter_test: 18 | sdk: flutter 19 | 20 | # For information on the generic Dart part of this file, see the 21 | # following page: https://dart.dev/tools/pub/pubspec 22 | 23 | # The following section is specific to Flutter. 24 | flutter: 25 | 26 | # To add assets to your package, add an assets section, like this: 27 | # assets: 28 | # - images/a_dot_burr.jpeg 29 | # - images/a_dot_ham.jpeg 30 | # 31 | # For details regarding assets in packages, see 32 | # https://flutter.dev/assets-and-images/#from-packages 33 | # 34 | # An image asset can refer to one or more resolution-specific "variants", see 35 | # https://flutter.dev/assets-and-images/#resolution-aware. 36 | 37 | # To add custom fonts to your package, add a fonts section here, 38 | # in this "flutter" section. Each entry in this list should have a 39 | # "family" key with the font family name, and a "fonts" key with a 40 | # list giving the asset and other descriptors for the font. For 41 | # example: 42 | # fonts: 43 | # - family: Schyler 44 | # fonts: 45 | # - asset: fonts/Schyler-Regular.ttf 46 | # - asset: fonts/Schyler-Italic.ttf 47 | # style: italic 48 | # - family: Trajan Pro 49 | # fonts: 50 | # - asset: fonts/TrajanPro.ttf 51 | # - asset: fonts/TrajanPro_Bold.ttf 52 | # weight: 700 53 | # 54 | # For details regarding fonts in packages, see 55 | # https://flutter.dev/custom-fonts/#from-packages 56 | -------------------------------------------------------------------------------- /test/fluttercustomcalendar_test.dart: -------------------------------------------------------------------------------- 1 | void main() { 2 | // test('adds one to input values', () { 3 | // final calculator = Calculator(); 4 | // expect(calculator.addOne(2), 3); 5 | // expect(calculator.addOne(-7), -6); 6 | // expect(calculator.addOne(0), 1); 7 | // expect(() => calculator.addOne(null), throwsNoSuchMethodError); 8 | // }); 9 | } 10 | --------------------------------------------------------------------------------