├── .gitignore ├── .metadata ├── README.md ├── android ├── app │ ├── build.gradle │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ └── com │ │ │ └── kuky │ │ │ └── demo │ │ │ └── flutterartsdemosapp │ │ │ └── MainActivity.kt │ │ └── res │ │ ├── drawable │ │ ├── ic_app_icon.png │ │ └── launch_background.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ └── values │ │ ├── strings.xml │ │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── fonts └── third_part_icon.ttf ├── images ├── ali.jpg ├── app_bar_hor.jpg ├── app_icon.png ├── ava_default.png ├── lm.jpg ├── login_bg.png └── timg.jpg ├── ios ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ └── contents.xcworkspacedata └── Runner │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-App-1024x1024@1x.png │ │ ├── Icon-App-20x20@1x.png │ │ ├── Icon-App-20x20@2x.png │ │ ├── Icon-App-20x20@3x.png │ │ ├── Icon-App-29x29@1x.png │ │ ├── Icon-App-29x29@2x.png │ │ ├── Icon-App-29x29@3x.png │ │ ├── Icon-App-40x40@1x.png │ │ ├── Icon-App-40x40@2x.png │ │ ├── Icon-App-40x40@3x.png │ │ ├── Icon-App-60x60@2x.png │ │ ├── Icon-App-60x60@3x.png │ │ ├── Icon-App-76x76@1x.png │ │ ├── Icon-App-76x76@2x.png │ │ └── Icon-App-83.5x83.5@2x.png │ └── LaunchImage.imageset │ │ ├── Contents.json │ │ ├── LaunchImage.png │ │ ├── LaunchImage@2x.png │ │ ├── LaunchImage@3x.png │ │ └── README.md │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ └── main.m ├── lib ├── application.dart ├── bloc │ ├── bloc_provider.dart │ ├── count_provider.dart │ ├── counter_bloc.dart │ ├── language_bloc.dart │ └── stream_main.dart ├── bloc_network │ ├── random_user.dart │ ├── random_user.g.dart │ ├── user_bloc.dart │ └── user_page_main.dart ├── colsed │ ├── fuli_bloc.dart │ ├── fuli_model.dart │ └── fuli_page_main.dart ├── custom_routes.dart ├── db_utils.dart ├── http_utils.dart ├── localizations │ ├── base_localization.dart │ ├── demo_localizations.dart │ ├── localization_en.dart │ └── localization_zh.dart ├── main.dart ├── pages │ ├── animation_main.dart │ ├── app_bar_main.dart │ ├── button_main.dart │ ├── checkbox_switch_main.dart │ ├── column_main.dart │ ├── custom_scroll_main.dart │ ├── custom_view_main.dart │ ├── data_persistence_main.dart │ ├── expansion_tile_main.dart │ ├── gesture_deep.dart │ ├── gesture_main.dart │ ├── gridview_main.dart │ ├── http_main.dart │ ├── image_main.dart │ ├── listview_main.dart │ ├── login_home_page.dart │ ├── nested_scroll_main.dart │ ├── prompt_main.dart │ ├── router_main.dart │ ├── scroll_controller_main.dart │ ├── scrollable_main.dart │ ├── single_child_scroll_main.dart │ ├── sliver_main.dart │ ├── stack_main.dart │ ├── staggered_animation_main.dart │ ├── suspension_main.dart │ ├── text_field_main.dart │ └── text_main.dart ├── third_icons.dart └── widget │ ├── flutter_suspension.dart │ └── suspension │ ├── suspension_list.dart │ ├── suspension_util.dart │ └── suspension_view.dart └── pubspec.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.lock 4 | *.log 5 | *.pyc 6 | *.swp 7 | .DS_Store 8 | .atom/ 9 | .buildlog/ 10 | .history 11 | .svn/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # Visual Studio Code related 20 | .vscode/ 21 | 22 | # Flutter/Dart/Pub related 23 | **/doc/api/ 24 | .dart_tool/ 25 | .flutter-plugins 26 | .packages 27 | .pub-cache/ 28 | .pub/ 29 | build/ 30 | 31 | # Android related 32 | **/android/**/gradle-wrapper.jar 33 | **/android/.gradle 34 | **/android/captures/ 35 | **/android/gradlew 36 | **/android/gradlew.bat 37 | **/android/local.properties 38 | **/android/**/GeneratedPluginRegistrant.java 39 | 40 | # iOS/XCode related 41 | **/ios/**/*.mode1v3 42 | **/ios/**/*.mode2v3 43 | **/ios/**/*.moved-aside 44 | **/ios/**/*.pbxuser 45 | **/ios/**/*.perspectivev3 46 | **/ios/**/*sync/ 47 | **/ios/**/.sconsign.dblite 48 | **/ios/**/.tags* 49 | **/ios/**/.vagrant/ 50 | **/ios/**/DerivedData/ 51 | **/ios/**/Icon? 52 | **/ios/**/Pods/ 53 | **/ios/**/.symlinks/ 54 | **/ios/**/profile 55 | **/ios/**/xcuserdata 56 | **/ios/.generated/ 57 | **/ios/Flutter/App.framework 58 | **/ios/Flutter/Flutter.framework 59 | **/ios/Flutter/Generated.xcconfig 60 | **/ios/Flutter/app.flx 61 | **/ios/Flutter/app.zip 62 | **/ios/Flutter/flutter_assets/ 63 | **/ios/ServiceDefinitions.json 64 | **/ios/Runner/GeneratedPluginRegistrant.* 65 | 66 | # Exceptions to above rules. 67 | !**/ios/**/default.mode1v3 68 | !**/ios/**/default.mode2v3 69 | !**/ios/**/default.pbxuser 70 | !**/ios/**/default.perspectivev3 71 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 72 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 1407091bfb5bb535630d4d596541817737da8412 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### flutter_arts_demos_app 2 | 3 | 这个仓库用于托管 `Flutter` 系列文章的 `demo` 代码,查看文章直接点击链接查看 4 | [Flutter 入门指北系列](https://www.jianshu.com/nb/34950817) 5 | 6 | 7 | ### Getting Started 8 | 9 | This project is a starting point for a Flutter application. 10 | 11 | A few resources to get you started if this is your first Flutter project: 12 | 13 | - [Lab: Write your first Flutter app](https://flutter.io/docs/get-started/codelab) 14 | - [Cookbook: Useful Flutter samples](https://flutter.io/docs/cookbook) 15 | 16 | For help getting started with Flutter, view our 17 | [online documentation](https://flutter.io/docs), which offers tutorials, 18 | samples, guidance on mobile development, and a full API reference. 19 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 28 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | lintOptions { 36 | disable 'InvalidPackage' 37 | } 38 | 39 | defaultConfig { 40 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 41 | applicationId "com.kuky.demo.flutterartsdemosapp" 42 | minSdkVersion 16 43 | targetSdkVersion 28 44 | versionCode flutterVersionCode.toInteger() 45 | versionName flutterVersionName 46 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 47 | } 48 | 49 | buildTypes { 50 | release { 51 | // TODO: Add your own signing config for the release build. 52 | // Signing with the debug keys for now, so `flutter run --release` works. 53 | signingConfig signingConfigs.debug 54 | } 55 | } 56 | } 57 | 58 | flutter { 59 | source '../..' 60 | } 61 | 62 | dependencies { 63 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 64 | testImplementation 'junit:junit:4.12' 65 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 66 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 67 | } 68 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 9 | 10 | 11 | 12 | 13 | 18 | 23 | 30 | 34 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/kuky/demo/flutterartsdemosapp/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.kuky.demo.flutterartsdemosapp 2 | 3 | import android.os.Bundle 4 | 5 | import io.flutter.app.FlutterActivity 6 | import io.flutter.plugins.GeneratedPluginRegistrant 7 | 8 | class MainActivity: FlutterActivity() { 9 | override fun onCreate(savedInstanceState: Bundle?) { 10 | super.onCreate(savedInstanceState) 11 | GeneratedPluginRegistrant.registerWith(this) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/ic_app_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kukyxs/flutter_arts_demos_app/8bf525c797e0fe5a4a4397402fa855b369a6947e/android/app/src/main/res/drawable/ic_app_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kukyxs/flutter_arts_demos_app/8bf525c797e0fe5a4a4397402fa855b369a6947e/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kukyxs/flutter_arts_demos_app/8bf525c797e0fe5a4a4397402fa855b369a6947e/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kukyxs/flutter_arts_demos_app/8bf525c797e0fe5a4a4397402fa855b369a6947e/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kukyxs/flutter_arts_demos_app/8bf525c797e0fe5a4a4397402fa855b369a6947e/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kukyxs/flutter_arts_demos_app/8bf525c797e0fe5a4a4397402fa855b369a6947e/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Art Demos 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.11' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.3.2' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Apr 24 21:42:41 CST 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip 7 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /fonts/third_part_icon.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kukyxs/flutter_arts_demos_app/8bf525c797e0fe5a4a4397402fa855b369a6947e/fonts/third_part_icon.ttf -------------------------------------------------------------------------------- /images/ali.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kukyxs/flutter_arts_demos_app/8bf525c797e0fe5a4a4397402fa855b369a6947e/images/ali.jpg -------------------------------------------------------------------------------- /images/app_bar_hor.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kukyxs/flutter_arts_demos_app/8bf525c797e0fe5a4a4397402fa855b369a6947e/images/app_bar_hor.jpg -------------------------------------------------------------------------------- /images/app_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kukyxs/flutter_arts_demos_app/8bf525c797e0fe5a4a4397402fa855b369a6947e/images/app_icon.png -------------------------------------------------------------------------------- /images/ava_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kukyxs/flutter_arts_demos_app/8bf525c797e0fe5a4a4397402fa855b369a6947e/images/ava_default.png -------------------------------------------------------------------------------- /images/lm.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kukyxs/flutter_arts_demos_app/8bf525c797e0fe5a4a4397402fa855b369a6947e/images/lm.jpg -------------------------------------------------------------------------------- /images/login_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kukyxs/flutter_arts_demos_app/8bf525c797e0fe5a4a4397402fa855b369a6947e/images/login_bg.png -------------------------------------------------------------------------------- /images/timg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kukyxs/flutter_arts_demos_app/8bf525c797e0fe5a4a4397402fa855b369a6947e/images/timg.jpg -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : FlutterAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #include "AppDelegate.h" 2 | #include "GeneratedPluginRegistrant.h" 3 | 4 | @implementation AppDelegate 5 | 6 | - (BOOL)application:(UIApplication *)application 7 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 8 | [GeneratedPluginRegistrant registerWithRegistry:self]; 9 | // Override point for customization after application launch. 10 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 11 | } 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kukyxs/flutter_arts_demos_app/8bf525c797e0fe5a4a4397402fa855b369a6947e/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kukyxs/flutter_arts_demos_app/8bf525c797e0fe5a4a4397402fa855b369a6947e/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kukyxs/flutter_arts_demos_app/8bf525c797e0fe5a4a4397402fa855b369a6947e/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kukyxs/flutter_arts_demos_app/8bf525c797e0fe5a4a4397402fa855b369a6947e/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kukyxs/flutter_arts_demos_app/8bf525c797e0fe5a4a4397402fa855b369a6947e/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kukyxs/flutter_arts_demos_app/8bf525c797e0fe5a4a4397402fa855b369a6947e/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kukyxs/flutter_arts_demos_app/8bf525c797e0fe5a4a4397402fa855b369a6947e/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kukyxs/flutter_arts_demos_app/8bf525c797e0fe5a4a4397402fa855b369a6947e/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kukyxs/flutter_arts_demos_app/8bf525c797e0fe5a4a4397402fa855b369a6947e/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kukyxs/flutter_arts_demos_app/8bf525c797e0fe5a4a4397402fa855b369a6947e/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kukyxs/flutter_arts_demos_app/8bf525c797e0fe5a4a4397402fa855b369a6947e/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kukyxs/flutter_arts_demos_app/8bf525c797e0fe5a4a4397402fa855b369a6947e/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kukyxs/flutter_arts_demos_app/8bf525c797e0fe5a4a4397402fa855b369a6947e/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kukyxs/flutter_arts_demos_app/8bf525c797e0fe5a4a4397402fa855b369a6947e/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kukyxs/flutter_arts_demos_app/8bf525c797e0fe5a4a4397402fa855b369a6947e/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kukyxs/flutter_arts_demos_app/8bf525c797e0fe5a4a4397402fa855b369a6947e/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kukyxs/flutter_arts_demos_app/8bf525c797e0fe5a4a4397402fa855b369a6947e/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kukyxs/flutter_arts_demos_app/8bf525c797e0fe5a4a4397402fa855b369a6947e/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | flutter_arts_demos_app 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /ios/Runner/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char* argv[]) { 6 | @autoreleasepool { 7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lib/application.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_arts_demos_app/http_utils.dart'; 2 | 3 | class Application { 4 | static HttpUtils http; 5 | } 6 | -------------------------------------------------------------------------------- /lib/bloc/bloc_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | abstract class BaseBloc { 4 | void dispose(); 5 | } 6 | 7 | class BlocProvider extends StatefulWidget { 8 | final Widget child; 9 | final T bloc; 10 | 11 | BlocProvider({Key key, @required this.child, @required this.bloc}) : super(key: key); 12 | 13 | @override 14 | _BlocProviderState createState() => _BlocProviderState(); 15 | 16 | static T of(BuildContext context) { 17 | final type = _typeOf>(); 18 | BlocProvider provider = context.ancestorWidgetOfExactType(type); 19 | return provider.bloc; 20 | } 21 | 22 | static Type _typeOf() => T; 23 | } 24 | 25 | class _BlocProviderState extends State> { 26 | 27 | @override 28 | void dispose() { 29 | widget.bloc.dispose(); 30 | super.dispose(); 31 | } 32 | 33 | @override 34 | Widget build(BuildContext context) { 35 | return widget.child; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/bloc/count_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class CountProvider with ChangeNotifier { 4 | int _value = 0; 5 | 6 | int get value => _value; 7 | 8 | void changeValue(int value) { 9 | _value = value; 10 | notifyListeners(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/bloc/counter_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_arts_demos_app/bloc/bloc_provider.dart'; 2 | import 'package:rxdart/rxdart.dart'; 3 | 4 | class CounterBloc extends BaseBloc { 5 | int _count = 0; 6 | 7 | int get count => _count; 8 | 9 | // rx 10 | BehaviorSubject _countController = BehaviorSubject(); 11 | 12 | Observable get countStream => Observable(_countController.stream); 13 | 14 | void dispatch(int value) { 15 | _count = value; 16 | _countController.add(_count); 17 | } 18 | 19 | // stream 20 | // StreamController _countController = StreamController.broadcast(); 21 | 22 | // Stream get countStream => _countController.stream; 23 | 24 | // void dispatch(int value) { 25 | // _count = value; 26 | // _countController.sink.add(_count); 27 | // } 28 | 29 | @override 30 | void dispose() { 31 | _countController.close(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/bloc/language_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'bloc_provider.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:rxdart/rxdart.dart'; 4 | 5 | class LanguageBloc extends BaseBloc { 6 | 7 | Locale _currentLanguage; 8 | 9 | Locale get currentLanguage => _currentLanguage; 10 | 11 | BehaviorSubject _localeController = BehaviorSubject(); 12 | 13 | Observable get localeStream => Observable(_localeController.stream); 14 | 15 | changeLanguage(Locale locale) { 16 | _currentLanguage = locale; 17 | _localeController.add(locale); 18 | } 19 | 20 | @override 21 | void dispose() { 22 | _localeController?.close(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/bloc/stream_main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_arts_demos_app/bloc/count_provider.dart'; 3 | import 'package:provide/provide.dart'; 4 | 5 | //void main() => runApp(StreamApp()); 6 | 7 | void main() { 8 | final providers = Providers() 9 | ..provide(Provider.function((_) => CountProvider())); 10 | runApp(ProviderNode(child: StreamApp(), providers: providers)); 11 | } 12 | 13 | class StreamApp extends StatelessWidget { 14 | @override 15 | Widget build(BuildContext context) { 16 | // return BlocProvider( 17 | // child: MaterialApp( 18 | // debugShowCheckedModeBanner: false, 19 | // home: StreamHome(), 20 | // ), 21 | // bloc: CounterBloc()); 22 | return MaterialApp( 23 | debugShowCheckedModeBanner: false, 24 | home: StreamHome(), 25 | ); 26 | } 27 | } 28 | 29 | class StreamHome extends StatelessWidget { 30 | @override 31 | Widget build(BuildContext context) { 32 | return Scaffold( 33 | body: SafeArea( 34 | child: Container( 35 | alignment: Alignment.center, 36 | child: Provide(builder: (_, widget, countProvider) => Text('${countProvider.value}')), 37 | )), 38 | floatingActionButton: FloatingActionButton( 39 | onPressed: () => 40 | Provide.value(context).changeValue( 41 | Provide.value(context).value + 1), 42 | child: Icon(Icons.add)), 43 | ); 44 | } 45 | } 46 | 47 | //class StreamHome extends StatelessWidget { 48 | // @override 49 | // Widget build(BuildContext context) { 50 | // final CounterBloc _bloc = BlocProvider.of(context); 51 | // return Scaffold( 52 | // body: SafeArea( 53 | // child: Container( 54 | // alignment: Alignment.center, 55 | // child: StreamBuilder( 56 | // initialData: _bloc.count, 57 | // stream: _bloc.countStream, 58 | // builder: (_, snapshot) => Text('${snapshot.data}', style: TextStyle(fontSize: 20.0)), 59 | // ), 60 | // )), 61 | // floatingActionButton: 62 | // FloatingActionButton(onPressed: () => _bloc.dispatch(_bloc.count + 1), child: Icon(Icons.add)), 63 | // ); 64 | // } 65 | //} 66 | 67 | //class StreamHome extends StatefulWidget { 68 | // @override 69 | // _StreamHomeState createState() => _StreamHomeState(); 70 | //} 71 | // 72 | //class _StreamHomeState extends State { 73 | // // 定义一个全局的 `StreamController` 74 | // StreamController _controller = StreamController.broadcast(); 75 | // // `sink` 用于传入新的数据 76 | // Sink _sink; 77 | // int _counter = 0; 78 | // 79 | //// StreamSubscription _subscription; 80 | // 81 | // @override 82 | // void initState() { 83 | // super.initState(); 84 | // 85 | // _sink = _controller.sink; 86 | // 87 | //// _subscription = _controller.stream.where((value) => value > 10).take(5).listen((data) => print('Listen: $data')); 88 | //// 89 | //// List.generate(20, (index) => _sink.add(index)); 90 | //// 91 | //// _sink.add('A'); 92 | //// _sink.add(11); 93 | //// _subscription.pause(); 94 | //// _sink.add(11.16); 95 | //// _subscription.resume(); 96 | //// _sink.add([1, 2, 3]); 97 | //// _sink.add({'a': 1, 'b': 2}); 98 | // } 99 | // 100 | // @override 101 | // void dispose() { 102 | // super.dispose(); 103 | // // 需要销毁资源 104 | // _sink.close(); 105 | // _controller.close(); 106 | //// _subscription.cancel(); 107 | // } 108 | // 109 | // @override 110 | // Widget build(BuildContext context) { 111 | // return Scaffold( 112 | // body: SafeArea( 113 | // child: Container( 114 | // alignment: Alignment.center, 115 | // child: StreamBuilder( 116 | // builder: (_, snapshot) => Text('${snapshot.data}', style: TextStyle(fontSize: 24.0)), 117 | // stream: _controller.stream, // stream 在 StreamBuilder 销毁的时候会自动销毁 118 | // initialData: _counter, 119 | // ), 120 | // )), 121 | // // 通过 `sink` 传入新的数据,去通知 `stream` 更新到 builder 中 122 | // floatingActionButton: FloatingActionButton( 123 | // onPressed: () => _sink.add(_counter++), 124 | // child: Icon(Icons.add), 125 | // ), 126 | // ); 127 | // } 128 | //} 129 | -------------------------------------------------------------------------------- /lib/bloc_network/random_user.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'random_user.g.dart'; 4 | 5 | @JsonSerializable() 6 | class RandomUserModel { 7 | InfoBean info; 8 | List results; 9 | 10 | RandomUserModel(); 11 | 12 | factory RandomUserModel.fromJson(json) => _$RandomUserModelFromJson(json); 13 | 14 | static Map toJson(RandomUserModel model) => _$RandomUserModelToJson(model); 15 | 16 | static RandomUserModel fromMap(Map map) { 17 | RandomUserModel user = new RandomUserModel(); 18 | user.info = InfoBean.fromMap(map['info']); 19 | user.results = UserInfo.fromMapList(map['results']); 20 | return user; 21 | } 22 | 23 | static List fromMapList(dynamic mapList) { 24 | List list = new List(mapList.length); 25 | for (int i = 0; i < mapList.length; i++) { 26 | list[i] = fromMap(mapList[i]); 27 | } 28 | return list; 29 | } 30 | } 31 | 32 | @JsonSerializable() 33 | class InfoBean { 34 | String seed; 35 | String version; 36 | int results; 37 | int page; 38 | 39 | InfoBean(); 40 | 41 | factory InfoBean.fromJson(json) => _$InfoBeanFromJson(json); 42 | 43 | static toJson(InfoBean info) => _$InfoBeanToJson(info); 44 | 45 | static InfoBean fromMap(Map map) { 46 | InfoBean infoBean = new InfoBean(); 47 | infoBean.seed = map['seed']; 48 | infoBean.version = map['version']; 49 | infoBean.results = map['results']; 50 | infoBean.page = map['page']; 51 | return infoBean; 52 | } 53 | 54 | static List fromMapList(dynamic mapList) { 55 | List list = new List(mapList.length); 56 | for (int i = 0; i < mapList.length; i++) { 57 | list[i] = fromMap(mapList[i]); 58 | } 59 | return list; 60 | } 61 | } 62 | 63 | @JsonSerializable() 64 | class UserInfo { 65 | String gender; 66 | String email; 67 | String phone; 68 | String cell; 69 | String nat; 70 | DobBean dob; 71 | IdBean id; 72 | LocationBean location; 73 | LoginBean login; 74 | NameBean name; 75 | PictureBean picture; 76 | RegisteredBean registered; 77 | 78 | UserInfo(); 79 | 80 | factory UserInfo.fromJson(json) => _$UserInfoFromJson(json); 81 | 82 | static toJson(UserInfo user) => _$UserInfoToJson(user); 83 | 84 | static UserInfo fromMap(Map map) { 85 | UserInfo resultsListBean = new UserInfo(); 86 | resultsListBean.gender = map['gender']; 87 | resultsListBean.email = map['email']; 88 | resultsListBean.phone = map['phone']; 89 | resultsListBean.cell = map['cell']; 90 | resultsListBean.nat = map['nat']; 91 | resultsListBean.dob = DobBean.fromMap(map['dob']); 92 | resultsListBean.id = IdBean.fromMap(map['id']); 93 | resultsListBean.location = LocationBean.fromMap(map['location']); 94 | resultsListBean.login = LoginBean.fromMap(map['login']); 95 | resultsListBean.name = NameBean.fromMap(map['name']); 96 | resultsListBean.picture = PictureBean.fromMap(map['picture']); 97 | resultsListBean.registered = RegisteredBean.fromMap(map['registered']); 98 | return resultsListBean; 99 | } 100 | 101 | static List fromMapList(dynamic mapList) { 102 | List list = new List(mapList.length); 103 | for (int i = 0; i < mapList.length; i++) { 104 | list[i] = fromMap(mapList[i]); 105 | } 106 | return list; 107 | } 108 | } 109 | 110 | @JsonSerializable() 111 | class DobBean { 112 | String date; 113 | int age; 114 | 115 | DobBean(); 116 | 117 | factory DobBean.fromJson(json) => _$DobBeanFromJson(json); 118 | 119 | static toJson(DobBean dob) => _$DobBeanToJson(dob); 120 | 121 | static DobBean fromMap(Map map) { 122 | DobBean dobBean = new DobBean(); 123 | dobBean.date = map['date']; 124 | dobBean.age = map['age']; 125 | return dobBean; 126 | } 127 | 128 | static List fromMapList(dynamic mapList) { 129 | List list = new List(mapList.length); 130 | for (int i = 0; i < mapList.length; i++) { 131 | list[i] = fromMap(mapList[i]); 132 | } 133 | return list; 134 | } 135 | } 136 | 137 | @JsonSerializable() 138 | class IdBean { 139 | String name; 140 | String value; 141 | 142 | IdBean(); 143 | 144 | factory IdBean.fromJson(json) => _$IdBeanFromJson(json); 145 | 146 | static toJson(IdBean id) => _$IdBeanToJson(id); 147 | 148 | static IdBean fromMap(Map map) { 149 | IdBean idBean = new IdBean(); 150 | idBean.name = map['name']; 151 | idBean.value = map['value']; 152 | return idBean; 153 | } 154 | 155 | static List fromMapList(dynamic mapList) { 156 | List list = new List(mapList.length); 157 | for (int i = 0; i < mapList.length; i++) { 158 | list[i] = fromMap(mapList[i]); 159 | } 160 | return list; 161 | } 162 | } 163 | 164 | @JsonSerializable() 165 | class LocationBean { 166 | String street; 167 | String city; 168 | String state; 169 | String postcode; 170 | CoordinatesBean coordinates; 171 | TimezoneBean timezone; 172 | 173 | LocationBean(); 174 | 175 | factory LocationBean.fromJson(json) => _$LocationBeanFromJson(json); 176 | 177 | static toJson(LocationBean location) => _$LocationBeanToJson(location); 178 | 179 | static LocationBean fromMap(Map map) { 180 | LocationBean locationBean = new LocationBean(); 181 | locationBean.street = map['street']; 182 | locationBean.city = map['city']; 183 | locationBean.state = map['state']; 184 | locationBean.postcode = '${map['postcode']}'; 185 | locationBean.coordinates = CoordinatesBean.fromMap(map['coordinates']); 186 | locationBean.timezone = TimezoneBean.fromMap(map['timezone']); 187 | return locationBean; 188 | } 189 | 190 | static List fromMapList(dynamic mapList) { 191 | List list = new List(mapList.length); 192 | for (int i = 0; i < mapList.length; i++) { 193 | list[i] = fromMap(mapList[i]); 194 | } 195 | return list; 196 | } 197 | } 198 | 199 | @JsonSerializable() 200 | class LoginBean { 201 | String uuid; 202 | String username; 203 | String password; 204 | String salt; 205 | String md5; 206 | String sha1; 207 | String sha256; 208 | 209 | LoginBean(); 210 | 211 | factory LoginBean.fromJson(json) => _$LoginBeanFromJson(json); 212 | 213 | static toJson(LoginBean login) => _$LoginBeanToJson(login); 214 | 215 | static LoginBean fromMap(Map map) { 216 | LoginBean loginBean = new LoginBean(); 217 | loginBean.uuid = map['uuid']; 218 | loginBean.username = map['username']; 219 | loginBean.password = map['password']; 220 | loginBean.salt = map['salt']; 221 | loginBean.md5 = map['md5']; 222 | loginBean.sha1 = map['sha1']; 223 | loginBean.sha256 = map['sha256']; 224 | return loginBean; 225 | } 226 | 227 | static List fromMapList(dynamic mapList) { 228 | List list = new List(mapList.length); 229 | for (int i = 0; i < mapList.length; i++) { 230 | list[i] = fromMap(mapList[i]); 231 | } 232 | return list; 233 | } 234 | } 235 | 236 | @JsonSerializable() 237 | class NameBean { 238 | String title; 239 | String first; 240 | String last; 241 | 242 | NameBean(); 243 | 244 | factory NameBean.fromJson(json) => _$NameBeanFromJson(json); 245 | 246 | static toJson(NameBean name) => _$NameBeanToJson(name); 247 | 248 | static NameBean fromMap(Map map) { 249 | NameBean nameBean = new NameBean(); 250 | nameBean.title = map['title']; 251 | nameBean.first = map['first']; 252 | nameBean.last = map['last']; 253 | return nameBean; 254 | } 255 | 256 | static List fromMapList(dynamic mapList) { 257 | List list = new List(mapList.length); 258 | for (int i = 0; i < mapList.length; i++) { 259 | list[i] = fromMap(mapList[i]); 260 | } 261 | return list; 262 | } 263 | } 264 | 265 | @JsonSerializable() 266 | class PictureBean { 267 | String large; 268 | String medium; 269 | String thumbnail; 270 | 271 | PictureBean(); 272 | 273 | factory PictureBean.fromJson(json) => _$PictureBeanFromJson(json); 274 | 275 | static toJson(PictureBean pic) => _$PictureBeanToJson(pic); 276 | 277 | static PictureBean fromMap(Map map) { 278 | PictureBean pictureBean = new PictureBean(); 279 | pictureBean.large = map['large']; 280 | pictureBean.medium = map['medium']; 281 | pictureBean.thumbnail = map['thumbnail']; 282 | return pictureBean; 283 | } 284 | 285 | static List fromMapList(dynamic mapList) { 286 | List list = new List(mapList.length); 287 | for (int i = 0; i < mapList.length; i++) { 288 | list[i] = fromMap(mapList[i]); 289 | } 290 | return list; 291 | } 292 | } 293 | 294 | @JsonSerializable() 295 | class RegisteredBean { 296 | String date; 297 | int age; 298 | 299 | RegisteredBean(); 300 | 301 | factory RegisteredBean.fromJson(json) => _$RegisteredBeanFromJson(json); 302 | 303 | static toJson(RegisteredBean register) => _$RegisteredBeanToJson(register); 304 | 305 | static RegisteredBean fromMap(Map map) { 306 | RegisteredBean registeredBean = new RegisteredBean(); 307 | registeredBean.date = map['date']; 308 | registeredBean.age = map['age']; 309 | return registeredBean; 310 | } 311 | 312 | static List fromMapList(dynamic mapList) { 313 | List list = new List(mapList.length); 314 | for (int i = 0; i < mapList.length; i++) { 315 | list[i] = fromMap(mapList[i]); 316 | } 317 | return list; 318 | } 319 | } 320 | 321 | @JsonSerializable() 322 | class CoordinatesBean { 323 | String latitude; 324 | String longitude; 325 | 326 | CoordinatesBean(); 327 | 328 | factory CoordinatesBean.fromJson(json) => _$CoordinatesBeanFromJson(json); 329 | 330 | static toJson(CoordinatesBean coordinates) => _$CoordinatesBeanToJson(coordinates); 331 | 332 | static CoordinatesBean fromMap(Map map) { 333 | CoordinatesBean coordinatesBean = new CoordinatesBean(); 334 | coordinatesBean.latitude = map['latitude']; 335 | coordinatesBean.longitude = map['longitude']; 336 | return coordinatesBean; 337 | } 338 | 339 | static List fromMapList(dynamic mapList) { 340 | List list = new List(mapList.length); 341 | for (int i = 0; i < mapList.length; i++) { 342 | list[i] = fromMap(mapList[i]); 343 | } 344 | return list; 345 | } 346 | } 347 | 348 | @JsonSerializable() 349 | class TimezoneBean { 350 | String offset; 351 | String description; 352 | 353 | TimezoneBean(); 354 | 355 | factory TimezoneBean.fromJson(json) => _$TimezoneBeanFromJson(json); 356 | 357 | static toJson(TimezoneBean tz) => _$TimezoneBeanToJson(tz); 358 | 359 | static TimezoneBean fromMap(Map map) { 360 | TimezoneBean timezoneBean = new TimezoneBean(); 361 | timezoneBean.offset = map['offset']; 362 | timezoneBean.description = map['description']; 363 | return timezoneBean; 364 | } 365 | 366 | static List fromMapList(dynamic mapList) { 367 | List list = new List(mapList.length); 368 | for (int i = 0; i < mapList.length; i++) { 369 | list[i] = fromMap(mapList[i]); 370 | } 371 | return list; 372 | } 373 | } 374 | -------------------------------------------------------------------------------- /lib/bloc_network/random_user.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'random_user.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | RandomUserModel _$RandomUserModelFromJson(Map json) { 10 | return RandomUserModel() 11 | ..info = json['info'] == null 12 | ? null 13 | : InfoBean.fromJson(json['info'] as Map) 14 | ..results = (json['results'] as List) 15 | ?.map((e) => 16 | e == null ? null : UserInfo.fromJson(e as Map)) 17 | ?.toList(); 18 | } 19 | 20 | Map _$RandomUserModelToJson(RandomUserModel instance) => 21 | {'info': instance.info, 'results': instance.results}; 22 | 23 | InfoBean _$InfoBeanFromJson(Map json) { 24 | return InfoBean() 25 | ..seed = json['seed'] as String 26 | ..version = json['version'] as String 27 | ..results = json['results'] as int 28 | ..page = json['page'] as int; 29 | } 30 | 31 | Map _$InfoBeanToJson(InfoBean instance) => { 32 | 'seed': instance.seed, 33 | 'version': instance.version, 34 | 'results': instance.results, 35 | 'page': instance.page 36 | }; 37 | 38 | UserInfo _$UserInfoFromJson(Map json) { 39 | return UserInfo() 40 | ..gender = json['gender'] as String 41 | ..email = json['email'] as String 42 | ..phone = json['phone'] as String 43 | ..cell = json['cell'] as String 44 | ..nat = json['nat'] as String 45 | ..dob = json['dob'] == null 46 | ? null 47 | : DobBean.fromJson(json['dob'] as Map) 48 | ..id = json['id'] == null 49 | ? null 50 | : IdBean.fromJson(json['id'] as Map) 51 | ..location = json['location'] == null 52 | ? null 53 | : LocationBean.fromJson(json['location'] as Map) 54 | ..login = json['login'] == null 55 | ? null 56 | : LoginBean.fromJson(json['login'] as Map) 57 | ..name = json['name'] == null 58 | ? null 59 | : NameBean.fromJson(json['name'] as Map) 60 | ..picture = json['picture'] == null 61 | ? null 62 | : PictureBean.fromJson(json['picture'] as Map) 63 | ..registered = json['registered'] == null 64 | ? null 65 | : RegisteredBean.fromJson(json['registered'] as Map); 66 | } 67 | 68 | Map _$UserInfoToJson(UserInfo instance) => { 69 | 'gender': instance.gender, 70 | 'email': instance.email, 71 | 'phone': instance.phone, 72 | 'cell': instance.cell, 73 | 'nat': instance.nat, 74 | 'dob': instance.dob, 75 | 'id': instance.id, 76 | 'location': instance.location, 77 | 'login': instance.login, 78 | 'name': instance.name, 79 | 'picture': instance.picture, 80 | 'registered': instance.registered 81 | }; 82 | 83 | DobBean _$DobBeanFromJson(Map json) { 84 | return DobBean() 85 | ..date = json['date'] as String 86 | ..age = json['age'] as int; 87 | } 88 | 89 | Map _$DobBeanToJson(DobBean instance) => 90 | {'date': instance.date, 'age': instance.age}; 91 | 92 | IdBean _$IdBeanFromJson(Map json) { 93 | return IdBean() 94 | ..name = json['name'] as String 95 | ..value = json['value'] as String; 96 | } 97 | 98 | Map _$IdBeanToJson(IdBean instance) => 99 | {'name': instance.name, 'value': instance.value}; 100 | 101 | LocationBean _$LocationBeanFromJson(Map json) { 102 | return LocationBean() 103 | ..street = json['street'] as String 104 | ..city = json['city'] as String 105 | ..state = json['state'] as String 106 | ..postcode = json['postcode'] as String 107 | ..coordinates = json['coordinates'] == null 108 | ? null 109 | : CoordinatesBean.fromJson(json['coordinates'] as Map) 110 | ..timezone = json['timezone'] == null 111 | ? null 112 | : TimezoneBean.fromJson(json['timezone'] as Map); 113 | } 114 | 115 | Map _$LocationBeanToJson(LocationBean instance) => 116 | { 117 | 'street': instance.street, 118 | 'city': instance.city, 119 | 'state': instance.state, 120 | 'postcode': instance.postcode, 121 | 'coordinates': instance.coordinates, 122 | 'timezone': instance.timezone 123 | }; 124 | 125 | LoginBean _$LoginBeanFromJson(Map json) { 126 | return LoginBean() 127 | ..uuid = json['uuid'] as String 128 | ..username = json['username'] as String 129 | ..password = json['password'] as String 130 | ..salt = json['salt'] as String 131 | ..md5 = json['md5'] as String 132 | ..sha1 = json['sha1'] as String 133 | ..sha256 = json['sha256'] as String; 134 | } 135 | 136 | Map _$LoginBeanToJson(LoginBean instance) => { 137 | 'uuid': instance.uuid, 138 | 'username': instance.username, 139 | 'password': instance.password, 140 | 'salt': instance.salt, 141 | 'md5': instance.md5, 142 | 'sha1': instance.sha1, 143 | 'sha256': instance.sha256 144 | }; 145 | 146 | NameBean _$NameBeanFromJson(Map json) { 147 | return NameBean() 148 | ..title = json['title'] as String 149 | ..first = json['first'] as String 150 | ..last = json['last'] as String; 151 | } 152 | 153 | Map _$NameBeanToJson(NameBean instance) => { 154 | 'title': instance.title, 155 | 'first': instance.first, 156 | 'last': instance.last 157 | }; 158 | 159 | PictureBean _$PictureBeanFromJson(Map json) { 160 | return PictureBean() 161 | ..large = json['large'] as String 162 | ..medium = json['medium'] as String 163 | ..thumbnail = json['thumbnail'] as String; 164 | } 165 | 166 | Map _$PictureBeanToJson(PictureBean instance) => 167 | { 168 | 'large': instance.large, 169 | 'medium': instance.medium, 170 | 'thumbnail': instance.thumbnail 171 | }; 172 | 173 | RegisteredBean _$RegisteredBeanFromJson(Map json) { 174 | return RegisteredBean() 175 | ..date = json['date'] as String 176 | ..age = json['age'] as int; 177 | } 178 | 179 | Map _$RegisteredBeanToJson(RegisteredBean instance) => 180 | {'date': instance.date, 'age': instance.age}; 181 | 182 | CoordinatesBean _$CoordinatesBeanFromJson(Map json) { 183 | return CoordinatesBean() 184 | ..latitude = json['latitude'] as String 185 | ..longitude = json['longitude'] as String; 186 | } 187 | 188 | Map _$CoordinatesBeanToJson(CoordinatesBean instance) => 189 | { 190 | 'latitude': instance.latitude, 191 | 'longitude': instance.longitude 192 | }; 193 | 194 | TimezoneBean _$TimezoneBeanFromJson(Map json) { 195 | return TimezoneBean() 196 | ..offset = json['offset'] as String 197 | ..description = json['description'] as String; 198 | } 199 | 200 | Map _$TimezoneBeanToJson(TimezoneBean instance) => 201 | { 202 | 'offset': instance.offset, 203 | 'description': instance.description 204 | }; 205 | -------------------------------------------------------------------------------- /lib/bloc_network/user_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_arts_demos_app/application.dart'; 2 | import 'package:flutter_arts_demos_app/bloc/bloc_provider.dart'; 3 | import 'package:flutter_arts_demos_app/bloc_network/random_user.dart'; 4 | import 'package:rxdart/rxdart.dart'; 5 | 6 | class UserBloc extends BaseBloc { 7 | RandomUserModel _user; 8 | 9 | RandomUserModel get user => _user; 10 | 11 | BehaviorSubject _controller = BehaviorSubject(); 12 | 13 | Observable get stream => Observable(_controller.stream); 14 | 15 | updateUserInfo() { 16 | Application.http.getRequest('/api').then((response) { 17 | RandomUserModel model = RandomUserModel.fromMap(response.data); 18 | _user = model; 19 | _controller.add(model); 20 | }); 21 | } 22 | 23 | @override 24 | void dispose() { 25 | _controller?.close(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/bloc_network/user_page_main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_arts_demos_app/bloc/bloc_provider.dart'; 4 | import 'package:flutter_arts_demos_app/bloc_network/random_user.dart'; 5 | import 'package:flutter_arts_demos_app/bloc_network/user_bloc.dart'; 6 | 7 | class UserPageDemo extends StatelessWidget { 8 | String _upperFirst(String content) { 9 | assert(content != null && content.isNotEmpty); 10 | return '${content.substring(0, 1).toUpperCase()}${content.substring(1)}'; 11 | } 12 | 13 | Widget _userLocation(String info) => Padding( 14 | padding: const EdgeInsets.only(top: 4.0), 15 | child: Text(info, style: TextStyle(color: Colors.white, fontSize: 16.0))); 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | UserBloc _bloc = BlocProvider.of(context); 20 | _bloc.updateUserInfo(); 21 | 22 | return Scaffold( 23 | body: StreamBuilder( 24 | builder: (_, AsyncSnapshot snapshot) => Container( 25 | alignment: Alignment.center, 26 | decoration: BoxDecoration( 27 | gradient: LinearGradient( 28 | begin: Alignment.topCenter, 29 | end: Alignment.bottomCenter, 30 | colors: [Colors.blue[600], Colors.blue[400]])), 31 | child: !snapshot.hasData 32 | ? CupertinoActivityIndicator(radius: 12.0) 33 | : Column(mainAxisAlignment: MainAxisAlignment.center, children: [ 34 | InkWell( 35 | child: ClipOval( 36 | child: FadeInImage.assetNetwork( 37 | placeholder: 'images/ava_default.png', image: snapshot.data.results[0].picture.large), 38 | ), 39 | onTap: () => _bloc.updateUserInfo()), 40 | Padding( 41 | padding: const EdgeInsets.only(top: 20.0), 42 | child: Text( 43 | '${_upperFirst(snapshot.data.results[0].name.first)} ${_upperFirst(snapshot.data.results[0].name.last)}', 44 | style: TextStyle(color: Colors.white, fontSize: 24.0)), 45 | ), 46 | Text('${snapshot.data.results[0].email}', 47 | style: TextStyle(color: Colors.white, fontSize: 18.0)), 48 | _userLocation('${snapshot.data.results[0].location.street}'), 49 | _userLocation('${_upperFirst(snapshot.data.results[0].location.city)}'), 50 | _userLocation('${_upperFirst(snapshot.data.results[0].location.state)}'), 51 | ]), 52 | ), 53 | initialData: _bloc.user, 54 | stream: _bloc.stream), 55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /lib/colsed/fuli_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'package:flutter_arts_demos_app/application.dart'; 3 | import 'package:flutter_arts_demos_app/bloc/bloc_provider.dart'; 4 | import 'package:flutter_arts_demos_app/colsed/fuli_model.dart'; 5 | import 'package:rxdart/rxdart.dart'; 6 | 7 | class FuliBloc extends BaseBloc { 8 | List _models = []; 9 | int _page = 1; 10 | 11 | List get fuLi => _models; 12 | 13 | int get page => _page; 14 | 15 | BehaviorSubject> _controller = BehaviorSubject(); 16 | 17 | Observable> get stream => Observable(_controller.stream); 18 | 19 | increasePage() => _page++; 20 | 21 | refreshFuli(List models) { 22 | _models.clear(); 23 | _models.addAll(models); 24 | _controller.add(_models); 25 | } 26 | 27 | loadMoreFuli(List models) { 28 | _models.addAll(models); 29 | _controller.add(_models); 30 | } 31 | 32 | Future> requestFuli(int page) async { 33 | Response resp = await Application.http.getRequest('http://adr.meizitu.net/wp-json/wp/v2/posts', 34 | params: {'page': page, 'per_page': 10}, callback: (msg) => print(msg)); 35 | return resp != null ? FuliModel.fromMapList(resp.data) : []; 36 | } 37 | 38 | @override 39 | void dispose() { 40 | _controller?.close(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/colsed/fuli_model.dart: -------------------------------------------------------------------------------- 1 | class FuliModel { 2 | String title; 3 | 4 | // ignore: non_constant_identifier_names 5 | String thumb_src; 6 | 7 | // ignore: non_constant_identifier_names 8 | String thumb_src_min; 9 | int id; 10 | 11 | // ignore: non_constant_identifier_names 12 | int img_num; 13 | 14 | static FuliModel fromMap(Map map) { 15 | FuliModel model = new FuliModel(); 16 | model.title = map['title']; 17 | model.thumb_src = map['thumb_src']; 18 | model.thumb_src_min = map['thumb_src_min']; 19 | model.id = map['id']; 20 | model.img_num = map['img_num']; 21 | return model; 22 | } 23 | 24 | static List fromMapList(dynamic mapList) { 25 | List list = new List(mapList.length); 26 | for (int i = 0; i < mapList.length; i++) { 27 | list[i] = fromMap(mapList[i]); 28 | } 29 | return list; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/colsed/fuli_page_main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_arts_demos_app/bloc/bloc_provider.dart'; 4 | import 'package:flutter_arts_demos_app/colsed/fuli_bloc.dart'; 5 | import 'package:flutter_arts_demos_app/colsed/fuli_model.dart'; 6 | import 'package:flutter_easyrefresh/easy_refresh.dart'; 7 | import 'package:flutter_easyrefresh/ball_pulse_footer.dart'; 8 | 9 | class FuliPage extends StatefulWidget { 10 | @override 11 | _FuliPageState createState() => _FuliPageState(); 12 | } 13 | 14 | class _FuliPageState extends State { 15 | ScrollController _controller = ScrollController(); 16 | GlobalKey _refreshKey = GlobalKey(); 17 | GlobalKey _footerKey = GlobalKey(); 18 | FuliBloc _bloc; 19 | 20 | @override 21 | void initState() { 22 | super.initState(); 23 | _bloc = BlocProvider.of(context); 24 | _request(); 25 | } 26 | 27 | _request() async { 28 | List fulis = await _bloc.requestFuli(_bloc.page); 29 | _bloc.page == 1 ? _bloc.refreshFuli(fulis) : _bloc.loadMoreFuli(fulis); 30 | _bloc.increasePage(); 31 | } 32 | 33 | @override 34 | Widget build(BuildContext context) { 35 | return Scaffold( 36 | body: StreamBuilder( 37 | builder: (_, AsyncSnapshot> snapshot) => Container( 38 | alignment: Alignment.center, 39 | child: !snapshot.hasData 40 | ? CupertinoActivityIndicator(radius: 12.0) 41 | : EasyRefresh( 42 | key: _refreshKey, 43 | refreshFooter: BallPulseFooter(key: _footerKey), 44 | loadMore: () { 45 | _request(); 46 | }, 47 | child: GridView.count( 48 | crossAxisSpacing: 8.0, 49 | mainAxisSpacing: 4.0, 50 | childAspectRatio: 0.5, 51 | controller: _controller, 52 | crossAxisCount: 2, 53 | children: snapshot.data 54 | .map( 55 | (p) => Column( 56 | children: [ 57 | FadeInImage.assetNetwork( 58 | placeholder: 'images/ali.jpg', 59 | image: '${p.thumb_src}', 60 | fit: BoxFit.cover, 61 | ), 62 | Padding( 63 | padding: const EdgeInsets.only(top: 8.0), 64 | child: 65 | Text('${p.title}', style: TextStyle(fontSize: 14.0, color: Colors.black))) 66 | ], 67 | ), 68 | ) 69 | .toList(), 70 | )), 71 | ), 72 | stream: _bloc.stream, 73 | initialData: _bloc.fuLi, 74 | ), 75 | ); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /lib/custom_routes.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ScalePageRoute extends PageRouteBuilder { 4 | final Widget widget; 5 | 6 | ScalePageRoute(this.widget) 7 | : super( 8 | transitionDuration: Duration(milliseconds: 500), 9 | pageBuilder: (context, anim, _) => widget, 10 | transitionsBuilder: (context, anim, _, child) => ScaleTransition( 11 | scale: Tween(begin: 0.0, end: 1.0).animate(anim), 12 | child: child, 13 | )); 14 | } 15 | 16 | class FadeInPageRoute extends PageRouteBuilder { 17 | final Widget widget; 18 | 19 | FadeInPageRoute(this.widget) 20 | : super( 21 | transitionDuration: Duration(milliseconds: 1000), 22 | pageBuilder: (context, animation, _) => widget, 23 | transitionsBuilder: (context, animation, _, child) => FadeTransition( 24 | opacity: Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation(parent: animation, curve: Curves.ease)), 25 | child: child, 26 | )); 27 | } 28 | 29 | class RotateScalePageRoute extends PageRouteBuilder { 30 | final Widget widget; 31 | 32 | RotateScalePageRoute(this.widget) 33 | : super( 34 | transitionDuration: Duration(milliseconds: 500), 35 | pageBuilder: (context, animation, _) => widget, 36 | transitionsBuilder: (context, animation, _, child) => RotationTransition( 37 | 38 | /// turns 值表示旋转 turns * 2π 39 | turns: Tween(begin: 0.0, end: 1.0).animate( 40 | CurvedAnimation(parent: animation, curve: Curves.ease), 41 | ), 42 | child: ScaleTransition( 43 | scale: Tween(begin: 0.1, end: 1.0).animate( 44 | CurvedAnimation(parent: animation, curve: Curves.ease), 45 | ), 46 | child: child, 47 | ))); 48 | } 49 | -------------------------------------------------------------------------------- /lib/db_utils.dart: -------------------------------------------------------------------------------- 1 | import 'package:path/path.dart'; 2 | import 'package:sqflite/sqflite.dart'; 3 | 4 | class DatabaseUtils { 5 | final String _tableStudent = 'student'; 6 | 7 | static Database _database; // 创建单例,防止重复打开消耗内存 8 | 9 | static DatabaseUtils _instance; 10 | 11 | static DatabaseUtils get instance => DatabaseUtils(); 12 | 13 | DatabaseUtils._internal() { 14 | getDatabasesPath().then((path) async { 15 | _database = await openDatabase(join(path, 'demo.db'), version: 2, onCreate: (db, version) { 16 | // 创建数据库的时候在这边调用 17 | db.execute('create table $_tableStudent(' 18 | 'id integer primary key,' 19 | 'name text not null,' 20 | 'weight real not null,' 21 | 'age integer not null default 0,' 22 | 'gender integer not null default 0)'); 23 | 24 | // 更新升级增加的字段 25 | db.execute('alter table $_tableStudent add column birthday text'); 26 | }, onUpgrade: (db, oldVersion, newVersion) { 27 | // 更新升级数据库的时候在这操作 28 | if (oldVersion == 1) db.execute('alter table $_tableStudent add column birthday text'); 29 | }, onOpen: (db) { 30 | // 打开数据库时候的回调 31 | print('${db.path}'); 32 | }); 33 | }); 34 | } 35 | 36 | factory DatabaseUtils() { 37 | if (_instance == null) _instance = DatabaseUtils._internal(); 38 | return _instance; 39 | } 40 | 41 | Future insertStudent(StudentDbModel student) async => _database.insert(_tableStudent, student.toMap()); 42 | 43 | // 批量操作需要通过 batch 实现,最后需要 commit 提交 44 | Future insertStudents(List students) async { 45 | var batch = _database.batch(); 46 | students.forEach((s) => insertStudent(s)); 47 | batch.commit(); 48 | } 49 | 50 | // 删除操作 51 | Future deleteStudent(int id) => _database.rawDelete('delete from $_tableStudent where id = ?', [id]); 52 | 53 | // 更新数据操作 54 | Future updateStudentAge(int id, int age) => 55 | _database.rawUpdate('update $_tableStudent set age = ? where id = ?', [age, id]); 56 | 57 | Future updateStudentGender(int id, int gender) => 58 | _database.rawUpdate('update $_tableStudent set gender = ? where id = ?', [gender, id]); 59 | 60 | // 查询 61 | Future> queryStudents() async { 62 | List students = []; 63 | List maps = await _database.query(_tableStudent); 64 | maps.forEach((map) => students.add(StudentDbModel.fromMap(map))); 65 | return students; 66 | } 67 | 68 | // 条件查询,会返回一个 map 列表 69 | Future exitsById(int id) async => 70 | (await _database.rawQuery('select id, name, age, gender from $_tableStudent where id = ?', [id])).isNotEmpty; 71 | 72 | Future exitsByName(String name) async => 73 | (await _database.rawQuery('select id, name, age, gender from $_tableStudent where name = ?', [name])).isNotEmpty; 74 | } 75 | 76 | class StudentDbModel { 77 | String name; 78 | int age; 79 | int gender; 80 | double weight; 81 | String birthday; 82 | 83 | StudentDbModel.empty(); 84 | 85 | StudentDbModel(this.name, this.age, this.gender, this.weight, this.birthday); 86 | 87 | StudentDbModel.fromMap(Map map) 88 | : this.name = map['name'], 89 | this.age = map['age'], 90 | this.gender = map['gender'], 91 | this.weight = map['weight'], 92 | this.birthday = map['birthday']; 93 | 94 | Map toMap() { 95 | return {'name': name, 'age': age, 'gender': gender, 'weight': weight, 'birthday': birthday}; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /lib/http_utils.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | 3 | typedef ErrorCallback = void Function(String msg); 4 | 5 | class HttpUtils { 6 | static const GET = 'get'; 7 | static const POST = 'post'; 8 | 9 | static Dio _dio; 10 | 11 | static HttpUtils _instance; 12 | 13 | Dio get hp => _dio; 14 | 15 | HttpUtils._internal(String base) { 16 | _dio = Dio(BaseOptions(baseUrl: base, connectTimeout: 10000, receiveTimeout: 10000)); 17 | } 18 | 19 | factory HttpUtils(String base) { 20 | if (_instance == null) _instance = HttpUtils._internal(base); 21 | return _instance; 22 | } 23 | 24 | addInterceptor(List interceptors) { 25 | _dio.interceptors.clear(); 26 | _dio.interceptors.addAll(interceptors); 27 | } 28 | 29 | Future> getRequest(url, {Map params, ErrorCallback callback}) => 30 | _request(url, GET, params: params, callback: callback); 31 | 32 | Future> postRequest(url, {Map params, ErrorCallback callback}) => 33 | _request(url, POST, params: params, callback: callback); 34 | 35 | Future download(url, path, {ProgressCallback receive, CancelToken token}) => 36 | _dio.download(url, path, onReceiveProgress: receive, cancelToken: token); 37 | 38 | Future> _request( 39 | url, 40 | String method, { 41 | Map params, 42 | Options opt, 43 | ErrorCallback callback, 44 | ProgressCallback send, 45 | ProgressCallback receive, 46 | CancelToken token, 47 | }) async { 48 | try { 49 | Response rep; 50 | 51 | if (method == GET) { 52 | /// 当有参数的时候,get 方法使用 queryParams 会出错,不懂原因,使用拼接没有问题 53 | if (params != null && params.isNotEmpty) { 54 | var sb = StringBuffer('?'); 55 | params.forEach((key, value) { 56 | sb.write('$key=$value&'); 57 | }); 58 | url += sb.toString().substring(0, sb.length - 1); 59 | } 60 | rep = await _dio.get(url, options: opt, onReceiveProgress: receive, cancelToken: token); 61 | } else if (method == POST) { 62 | rep = params == null 63 | ? await _dio.post(url, options: opt, cancelToken: token, onSendProgress: send, onReceiveProgress: receive) 64 | : await _dio.post(url, 65 | data: params, options: opt, cancelToken: token, onSendProgress: send, onReceiveProgress: receive); 66 | } 67 | 68 | if (rep.statusCode != 200 && callback != null) { 69 | callback('network error, and code is ${rep.statusCode}'); 70 | return null; 71 | } 72 | return rep; 73 | } catch (e) { 74 | if (callback != null) { 75 | callback('network error, catch error: ${e.toString()}'); 76 | } 77 | return null; 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /lib/localizations/base_localization.dart: -------------------------------------------------------------------------------- 1 | abstract class BaseLocalization { 2 | String homeTitle(); 3 | 4 | String listDemo(); 5 | 6 | String appbarDemo(); 7 | 8 | String textDemo(); 9 | 10 | String imageDemo(); 11 | 12 | String buttonDemo(); 13 | 14 | String columnDemo(); 15 | } 16 | -------------------------------------------------------------------------------- /lib/localizations/demo_localizations.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/foundation.dart'; 3 | 4 | import 'base_localization.dart'; 5 | import 'localization_en.dart'; 6 | import 'localization_zh.dart'; 7 | 8 | class DemoLocalization { 9 | final Locale locale; 10 | 11 | DemoLocalization(this.locale); 12 | 13 | static Map _localizationValue = { 14 | "en": LocalizationEn(), 15 | "zh": LocalizationZh(), 16 | }; 17 | 18 | BaseLocalization get currentLocale => _localizationValue[locale.languageCode]; 19 | 20 | static DemoLocalization of(BuildContext context) => Localizations.of(context, DemoLocalization); 21 | } 22 | 23 | class DemoLocalizationDelegate extends LocalizationsDelegate { 24 | static DemoLocalizationDelegate delegate = DemoLocalizationDelegate(); 25 | 26 | @override 27 | bool isSupported(Locale locale) { 28 | return ['en', 'zh'].contains(locale.languageCode); 29 | } 30 | 31 | @override 32 | Future load(Locale locale) { 33 | return SynchronousFuture(DemoLocalization(locale)); 34 | } 35 | 36 | @override 37 | bool shouldReload(LocalizationsDelegate old) { 38 | return false; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/localizations/localization_en.dart: -------------------------------------------------------------------------------- 1 | import 'base_localization.dart'; 2 | 3 | class LocalizationEn extends BaseLocalization { 4 | @override 5 | String homeTitle() { 6 | return "En Language"; 7 | } 8 | 9 | @override 10 | String appbarDemo() { 11 | return "Appbar Demo"; 12 | } 13 | 14 | @override 15 | String buttonDemo() { 16 | return "Button Demo"; 17 | } 18 | 19 | @override 20 | String columnDemo() { 21 | return "Column Demo"; 22 | } 23 | 24 | @override 25 | String imageDemo() { 26 | return "Image Demo"; 27 | } 28 | 29 | @override 30 | String listDemo() { 31 | return "List Demo"; 32 | } 33 | 34 | @override 35 | String textDemo() { 36 | return "Text Demo"; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/localizations/localization_zh.dart: -------------------------------------------------------------------------------- 1 | import 'base_localization.dart'; 2 | 3 | class LocalizationZh extends BaseLocalization { 4 | @override 5 | String homeTitle() { 6 | return "Zh Language"; 7 | } 8 | 9 | @override 10 | String appbarDemo() { 11 | return "Appbar 示例"; 12 | } 13 | 14 | @override 15 | String textDemo() { 16 | return "Text 示例"; 17 | } 18 | 19 | @override 20 | String buttonDemo() { 21 | return "Button 示例"; 22 | } 23 | 24 | @override 25 | String columnDemo() { 26 | return "Column 示例"; 27 | } 28 | 29 | @override 30 | String imageDemo() { 31 | return "Image 示例"; 32 | } 33 | 34 | @override 35 | String listDemo() { 36 | return "List 示例"; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:dio/dio.dart'; 4 | import 'package:flutter/cupertino.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:flutter/services.dart'; 7 | import 'package:flutter_arts_demos_app/application.dart'; 8 | import 'package:flutter_arts_demos_app/bloc/bloc_provider.dart'; 9 | import 'package:flutter_arts_demos_app/bloc_network/user_bloc.dart'; 10 | import 'package:flutter_arts_demos_app/bloc_network/user_page_main.dart'; 11 | import 'package:flutter_arts_demos_app/custom_routes.dart'; 12 | import 'package:flutter_arts_demos_app/http_utils.dart'; 13 | import 'package:flutter_arts_demos_app/pages/animation_main.dart'; 14 | import 'package:flutter_arts_demos_app/pages/app_bar_main.dart'; 15 | import 'package:flutter_arts_demos_app/pages/button_main.dart'; 16 | import 'package:flutter_arts_demos_app/pages/checkbox_switch_main.dart'; 17 | import 'package:flutter_arts_demos_app/pages/column_main.dart'; 18 | import 'package:flutter_arts_demos_app/pages/custom_view_main.dart'; 19 | import 'package:flutter_arts_demos_app/pages/data_persistence_main.dart'; 20 | import 'package:flutter_arts_demos_app/pages/expansion_tile_main.dart'; 21 | import 'package:flutter_arts_demos_app/pages/gesture_main.dart'; 22 | import 'package:flutter_arts_demos_app/pages/http_main.dart'; 23 | import 'package:flutter_arts_demos_app/pages/image_main.dart'; 24 | import 'package:flutter_arts_demos_app/pages/login_home_page.dart'; 25 | import 'package:flutter_arts_demos_app/pages/prompt_main.dart'; 26 | import 'package:flutter_arts_demos_app/pages/scrollable_main.dart'; 27 | import 'package:flutter_arts_demos_app/pages/sliver_main.dart'; 28 | import 'package:flutter_arts_demos_app/pages/stack_main.dart'; 29 | import 'package:flutter_arts_demos_app/pages/staggered_animation_main.dart'; 30 | import 'package:flutter_arts_demos_app/pages/suspension_main.dart'; 31 | import 'package:flutter_arts_demos_app/pages/text_field_main.dart'; 32 | import 'package:flutter_arts_demos_app/pages/text_main.dart'; 33 | import 'package:flutter_localizations/flutter_localizations.dart'; 34 | 35 | import 'bloc/language_bloc.dart'; 36 | import 'localizations/demo_localizations.dart'; 37 | 38 | void main() { 39 | Application.http = HttpUtils('https://randomuser.me'); 40 | // 强制竖屏 41 | SystemChrome.setPreferredOrientations([DeviceOrientation.portraitDown, DeviceOrientation.portraitUp]).then((_) { 42 | runApp(BlocProvider(child: DemoApp(), bloc: LanguageBloc())); 43 | 44 | // 透明状态栏 45 | if (Platform.isAndroid) { 46 | SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(statusBarColor: Colors.transparent)); 47 | } 48 | }); 49 | } 50 | 51 | class DemoApp extends StatelessWidget { 52 | @override 53 | Widget build(BuildContext context) { 54 | var bloc = BlocProvider.of(context); 55 | return StreamBuilder( 56 | stream: bloc.localeStream, 57 | initialData: Locale('zh'), 58 | builder: (context, locale) { 59 | return MaterialApp( 60 | title: 'Flutter Learning Demo', 61 | home: MainHomePage(), 62 | routes: {}, 63 | locale: locale.data, 64 | supportedLocales: [locale.data], 65 | localizationsDelegates: [ 66 | GlobalMaterialLocalizations.delegate, 67 | GlobalWidgetsLocalizations.delegate, 68 | DemoLocalizationDelegate.delegate, 69 | ], 70 | debugShowCheckedModeBanner: false, 71 | ); 72 | }); 73 | } 74 | } 75 | 76 | class MainHomePage extends StatelessWidget { 77 | @override 78 | Widget build(BuildContext context) { 79 | return Scaffold( 80 | appBar: AppBar( 81 | title: Text(DemoLocalization.of(context).currentLocale.homeTitle()), 82 | actions: [ 83 | PopupMenuButton(onSelected: (code) { 84 | BlocProvider.of(context).changeLanguage(Locale(code)); 85 | }, itemBuilder: (_) { 86 | return [ 87 | PopupMenuItem( 88 | child: Text('English'), 89 | value: 'en', 90 | ), 91 | PopupMenuItem( 92 | child: Text('中文'), 93 | value: 'zh', 94 | ) 95 | ]; 96 | }) 97 | ], 98 | ), 99 | body: ListView(children: [ 100 | MenuActionItem( 101 | title: DemoLocalization.of(context).currentLocale.listDemo(), 102 | clickAction: () => Navigator.push(context, ScalePageRoute(SuspensionPage())), 103 | ), 104 | MenuActionItem( 105 | title: DemoLocalization.of(context).currentLocale.appbarDemo(), 106 | clickAction: () => Navigator.push(context, ScalePageRoute(AppBarDemoPage())), 107 | ), 108 | MenuActionItem( 109 | title: DemoLocalization.of(context).currentLocale.textDemo(), 110 | clickAction: () => Navigator.push(context, FadeInPageRoute(TextDemoPage())), 111 | ), 112 | MenuActionItem( 113 | title: DemoLocalization.of(context).currentLocale.imageDemo(), 114 | clickAction: () => Navigator.push(context, RotateScalePageRoute(ImageDemoPage())), 115 | ), 116 | MenuActionItem( 117 | title: DemoLocalization.of(context).currentLocale.buttonDemo(), 118 | // CupertinoPageRoute 为 iOS 风格切换,支持侧滑关闭当前页面 119 | clickAction: () => Navigator.push(context, CupertinoPageRoute(builder: (_) => ButtonDemoPage())), 120 | ), 121 | MenuActionItem( 122 | title: DemoLocalization.of(context).currentLocale.columnDemo(), 123 | clickAction: () => Navigator.push(context, MaterialPageRoute(builder: (_) => ColumnDemoPage())), 124 | ), 125 | MenuActionItem( 126 | title: 'Stack 示例', 127 | clickAction: () => Navigator.push(context, ScalePageRoute(StackDemoPage())), 128 | ), 129 | MenuActionItem( 130 | title: 'Check Switch 示例', 131 | clickAction: () => Navigator.push(context, ScalePageRoute(CheckSwitchDemoPage())), 132 | ), 133 | MenuActionItem( 134 | title: 'TextField 示例', 135 | clickAction: () => Navigator.push(context, FadeInPageRoute(TextFieldDemoPage())), 136 | ), 137 | MenuActionItem( 138 | title: '登录注册页面示例', 139 | clickAction: () => Navigator.push(context, RotateScalePageRoute(LoginDemoPage())), 140 | ), 141 | MenuActionItem( 142 | title: '滑动列表示例', 143 | clickAction: () => Navigator.push(context, MaterialPageRoute(builder: (_) => ScrollableDemoPage())), 144 | ), 145 | MenuActionItem( 146 | title: '折叠列表示例', 147 | clickAction: () => Navigator.push(context, MaterialPageRoute(builder: (_) => ExpansionTilesDemoPage())), 148 | ), 149 | MenuActionItem( 150 | title: 'Sliver 系列示例', 151 | clickAction: () => Navigator.push(context, MaterialPageRoute(builder: (_) => SliverDemoPage())), 152 | ), 153 | MenuActionItem( 154 | title: '弹窗示例', 155 | clickAction: () => Navigator.push(context, MaterialPageRoute(builder: (_) => PromptDemoPage())), 156 | ), 157 | MenuActionItem( 158 | title: '手势(基础)示例', 159 | clickAction: () => Navigator.push(context, MaterialPageRoute(builder: (_) => GestureDemoPage())), 160 | ), 161 | MenuActionItem( 162 | title: '动画(基础)示例', 163 | clickAction: () => Navigator.push(context, MaterialPageRoute(builder: (_) => AnimationDemoPage())), 164 | ), 165 | MenuActionItem( 166 | title: '交错动画示例 ', clickAction: () => Navigator.push(context, MaterialPageRoute(builder: (_) => StaggeredAnimationsDemoPage()))), 167 | MenuActionItem(title: '数据持久化示例', clickAction: () => Navigator.push(context, MaterialPageRoute(builder: (_) => DataPersistenceDemoPage()))), 168 | MenuActionItem( 169 | title: '网络示例', 170 | clickAction: () => Navigator.push(context, MaterialPageRoute(builder: (_) => HttpDemoPage())), 171 | ), 172 | MenuActionItem( 173 | title: 'BLoC 展示', 174 | clickAction: () => Navigator.push(context, MaterialPageRoute(builder: (_) => BlocProvider(child: UserPageDemo(), bloc: UserBloc()))), 175 | ), 176 | MenuActionItem( 177 | title: '自定义 view 示例', 178 | clickAction: () => Navigator.push(context, MaterialPageRoute(builder: (_) => CustomViewDemoPage())), 179 | ), 180 | 181 | /// Router 界面因为涉及到带 `Name` 方法的执行,需要单独运行 `router_main.dart` 文件 182 | // MenuActionItem( 183 | // title: 'Route Demo', 184 | // clickAction: () => Navigator.push(context, MaterialPageRoute(builder: (_) => APage())), 185 | // ), 186 | ]) /*, 187 | floatingActionButton: FloatingActionButton( 188 | mini: true, 189 | onPressed: () => Navigator.push( 190 | context, 191 | MaterialPageRoute( 192 | builder: (_) => BlocProvider(child: FuliPage(), bloc: FuliBloc()), 193 | )), 194 | child: Icon(Icons.beach_access, color: Colors.red), 195 | ),*/ 196 | ); 197 | } 198 | } 199 | 200 | /// 点击跳转组件 201 | class MenuActionItem extends StatelessWidget { 202 | final String title; 203 | final VoidCallback clickAction; 204 | 205 | MenuActionItem({Key key, @required this.title, @required this.clickAction}) 206 | : assert(title != null), 207 | assert(clickAction != null), 208 | super(key: key); 209 | 210 | @override 211 | Widget build(BuildContext context) { 212 | return InkWell( 213 | child: Padding( 214 | padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 16.0), 215 | child: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ 216 | Text(title, style: TextStyle(color: Theme.of(context).primaryColor, fontSize: 18.0)), 217 | Icon(Icons.arrow_forward_ios, size: 24.0, color: Theme.of(context).primaryColor) 218 | ])), 219 | onTap: clickAction); 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /lib/pages/animation_main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class AnimationDemoPage extends StatefulWidget { 4 | @override 5 | _AnimationDemoPageState createState() => _AnimationDemoPageState(); 6 | } 7 | 8 | class _AnimationDemoPageState extends State with TickerProviderStateMixin { 9 | AnimationController _animationController; 10 | Animation _scaleAnimation; // 用于控制图标大小 11 | Animation _colorAnimation; // 控制图标颜色 12 | Animation _positionAnimation; // 控制图标位置 13 | 14 | @override 15 | void initState() { 16 | super.initState(); 17 | _animationController = AnimationController(vsync: this, duration: Duration(milliseconds: 2000)); 18 | 19 | // 当动画值发生变化的时候,重绘下 icon 20 | // _animationController.addListener(() { 21 | // setState(() {}); 22 | // }); 23 | 24 | _animationController.addStatusListener((status) { 25 | if (_animationController.status == AnimationStatus.completed) 26 | _animationController.reverse(); 27 | else if (_animationController.status == AnimationStatus.dismissed) _animationController.forward(); 28 | }); 29 | 30 | // 通过 `Tween` 的 `animate` 生成一个 Animation 31 | // 再通过 Animation.value 进行值的修改 32 | // 通过 `chain` 结合 `CurveTween` 修改动画的运动方式 33 | _scaleAnimation = 34 | Tween(begin: 28.0, end: 50.0).chain(CurveTween(curve: Curves.decelerate)).animate(_animationController); 35 | 36 | _colorAnimation = ColorTween(begin: Colors.red[200], end: Colors.red[900]) 37 | .chain(CurveTween(curve: Curves.easeIn)) 38 | .animate(_animationController); 39 | 40 | _positionAnimation = Tween(begin: Offset(100, 100), end: Offset(300, 300)) 41 | .chain(CurveTween(curve: Curves.bounceInOut)) 42 | .animate(_animationController); 43 | 44 | _animationController.forward(); // 启动动画 45 | } 46 | 47 | @override 48 | void dispose() { 49 | // 一定要释放资源 50 | _animationController.dispose(); 51 | super.dispose(); 52 | } 53 | 54 | @override 55 | Widget build(BuildContext context) { 56 | return Scaffold( 57 | appBar: AppBar( 58 | title: Text('Animation Demo'), 59 | ), 60 | // body: Center( 61 | // child: IconButton( 62 | // icon: Icon(Icons.android, color: Colors.green[500], size: _animationController.value), 63 | // onPressed: () { 64 | // // 根据状态执行不同动画运动方式 65 | // if (_animationController.status == AnimationStatus.completed) 66 | // _animationController.reverse(); 67 | // else if (_animationController.status == AnimationStatus.dismissed) 68 | // _animationController.forward(); 69 | // }), 70 | // ), 71 | 72 | // body: Center( 73 | // child: Icon(Icons.favorite, color: Colors.red, size: _scaleAnimation.value), 74 | // ), 75 | 76 | // body: Stack( 77 | // children: [ 78 | // Positioned( 79 | // child: Icon(Icons.favorite, color: _colorAnimation.value, size: _scaleAnimation.value), 80 | // left: _positionAnimation.value.dx, 81 | // top: _positionAnimation.value.dy, 82 | // ) 83 | // ], 84 | // ), 85 | 86 | // body: RunningHeart( 87 | // animations: [_colorAnimation, _scaleAnimation, _positionAnimation], 88 | // animationController: _animationController, 89 | // ), 90 | 91 | body: Container( 92 | alignment: Alignment.center, 93 | child: InkWell( 94 | child: Hero( 95 | tag: 'hero_tag', 96 | child: Image.asset('images/ali.jpg', width: 100.0, height: 100.0), 97 | ), 98 | onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => HeroPage())), 99 | ), 100 | ), 101 | ); 102 | } 103 | } 104 | 105 | class RunningHeart extends AnimatedWidget { 106 | final List animations; // 传入动画列表 107 | final AnimationController animationController; // 控制动画 108 | 109 | RunningHeart({this.animations, this.animationController}) 110 | : assert(animations.length == 3), 111 | assert(animations[0] is Animation), 112 | assert(animations[1] is Animation), 113 | assert(animations[2] is Animation), 114 | super(listenable: animationController); 115 | 116 | @override 117 | Widget build(BuildContext context) { 118 | return Stack( 119 | children: [ 120 | Positioned( 121 | child: Icon(Icons.favorite, color: animations[0].value, size: animations[1].value), 122 | left: animations[2].value.dx, 123 | top: animations[2].value.dy, 124 | ) 125 | ], 126 | ); 127 | } 128 | } 129 | 130 | class HeroPage extends StatelessWidget { 131 | @override 132 | Widget build(BuildContext context) { 133 | return Scaffold( 134 | body: Container( 135 | alignment: Alignment.center, 136 | child: InkWell( 137 | child: Hero(tag: 'hero_tag', child: Image.asset('images/ali.jpg', width: 200.0, height: 200.0)), 138 | onTap: () => Navigator.pop(context), 139 | ), 140 | ), 141 | ); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /lib/pages/app_bar_main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class AppBarDemoPage extends StatefulWidget { 4 | @override 5 | _AppBarDemoPageState createState() => _AppBarDemoPageState(); 6 | } 7 | 8 | class _AppBarDemoPageState extends State with SingleTickerProviderStateMixin { 9 | List _abs = ['A', 'B', 'S']; 10 | TabController _tabController; 11 | 12 | // 用于同 TabBar 进行联动 13 | PageController _pageController; 14 | 15 | @override 16 | void initState() { 17 | super.initState(); 18 | _tabController = TabController(length: _abs.length, vsync: this); 19 | _pageController = PageController(initialPage: 0); 20 | 21 | _tabController.addListener(() { 22 | // 判断 TabBar 是否切换位置了,如果切换了,则修改 PageView 的显示 23 | if (_tabController.indexIsChanging) { 24 | // PageView 的切换通过 controller 进行滚动 25 | // duration 表示切换滚动的时长,curve 表示滚动动画的样式, 26 | // flutter 已经在 Curves 中定义许多样式,可以自行切换查看效果 27 | _pageController.animateToPage(_tabController.index, 28 | duration: Duration(milliseconds: 300), curve: Curves.decelerate); 29 | } 30 | }); 31 | } 32 | 33 | @override 34 | void dispose() { 35 | _tabController.dispose(); 36 | super.dispose(); 37 | } 38 | 39 | @override 40 | Widget build(BuildContext context) { 41 | return Scaffold( 42 | appBar: AppBar( 43 | centerTitle: true, 44 | // automaticallyImplyLeading: false, 45 | // leading: Icon(Icons.menu, color: Colors.red, size: 30.0), 46 | // flexibleSpace: Image.asset('images/app_bar_hor.jpg', fit: BoxFit.cover), 47 | title: Text('AppBar Demo', style: TextStyle(color: Colors.red)), 48 | actions: [ 49 | PopupMenuButton( 50 | offset: Offset(50.0, 100.0), 51 | onSelected: (val) => print('Selected item is $val'), 52 | icon: Icon(Icons.more_vert, color: Colors.red), 53 | itemBuilder: (context) => 54 | List.generate(_abs.length, (index) => PopupMenuItem(value: _abs[index], child: Text(_abs[index])))) 55 | ], 56 | bottom: TabBar( 57 | labelColor: Colors.red, 58 | unselectedLabelColor: Colors.white, 59 | controller: _tabController, 60 | isScrollable: false, 61 | indicatorColor: Colors.yellow, 62 | indicatorSize: TabBarIndicatorSize.tab, 63 | indicatorWeight: 5.0, 64 | tabs: List.generate(_abs.length, (index) => Tab(text: _abs[index], icon: Icon(Icons.android)))), 65 | ), 66 | // 通过 body 来展示内容,body 可以传入任何 Widget,里面就是你需要展示的界面内容 67 | // 所以前面留下 Scaffold 中 body 部分的坑就解决了 68 | body: PageView( 69 | controller: _pageController, 70 | children: 71 | _abs.map((str) => TabChangePage(content: str)).toList(), // 通过 Map 转换后再通过 toList 转换成列表,效果同 List.generate 72 | onPageChanged: (position) { 73 | // PageView 切换的监听,这边切换 PageView 的页面后,TabBar 也需要随之改变 74 | // 通过 tabController 来改变 TabBar 的显示位置 75 | _tabController.index = position; 76 | }, 77 | ), 78 | drawer: Drawer( 79 | child: SafeArea( 80 | child: Container( 81 | child: Text('Drawer', style: TextStyle(color: Theme.of(context).primaryColor, fontSize: 30.0)), 82 | )), 83 | ), 84 | bottomNavigationBar: BottomAppBar( 85 | shape: CircularNotchedRectangle(), 86 | child: Row( 87 | mainAxisSize: MainAxisSize.max, 88 | mainAxisAlignment: MainAxisAlignment.spaceAround, 89 | children: [ 90 | IconButton(icon: Icon(Icons.android, size: 30.0, color: Theme.of(context).primaryColor), onPressed: () {}), 91 | IconButton(icon: Icon(Icons.people, size: 30.0, color: Theme.of(context).primaryColor), onPressed: () {}) 92 | ], 93 | ), 94 | ), 95 | floatingActionButton: 96 | FloatingActionButton(onPressed: () => print('Add'), child: Icon(Icons.add, color: Colors.white)), 97 | floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked, 98 | ); 99 | 100 | // return Scaffold(body: TabChangePage(content: 'Content')); 101 | } 102 | } 103 | 104 | class TabChangePage extends StatelessWidget { 105 | // 需要传入的参数 106 | final String content; 107 | 108 | // TabChangePage(this.content); 不推荐这样写构造方法 109 | 110 | // 推荐用这样的构造方法,key 可以作为唯一值查找 111 | TabChangePage({Key key, this.content}) : super(key: key); 112 | 113 | @override 114 | Widget build(BuildContext context) { 115 | return SafeArea( 116 | child: Container( 117 | alignment: Alignment.center, 118 | child: Text(content, style: TextStyle(color: Theme.of(context).primaryColor, fontSize: 30.0)))); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /lib/pages/button_main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ButtonDemoPage extends StatelessWidget { 4 | @override 5 | Widget build(BuildContext context) { 6 | return Scaffold( 7 | appBar: AppBar(), 8 | body: Container( 9 | padding: const EdgeInsets.only(top: 10.0), 10 | child: Center( 11 | child: Column( 12 | mainAxisAlignment: MainAxisAlignment.center, 13 | children: [ 14 | RaisedButton( 15 | onPressed: () { 16 | print('This is a Rased Button can be clicked'); 17 | }, 18 | child: Text('Raised Enable'), 19 | ), 20 | RaisedButton(onPressed: null, child: Text('Raised Disable')), 21 | FlatButton( 22 | onPressed: () => print('This is a Flat Button can be clicker'), 23 | child: Text('Flat Enable'), 24 | ), 25 | FlatButton(onPressed: null, child: Text('Flat Disable')), 26 | IconButton(icon: Icon(Icons.android), onPressed: () {}), 27 | IconButton(icon: Icon(Icons.android), onPressed: null), 28 | MaterialButton(onPressed: () {}, child: Text('Material Enable')), 29 | MaterialButton(onPressed: null, child: Text('Material Disable')), 30 | OutlineButton(onPressed: () {}, child: Text('Outline Enable')), 31 | OutlineButton(onPressed: null, child: Text('Outline Enable')), 32 | ], 33 | )), 34 | ), 35 | floatingActionButton: 36 | FloatingActionButton.extended(onPressed: () {}, icon: Icon(Icons.android), label: Text('Android')), 37 | floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked, 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/pages/checkbox_switch_main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class CheckSwitchDemoPage extends StatefulWidget { 4 | @override 5 | _CheckSwitchDemoPageState createState() => _CheckSwitchDemoPageState(); 6 | } 7 | 8 | class _CheckSwitchDemoPageState extends State { 9 | var _isChecked = false; 10 | var _isTitleChecked = false; 11 | var _isOn = false; 12 | var _isTitleOn = false; 13 | 14 | @override 15 | void initState() { 16 | super.initState(); 17 | } 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return Scaffold( 22 | appBar: AppBar( 23 | title: Text('Check Switch Demo'), 24 | ), 25 | body: Column(children: [ 26 | Row( 27 | mainAxisAlignment: MainAxisAlignment.spaceAround, 28 | children: [ 29 | Checkbox( 30 | // 是否开启三态 31 | tristate: true, 32 | // 控制当前 checkbox 的开启状态 33 | value: _isChecked, 34 | // 不设置该方法,处于不可用状态 35 | onChanged: (checked) { 36 | // 管理状态值 37 | setState(() => _isChecked = checked); 38 | }, 39 | // 选中时的颜色 40 | activeColor: Colors.pink, 41 | // 这个值有 padded 和 shrinkWrap 两个值, 42 | // padded 时候所占有的空间比 shrinkWrap 大,别的原谅我没看出啥 43 | materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, 44 | ), 45 | 46 | /// 点击无响应 47 | Checkbox(value: _isChecked, onChanged: null, tristate: true) 48 | ], 49 | ), 50 | Row( 51 | mainAxisAlignment: MainAxisAlignment.spaceAround, 52 | children: [ 53 | Switch( 54 | // 开启时候,那个条的颜色 55 | activeTrackColor: Colors.yellow, 56 | // 关闭时候,那个条的颜色 57 | inactiveTrackColor: Colors.yellow[200], 58 | // 设置指示器的图片,当然也有 color 可以设置 59 | activeThumbImage: AssetImage('images/ali.jpg'), 60 | inactiveThumbImage: AssetImage('images/ali.jpg'), 61 | // 开始时候的颜色,貌似会被 activeTrackColor 顶掉 62 | activeColor: Colors.pink, 63 | materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, 64 | value: _isOn, 65 | onChanged: (onState) { 66 | setState(() => _isOn = onState); 67 | }), 68 | 69 | /// 点击无响应 70 | Switch(value: _isOn, onChanged: null) 71 | ], 72 | ), 73 | CheckboxListTile( 74 | // 描述选项 75 | title: Text('Make this item checked'), 76 | // 二级描述 77 | subtitle: Text('description...description...\ndescription...description...'), 78 | // 和 checkbox 对立边的部件,例如 checkbox 在头部,则 secondary 在尾部 79 | secondary: Image.asset('images/ali.jpg', width: 30.0, height: 30.0), 80 | value: _isTitleChecked, 81 | // title 和 subtitle 是否为垂直密集列表中一员,最明显就是部件会变小 82 | dense: true, 83 | // 是否需要使用 3 行的高度,该值为 true 时候,subtitle 不可为空 84 | isThreeLine: true, 85 | // 控制 checkbox 选择框是在前面还是后面 86 | controlAffinity: ListTileControlAffinity.leading, 87 | // 是否将主题色应用到文字或者图标 88 | selected: true, 89 | onChanged: (checked) { 90 | setState(() => _isTitleChecked = checked); 91 | }, 92 | ), 93 | SwitchListTile( 94 | title: Text('Turn On this item'), 95 | subtitle: Text('description...description...\ndescription...description...'), 96 | secondary: Image.asset('images/ali.jpg', width: 30.0, height: 30.0), 97 | isThreeLine: true, 98 | value: _isTitleOn, 99 | selected: true, 100 | onChanged: (onState) { 101 | setState(() => _isTitleOn = onState); 102 | }) 103 | ]), 104 | ); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /lib/pages/column_main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ColumnDemoPage extends StatelessWidget { 4 | @override 5 | Widget build(BuildContext context) { 6 | return Scaffold( 7 | body: SafeArea( 8 | child: Container( 9 | alignment: Alignment.center, 10 | // 宽,高度同上层容器相同 11 | width: MediaQuery.of(context).size.width, 12 | height: MediaQuery.of(context).size.height, 13 | margin: const EdgeInsets.all(8.0), 14 | padding: const EdgeInsets.all(20.0), 15 | // color: Theme.of(context).primaryColor, // 该属性不可和 decoration 一起使用 16 | // Container 的样式 17 | decoration: BoxDecoration( 18 | borderRadius: BorderRadius.circular(20.0), 19 | color: Colors.red, 20 | // shape: BoxShape.circle, // 该属性不可同 borderRadius 一起使用 21 | backgroundBlendMode: BlendMode.colorDodge, // 背景图片和颜色混合模式 22 | image: DecorationImage(image: AssetImage('images/ali.jpg'), fit: BoxFit.cover)), 23 | 24 | // child: Column( 25 | // children: [ 26 | // Text('Container Text', style: TextStyle(color: Colors.white, fontSize: 30.0)), 27 | // Padding( 28 | // padding: const EdgeInsets.symmetric(vertical: 12.0), 29 | // child: Text('Container Text', style: TextStyle(color: Colors.white, fontSize: 30.0))) 30 | // ], 31 | // ), 32 | child: Wrap( 33 | spacing: 50.0, 34 | runAlignment: WrapAlignment.center, 35 | runSpacing: 50.0, 36 | crossAxisAlignment: WrapCrossAlignment.center, 37 | children: [ 38 | Text('ABC' * 10, style: TextStyle(color: Colors.white, fontSize: 16.0)), 39 | Text('ABC' * 4, style: TextStyle(color: Colors.white, fontSize: 16.0)), 40 | Text('ABC' * 5, style: TextStyle(color: Colors.white, fontSize: 16.0)), 41 | Text('ABC' * 5, style: TextStyle(color: Colors.white, fontSize: 16.0)), 42 | Text('ABC' * 20, style: TextStyle(color: Colors.white, fontSize: 16.0)) 43 | ], 44 | ), 45 | // child: Column( 46 | // mainAxisAlignment: MainAxisAlignment.spaceAround, 47 | // children: [ 48 | // Text('Container Text 1', style: TextStyle(color: Colors.white, fontSize: 30.0)), 49 | // Text('Container Text 2', style: TextStyle(color: Colors.white, fontSize: 30.0)), 50 | // Text('Container Text 3', style: TextStyle(color: Colors.white, fontSize: 30.0)), 51 | // Text('Container Text 4', style: TextStyle(color: Colors.white, fontSize: 30.0)), 52 | // Text('Container Text 5', style: TextStyle(color: Colors.white, fontSize: 30.0)), 53 | // ], 54 | // ), 55 | )), 56 | ); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /lib/pages/custom_scroll_main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class CustomScrollDemoPage extends StatelessWidget { 4 | final List letters = [ 5 | 'A', 6 | 'B', 7 | 'C', 8 | 'D', 9 | 'E', 10 | 'F', 11 | 'G', 12 | 'H', 13 | 'I', 14 | 'J', 15 | 'K', 16 | 'L', 17 | 'M', 18 | 'N', 19 | 'O', 20 | 'P', 21 | 'Q', 22 | 'R', 23 | 'S', 24 | 'T', 25 | 'U', 26 | 'V', 27 | 'W', 28 | 'X', 29 | 'Y', 30 | 'Z' 31 | ]; 32 | 33 | final List colors = [Colors.red, Colors.green, Colors.blue, Colors.pink, Colors.yellow, Colors.deepPurple]; 34 | 35 | @override 36 | Widget build(BuildContext context) { 37 | return Scaffold( 38 | appBar: AppBar( 39 | title: Text('CustomScrollDemo'), 40 | ), 41 | body: CustomScrollView( 42 | slivers: [ 43 | SliverGrid( 44 | delegate: SliverChildBuilderDelegate( 45 | (_, index) => InkWell( 46 | child: Image.asset( 47 | 'images/ali.jpg', /*color: colors[index], colorBlendMode: BlendMode.srcIn*/ 48 | ), 49 | onTap: () {}, 50 | ), 51 | childCount: 8), 52 | gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( 53 | crossAxisCount: 4, mainAxisSpacing: 10.0, crossAxisSpacing: 10.0)), 54 | SliverToBoxAdapter( 55 | child: Container( 56 | color: Colors.black12, 57 | margin: const EdgeInsets.symmetric(vertical: 10.0), 58 | child: Column(children: [ 59 | Divider(height: 2.0, color: Colors.black54), 60 | Stack( 61 | alignment: Alignment.center, 62 | children: [ 63 | Image.asset('images/app_bar_hor.jpg', fit: BoxFit.cover), 64 | Text('我是一些别的东西..例如广告', textScaleFactor: 1.5, style: TextStyle(color: Colors.red)) 65 | ], 66 | ), 67 | Divider(height: 2.0, color: Colors.black54), 68 | ], mainAxisAlignment: MainAxisAlignment.spaceBetween), 69 | alignment: Alignment.center)), 70 | SliverFixedExtentList( 71 | delegate: SliverChildBuilderDelegate( 72 | (_, index) => InkWell( 73 | child: Container( 74 | child: Text(letters[index] * 10, 75 | style: TextStyle(color: colors[index % colors.length], letterSpacing: 2.0), 76 | textScaleFactor: 1.5), 77 | alignment: Alignment.center, 78 | ), 79 | onTap: () {}, 80 | ), 81 | childCount: letters.length), 82 | itemExtent: 60.0) 83 | ], 84 | ), 85 | ); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /lib/pages/custom_view_main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | import 'dart:ui'; 3 | 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter/painting.dart'; 6 | 7 | class CustomViewDemoPage extends StatefulWidget { 8 | @override 9 | _CustomViewDemoPageState createState() => _CustomViewDemoPageState(); 10 | } 11 | 12 | class _CustomViewDemoPageState extends State with SingleTickerProviderStateMixin { 13 | AnimationController _animController; 14 | Animation _radiusAnimation; 15 | Animation _colorAnimation; 16 | 17 | @override 18 | void initState() { 19 | super.initState(); 20 | 21 | _animController = AnimationController(vsync: this, duration: Duration(milliseconds: 3000)); 22 | _radiusAnimation = Tween(begin: 0.0, end: 100.0).chain(CurveTween(curve: Curves.decelerate)).animate(_animController); 23 | _colorAnimation = ColorTween(begin: Colors.red[300], end: Colors.red[900]).chain(CurveTween(curve: Curves.ease)).animate(_animController); 24 | 25 | // _animController.addListener(() { 26 | // setState(() {}); 27 | // }); 28 | 29 | // _animController.addStatusListener((status) { 30 | // if (status == AnimationStatus.completed) { 31 | // _animController.reverse(); 32 | // } else if (status == AnimationStatus.dismissed) { 33 | // _animController.forward(); 34 | // } 35 | // }); 36 | // 37 | // _animController.forward(); 38 | } 39 | 40 | @override 41 | void dispose() { 42 | super.dispose(); 43 | } 44 | 45 | @override 46 | Widget build(BuildContext context) { 47 | return Scaffold( 48 | appBar: AppBar( 49 | title: Text('CustomView Demo'), 50 | ), 51 | body: Container( 52 | alignment: Alignment.center, 53 | width: MediaQuery.of(context).size.width, 54 | height: MediaQuery.of(context).size.height, 55 | child: CustomPaint( 56 | // painter: AnimatedCirclePainter(_radiusAnimation.value, _colorAnimation.value), 57 | // painter: LoveView(), 58 | // painter: LabelViewPainter(labelWidth: 100.0, useAngle: false, alignment: LabelAlignment.bottomRight), 59 | painter: LabelTextPainter('HOT'), 60 | size: MediaQuery.of(context).size, 61 | ), 62 | ), 63 | ); 64 | } 65 | } 66 | 67 | enum LabelAlignment { topLeft, topRight, bottomLeft, bottomRight } 68 | 69 | class LabelTextPainter extends CustomPainter { 70 | final double labelWidth; 71 | final String labelText; 72 | final TextStyle labelTextStyle; 73 | final LabelAlignment alignment; 74 | 75 | var _textPainter = TextPainter(); 76 | 77 | LabelTextPainter(this.labelText, {this.labelWidth = 100.0, this.labelTextStyle, this.alignment = LabelAlignment.topLeft}) 78 | : assert(labelWidth != null && labelWidth >= 50.0), 79 | assert(labelText != null), 80 | assert(alignment != null); 81 | 82 | @override 83 | void paint(Canvas canvas, Size size) { 84 | double rotateAngle; 85 | 86 | _textPainter 87 | ..text = TextSpan(style: labelTextStyle ?? TextStyle(color: Colors.black, fontSize: 14.0), text: labelText) 88 | ..textDirection = TextDirection.ltr; 89 | 90 | switch (alignment) { 91 | case LabelAlignment.topLeft: 92 | rotateAngle = -pi / 4; 93 | break; 94 | case LabelAlignment.topRight: 95 | rotateAngle = pi / 4; 96 | break; 97 | case LabelAlignment.bottomLeft: 98 | rotateAngle = pi / 4; 99 | break; 100 | case LabelAlignment.bottomRight: 101 | rotateAngle = -pi / 4; 102 | break; 103 | } 104 | 105 | canvas.save(); 106 | canvas.translate(labelWidth, labelWidth); 107 | canvas.rotate(rotateAngle); 108 | _textPainter.layout(maxWidth: size.width); 109 | _textPainter.paint(canvas, Offset(0, 0)); 110 | canvas.restore(); 111 | } 112 | 113 | @override 114 | bool shouldRepaint(LabelTextPainter oldDelegate) { 115 | return false; 116 | } 117 | } 118 | 119 | class LabelViewPainter extends CustomPainter { 120 | final double labelWidth; 121 | final LabelAlignment alignment; 122 | final bool useAngle; 123 | final Color labelColor; 124 | final double showPart; 125 | 126 | var _labelPaint = Paint()..isAntiAlias = true; 127 | var _labelOutPath = Path(); 128 | 129 | LabelViewPainter( 130 | {this.labelWidth = 100.0, this.alignment = LabelAlignment.topLeft, this.useAngle = true, this.labelColor = Colors.red, this.showPart = 0.6}) 131 | : assert(labelWidth != null && labelWidth >= 50.0), 132 | assert(alignment != null), 133 | assert(useAngle != null), 134 | assert(labelColor != null), 135 | assert(showPart <= 1.0 && showPart >= 0.6); 136 | 137 | @override 138 | void paint(Canvas canvas, Size size) { 139 | _labelPaint..color = labelColor; 140 | 141 | switch (alignment) { 142 | case LabelAlignment.topLeft: 143 | if (useAngle) { 144 | _labelOutPath.moveTo(.0, .0); 145 | _labelOutPath.lineTo(labelWidth, 0); 146 | _labelOutPath.lineTo(0, labelWidth); 147 | _labelOutPath.close(); 148 | } else { 149 | _labelOutPath.moveTo(labelWidth * (1 - showPart), .0); 150 | _labelOutPath.lineTo(labelWidth, .0); 151 | _labelOutPath.lineTo(.0, labelWidth); 152 | _labelOutPath.lineTo(0, labelWidth * (1 - showPart)); 153 | _labelOutPath.close(); 154 | } 155 | break; 156 | case LabelAlignment.topRight: 157 | if (useAngle) { 158 | _labelOutPath.moveTo(size.width, .0); 159 | _labelOutPath.lineTo(size.width - labelWidth, .0); 160 | _labelOutPath.lineTo(size.width, labelWidth); 161 | _labelOutPath.close(); 162 | } else { 163 | _labelOutPath.moveTo(size.width - labelWidth * (1 - showPart), .0); 164 | _labelOutPath.lineTo(size.width - labelWidth, .0); 165 | _labelOutPath.lineTo(size.width, labelWidth); 166 | _labelOutPath.lineTo(size.width, labelWidth * (1 - showPart)); 167 | _labelOutPath.close(); 168 | } 169 | break; 170 | case LabelAlignment.bottomLeft: 171 | if (useAngle) { 172 | _labelOutPath.moveTo(.0, size.height); 173 | _labelOutPath.lineTo(labelWidth, size.height); 174 | _labelOutPath.lineTo(0, size.height - labelWidth); 175 | _labelOutPath.close(); 176 | } else { 177 | _labelOutPath.moveTo(labelWidth * (1 - showPart), size.height); 178 | _labelOutPath.lineTo(labelWidth, size.height); 179 | _labelOutPath.lineTo(.0, size.height - labelWidth); 180 | _labelOutPath.lineTo(0, size.height - labelWidth * (1 - showPart)); 181 | _labelOutPath.close(); 182 | } 183 | break; 184 | case LabelAlignment.bottomRight: 185 | if (useAngle) { 186 | _labelOutPath.moveTo(size.width, size.height); 187 | _labelOutPath.lineTo(size.width - labelWidth, size.height); 188 | _labelOutPath.lineTo(size.width, size.height - labelWidth); 189 | _labelOutPath.close(); 190 | } else { 191 | _labelOutPath.moveTo(size.width - labelWidth * (1 - showPart), size.height); 192 | _labelOutPath.lineTo(size.width - labelWidth, size.height); 193 | _labelOutPath.lineTo(size.width, size.height - labelWidth); 194 | _labelOutPath.lineTo(size.width, size.height - labelWidth * (1 - showPart)); 195 | _labelOutPath.close(); 196 | } 197 | break; 198 | } 199 | 200 | canvas.drawPath(_labelOutPath, _labelPaint); 201 | } 202 | 203 | @override 204 | bool shouldRepaint(LabelViewPainter oldDelegate) { 205 | return false; 206 | } 207 | } 208 | 209 | class AnimatedCirclePainter extends CustomPainter { 210 | final double radius; 211 | final Color color; 212 | 213 | Paint _paint; 214 | 215 | AnimatedCirclePainter(this.radius, this.color) { 216 | _paint = Paint() 217 | ..color = color 218 | ..style = PaintingStyle.stroke 219 | ..isAntiAlias = true 220 | ..strokeWidth = 1.0; 221 | } 222 | 223 | @override 224 | void paint(Canvas canvas, Size size) { 225 | var width = size.width; 226 | var height = size.height; 227 | canvas.drawCircle(Offset(width / 2, height / 2), radius, _paint); 228 | } 229 | 230 | @override 231 | bool shouldRepaint(AnimatedCirclePainter oldDelegate) { 232 | return oldDelegate.radius != radius || oldDelegate.color != color; 233 | } 234 | } 235 | 236 | class LoveView extends CustomPainter { 237 | final _paint = Paint() 238 | ..color = Colors.red 239 | ..style = PaintingStyle.stroke 240 | ..isAntiAlias = true 241 | ..strokeWidth = 2.0 242 | ..strokeCap = StrokeCap.round; 243 | 244 | @override 245 | void paint(Canvas canvas, Size size) { 246 | // final path = Path()..addRect(Rect.fromCircle(center: Offset(size.width / 2, size.height / 2), radius: min(size.width, size.height) / 2 - 10)); 247 | final pLeft = Path() 248 | ..moveTo(size.width / 2, size.height / 4) 249 | ..cubicTo(size.width / 7, size.height / 9, size.width / 13, (size.height * 2) / 5, size.width / 2, (size.height * 7) / 12) 250 | ..cubicTo(size.width * 12 / 13, (size.height * 2) / 5, size.width * 6 / 7, size.height / 9, size.width / 2, size.height / 4); 251 | 252 | canvas.drawPath(pLeft, _paint); 253 | } 254 | 255 | @override 256 | bool shouldRepaint(LoveView oldDelegate) { 257 | return false; 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /lib/pages/data_persistence_main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:fluttertoast/fluttertoast.dart'; 5 | import 'package:path_provider/path_provider.dart'; 6 | import 'package:permission_handler/permission_handler.dart'; 7 | import 'package:shared_preferences/shared_preferences.dart'; 8 | 9 | class DataPersistenceDemoPage extends StatefulWidget { 10 | @override 11 | DataPersistenceDemoPageState createState() => DataPersistenceDemoPageState(); 12 | } 13 | 14 | class DataPersistenceDemoPageState extends State { 15 | // 用于单选按钮的选项 16 | final List _radioText = ['TemporaryDirectory', 'ApplicationDocumentsDirectory', 'ExternalStorageDirectory']; 17 | final List _radioDescriptions = [ 18 | 'Path to the temporary directory on the device.', 19 | 'Path to a directory where the application place files that are private to application', 20 | 'Path to a directory where the application access top level storage.' 21 | ]; 22 | 23 | String _fileContent = ''; 24 | String _shareContent = ''; 25 | String _currentValue; 26 | var _editController = TextEditingController(); 27 | var _shareKeyController = TextEditingController(); 28 | var _shareValueController = TextEditingController(); 29 | 30 | @override 31 | void initState() { 32 | super.initState(); 33 | _currentValue = _radioText[0]; 34 | } 35 | 36 | @override 37 | void dispose() { 38 | _editController.dispose(); 39 | _shareKeyController.dispose(); 40 | _shareValueController.dispose(); 41 | super.dispose(); 42 | } 43 | 44 | // 获取文件的存储路径,根据选择来获取 45 | Future _getFilePath() async { 46 | String dir = (await getTemporaryDirectory()).path; // 默认为临时目录 47 | 48 | switch (_currentValue) { 49 | case 'TemporaryDirectory': 50 | dir = (await getTemporaryDirectory()).path; 51 | break; 52 | case 'ApplicationDocumentsDirectory': 53 | dir = (await getApplicationDocumentsDirectory()).path; // 缓存目录 54 | break; 55 | case 'ExternalStorageDirectory': 56 | if (Platform.isAndroid) { 57 | dir = (await getExternalStorageDirectory()).path; // 外部存储目录,仅对 android 有效 58 | } else if (Platform.isIOS) { 59 | dir = (await getTemporaryDirectory()).path; // iOS 不存在外部存储,设为临时目录 60 | } 61 | break; 62 | } 63 | return '$dir/store.txt'; 64 | } 65 | 66 | // 如果写入外部内存需要读写权限 67 | void _writeTextIntoFile() async { 68 | if (_currentValue == _radioText[2]) { 69 | PermissionStatus status = await PermissionHandler().checkPermissionStatus(PermissionGroup.storage); 70 | if (status == PermissionStatus.granted) 71 | _writeContent(); 72 | else if (status == PermissionStatus.disabled) 73 | Fluttertoast.showToast(msg: '未打开相关权限'); 74 | else 75 | PermissionHandler().requestPermissions([PermissionGroup.storage]); 76 | } else 77 | _writeContent(); 78 | } 79 | 80 | void _writeContent() async { 81 | // 写入文本操作 82 | var text = _editController.value.text; // 获取文本框的内容 83 | File file = File(await _getFilePath()); // 获取相应的文件 84 | 85 | if (text == null || text.isEmpty) { 86 | Fluttertoast.showToast(msg: '请输入内容'); // 内容为空,则不写入并提醒 87 | } else { 88 | // 内容不空,则判断是否已经存在,存在先删除,重新创建后写入信息 89 | if (await file.exists()) file.deleteSync(); 90 | file.createSync(); 91 | file.writeAsStringSync(text); 92 | _editController.clear(); // 写入文件后清空输入框信息 93 | } 94 | } 95 | 96 | void _readTextFromFile() async { 97 | // 读取文本操作 98 | File file = File(await _getFilePath()); 99 | if (await file.exists()) { 100 | setState(() => _fileContent = file.readAsStringSync()); // 文件存在则直接显示文本信息 101 | } else { 102 | setState(() => _fileContent = ''); // 文件不存在则清空显示文本信息,并提示 103 | Fluttertoast.showToast(msg: '文件还未创建,请先通过写入信息来创建文件'); 104 | } 105 | } 106 | 107 | void _writeIntoShare() async { 108 | var shareKey = _shareKeyController.value.text; 109 | var shareContent = _shareValueController.value.text; 110 | 111 | if (shareKey == null || shareKey.isEmpty) { 112 | Fluttertoast.showToast(msg: '请输入 key'); 113 | } else if (shareContent == null || shareContent.isEmpty) { 114 | Fluttertoast.showToast(msg: '请输入保存的内容'); 115 | } else { 116 | var sp = await SharedPreferences.getInstance(); 117 | sp.setString(shareKey, shareContent); 118 | } 119 | } 120 | 121 | void _readFromShare() async { 122 | var shareKey = _shareKeyController.value.text; 123 | 124 | if (shareKey == null || shareKey.isEmpty) { 125 | Fluttertoast.showToast(msg: '请输入 key'); 126 | } else { 127 | var sp = await SharedPreferences.getInstance(); 128 | var value = sp.getString(shareKey); 129 | 130 | if (value == null) { 131 | Fluttertoast.showToast(msg: '未找到该 key'); 132 | setState(() => _shareContent = ''); 133 | } else { 134 | setState(() => _shareContent = value); 135 | } 136 | } 137 | } 138 | 139 | Widget _fileIoPart() { 140 | return Card( 141 | margin: const EdgeInsets.all(8.0), 142 | shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(8.0))), 143 | child: Column(children: [ 144 | Padding( 145 | padding: const EdgeInsets.all(12.0), 146 | child: Text('File IO', style: TextStyle(fontSize: 20.0, color: Theme.of(context).primaryColor)), 147 | ), 148 | // RadioList 是单选按钮部件,通过选择不同的情况,创建不同目录的文件 149 | RadioListTile( 150 | value: _radioText[0], 151 | title: Text(_radioText[0]), 152 | subtitle: Text(_radioDescriptions[0]), 153 | groupValue: _currentValue, 154 | onChanged: ((value) { 155 | setState(() => _currentValue = value); 156 | })), 157 | RadioListTile( 158 | value: _radioText[1], 159 | title: Text(_radioText[1]), 160 | subtitle: Text(_radioDescriptions[1]), 161 | groupValue: _currentValue, 162 | onChanged: ((value) { 163 | setState(() => _currentValue = value); 164 | })), 165 | RadioListTile( 166 | value: _radioText[2], 167 | title: Text(_radioText[2]), 168 | subtitle: Text(_radioDescriptions[2]), 169 | groupValue: _currentValue, 170 | onChanged: ((value) { 171 | setState(() => _currentValue = value); 172 | })), 173 | Padding( 174 | padding: const EdgeInsets.all(12.0), 175 | // 用于写入文本信息 176 | child: TextField( 177 | controller: _editController, 178 | decoration: InputDecoration(labelText: '输入存储的文本内容', icon: Icon(Icons.text_fields)), 179 | ), 180 | ), 181 | Container( 182 | margin: const EdgeInsets.symmetric(horizontal: 12.0), 183 | width: MediaQuery.of(context).size.width, 184 | child: RaisedButton( 185 | onPressed: _writeTextIntoFile, 186 | child: Text('写入文件信息'), 187 | ), 188 | ), 189 | Padding( 190 | padding: const EdgeInsets.all(12.0), 191 | child: Row( 192 | crossAxisAlignment: CrossAxisAlignment.start, 193 | mainAxisAlignment: MainAxisAlignment.center, 194 | children: [Text('文件内容:'), Expanded(child: Text(_fileContent, softWrap: true))], 195 | ), 196 | ), 197 | Container( 198 | margin: const EdgeInsets.symmetric(horizontal: 12.0), 199 | width: MediaQuery.of(context).size.width, 200 | child: RaisedButton( 201 | onPressed: _readTextFromFile, 202 | child: Text('读取文件信息'), 203 | ), 204 | ), 205 | ]), 206 | ); 207 | } 208 | 209 | Widget _sharedPart() { 210 | return Card( 211 | margin: const EdgeInsets.all(8.0), 212 | shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(8.0))), 213 | child: Column( 214 | children: [ 215 | Padding( 216 | padding: const EdgeInsets.all(12.0), 217 | child: 218 | Text('Shared Preferences', style: TextStyle(fontSize: 20.0, color: Theme.of(context).primaryColor)), 219 | ), 220 | Padding( 221 | padding: const EdgeInsets.fromLTRB(12.0, 0, 12.0, 12.0), 222 | // 用于设置 key 信息 223 | child: TextField( 224 | controller: _shareKeyController, 225 | decoration: InputDecoration(labelText: '输入 share 存储的 key', icon: Icon(Icons.lock_outline)), 226 | ), 227 | ), 228 | Padding( 229 | padding: const EdgeInsets.fromLTRB(12.0, 0, 12.0, 12.0), 230 | // 用于写入文本信息 231 | child: TextField( 232 | controller: _shareValueController, 233 | decoration: InputDecoration(labelText: '输入 share 存储的 value', icon: Icon(Icons.text_fields)), 234 | ), 235 | ), 236 | Container( 237 | margin: const EdgeInsets.symmetric(horizontal: 12.0), 238 | width: MediaQuery.of(context).size.width, 239 | child: RaisedButton( 240 | onPressed: _writeIntoShare, 241 | child: Text('写入 share'), 242 | ), 243 | ), 244 | Padding( 245 | padding: const EdgeInsets.all(12.0), 246 | child: Row( 247 | crossAxisAlignment: CrossAxisAlignment.start, 248 | mainAxisAlignment: MainAxisAlignment.center, 249 | children: [Text('share 存储内容:'), Expanded(child: Text(_shareContent, softWrap: true))], 250 | ), 251 | ), 252 | Container( 253 | margin: const EdgeInsets.symmetric(horizontal: 12.0), 254 | width: MediaQuery.of(context).size.width, 255 | child: RaisedButton( 256 | onPressed: _readFromShare, 257 | child: Text('读取 share'), 258 | ), 259 | ), 260 | ], 261 | )); 262 | } 263 | 264 | @override 265 | Widget build(BuildContext context) { 266 | return Scaffold( 267 | appBar: AppBar( 268 | title: Text('FileIO Demo'), 269 | ), 270 | body: SingleChildScrollView( 271 | child: Column( 272 | children: [_fileIoPart(), _sharedPart()], 273 | ), 274 | ), 275 | ); 276 | } 277 | } 278 | -------------------------------------------------------------------------------- /lib/pages/expansion_tile_main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ExpansionTilesDemoPage extends StatelessWidget { 4 | final _keys = ['ParentA', 'ParentB', 'ParentC', 'ParentD', 'ParentE', 'ParentF']; 5 | final Map> _data = { 6 | 'ParentA': ['Child A0', 'Child A1', 'Child A2', 'Child A3', 'Child A4', 'Child A5'], 7 | 'ParentB': ['Child B0', 'Child B1', 'Child B2', 'Child B3', 'Child B4', 'Child B5'], 8 | 'ParentC': ['Child C0', 'Child C1', 'Child C2', 'Child C3', 'Child C4', 'Child C5'], 9 | 'ParentD': ['Child D0', 'Child D1', 'Child D2', 'Child D3', 'Child D4', 'Child D5'], 10 | 'ParentE': ['Child E0', 'Child E1', 'Child E2', 'Child E3', 'Child E4', 'Child E5'], 11 | 'ParentF': ['Child F0', 'Child F1', 'Child F2', 'Child F3', 'Child F4', 'Child F5'] 12 | }; 13 | 14 | final _forthLevelData = [ 15 | [ 16 | ['A0', 'A1', 'A2', 'A3'], 17 | ['B0', 'B1', 'B2', 'B3'] 18 | ], 19 | [ 20 | ['Aa0', 'Aa1', 'Aa2', 'Aa3'], 21 | ['Bb0', 'Bb1', 'Bb2', 'Bb3'] 22 | ] 23 | ]; 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | return Scaffold( 28 | appBar: AppBar( 29 | title: Text('ExpansionTile Demo'), 30 | ), 31 | // body: ExpansionTile( 32 | // // 最前面的 widget 33 | // leading: Icon(Icons.phone_android), 34 | // // 替换默认箭头 35 | //// trailing: Icon(Icons.phone_iphone), 36 | // title: Text('Parent'), 37 | // // 默认是否展开 38 | // initiallyExpanded: true, 39 | // // 展开时候的背景色 40 | // backgroundColor: Colors.yellow[100], 41 | // // 展开或者收缩的回调,true 表示展开 42 | // onExpansionChanged: (expanded) => print('ExpansionTile is ${expanded ? 'expanded' : 'collapsed'}'), 43 | // children: List.generate( 44 | // 10, 45 | // (position) => 46 | // Container( 47 | // padding: const EdgeInsets.only(left: 80.0), 48 | // child: Text('Children ${position + 1}'), 49 | // height: 50.0, 50 | // alignment: Alignment.centerLeft, 51 | // )), 52 | // ), 53 | 54 | // body: ListView( 55 | // children: _keys 56 | // .map((key) => ExpansionTile( 57 | // title: Text(key), 58 | // children: _data[key] 59 | // .map((value) => InkWell( 60 | // child: Container( 61 | // child: Text(value), 62 | // padding: const EdgeInsets.only(left: 80.0), 63 | // height: 50.0, 64 | // alignment: Alignment.centerLeft, 65 | // ), 66 | // onTap: () {})) 67 | // .toList(), 68 | // )) 69 | // .toList()), 70 | 71 | body: SingleChildScrollView( 72 | child: ExpansionTile( 73 | title: Text('1stParent'), 74 | children: _forthLevelData 75 | .map((threeLevelData) => ExpansionTile( 76 | title: Text('2ndParent'), 77 | children: threeLevelData 78 | .map((towLevelData) => ExpansionTile( 79 | title: Text('3rdParent'), 80 | children: towLevelData 81 | .map((lastLevelData) => Container(child: Text(lastLevelData), height: 50.0)) 82 | .toList(), 83 | )) 84 | .toList())) 85 | .toList()), 86 | ), 87 | ); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /lib/pages/gesture_deep.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class GestureDeepened extends StatefulWidget { 4 | @override 5 | _GestureDeepenedState createState() => _GestureDeepenedState(); 6 | } 7 | 8 | class _GestureDeepenedState extends State with SingleTickerProviderStateMixin { 9 | AnimationController _controller; 10 | double _touchY, _touchX; 11 | double _scale = 1.0; 12 | Tween _scaleVale; 13 | 14 | @override 15 | void initState() { 16 | super.initState(); 17 | _controller = AnimationController(vsync: this, duration: Duration(milliseconds: 200)); 18 | _scaleVale = Tween(begin: 1.0, end: 1 / 2); 19 | _scaleVale.chain(CurveTween(curve: Curves.decelerate)); 20 | } 21 | 22 | @override 23 | void dispose() { 24 | super.dispose(); 25 | } 26 | 27 | @override 28 | Widget build(BuildContext context) { 29 | var width = MediaQuery.of(context).size.width; 30 | var height = MediaQuery.of(context).size.height; 31 | 32 | print('width: $width'); 33 | 34 | return Scaffold( 35 | appBar: AppBar( 36 | backgroundColor: Colors.transparent, 37 | ), 38 | body: Container( 39 | alignment: Alignment.center, 40 | width: MediaQuery.of(context).size.width, 41 | child: Stack( 42 | children: [ 43 | ScaleTransition( 44 | scale: _scaleVale.animate(_controller), 45 | child: Container( 46 | width: width * (_scale == 1.0 ? 1.0 : 1.0 - _scale), 47 | height: height * (_scale == 1.0 ? 1.0 : 1.0 - _scale) * 0.85, 48 | child: Image.asset( 49 | 'images/lm.jpg', 50 | fit: BoxFit.contain, 51 | ), 52 | ), 53 | ), 54 | ], 55 | ), 56 | ), 57 | bottomNavigationBar: BottomAppBar( 58 | child: GestureDetector( 59 | behavior: HitTestBehavior.opaque, 60 | child: Container( 61 | width: MediaQuery.of(context).size.width, 62 | height: 40.0, 63 | ), 64 | onPanDown: (detail) { 65 | if (_controller.isCompleted) { 66 | _controller.reverse(); 67 | } 68 | 69 | setState(() { 70 | _touchX = detail.globalPosition.dx; 71 | _touchY = detail.globalPosition.dy; 72 | _scale = 1.0; 73 | }); 74 | }, 75 | onPanUpdate: (detail) { 76 | var deltaX = detail.globalPosition.dx - _touchX; 77 | var deltaY = detail.globalPosition.dy - _touchY; 78 | var absY = deltaY < 0 ? -deltaY : deltaY; 79 | var absX = deltaX < 0 ? -deltaX : deltaX; 80 | 81 | if (deltaY > 0) { 82 | setState(() { 83 | _scale = 1.0; 84 | }); 85 | } else { 86 | var delta = absY.clamp(0, MediaQuery.of(context).size.height / 2); 87 | setState(() { 88 | _scale = delta / MediaQuery.of(context).size.height; 89 | }); 90 | } 91 | }, 92 | onPanCancel: () { 93 | setState(() { 94 | _scale = 1.0; 95 | }); 96 | }, 97 | onPanEnd: (details) { 98 | print('drag end: ${details.velocity}'); 99 | if (_scale < 0.15) { 100 | _scaleVale.end = 1 / (1 - _scale); 101 | } else { 102 | _scaleVale.begin = _scale.clamp(1 / 2, 1.0); 103 | } 104 | print('dismissed: ${_controller.isDismissed}, completed: ${_controller.isCompleted}'); 105 | if (_controller.isDismissed) { 106 | _controller.forward(); 107 | } 108 | }, 109 | ), 110 | ), 111 | ); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /lib/pages/gesture_main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class GestureDemoPage extends StatefulWidget { 4 | @override 5 | _GestureDemoPageState createState() => _GestureDemoPageState(); 6 | } 7 | 8 | class _GestureDemoPageState extends State { 9 | double left = 0.0; 10 | double top = 0.0; 11 | 12 | @override 13 | void initState() { 14 | super.initState(); 15 | } 16 | 17 | @override 18 | void dispose() { 19 | super.dispose(); 20 | } 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | return Scaffold( 25 | appBar: AppBar( 26 | title: Text('Gesture Demo'), 27 | ), 28 | body: Stack( 29 | alignment: Alignment.center, 30 | children: [ 31 | Positioned(child: Container(width: 50.0, height: 50.0, color: Colors.red), left: left, top: top), 32 | GestureDetector( 33 | behavior: HitTestBehavior.translucent, 34 | child: Container( 35 | color: Colors.transparent, 36 | width: MediaQuery.of(context).size.width - 10, 37 | height: MediaQuery.of(context).size.height), 38 | onPanDown: (details) { 39 | setState(() { 40 | left = details.globalPosition.dx; 41 | top = details.globalPosition.dy; 42 | }); 43 | }, 44 | onPanUpdate: (details) { 45 | setState(() { 46 | left = details.globalPosition.dx; 47 | top = details.globalPosition.dy; 48 | }); 49 | }, 50 | onPanCancel: () { 51 | setState(() { 52 | left = 0.0; 53 | top = 0.0; 54 | }); 55 | }, 56 | onPanEnd: (details) { 57 | setState(() { 58 | left = 0.0; 59 | top = 0.0; 60 | }); 61 | }, 62 | 63 | onScaleUpdate: (details){ 64 | print(details.scale); 65 | }, 66 | ) 67 | ], 68 | )); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /lib/pages/gridview_main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class GridViewDemoPage extends StatelessWidget { 4 | final List letters = [ 5 | 'A', 6 | 'B', 7 | 'C', 8 | 'D', 9 | 'E', 10 | 'F', 11 | 'G', 12 | 'H', 13 | 'I', 14 | 'J', 15 | 'K', 16 | 'L', 17 | 'M', 18 | 'N', 19 | 'O', 20 | 'P', 21 | 'Q', 22 | 'R', 23 | 'S', 24 | 'T', 25 | 'U', 26 | 'V', 27 | 'W', 28 | 'X', 29 | 'Y', 30 | 'Z' 31 | ]; 32 | 33 | // 用于区分网格单元 34 | final List colors = [Colors.red, Colors.green, Colors.blue, Colors.pink]; 35 | 36 | @override 37 | Widget build(BuildContext context) { 38 | return Scaffold( 39 | appBar: AppBar( 40 | title: Text('GridView Demo'), 41 | ), 42 | 43 | // body: GridView( 44 | // // gridDelegate 目前为两种 45 | // // `SliverGridDelegateWithFixedCrossAxisCount` 固定单排数量的 Delegate,相对使用比较多 46 | // // `SliverGridDelegateWithMaxCrossAxisExtent` 设置单个 item 最大宽度/长度,单排会按照最少的数量进行排布 47 | // gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( 48 | // crossAxisCount: 5, // 单行的个数 49 | // mainAxisSpacing: 10.0, // 同 scrollDirection 挂钩,item 之间在主轴方向的间隔 50 | // crossAxisSpacing: 10.0, // item 之间在副轴方法的间隔 51 | // childAspectRatio: 1.0 // item 的宽高比 52 | // ), 53 | // // 需要根据 index 设置不同背景色,所以使用 List.generate,如果不设置背景色,也可用 iterable.map().toList 54 | // children: List.generate( 55 | // letters.length, 56 | // (index) => Container( 57 | // alignment: Alignment.center, 58 | // child: Text(letters[index]), 59 | // color: colors[index % 4], 60 | // )), 61 | // ), 62 | 63 | // body: GridView( 64 | // // 通过设置 `maxCrossAxisExtent` 来指定最大的宽度,在这个值范围内,会选取相对较大的值 65 | // gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( 66 | // maxCrossAxisExtent: 60.0, crossAxisSpacing: 10.0, mainAxisSpacing: 10.0, childAspectRatio: 1.0), 67 | // children: List.generate( 68 | // letters.length, 69 | // (index) => Container( 70 | // alignment: Alignment.center, 71 | // child: Text(letters[index]), 72 | // color: colors[index % 4], 73 | // )), 74 | // ), 75 | 76 | // 通过 `IndexedWidgetBuilder` 来构建 item,别的参数同上 77 | body: GridView.builder( 78 | gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( 79 | crossAxisCount: 5, crossAxisSpacing: 10.0, mainAxisSpacing: 10.0, childAspectRatio: 1.0), 80 | itemCount: letters.length, 81 | itemBuilder: (_, index) => 82 | Container(color: colors[index % 4], child: Text(letters[index]), alignment: Alignment.center)), 83 | 84 | // body: GridView.custom( 85 | // gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( 86 | // crossAxisCount: 5, mainAxisSpacing: 10.0, crossAxisSpacing: 10.0, childAspectRatio: 1.0), 87 | // // item 通过 delegate 来生成,内部实现还是 `IndexedWidgetBuilder` 88 | // childrenDelegate: SliverChildBuilderDelegate( 89 | // (_, index) => Container( 90 | // alignment: Alignment.center, 91 | // color: colors[index % 4], 92 | // child: Text(letters[index]), 93 | // ), 94 | // childCount: letters.length)), 95 | 96 | // body: GridView.custom( 97 | // gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 5), 98 | // // 内部通过返回控件列表实现 99 | // childrenDelegate: SliverChildListDelegate( 100 | // List.generate( 101 | // letters.length, 102 | // (index) => Container( 103 | // child: Text(letters[index]), 104 | // alignment: Alignment.center, 105 | // color: colors[index % 4], 106 | // )), 107 | // )), 108 | 109 | // 这种情况就是简化了 `GridView` 使用 `SliverGridDelegateWithFixedCrossAxisCount` 代理的方法 110 | // body: GridView.count( 111 | // crossAxisCount: 5, 112 | // childAspectRatio: 2.0, 113 | // children: List.generate( 114 | // letters.length, 115 | // (index) => Container( 116 | // alignment: Alignment.center, 117 | // color: colors[index % 4], 118 | // child: Text(letters[index]), 119 | // ))), 120 | 121 | // 这种情况就是简化了 `GridView` 使用 `SliverGridDelegateWithMaxCrossAxisExtent` 代理的方法 122 | // body: GridView.extent( 123 | // crossAxisSpacing: 10.0, 124 | // mainAxisSpacing: 10.0, 125 | // childAspectRatio: 1.0, 126 | // maxCrossAxisExtent: 80.0, 127 | // children: List.generate( 128 | // letters.length, 129 | // (index) => Container( 130 | // alignment: Alignment.center, 131 | // color: colors[index % 4], 132 | // child: Text(letters[index]), 133 | // ))), 134 | ); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /lib/pages/http_main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | 4 | import 'package:dio/dio.dart'; 5 | import 'package:flutter/material.dart'; 6 | 7 | class HttpDemoPage extends StatefulWidget { 8 | @override 9 | _HttpDemoPageState createState() => _HttpDemoPageState(); 10 | } 11 | 12 | class _HttpDemoPageState extends State { 13 | static const _BIRD_SO_URL = 'http://www.caup.cn/'; 14 | static const _USER_ME_URL = 'https://randomuser.me/api/'; 15 | String _netBack = ''; 16 | 17 | @override 18 | void initState() { 19 | super.initState(); 20 | } 21 | 22 | _httpClientRequest() async { 23 | HttpClient client; 24 | try { 25 | // step 1 26 | client = HttpClient(); 27 | 28 | // step 2 29 | // Uri uri = Uri(scheme: 'https', host: 'www.xxx.com', queryParameters: {'a': 'AAA'}); 30 | HttpClientRequest request = await client.getUrl(Uri.parse(_BIRD_SO_URL)); 31 | // request.add(utf8.encode('{"a": "aaa"}')); 32 | request.headers.add('token', 'Bear ${'x' * 20}'); 33 | request.headers.add('user-agent', 34 | 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1'); 35 | 36 | // step 3 37 | HttpClientResponse response = await request.close(); 38 | print('http headers: ${response.headers.toString()}'); 39 | 40 | // step 4 41 | String strResponse = await response.transform(utf8.decoder).join(); 42 | // print('json: ${json.decode(strResponse)}'); 43 | setState(() => _netBack = strResponse); 44 | } catch (e) { 45 | print('${e.toString()}'); 46 | setState(() => _netBack = 'Fail'); 47 | } finally { 48 | // step 5 49 | client.close(); 50 | } 51 | } 52 | 53 | _dioRequest() async { 54 | BaseOptions options = BaseOptions(connectTimeout: 5000, receiveTimeout: 60000); 55 | Dio dio = Dio(options); 56 | 57 | // dio.interceptors.add(InterceptorsWrapper(onRequest: (opt) async => dio.resolve('{"fake": "fake data"}'))); 58 | 59 | // dio.interceptors.add(InterceptorsWrapper(onRequest: (opt) async => dio.reject('{"fake": "fake data"}'))); 60 | 61 | dio.interceptors.add(InterceptorsWrapper(onRequest: (opt) async { 62 | dio.interceptors.requestLock.lock(); 63 | await Future.delayed(Duration(milliseconds: 1000)).then((_) { 64 | Map params = opt.queryParameters; 65 | params.forEach((key, value) => opt.queryParameters[key] = '$value'.toLowerCase()); 66 | dio.interceptors.requestLock.unlock(); 67 | }); 68 | opt.headers['authorization'] = 'token'; 69 | return opt; 70 | }, onResponse: (resp) { 71 | resp.data = '${'${resp.data}'.split(', info').first}}'; 72 | return resp; 73 | }, onError: (error) { 74 | return error; 75 | })); 76 | 77 | Response response = await dio.get(_USER_ME_URL, queryParameters: {'a': 'AAA', 'b': 'BbBbBb'}); 78 | print(response.data); 79 | print(response.request.headers); 80 | print(response.request.queryParameters); 81 | setState(() => _netBack = response.data.toString()); 82 | } 83 | 84 | @override 85 | Widget build(BuildContext context) { 86 | return Scaffold( 87 | appBar: AppBar( 88 | title: Text('Http Demo'), 89 | ), 90 | body: SingleChildScrollView( 91 | child: Column( 92 | children: [ 93 | SizedBox( 94 | width: MediaQuery.of(context).size.width, 95 | child: RaisedButton(onPressed: _dioRequest, child: Text('Request')), 96 | ), 97 | Text(_netBack) 98 | ], 99 | ), 100 | ), 101 | ); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /lib/pages/image_main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ImageDemoPage extends StatelessWidget { 4 | final String _assetAli = 'images/ali.jpg'; 5 | final String _picUrl = 6 | 'https://timg05.bdimg.com/timg?wapbaike&quality=60&size=b1440_952&cut_x=143&cut_y=0&cut_w=1633&' 7 | 'cut_h=1080&sec=1349839550&di=cbbc175a45ccec5482ce2cff09a3ae34&' 8 | 'src=http://imgsrc.baidu.com/baike/pic/item/4afbfbedab64034f104872baa7c379310b551d80.jpg'; 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return Scaffold( 13 | appBar: AppBar(), 14 | body: Container( 15 | padding: const EdgeInsets.only(top: 10.0), 16 | child: Center( 17 | child: Column( 18 | mainAxisAlignment: MainAxisAlignment.center, 19 | children: [ 20 | // 这种展示图片方式和下一种会有相同的效果 21 | Image(image: AssetImage(_assetAli), width: 80.0, height: 80.0), 22 | // 接下来加载图片都会使用这些比较方便的方法 23 | Image.asset(_assetAli, width: 80.0, height: 80.0), 24 | // 加载一张网络图片 25 | Image.network(_picUrl, 26 | height: 80.0, 27 | // 横向重复 28 | repeat: ImageRepeat.repeatX, 29 | // MediaQuery.of(context).size 获取到的为上层容器的宽高 30 | width: MediaQuery.of(context).size.width), 31 | // 通过设置混合模式,可以看到图片展示的样式已经修改 32 | Image.asset(_assetAli, 33 | width: 80.0, height: 80.0, color: Colors.green, colorBlendMode: BlendMode.colorDodge), 34 | // 会优先加载指定的 asset 图片,然后等网络图片读取成功后加载网络图片,会通过渐隐渐现方式展现 35 | // cover 方式按照较小的边布满,较大的给切割 36 | // contain 会按照最大的边布满,较小的会被留白 37 | // fill 会把较大的一边压缩 38 | // fitHeight, fitWidth 分别按照长宽来布满 39 | FadeInImage.assetNetwork( 40 | placeholder: _assetAli, image: _picUrl, width: 120.0, height: 120.0, fit: BoxFit.cover), 41 | Icon(Icons.android, size: 40.0, color: Theme.of(context).primaryColorDark) 42 | ], 43 | )), 44 | )); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lib/pages/listview_main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ListViewDemoPage extends StatelessWidget { 4 | final List letters = [ 5 | 'A', 6 | 'B', 7 | 'C', 8 | 'D', 9 | 'E', 10 | 'F', 11 | 'G', 12 | 'H', 13 | 'I', 14 | 'J', 15 | 'K', 16 | 'L', 17 | 'M', 18 | 'N', 19 | 'O', 20 | 'P', 21 | 'Q', 22 | 'R', 23 | 'S', 24 | 'T', 25 | 'U', 26 | 'V', 27 | 'W', 28 | 'X', 29 | 'Y', 30 | 'Z' 31 | ]; 32 | 33 | @override 34 | Widget build(BuildContext context) { 35 | return Scaffold( 36 | appBar: AppBar( 37 | title: Text('ListView Demo'), 38 | ), 39 | 40 | // 最普通的生成方式,例如每个 item 都不一样,需要通过一个个添加的方式,推荐使用 41 | // body: ListView( 42 | // // 通过修改滑动方向设置水平或者垂直方向滚动 43 | // scrollDirection: Axis.vertical, 44 | // // 通过 iterable.map().toList 和 List.generate 方法效果是一样的 45 | // children: letters 46 | // .map((s) => Padding( 47 | // padding: const EdgeInsets.symmetric(vertical: 8.0), 48 | // child: Center( 49 | // child: Text(s), 50 | // ))) 51 | // .toList()), 52 | 53 | // body: ListView.custom( 54 | // // 指定 item 的高度,可以加快渲染的速度 55 | // itemExtent: 40.0, 56 | // // item 代理 57 | // childrenDelegate: SliverChildBuilderDelegate( 58 | // // IndexedWidgetBuilder,根据 index 设置 item 中需要变化的数据 59 | // (_, index) => Center(child: Text(letters[index], style: TextStyle(color: Colors.red))), 60 | // // 指定 item 的数量 61 | // childCount: letters.length, 62 | // )), 63 | 64 | // 推荐指数:5星 65 | // body: ListView.builder( 66 | // itemBuilder: (_, index) => Center(child: Text(letters[index], style: TextStyle(color: Colors.green))), 67 | // itemExtent: 40.0, 68 | // itemCount: letters.length), 69 | 70 | // 需要分割线的时候才使用,不能指定 item 的高度 71 | body: ListView.separated( 72 | itemBuilder: (_, index) => Padding( 73 | padding: const EdgeInsets.symmetric(vertical: 20.0), 74 | child: Center(child: Text(letters[index], style: TextStyle(color: Colors.blue))), 75 | ), 76 | separatorBuilder: (_, index) => Divider(height: 1.0, color: index % 2 == 0 ? Colors.black : Colors.red), 77 | itemCount: letters.length), 78 | ); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /lib/pages/login_home_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_arts_demos_app/third_icons.dart'; 3 | import 'package:fluttertoast/fluttertoast.dart'; 4 | 5 | class LoginDemoPage extends StatefulWidget { 6 | @override 7 | _LoginDemoPageState createState() => _LoginDemoPageState(); 8 | } 9 | 10 | class _LoginDemoPageState extends State with SingleTickerProviderStateMixin { 11 | TabController _tabController; 12 | List _pageIndicators = ['登录', '注册']; 13 | List _pages = []; 14 | int _position = 0; 15 | 16 | @override 17 | void initState() { 18 | super.initState(); 19 | _tabController = TabController(length: _pageIndicators.length, vsync: this); 20 | _pages..add(LoginPage())..add(RegisterPage()); 21 | 22 | _tabController.addListener(() { 23 | // 当 tab 切换的时候,联动 IndexStack 的 child 页面也进行修改,通过 setState 来修改值 24 | if (_tabController.indexIsChanging) setState(() => _position = _tabController.index); 25 | }); 26 | } 27 | 28 | @override 29 | void dispose() { 30 | super.dispose(); 31 | } 32 | 33 | @override 34 | Widget build(BuildContext context) { 35 | return Theme( 36 | data: ThemeData(primarySwatch: Colors.pink, iconTheme: IconThemeData(color: Colors.pink)), 37 | child: Scaffold( 38 | body: Container( 39 | padding: const EdgeInsets.all(20.0), 40 | alignment: Alignment.center, 41 | decoration: 42 | BoxDecoration(image: DecorationImage(image: AssetImage('images/login_bg.png'), fit: BoxFit.cover)), 43 | child: SingleChildScrollView( 44 | child: SafeArea( 45 | child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [ 46 | // 顶部页面切换指示器 47 | TabBar( 48 | indicatorSize: TabBarIndicatorSize.label, 49 | controller: _tabController, 50 | indicatorWeight: 4.0, 51 | indicatorColor: Colors.white, 52 | tabs: _pageIndicators 53 | .map((v) => Text(v, style: TextStyle(color: Colors.white, fontSize: 24.0))) 54 | .toList()), 55 | Padding( 56 | padding: const EdgeInsets.only(top: 30.0), 57 | child: SizedBox( 58 | // 切换界面列表 59 | child: IndexedStack(children: _pages, index: _position), 60 | height: MediaQuery.of(context).size.height / 2)) 61 | ])), 62 | ), 63 | ), 64 | )); 65 | } 66 | } 67 | 68 | /// 登录界面 69 | class LoginPage extends StatefulWidget { 70 | @override 71 | _LoginPageState createState() => _LoginPageState(); 72 | } 73 | 74 | class _LoginPageState extends State { 75 | GlobalKey _formKey = GlobalKey(); 76 | TextEditingController _usernameController = TextEditingController(); 77 | TextEditingController _passwordController = TextEditingController(); 78 | 79 | @override 80 | void initState() { 81 | super.initState(); 82 | } 83 | 84 | @override 85 | void dispose() { 86 | _usernameController.dispose(); 87 | _passwordController.dispose(); 88 | super.dispose(); 89 | } 90 | 91 | _login() { 92 | // 取消焦点 93 | FocusScope.of(context).requestFocus(FocusNode()); 94 | 95 | // 判断表单是否有效 96 | if (_formKey.currentState.validate()) { 97 | // 获取输入框内容 98 | var username = _usernameController.value.text; 99 | var password = _passwordController.value.text; 100 | 101 | // 判断登录条件 102 | if (username == 'kuky' && password == '123456') { 103 | Fluttertoast.showToast(msg: '登录成功'); 104 | Navigator.pop(context); 105 | } else 106 | Fluttertoast.showToast(msg: '登录失败'); 107 | } 108 | } 109 | 110 | @override 111 | Widget build(BuildContext context) { 112 | return Form( 113 | // 将 key 设置给表单,用于判断表单是否有效 114 | key: _formKey, 115 | child: Column( 116 | children: [ 117 | Padding( 118 | padding: const EdgeInsets.symmetric(vertical: 4.0), 119 | // 表单输入框,参数同 TextField 基本类似 120 | child: TextFormField( 121 | controller: _usernameController, 122 | style: TextStyle(color: Colors.white, fontSize: 16.0), 123 | decoration: InputDecoration( 124 | icon: Icon(ThirdIcons.username, size: 24.0, color: Colors.white), 125 | labelText: '请输入用户名', 126 | labelStyle: TextStyle(color: Colors.white), 127 | helperStyle: TextStyle(color: Colors.white)), 128 | // 有效条件(为空不通过,返回提示语,通过返回 null) 129 | validator: (value) => value.trim().isEmpty ? '用户名不能为空' : null, 130 | ), 131 | ), 132 | Padding( 133 | padding: const EdgeInsets.symmetric(vertical: 4.0), 134 | child: TextFormField( 135 | obscureText: true, 136 | controller: _passwordController, 137 | style: TextStyle(color: Colors.white, fontSize: 16.0), 138 | decoration: InputDecoration( 139 | icon: Icon(ThirdIcons.password, size: 24.0, color: Colors.white), 140 | labelText: '请输入密码', 141 | labelStyle: TextStyle(color: Colors.white), 142 | helperStyle: TextStyle(color: Colors.white)), 143 | validator: (value) => value.trim().length < 6 ? '密码长度不能小于6位' : null, 144 | ), 145 | ), 146 | Padding( 147 | padding: const EdgeInsets.only(top: 20.0), 148 | child: SizedBox( 149 | // 主要用于使 RaisedButton 和上层容器同宽 150 | width: MediaQuery.of(context).size.width, 151 | child: RaisedButton( 152 | color: Colors.pink, 153 | shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0))), 154 | onPressed: _login, 155 | child: Text( 156 | '登录', 157 | style: TextStyle(color: Colors.white, fontSize: 20.0), 158 | )), 159 | ), 160 | ) 161 | ], 162 | )); 163 | } 164 | } 165 | 166 | /// 注册界面 167 | class RegisterPage extends StatefulWidget { 168 | @override 169 | _RegisterPageState createState() => _RegisterPageState(); 170 | } 171 | 172 | class _RegisterPageState extends State { 173 | GlobalKey _formKey = GlobalKey(); 174 | TextEditingController _usernameController = TextEditingController(); 175 | TextEditingController _passwordController = TextEditingController(); 176 | TextEditingController _confirmController = TextEditingController(); 177 | 178 | @override 179 | void initState() { 180 | super.initState(); 181 | } 182 | 183 | _register() { 184 | FocusScope.of(context).requestFocus(FocusNode()); 185 | if (_formKey.currentState.validate()) { 186 | var username = _usernameController.value.text; 187 | 188 | if (username != 'kuky') 189 | Fluttertoast.showToast(msg: '注册成功'); 190 | else 191 | Fluttertoast.showToast(msg: '用户名已存在'); 192 | } 193 | } 194 | 195 | @override 196 | void dispose() { 197 | _usernameController.dispose(); 198 | _passwordController.dispose(); 199 | _confirmController.dispose(); 200 | super.dispose(); 201 | } 202 | 203 | @override 204 | Widget build(BuildContext context) { 205 | return Form( 206 | key: _formKey, 207 | child: Column( 208 | children: [ 209 | Padding( 210 | padding: const EdgeInsets.symmetric(vertical: 4.0), 211 | child: TextFormField( 212 | controller: _usernameController, 213 | style: TextStyle(color: Colors.white, fontSize: 16.0), 214 | decoration: InputDecoration( 215 | icon: Icon(ThirdIcons.username, size: 24.0, color: Colors.white), 216 | labelText: '请输入用户名', 217 | labelStyle: TextStyle(color: Colors.white), 218 | helperStyle: TextStyle(color: Colors.white)), 219 | validator: (value) => value.trim().isEmpty ? '用户名不能为空' : null, 220 | ), 221 | ), 222 | Padding( 223 | padding: const EdgeInsets.symmetric(vertical: 4.0), 224 | child: TextFormField( 225 | obscureText: true, 226 | controller: _passwordController, 227 | style: TextStyle(color: Colors.white, fontSize: 16.0), 228 | decoration: InputDecoration( 229 | icon: Icon(ThirdIcons.password, size: 24.0, color: Colors.white), 230 | labelText: '请输入密码', 231 | labelStyle: TextStyle(color: Colors.white), 232 | helperStyle: TextStyle(color: Colors.white)), 233 | validator: (value) => value.trim().length < 6 ? '密码长度不能小于6位' : null, 234 | ), 235 | ), 236 | Padding( 237 | padding: const EdgeInsets.symmetric(vertical: 4.0), 238 | child: TextFormField( 239 | obscureText: true, 240 | controller: _confirmController, 241 | style: TextStyle(color: Colors.white, fontSize: 16.0), 242 | decoration: InputDecoration( 243 | icon: Icon(ThirdIcons.password, size: 24.0, color: Colors.white), 244 | labelText: '请确认密码', 245 | labelStyle: TextStyle(color: Colors.white), 246 | helperStyle: TextStyle(color: Colors.white)), 247 | validator: (value) => value.trim() != _passwordController.value.text ? '两次输入密码不相同' : null, 248 | ), 249 | ), 250 | Padding( 251 | padding: const EdgeInsets.only(top: 20.0), 252 | child: SizedBox( 253 | width: MediaQuery.of(context).size.width, 254 | child: RaisedButton( 255 | color: Colors.pink, 256 | shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0))), 257 | onPressed: _register, 258 | child: Text( 259 | '注册', 260 | style: TextStyle(color: Colors.white, fontSize: 20.0), 261 | )), 262 | ), 263 | ) 264 | ], 265 | )); 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /lib/pages/nested_scroll_main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class NestedScrollDemoPage extends StatelessWidget { 4 | final _tabs = ['TabA', 'TabB']; 5 | final colors = [Colors.red, Colors.green, Colors.blue, Colors.pink, Colors.yellow, Colors.deepPurple]; 6 | 7 | @override 8 | Widget build(BuildContext context) { 9 | return Scaffold( 10 | body: DefaultTabController( 11 | length: _tabs.length, 12 | child: NestedScrollView( 13 | headerSliverBuilder: (context, innerScrolled) => [ 14 | SliverOverlapAbsorber( 15 | // 传入 handle 值,直接通过 `sliverOverlapAbsorberHandleFor` 获取即可 16 | handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), 17 | child: SliverAppBar( 18 | pinned: true, 19 | title: Text('NestedScroll Demo'), 20 | expandedHeight: 200.0, 21 | flexibleSpace: FlexibleSpaceBar(background: Image.asset('images/timg.jpg', fit: BoxFit.cover)), 22 | bottom: TabBar(tabs: _tabs.map((tab) => Text(tab, style: TextStyle(fontSize: 18.0))).toList()), 23 | forceElevated: innerScrolled, 24 | ), 25 | ) 26 | ], 27 | body: TabBarView( 28 | children: _tabs 29 | // 这边需要通过 Builder 来创建 TabBarView 的内容,否则会报错 30 | // NestedScrollView.sliverOverlapAbsorberHandleFor must be called with a context that contains a NestedScrollView. 31 | .map((tab) => Builder( 32 | builder: (context) => CustomScrollView( 33 | // key 保证唯一性 34 | key: PageStorageKey(tab), 35 | slivers: [ 36 | // 将子部件同 `SliverAppBar` 重叠部分顶出来,否则会被遮挡 37 | SliverOverlapInjector( 38 | handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context)), 39 | SliverGrid( 40 | delegate: SliverChildBuilderDelegate( 41 | (_, index) => Image.asset('images/ali.jpg'), 42 | childCount: 8), 43 | gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( 44 | crossAxisCount: 4, mainAxisSpacing: 10.0, crossAxisSpacing: 10.0)), 45 | SliverFixedExtentList( 46 | delegate: SliverChildBuilderDelegate( 47 | (_, index) => Container( 48 | child: Text('$tab - item${index + 1}', 49 | style: TextStyle(fontSize: 20.0, color: colors[index % 6])), 50 | alignment: Alignment.center), 51 | childCount: 15), 52 | itemExtent: 50.0) 53 | ], 54 | ), 55 | )) 56 | .toList()))), 57 | ); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /lib/pages/prompt_main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:fluttertoast/fluttertoast.dart'; 4 | 5 | class PromptDemoPage extends StatefulWidget { 6 | @override 7 | _PromptDemoPageState createState() => _PromptDemoPageState(); 8 | } 9 | 10 | class _PromptDemoPageState extends State { 11 | var _count = 0; 12 | var _genders = ['男', '女']; 13 | 14 | @override 15 | void initState() { 16 | super.initState(); 17 | } 18 | 19 | @override 20 | void dispose() { 21 | super.dispose(); 22 | } 23 | 24 | increase() { 25 | setState(() => _count++); 26 | } 27 | 28 | decrease() { 29 | setState(() => _count--); 30 | } 31 | 32 | _changeValue(BuildContext context) { 33 | increase(); 34 | Scaffold.of(context).showSnackBar(SnackBar( 35 | content: Text('当前值已修改'), 36 | action: SnackBarAction(label: '撤销', onPressed: decrease), 37 | duration: Duration(milliseconds: 2000))); 38 | } 39 | 40 | _showBottomSheet(BuildContext context) { 41 | showBottomSheet( 42 | context: context, 43 | builder: (context) => ListView( 44 | // 生成一个列表选择器 45 | children: List.generate( 46 | 20, 47 | (index) => InkWell( 48 | child: Container(alignment: Alignment.center, height: 60.0, child: Text('Item ${index + 1}')), 49 | onTap: () { 50 | print('tapped item ${index + 1}'); 51 | Navigator.pop(context); 52 | }), 53 | )), 54 | ); 55 | } 56 | 57 | _showModalBottomSheet(BuildContext context) { 58 | showModalBottomSheet( 59 | context: context, 60 | builder: (context) => Container( 61 | child: ListView( 62 | children: List.generate( 63 | 2, 64 | (index) => InkWell( 65 | child: Container(alignment: Alignment.center, height: 60.0, child: Text('Item ${index + 1}')), 66 | onTap: () { 67 | print('tapped item ${index + 1}'); 68 | Navigator.pop(context); 69 | }), 70 | )), 71 | height: 120, 72 | ), 73 | ); 74 | } 75 | 76 | _showAlertDialog() { 77 | showDialog( 78 | // 设置点击 dialog 外部不取消 dialog 79 | barrierDismissible: false, 80 | context: context, 81 | builder: (context) => AlertDialog( 82 | title: Text('我是个标题...嗯,标题..'), 83 | titleTextStyle: TextStyle(color: Colors.purple), 84 | // 标题文字样式 85 | content: Text(r'我是内容\(^o^)/~, 我是内容\(^o^)/~, 我是内容\(^o^)/~'), 86 | contentTextStyle: TextStyle(color: Colors.green), 87 | // 内容文字样式 88 | backgroundColor: CupertinoColors.white, 89 | elevation: 8.0, 90 | // 投影的阴影高度 91 | semanticLabel: 'Label', 92 | // 这个用于无障碍下弹出 dialog 的提示 93 | shape: Border.all(), 94 | // dialog 的操作按钮,actions 的个数尽量控制不要过多,否则会溢出 `Overflow` 95 | actions: [ 96 | FlatButton(onPressed: increase, child: Text('点我增加')), 97 | FlatButton(onPressed: decrease, child: Text('点我减少')), 98 | // 关闭 dialog 也需要通过 Navigator 进行操作 99 | FlatButton(onPressed: () => Navigator.pop(context), child: Text('你点我试试.')), 100 | ], 101 | )); 102 | } 103 | 104 | _showSimpleDialog() { 105 | showDialog( 106 | barrierDismissible: false, 107 | context: context, 108 | builder: (context) => SimpleDialog( 109 | title: Text('我是个比较正经的标题...\n选择你的性别'), 110 | children: _genders 111 | .map((gender) => InkWell( 112 | child: Container(height: 40.0, child: Text(gender), alignment: Alignment.center), 113 | onTap: () { 114 | Navigator.pop(context); 115 | Fluttertoast.showToast(msg: '你选择的性别是 $gender'); 116 | }, 117 | )) 118 | .toList(), 119 | )); 120 | } 121 | 122 | _showAboutDialog() { 123 | // showDialog( 124 | // barrierDismissible: false, 125 | // context: context, 126 | // builder: (context) => AboutDialog( 127 | // applicationName: 'Flutter 入门指北', 128 | // applicationVersion: '0.1.1', 129 | // applicationLegalese: 'Copyright: this is a copyright notice topically', 130 | // applicationIcon: Image.asset('images/app_icon.png', width: 40.0, height: 40.0), 131 | // children: [Text('我是个比较正经的对话框内容...你可以随便把我替换成任何部件,只要你喜欢(*^▽^*)')], 132 | // )); 133 | 134 | showAboutDialog( 135 | context: context, 136 | applicationName: 'Flutter 入门指北', 137 | applicationVersion: '0.1.1', 138 | applicationLegalese: 'Copyright: this is a copyright notice topically', 139 | applicationIcon: Image.asset('images/app_icon.png', width: 40.0, height: 40.0), 140 | children: [Text('我是个比较正经的对话框内容...你可以随便把我替换成任何部件,只要你喜欢(*^▽^*)')], 141 | ); 142 | } 143 | 144 | _showStateDialog() { 145 | showDialog( 146 | context: context, 147 | barrierDismissible: false, 148 | // 通过 StatefulBuilder 来保存 dialog 状态 149 | // builder 需要传入一个 BuildContext 和 StateSetter 类型参数 150 | // StateSetter 有一个 VoidCallback,修改状态的方法在这写 151 | builder: (context) => StatefulBuilder( 152 | builder: (context, dialogStateState) => SimpleDialog( 153 | title: Text('我这边能实时修改状态值'), 154 | contentPadding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0), 155 | children: [ 156 | Text('当前的值是: $_count', style: TextStyle(fontSize: 18.0)), 157 | Padding( 158 | padding: const EdgeInsets.symmetric(vertical: 12.0), 159 | child: Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ 160 | RaisedButton( 161 | // 通过 StatefulBuilder 的 StateSetter 来修改值 162 | onPressed: () => dialogStateState(() => increase()), 163 | child: Text('点我自增'), 164 | ), 165 | RaisedButton( 166 | onPressed: () => dialogStateState(() => decrease()), 167 | child: Text('点我自减'), 168 | ), 169 | RaisedButton( 170 | onPressed: () => Navigator.pop(context), 171 | child: Text('点我关闭'), 172 | ) 173 | ]), 174 | ) 175 | ], 176 | ))); 177 | } 178 | 179 | @override 180 | Widget build(BuildContext context) { 181 | return Scaffold( 182 | appBar: AppBar( 183 | title: Text('Prompt Demo'), 184 | ), 185 | body: Column(children: [ 186 | Text('当前值:$_count', style: TextStyle(fontSize: 20.0)), 187 | Expanded( 188 | child: ListView(padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0), children: [ 189 | // SnackBar 需要提供一个包含 context,但是 context 不能是 Scaffold 节点下的 context,所以需要通过 Builder 包裹一层 190 | Builder(builder: (context) => RaisedButton(onPressed: () => _changeValue(context), child: Text('修改当前值'))), 191 | // BottomSheet 的 context 也不能是 Scaffold 节点下的 context 192 | Builder( 193 | builder: (context) => 194 | RaisedButton(onPressed: () => _showBottomSheet(context), child: Text('BottomSheet'))), 195 | Builder( 196 | builder: (context) => 197 | RaisedButton(onPressed: () => _showModalBottomSheet(context), child: Text('ModaBottomSheet'))), 198 | RaisedButton(onPressed: _showAlertDialog, child: Text('AlertDialog')), 199 | RaisedButton(onPressed: _showSimpleDialog, child: Text('SimpleDialog')), 200 | RaisedButton(onPressed: _showAboutDialog, child: Text('AboutDialog')), 201 | RaisedButton(onPressed: _showStateDialog, child: Text('DialogState')) 202 | ])) 203 | ]), 204 | floatingActionButton: Builder( 205 | builder: (context) => FloatingActionButton(onPressed: () => _changeValue(context), child: Icon(Icons.send))), 206 | ); 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /lib/pages/router_main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /// router 测试界面需要单独运行 4 | class DemoApp extends StatelessWidget { 5 | @override 6 | Widget build(BuildContext context) { 7 | return MaterialApp( 8 | title: 'Flutter Learning Demo', 9 | // 在这里注册路由,关联 name 和界面 10 | // '/' 表示根页面,也就是 home 所对应的页面 11 | routes: {'/': (_) => APage(), /*'/page_b': (_) => BPage(),*/ '/page_c': (_) => CPage()}, 12 | debugShowCheckedModeBanner: false, 13 | ); 14 | } 15 | } 16 | 17 | class APage extends StatelessWidget { 18 | @override 19 | Widget build(BuildContext context) { 20 | return Scaffold( 21 | appBar: AppBar( 22 | title: Text('Page A'), 23 | ), 24 | body: Center( 25 | child: RaisedButton( 26 | onPressed: () { 27 | Navigator.push(context, MaterialPageRoute(builder: (_) => BPage(message: 'Message From Page A'))) 28 | .then((value) => print('BACK MESSAGE => $value')); 29 | // Navigator.pushNamed(context, '/page_b'); 30 | // Navigator.pushReplacement(context, MaterialPageRoute(builder: (_) => BPage())); 31 | // Navigator.pushReplacementNamed(context, '/page_b'); 32 | // Navigator.popAndPushNamed(context, '/page_b'); 33 | }, 34 | child: Text('To Page B'))), 35 | ); 36 | } 37 | } 38 | 39 | class BPage extends StatelessWidget { 40 | final String message; 41 | 42 | BPage({Key key, @required this.message}) : super(key: key); 43 | 44 | @override 45 | Widget build(BuildContext context) { 46 | print('passed value: $message'); 47 | return Scaffold( 48 | appBar: AppBar( 49 | title: Text('Page B'), 50 | ), 51 | body: Center( 52 | child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [ 53 | RaisedButton( 54 | onPressed: () { 55 | // Navigator.pushAndRemoveUntil( 56 | // context, MaterialPageRoute(builder: (_) => CPage()), (Route router) => false); 57 | // Navigator.pushNamedAndRemoveUntil(context, '/page_c', (Route router) => false); 58 | // Navigator.pushAndRemoveUntil( 59 | // context, MaterialPageRoute(builder: (_) => CPage()), ModalRoute.withName('/')); 60 | // Navigator.pushNamedAndRemoveUntil(context, '/page_c', ModalRoute.withName('/')); 61 | // Navigator.popAndPushNamed(context, '/page_c'); 62 | // Navigator.push(context, MaterialPageRoute(builder: (_) => CPage())) 63 | // .then((value) => print('Message Form C: $value')); 64 | Navigator.push( 65 | context, 66 | PageRouteBuilder( 67 | // 返回目标页面 68 | pageBuilder: (context, anim, _) => CPage(), 69 | // 切换动画的切换时长 70 | transitionDuration: Duration(milliseconds: 500), 71 | // 切换动画的切换效果,系统自带的常用 Transition 72 | // ScaleTransition: 缩放 SlideTransition: 滑动 73 | // RotationTransition: 旋转 FadeTransition: 透明度 74 | transitionsBuilder: (context, anim, _, child) => ScaleTransition( 75 | // Tween 是 flutter 的补间动画 76 | scale: Tween(begin: 0.0, end: 1.0).animate(anim), 77 | // 这个值必须记得要传,否则会不显示界面 78 | child: FadeTransition( 79 | opacity: Tween(begin: 0.0, end: 1.0).animate(anim), 80 | child: child, 81 | ), 82 | ))); 83 | }, 84 | child: Text('To Page C')), 85 | RaisedButton( 86 | onPressed: () { 87 | Navigator.pop(context, 'Message back to PageA From BPage'); 88 | }, 89 | child: Text('Back Page A')) 90 | ])), 91 | ); 92 | } 93 | } 94 | 95 | class CPage extends StatelessWidget { 96 | @override 97 | Widget build(BuildContext context) { 98 | return WillPopScope( 99 | child: Scaffold( 100 | appBar: AppBar( 101 | title: Text('Page C'), 102 | ), 103 | body: Center( 104 | child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [ 105 | RaisedButton( 106 | onPressed: () { 107 | Navigator.popUntil(context, ModalRoute.withName('/')); 108 | }, 109 | child: Text('Back Last Page')) 110 | ])), 111 | ), 112 | onWillPop: () async { 113 | Navigator.pop(context, 'Hello~'); 114 | return false; 115 | }); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /lib/pages/scroll_controller_main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ScrollControllerDemoPage extends StatefulWidget { 4 | @override 5 | _ScrollControllerDemoPageState createState() => _ScrollControllerDemoPageState(); 6 | } 7 | 8 | class _ScrollControllerDemoPageState extends State { 9 | var _scrollController = ScrollController(); 10 | var _showBackTop = false; 11 | 12 | @override 13 | void initState() { 14 | super.initState(); 15 | 16 | // 对 scrollController 进行监听 17 | _scrollController.addListener(() { 18 | // _scrollController.position.pixels 获取当前滚动部件滚动的距离 19 | // 当滚动距离大于 800 之后,显示回到顶部按钮 20 | setState(() => _showBackTop = _scrollController.position.pixels >= 800); 21 | }); 22 | } 23 | 24 | @override 25 | void dispose() { 26 | // 记得销毁对象 27 | _scrollController.dispose(); 28 | super.dispose(); 29 | } 30 | 31 | @override 32 | Widget build(BuildContext context) { 33 | return Scaffold( 34 | appBar: AppBar( 35 | title: Text('ScrollController Demo'), 36 | ), 37 | body: ListView( 38 | controller: _scrollController, 39 | children: List.generate( 40 | 20, (index) => Container(height: 100.0, alignment: Alignment.center, child: Text('Item ${index + 1}'))), 41 | ), 42 | floatingActionButton: _showBackTop // 当需要显示的时候展示按钮,不需要的时候隐藏,设置 null 43 | ? FloatingActionButton( 44 | onPressed: () { 45 | // scrollController 通过 animateTo 方法滚动到某个具体高度 46 | // duration 表示动画的时长,curve 表示动画的运行方式,flutter 在 Curves 提供了许多方式 47 | _scrollController.animateTo(0.0, duration: Duration(milliseconds: 500), curve: Curves.decelerate); 48 | }, 49 | child: Icon(Icons.vertical_align_top), 50 | ) 51 | : null, 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/pages/scrollable_main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_arts_demos_app/pages/custom_scroll_main.dart'; 3 | import 'package:flutter_arts_demos_app/pages/gridview_main.dart'; 4 | import 'package:flutter_arts_demos_app/pages/listview_main.dart'; 5 | import 'package:flutter_arts_demos_app/pages/nested_scroll_main.dart'; 6 | import 'package:flutter_arts_demos_app/pages/scroll_controller_main.dart'; 7 | import 'package:flutter_arts_demos_app/pages/single_child_scroll_main.dart'; 8 | 9 | import '../main.dart'; 10 | 11 | class ScrollableDemoPage extends StatelessWidget { 12 | @override 13 | Widget build(BuildContext context) { 14 | return Scaffold( 15 | appBar: AppBar( 16 | title: Text('Scrollable Demo'), 17 | ), 18 | body: Column(children: [ 19 | MenuActionItem( 20 | title: 'SingleChildScrollView', 21 | clickAction: () => Navigator.push(context, MaterialPageRoute(builder: (_) => SingleChildScrollDemoPage()))), 22 | MenuActionItem( 23 | title: 'ListView', 24 | clickAction: () => Navigator.push(context, MaterialPageRoute(builder: (_) => ListViewDemoPage()))), 25 | MenuActionItem( 26 | title: 'GridView', 27 | clickAction: () => Navigator.push(context, MaterialPageRoute(builder: (_) => GridViewDemoPage()))), 28 | MenuActionItem( 29 | title: 'CustomScrollView', 30 | clickAction: () => Navigator.push(context, MaterialPageRoute(builder: (_) => CustomScrollDemoPage()))), 31 | MenuActionItem( 32 | title: 'NestedScrollView', 33 | clickAction: () => Navigator.push(context, MaterialPageRoute(builder: (_) => NestedScrollDemoPage()))), 34 | MenuActionItem( 35 | title: 'ScrollController', 36 | clickAction: () => Navigator.push(context, MaterialPageRoute(builder: (_) => ScrollControllerDemoPage()))), 37 | ]), 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/pages/single_child_scroll_main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class SingleChildScrollDemoPage extends StatelessWidget { 4 | @override 5 | Widget build(BuildContext context) { 6 | final List letters = [ 7 | 'A', 8 | 'B', 9 | 'C', 10 | 'D', 11 | 'E', 12 | 'F', 13 | 'G', 14 | 'H', 15 | 'I', 16 | 'J', 17 | 'K', 18 | 'L', 19 | 'M', 20 | 'N', 21 | 'O', 22 | 'P', 23 | 'Q', 24 | 'R', 25 | 'S', 26 | 'T', 27 | 'U', 28 | 'V', 29 | 'W', 30 | 'X', 31 | 'Y', 32 | 'Z' 33 | ]; 34 | 35 | return Scaffold( 36 | appBar: AppBar( 37 | title: Text('Single Child Demo'), 38 | ), 39 | 40 | // body: SingleChildScrollView(child: Text('一段又臭又长的文字' * 1000, softWrap: true)), 41 | 42 | // 垂直滑动 demo 43 | // body: SingleChildScrollView( 44 | // child: Center( 45 | // child: Column( 46 | // children: List.generate( 47 | // letters.length, 48 | // (index) => Padding( 49 | // padding: const EdgeInsets.symmetric(vertical: 4.0), 50 | // child: Text(letters[index], style: TextStyle(fontSize: 18.0)), 51 | // )), 52 | // ), 53 | // )), 54 | 55 | // 横向滑动 56 | body: SingleChildScrollView( 57 | scrollDirection: Axis.horizontal, 58 | child: Center( 59 | child: Row( 60 | children: List.generate( 61 | letters.length, 62 | (index) => Container( 63 | child: Padding( 64 | padding: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 6.0), 65 | child: Text(letters[index], style: TextStyle(fontSize: 18.0)), 66 | ), 67 | width: 30.0)), 68 | ), 69 | )), 70 | ); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /lib/pages/sliver_main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class SliverDemoPage extends StatelessWidget { 4 | final List colors = [Colors.red, Colors.green, Colors.blue, Colors.pink]; 5 | 6 | @override 7 | Widget build(BuildContext context) { 8 | return Scaffold( 9 | body: CustomScrollView(slivers: [ 10 | SliverAppBar( 11 | title: Text('Sliver Demo'), 12 | centerTitle: true, 13 | // 展开的高度 14 | expandedHeight: 300.0, 15 | // 强制显示阴影 16 | forceElevated: true, 17 | // 设置该属性,当有下滑手势的时候,就会显示 AppBar 18 | floating: true, 19 | // 该属性只有在 floating 为 true 的情况下使用,不然会报错 20 | // 当上滑到一定的比例,会自动把 AppBar 收缩(不知道是不是 bug,当 AppBar 下面的部件没有被 AppBar 覆盖的时候,不会自动收缩) 21 | // 当下滑到一定比例,会自动把 AppBar 展开 22 | snap: true, 23 | // 设置该属性使 Appbar 折叠后不消失 24 | pinned: true, 25 | // 通过这个属性设置 AppBar 的背景 26 | flexibleSpace: FlexibleSpaceBar( 27 | // title: Text('Expanded Title'), 28 | // 背景折叠动画 29 | collapseMode: CollapseMode.parallax, 30 | background: Image.asset('images/timg.jpg', fit: BoxFit.cover), 31 | ), 32 | ), 33 | 34 | // SliverPersistentHeader( 35 | // // 属性同 SliverAppBar 36 | // pinned: true, 37 | // floating: true, 38 | // // 因为 SliverPersistentHeaderDelegate 是一个抽象类,所以需要自定义 39 | // delegate: CustomSliverPersistentHeaderDelegate( 40 | // max: 300.0, min: 100.0, child: Image.asset('images/timg.jpg', fit: BoxFit.cover)), 41 | // ), 42 | 43 | // SliverPersistentHeader(delegate: DemoHeader(), pinned: true), 44 | 45 | // 如果需要设置部件之间的间隔,需要使用 SliverPadding,因为需要传入一个 sliver 的 child, 46 | // 所以可以通过 SliverToBoxAdapter 来进行包裹后传入, 47 | // 可以理解为 SliverToBoxAdapter 专门将普通的部件装饰为 sliver 部件 48 | // SliverPadding( 49 | // padding: const EdgeInsets.symmetric(vertical: 6.0), 50 | // sliver: SliverToBoxAdapter( 51 | // child: Container( 52 | // color: Colors.black12, 53 | // child: Column(children: [ 54 | // Divider(height: 2.0, color: Colors.black54), 55 | // Stack( 56 | // alignment: Alignment.center, 57 | // children: [ 58 | // Image.asset('images/app_bar_hor.jpg', fit: BoxFit.cover), 59 | // Text('我是一些别的东西..例如广告', textScaleFactor: 1.5, style: TextStyle(color: Colors.red)) 60 | // ], 61 | // ), 62 | // Divider(height: 2.0, color: Colors.black54), 63 | // ], mainAxisAlignment: MainAxisAlignment.spaceBetween), 64 | // alignment: Alignment.center)), 65 | // ), 66 | // 67 | SliverFixedExtentList( 68 | delegate: SliverChildBuilderDelegate( 69 | (_, index) => Container( 70 | decoration: BoxDecoration(border: Border.all(color: Colors.black12)), 71 | child: Text('Item $index', style: TextStyle(color: Colors.pink, fontSize: 20.0)), 72 | alignment: Alignment.center), 73 | childCount: 15), 74 | itemExtent: 80.0), 75 | 76 | // 可以通过 viewportFraction 设置单个 item 占屏幕的比例,默认 1.0 即为一整个屏幕的宽度 77 | // SliverFillViewport( 78 | // viewportFraction: 1.5, 79 | // delegate: SliverChildBuilderDelegate( 80 | // (_, index) => 81 | // Container(child: Text('Item $index'), alignment: Alignment.center, color: colors[index % 4]), 82 | // childCount: 10)), 83 | 84 | // 这个部件一般用于最后填充用的,会占有一个屏幕的高度, 85 | // 可以在 child 属性加入需要展示的部件 86 | // SliverFillRemaining( 87 | // child: Center(child: Text('FillRemaining', style: TextStyle(fontSize: 30.0))), 88 | // ), 89 | ])); 90 | } 91 | } 92 | 93 | // 自定义 SliverPersistentHeaderDelegate 94 | class CustomSliverPersistentHeaderDelegate extends SliverPersistentHeaderDelegate { 95 | final double max; // 最大高度 96 | final double min; // 最小高度 97 | final Widget child; // 需要展示的内容 98 | 99 | CustomSliverPersistentHeaderDelegate({@required this.max, @required this.min, @required this.child}) 100 | // 如果 assert 内部条件不成立,会报错 101 | : assert(max != null), 102 | assert(min != null), 103 | assert(child != null), 104 | assert(min <= max), 105 | super(); 106 | 107 | // 返回展示的内容,如果内容固定可以直接在这定义,如果需要可扩展,这边通过传入值来定义 108 | @override 109 | Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) => child; 110 | 111 | @override 112 | double get maxExtent => max; // 返回最大高度 113 | 114 | @override 115 | double get minExtent => min; // 返回最小高度 116 | 117 | @override 118 | bool shouldRebuild(CustomSliverPersistentHeaderDelegate oldDelegate) { 119 | // 是否需要更新,这里我们定义当高度范围和展示内容被替换的时候进行刷新界面 120 | return max != oldDelegate.max || min != oldDelegate.min || child != oldDelegate.child; 121 | } 122 | } 123 | 124 | class DemoHeader extends SliverPersistentHeaderDelegate { 125 | @override 126 | Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) { 127 | return Container( 128 | color: Colors.pink, 129 | alignment: Alignment.center, 130 | child: Text('我是一个头部部件', style: TextStyle(color: Colors.white, fontSize: 30.0))); 131 | } // 头部展示内容 132 | 133 | @override 134 | double get maxExtent => 300.0; // 最大高度 135 | 136 | @override 137 | double get minExtent => 100.0; // 最小高度 138 | 139 | @override 140 | bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) => false; // 因为所有的内容都是固定的,所以不需要更新 141 | } 142 | -------------------------------------------------------------------------------- /lib/pages/stack_main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class StackDemoPage extends StatelessWidget { 5 | @override 6 | Widget build(BuildContext context) { 7 | return Scaffold( 8 | body: Center( 9 | child: Stack( 10 | alignment: Alignment.center, 11 | children: [ 12 | CircleAvatar(backgroundImage: AssetImage('images/ali.jpg'), radius: 100.0), 13 | Text( 14 | 'Kuky', 15 | style: TextStyle(color: Colors.white, fontSize: 34.0), 16 | ), 17 | Positioned(child: Text('另外一段文字', style: TextStyle(color: Colors.white, fontSize: 20.0)), bottom: 10.0), 18 | ], 19 | )), 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/pages/staggered_animation_main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class StaggeredAnimationsDemoPage extends StatefulWidget { 4 | @override 5 | _StaggeredAnimationsDemoPageState createState() => _StaggeredAnimationsDemoPageState(); 6 | } 7 | 8 | class _StaggeredAnimationsDemoPageState extends State with SingleTickerProviderStateMixin { 9 | AnimationController _controller; 10 | 11 | @override 12 | void initState() { 13 | super.initState(); 14 | _controller = AnimationController(vsync: this, duration: Duration(milliseconds: 5000)); 15 | } 16 | 17 | @override 18 | void dispose() { 19 | _controller.dispose(); 20 | super.dispose(); 21 | } 22 | 23 | Future _playAnim() async { 24 | try { 25 | await _controller.forward().orCancel; 26 | await _controller.reverse().orCancel; 27 | } on TickerCanceled { 28 | print('cancel'); 29 | } 30 | } 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | return Scaffold( 35 | appBar: AppBar( 36 | title: Text('StaggeredAnimation Demo'), 37 | ), 38 | body: GestureDetector( 39 | behavior: HitTestBehavior.opaque, 40 | onTap: _playAnim, 41 | child: Center( 42 | // 定义一个外层圈,能够使动画显眼点 43 | child: Container( 44 | width: 300, 45 | height: 300, 46 | decoration: BoxDecoration( 47 | color: Colors.black.withOpacity(0.1), border: Border.all(color: Colors.black.withOpacity(0.5))), 48 | child: StaggeredAnim(controller: _controller), 49 | ), 50 | ), 51 | ), 52 | ); 53 | } 54 | } 55 | 56 | class StaggeredAnim extends StatelessWidget { 57 | final AnimationController controller; 58 | final Animation opacity; 59 | final Animation width; 60 | final Animation height; 61 | final Animation padding; 62 | final Animation border; 63 | final Animation color; 64 | final Animation rotate; 65 | 66 | StaggeredAnim({Key key, this.controller}) 67 | : 68 | // widget 透明度 69 | opacity = Tween(begin: 0.0, end: 1.0) 70 | .animate(CurvedAnimation(parent: controller, curve: Interval(0.0, 0.1, curve: Curves.ease))), 71 | // widget 宽 72 | width = Tween(begin: 50.0, end: 150.0) 73 | .animate(CurvedAnimation(parent: controller, curve: Interval(0.1, 0.250, curve: Curves.ease))), 74 | // widget 高 75 | height = Tween(begin: 50.0, end: 150.0) 76 | .animate(CurvedAnimation(parent: controller, curve: Interval(0.25, 0.375, curve: Curves.ease))), 77 | // widget 底部距离 78 | padding = EdgeInsetsTween(begin: const EdgeInsets.only(top: 150.0), end: const EdgeInsets.only(top: .0)) 79 | .animate(CurvedAnimation(parent: controller, curve: Interval(0.25, 0.375, curve: Curves.ease))), 80 | // widget 旋转 81 | rotate = Tween(begin: 0.0, end: 0.25) 82 | .animate(CurvedAnimation(parent: controller, curve: Interval(0.375, 0.5, curve: Curves.ease))), 83 | // widget 外形 84 | border = BorderRadiusTween(begin: BorderRadius.circular(5.0), end: BorderRadius.circular(75.0)) 85 | .animate(CurvedAnimation(parent: controller, curve: Interval(0.5, 0.75, curve: Curves.ease))), 86 | // widget 颜色 87 | color = ColorTween(begin: Colors.blue, end: Colors.orange) 88 | .animate(CurvedAnimation(parent: controller, curve: Interval(0.75, 1.0, curve: Curves.ease))), 89 | super(key: key); 90 | 91 | Widget _buildAnimWidget(BuildContext context, Widget child) { 92 | return Container( 93 | padding: padding.value, 94 | alignment: Alignment.center, 95 | // 旋转变化 96 | child: RotationTransition( 97 | turns: rotate, // turns 表示当前动画的值 * 360° 角度 98 | child: Opacity( 99 | opacity: opacity.value, // 透明度变化 100 | child: Container( 101 | width: width.value, // 宽度变化 102 | height: height.value, // 高度变化 103 | decoration: BoxDecoration( 104 | color: color.value, // 颜色变化 105 | border: Border.all(color: Colors.indigo[300], width: 3.0), 106 | borderRadius: border.value), // 外形变化 107 | ), 108 | ), 109 | ), 110 | ); 111 | } 112 | 113 | @override 114 | Widget build(BuildContext context) { 115 | return AnimatedBuilder(animation: controller, builder: _buildAnimWidget); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /lib/pages/suspension_main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:flutter_arts_demos_app/widget/flutter_suspension.dart'; 4 | import 'package:english_words/english_words.dart'; 5 | 6 | class SuspensionPage extends StatefulWidget { 7 | @override 8 | _SuspensionPageState createState() => _SuspensionPageState(); 9 | } 10 | 11 | class _SuspensionPageState extends State { 12 | List list; 13 | List _keys; 14 | List _ranges; 15 | 16 | var _clickValue = 0; 17 | var _scrollController = ScrollController(); 18 | var _itemExtent = 50.0; 19 | var _headExtent = 30.0; 20 | var _dividerExtent = 1.0; 21 | var _rule = (String a, String b) => a.compareTo(b); 22 | List _suspensions; 23 | 24 | @override 25 | void initState() { 26 | super.initState(); 27 | list = nouns.take(200).toList(); 28 | _suspensions = list.map((str) => StrSuspensionView(str)).toList(); 29 | var result = SuspensionUtils.getSuspensionKeyRanges(_suspensions, _itemExtent, _headExtent, divideHeight: _dividerExtent, rule: _rule); 30 | _keys = result['keys']; 31 | _ranges = result['ranges']; 32 | } 33 | 34 | @override 35 | void dispose() { 36 | super.dispose(); 37 | _scrollController.dispose(); 38 | } 39 | 40 | @override 41 | Widget build(BuildContext context) { 42 | return Scaffold( 43 | appBar: AppBar( 44 | title: Text('Suspension Page'), 45 | ), 46 | body: Row( 47 | children: [ 48 | SizedBox( 49 | width: 50.0, 50 | child: ListView.separated( 51 | itemBuilder: (_, index) => InkWell( 52 | child: Container( 53 | height: 30.0, 54 | color: _clickValue == index ? Colors.grey[200] : Colors.white, 55 | alignment: Alignment.center, 56 | child: Text('${_keys[index]}'), 57 | ), 58 | onTap: () { 59 | _scrollController.animateTo(_ranges[index], duration: Duration(milliseconds: 300), curve: Curves.linear); 60 | // _scrollController.jumpTo(_ranges[index]); 61 | }, 62 | ), 63 | separatorBuilder: (_, index) => Divider( 64 | height: 1, 65 | color: Colors.red, 66 | ), 67 | itemCount: _keys.length), 68 | ), 69 | VerticalDivider(width: 1.0, color: Colors.grey), 70 | Expanded( 71 | child: SuspensionListView( 72 | itemExtent: _itemExtent, 73 | headExtent: _headExtent, 74 | physics: ScrollPhysics(), 75 | scrollController: _scrollController, 76 | items: _suspensions, 77 | headAlign: Alignment.centerLeft, 78 | headPadding: EdgeInsets.symmetric(horizontal: 12.0), 79 | headDecoration: BoxDecoration(color: Colors.grey[300]), 80 | itemDivider: Divider(height: _dividerExtent, color: Colors.red[400]), 81 | divideHeight: _dividerExtent, 82 | callback: (index) { 83 | setState(() { 84 | _clickValue = index; 85 | }); 86 | }, 87 | sortRule: _rule, 88 | )) 89 | ], 90 | ), 91 | ); 92 | } 93 | } 94 | 95 | class StrSuspensionView extends SuspensionView { 96 | StrSuspensionView(String data) : super(data); 97 | 98 | @override 99 | Widget get itemLayout => Container( 100 | alignment: Alignment.center, 101 | padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 12.0), 102 | child: Text(this.data), 103 | ); 104 | 105 | @override 106 | String get tagRule => (data as String).trimLeft().substring(0, 1).toUpperCase(); 107 | } 108 | -------------------------------------------------------------------------------- /lib/pages/text_field_main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class TextFieldDemoPage extends StatefulWidget { 4 | @override 5 | _TextFieldDemoPageState createState() => _TextFieldDemoPageState(); 6 | } 7 | 8 | class _TextFieldDemoPageState extends State { 9 | // 可以传入初始值 10 | TextEditingController _editController = TextEditingController(); 11 | FocusNode _editNode = FocusNode(); 12 | String _content = ''; 13 | String _spyContent = ''; 14 | 15 | @override 16 | void initState() { 17 | super.initState(); 18 | 19 | _editNode.addListener(() { 20 | print('edit has focus? => ${_editNode.hasFocus}'); 21 | }); 22 | } 23 | 24 | @override 25 | void dispose() { 26 | // 记得销毁 27 | _editController.dispose(); 28 | _editNode.dispose(); 29 | super.dispose(); 30 | } 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | return Scaffold( 35 | appBar: AppBar( 36 | title: Text('Input Content'), 37 | ), 38 | body: Container( 39 | padding: const EdgeInsets.symmetric(horizontal: 12.0), 40 | child: Column( 41 | children: [ 42 | TextField( 43 | controller: _editController, 44 | focusNode: _editNode, 45 | decoration: InputDecoration( 46 | icon: Icon(Icons.phone_iphone, color: Theme.of(context).primaryColor), 47 | labelText: '请输入手机号', 48 | helperText: '手机号', 49 | hintText: '手机号...在这儿输入呢'), 50 | keyboardType: TextInputType.number, 51 | // 输入类型为数字类型 52 | textInputAction: TextInputAction.done, 53 | style: TextStyle(color: Colors.redAccent, fontSize: 18.0), 54 | textDirection: TextDirection.ltr, 55 | maxLength: 11, 56 | // 最大长度为 11 57 | maxLengthEnforced: true, 58 | // 超过长度的不显示 59 | onChanged: (v) { 60 | // 输入的内容发生改变会调用 61 | setState(() => _spyContent = v); 62 | }, 63 | onSubmitted: (s) { 64 | // 点击确定按钮时候会调用 65 | setState(() => _spyContent = _editController.value.text); 66 | }, 67 | ), 68 | Padding( 69 | padding: const EdgeInsets.symmetric(vertical: 8.0), 70 | child: RaisedButton( 71 | onPressed: () { 72 | // 获取输入的内容 73 | setState(() => _content = _editController.value.text); 74 | // 清理输入内容 75 | _editController.clear(); 76 | setState(() => _spyContent = ''); 77 | }, 78 | child: Text('获取输入内容'))), 79 | // 展示输入的内容,点击按钮会显示 80 | Text(_content.isNotEmpty ? '获取到输入内容: $_content' : '还未获取到任何内容...'), 81 | Padding( 82 | padding: const EdgeInsets.symmetric(vertical: 8.0), 83 | // 监听输入内容的变化,会跟随输入的内容进行改变 84 | child: Text('我是文字内容监听:$_spyContent'), 85 | ) 86 | ], 87 | )), 88 | ); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /lib/pages/text_main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class TextDemoPage extends StatelessWidget { 4 | @override 5 | Widget build(BuildContext context) { 6 | return Scaffold( 7 | appBar: AppBar(title: Text('Text Demo Page')), 8 | body: Container( 9 | padding: const EdgeInsets.only(top: 10.0), 10 | child: Center( 11 | child: Column( 12 | mainAxisAlignment: MainAxisAlignment.center, 13 | children: [ 14 | Text('绿色背景黑色文字展示', 15 | style: TextStyle( 16 | color: Colors.black, 17 | fontSize: 24.0, 18 | letterSpacing: 2.0, 19 | background: Paint()..color = Colors.green)), 20 | Text('这是一个带红色下划线的文字展示', 21 | style: TextStyle( 22 | color: Colors.black, 23 | fontSize: 24.0, 24 | decoration: TextDecoration.underline, 25 | decorationStyle: TextDecorationStyle.solid, 26 | decorationColor: Colors.red)) 27 | ], 28 | )), 29 | )); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/third_icons.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ThirdIcons { 4 | static const IconData username = ThirdIconData(0xe672); 5 | static const IconData password = ThirdIconData(0xe62f); 6 | } 7 | 8 | class ThirdIconData extends IconData { 9 | // fontFamily 就是我们在 `pubspec.yaml` 中注册的 family 值 10 | const ThirdIconData(int codePoint) : super(codePoint, fontFamily: 'ThirdPartIcons'); 11 | } 12 | -------------------------------------------------------------------------------- /lib/widget/flutter_suspension.dart: -------------------------------------------------------------------------------- 1 | export 'package:flutter_arts_demos_app/widget/suspension/suspension_list.dart'; 2 | export 'package:flutter_arts_demos_app/widget/suspension/suspension_util.dart'; 3 | export 'package:flutter_arts_demos_app/widget/suspension/suspension_view.dart'; 4 | -------------------------------------------------------------------------------- /lib/widget/suspension/suspension_list.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_arts_demos_app/widget/suspension/suspension_view.dart'; 5 | 6 | typedef GroupChangeCallback(int index); 7 | 8 | typedef int SortRule(String a, String b); 9 | 10 | class SuspensionListView extends StatefulWidget { 11 | final double itemExtent; 12 | final double headExtent; 13 | final List items; 14 | final ScrollController scrollController; 15 | final ScrollPhysics physics; 16 | final TextStyle headStyle; 17 | final Decoration headDecoration; 18 | final EdgeInsetsGeometry headPadding; 19 | final Alignment headAlign; 20 | final Widget itemDivider; 21 | final double divideHeight; 22 | final GroupChangeCallback callback; 23 | final SortRule sortRule; 24 | 25 | SuspensionListView({ 26 | @required this.itemExtent, 27 | @required this.headExtent, 28 | @required this.items, 29 | @required this.scrollController, 30 | this.physics, 31 | this.headStyle, 32 | this.headDecoration, 33 | this.headPadding, 34 | this.headAlign, 35 | this.itemDivider, 36 | this.divideHeight = 0.0, 37 | this.callback, 38 | this.sortRule, 39 | }); 40 | 41 | @override 42 | _SuspensionListViewState createState() => _SuspensionListViewState(); 43 | } 44 | 45 | class _SuspensionListViewState extends State { 46 | var _children = []; 47 | var _keys = []; 48 | var _groups = >{}; 49 | var _needHeader = true; 50 | var _keyRange = []; 51 | var _selectedIndex = 0; 52 | 53 | @override 54 | void initState() { 55 | super.initState(); 56 | 57 | widget.items.forEach((suspensionView) { 58 | if (!_keys.contains(suspensionView.tagRule)) { 59 | _keys.add(suspensionView.tagRule); 60 | _groups[suspensionView.tagRule] = []; 61 | } 62 | 63 | var list = _groups[suspensionView.tagRule] ?? []; 64 | list.add(suspensionView); 65 | }); 66 | 67 | if (widget.sortRule != null) { 68 | _keys.sort(widget.sortRule); 69 | } 70 | 71 | _keys.forEach((key) { 72 | var header = Container( 73 | height: widget.headExtent, 74 | alignment: widget.headAlign ?? Alignment.centerLeft, 75 | padding: widget.headPadding ?? const EdgeInsets.all(8.0), 76 | decoration: widget.headDecoration ?? BoxDecoration(color: Colors.grey[300]), 77 | child: Text(key, style: widget.headStyle), 78 | ); 79 | 80 | _children.add(header); 81 | 82 | var groupList = _groups[key]; 83 | 84 | _keyRange.add(widget.headExtent + 85 | groupList.length * widget.itemExtent + 86 | (_keyRange.isEmpty ? 0.0 : _keyRange.last) + 87 | (groupList.length - 1) * widget.divideHeight); 88 | 89 | for (var index = 0; index < groupList.length; index++) { 90 | var content = SizedBox( 91 | height: widget.itemExtent, 92 | child: groupList[index].itemLayout, 93 | ); 94 | 95 | _children.add(content); 96 | 97 | if (widget.itemDivider != null && index != groupList.length - 1) { 98 | _children.add(widget.itemDivider); 99 | } 100 | } 101 | }); 102 | 103 | widget.scrollController.addListener(() { 104 | setState(() => _needHeader = widget.scrollController.offset >= 0); 105 | 106 | for (var index = 0; index < _keyRange.length; index++) { 107 | var min = index == 0 ? 0 : _keyRange[index - 1]; 108 | var max = _keyRange[index]; 109 | 110 | if (widget.scrollController.offset < max && widget.scrollController.offset >= min && _selectedIndex != index) { 111 | setState(() { 112 | _selectedIndex = index; 113 | }); 114 | 115 | if (widget.callback != null) { 116 | widget.callback(_selectedIndex); 117 | break; 118 | } 119 | } 120 | } 121 | }); 122 | } 123 | 124 | @override 125 | Widget build(BuildContext context) { 126 | return SafeArea( 127 | child: Stack( 128 | alignment: Alignment.centerRight, 129 | children: [ 130 | ListView( 131 | physics: widget.physics, 132 | controller: widget.scrollController, 133 | children: _children, 134 | ), 135 | Positioned( 136 | top: 0.0, 137 | left: 0.0, 138 | child: _needHeader 139 | ? Container( 140 | width: MediaQuery.of(context).size.width, 141 | height: widget.headExtent, 142 | alignment: widget.headAlign, 143 | padding: widget.headPadding, 144 | decoration: widget.headDecoration, 145 | child: Text(_keys[_selectedIndex], style: widget.headStyle), 146 | ) 147 | : Container( 148 | color: Colors.transparent, 149 | height: widget.headExtent, 150 | ), 151 | ), 152 | ], 153 | ), 154 | ); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /lib/widget/suspension/suspension_util.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_arts_demos_app/widget/suspension/suspension_list.dart'; 2 | import 'package:flutter_arts_demos_app/widget/suspension/suspension_view.dart'; 3 | 4 | class SuspensionUtils { 5 | static Map> getSuspensionKeyRanges( 6 | List items, 7 | double itemExtent, 8 | double headExtent, { 9 | double divideHeight = 0, 10 | SortRule rule, 11 | }) { 12 | var _keys = []; 13 | var _ranges = []; 14 | var _groups = >{}; 15 | var result = >{}; 16 | 17 | items.forEach((suspensionView) { 18 | if (!_keys.contains(suspensionView.tagRule)) { 19 | _keys.add(suspensionView.tagRule); 20 | _groups.putIfAbsent(suspensionView.tagRule, () => []); 21 | } 22 | 23 | var list = _groups[suspensionView.tagRule] ?? []; 24 | list.add(suspensionView); 25 | }); 26 | 27 | if (rule != null) { 28 | _keys.sort(rule); 29 | } 30 | 31 | _ranges.add(0); 32 | 33 | _keys.forEach((key) { 34 | var groupList = _groups[key]; 35 | _ranges.add(headExtent + groupList.length * itemExtent + (_ranges.isEmpty ? 0.0 : _ranges.last) + (groupList.length - 1) * divideHeight); 36 | }); 37 | 38 | result['keys'] = _keys; 39 | result['ranges'] = _ranges; 40 | 41 | return result; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/widget/suspension/suspension_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | abstract class SuspensionView { 4 | Object data; 5 | 6 | SuspensionView(this.data) : assert(data != null); 7 | 8 | String get tagRule; 9 | 10 | Widget get itemLayout; 11 | } 12 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_arts_demos_app 2 | description: An application record art demos 3 | 4 | # The following defines the version and build number for your application. 5 | # A version number is three numbers separated by dots, like 1.2.43 6 | # followed by an optional build number separated by a +. 7 | # Both the version and the builder number may be overridden in flutter 8 | # build by specifying --build-name and --build-number, respectively. 9 | # Read more about versioning at semver.org. 10 | version: 1.0.0+1 11 | 12 | environment: 13 | sdk: ">=2.1.0 <3.0.0" 14 | 15 | dependencies: 16 | flutter: 17 | sdk: flutter 18 | flutter_localizations: 19 | sdk: flutter 20 | 21 | # The following adds the Cupertino Icons font to your application. 22 | # Use with the CupertinoIcons class for iOS style icons. 23 | cupertino_icons: ^0.1.2 24 | intl: ^0.15.8 25 | fluttertoast: ^3.0.3 26 | rxdart: ^0.21.0 27 | provide: ^1.0.2 28 | path_provider: ^0.5.0+1 29 | permission_handler: ^2.2.0 30 | shared_preferences: ^0.5.1+2 31 | sqflite: ^1.1.0 32 | dio: ^2.1.0 33 | flutter_easyrefresh: ^1.2.7 34 | json_annotation: ^2.0.0 35 | english_words: ^3.1.5 36 | provider: ^3.0.0+1 37 | 38 | dev_dependencies: 39 | build_runner: ^1.0.0 40 | json_serializable: ^2.0.0 41 | 42 | flutter_test: 43 | sdk: flutter 44 | 45 | 46 | # For information on the generic Dart part of this file, see the 47 | # following page: https://www.dartlang.org/tools/pub/pubspec 48 | 49 | # The following section is specific to Flutter. 50 | flutter: 51 | 52 | # The following line ensures that the Material Icons font is 53 | # included with your application, so that you can use the icons in 54 | # the material Icons class. 55 | uses-material-design: true 56 | 57 | assets: 58 | - images/ 59 | 60 | 61 | fonts: 62 | - family: ThirdPartIcons 63 | fonts: 64 | - asset: fonts/third_part_icon.ttf 65 | # To add assets to your application, add an assets section, like this: 66 | # assets: 67 | # - images/a_dot_burr.jpeg 68 | # - images/a_dot_ham.jpeg 69 | 70 | # An image asset can refer to one or more resolution-specific "variants", see 71 | # https://flutter.io/assets-and-images/#resolution-aware. 72 | 73 | # For details regarding adding assets from package dependencies, see 74 | # https://flutter.io/assets-and-images/#from-packages 75 | 76 | # To add custom fonts to your application, add a fonts section here, 77 | # in this "flutter" section. Each entry in this list should have a 78 | # "family" key with the font family name, and a "fonts" key with a 79 | # list giving the asset and other descriptors for the font. For 80 | # example: 81 | # fonts: 82 | # - family: Schyler 83 | # fonts: 84 | # - asset: fonts/Schyler-Regular.ttf 85 | # - asset: fonts/Schyler-Italic.ttf 86 | # style: italic 87 | # - family: Trajan Pro 88 | # fonts: 89 | # - asset: fonts/TrajanPro.ttf 90 | # - asset: fonts/TrajanPro_Bold.ttf 91 | # weight: 700 92 | # 93 | # For details regarding fonts from package dependencies, 94 | # see https://flutter.io/custom-fonts/#from-packages 95 | --------------------------------------------------------------------------------