提交 3a2ccd5b authored 作者: caoyongfeng's avatar caoyongfeng

Initial commit

上级
.DS_Store
.idea/
*.iml
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: 2d2a1ffec95cc70a3218872a2cd3f8de4933c42f
channel: stable
project_type: package
## 2.1.1 - 2023.01.30
* Support `dark` theme.
## 2.1.0 - 2023.01.29
* Follow the file naming convention in Effective Dart, #59
* fix: AM/PM are backward for 12:00, #185
* added ROMANIAN localization
## 2.0.5 - 2022.07.19
* close debug log (#178)
## 2.0.4 - 2022.06.28
* Add `PickerLocalizations.registerCustomLanguage` function
* Add `onBuilderItem` params. Allow custom item to be generated.
* Fixed bug: #158, #168, #169, #177
## 2.0.3 - 2022.02.212
* Fixed bug: #161, #165 ...
* Add `onBuilder`, `backgroundColor` parameter to `show`, `showDialog`, `showModal`
## 2.0.2 - 2021.06.25
* Fixed bug: #142, #98
* Add demo DurationPicker
* Add onConfirmBefore callback event;
## 2.0.1
* Fixed bug.
* Add PickerWidget.of(context) Get Picker
* Add selectedIconTheme property
## 2.0.0
* Support Null safety
* Optimize code, Update demo list
## 1.1.5
* Repair BUG (Including the problem of datetime adapter before the month, add the needUpdatePrev interface to the adapter)
* add builderHeader params. Allow custom headers to be generated.
* DateTimePickerAdapter add minHour, maxHour.
* Thank you for your timely feedback in GitHub!
## 1.1.4
* performance optimization
* Add a demo with a pop-up fillet background at the bottom.
## 1.1.3
* return for showDialog , add barrierDismissible (thanks: Ali1Ammar)
## 1.1.2
* Add the ability to jump minutes in DateTimePickerAdapter #76 (thank: MoacirSchmidt)
* Repair BUG
## 1.1.1
* Add localization for Turkish language
* Refactor localization code
* Repair BUG
## 1.1.0
* Project reconstruction, removing Android, IOS directory.
* Add localization for Spanish language
## 1.0.15
* Move the edge of the head to the bottom of the head so that it can be hidden by customization. #55 (thank: @StarOfLife)
* Repair bug: .showDialog(context) - error for define style #49 (thank: @nielgomes)
* Repair bug: Change superior data, lower data display blank #48 (thank: @DemoJameson)
## 1.0.14
* Added return for showModal #44 (thank: @GiorgioBertolotti)
## 1.0.13
* Add parameter: selectedTextStyle, Styles for setting selected items. (thank: @TristanWYL)
* Add parameter: footer, Widgets can be added at the bottom.
## 1.0.12
* Repair BUG: #38, #40
* DateTimePickerAdapter: Add the twoDigitYear attribute to support small screens (#39)
## 1.0.11
* Add localization for Arabic language
* Add parameter: cancel and confirm, Allow yourself to specify widgets to be placed in the identified and cancelled locations
* DateTimePickerAdapter supports minValue and maxValue, which are used to limit the upper and lower bounds for selecting date and time.
## 1.0.10
* Solving #25 Problem
* Add attributes: headerDecoration, Can be used to control header borders.
* Add Italian locale.
## 1.0.9
* Add the ability to jump numbers in NumberPickerColumn (thank: @jesusrp98)
## 1.0.8
* Repair Bug: showDialog
## 1.0.7
* Add korea locale
## 1.0.6
* Remove debugging output information
## 1.0.5
* Modifying description information
## 1.0.3
* Repair Bug.
* NumberPickerAdapter, data Add onFormatValue property
* DateTimePickerAdapter, Add customColumnType property, Support custom column order.
## 1.0.2
* Repair Bug.
## 1.0.1
* NumberPickerAdapter Support postfix; suffix.
## 1.0.0
* Add NumberPickerAdapter, Support specified number range
* Add isLinkage property, Support Array.
* Add delimiter, Insert separators between columns
* Add localization support
* Optimization code
## 0.0.5
* Adapter add notifyDataChanged function.
## 0.0.4
* Add PickerDateTimeType.kYM.
## 0.0.3
* Support column Flex setting. Add new DateTimeAdapter type.
## 0.0.2
* Code refactoring, adding adapters. Support dialog box. Increase the date and time selection.
## 0.0.1
* Describe initial release.
MIT License
Copyright (c) 2018 - 2019
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
# flutter_picker
[![pub package](https://img.shields.io/pub/v/flutter_picker.svg)](https://pub.dartlang.org/packages/flutter_picker)
![GitHub](https://img.shields.io/github/license/yangyxd/flutter_picker.svg)
[![GitHub stars](https://img.shields.io/github/stars/yangyxd/flutter_picker.svg?style=social&label=Stars)](https://github.com/yangyxd/flutter_picker)
Flutter plugin picker. Include NumberPicker, DateTimePicker, ArrayPicker, and default linkage Picker. Provide flexible parameters to meet various needs. At the same time, you can extend more functions through custom adapters.
> Supported Platforms
> * Android
> * IOS
> * Windows
> * MacOS
> * Linux
> * Web
![image](https://github.com/yangyxd/flutter_picker/blob/master/raw/views.gif)
## How to Use
```yaml
# add this line to your dependencies
flutter_picker:
git:
url: https://github.com/yangyxd/flutter_picker.git
```
```dart
import 'package:flutter_picker/flutter_picker.dart';
```
```dart
showPicker(BuildContext context) {
Picker picker = new Picker(
adapter: PickerDataAdapter<String>(pickerdata: new JsonDecoder().convert(PickerData)),
changeToFirst: true,
textAlign: TextAlign.left,
columnPadding: const EdgeInsets.all(8.0),
onConfirm: (Picker picker, List value) {
print(value.toString());
print(picker.getSelectedValues());
}
);
picker.show(_scaffoldKey.currentState);
}
showPickerModal(BuildContext context) {
new Picker(
adapter: PickerDataAdapter<String>(pickerdata: new JsonDecoder().convert(PickerData)),
changeToFirst: true,
hideHeader: false,
onConfirm: (Picker picker, List value) {
print(value.toString());
print(picker.adapter.text);
}
).showModal(this.context); //_scaffoldKey.currentState);
}
showPickerIcons(BuildContext context) {
new Picker(
adapter: PickerDataAdapter(data: [
new PickerItem(text: Icon(Icons.add), value: Icons.add, children: [
new PickerItem(text: Icon(Icons.more)),
new PickerItem(text: Icon(Icons.aspect_ratio)),
new PickerItem(text: Icon(Icons.android)),
new PickerItem(text: Icon(Icons.menu)),
]),
new PickerItem(text: Icon(Icons.title), value: Icons.title, children: [
new PickerItem(text: Icon(Icons.more_vert)),
new PickerItem(text: Icon(Icons.ac_unit)),
new PickerItem(text: Icon(Icons.access_alarm)),
new PickerItem(text: Icon(Icons.account_balance)),
]),
new PickerItem(text: Icon(Icons.face), value: Icons.face, children: [
new PickerItem(text: Icon(Icons.add_circle_outline)),
new PickerItem(text: Icon(Icons.add_a_photo)),
new PickerItem(text: Icon(Icons.access_time)),
new PickerItem(text: Icon(Icons.adjust)),
]),
new PickerItem(text: Icon(Icons.linear_scale), value: Icons.linear_scale, children: [
new PickerItem(text: Icon(Icons.assistant_photo)),
new PickerItem(text: Icon(Icons.account_balance)),
new PickerItem(text: Icon(Icons.airline_seat_legroom_extra)),
new PickerItem(text: Icon(Icons.airport_shuttle)),
new PickerItem(text: Icon(Icons.settings_bluetooth)),
]),
new PickerItem(text: Icon(Icons.close), value: Icons.close),
]),
title: new Text("Select Icon"),
onConfirm: (Picker picker, List value) {
print(value.toString());
print(picker.getSelectedValues());
}
).show(_scaffoldKey.currentState);
}
showPickerDialog(BuildContext context) {
new Picker(
adapter: PickerDataAdapter<String>(pickerdata: new JsonDecoder().convert(PickerData)),
hideHeader: true,
title: new Text("Select Data"),
onConfirm: (Picker picker, List value) {
print(value.toString());
print(picker.getSelectedValues());
}
).showDialog(context);
}
showPickerArray(BuildContext context) {
new Picker(
adapter: PickerDataAdapter<String>(pickerdata: new JsonDecoder().convert(PickerData2), isArray: true),
hideHeader: true,
title: new Text("Please Select"),
onConfirm: (Picker picker, List value) {
print(value.toString());
print(picker.getSelectedValues());
}
).showDialog(context);
}
showPickerNumber(BuildContext context) {
new Picker(
adapter: NumberPickerAdapter(data: [
NumberPickerColumn(begin: 0, end: 999),
NumberPickerColumn(begin: 100, end: 200),
]),
delimiter: [
PickerDelimiter(child: Container(
width: 30.0,
alignment: Alignment.center,
child: Icon(Icons.more_vert),
))
],
hideHeader: true,
title: new Text("Please Select"),
onConfirm: (Picker picker, List value) {
print(value.toString());
print(picker.getSelectedValues());
}
).showDialog(context);
}
```
## PickerData Example
### Array
```dart
const PickerData2 = '''
[
[
1,
2,
3,
4
],
[
11,
22,
33,
44
],
[
"aaa",
"bbb",
"ccc"
]
]
''';
```
### Linkage
```dart
const PickerData = '''
[
{
"a": [
{
"a1": [
1,
2,
3,
4
]
},
{
"a2": [
5,
6,
7,
8
]
},
{
"a3": [
9,
10,
11,
12
]
}
]
},
{
"b": [
{
"b1": [
11,
22,
33,
44
]
},
{
"b2": [
55,
66,
77,
88
]
},
{
"b3": [
99,
1010,
1111,
1212
]
}
]
},
{
"c": [
{
"c1": [
"a",
"b",
"c"
]
},
{
"c2": [
"aa",
"bb",
"cc"
]
},
{
"c3": [
"aaa",
"bbb",
"ccc"
]
}
]
}
]
''';
```
\ No newline at end of file
.DS_Store
.dart_tool/
.packages
.pub/
build/
.flutter-plugins
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled.
version:
revision: cd41fdd495f6944ecd3506c21e94c6567b073278
channel: stable
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
base_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
- platform: android
create_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
base_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
- platform: ios
create_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
base_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
- platform: linux
create_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
base_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
- platform: macos
create_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
base_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
- platform: web
create_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
base_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
- platform: windows
create_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
base_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'
# flutter_picker_example
Demonstrates how to use the flutter_picker plugin.
## Getting Started
For help getting started with Flutter, view our online
[documentation](https://flutter.io/).
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at
# https://dart-lang.github.io/linter/lints/index.html.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
# Remember to never publicly share your keystore.
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
key.properties
**/*.keystore
**/*.jks
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion flutter.compileSdkVersion
ndkVersion flutter.ndkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.yangyxd.example"
// You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
minSdkVersion flutter.minSdkVersion
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
}
}
}
flutter {
source '../..'
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.yangyxd.example">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.yangyxd.example">
<application
android:label="example"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>
package com.yangyxd.example
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity() {
}
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.yangyxd.example">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
buildscript {
ext.kotlin_version = '1.6.10'
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.1.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
}
task clean(type: Delete) {
delete rootProject.buildDir
}
org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true
#Fri Jun 23 08:50:38 CEST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip
include ':app'
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
def properties = new Properties()
assert localPropertiesFile.exists()
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
.idea/
.vagrant/
.sconsign.dblite
.svn/
.DS_Store
*.swp
profile
DerivedData/
build/
GeneratedPluginRegistrant.h
GeneratedPluginRegistrant.m
.generated/
*.pbxuser
*.mode1v3
*.mode2v3
*.perspectivev3
!default.pbxuser
!default.mode1v3
!default.mode2v3
!default.perspectivev3
xcuserdata
*.moved-aside
*.pyc
*sync/
Icon?
.tags*
/Flutter/app.flx
/Flutter/app.zip
/Flutter/flutter_assets/
/Flutter/App.framework
/Flutter/Flutter.framework
/Flutter/Generated.xcconfig
/ServiceDefinitions.json
Pods/
.symlinks/
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>12.0</string>
</dict>
</plist>
#include "Generated.xcconfig"
#include "Generated.xcconfig"
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; };
9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB31CF90195004384FC /* Generated.xcconfig */; };
978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; };
97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
);
name = Flutter;
sourceTree = "<group>";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
CF3B75C9A7D2FA2A4C99F110 /* Frameworks */,
);
sourceTree = "<group>";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
);
name = Products;
sourceTree = "<group>";
};
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */,
7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */,
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
97C146F11CF9000F007C117D /* Supporting Files */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
);
path = Runner;
sourceTree = "<group>";
};
97C146F11CF9000F007C117D /* Supporting Files */ = {
isa = PBXGroup;
children = (
97C146F21CF9000F007C117D /* main.m */,
);
name = "Supporting Files";
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "The Chromium Authors";
TargetAttributes = {
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
);
name = "Thin Binary";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */,
97C146F31CF9000F007C117D /* main.m in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C146FB1CF9000F007C117D /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C147001CF9000F007C117D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
97C147061CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = com.yangyxd.flutterPickerExample;
PRODUCT_NAME = "$(TARGET_NAME)";
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
97C147071CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = com.yangyxd.flutterPickerExample;
PRODUCT_NAME = "$(TARGET_NAME)";
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147031CF9000F007C117D /* Debug */,
97C147041CF9000F007C117D /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147061CF9000F007C117D /* Debug */,
97C147071CF9000F007C117D /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>
#import <Flutter/Flutter.h>
#import <UIKit/UIKit.h>
@interface AppDelegate : FlutterAppDelegate
@end
#include "AppDelegate.h"
#include "GeneratedPluginRegistrant.h"
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[GeneratedPluginRegistrant registerWithRegistry:self];
// Override point for customization after application launch.
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
@end
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
{
"images" : [
{
"idiom" : "universal",
"filename" : "LaunchImage.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
# Launch Screen Assets
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
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.
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
</resources>
</document>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>flutter_picker_example</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
</dict>
</plist>
#import "GeneratedPluginRegistrant.h"
#import <Flutter/Flutter.h>
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char* argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
import 'dart:io';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'dart:convert';
import 'package:flutter_picker/flutter_picker.dart';
import 'package:flutter_picker_example/picker_test.dart';
import 'picker_data.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
final String _fontFamily = Platform.isWindows ? "Roboto" : "";
const Set<PointerDeviceKind> _kTouchLikeDeviceTypes = <PointerDeviceKind>{
PointerDeviceKind.touch,
PointerDeviceKind.mouse,
PointerDeviceKind.stylus,
PointerDeviceKind.invertedStylus,
PointerDeviceKind.unknown
};
class _MyAppState extends State<MyApp> {
bool dark = false;
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
fontFamily: _fontFamily,
brightness: dark ? Brightness.dark : Brightness.light,
primaryTextTheme: TextTheme().apply(fontFamily: _fontFamily),
),
scrollBehavior: const MaterialScrollBehavior()
.copyWith(scrollbars: true, dragDevices: _kTouchLikeDeviceTypes),
localizationsDelegates: [
PickerLocalizationsDelegate.delegate, // 如果要使用本地化,请添加此行,则可以显示中文按钮
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
supportedLocales: [
const Locale('en', 'US'),
const Locale('zh', 'CH'),
const Locale('ko', 'KO'),
const Locale('it', 'IT'),
const Locale('ar', 'AR'),
const Locale('tr', 'TR'),
const Locale('ro', 'RO'),
],
home: MyHomePage());
}
}
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final double listSpec = 4.0;
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
String stateText = "";
@override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
title: Text('Picker ${stateText.isEmpty ? "" : " - " + stateText}'),
automaticallyImplyLeading: false,
elevation: 0.0,
actions: [
IconButton(
onPressed: () {
final appState = context.findAncestorStateOfType<_MyAppState>();
if (appState == null) return;
appState.setState(() {
appState.dark = !appState.dark;
});
},
icon: Icon(Icons.sunny_snowing)),
IconButton(
onPressed: () => Navigator.push(
context, MaterialPageRoute(builder: (v) => RawPickerTest())),
icon: Icon(Icons.add)),
],
),
body: Builder(
builder: (context) => ListView(
padding: EdgeInsets.all(10.0),
children: <Widget>[
ListTile(
title: Text('1. Picker Show'),
onTap: () {
showPicker(context);
},
),
ListTile(
title: Text('2. Picker Show Modal'),
onTap: () {
showPickerModal(context);
},
),
ListTile(
title: Text('3. Picker Show Icons'),
onTap: () {
showPickerIcons(context);
},
),
ListTile(
title: Text('4. Picker Show (Array)'),
onTap: () {
showPickerArray(context);
},
),
ListTile(
title: Text('5. Picker Show Number'),
onTap: () {
showPickerNumber(context);
},
),
ListTile(
title: Text('6. Picker Show Number FormatValue'),
onTap: () {
showPickerNumberFormatValue(context);
},
),
ListTile(
title: Text('7. Picker Show Date'),
onTap: () {
showPickerDate(context);
},
),
ListTile(
title: Text('8. Picker Show Datetime'),
onTap: () {
showPickerDateTime(context);
},
),
ListTile(
title: Text('9. Picker Show Date (Custom)'),
onTap: () {
showPickerDateCustom(context);
},
),
ListTile(
title: Text('10. Picker Show Datetime (24)'),
onTap: () {
showPickerDateTime24(context);
},
),
ListTile(
title: Text('11. Picker Show Datetime (Round background)'),
onTap: () {
showPickerDateTimeRoundBg(context);
},
),
ListTile(
title: Text('12. Picker Show Date Range'),
onTap: () {
showPickerDateRange(context);
},
),
ListTile(
title: Text('13. DurationPicker (time)'),
onTap: () {
showPickerDurationSelect(context);
},
),
ListTile(
title: Text('14. Customize UI effects (time)'),
onTap: () {
showPickerCustomizeUI(context);
},
),
ListTile(
title: Text('15. Use onBuilderItem'),
onTap: () {
showPickerCustomBuilder(context);
},
),
ListTile(
title: Text('16. Select year'),
onTap: () {
showPickerSelectYear(context);
},
),
const SizedBox(height: 60),
],
)),
);
}
showMsg(String msg) {
final state = ScaffoldMessenger.of(context);
state.showSnackBar(SnackBar(content: Text(msg)));
}
showPicker(BuildContext context) async {
Picker picker = Picker(
adapter: PickerDataAdapter<String>(
pickerData: JsonDecoder().convert(PickerData)),
changeToFirst: false,
textAlign: TextAlign.left,
textStyle: TextStyle(
color: Theme.of(context).brightness == Brightness.dark
? Colors.yellow
: Colors.blue,
fontFamily: _fontFamily),
selectedTextStyle: TextStyle(color: Colors.red),
columnPadding: const EdgeInsets.all(8.0),
onConfirm: (Picker picker, List value) {
print(value.toString());
print(picker.getSelectedValues());
});
picker.showBottomSheet(context);
}
showPickerModal(BuildContext context) async {
final result = await Picker(
adapter: PickerDataAdapter<String>(
pickerData: JsonDecoder().convert(PickerData)),
changeToFirst: true,
hideHeader: false,
selectedTextStyle: TextStyle(color: Colors.blue),
// builderHeader: (context) {
// final picker = PickerWidget.of(context);
// return picker?.data?.title ?? Text("xxx");
// },
onConfirm: (picker, value) {
print(value.toString());
print(picker.adapter.text);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Scaffold(
appBar: AppBar(title: Text("Hello")),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("You click the Confirm button."),
SizedBox(height: 32),
Text("result: \n ${picker.adapter.text}")
],
)),
)));
}).showModal(this.context); //_sca
print("result: $result"); // ffoldKey.currentState);
}
showPickerIcons(BuildContext context) {
Picker(
adapter: PickerDataAdapter(data: [
PickerItem(text: Icon(Icons.add), value: Icons.add, children: [
PickerItem(text: Icon(Icons.more)),
PickerItem(text: Icon(Icons.aspect_ratio)),
PickerItem(text: Icon(Icons.android)),
PickerItem(text: Icon(Icons.menu), children: [
// 测试:多加了一维数据
PickerItem(text: Icon(Icons.account_box)),
PickerItem(text: Icon(Icons.analytics)),
]),
]),
PickerItem(text: Icon(Icons.title), value: Icons.title, children: [
PickerItem(text: Icon(Icons.more_vert)),
PickerItem(text: Icon(Icons.ac_unit)),
PickerItem(text: Icon(Icons.access_alarm)),
PickerItem(text: Icon(Icons.account_balance)),
]),
PickerItem(text: Icon(Icons.face), value: Icons.face, children: [
PickerItem(text: Icon(Icons.add_circle_outline)),
PickerItem(text: Icon(Icons.add_a_photo)),
PickerItem(text: Icon(Icons.access_time)),
PickerItem(text: Icon(Icons.adjust)),
]),
PickerItem(
text: Icon(Icons.linear_scale),
value: Icons.linear_scale,
children: [
PickerItem(text: Icon(Icons.assistant_photo)),
PickerItem(text: Icon(Icons.account_balance)),
PickerItem(text: Icon(Icons.airline_seat_legroom_extra)),
PickerItem(text: Icon(Icons.airport_shuttle)),
PickerItem(text: Icon(Icons.settings_bluetooth)),
]),
PickerItem(text: Icon(Icons.close), value: Icons.close),
]),
title: Text("Select Icon"),
selectedTextStyle: TextStyle(color: Colors.blue, fontSize: 12),
onConfirm: (Picker picker, List value) {
print(value.toString());
print(picker.getSelectedValues());
},
).showBottomSheet(context);
}
showPickerDialog(BuildContext context) {
Picker(
adapter: PickerDataAdapter<String>(
pickerData: JsonDecoder().convert(PickerData)),
hideHeader: true,
title: Text("Select Data"),
selectedTextStyle: TextStyle(color: Colors.blue),
onConfirm: (Picker picker, List value) {
print(value.toString());
print(picker.getSelectedValues());
}).showDialog(context);
}
showPickerArray(BuildContext context) {
Picker(
adapter: PickerDataAdapter<String>(
pickerData: JsonDecoder().convert(PickerData2),
isArray: true,
),
hideHeader: true,
selecteds: [3, 0, 2],
title: Text("Please Select"),
selectedTextStyle: TextStyle(color: Colors.blue),
cancel: TextButton(
onPressed: () {
Navigator.pop(context);
},
child: Icon(Icons.child_care)),
onConfirm: (Picker picker, List value) {
print(value.toString());
print(picker.getSelectedValues());
}).showDialog(context);
}
showPickerNumber(BuildContext context) {
Picker(
adapter: NumberPickerAdapter(data: [
NumberPickerColumn(
begin: 0,
end: 999,
postfix: Text("\$"),
suffix: Icon(Icons.insert_emoticon)),
NumberPickerColumn(begin: 200, end: 100, jump: -10),
]),
delimiter: [
PickerDelimiter(
child: Container(
width: 30.0,
alignment: Alignment.center,
child: Icon(Icons.more_vert),
))
],
hideHeader: true,
title: Text("Please Select"),
selectedTextStyle: TextStyle(color: Colors.blue),
onConfirm: (Picker picker, List value) {
print(value.toString());
print(picker.getSelectedValues());
}).showDialog(context);
}
showPickerNumberFormatValue(BuildContext context) {
Picker(
adapter: NumberPickerAdapter(data: [
NumberPickerColumn(
begin: 0,
end: 999,
onFormatValue: (v) {
return v < 10 ? "0$v" : "$v";
}),
NumberPickerColumn(begin: 100, end: 200),
]),
delimiter: [
PickerDelimiter(
child: Container(
width: 30.0,
alignment: Alignment.center,
child: Icon(Icons.more_vert),
))
],
hideHeader: true,
title: Text("Please Select"),
selectedTextStyle: TextStyle(color: Colors.blue),
onConfirm: (Picker picker, List value) {
print(value.toString());
print(picker.getSelectedValues());
}).showDialog(context);
}
showPickerDate(BuildContext context) {
Picker(
hideHeader: true,
adapter: DateTimePickerAdapter(),
title: Text("Select Data"),
selectedTextStyle: TextStyle(color: Colors.blue),
onConfirm: (Picker picker, List value) {
print((picker.adapter as DateTimePickerAdapter).value);
}).showDialog(context);
}
showPickerDateCustom(BuildContext context) {
Picker(
hideHeader: true,
adapter: DateTimePickerAdapter(
customColumnType: [2, 1, 0, 3, 4],
),
title: Text("Select Data"),
selectedTextStyle: TextStyle(color: Colors.blue),
onConfirm: (Picker picker, List value) {
print((picker.adapter as DateTimePickerAdapter).value);
}).showDialog(context);
}
showPickerDateTime(BuildContext context) {
Picker(
adapter: DateTimePickerAdapter(
type: PickerDateTimeType.kMDYHM_AP,
isNumberMonth: true,
//strAMPM: const["上午", "下午"],
yearSuffix: "年",
monthSuffix: "月",
daySuffix: "日",
hourSuffix: "時",
minuteSuffix: "分",
secondSuffix: "秒",
minValue: DateTime.now(),
minuteInterval: 30,
//value: DateTime.tryParse("2026-01-29 00:00:00.000"),
//minHour: 1,
//maxHour: 23,
// twoDigitYear: true,
),
title: Text("Select DateTime"),
textAlign: TextAlign.right,
// selectedTextStyle: TextStyle(color: Colors.blue),
delimiter: [
PickerDelimiter(
column: 5,
child: Container(
width: 16.0,
alignment: Alignment.center,
child: Text(':', style: TextStyle(fontWeight: FontWeight.bold)),
))
],
footer: Container(
height: 50.0,
alignment: Alignment.center,
child: Text('Footer'),
),
onConfirm: (Picker picker, List value) {
print(picker.adapter.text);
},
onSelect: (Picker picker, int index, List<int> selected) {
setState(() {
stateText = picker.adapter.toString();
});
}).showBottomSheet(context);
}
showPickerDateRange(BuildContext context) {
print("canceltext: ${PickerLocalizations.of(context).cancelText}");
Picker ps = Picker(
hideHeader: true,
adapter: DateTimePickerAdapter(
type: PickerDateTimeType.kYMD, isNumberMonth: true),
onConfirm: (Picker picker, List value) {
print((picker.adapter as DateTimePickerAdapter).value);
});
Picker pe = Picker(
hideHeader: true,
adapter: DateTimePickerAdapter(type: PickerDateTimeType.kYMD),
onConfirm: (Picker picker, List value) {
print((picker.adapter as DateTimePickerAdapter).value);
});
List<Widget> actions = [
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: Text(PickerLocalizations.of(context).cancelText ?? '')),
TextButton(
onPressed: () {
Navigator.pop(context);
ps.onConfirm?.call(ps, ps.selecteds);
pe.onConfirm?.call(pe, pe.selecteds);
},
child: Text(PickerLocalizations.of(context).confirmText ?? ''))
];
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text("Select Date Range"),
actions: actions,
content: Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text("Begin:"),
ps.makePicker(),
Text("End:"),
pe.makePicker()
],
),
),
);
});
}
showPickerDateTime24(BuildContext context) {
Picker(
adapter: DateTimePickerAdapter(
type: PickerDateTimeType.kMDYHM,
isNumberMonth: true,
yearSuffix: "年",
monthSuffix: "月",
daySuffix: "日",
hourSuffix: "時",
minuteSuffix: "分",
secondSuffix: "秒",
minHour: 8,
maxHour: 19,
yearBegin: 1950,
yearEnd: 1998,
),
title: Text("Select DateTime"),
onConfirm: (Picker picker, List value) {
print(picker.adapter.text);
},
onSelect: (Picker picker, int index, List<int> selected) {
showMsg(picker.adapter.toString());
}).showBottomSheet(context);
}
/// 圆角背景
showPickerDateTimeRoundBg(BuildContext context) {
var picker = Picker(
backgroundColor: Colors.transparent,
headerDecoration: BoxDecoration(
border:
Border(bottom: BorderSide(color: Colors.black12, width: 0.5))),
adapter: DateTimePickerAdapter(
type: PickerDateTimeType.kMDYHM,
isNumberMonth: true,
yearSuffix: "年",
monthSuffix: "月",
daySuffix: "日"),
delimiter: [
PickerDelimiter(
column: 3,
child: Container(
width: 8.0,
alignment: Alignment.center,
)),
PickerDelimiter(
column: 5,
child: Container(
width: 12.0,
alignment: Alignment.center,
child: Text(':', style: TextStyle(fontWeight: FontWeight.bold)),
)),
],
title: Text("Select DateTime"),
onConfirm: (Picker picker, List value) {
print(picker.adapter.text);
},
onSelect: (Picker picker, int index, List<int> selected) {
showMsg(picker.adapter.toString());
});
picker.showModal(context, backgroundColor: Colors.transparent,
builder: (context, view) {
return Material(
// color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10), topRight: Radius.circular(10)),
child: Container(
padding: const EdgeInsets.only(top: 4),
child: view,
));
});
}
/// 使用 onBuilderItem 方法
showPickerCustomBuilder(BuildContext context) {
Picker(
hideHeader: true,
adapter: DateTimePickerAdapter(
customColumnType: [2, 1, 0, 3, 4],
),
title: Text("Select Data"),
selectedTextStyle: TextStyle(color: Colors.blue),
onBuilderItem: (context, text, child, selected, col, index) {
if (col == 0 || selected) return null;
return Text(text ?? '',
style: TextStyle(
color: Colors.green,
));
},
onConfirm: (Picker picker, List value) {
print((picker.adapter as DateTimePickerAdapter).value);
}).showDialog(context);
}
/// 自定义UI效果
showPickerCustomizeUI(BuildContext context) {
final itemExtent = 42.0;
final bgColor = Colors.greenAccent.shade700;
final txtColor = Colors.white;
final txtStyle = TextStyle(color: txtColor);
final selectColor = Colors.black.withOpacity(0.20);
final delimiterChild = Align(
alignment: Alignment.center,
child: Container(width: 50, height: itemExtent, color: selectColor),
);
Picker(
itemExtent: itemExtent,
backgroundColor: Colors.transparent,
containerColor: bgColor,
selectionOverlay: Container(height: itemExtent, color: selectColor),
headerDecoration: BoxDecoration(color: Colors.black.withOpacity(0.05)),
textStyle: txtStyle,
cancelTextStyle: txtStyle,
confirmTextStyle: txtStyle,
selectedTextStyle: TextStyle(color: txtColor, fontSize: 20),
adapter: DateTimePickerAdapter(type: PickerDateTimeType.kHM),
delimiter: [
PickerDelimiter(column: 0, child: delimiterChild),
PickerDelimiter(
column: 2,
child: Align(
alignment: Alignment.center,
child: Container(
width: 15,
height: itemExtent,
color: selectColor,
alignment: Alignment.center,
child: Text(':',
style: TextStyle(
fontWeight: FontWeight.bold, color: txtColor)),
),
)),
PickerDelimiter(column: 4, child: delimiterChild),
],
title: Padding(
padding: const EdgeInsets.fromLTRB(0, 12, 0, 12),
child: Text("Select Time", style: txtStyle),
),
onConfirm: (Picker picker, List value) {
print(picker.adapter.text);
}).showModal(context, builder: (context, view) {
return Material(
elevation: 0.0,
color: Colors.transparent,
child: Container(
padding: const EdgeInsets.only(top: 0, left: 0, right: 0),
decoration: BoxDecoration(
color: bgColor,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10), topRight: Radius.circular(10)),
),
child: view,
));
}, backgroundColor: Colors.transparent);
}
showPickerDurationSelect(BuildContext context) {
final range = <DateTime?>[
DateTime(0, 1, 1, 8, 30),
DateTime(0, 1, 1, 14, 30)
];
final p1 = Picker(
adapter: DateTimePickerAdapter(
customColumnType: [6, 7, 4],
value: range[0],
),
delimiter: [
PickerDelimiter(
column: 0,
child: Container(
alignment: Alignment.center,
width: 100,
padding: EdgeInsets.fromLTRB(12, 0, 8, 0),
child: Text('Start',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.red,
fontSize: 14)),
// color: Colors.white,
)),
],
onSelect: (Picker picker, int index, List<int> selected) {
range[0] = (picker.adapter as DateTimePickerAdapter).value;
},
onConfirmBefore: (picker, selected) async {
if (range[0] == null) {
showMsg("Please select the start time.");
return false;
}
if (range[1] == null) {
showMsg("Please select the end time.");
return false;
}
return true;
},
onConfirm: (picker, selected) {
showMsg(
"Start: ${range[0].toString()}, End: ${range[1].toString()}, ${picker.adapter}");
});
final p2 = Picker(
adapter: DateTimePickerAdapter(
customColumnType: [6, 7, 4],
value: range[1],
),
hideHeader: true,
delimiter: [
PickerDelimiter(
column: 0,
child: Container(
alignment: Alignment.center,
padding: EdgeInsets.fromLTRB(12, 0, 8, 0),
width: 100,
child: Text('End',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.red,
fontSize: 14)),
// color: Colors.white,
)),
],
onSelect: (Picker picker, int index, List<int> selected) {
range[1] = (picker.adapter as DateTimePickerAdapter).value;
});
showBottomSheet(
context: context,
builder: (BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [p1.makePicker(), p2.makePicker()],
);
});
}
showPickerSelectYear(BuildContext context) {
Picker(
adapter: DateTimePickerAdapter(
type: PickerDateTimeType.kY,
yearSuffix: "年",
yearBegin: 1950,
yearEnd: 2025,
),
title: Text("Select Year"),
onConfirm: (Picker picker, List value) {
print(picker.adapter.text);
},
onSelect: (Picker picker, int index, List<int> selected) {
print(picker.adapter.toString());
}).showBottomSheet(context);
}
}
const PickerData = '''
[
{
"a": [
{
"a1": [
1,
2,
3,
4
]
},
{
"a2": [
5,
6,
7,
8,
555,
666,
999
]
},
{
"a3": [
9,
10,
11,
12
]
}
]
},
{
"b": [
{
"b1": [
11,
22,
33,
44
]
},
{
"b2": [
55,
66,
77,
88,
99,
1010,
1111,
1212,
1313,
1414,
1515,
1616
]
},
{
"b3": [
1010,
1111,
1212,
1313,
1414,
1515,
1616
]
}
]
},
{
"c": [
{
"c1": [
"a",
"b",
"c"
]
},
{
"c2": [
"aa",
"bb",
"cc"
]
},
{
"c3": [
"aaa",
"bbb",
"ccc"
]
},
{
"c4": [
"a1",
"b1",
"c1",
"d1"
]
}
]
}
]
''';
const PickerData2 = '''
[
[
1,
2,
3,
4
],
[
11,
22,
33,
44
],
[
"aaa",
"bbb",
"ccc"
]
]
''';
\ No newline at end of file
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class RawPickerTest extends StatefulWidget {
const RawPickerTest({Key? key}) : super(key: key);
@override
State<RawPickerTest> createState() => _RawPickerTestState();
}
class _RawPickerTestState extends State<RawPickerTest> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Test Raw Picker')),
body: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(height: 50),
SizedBox(
height: 320,
child: Row(
children: [
Expanded(child: _buildCupertinoPicker(context, 0, 20, ValueKey(0))),
Expanded(child: _buildCupertinoPicker(context, 1, 120, ValueKey(1))),
Expanded(child: _buildCupertinoPicker(context, 2, 50, ValueKey(2))),
],
),
)
],
),
);
}
Widget _buildCupertinoPicker(BuildContext context,
int i, int _length, Key? key) {
return CupertinoPicker.builder(
key: key,
scrollController: FixedExtentScrollController(),
itemExtent: 42,
selectionOverlay: const CupertinoPickerDefaultSelectionOverlay(),
childCount: _length,
itemBuilder: (context, index) {
return Text("$i.$index");
},
onSelectedItemChanged: (int _index) {
if (_length <= 0) return;
var index = _index % _length;
print("onSelectedItemChanged. col: $i, row: $index");
},
);
}
}
name: flutter_picker_example
description: Demonstrates how to use the flutter_picker plugin.
# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# Read more about versioning at semver.org.
version: 1.0.0+1
environment:
sdk: '>=2.12.0 <3.0.0'
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
# intl: 0.18.0
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.5
dev_dependencies:
flutter_test:
sdk: flutter
flutter_picker:
path: ../
# For information on the generic Dart part of this file, see the
# following page: https://www.dartlang.org/tools/pub/pubspec
# The following section is specific to Flutter.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
# To add assets to your application, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.io/assets-and-images/#resolution-aware.
# For details regarding adding assets from package dependencies, see
# https://flutter.io/assets-and-images/#from-packages
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.io/custom-fonts/#from-packages
<!DOCTYPE html>
<html>
<head>
<!--
If you are serving your web app in a path other than the root, change the
href value below to reflect the base path you are serving from.
The path provided below has to start and end with a slash "/" in order for
it to work correctly.
For more details:
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
This is a placeholder for base href that will be replaced by the value of
the `--base-href` argument provided to `flutter build`.
-->
<base href="$FLUTTER_BASE_HREF">
<meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="description" content="A new Flutter project.">
<!-- iOS meta tags & icons -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="example">
<link rel="apple-touch-icon" href="icons/Icon-192.png">
<!-- Favicon -->
<link rel="icon" type="image/png" href="favicon.png"/>
<title>example</title>
<link rel="manifest" href="manifest.json">
<script>
// The value below is injected by flutter build, do not touch.
var serviceWorkerVersion = null;
</script>
<!-- This script adds the flutter initialization JS code -->
<script src="flutter.js" defer></script>
</head>
<body>
<script>
window.addEventListener('load', function(ev) {
// Download main.dart.js
_flutter.loader.loadEntrypoint({
serviceWorker: {
serviceWorkerVersion: serviceWorkerVersion,
}
}).then(function(engineInitializer) {
return engineInitializer.initializeEngine();
}).then(function(appRunner) {
return appRunner.runApp();
});
});
</script>
</body>
</html>
{
"name": "example",
"short_name": "example",
"start_url": ".",
"display": "standalone",
"background_color": "#0175C2",
"theme_color": "#0175C2",
"description": "A new Flutter project.",
"orientation": "portrait-primary",
"prefer_related_applications": false,
"icons": [
{
"src": "icons/Icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "icons/Icon-512.png",
"sizes": "512x512",
"type": "image/png"
},
{
"src": "icons/Icon-maskable-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "icons/Icon-maskable-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
]
}
flutter/ephemeral/
# Visual Studio user-specific files.
*.suo
*.user
*.userosscache
*.sln.docstates
# Visual Studio build-related files.
x64/
x86/
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
cmake_minimum_required(VERSION 3.15)
project(example LANGUAGES CXX)
set(BINARY_NAME "example")
cmake_policy(SET CMP0063 NEW)
set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
# Configure build options.
get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
if(IS_MULTICONFIG)
set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release"
CACHE STRING "" FORCE)
else()
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE "Debug" CACHE
STRING "Flutter build mode" FORCE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
"Debug" "Profile" "Release")
endif()
endif()
set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}")
set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}")
set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}")
set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}")
# Use Unicode for all projects.
add_definitions(-DUNICODE -D_UNICODE)
# Compilation settings that should be applied to most targets.
function(APPLY_STANDARD_SETTINGS TARGET)
target_compile_features(${TARGET} PUBLIC cxx_std_17)
target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100")
target_compile_options(${TARGET} PRIVATE /EHsc)
target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0")
target_compile_definitions(${TARGET} PRIVATE "$<$<CONFIG:Debug>:_DEBUG>")
endfunction()
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
# Flutter library and tool build rules.
add_subdirectory(${FLUTTER_MANAGED_DIR})
# Application build
add_subdirectory("runner")
# Generated plugin build rules, which manage building the plugins and adding
# them to the application.
include(flutter/generated_plugins.cmake)
# === Installation ===
# Support files are copied into place next to the executable, so that it can
# run in place. This is done instead of making a separate bundle (as on Linux)
# so that building and running from within Visual Studio will work.
set(BUILD_BUNDLE_DIR "$<TARGET_FILE_DIR:${BINARY_NAME}>")
# Make the "install" step default, as it's required to run.
set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1)
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
endif()
set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}")
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
COMPONENT Runtime)
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
COMPONENT Runtime)
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
if(PLUGIN_BUNDLED_LIBRARIES)
install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
endif()
# Fully re-copy the assets directory on each build to avoid having stale files
# from a previous install.
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
install(CODE "
file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
" COMPONENT Runtime)
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
# Install the AOT library on non-Debug builds only.
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
CONFIGURATIONS Profile;Release
COMPONENT Runtime)
cmake_minimum_required(VERSION 3.15)
set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
# Configuration provided via flutter tool.
include(${EPHEMERAL_DIR}/generated_config.cmake)
# TODO: Move the rest of this into files in ephemeral. See
# https://github.com/flutter/flutter/issues/57146.
set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper")
# === Flutter Library ===
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll")
# Published to parent scope for install step.
set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE)
list(APPEND FLUTTER_LIBRARY_HEADERS
"flutter_export.h"
"flutter_windows.h"
"flutter_messenger.h"
"flutter_plugin_registrar.h"
"flutter_texture_registrar.h"
)
list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/")
add_library(flutter INTERFACE)
target_include_directories(flutter INTERFACE
"${EPHEMERAL_DIR}"
)
target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib")
add_dependencies(flutter flutter_assemble)
# === Wrapper ===
list(APPEND CPP_WRAPPER_SOURCES_CORE
"core_implementations.cc"
"standard_codec.cc"
)
list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/")
list(APPEND CPP_WRAPPER_SOURCES_PLUGIN
"plugin_registrar.cc"
)
list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/")
list(APPEND CPP_WRAPPER_SOURCES_APP
"flutter_engine.cc"
"flutter_view_controller.cc"
)
list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/")
# Wrapper sources needed for a plugin.
add_library(flutter_wrapper_plugin STATIC
${CPP_WRAPPER_SOURCES_CORE}
${CPP_WRAPPER_SOURCES_PLUGIN}
)
apply_standard_settings(flutter_wrapper_plugin)
set_target_properties(flutter_wrapper_plugin PROPERTIES
POSITION_INDEPENDENT_CODE ON)
set_target_properties(flutter_wrapper_plugin PROPERTIES
CXX_VISIBILITY_PRESET hidden)
target_link_libraries(flutter_wrapper_plugin PUBLIC flutter)
target_include_directories(flutter_wrapper_plugin PUBLIC
"${WRAPPER_ROOT}/include"
)
add_dependencies(flutter_wrapper_plugin flutter_assemble)
# Wrapper sources needed for the runner.
add_library(flutter_wrapper_app STATIC
${CPP_WRAPPER_SOURCES_CORE}
${CPP_WRAPPER_SOURCES_APP}
)
apply_standard_settings(flutter_wrapper_app)
target_link_libraries(flutter_wrapper_app PUBLIC flutter)
target_include_directories(flutter_wrapper_app PUBLIC
"${WRAPPER_ROOT}/include"
)
add_dependencies(flutter_wrapper_app flutter_assemble)
# === Flutter tool backend ===
# _phony_ is a non-existent file to force this command to run every time,
# since currently there's no way to get a full input/output list from the
# flutter tool.
set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_")
set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE)
add_custom_command(
OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN}
${CPP_WRAPPER_SOURCES_APP}
${PHONY_OUTPUT}
COMMAND ${CMAKE_COMMAND} -E env
${FLUTTER_TOOL_ENVIRONMENT}
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat"
windows-x64 $<CONFIG>
VERBATIM
)
add_custom_target(flutter_assemble DEPENDS
"${FLUTTER_LIBRARY}"
${FLUTTER_LIBRARY_HEADERS}
${CPP_WRAPPER_SOURCES_CORE}
${CPP_WRAPPER_SOURCES_PLUGIN}
${CPP_WRAPPER_SOURCES_APP}
)
//
// Generated file. Do not edit.
//
// clang-format off
#include "generated_plugin_registrant.h"
void RegisterPlugins(flutter::PluginRegistry* registry) {
}
//
// Generated file. Do not edit.
//
// clang-format off
#ifndef GENERATED_PLUGIN_REGISTRANT_
#define GENERATED_PLUGIN_REGISTRANT_
#include <flutter/plugin_registry.h>
// Registers Flutter plugins.
void RegisterPlugins(flutter::PluginRegistry* registry);
#endif // GENERATED_PLUGIN_REGISTRANT_
#
# Generated file, do not edit.
#
list(APPEND FLUTTER_PLUGIN_LIST
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
)
set(PLUGIN_BUNDLED_LIBRARIES)
foreach(plugin ${FLUTTER_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
endforeach(plugin)
foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin})
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
endforeach(ffi_plugin)
cmake_minimum_required(VERSION 3.15)
project(runner LANGUAGES CXX)
add_executable(${BINARY_NAME} WIN32
"flutter_window.cpp"
"main.cpp"
"run_loop.cpp"
"utils.cpp"
"win32_window.cpp"
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
"Runner.rc"
"runner.exe.manifest"
)
apply_standard_settings(${BINARY_NAME})
target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
add_dependencies(${BINARY_NAME} flutter_assemble)
// Microsoft Visual C++ generated resource script.
//
#pragma code_page(65001)
#include "resource.h"
#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h"
/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
// English (United States) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//
1 TEXTINCLUDE
BEGIN
"resource.h\0"
END
2 TEXTINCLUDE
BEGIN
"#include ""winres.h""\r\n"
"\0"
END
3 TEXTINCLUDE
BEGIN
"\r\n"
"\0"
END
#endif // APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Icon
//
// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
IDI_APP_ICON ICON "resources\\app_icon.ico"
/////////////////////////////////////////////////////////////////////////////
//
// Version
//
#ifdef FLUTTER_BUILD_NUMBER
#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER
#else
#define VERSION_AS_NUMBER 1,0,0
#endif
#ifdef FLUTTER_BUILD_NAME
#define VERSION_AS_STRING #FLUTTER_BUILD_NAME
#else
#define VERSION_AS_STRING "1.0.0"
#endif
VS_VERSION_INFO VERSIONINFO
FILEVERSION VERSION_AS_NUMBER
PRODUCTVERSION VERSION_AS_NUMBER
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
#ifdef _DEBUG
FILEFLAGS VS_FF_DEBUG
#else
FILEFLAGS 0x0L
#endif
FILEOS VOS__WINDOWS32
FILETYPE VFT_APP
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904e4"
BEGIN
VALUE "CompanyName", "com.yangyxd" "\0"
VALUE "FileDescription", "A new Flutter project." "\0"
VALUE "FileVersion", VERSION_AS_STRING "\0"
VALUE "InternalName", "example" "\0"
VALUE "LegalCopyright", "Copyright (C) 2021 com.yangyxd. All rights reserved." "\0"
VALUE "OriginalFilename", "example.exe" "\0"
VALUE "ProductName", "example" "\0"
VALUE "ProductVersion", VERSION_AS_STRING "\0"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1252
END
END
#endif // English (United States) resources
/////////////////////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
/////////////////////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED
#include "flutter_window.h"
#include <optional>
#include "flutter/generated_plugin_registrant.h"
FlutterWindow::FlutterWindow(RunLoop* run_loop,
const flutter::DartProject& project)
: run_loop_(run_loop), project_(project) {}
FlutterWindow::~FlutterWindow() {}
bool FlutterWindow::OnCreate() {
if (!Win32Window::OnCreate()) {
return false;
}
RECT frame = GetClientArea();
// The size here must match the window dimensions to avoid unnecessary surface
// creation / destruction in the startup path.
flutter_controller_ = std::make_unique<flutter::FlutterViewController>(
frame.right - frame.left, frame.bottom - frame.top, project_);
// Ensure that basic setup of the controller was successful.
if (!flutter_controller_->engine() || !flutter_controller_->view()) {
return false;
}
RegisterPlugins(flutter_controller_->engine());
run_loop_->RegisterFlutterInstance(flutter_controller_->engine());
SetChildContent(flutter_controller_->view()->GetNativeWindow());
return true;
}
void FlutterWindow::OnDestroy() {
if (flutter_controller_) {
run_loop_->UnregisterFlutterInstance(flutter_controller_->engine());
flutter_controller_ = nullptr;
}
Win32Window::OnDestroy();
}
LRESULT
FlutterWindow::MessageHandler(HWND hwnd, UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept {
// Give Flutter, including plugins, an opporutunity to handle window messages.
if (flutter_controller_) {
std::optional<LRESULT> result =
flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam,
lparam);
if (result) {
return *result;
}
}
switch (message) {
case WM_FONTCHANGE:
flutter_controller_->engine()->ReloadSystemFonts();
break;
}
return Win32Window::MessageHandler(hwnd, message, wparam, lparam);
}
#ifndef RUNNER_FLUTTER_WINDOW_H_
#define RUNNER_FLUTTER_WINDOW_H_
#include <flutter/dart_project.h>
#include <flutter/flutter_view_controller.h>
#include <memory>
#include "run_loop.h"
#include "win32_window.h"
// A window that does nothing but host a Flutter view.
class FlutterWindow : public Win32Window {
public:
// Creates a new FlutterWindow driven by the |run_loop|, hosting a
// Flutter view running |project|.
explicit FlutterWindow(RunLoop* run_loop,
const flutter::DartProject& project);
virtual ~FlutterWindow();
protected:
// Win32Window:
bool OnCreate() override;
void OnDestroy() override;
LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam,
LPARAM const lparam) noexcept override;
private:
// The run loop driving events for this window.
RunLoop* run_loop_;
// The project to run.
flutter::DartProject project_;
// The Flutter instance hosted by this window.
std::unique_ptr<flutter::FlutterViewController> flutter_controller_;
};
#endif // RUNNER_FLUTTER_WINDOW_H_
#include <flutter/dart_project.h>
#include <flutter/flutter_view_controller.h>
#include <windows.h>
#include "flutter_window.h"
#include "run_loop.h"
#include "utils.h"
int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
_In_ wchar_t *command_line, _In_ int show_command) {
// Attach to console when present (e.g., 'flutter run') or create a
// new console when running with a debugger.
if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
CreateAndAttachConsole();
}
// Initialize COM, so that it is available for use in the library and/or
// plugins.
::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
RunLoop run_loop;
flutter::DartProject project(L"data");
std::vector<std::string> command_line_arguments =
GetCommandLineArguments();
project.set_dart_entrypoint_arguments(std::move(command_line_arguments));
FlutterWindow window(&run_loop, project);
Win32Window::Point origin(10, 10);
Win32Window::Size size(1280, 720);
if (!window.CreateAndShow(L"example", origin, size)) {
return EXIT_FAILURE;
}
window.SetQuitOnClose(true);
run_loop.Run();
::CoUninitialize();
return EXIT_SUCCESS;
}
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by Runner.rc
//
#define IDI_APP_ICON 101
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 102
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1001
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif
#include "run_loop.h"
#include <windows.h>
#include <algorithm>
RunLoop::RunLoop() {}
RunLoop::~RunLoop() {}
void RunLoop::Run() {
bool keep_running = true;
TimePoint next_flutter_event_time = TimePoint::clock::now();
while (keep_running) {
std::chrono::nanoseconds wait_duration =
std::max(std::chrono::nanoseconds(0),
next_flutter_event_time - TimePoint::clock::now());
::MsgWaitForMultipleObjects(
0, nullptr, FALSE, static_cast<DWORD>(wait_duration.count() / 1000),
QS_ALLINPUT);
bool processed_events = false;
MSG message;
// All pending Windows messages must be processed; MsgWaitForMultipleObjects
// won't return again for items left in the queue after PeekMessage.
while (::PeekMessage(&message, nullptr, 0, 0, PM_REMOVE)) {
processed_events = true;
if (message.message == WM_QUIT) {
keep_running = false;
break;
}
::TranslateMessage(&message);
::DispatchMessage(&message);
// Allow Flutter to process messages each time a Windows message is
// processed, to prevent starvation.
next_flutter_event_time =
std::min(next_flutter_event_time, ProcessFlutterMessages());
}
// If the PeekMessage loop didn't run, process Flutter messages.
if (!processed_events) {
next_flutter_event_time =
std::min(next_flutter_event_time, ProcessFlutterMessages());
}
}
}
void RunLoop::RegisterFlutterInstance(
flutter::FlutterEngine* flutter_instance) {
flutter_instances_.insert(flutter_instance);
}
void RunLoop::UnregisterFlutterInstance(
flutter::FlutterEngine* flutter_instance) {
flutter_instances_.erase(flutter_instance);
}
RunLoop::TimePoint RunLoop::ProcessFlutterMessages() {
TimePoint next_event_time = TimePoint::max();
for (auto instance : flutter_instances_) {
std::chrono::nanoseconds wait_duration = instance->ProcessMessages();
if (wait_duration != std::chrono::nanoseconds::max()) {
next_event_time =
std::min(next_event_time, TimePoint::clock::now() + wait_duration);
}
}
return next_event_time;
}
#ifndef RUNNER_RUN_LOOP_H_
#define RUNNER_RUN_LOOP_H_
#include <flutter/flutter_engine.h>
#include <chrono>
#include <set>
// A runloop that will service events for Flutter instances as well
// as native messages.
class RunLoop {
public:
RunLoop();
~RunLoop();
// Prevent copying
RunLoop(RunLoop const&) = delete;
RunLoop& operator=(RunLoop const&) = delete;
// Runs the run loop until the application quits.
void Run();
// Registers the given Flutter instance for event servicing.
void RegisterFlutterInstance(
flutter::FlutterEngine* flutter_instance);
// Unregisters the given Flutter instance from event servicing.
void UnregisterFlutterInstance(
flutter::FlutterEngine* flutter_instance);
private:
using TimePoint = std::chrono::steady_clock::time_point;
// Processes all currently pending messages for registered Flutter instances.
TimePoint ProcessFlutterMessages();
std::set<flutter::FlutterEngine*> flutter_instances_;
};
#endif // RUNNER_RUN_LOOP_H_
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
</windowsSettings>
</application>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
<!-- Windows 8.1 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
<!-- Windows 8 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
<!-- Windows 7 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
</application>
</compatibility>
</assembly>
#include "utils.h"
#include <flutter_windows.h>
#include <io.h>
#include <stdio.h>
#include <windows.h>
#include <iostream>
void CreateAndAttachConsole() {
if (::AllocConsole()) {
FILE *unused;
if (freopen_s(&unused, "CONOUT$", "w", stdout)) {
_dup2(_fileno(stdout), 1);
}
if (freopen_s(&unused, "CONOUT$", "w", stderr)) {
_dup2(_fileno(stdout), 2);
}
std::ios::sync_with_stdio();
FlutterDesktopResyncOutputStreams();
}
}
std::vector<std::string> GetCommandLineArguments() {
// Convert the UTF-16 command line arguments to UTF-8 for the Engine to use.
int argc;
wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc);
if (argv == nullptr) {
return std::vector<std::string>();
}
std::vector<std::string> command_line_arguments;
// Skip the first argument as it's the binary name.
for (int i = 1; i < argc; i++) {
command_line_arguments.push_back(Utf8FromUtf16(argv[i]));
}
::LocalFree(argv);
return command_line_arguments;
}
std::string Utf8FromUtf16(const wchar_t* utf16_string) {
if (utf16_string == nullptr) {
return std::string();
}
int target_length = ::WideCharToMultiByte(
CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
-1, nullptr, 0, nullptr, nullptr);
if (target_length == 0) {
return std::string();
}
std::string utf8_string;
utf8_string.resize(target_length);
int converted_length = ::WideCharToMultiByte(
CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
-1, utf8_string.data(),
target_length, nullptr, nullptr);
if (converted_length == 0) {
return std::string();
}
return utf8_string;
}
#ifndef RUNNER_UTILS_H_
#define RUNNER_UTILS_H_
#include <string>
#include <vector>
// Creates a console for the process, and redirects stdout and stderr to
// it for both the runner and the Flutter library.
void CreateAndAttachConsole();
// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string
// encoded in UTF-8. Returns an empty std::string on failure.
std::string Utf8FromUtf16(const wchar_t* utf16_string);
// Gets the command line arguments passed in as a std::vector<std::string>,
// encoded in UTF-8. Returns an empty std::vector<std::string> on failure.
std::vector<std::string> GetCommandLineArguments();
#endif // RUNNER_UTILS_H_
#include "win32_window.h"
#include <flutter_windows.h>
#include "resource.h"
namespace {
constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW";
// The number of Win32Window objects that currently exist.
static int g_active_window_count = 0;
using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd);
// Scale helper to convert logical scaler values to physical using passed in
// scale factor
int Scale(int source, double scale_factor) {
return static_cast<int>(source * scale_factor);
}
// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module.
// This API is only needed for PerMonitor V1 awareness mode.
void EnableFullDpiSupportIfAvailable(HWND hwnd) {
HMODULE user32_module = LoadLibraryA("User32.dll");
if (!user32_module) {
return;
}
auto enable_non_client_dpi_scaling =
reinterpret_cast<EnableNonClientDpiScaling*>(
GetProcAddress(user32_module, "EnableNonClientDpiScaling"));
if (enable_non_client_dpi_scaling != nullptr) {
enable_non_client_dpi_scaling(hwnd);
FreeLibrary(user32_module);
}
}
} // namespace
// Manages the Win32Window's window class registration.
class WindowClassRegistrar {
public:
~WindowClassRegistrar() = default;
// Returns the singleton registar instance.
static WindowClassRegistrar* GetInstance() {
if (!instance_) {
instance_ = new WindowClassRegistrar();
}
return instance_;
}
// Returns the name of the window class, registering the class if it hasn't
// previously been registered.
const wchar_t* GetWindowClass();
// Unregisters the window class. Should only be called if there are no
// instances of the window.
void UnregisterWindowClass();
private:
WindowClassRegistrar() = default;
static WindowClassRegistrar* instance_;
bool class_registered_ = false;
};
WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr;
const wchar_t* WindowClassRegistrar::GetWindowClass() {
if (!class_registered_) {
WNDCLASS window_class{};
window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
window_class.lpszClassName = kWindowClassName;
window_class.style = CS_HREDRAW | CS_VREDRAW;
window_class.cbClsExtra = 0;
window_class.cbWndExtra = 0;
window_class.hInstance = GetModuleHandle(nullptr);
window_class.hIcon =
LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON));
window_class.hbrBackground = 0;
window_class.lpszMenuName = nullptr;
window_class.lpfnWndProc = Win32Window::WndProc;
RegisterClass(&window_class);
class_registered_ = true;
}
return kWindowClassName;
}
void WindowClassRegistrar::UnregisterWindowClass() {
UnregisterClass(kWindowClassName, nullptr);
class_registered_ = false;
}
Win32Window::Win32Window() {
++g_active_window_count;
}
Win32Window::~Win32Window() {
--g_active_window_count;
Destroy();
}
bool Win32Window::CreateAndShow(const std::wstring& title,
const Point& origin,
const Size& size) {
Destroy();
const wchar_t* window_class =
WindowClassRegistrar::GetInstance()->GetWindowClass();
const POINT target_point = {static_cast<LONG>(origin.x),
static_cast<LONG>(origin.y)};
HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST);
UINT dpi = FlutterDesktopGetDpiForMonitor(monitor);
double scale_factor = dpi / 96.0;
HWND window = CreateWindow(
window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE,
Scale(origin.x, scale_factor), Scale(origin.y, scale_factor),
Scale(size.width, scale_factor), Scale(size.height, scale_factor),
nullptr, nullptr, GetModuleHandle(nullptr), this);
if (!window) {
return false;
}
return OnCreate();
}
// static
LRESULT CALLBACK Win32Window::WndProc(HWND const window,
UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept {
if (message == WM_NCCREATE) {
auto window_struct = reinterpret_cast<CREATESTRUCT*>(lparam);
SetWindowLongPtr(window, GWLP_USERDATA,
reinterpret_cast<LONG_PTR>(window_struct->lpCreateParams));
auto that = static_cast<Win32Window*>(window_struct->lpCreateParams);
EnableFullDpiSupportIfAvailable(window);
that->window_handle_ = window;
} else if (Win32Window* that = GetThisFromHandle(window)) {
return that->MessageHandler(window, message, wparam, lparam);
}
return DefWindowProc(window, message, wparam, lparam);
}
LRESULT
Win32Window::MessageHandler(HWND hwnd,
UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept {
switch (message) {
case WM_DESTROY:
window_handle_ = nullptr;
Destroy();
if (quit_on_close_) {
PostQuitMessage(0);
}
return 0;
case WM_DPICHANGED: {
auto newRectSize = reinterpret_cast<RECT*>(lparam);
LONG newWidth = newRectSize->right - newRectSize->left;
LONG newHeight = newRectSize->bottom - newRectSize->top;
SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth,
newHeight, SWP_NOZORDER | SWP_NOACTIVATE);
return 0;
}
case WM_SIZE: {
RECT rect = GetClientArea();
if (child_content_ != nullptr) {
// Size and position the child window.
MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left,
rect.bottom - rect.top, TRUE);
}
return 0;
}
case WM_ACTIVATE:
if (child_content_ != nullptr) {
SetFocus(child_content_);
}
return 0;
}
return DefWindowProc(window_handle_, message, wparam, lparam);
}
void Win32Window::Destroy() {
OnDestroy();
if (window_handle_) {
DestroyWindow(window_handle_);
window_handle_ = nullptr;
}
if (g_active_window_count == 0) {
WindowClassRegistrar::GetInstance()->UnregisterWindowClass();
}
}
Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept {
return reinterpret_cast<Win32Window*>(
GetWindowLongPtr(window, GWLP_USERDATA));
}
void Win32Window::SetChildContent(HWND content) {
child_content_ = content;
SetParent(content, window_handle_);
RECT frame = GetClientArea();
MoveWindow(content, frame.left, frame.top, frame.right - frame.left,
frame.bottom - frame.top, true);
SetFocus(child_content_);
}
RECT Win32Window::GetClientArea() {
RECT frame;
GetClientRect(window_handle_, &frame);
return frame;
}
HWND Win32Window::GetHandle() {
return window_handle_;
}
void Win32Window::SetQuitOnClose(bool quit_on_close) {
quit_on_close_ = quit_on_close;
}
bool Win32Window::OnCreate() {
// No-op; provided for subclasses.
return true;
}
void Win32Window::OnDestroy() {
// No-op; provided for subclasses.
}
#ifndef RUNNER_WIN32_WINDOW_H_
#define RUNNER_WIN32_WINDOW_H_
#include <windows.h>
#include <functional>
#include <memory>
#include <string>
// A class abstraction for a high DPI-aware Win32 Window. Intended to be
// inherited from by classes that wish to specialize with custom
// rendering and input handling
class Win32Window {
public:
struct Point {
unsigned int x;
unsigned int y;
Point(unsigned int x, unsigned int y) : x(x), y(y) {}
};
struct Size {
unsigned int width;
unsigned int height;
Size(unsigned int width, unsigned int height)
: width(width), height(height) {}
};
Win32Window();
virtual ~Win32Window();
// Creates and shows a win32 window with |title| and position and size using
// |origin| and |size|. New windows are created on the default monitor. Window
// sizes are specified to the OS in physical pixels, hence to ensure a
// consistent size to will treat the width height passed in to this function
// as logical pixels and scale to appropriate for the default monitor. Returns
// true if the window was created successfully.
bool CreateAndShow(const std::wstring& title,
const Point& origin,
const Size& size);
// Release OS resources associated with window.
void Destroy();
// Inserts |content| into the window tree.
void SetChildContent(HWND content);
// Returns the backing Window handle to enable clients to set icon and other
// window properties. Returns nullptr if the window has been destroyed.
HWND GetHandle();
// If true, closing this window will quit the application.
void SetQuitOnClose(bool quit_on_close);
// Return a RECT representing the bounds of the current client area.
RECT GetClientArea();
protected:
// Processes and route salient window messages for mouse handling,
// size change and DPI. Delegates handling of these to member overloads that
// inheriting classes can handle.
virtual LRESULT MessageHandler(HWND window,
UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept;
// Called when CreateAndShow is called, allowing subclass window-related
// setup. Subclasses should return false if setup fails.
virtual bool OnCreate();
// Called when Destroy is called.
virtual void OnDestroy();
private:
friend class WindowClassRegistrar;
// OS callback called by message pump. Handles the WM_NCCREATE message which
// is passed when the non-client area is being created and enables automatic
// non-client DPI scaling so that the non-client area automatically
// responsponds to changes in DPI. All other messages are handled by
// MessageHandler.
static LRESULT CALLBACK WndProc(HWND const window,
UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept;
// Retrieves a class instance pointer for |window|
static Win32Window* GetThisFromHandle(HWND const window) noexcept;
bool quit_on_close_ = false;
// window handle for top level window.
HWND window_handle_ = nullptr;
// window handle for hosted content.
HWND child_content_ = nullptr;
};
#endif // RUNNER_WIN32_WINDOW_H_
export 'picker.dart';
export 'picker_localizations.dart';
export 'picker_localizations_delegate.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/material.dart' as Dialog;
import 'dart:async';
import 'picker_localizations.dart';
/// Picker selected callback.
typedef PickerSelectedCallback = void Function(
Picker picker, int index, List<int> selected);
/// Picker confirm callback.
typedef PickerConfirmCallback = void Function(
Picker picker, List<int> selected);
/// Picker confirm before callback.
typedef PickerConfirmBeforeCallback = Future<bool> Function(
Picker picker, List<int> selected);
/// Picker value format callback.
typedef PickerValueFormat<T> = String Function(T value);
/// Picker widget builder
typedef PickerWidgetBuilder = Widget Function(
BuildContext context, Widget pickerWidget);
/// Picker build item, If 'null' is returned, the default build is used
typedef PickerItemBuilder = Widget? Function(BuildContext context, String? text,
Widget? child, bool selected, int col, int index);
/// Picker
class Picker {
static const double DefaultTextSize = 18.0;
/// Index of currently selected items
late List<int> selecteds;
/// Picker adapter, Used to provide data and generate widgets
late PickerAdapter adapter;
/// insert separator before picker columns
final List<PickerDelimiter>? delimiter;
final VoidCallback? onCancel;
final PickerSelectedCallback? onSelect;
final PickerConfirmCallback? onConfirm;
final PickerConfirmBeforeCallback? onConfirmBefore;
/// When the previous level selection changes, scroll the child to the first item.
final changeToFirst;
/// Specify flex for each column
final List<int>? columnFlex;
final Widget? title;
final Widget? cancel;
final Widget? confirm;
final String? cancelText;
final String? confirmText;
final double height;
/// Height of list item
final double itemExtent;
final TextStyle? textStyle,
cancelTextStyle,
confirmTextStyle,
selectedTextStyle;
final TextAlign textAlign;
final IconThemeData? selectedIconTheme;
/// Text scaling factor
final double? textScaleFactor;
final EdgeInsetsGeometry? columnPadding;
final Color? backgroundColor, headerColor, containerColor;
/// Hide head
final bool hideHeader;
/// Show pickers in reversed order
final bool reversedOrder;
/// Generate a custom header, [hideHeader] = true
final WidgetBuilder? builderHeader;
/// Generate a custom item widget, If 'null' is returned, the default builder is used
final PickerItemBuilder? onBuilderItem;
/// List item loop
final bool looping;
/// Delay generation for smoother animation, This is the number of milliseconds to wait. It is recommended to > = 200
final int smooth;
final Widget? footer;
/// A widget overlaid on the picker to highlight the currently selected entry.
final Widget selectionOverlay;
final Decoration? headerDecoration;
final double magnification;
final double diameterRatio;
final double squeeze;
final bool printDebug;
Widget? _widget;
PickerWidgetState? _state;
Picker(
{required this.adapter,
this.delimiter,
List<int>? selecteds,
this.height = 150.0,
this.itemExtent = 28.0,
this.columnPadding,
this.textStyle,
this.cancelTextStyle,
this.confirmTextStyle,
this.selectedTextStyle,
this.selectedIconTheme,
this.textAlign = TextAlign.start,
this.textScaleFactor,
this.title,
this.cancel,
this.confirm,
this.cancelText,
this.confirmText,
this.backgroundColor,
this.containerColor,
this.headerColor,
this.builderHeader,
this.changeToFirst = false,
this.hideHeader = false,
this.looping = false,
this.reversedOrder = false,
this.headerDecoration,
this.columnFlex,
this.footer,
this.smooth = 0,
this.magnification = 1.0,
this.diameterRatio = 1.1,
this.squeeze = 1.45,
this.selectionOverlay = const CupertinoPickerDefaultSelectionOverlay(),
this.onBuilderItem,
this.onCancel,
this.onSelect,
this.onConfirmBefore,
this.onConfirm,
this.printDebug = false}) {
this.selecteds = selecteds == null ? <int>[] : selecteds;
}
Widget? get widget => _widget;
PickerWidgetState? get state => _state;
int _maxLevel = 1;
/// 生成picker控件
///
/// Build picker control
Widget makePicker([ThemeData? themeData, bool isModal = false, Key? key]) {
_maxLevel = adapter.maxLevel;
adapter.picker = this;
adapter.initSelects();
_widget = PickerWidget(
key: key ?? ValueKey(this),
child:
_PickerWidget(picker: this, themeData: themeData, isModal: isModal),
data: this,
);
return _widget!;
}
/// show picker bottom sheet
void show(
ScaffoldState state, {
ThemeData? themeData,
Color? backgroundColor,
PickerWidgetBuilder? builder,
}) {
state.showBottomSheet((BuildContext context) {
final picker = makePicker(themeData);
return builder == null ? picker : builder(context, picker);
}, backgroundColor: backgroundColor);
}
/// show picker bottom sheet
void showBottomSheet(
BuildContext context, {
ThemeData? themeData,
Color? backgroundColor,
PickerWidgetBuilder? builder,
}) {
Scaffold.of(context).showBottomSheet((BuildContext context) {
final picker = makePicker(themeData);
return builder == null ? picker : builder(context, picker);
}, backgroundColor: backgroundColor);
}
/// Display modal picker
Future<T?> showModal<T>(BuildContext context,
{ThemeData? themeData,
bool isScrollControlled = false,
bool useRootNavigator = false,
Color? backgroundColor,
PickerWidgetBuilder? builder}) async {
return await showModalBottomSheet<T>(
context: context, //state.context,
isScrollControlled: isScrollControlled,
useRootNavigator: useRootNavigator,
backgroundColor: backgroundColor,
builder: (BuildContext context) {
final picker = makePicker(themeData, true);
return builder == null ? picker : builder(context, picker);
});
}
/// show dialog picker
Future<List<int>?> showDialog(BuildContext context,
{bool barrierDismissible = true,
Color? backgroundColor,
PickerWidgetBuilder? builder,
Key? key}) {
return Dialog.showDialog<List<int>>(
context: context,
barrierDismissible: barrierDismissible,
builder: (BuildContext context) {
final actions = <Widget>[];
final theme = Theme.of(context);
final _cancel = PickerWidgetState._buildButton(
context, cancelText, cancel, cancelTextStyle, true, theme, () {
Navigator.pop<List<int>>(context, null);
if (onCancel != null) {
onCancel!();
}
});
if (_cancel != null) {
actions.add(_cancel);
}
final _confirm = PickerWidgetState._buildButton(
context, confirmText, confirm, confirmTextStyle, false, theme,
() async {
if (onConfirmBefore != null &&
!(await onConfirmBefore!(this, selecteds))) {
return; // Cancel;
}
Navigator.pop<List<int>>(context, selecteds);
if (onConfirm != null) {
onConfirm!(this, selecteds);
}
});
if (_confirm != null) {
actions.add(_confirm);
}
return AlertDialog(
key: key ?? Key('picker-dialog'),
title: title,
backgroundColor: backgroundColor,
actions: actions,
content: builder == null
? makePicker(theme)
: builder(context, makePicker(theme)),
);
});
}
/// 获取当前选择的值
/// Get the value of the current selection
List getSelectedValues() {
return adapter.getSelectedValues();
}
/// 取消
void doCancel(BuildContext context) {
Navigator.of(context).pop<List<int>>(null);
if (onCancel != null) onCancel!();
_widget = null;
}
/// 确定
void doConfirm(BuildContext context) async {
if (onConfirmBefore != null && !(await onConfirmBefore!(this, selecteds))) {
return; // Cancel;
}
Navigator.of(context).pop<List<int>>(selecteds);
if (onConfirm != null) onConfirm!(this, selecteds);
_widget = null;
}
/// 弹制更新指定列的内容
/// 当 onSelect 事件中,修改了当前列前面的列的内容时,可以调用此方法来更新显示
void updateColumn(int index, [bool all = false]) {
if (all) {
_state?.update();
return;
}
if (_state?._keys[index] != null) {
adapter.setColumn(index - 1);
_state?._keys[index]!(() => null);
}
}
static ButtonStyle _getButtonStyle(ButtonThemeData? theme,
[isCancelButton = false]) =>
TextButton.styleFrom(
minimumSize: Size(theme?.minWidth ?? 0.0, 42),
textStyle: TextStyle(
fontSize: Picker.DefaultTextSize,
color: isCancelButton ? null : theme?.colorScheme?.secondary,
),
padding: theme?.padding);
}
/// 分隔符
class PickerDelimiter {
final Widget? child;
final int column;
PickerDelimiter({required this.child, this.column = 1});
}
/// picker data list item
class PickerItem<T> {
/// 显示内容
final Widget? text;
/// 数据值
final T? value;
/// 子项
final List<PickerItem<T>>? children;
PickerItem({this.text, this.value, this.children});
}
class PickerWidget<T> extends InheritedWidget {
final Picker data;
const PickerWidget({Key? key, required this.data, required Widget child})
: super(key: key, child: child);
@override
bool updateShouldNotify(covariant PickerWidget oldWidget) =>
oldWidget.data != data;
static PickerWidget of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<PickerWidget>()
as PickerWidget;
}
}
class _PickerWidget<T> extends StatefulWidget {
final Picker picker;
final ThemeData? themeData;
final bool isModal;
_PickerWidget(
{Key? key, required this.picker, this.themeData, required this.isModal})
: super(key: key);
@override
PickerWidgetState createState() =>
PickerWidgetState<T>(picker: this.picker, themeData: this.themeData);
}
class PickerWidgetState<T> extends State<_PickerWidget> {
final Picker picker;
final ThemeData? themeData;
PickerWidgetState({required this.picker, this.themeData});
ThemeData? theme;
final List<FixedExtentScrollController> scrollController = [];
final List<StateSetter?> _keys = [];
@override
void initState() {
super.initState();
picker._state = this;
picker.adapter.doShow();
if (scrollController.length == 0) {
for (int i = 0; i < picker._maxLevel; i++) {
scrollController
.add(FixedExtentScrollController(initialItem: picker.selecteds[i]));
_keys.add(null);
}
}
}
void update() {
setState(() {});
}
// var ref = 0;
@override
Widget build(BuildContext context) {
// print("picker build ${ref++}");
theme = themeData ?? Theme.of(context);
if (_wait && picker.smooth > 0) {
Future.delayed(Duration(milliseconds: picker.smooth), () {
if (!_wait) return;
setState(() {
_wait = false;
});
});
} else
_wait = false;
final _body = <Widget>[];
if (!picker.hideHeader) {
if (picker.builderHeader != null) {
_body.add(picker.headerDecoration == null
? picker.builderHeader!(context)
: DecoratedBox(
child: picker.builderHeader!(context),
decoration: picker.headerDecoration!));
} else {
_body.add(DecoratedBox(
child: Row(
children: _buildHeaderViews(context),
),
decoration: picker.headerDecoration ??
BoxDecoration(
border: Border(
top: BorderSide(color: theme!.dividerColor, width: 0.5),
bottom: BorderSide(color: theme!.dividerColor, width: 0.5),
),
color: picker.headerColor == null
? theme?.bottomAppBarTheme.color
: picker.headerColor,
),
));
}
}
_body.add(_wait
? Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildViews(),
)
: AnimatedSwitcher(
duration: Duration(milliseconds: 300),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildViews(),
),
));
if (picker.footer != null) _body.add(picker.footer!);
Widget v = Column(
mainAxisSize: MainAxisSize.min,
children: _body,
);
if (widget.isModal) {
return GestureDetector(
onTap: () {},
child: v,
);
}
return v;
}
List<Widget>? _headerItems;
List<Widget> _buildHeaderViews(BuildContext context) {
if (_headerItems != null) {
return _headerItems!;
}
if (theme == null) theme = Theme.of(context);
List<Widget> items = [];
final _cancel = _buildButton(context, picker.cancelText, picker.cancel,
picker.cancelTextStyle, true, theme, () => picker.doCancel(context));
if (_cancel != null) {
items.add(_cancel);
}
items.add(Expanded(
child: picker.title == null
? SizedBox()
: DefaultTextStyle(
style: (theme!.textTheme.headlineMedium ?? theme!.textTheme.titleLarge)
?.copyWith(
fontSize: Picker.DefaultTextSize,
) ??
TextStyle(fontSize: Picker.DefaultTextSize),
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
child: picker.title!),
));
final _confirm = _buildButton(context, picker.confirmText, picker.confirm,
picker.confirmTextStyle, false, theme, () => picker.doConfirm(context));
if (_confirm != null) {
items.add(_confirm);
}
_headerItems = items;
return items;
}
static Widget? _buildButton(
BuildContext context,
String? text,
Widget? widget,
TextStyle? textStyle,
bool isCancel,
ThemeData? theme,
VoidCallback? onPressed) {
if (widget == null) {
String? _txt = text ??
(isCancel
? PickerLocalizations.of(context).cancelText
: PickerLocalizations.of(context).confirmText);
if (_txt == null || _txt.isEmpty) {
return null;
}
return TextButton(
style: Picker._getButtonStyle(ButtonTheme.of(context), isCancel),
onPressed: onPressed,
child: Text(_txt,
overflow: TextOverflow.ellipsis,
textScaleFactor: MediaQuery.of(context).textScaleFactor,
style: textStyle));
} else {
return textStyle == null
? widget
: DefaultTextStyle(style: textStyle, child: widget);
}
}
bool _changing = false;
bool _wait = true;
final Map<int, int> lastData = {};
List<Widget> _buildViews() {
if (picker.printDebug) print("_buildViews");
if (theme == null) theme = Theme.of(context);
for (int j = 0; j < _keys.length; j++) _keys[j] = null;
List<Widget> items = [];
PickerAdapter? adapter = picker.adapter;
adapter.setColumn(-1);
final _decoration = BoxDecoration(
color: picker.containerColor == null
? theme!.dialogBackgroundColor
: picker.containerColor,
);
if (adapter.length > 0) {
for (int i = 0; i < picker._maxLevel; i++) {
Widget view = Expanded(
flex: adapter.getColumnFlex(i),
child: Container(
padding: picker.columnPadding,
height: picker.height,
decoration: _decoration,
child: _wait
? null
: StatefulBuilder(
builder: (context, state) {
_keys[i] = state;
adapter.setColumn(i - 1);
if (picker.printDebug) print("builder. col: $i");
// 上一次是空列表
final _lastIsEmpty = scrollController[i].hasClients &&
!scrollController[i].position.hasContentDimensions;
final _length = adapter.length;
final _view = _buildCupertinoPicker(context, i, _length,
adapter, _lastIsEmpty ? ValueKey(_length) : null);
if (_lastIsEmpty ||
(!picker.changeToFirst &&
picker.selecteds[i] >= _length)) {
Timer(Duration(milliseconds: 100), () {
if (!this.mounted) return;
if (picker.printDebug) print("timer last");
var _len = adapter.length;
var _index = (_len < _length ? _len : _length) - 1;
if (scrollController[i]
.position
.hasContentDimensions) {
scrollController[i].jumpToItem(_index);
} else {
scrollController[i] = FixedExtentScrollController(
initialItem: _index);
if (_keys[i] != null) {
_keys[i]!(() => null);
}
}
});
}
return _view;
},
),
),
);
items.add(view);
}
}
if (picker.delimiter != null && !_wait) {
for (int i = 0; i < picker.delimiter!.length; i++) {
var o = picker.delimiter![i];
if (o.child == null) continue;
var item = SizedBox(
child: DecoratedBox(
decoration: _decoration,
child: o.child,
),
height: picker.height);
if (o.column < 0)
items.insert(0, item);
else if (o.column >= items.length)
items.add(item);
else
items.insert(o.column, item);
}
}
if (picker.reversedOrder) return items.reversed.toList();
return items;
}
Widget _buildCupertinoPicker(BuildContext context, int i, int _length,
PickerAdapter adapter, Key? key) {
return CupertinoPicker.builder(
key: key,
backgroundColor: picker.backgroundColor,
scrollController: scrollController[i],
itemExtent: picker.itemExtent,
// looping: picker.looping,
magnification: picker.magnification,
diameterRatio: picker.diameterRatio,
squeeze: picker.squeeze,
selectionOverlay: picker.selectionOverlay,
childCount: picker.looping ? null : _length,
itemBuilder: (context, index) {
adapter.setColumn(i - 1);
return adapter.buildItem(context, index % _length);
},
onSelectedItemChanged: (int _index) {
if (_length <= 0) return;
var index = _index % _length;
if (picker.printDebug)
print("onSelectedItemChanged. col: $i, row: $index");
picker.selecteds[i] = index;
updateScrollController(i);
adapter.doSelect(i, index);
if (picker.changeToFirst) {
for (int j = i + 1; j < picker.selecteds.length; j++) {
picker.selecteds[j] = 0;
scrollController[j].jumpTo(0.0);
}
}
if (picker.onSelect != null)
picker.onSelect!(picker, i, picker.selecteds);
if (adapter.needUpdatePrev(i)) {
for (int j = 0; j < picker.selecteds.length; j++) {
if (j != i && _keys[j] != null) {
adapter.setColumn(j - 1);
_keys[j]!(() => null);
}
}
// setState(() {});
} else {
if (_keys[i] != null) _keys[i]!(() => null);
if (adapter.isLinkage) {
for (int j = i + 1; j < picker.selecteds.length; j++) {
if (j == i) continue;
adapter.setColumn(j - 1);
_keys[j]?.call(() => null);
}
}
}
},
);
}
void updateScrollController(int col) {
if (_changing || picker.adapter.isLinkage == false) return;
_changing = true;
for (int j = 0; j < picker.selecteds.length; j++) {
if (j != col) {
if (scrollController[j].hasClients &&
scrollController[j].position.hasContentDimensions) {
scrollController[j].position.notifyListeners();
}
}
}
_changing = false;
}
@override
void debugFillProperties(properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<bool>('_changing', _changing));
}
}
/// 选择器数据适配器
abstract class PickerAdapter<T> {
Picker? picker;
int getLength();
int getMaxLevel();
void setColumn(int index);
void initSelects();
Widget buildItem(BuildContext context, int index);
/// 是否需要更新前面的列
/// Need to update previous columns
bool needUpdatePrev(int curIndex) {
return false;
}
Widget makeText(Widget? child, String? text, bool isSel) {
final theme = picker!.textStyle != null || picker!.state?.context == null
? null
: Theme.of(picker!.state!.context);
return Center(
child: DefaultTextStyle(
overflow: TextOverflow.ellipsis,
maxLines: 1,
textAlign: picker!.textAlign,
style: picker!.textStyle ??
TextStyle(
color: theme?.brightness == Brightness.dark
? Colors.white
: Colors.black87,
fontFamily: theme == null
? ""
: theme.textTheme.headlineMedium?.fontFamily,
fontSize: Picker.DefaultTextSize),
child: child != null
? (isSel && picker!.selectedIconTheme != null
? IconTheme(
data: picker!.selectedIconTheme!,
child: child,
)
: child)
: Text(text ?? "",
textScaleFactor: picker!.textScaleFactor,
style: (isSel ? picker!.selectedTextStyle : null))));
}
Widget makeTextEx(
Widget? child, String text, Widget? postfix, Widget? suffix, bool isSel) {
List<Widget> items = [];
if (postfix != null) items.add(postfix);
items.add(
child ?? Text(text, style: (isSel ? picker!.selectedTextStyle : null)));
if (suffix != null) items.add(suffix);
final theme = picker!.textStyle != null || picker!.state?.context == null
? null
: Theme.of(picker!.state!.context);
Color? _txtColor =
theme?.brightness == Brightness.dark ? Colors.white : Colors.black87;
double? _txtSize = Picker.DefaultTextSize;
if (isSel && picker!.selectedTextStyle != null) {
if (picker!.selectedTextStyle!.color != null)
_txtColor = picker!.selectedTextStyle!.color;
if (picker!.selectedTextStyle!.fontSize != null)
_txtSize = picker!.selectedTextStyle!.fontSize;
}
return Center(
child: DefaultTextStyle(
overflow: TextOverflow.ellipsis,
maxLines: 1,
textAlign: picker!.textAlign,
style: picker!.textStyle ??
TextStyle(
color: _txtColor,
fontSize: _txtSize,
fontFamily: theme == null
? ""
: theme.textTheme.headlineMedium?.fontFamily),
child: Wrap(
children: items,
)));
}
String getText() {
return getSelectedValues().toString();
}
List<T> getSelectedValues() {
return [];
}
void doShow() {}
void doSelect(int column, int index) {}
int getColumnFlex(int column) {
if (picker!.columnFlex != null && column < picker!.columnFlex!.length)
return picker!.columnFlex![column];
return 1;
}
int get maxLevel => getMaxLevel();
/// Content length of current column
int get length => getLength();
String get text => getText();
// 是否联动,即后面的列受前面列数据影响
bool get isLinkage => getIsLinkage();
@override
String toString() {
return getText();
}
bool getIsLinkage() {
return true;
}
/// 通知适配器数据改变
void notifyDataChanged() {
if (picker?.state != null) {
picker!.adapter.doShow();
picker!.adapter.initSelects();
for (int j = 0; j < picker!.selecteds.length; j++) {
picker!.state!.scrollController[j].jumpToItem(picker!.selecteds[j]);
}
}
}
}
/// 数据适配器
class PickerDataAdapter<T> extends PickerAdapter<T> {
late List<PickerItem<T>> data;
List<PickerItem<dynamic>>? _datas;
int _maxLevel = -1;
int _col = 0;
final bool isArray;
PickerDataAdapter(
{List? pickerData, List<PickerItem<T>>? data, this.isArray = false}) {
this.data = data ?? <PickerItem<T>>[];
_parseData(pickerData);
}
@override
bool getIsLinkage() {
return !isArray;
}
void _parseData(List? pickerData) {
if (pickerData != null && pickerData.length > 0 && (data.length == 0)) {
if (isArray) {
_parseArrayPickerDataItem(pickerData, data);
} else {
_parsePickerDataItem(pickerData, data);
}
}
}
_parseArrayPickerDataItem(List? pickerData, List<PickerItem> data) {
if (pickerData == null) return;
var len = pickerData.length;
for (int i = 0; i < len; i++) {
var v = pickerData[i];
if (!(v is List)) continue;
List lv = v;
if (lv.length == 0) continue;
PickerItem item = PickerItem<T>(children: <PickerItem<T>>[]);
data.add(item);
for (int j = 0; j < lv.length; j++) {
var o = lv[j];
if (o is T) {
item.children!.add(PickerItem<T>(value: o));
} else if (T == String) {
String _v = o.toString();
item.children!.add(PickerItem<T>(value: _v as T));
}
}
}
if (picker?.printDebug == true) print("data.length: ${data.length}");
}
_parsePickerDataItem(List? pickerData, List<PickerItem> data) {
if (pickerData == null) return;
var len = pickerData.length;
for (int i = 0; i < len; i++) {
var item = pickerData[i];
if (item is T) {
data.add(new PickerItem<T>(value: item));
} else if (item is Map) {
final Map map = item;
if (map.length == 0) continue;
List<T> _mapList = map.keys.toList().cast();
for (int j = 0; j < _mapList.length; j++) {
var _o = map[_mapList[j]];
if (_o is List && _o.length > 0) {
List<PickerItem<T>> _children = <PickerItem<T>>[];
//print('add: ${data.runtimeType.toString()}');
data.add(PickerItem<T>(value: _mapList[j], children: _children));
_parsePickerDataItem(_o, _children);
}
}
} else if (T == String && !(item is List)) {
String _v = item.toString();
//print('add: $_v');
data.add(PickerItem<T>(value: _v as T));
}
}
}
void setColumn(int index) {
if (_datas != null && _col == index + 1) return;
_col = index + 1;
if (isArray) {
if (picker!.printDebug) print("index: $index");
if (_col < data.length)
_datas = data[_col].children;
else
_datas = null;
return;
}
if (index < 0) {
_datas = data;
} else {
_datas = data;
// 列数过多会有性能问题
for (int i = 0; i <= index; i++) {
var j = picker!.selecteds[i];
if (_datas != null && _datas!.length > j)
_datas = _datas![j].children;
else {
_datas = null;
break;
}
}
}
}
@override
int getLength() => _datas?.length ?? 0;
@override
getMaxLevel() {
if (_maxLevel == -1) _checkPickerDataLevel(data, 1);
return _maxLevel;
}
@override
Widget buildItem(BuildContext context, int index) {
final PickerItem item = _datas![index];
final isSel = index == picker!.selecteds[_col];
if (picker!.onBuilderItem != null) {
final _v = picker!.onBuilderItem!(
context, item.value.toString(), item.text, isSel, _col, index);
if (_v != null) return makeText(_v, null, isSel);
}
if (item.text != null) {
return isSel && picker!.selectedTextStyle != null
? DefaultTextStyle(
style: picker!.selectedTextStyle!,
textAlign: picker!.textAlign,
child: picker!.selectedIconTheme != null
? IconTheme(
data: picker!.selectedIconTheme!,
child: item.text!,
)
: item.text!)
: item.text!;
}
return makeText(
item.text, item.text != null ? null : item.value.toString(), isSel);
}
@override
void initSelects() {
// ignore: unnecessary_null_comparison
if (picker!.selecteds == null) picker!.selecteds = <int>[];
if (picker!.selecteds.length == 0) {
for (int i = 0; i < _maxLevel; i++) picker!.selecteds.add(0);
}
}
@override
List<T> getSelectedValues() {
List<T> _items = [];
var _sLen = picker!.selecteds.length;
if (isArray) {
for (int i = 0; i < _sLen; i++) {
int j = picker!.selecteds[i];
if (j < 0 || data[i].children == null || j >= data[i].children!.length)
break;
_items.add(data[i].children![j].value!);
}
} else {
List<PickerItem<dynamic>>? datas = data;
for (int i = 0; i < _sLen; i++) {
int j = picker!.selecteds[i];
if (j < 0 || j >= datas!.length) break;
_items.add(datas[j].value);
datas = datas[j].children;
if (datas == null || datas.length == 0) break;
}
}
return _items;
}
_checkPickerDataLevel(List<PickerItem>? data, int level) {
if (data == null) return;
if (isArray) {
_maxLevel = data.length;
return;
}
for (int i = 0; i < data.length; i++) {
if (data[i].children != null && data[i].children!.length > 0)
_checkPickerDataLevel(data[i].children, level + 1);
}
if (_maxLevel < level) _maxLevel = level;
}
}
class NumberPickerColumn {
final List<int>? items;
final int begin;
final int end;
final int? initValue;
final int columnFlex;
final int jump;
final Widget? postfix, suffix;
final PickerValueFormat<int>? onFormatValue;
const NumberPickerColumn({
this.begin = 0,
this.end = 9,
this.items,
this.initValue,
this.jump = 1,
this.columnFlex = 1,
this.postfix,
this.suffix,
this.onFormatValue,
});
int indexOf(int? value) {
if (value == null) return -1;
if (items != null) return items!.indexOf(value);
if (value < begin || value > end) return -1;
return (value - begin) ~/ (this.jump == 0 ? 1 : this.jump);
}
int valueOf(int index) {
if (items != null) {
return items![index];
}
return begin + index * (this.jump == 0 ? 1 : this.jump);
}
String getValueText(int index) {
return onFormatValue == null
? "${valueOf(index)}"
: onFormatValue!(valueOf(index));
}
int count() {
var v = (end - begin) ~/ (this.jump == 0 ? 1 : this.jump) + 1;
if (v < 1) return 0;
return v;
}
}
class NumberPickerAdapter extends PickerAdapter<int> {
NumberPickerAdapter({required this.data});
final List<NumberPickerColumn> data;
NumberPickerColumn? cur;
int _col = 0;
@override
int getLength() {
if (cur == null) return 0;
if (cur!.items != null) return cur!.items!.length;
return cur!.count();
}
@override
int getMaxLevel() => data.length;
@override
bool getIsLinkage() {
return false;
}
@override
void setColumn(int index) {
if (index != -1 && _col == index + 1) return;
_col = index + 1;
if (_col >= data.length) {
cur = null;
} else {
cur = data[_col];
}
}
@override
void initSelects() {
int _maxLevel = getMaxLevel();
// ignore: unnecessary_null_comparison
if (picker!.selecteds == null) picker!.selecteds = <int>[];
if (picker!.selecteds.length == 0) {
for (int i = 0; i < _maxLevel; i++) {
int v = data[i].indexOf(data[i].initValue);
if (v < 0) v = 0;
picker!.selecteds.add(v);
}
}
}
@override
Widget buildItem(BuildContext context, int index) {
final txt = cur!.getValueText(index);
final isSel = index == picker!.selecteds[_col];
if (picker!.onBuilderItem != null) {
final _v = picker!.onBuilderItem!(context, txt, null, isSel, _col, index);
if (_v != null) return makeText(_v, null, isSel);
}
if (cur!.postfix == null && cur!.suffix == null)
return makeText(null, txt, isSel);
else
return makeTextEx(null, txt, cur!.postfix, cur!.suffix, isSel);
}
@override
int getColumnFlex(int column) {
return data[column].columnFlex;
}
@override
List<int> getSelectedValues() {
List<int> _items = [];
for (int i = 0; i < picker!.selecteds.length; i++) {
int j = picker!.selecteds[i];
int v = data[i].valueOf(j);
_items.add(v);
}
return _items;
}
}
/// Picker DateTime Adapter Type
class PickerDateTimeType {
static const int kMDY = 0; // m, d, y
static const int kHM = 1; // hh, mm
static const int kHMS = 2; // hh, mm, ss
static const int kHM_AP = 3; // hh, mm, ap(AM/PM)
static const int kMDYHM = 4; // m, d, y, hh, mm
static const int kMDYHM_AP = 5; // m, d, y, hh, mm, AM/PM
static const int kMDYHMS = 6; // m, d, y, hh, mm, ss
static const int kYMD = 7; // y, m, d
static const int kYMDHM = 8; // y, m, d, hh, mm
static const int kYMDHMS = 9; // y, m, d, hh, mm, ss
static const int kYMD_AP_HM = 10; // y, m, d, ap, hh, mm
static const int kYM = 11; // y, m
static const int kDMY = 12; // d, m, y
static const int kY = 13; // y
}
class DateTimePickerAdapter extends PickerAdapter<DateTime> {
/// display type, ref: [columnType]
final int type;
/// Whether to display the month in numerical form.If true, months is not used.
final bool isNumberMonth;
/// custom months strings
final List<String>? months;
/// Custom AM, PM strings
final List<String>? strAMPM;
/// year begin...end.
final int? yearBegin, yearEnd;
/// hour min ... max, min >= 0, max <= 23, max > min
final int? minHour, maxHour;
/// minimum datetime
final DateTime? minValue, maxValue;
/// jump minutes, user could select time in intervals of 30min, 5mins, etc....
final int? minuteInterval;
/// Year, month, day suffix
final String? yearSuffix,
monthSuffix,
daySuffix,
hourSuffix,
minuteSuffix,
secondSuffix;
/// use two-digit year, 2019, displayed as 19
final bool twoDigitYear;
/// year 0, month 1, day 2, hour 3, minute 4, sec 5, am/pm 6, hour-ap: 7
final List<int>? customColumnType;
static const List<String> MonthsList_EN = const [
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec"
];
static const List<String> MonthsList_EN_L = const [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December"
];
DateTimePickerAdapter({
Picker? picker,
this.type = 0,
this.isNumberMonth = false,
this.months = MonthsList_EN,
this.strAMPM,
this.yearBegin = 1900,
this.yearEnd = 2100,
this.value,
this.minValue,
this.maxValue,
this.minHour,
this.maxHour,
this.secondSuffix,
this.minuteSuffix,
this.hourSuffix,
this.yearSuffix,
this.monthSuffix,
this.daySuffix,
this.minuteInterval,
this.customColumnType,
this.twoDigitYear = false,
}) : assert(minuteInterval == null ||
(minuteInterval >= 1 &&
minuteInterval <= 30 &&
(60 % minuteInterval == 0))) {
super.picker = picker;
_yearBegin = yearBegin ?? 0;
if (minValue != null && minValue!.year > _yearBegin) {
_yearBegin = minValue!.year;
}
// Judge whether the day is in front of the month
// If in the front, set "needUpdatePrev" = true
List<int> _columnType;
if (customColumnType != null)
_columnType = customColumnType!;
else
_columnType = columnType[type];
var month = _columnType.indexWhere((element) => element == 1);
var day = _columnType.indexWhere((element) => element == 2);
_needUpdatePrev =
day < month || day < _columnType.indexWhere((element) => element == 0);
if (!_needUpdatePrev) {
// check am/pm before hour-ap
var ap = _columnType.indexWhere((element) => element == 6);
if (ap > _columnType.indexWhere((element) => element == 7)) {
_apBeforeHourAp = true;
_needUpdatePrev = true;
}
}
if (value == null) {
value = DateTime.now();
}
_existSec = existSec();
_verificationMinMaxValue();
}
bool _existSec = false;
int _col = 0;
int _colAP = -1;
int _colHour = -1;
int _colDay = -1;
int _yearBegin = 0;
bool _needUpdatePrev = false;
bool _apBeforeHourAp = false;
/// Currently selected value
DateTime? value;
// but it can improve the performance, so keep it.
static const List<List<int>> lengths = const [
[12, 31, 0],
[24, 60],
[24, 60, 60],
[12, 60, 2],
[12, 31, 0, 24, 60],
[12, 31, 0, 12, 60, 2],
[12, 31, 0, 24, 60, 60],
[0, 12, 31],
[0, 12, 31, 24, 60],
[0, 12, 31, 24, 60, 60],
[0, 12, 31, 2, 12, 60],
[0, 12],
[31, 12, 0],
[0],
];
static const Map<int, int> columnTypeLength = {
0: 0,
1: 12,
2: 31,
3: 24,
4: 60,
5: 60,
6: 2,
7: 12
};
/// year 0, month 1, day 2, hour 3, minute 4, sec 5, am/pm 6, hour-ap: 7
static const List<List<int>> columnType = const [
[1, 2, 0],
[3, 4],
[3, 4, 5],
[7, 4, 6],
[1, 2, 0, 3, 4],
[1, 2, 0, 7, 4, 6],
[1, 2, 0, 3, 4, 5],
[0, 1, 2],
[0, 1, 2, 3, 4],
[0, 1, 2, 3, 4, 5],
[0, 1, 2, 6, 7, 4],
[0, 1],
[2, 1, 0],
[0],
];
// static const List<int> leapYearMonths = const <int>[1, 3, 5, 7, 8, 10, 12];
// 获取当前列的类型
int getColumnType(int index) {
if (customColumnType != null) return customColumnType![index];
List<int> items = columnType[type];
if (index >= items.length) return -1;
return items[index];
}
// 判断是否存在秒
bool existSec() {
final _columns =
customColumnType == null ? columnType[type] : customColumnType!;
return _columns.indexOf(5) >= 0;
}
@override
int getLength() {
int v = (customColumnType == null
? lengths[type][_col]
: columnTypeLength[customColumnType![_col]])!;
if (v == 0) {
int ye = yearEnd!;
if (maxValue != null) ye = maxValue!.year;
return ye - _yearBegin + 1;
}
if (v == 31) return _calcDateCount(value!.year, value!.month);
int _type = getColumnType(_col);
switch (_type) {
case 3: // hour
if ((minHour != null && minHour! >= 0) ||
(maxHour != null && maxHour! <= 23))
return (maxHour ?? 23) - (minHour ?? 0) + 1;
break;
case 4: // minute
if (minuteInterval != null && minuteInterval! > 1)
return v ~/ minuteInterval!;
break;
case 7: // hour am/pm
if ((minHour != null && minHour! >= 0) ||
(maxHour != null && maxHour! <= 23)) if (_colAP < 0) {
// I don't know AM or PM
return 12;
} else {
var _min = 0;
var _max = 0;
if (picker!.selecteds[_colAP] == 0) {
// am
_min = minHour == null
? 1
: minHour! >= 12
? 12
: minHour! + 1;
_max = maxHour == null
? 12
: maxHour! >= 12
? 12
: maxHour! + 1;
} else {
// pm
_min = minHour == null
? 1
: minHour! >= 12
? 24 - minHour! - 12
: 1;
_max = maxHour == null
? 12
: maxHour! >= 12
? maxHour! - 12
: 1;
}
return _max > _min ? _max - _min + 1 : _min - _max + 1;
}
break;
}
return v;
}
@override
int getMaxLevel() {
return customColumnType == null
? lengths[type].length
: customColumnType!.length;
}
@override
bool needUpdatePrev(int curIndex) {
if (_needUpdatePrev) {
if (value?.month == 2) {
// Only February needs to be dealt with
var _curType = getColumnType(curIndex);
return _curType == 1 || _curType == 0;
} else if (_apBeforeHourAp) {
return getColumnType(curIndex) == 6;
}
}
return false;
}
@override
void setColumn(int index) {
//print("setColumn index: $index");
_col = index + 1;
if (_col < 0) _col = 0;
}
@override
void initSelects() {
_colAP = _getAPColIndex();
int _maxLevel = getMaxLevel();
// ignore: unnecessary_null_comparison
if (picker!.selecteds == null) picker!.selecteds = <int>[];
if (picker!.selecteds.length == 0) {
for (int i = 0; i < _maxLevel; i++) picker!.selecteds.add(0);
}
}
@override
Widget buildItem(BuildContext context, int index) {
String _text = "";
int colType = getColumnType(_col);
switch (colType) {
case 0:
if (twoDigitYear) {
_text = "${_yearBegin + index}";
var _l = _text.length;
_text =
"${_text.substring(_l - (_l - 2), _l)}${_checkStr(yearSuffix)}";
} else
_text = "${_yearBegin + index}${_checkStr(yearSuffix)}";
break;
case 1:
if (isNumberMonth) {
_text = "${index + 1}${_checkStr(monthSuffix)}";
} else {
if (months != null)
_text = "${months![index]}";
else {
List _months =
PickerLocalizations.of(context).months ?? MonthsList_EN;
_text = "${_months[index]}";
}
}
break;
case 2:
_text = "${index + 1}${_checkStr(daySuffix)}";
break;
case 3:
_text = "${intToStr(index + (minHour ?? 0))}${_checkStr(hourSuffix)}";
break;
case 5:
_text = "${intToStr(index)}${_checkStr(secondSuffix)}";
break;
case 4:
if (minuteInterval == null || minuteInterval! < 2)
_text = "${intToStr(index)}${_checkStr(minuteSuffix)}";
else
_text =
"${intToStr(index * minuteInterval!)}${_checkStr(minuteSuffix)}";
break;
case 6:
final apStr = strAMPM ??
PickerLocalizations.of(context).ampm ??
const ['AM', 'PM'];
_text = "${apStr[index]}";
break;
case 7:
_text =
"${intToStr(index + (minHour == null ? 0 : (picker!.selecteds[_colAP] == 0 ? minHour! : 0)) + 1)}";
break;
}
final isSel = picker!.selecteds[_col] == index;
if (picker!.onBuilderItem != null) {
var _v = picker!.onBuilderItem!(context, _text, null, isSel, _col, index);
if (_v != null) return makeText(_v, null, isSel);
}
return makeText(null, _text, isSel);
}
@override
String getText() {
return value.toString();
}
@override
int getColumnFlex(int column) {
if (picker!.columnFlex != null && column < picker!.columnFlex!.length)
return picker!.columnFlex![column];
if (getColumnType(column) == 0) return 3;
return 2;
}
@override
void doShow() {
if (_yearBegin == 0) getLength();
var _maxLevel = getMaxLevel();
final sh = value!.hour;
for (int i = 0; i < _maxLevel; i++) {
int colType = getColumnType(i);
switch (colType) {
case 0:
picker!.selecteds[i] = yearEnd != null && value!.year > yearEnd!
? yearEnd! - _yearBegin
: value!.year - _yearBegin;
break;
case 1:
picker!.selecteds[i] = value!.month - 1;
break;
case 2:
picker!.selecteds[i] = value!.day - 1;
break;
case 3:
var h = sh;
if ((minHour != null && minHour! >= 0) ||
(maxHour != null && maxHour! <= 23)) {
if (minHour != null) {
h = h > minHour! ? h - minHour! : 0;
} else {
h = (maxHour ?? 23) - (minHour ?? 0) + 1;
}
}
picker!.selecteds[i] = h;
break;
case 4:
// minute
if (minuteInterval == null || minuteInterval! < 2) {
picker!.selecteds[i] = value!.minute;
} else {
picker!.selecteds[i] = value!.minute ~/ minuteInterval!;
final m = picker!.selecteds[i] * minuteInterval!;
if (m != value!.minute) {
// 需要更新 value
var s = value!.second;
if (type != 2 && type != 6) s = 0;
final h = _colAP >= 0 ? _calcHourOfAMPM(sh, m) : sh;
value = DateTime(value!.year, value!.month, value!.day, h, m, s);
}
}
break;
case 5:
picker!.selecteds[i] = value!.second;
break;
case 6:
// am/pm
picker!.selecteds[i] = (sh > 12 ||
(sh == 12 && (value!.minute > 0 || value!.second > 0)))
? 1
: 0;
break;
case 7:
picker!.selecteds[i] = sh == 0
? 11
: (sh > 12)
? sh - 12 - 1
: sh - 1;
break;
}
}
}
@override
void doSelect(int column, int index) {
int year, month, day, h, m, s;
year = value!.year;
month = value!.month;
day = value!.day;
h = value!.hour;
m = value!.minute;
s = _existSec ? value!.second : 0;
int colType = getColumnType(column);
switch (colType) {
case 0:
year = _yearBegin + index;
break;
case 1:
month = index + 1;
break;
case 2:
day = index + 1;
break;
case 3:
h = index + (minHour ?? 0);
break;
case 4:
m = (minuteInterval == null || minuteInterval! < 2)
? index
: index * minuteInterval!;
if (_colAP >= 0) {
h = _calcHourOfAMPM(h, m);
}
break;
case 5:
s = index;
break;
case 6:
h = _calcHourOfAMPM(h, m);
if (minHour != null || maxHour != null) {
if (minHour != null && _colHour >= 0) {
if (h < minHour!) {
picker!.selecteds[_colHour] = 0;
picker!.updateColumn(_colHour);
return;
}
}
if (maxHour != null && h > maxHour!) h = maxHour!;
}
break;
case 7:
h = index +
(minHour == null
? 0
: (picker!.selecteds[_colAP] == 0 ? minHour! : 0)) +
1;
if (_colAP >= 0) {
h = _calcHourOfAMPM(h, m);
}
if (h > 23) h = 0;
break;
}
int __day = _calcDateCount(year, month);
bool _isChangeDay = false;
if (day > __day) {
day = __day;
_isChangeDay = true;
}
value = DateTime(year, month, day, h, m, s);
if (_verificationMinMaxValue()) {
notifyDataChanged();
} else if (_isChangeDay && _colDay >= 0) {
doShow();
picker!.updateColumn(_colDay);
}
}
bool _verificationMinMaxValue() {
DateTime? _minV = minValue;
DateTime? _maxV = maxValue;
if (_minV == null && yearBegin != null) {
_minV = DateTime(yearBegin!, 1, 1, minHour ?? 0);
}
if (_maxV == null && yearEnd != null) {
_maxV = DateTime(yearEnd!, 12, 31, maxHour ?? 23, 59, 59);
}
if (_minV != null &&
(value!.millisecondsSinceEpoch < _minV.millisecondsSinceEpoch)) {
value = _minV;
return true;
} else if (_maxV != null &&
value!.millisecondsSinceEpoch > _maxV.millisecondsSinceEpoch) {
value = _maxV;
return true;
}
return false;
}
// Calculate am/pm time transfer
int _calcHourOfAMPM(int h, int m) {
// 12:00 AM , 00:00:000
// 12:30 AM , 12:30:000
// 12:00 PM , 12:00:000
// 12:30 PM , 00:30:000
if (picker!.selecteds[_colAP] == 0) {
// am
if (h == 12 && m == 0) {
h = 0;
} else if (h == 0 && m > 0) {
h = 12;
}
if (h > 12) h = h - 12;
} else {
// pm
if (h > 0 && h < 12) h = h + 12;
if (h == 12 && m > 0) {
h = 0;
} else if (h == 0 && m == 0) {
h = 12;
}
}
return h;
}
int _getAPColIndex() {
List<int> items = customColumnType ?? columnType[type];
_colHour = items.indexWhere((e) => e == 7);
_colDay = items.indexWhere((e) => e == 2);
for (int i = 0; i < items.length; i++) {
if (items[i] == 6) return i;
}
return -1;
}
int _calcDateCount(int year, int month) {
switch (month) {
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
return 31;
case 2:
{
if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) {
return 29;
}
return 28;
}
}
return 30;
}
String intToStr(int v) {
return (v < 10) ? "0$v" : "$v";
}
String _checkStr(String? v) {
return v == null ? "" : v;
}
}
import 'package:flutter/material.dart';
abstract class PickerLocalizationsBase {
final Locale? locale;
const PickerLocalizationsBase(this.locale);
Object? getItem(String key);
String? get cancelText => getItem('cancelText') as String;
String? get confirmText => getItem('confirmText') as String;
List? get ampm => getItem('ampm') as List;
List? get months => getItem('months') as List;
List? get monthsLong => getItem('monthsLong') as List;
}
/// localizations
class PickerLocalizations extends PickerLocalizationsBase {
static PickerLocalizations _static = PickerLocalizations(null);
const PickerLocalizations(Locale? locale) : super(locale);
@override
Object? getItem(String key) {
Map? localData;
if (locale != null) {
localData = localizedValues[locale!.languageCode];
}
if (localData == null) return localizedValues['en']![key];
return localData[key];
}
static PickerLocalizations of(BuildContext context) {
return Localizations.of<PickerLocalizations>(
context, PickerLocalizations) ??
_static;
}
/// 注册自定义语言
///
/// Register custom language
static registerCustomLanguage(String name,
{String? cancelText,
String? confirmText,
List<String>? ampm,
List<String>? months,
List<String>? monthsLong}) {
if (name.isEmpty) return;
if (ampm != null && ampm.length != 2) {
throw Exception('ampm array length must be 2');
}
if (months != null && months.length != 12) {
throw Exception('months array length must be 12');
}
if (monthsLong != null && monthsLong.length != 12) {
throw Exception('monthsLong array length must be 12');
}
if (!languages.contains(name)) {
languages.add(name);
}
final _defaultValue = localizedValues['en']!;
final data = {
'cancelText': cancelText ?? _defaultValue['cancelText'] as String,
'confirmText': cancelText ?? _defaultValue['confirmText'] as String,
'ampm': ampm ?? _defaultValue['ampm'] as List,
};
if (months != null) {
data['months'] = months;
}
if (monthsLong != null) {
data['monthsLong'] = monthsLong;
}
localizedValues[name] = data;
}
/// Language Support
static const List<String> languages = [
'en',
'ja',
'zh',
'ko',
'it',
'ar',
'fr',
'es',
'tr',
'ro'
];
/// Language Values
static const Map<String, Map<String, Object>> localizedValues = {
'en': {
'cancelText': 'Cancel',
'confirmText': 'Confirm',
'ampm': ['AM', 'PM'],
},
'ja': {
'cancelText': 'キャンセル',
'confirmText': '完了',
'ampm': ['午前', '午後'],
},
'zh': {
'cancelText': '取消',
'confirmText': '确定',
'ampm': ['上午', '下午'],
},
'ko': {
'cancelText': '취소',
'confirmText': '확인',
'ampm': ['오전', '오후'],
},
'it': {
'cancelText': 'Annulla',
'confirmText': 'Conferma',
'ampm': ['AM', 'PM'],
},
'ar': {
'cancelText': 'إلغاء الأمر',
'confirmText': 'تأكيد',
'ampm': ['صباحاً', 'مساءً'],
},
'fr': {
'cancelText': 'Annuler',
'confirmText': 'Confirmer',
'ampm': ['Matin', 'Après-midi'],
},
'es': {
'cancelText': 'Cancelar',
'confirmText': 'Confirmar',
'ampm': ['AM', 'PM'],
},
'tr': {
'cancelText': 'İptal',
'confirmText': 'Onay',
'ampm': ['ÖÖ', 'ÖS'],
'months': [
'Oca',
'Şub',
'Mar',
'Nis',
'May',
'Haz',
'Tem',
'Ağu',
'Eyl',
'Eki',
'Kas',
'Ara'
],
'monthsLong': [
'Ocak',
'Şubat',
'Mart',
'Nisan',
'Mayıs',
'Haziran',
'Temmuz',
'Ağustos',
'Eylül',
'Ekim',
'Kasım',
'Aralık'
],
},
'ro': {
'cancelText': 'Anulează',
'confirmText': 'Confirmă',
'ampm': ['AM', 'PM'],
'months': [
'Ian',
'Feb',
'Mart',
'Apr',
'Mai',
'Iun',
'Iul',
'Aug',
'Sept',
'Oct',
'Nov',
'Dec',
],
'monthsLong': [
'Ianuarie',
'Februarie',
'Martie',
'Aprilie',
'Mai',
'Iunie',
'Iulie',
'August',
'Septembrie',
'Octombrie',
'Noiembrie',
'Decembrie',
],
}
};
}
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'dart:async';
import 'picker_localizations.dart';
/// picker localizations
class PickerLocalizationsDelegate
extends LocalizationsDelegate<PickerLocalizations> {
const PickerLocalizationsDelegate();
static const PickerLocalizationsDelegate delegate =
const PickerLocalizationsDelegate();
@override
bool isSupported(Locale locale) =>
PickerLocalizations.languages.contains(locale.languageCode);
@override
Future<PickerLocalizations> load(Locale locale) {
return SynchronousFuture<PickerLocalizations>(
new PickerLocalizations(locale));
}
@override
bool shouldReload(PickerLocalizationsDelegate old) => false;
}
name: flutter_picker
description: Flutter plugin picker. Include NumberPicker, DateTimePicker, ArrayPicker, and default linkage Picker. Provide flexible parameters to meet various needs.
version: 2.1.1
environment:
sdk: ">=2.12.0 <3.0.0"
flutter: ">=1.17.0"
dependencies:
flutter:
sdk: flutter
dev_dependencies:
flutter_test:
sdk: flutter
flutter:
color: theme!.accentColor
import 'package:flutter_test/flutter_test.dart';
// import 'package:flutter_picker/flutter_picker.dart';
void main() {
test('adds one to input values', () {
final v = null as List;
print(v);
expect(v, null);
// final calculator = Calculator();
// expect(calculator.addOne(2), 3);
// expect(calculator.addOne(-7), -6);
// expect(calculator.addOne(0), 1);
// expect(() => calculator.addOne(null), throwsNoSuchMethodError);
});
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论