├── .github
├── FUNDING.yml
└── workflows
│ └── ci.yml
├── .gitignore
├── LICENSE
├── README.md
├── analysis_options.yaml
├── android
├── app
│ ├── build.gradle
│ └── src
│ │ ├── debug
│ │ └── AndroidManifest.xml
│ │ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── kotlin
│ │ │ └── com
│ │ │ │ └── gsy
│ │ │ │ └── shuyu
│ │ │ │ └── gsy_flutter_demo
│ │ │ │ └── MainActivity.kt
│ │ └── res
│ │ │ ├── drawable
│ │ │ └── launch_background.xml
│ │ │ ├── mipmap-hdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-mdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xhdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ └── ic_launcher.png
│ │ │ └── values
│ │ │ └── styles.xml
│ │ └── profile
│ │ └── AndroidManifest.xml
├── build.gradle
├── gradle.properties
├── gradle
│ └── wrapper
│ │ └── gradle-wrapper.properties
└── settings.gradle
├── assets
├── Agne.otf
├── Bobbers.ttf
├── Canterbury.ttf
├── HelloStockholm.otf
├── Horizon.otf
├── background.png
└── bglbt.ttf
├── demo.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.swift
│ ├── Assets.xcassets
│ ├── AppIcon.appiconset
│ │ ├── Contents.json
│ │ ├── Icon-App-1024x1024@1x.png
│ │ ├── Icon-App-20x20@1x.png
│ │ ├── Icon-App-20x20@2x.png
│ │ ├── Icon-App-20x20@3x.png
│ │ ├── Icon-App-29x29@1x.png
│ │ ├── Icon-App-29x29@2x.png
│ │ ├── Icon-App-29x29@3x.png
│ │ ├── Icon-App-40x40@1x.png
│ │ ├── Icon-App-40x40@2x.png
│ │ ├── Icon-App-40x40@3x.png
│ │ ├── Icon-App-60x60@2x.png
│ │ ├── Icon-App-60x60@3x.png
│ │ ├── Icon-App-76x76@1x.png
│ │ ├── Icon-App-76x76@2x.png
│ │ └── Icon-App-83.5x83.5@2x.png
│ └── LaunchImage.imageset
│ │ ├── Contents.json
│ │ ├── LaunchImage.png
│ │ ├── LaunchImage@2x.png
│ │ ├── LaunchImage@3x.png
│ │ └── README.md
│ ├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
│ ├── Info.plist
│ └── Runner-Bridging-Header.h
├── lib
├── main.dart
└── widget
│ ├── align_demo_page.dart
│ ├── anim_bg_demo_page.dart
│ ├── anim_bubble_gum.dart
│ ├── anim_button
│ ├── anim_button_demo_page.dart
│ ├── loading_anim_button.dart
│ └── play_anim_button.dart
│ ├── anim_juejin_logo_demo_page.dart
│ ├── anim_progress_img_demo_page.dart
│ ├── anim_scan_demo_page.dart
│ ├── anim_switch_layout_demo_page.dart
│ ├── anim_text_demo_page.dart
│ ├── anim_tip_demo_page.dart
│ ├── anima_demo_page.dart
│ ├── anima_demo_page2.dart
│ ├── anima_demo_page4.dart
│ ├── anima_demo_page5.dart
│ ├── animation_container_demo_page.dart
│ ├── arc_seek_bar_demo_page.dart
│ ├── async_to_sync_call_page.dart
│ ├── blur_demo_page.dart
│ ├── book_page
│ ├── book_page.dart
│ ├── book_painter.dart
│ ├── book_text_clip.dart
│ └── cal_point.dart
│ ├── bottom_anim_nav_page.dart
│ ├── bubble
│ ├── bubble_demo_page.dart
│ ├── bubble_painter.dart
│ └── bubble_tip_widget.dart
│ ├── canvas_click_demo_page.dart
│ ├── card_3d_demo_page.dart
│ ├── card_item_page.dart
│ ├── card_perspective_demo_page.dart
│ ├── card_real_3d_demo_page.dart
│ ├── chat_list_scroll_demo_page.dart
│ ├── chat_list_scroll_demo_page_2.dart
│ ├── clip_demo_page.dart
│ ├── cloud
│ ├── cloud_demo_page.dart
│ ├── cloud_render.dart
│ └── cloud_widget.dart
│ ├── color_progress_demo_page.dart
│ ├── controller_demo_page.dart
│ ├── custom_multi_render_demo_page.dart
│ ├── custom_pull
│ ├── gsy_refresh_sliver.dart
│ └── refrsh_demo_page3.dart
│ ├── custom_shader_path_demo_page.dart
│ ├── custom_sliver
│ ├── custom_sliver.dart
│ └── scroll_header_demo_page.dart
│ ├── custom_viewport
│ ├── custom_viewport.dart
│ ├── custom_viewport_page.dart
│ ├── first_tab_bar.dart
│ ├── first_tab_bar_render_sliver.dart
│ ├── first_tab_bar_sliver_widget.dart
│ ├── secondary_tab_bar.dart
│ ├── secondary_tab_bar_render_sliver.dart
│ └── secondary_tab_bar_sliver_widget.dart
│ ├── dash_3d_demo_page.dart
│ ├── demo_draggable_sheet_stick_page.dart
│ ├── demo_navigator_new.dart
│ ├── drag_img_demo_page.dart
│ ├── drop_select_menu
│ ├── drop_rect_tween.dart
│ ├── drop_select_controller.dart
│ ├── drop_select_demo_data.dart
│ ├── drop_select_demo_page.dart
│ ├── drop_select_expanded_menu.dart
│ ├── drop_select_grid_menu.dart
│ ├── drop_select_header.dart
│ ├── drop_select_list_menu.dart
│ ├── drop_select_menu.dart
│ ├── drop_select_object.dart
│ └── drop_select_widget.dart
│ ├── expand
│ └── expand_widget.dart
│ ├── floating_touch_demo_page.dart
│ ├── gesture_password
│ ├── gesture_password_demo_page.dart
│ ├── gesture_password_view.dart
│ └── src
│ │ ├── gesture_view_controller.dart
│ │ ├── gesture_view_model.dart
│ │ ├── gesture_view_path.dart
│ │ └── gesture_view_point.dart
│ ├── gradient_text_demo_page.dart
│ ├── honor_demo_page.dart
│ ├── index_stack_drag_card_demo_page.dart
│ ├── index_stack_drag_card_demo_page2.dart
│ ├── input_bottom_demo_page.dart
│ ├── juejin_3d_box_logo_demo_page.dart
│ ├── juejin_3d_logo_demo_page.dart
│ ├── keyboard_demo_page.dart
│ ├── link_sliver
│ ├── link_flexible_space_bar.dart
│ ├── link_sliver_demo_page.dart
│ └── link_sliver_header.dart
│ ├── list_anim
│ ├── header_appbar.dart
│ └── list_anim_demo_page.dart
│ ├── list_anim_2
│ ├── header_appbar.dart
│ └── list_anim_demo_page.dart
│ ├── list_link_bottomsheet_demo_page.dart
│ ├── matrix_custom_painter_page.dart
│ ├── overflow_image_page.dart
│ ├── pageview_in_pageview_demo_page.dart
│ ├── particle
│ ├── particle_model.dart
│ ├── particle_page.dart
│ ├── particle_painter.dart
│ └── particle_widget.dart
│ ├── photo_gallery_demo_page.dart
│ ├── png_shadow_demo_page.dart
│ ├── positioned_demo_page.dart
│ ├── refrsh_demo_page.dart
│ ├── refrsh_demo_page2.dart
│ ├── rich
│ └── real_rich_text.dart
│ ├── rich_text_demo_page.dart
│ ├── rich_text_demo_page2.dart
│ ├── route_demo_page.dart
│ ├── scroll_inner_content_demo_page.dart
│ ├── scroll_listener_demo_page.dart
│ ├── scroll_to_index_demo_page.dart
│ ├── scroll_to_index_demo_page2.dart
│ ├── shader_canvas_demo_page.dart
│ ├── silder_verify_page.dart
│ ├── sliver_list_demo_page.dart
│ ├── sliver_stick_demo_page.dart
│ ├── sliver_tab
│ ├── sliver_tab_child_page.dart
│ ├── sliver_tab_demo_page3.dart
│ └── sliver_tab_sliver.dart
│ ├── sliver_tab_demo_page.dart
│ ├── sliver_tab_demo_page2.dart
│ ├── star_bomb_button_page.dart
│ ├── statusbar_demo_page.dart
│ ├── stick
│ ├── stick_demo_page.dart
│ ├── stick_demo_page2.dart
│ ├── stick_render.dart
│ └── stick_widget.dart
│ ├── stick_sliver_list_demo_page.dart
│ ├── tag_demo_page.dart
│ ├── tear_text_demo_page.dart
│ ├── test_center_sliver
│ ├── test_center_sliver.dart
│ └── test_center_sliver_page.dart
│ ├── text_line_height_demo_page.dart
│ ├── text_size_demo_page.dart
│ ├── tick_click_demo_page.dart
│ ├── transform_canvas_demo_page.dart
│ ├── transform_demo_page.dart
│ ├── un_bounded_listview.dart
│ ├── verification_code_input_demo_page.dart
│ ├── verification_code_input_demo_page2.dart
│ ├── viewpager_demo_page.dart
│ ├── vp_list_demo_page.dart
│ └── wrap_content_page.dart
├── privacy.html
├── pubspec.lock
├── pubspec.yaml
├── static
├── card_down.png
├── card_down_2.png
├── card_up.png
├── card_up_2.png
├── gsy_cat.png
├── juejin.riv
├── test.jpeg
└── test_logo.png
├── test
└── widget_test.dart
├── thanks.jpg
├── web.jpg
└── web
└── index.html
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | custom: http://img.cdn.guoshuyu.cn/thanks.jpg
4 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | tags:
8 | - 'v*'
9 | pull_request:
10 | paths-ignore:
11 | - '**/*.md'
12 | - '**/*.txt'
13 | - '**/*.png'
14 | - '**/*.jpg'
15 |
16 | jobs:
17 | build:
18 | name: Build
19 | runs-on: ubuntu-latest
20 | steps:
21 | - uses: actions/checkout@v2
22 | - uses: actions/setup-java@v2
23 | with:
24 | distribution: 'zulu'
25 | java-version: 21
26 | - uses: subosito/flutter-action@v1
27 | with:
28 | flutter-version: '3.29.2'
29 | - run: flutter pub get
30 | - run: flutter build apk
31 |
32 | apk:
33 | name: Generate APK
34 | if: startsWith(github.ref, 'refs/tags/v')
35 | runs-on: ubuntu-latest
36 | steps:
37 | - name: Checkout
38 | uses: actions/checkout@v2
39 | - name: Setup JDK
40 | uses: actions/setup-java@v2
41 | with:
42 | distribution: 'zulu'
43 | java-version: 21
44 | - uses: subosito/flutter-action@v1
45 | with:
46 | flutter-version: '3.29.2'
47 | - run: flutter pub get
48 | - run: flutter build apk --target-platform android-arm64
49 | - name: Upload APK
50 | uses: actions/upload-artifact@v4
51 | with:
52 | name: apk
53 | path: build/app/outputs/apk/release/app-release.apk
54 | release:
55 | name: Release APK
56 | needs: apk
57 | if: startsWith(github.ref, 'refs/tags/v')
58 | runs-on: ubuntu-latest
59 | steps:
60 | - name: Download APK from build
61 | uses: actions/download-artifact@v4
62 | with:
63 | name: apk
64 | - name: Display structure of downloaded files
65 | run: ls -R
66 |
67 | - name: Create Release
68 | id: create_release
69 | uses: actions/create-release@v1.1.4
70 | env:
71 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
72 | with:
73 | tag_name: ${{ github.ref }}
74 | release_name: Release ${{ github.ref }}
75 | - name: Upload Release APK
76 | id: upload_release_asset
77 | uses: actions/upload-release-asset@v1.0.1
78 | env:
79 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
80 | with:
81 | upload_url: ${{ steps.create_release.outputs.upload_url }}
82 | asset_path: ./app-release.apk
83 | asset_name: app-release.apk
84 | asset_content_type: application/zip
85 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 |
12 | # IntelliJ related
13 | *.iml
14 | *.ipr
15 | *.iws
16 | .idea/
17 |
18 | # The .vscode folder contains launch configuration and tasks you configure in
19 | # VS Code which you may wish to be included in version control, so this line
20 | # is commented out by default.
21 | #.vscode/
22 |
23 | # Flutter/Dart/Pub related
24 | **/doc/api/
25 | .dart_tool/
26 | .flutter-plugins
27 | .packages
28 | .pub-cache/
29 | .pub/
30 | /build/
31 | .gradle
32 | .cxx/
33 |
34 | # Android related
35 | **/android/**/gradle-wrapper.jar
36 | **/android/.gradle
37 | **/android/captures/
38 | **/android/gradlew
39 | **/android/gradlew.bat
40 | **/android/local.properties
41 | **/android/**/GeneratedPluginRegistrant.java
42 |
43 | # iOS/XCode related
44 | **/ios/**/*.mode1v3
45 | **/ios/**/*.mode2v3
46 | **/ios/**/*.moved-aside
47 | **/ios/**/*.pbxuser
48 | **/ios/**/*.perspectivev3
49 | **/ios/**/*sync/
50 | **/ios/**/.sconsign.dblite
51 | **/ios/**/.tags*
52 | **/ios/**/.vagrant/
53 | **/ios/**/DerivedData/
54 | **/ios/**/Icon?
55 | **/ios/**/Pods/
56 | **/ios/**/.symlinks/
57 | **/ios/**/profile
58 | **/ios/**/xcuserdata
59 | **/ios/.generated/
60 | **/ios/Flutter/App.framework
61 | **/ios/Flutter/Flutter.framework
62 | **/ios/Flutter/Generated.xcconfig
63 | **/ios/Flutter/app.flx
64 | **/ios/Flutter/app.zip
65 | **/ios/Flutter/flutter_assets/
66 | **/ios/ServiceDefinitions.json
67 | **/ios/Runner/GeneratedPluginRegistrant.*
68 |
69 | # Exceptions to above rules.
70 | !**/ios/**/default.mode1v3
71 | !**/ios/**/default.mode2v3
72 | !**/ios/**/default.pbxuser
73 | !**/ios/**/default.perspectivev3
74 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
75 | ios/Flutter/flutter_export_environment.sh
76 |
77 | ios/Flutter/.last_build_id
78 | ios/Flutter/Flutter.podspec
79 |
80 | .flutter-plugins-dependencies
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Shuyu Guo
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | include: package:flutter_lints/flutter.yaml
2 |
3 | analyzer:
4 | errors:
5 | mixin_inherits_from_not_object: ignore
6 |
7 | linter:
8 | rules:
9 | library_private_types_in_public_api: false
10 | constant_identifier_names: false
11 | library_prefixes: false
--------------------------------------------------------------------------------
/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id "com.android.application"
3 | id "kotlin-android"
4 | id "dev.flutter.flutter-gradle-plugin"
5 | }
6 |
7 | def localProperties = new Properties()
8 | def localPropertiesFile = rootProject.file('local.properties')
9 | if (localPropertiesFile.exists()) {
10 | localPropertiesFile.withReader('UTF-8') { reader ->
11 | localProperties.load(reader)
12 | }
13 | }
14 |
15 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
16 | if (flutterVersionCode == null) {
17 | flutterVersionCode = '1'
18 | }
19 |
20 | def flutterVersionName = localProperties.getProperty('flutter.versionName')
21 | if (flutterVersionName == null) {
22 | flutterVersionName = '1.0'
23 | }
24 |
25 | android {
26 | compileSdkVersion 34
27 |
28 | namespace "com.gsy.shuyu.gsy_flutter_demo"
29 |
30 | sourceSets {
31 | main.java.srcDirs += 'src/main/kotlin'
32 | }
33 |
34 | lintOptions {
35 | disable 'InvalidPackage'
36 | checkReleaseBuilds false
37 | // Or, if you prefer, you can continue to check for errors in release builds,
38 | // but continue the build even when errors are found:
39 | abortOnError false
40 | }
41 |
42 | defaultConfig {
43 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
44 | applicationId "com.gsy.shuyu.gsy_flutter_demo"
45 | minSdkVersion flutter.minSdkVersion
46 | targetSdkVersion 33
47 | versionCode flutterVersionCode.toInteger()
48 | versionName flutterVersionName
49 | }
50 |
51 | buildTypes {
52 | release {
53 | // TODO: Add your own signing config for the release build.
54 | // Signing with the debug keys for now, so `flutter run --release` works.
55 | signingConfig signingConfigs.debug
56 | minifyEnabled false
57 | shrinkResources false
58 | }
59 | }
60 | // kotlinOptions {
61 | // jvmTarget = "1.8"
62 | // }
63 | }
64 |
65 | flutter {
66 | source '../..'
67 | }
68 |
69 | dependencies {}
70 |
--------------------------------------------------------------------------------
/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
9 |
10 |
11 |
12 |
13 |
14 |
17 |
25 |
29 |
32 |
33 |
34 |
35 |
36 |
37 |
39 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/android/app/src/main/kotlin/com/gsy/shuyu/gsy_flutter_demo/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.gsy.shuyu.gsy_flutter_demo
2 |
3 | import android.os.Bundle
4 | import io.flutter.embedding.android.FlutterActivity
5 | import io.flutter.embedding.engine.FlutterEngine
6 | import io.flutter.plugins.GeneratedPluginRegistrant
7 |
8 | class MainActivity: FlutterActivity() {
9 | }
10 |
--------------------------------------------------------------------------------
/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/CarGuo/gsy_flutter_demo/e315536718e4ccb98624373ed0b656f9745af299/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/e315536718e4ccb98624373ed0b656f9745af299/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/e315536718e4ccb98624373ed0b656f9745af299/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/e315536718e4ccb98624373ed0b656f9745af299/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/e315536718e4ccb98624373ed0b656f9745af299/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
--------------------------------------------------------------------------------
/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | allprojects {
2 | repositories {
3 | google()
4 | jcenter()
5 | }
6 | }
7 |
8 | rootProject.buildDir = '../build'
9 | subprojects {
10 | project.buildDir = "${rootProject.buildDir}/${project.name}"
11 | }
12 | subprojects {
13 | project.evaluationDependsOn(':app')
14 | }
15 |
16 | tasks.register("clean", Delete) {
17 | delete rootProject.buildDir
18 | }
19 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
3 | android.useAndroidX=true
4 | # Automatically convert third-party libraries to use AndroidX
5 | android.enableJetifier=true
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Jun 23 08:50:38 CEST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip
7 |
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | def flutterSdkPath = {
3 | def properties = new Properties()
4 | file("local.properties").withInputStream { properties.load(it) }
5 | def flutterSdkPath = properties.getProperty("flutter.sdk")
6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
7 | return flutterSdkPath
8 | }()
9 |
10 | includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
11 |
12 | repositories {
13 | google()
14 | mavenCentral()
15 | gradlePluginPortal()
16 | }
17 | }
18 |
19 | plugins {
20 | id "dev.flutter.flutter-plugin-loader" version "1.0.0"
21 | id "com.android.application" version "8.6.1" apply false
22 | id "org.jetbrains.kotlin.android" version "1.7.10" apply false
23 | }
24 |
25 | include ":app"
--------------------------------------------------------------------------------
/assets/Agne.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/e315536718e4ccb98624373ed0b656f9745af299/assets/Agne.otf
--------------------------------------------------------------------------------
/assets/Bobbers.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/e315536718e4ccb98624373ed0b656f9745af299/assets/Bobbers.ttf
--------------------------------------------------------------------------------
/assets/Canterbury.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/e315536718e4ccb98624373ed0b656f9745af299/assets/Canterbury.ttf
--------------------------------------------------------------------------------
/assets/HelloStockholm.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/e315536718e4ccb98624373ed0b656f9745af299/assets/HelloStockholm.otf
--------------------------------------------------------------------------------
/assets/Horizon.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/e315536718e4ccb98624373ed0b656f9745af299/assets/Horizon.otf
--------------------------------------------------------------------------------
/assets/background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/e315536718e4ccb98624373ed0b656f9745af299/assets/background.png
--------------------------------------------------------------------------------
/assets/bglbt.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/e315536718e4ccb98624373ed0b656f9745af299/assets/bglbt.ttf
--------------------------------------------------------------------------------
/demo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/e315536718e4ccb98624373ed0b656f9745af299/demo.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 | 12.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.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import Flutter
3 |
4 | @main
5 | @objc class AppDelegate: FlutterAppDelegate {
6 | override func application(
7 | _ application: UIApplication,
8 | didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?
9 | ) -> Bool {
10 | GeneratedPluginRegistrant.register(with: self)
11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions)
12 | }
13 | }
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/CarGuo/gsy_flutter_demo/e315536718e4ccb98624373ed0b656f9745af299/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/CarGuo/gsy_flutter_demo/e315536718e4ccb98624373ed0b656f9745af299/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/CarGuo/gsy_flutter_demo/e315536718e4ccb98624373ed0b656f9745af299/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/CarGuo/gsy_flutter_demo/e315536718e4ccb98624373ed0b656f9745af299/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/CarGuo/gsy_flutter_demo/e315536718e4ccb98624373ed0b656f9745af299/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/CarGuo/gsy_flutter_demo/e315536718e4ccb98624373ed0b656f9745af299/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/CarGuo/gsy_flutter_demo/e315536718e4ccb98624373ed0b656f9745af299/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/CarGuo/gsy_flutter_demo/e315536718e4ccb98624373ed0b656f9745af299/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/CarGuo/gsy_flutter_demo/e315536718e4ccb98624373ed0b656f9745af299/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/CarGuo/gsy_flutter_demo/e315536718e4ccb98624373ed0b656f9745af299/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/CarGuo/gsy_flutter_demo/e315536718e4ccb98624373ed0b656f9745af299/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/CarGuo/gsy_flutter_demo/e315536718e4ccb98624373ed0b656f9745af299/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/CarGuo/gsy_flutter_demo/e315536718e4ccb98624373ed0b656f9745af299/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/CarGuo/gsy_flutter_demo/e315536718e4ccb98624373ed0b656f9745af299/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/CarGuo/gsy_flutter_demo/e315536718e4ccb98624373ed0b656f9745af299/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/CarGuo/gsy_flutter_demo/e315536718e4ccb98624373ed0b656f9745af299/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/e315536718e4ccb98624373ed0b656f9745af299/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/e315536718e4ccb98624373ed0b656f9745af299/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 | gsy_flutter_demo
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 | CADisableMinimumFrameDurationOnPhone
45 |
46 | UIApplicationSupportsIndirectInputEvents
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
--------------------------------------------------------------------------------
/lib/widget/align_demo_page.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math' as math;
2 | import 'package:flutter/material.dart';
3 |
4 | class AlignDemoPage extends StatefulWidget {
5 | const AlignDemoPage({super.key});
6 |
7 | @override
8 | AlignDemoPageState createState() => AlignDemoPageState();
9 | }
10 |
11 | class AlignDemoPageState extends State
12 | with SingleTickerProviderStateMixin {
13 | getAlign(x) {
14 | return Align(
15 | alignment: Alignment(math.cos(x * math.pi), math.sin(x * math.pi)),
16 | child: Container(
17 | height: 20,
18 | width: 20,
19 | decoration: const BoxDecoration(
20 | color: Colors.green,
21 | borderRadius: BorderRadius.all(Radius.circular(10))),
22 | ),
23 | );
24 | }
25 |
26 | @override
27 | Widget build(BuildContext context) {
28 | int size = 20;
29 | return Scaffold(
30 | appBar: AppBar(
31 | title: const Text("AlignDemoPage"),
32 | ),
33 | body: Container(
34 | alignment: const Alignment(0, 0),
35 | child: SizedBox(
36 | height: MediaQuery.sizeOf(context).width,
37 | width: MediaQuery.sizeOf(context).width,
38 | child: Stack(
39 | children: List.generate(size, (index) {
40 | return getAlign(index.toDouble() / size / 2);
41 | }),
42 | ),
43 | ),
44 | ),
45 | );
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/lib/widget/anim_button/anim_button_demo_page.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:gsy_flutter_demo/widget/anim_button/play_anim_button.dart';
5 |
6 | import 'loading_anim_button.dart';
7 |
8 | class AnimButtonDemoPage extends StatefulWidget {
9 | const AnimButtonDemoPage({super.key});
10 |
11 | @override
12 | AnimButtonDemoPageState createState() => AnimButtonDemoPageState();
13 | }
14 |
15 | class AnimButtonDemoPageState extends State {
16 | LoadingState? loadingState = LoadingState.STATE_PRE;
17 |
18 | updateState() {
19 | setState(() {});
20 | }
21 |
22 | @override
23 | Widget build(BuildContext context) {
24 | Widget playButton;
25 | try {
26 | if (Platform.isAndroid == true || Platform.isIOS == true) {
27 | playButton = const SizedBox(
28 | height: 50,
29 | width: 50,
30 | child: PlayAnimButton(),
31 | );
32 | } else {
33 | playButton = const Text(
34 | "该控件效果暂不支持 Web,已隐藏",
35 | style: TextStyle(color: Colors.white, fontSize: 16),
36 | );
37 | }
38 | } catch (e) {
39 | playButton = const Text(
40 | "该效果暂不支持 Web",
41 | style: TextStyle(color: Colors.white, fontSize: 16),
42 | );
43 | }
44 | return Scaffold(
45 | appBar: AppBar(
46 | title: const Text("AnimButtonDemoPage"),
47 | ),
48 | body: Container(
49 | color: Colors.blueAccent,
50 | child: Center(
51 | child: Column(
52 | mainAxisAlignment: MainAxisAlignment.center,
53 | children: [
54 | const Text(
55 | "点击下方按键切换动画效果",
56 | style: TextStyle(color: Colors.white, fontSize: 16),
57 | ),
58 | const SizedBox(
59 | height: 50,
60 | ),
61 | playButton,
62 | const SizedBox(
63 | height: 50,
64 | ),
65 | SizedBox(
66 | height: 50,
67 | width: 50,
68 | child: InkWell(
69 | onTap: () {
70 | LoadingState nextState;
71 | switch (loadingState) {
72 | case LoadingState.STATE_PRE:
73 | nextState = LoadingState.STATE_COMPLETE;
74 | break;
75 | case LoadingState.STATE_END:
76 | nextState = LoadingState.STATE_DOWNLOADING;
77 | break;
78 | case LoadingState.STATE_DOWNLOADING:
79 | nextState = LoadingState.STATE_PRE;
80 | break;
81 | case LoadingState.STATE_COMPLETE:
82 | default:
83 | nextState = LoadingState.STATE_END;
84 | break;
85 | }
86 | setState(() {
87 | loadingState = nextState;
88 | });
89 | },
90 | child: LoadingAnimButton(
91 | loadingState: loadingState,
92 | loadingSpeed: 1,
93 | ),
94 | ),
95 | ),
96 | ],
97 | )),
98 | ),
99 | );
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/lib/widget/anim_juejin_logo_demo_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:rive/rive.dart';
3 |
4 | class AnimJueJinLogoDemoPage extends StatefulWidget {
5 | const AnimJueJinLogoDemoPage({super.key});
6 |
7 | @override
8 | State createState() => _AnimJueJinLogoDemoPageState();
9 | }
10 |
11 | class _AnimJueJinLogoDemoPageState extends State {
12 | @override
13 | Widget build(BuildContext context) {
14 | return Scaffold(
15 | appBar: AppBar(
16 | title: const Text("AnimJueJinLogoDemoPage"),
17 | ),
18 | body: const RiveAnimation.asset(
19 | 'static/juejin.riv',
20 | ),
21 | );
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/lib/widget/anim_tip_demo_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class AnimTipDemoPage extends StatefulWidget {
4 | const AnimTipDemoPage({super.key});
5 |
6 | @override
7 | _AnimTipDemoPageState createState() => _AnimTipDemoPageState();
8 | }
9 |
10 | class _AnimTipDemoPageState extends State {
11 | bool showTipItem = false;
12 |
13 | @override
14 | Widget build(BuildContext context) {
15 | return Scaffold(
16 | appBar: AppBar(
17 | title: const Text("AnimTipDemoPage"),
18 | ),
19 | body: Column(children: [
20 | AnimatedSwitcher(
21 | switchInCurve: const Cubic(0.4, 0.0, 0.2, 1.0),
22 | switchOutCurve: const Cubic(1.0, 0.1, 1.0, 0.1),
23 | transitionBuilder: (child, anim) {
24 | return SlideTransition(
25 | position: Tween(
26 | begin: const Offset(0.0, -1.0),
27 | end: const Offset(0.0, 0.0),
28 | ).animate(anim),
29 | child: child);
30 | },
31 | duration: const Duration(milliseconds: 500),
32 | child: showTipItem
33 | ? Container(
34 | alignment: Alignment.centerLeft,
35 | width: MediaQuery.sizeOf(context).width,
36 | height: 70,
37 | key: const ValueKey("TipItem"),
38 | color: Colors.amber,
39 | child: const Row(
40 | children: [
41 | Icon(Icons.ac_unit,
42 | color: Colors.white, size: 13),
43 | SizedBox(
44 | width: 10,
45 | ),
46 | Text(
47 | "StickText",
48 | style: TextStyle(color: Colors.white),
49 | ),
50 | ],
51 | ),
52 | )
53 | : Container(
54 | key: const ValueKey("hideItem"),
55 | ),
56 | ),
57 | Expanded(
58 | child: Center(
59 | child: TextButton(
60 | onPressed: () {
61 | setState(() {
62 | showTipItem = true;
63 | });
64 | Future.delayed(const Duration(seconds: 1), () {
65 | setState(() {
66 | showTipItem = false;
67 | });
68 | });
69 | },
70 | child: const Text("Click Me"),
71 | ),
72 | ),
73 | )
74 | ]),
75 | );
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/lib/widget/anima_demo_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class AnimaDemoPage extends StatefulWidget {
4 | const AnimaDemoPage({super.key});
5 |
6 | @override
7 | _AnimaDemoPageState createState() => _AnimaDemoPageState();
8 | }
9 |
10 | class _AnimaDemoPageState extends State
11 | with SingleTickerProviderStateMixin {
12 | late AnimationController controller1;
13 |
14 | Animation? animation1;
15 |
16 | late Animation animation2;
17 |
18 | @override
19 | void initState() {
20 | super.initState();
21 | controller1 =
22 | AnimationController(vsync: this, duration: const Duration(seconds: 3));
23 |
24 | animation1 = Tween(begin: 0.0, end: 200.0).animate(controller1)
25 | ..addListener(() {
26 | setState(() {});
27 | });
28 |
29 | animation2 = Tween(begin: 0.0, end: 1.0).animate(controller1);
30 |
31 | controller1.repeat();
32 | }
33 |
34 | @override
35 | void dispose() {
36 | controller1.dispose();
37 | super.dispose();
38 | }
39 |
40 | @override
41 | Widget build(BuildContext context) {
42 | return Scaffold(
43 | appBar: AppBar(
44 | title: const Text("AnimaDemoPage"),
45 | ),
46 |
47 | ///用封装好的 Transition 做动画
48 | body: RotationTransition(
49 | turns: animation2 as Animation,
50 | child: Center(
51 | child: Container(
52 | height: 200,
53 | width: 200,
54 | color: Colors.greenAccent,
55 | child: CustomPaint(
56 | ///直接使用值做动画
57 | foregroundPainter: _AnimationPainter(animation1),
58 | ),
59 | ),
60 | ),
61 | ),
62 | );
63 | }
64 | }
65 |
66 | class _AnimationPainter extends CustomPainter {
67 | final Paint _paint = Paint();
68 |
69 | Animation? animation;
70 |
71 | _AnimationPainter(this.animation);
72 |
73 | @override
74 | void paint(Canvas canvas, Size size) {
75 | _paint
76 | ..color = Colors.redAccent
77 | ..strokeWidth = 4
78 | ..style = PaintingStyle.stroke;
79 | canvas.drawCircle(const Offset(100, 100), animation!.value * 1.5, _paint);
80 | }
81 |
82 | @override
83 | bool shouldRepaint(CustomPainter oldDelegate) => true;
84 | }
85 |
--------------------------------------------------------------------------------
/lib/widget/anima_demo_page2.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math';
2 | import 'dart:ui';
3 |
4 | import 'package:flutter/material.dart';
5 |
6 | class AnimaDemoPage2 extends StatefulWidget {
7 | const AnimaDemoPage2({super.key});
8 |
9 | @override
10 | // ignore: library_private_types_in_public_api
11 | _AnimaDemoPageState2 createState() => _AnimaDemoPageState2();
12 | }
13 |
14 | class _AnimaDemoPageState2 extends State
15 | with SingleTickerProviderStateMixin {
16 | late AnimationController controller;
17 | Animation? animation;
18 |
19 | @override
20 | void initState() {
21 | super.initState();
22 |
23 | controller = AnimationController(
24 | vsync: this,
25 | duration: const Duration(milliseconds: 500),
26 | );
27 |
28 | animation = CurvedAnimation(
29 | parent: controller,
30 | curve: Curves.easeInSine,
31 | );
32 | }
33 |
34 | @override
35 | void dispose() {
36 | controller.dispose();
37 | super.dispose();
38 | }
39 |
40 | @override
41 | Widget build(BuildContext context) {
42 | return Scaffold(
43 | appBar: AppBar(
44 | title: const Text("AnimaDemoPage2"),
45 | ),
46 | body: Container(
47 | color: Colors.blueAccent,
48 | child: CRAnimation(
49 | minR: 0,
50 | maxR: 250,
51 | offset: Offset(MediaQuery.sizeOf(context).width / 2,
52 | MediaQuery.sizeOf(context).height / 2),
53 | animation: animation as Animation?,
54 | child: Center(
55 | child: Container(
56 | alignment: Alignment.center,
57 | height: 250,
58 | width: 250,
59 | color: Colors.greenAccent,
60 | child: const Text("我我我我我我我我我我我说"),
61 | ),
62 | ),
63 | ),
64 | ),
65 | floatingActionButton: FloatingActionButton(
66 | onPressed: () {
67 | if (controller.status == AnimationStatus.completed ||
68 | controller.status == AnimationStatus.forward) {
69 | controller.reverse();
70 | } else {
71 | controller.forward();
72 | }
73 | },
74 | child: const Text("点我"),
75 | ),
76 | );
77 | }
78 | }
79 |
80 | class CRAnimation extends StatelessWidget {
81 | final Offset? offset;
82 |
83 | final double? minR;
84 |
85 | final double? maxR;
86 |
87 | final Widget child;
88 |
89 | final Animation? animation;
90 |
91 | const CRAnimation({super.key,
92 | required this.child,
93 | required this.animation,
94 | this.offset,
95 | this.minR,
96 | this.maxR,
97 | });
98 |
99 | @override
100 | Widget build(BuildContext context) {
101 | return AnimatedBuilder(
102 | animation: animation!,
103 | builder: (_, __) {
104 | return ClipPath(
105 | clipper: AnimationClipper(
106 | value: animation!.value,
107 | minR: minR,
108 | maxR: maxR,
109 | offset: offset,
110 | ),
111 | child: child,
112 | );
113 | },
114 | );
115 | }
116 | }
117 |
118 | class AnimationClipper extends CustomClipper {
119 | final double? value;
120 |
121 | final double? minR;
122 |
123 | final double? maxR;
124 |
125 | final Offset? offset;
126 |
127 | AnimationClipper({
128 | this.value,
129 | this.offset,
130 | this.minR,
131 | this.maxR,
132 | });
133 |
134 | @override
135 | bool shouldReclip(oldClipper) => true;
136 |
137 | @override
138 | Path getClip(Size size) {
139 | var path = Path();
140 | var offset = this.offset ?? Offset(size.width / 2, size.height / 2);
141 |
142 | var maxRadius = minR ?? radiusSize(size, offset);
143 |
144 | var minRadius = maxR ?? 0;
145 |
146 | var radius = lerpDouble(minRadius, maxRadius, value!)!;
147 | var rect = Rect.fromCircle(
148 | radius: radius,
149 | center: offset,
150 | );
151 |
152 | path.addOval(rect);
153 | return path;
154 | }
155 |
156 | double radiusSize(Size size, Offset offset) {
157 | final height = max(offset.dy, size.height - offset.dy);
158 | final width = max(offset.dx, size.width - offset.dx);
159 | return sqrt(width * width + height * height);
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/lib/widget/anima_demo_page4.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class AnimaDemoPage4 extends StatefulWidget {
4 | const AnimaDemoPage4({super.key});
5 |
6 | @override
7 | _AnimaDemoPageState createState() => _AnimaDemoPageState();
8 | }
9 |
10 | class _AnimaDemoPageState extends State {
11 | IconData iconData = Icons.clear;
12 |
13 | @override
14 | Widget build(BuildContext context) {
15 | return Scaffold(
16 | appBar: AppBar(
17 | title: const Text('AnimaDemoPage4'),
18 | actions: [
19 | AnimatedSwitcher(
20 | transitionBuilder: (child, anim) {
21 | return ScaleTransition(scale: anim, child: child);
22 | },
23 | duration: const Duration(milliseconds: 300),
24 | child: IconButton(
25 | key: ValueKey(iconData),
26 | icon: Icon(iconData),
27 | onPressed: () {
28 | setState(() {
29 | if (iconData == Icons.clear) {
30 | iconData = Icons.add;
31 | } else {
32 | iconData = Icons.clear;
33 | }
34 | });
35 | }),
36 | )
37 | ],
38 | ),
39 | body: Container(),
40 | floatingActionButton: FloatingActionButton(
41 | onPressed: () {
42 | setState(() {
43 | if (iconData == Icons.clear) {
44 | iconData = Icons.add;
45 | } else {
46 | iconData = Icons.clear;
47 | }
48 | });
49 | },
50 | child: AnimatedSwitcher(
51 | transitionBuilder: (child, anim) {
52 | return ScaleTransition(scale: anim, child: child);
53 | },
54 | duration: const Duration(milliseconds: 300),
55 | child: IconButton(
56 | key: ValueKey(iconData),
57 | icon: Icon(iconData),
58 | onPressed: () {
59 | setState(() {
60 | if (iconData == Icons.clear) {
61 | iconData = Icons.add;
62 | } else {
63 | iconData = Icons.clear;
64 | }
65 | });
66 | }),
67 | ),
68 | ),
69 | );
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/lib/widget/anima_demo_page5.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | const String testString = "Hello GSY,欢迎你的交流";
4 |
5 | class AnimaDemoPage5 extends StatefulWidget {
6 | const AnimaDemoPage5({super.key});
7 |
8 | @override
9 | _AnimaDemoPageState createState() => _AnimaDemoPageState();
10 | }
11 |
12 | class _AnimaDemoPageState extends State
13 | with TickerProviderStateMixin {
14 | final List _charList = [];
15 | final List _controllerList =[];
16 | final List _moveAnimation = [];
17 |
18 | bool played = false;
19 | bool playing = false;
20 |
21 | @override
22 | void initState() {
23 | for (var value in testString.codeUnits) {
24 | _charList.add(String.fromCharCode(value));
25 | var controller = AnimationController(
26 | vsync: this, duration: const Duration(milliseconds: 600));
27 | _controllerList.add(controller);
28 | _moveAnimation.add(CurvedAnimation(
29 | parent: controller,
30 | curve: Curves.easeInOutExpo,
31 | ));
32 | }
33 | super.initState();
34 | }
35 |
36 | @override
37 | Widget build(BuildContext context) {
38 | return Scaffold(
39 | appBar: AppBar(title: const Text('AnimaDemoPage5')),
40 | body: Center(
41 | child: Wrap(
42 | children: List.generate(_charList.length, (i) {
43 | return AnimatedText(
44 | animation: _moveAnimation[i],
45 | child: Text(
46 | _charList[i],
47 | style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
48 | ));
49 | }),
50 | ),
51 | ),
52 | floatingActionButton: FloatingActionButton(
53 | onPressed: () {
54 | if (playing) {
55 | return;
56 | }
57 | if (played) {
58 | back();
59 | } else {
60 | play();
61 | }
62 | },
63 | child: const Icon(Icons.play_arrow),
64 | ),
65 | );
66 | }
67 |
68 | void play() {
69 | played = true;
70 | playing = true;
71 | for (int i = 0; i < _charList.length; i++) {
72 | Future.delayed(Duration(
73 | milliseconds: i * 80,
74 | )).then((_) {
75 | _controllerList[i].forward().whenComplete(() {
76 | if (i == _charList.length - 1) {
77 | playing = false;
78 | }
79 | });
80 | });
81 | }
82 | }
83 |
84 | void back() {
85 | played = false;
86 | playing = true;
87 | for (int i = 0; i < _charList.length; i++) {
88 | Future.delayed(Duration(
89 | milliseconds: i * 80,
90 | )).then((_) {
91 | _controllerList[i].reverse().whenComplete(() {
92 | if (i == _charList.length - 1) {
93 | playing = false;
94 | }
95 | });
96 | });
97 | }
98 | }
99 | }
100 |
101 | class AnimatedText extends AnimatedWidget {
102 | final Tween _opacityAnim = Tween(begin: 0, end: 1);
103 | final Widget? child;
104 |
105 | AnimatedText({super.key, required Animation animation, this.child})
106 | : super(listenable: animation);
107 |
108 | _getOpacity() {
109 | var value = _opacityAnim.evaluate(listenable as Animation);
110 | if (value < 0) {
111 | return 0;
112 | } else if (value > 1) {
113 | return 1;
114 | } else {
115 | return value;
116 | }
117 | }
118 |
119 | @override
120 | Widget build(BuildContext context) {
121 | return Opacity(
122 | opacity: _getOpacity(),
123 | child: SlideTransition(
124 | position:
125 | Tween(begin: const Offset(0, 5), end: const Offset(0, 0)).animate(listenable as Animation),
126 | child: child,
127 | ),
128 | );
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/lib/widget/animation_container_demo_page.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math' as math;
2 |
3 | import 'package:flutter/material.dart';
4 |
5 | ///来着 [Flutter 社区 公众号] 的动画例子
6 | class AnimationContainerDemoPage extends StatefulWidget {
7 | const AnimationContainerDemoPage({super.key});
8 |
9 | @override
10 | _AnimationContainerDemoPageState createState() => _AnimationContainerDemoPageState();
11 | }
12 |
13 | class _AnimationContainerDemoPageState extends State {
14 |
15 | ///定义需要执行的滑动效果数值
16 | double _width = 50;
17 | double _height = 50;
18 | Color _color = Colors.green;
19 | BorderRadiusGeometry _borderRadius = BorderRadius.circular(8);
20 |
21 |
22 | @override
23 | Widget build(BuildContext context) {
24 | return Scaffold(
25 | appBar: AppBar(
26 | title: const Text('AnimationContainerDemoPage Demo'),
27 | ),
28 | body: Center(
29 | child: AnimatedContainer(
30 | // Use the properties stored in the State class.
31 | width: _width,
32 | height: _height,
33 | decoration: BoxDecoration(
34 | color: _color,
35 | borderRadius: _borderRadius,
36 | ),
37 | // Define how long the animation should take.
38 | duration: const Duration(seconds: 1),
39 | // Provide an optional curve to make the animation feel smoother.
40 | curve: Curves.fastOutSlowIn,
41 | ),
42 | ),
43 | floatingActionButton: FloatingActionButton(
44 | child: const Icon(Icons.play_arrow),
45 | // When the user taps the button
46 | onPressed: () {
47 | // Use setState to rebuild the widget with new values.
48 | setState(() {
49 | // Create a random number generator.
50 | final random = math.Random();
51 |
52 | // Generate a random width and height.
53 | _width = random.nextInt(300).toDouble();
54 | _height = random.nextInt(300).toDouble();
55 |
56 | // Generate a random color.
57 | _color = Color.fromRGBO(
58 | random.nextInt(256),
59 | random.nextInt(256),
60 | random.nextInt(256),
61 | 1,
62 | );
63 |
64 | // Generate a random border radius.
65 | _borderRadius =
66 | BorderRadius.circular(random.nextInt(100).toDouble());
67 | });
68 | },
69 | ),
70 | );
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/lib/widget/async_to_sync_call_page.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:flutter/foundation.dart';
4 | import 'package:flutter/material.dart';
5 |
6 | class AsyncToSyncCallPage extends StatefulWidget {
7 | const AsyncToSyncCallPage({super.key});
8 |
9 | @override
10 | State createState() => _AsyncToSyncCallPageState();
11 | }
12 |
13 | class _AsyncToSyncCallPageState extends State {
14 | Completer? _syncCompleter;
15 |
16 | Future _syncWait(Future Function() fn) async {
17 | var currentAsync = _syncCompleter;
18 | final completer = Completer();
19 | _syncCompleter = completer;
20 |
21 | ///第一次是 null
22 | await currentAsync?.future;
23 | await fn.call();
24 | completer.complete();
25 | }
26 |
27 | ///因为 setState 不是同步的,只是内部把标记位标志为脏数据
28 | ///所以如果需要等待 setState 执行结束,需要做一个等待
29 | waitSetStateComplete(Future Function() fn) async {
30 | await fn();
31 | if (mounted) {
32 | setState(() {});
33 | }
34 | final completer = Completer();
35 |
36 | ///下一帧结束
37 | WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
38 | completer.complete();
39 | });
40 | return completer.future;
41 | }
42 |
43 | @override
44 | Widget build(BuildContext context) {
45 | return Scaffold(
46 | appBar: AppBar(
47 | title: const Text("AsyncToSyncCallPage"),
48 | ),
49 | floatingActionButton: FloatingActionButton(
50 | onPressed: () {
51 | if (kDebugMode) {
52 | print("Start······Waiting");
53 | }
54 | _syncWait(() async {
55 | await Future.delayed(const Duration(seconds: 4));
56 | if (kDebugMode) {
57 | print("Finish First");
58 | }
59 | });
60 |
61 | _syncWait(() async {
62 | await Future.delayed(const Duration(seconds: 2));
63 | if (kDebugMode) {
64 | print("Finish Tow");
65 | }
66 | });
67 |
68 | _syncWait(() async {
69 | await Future.delayed(const Duration(seconds: 1));
70 | if (kDebugMode) {
71 | print("Finish Three");
72 | }
73 | });
74 | },
75 | ),
76 | );
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/lib/widget/blur_demo_page.dart:
--------------------------------------------------------------------------------
1 | import 'dart:ui';
2 |
3 | import 'package:flutter/material.dart';
4 |
5 | class BlurDemoPage extends StatelessWidget {
6 | const BlurDemoPage({super.key});
7 |
8 | @override
9 | Widget build(BuildContext context) {
10 | return Scaffold(
11 | appBar: AppBar(
12 | title: const Text("BlurDemoPage"),
13 | ),
14 | body: Stack(
15 | children: [
16 | Positioned(
17 | top: 0,
18 | bottom: 0,
19 | left: 0,
20 | right: 0,
21 | child: Image.asset(
22 | "static/gsy_cat.png",
23 | fit: BoxFit.cover,
24 | width: MediaQuery.sizeOf(context).width,
25 | height: MediaQuery.sizeOf(context).height,
26 | ),
27 | ),
28 | Center(
29 | child: SizedBox(
30 | width: 200,
31 | height: 200,
32 | child: ClipRRect(
33 | borderRadius: BorderRadius.circular(15.0),
34 | child: BackdropFilter(
35 | filter: ImageFilter.blur(sigmaX: 8.0, sigmaY: 8.0),
36 | child: const Row(
37 | mainAxisSize: MainAxisSize.max,
38 | crossAxisAlignment: CrossAxisAlignment.center,
39 | mainAxisAlignment: MainAxisAlignment.center,
40 | children: [
41 | Icon(Icons.ac_unit),
42 | Text("哇!!")
43 | ],
44 | ),
45 | ),
46 | ),
47 | ),
48 | )
49 | ],
50 | ),
51 | );
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/lib/widget/book_page/book_text_clip.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class BookTextClip extends CustomClipper {
4 | final Path path;
5 |
6 | BookTextClip(this.path);
7 |
8 | @override
9 | Path getClip(Size size) {
10 | return path;
11 | }
12 |
13 | @override
14 | bool shouldReclip(CustomClipper oldClipper) {
15 | return true;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/lib/widget/book_page/cal_point.dart:
--------------------------------------------------------------------------------
1 | class CalPoint {
2 |
3 | CalPoint();
4 |
5 | CalPoint.data(this.x, this.y);
6 |
7 | double? x = -1;
8 | double? y = -1;
9 | }
--------------------------------------------------------------------------------
/lib/widget/card_item_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class CardItemPage extends StatelessWidget {
4 | const CardItemPage({super.key});
5 |
6 | @override
7 | Widget build(BuildContext context) {
8 | return Scaffold(
9 | backgroundColor: Colors.blueAccent,
10 | appBar: AppBar(
11 | title: const Text("CardItemPage"),
12 | ),
13 | body: Column(children: [
14 | renderImageNormal("static/gsy_cat.png"),
15 | renderImageRatio(context, "static/gsy_cat.png"),
16 | renderImageNormal("static/test.jpeg"),
17 | renderImageRatio(context, "static/test.jpeg"),
18 | ]),
19 | );
20 | }
21 |
22 | renderImageNormal(image) {
23 | return Card(
24 | margin: const EdgeInsets.all(5),
25 | child: Container(
26 | margin: const EdgeInsets.only(right: 10),
27 | child: Row(
28 | crossAxisAlignment: CrossAxisAlignment.center,
29 | children: [
30 | ClipRRect(
31 | borderRadius: const BorderRadius.only(
32 | topLeft: Radius.circular(4.0),
33 | bottomLeft: Radius.circular(4.0),
34 | ),
35 | child: Image.asset(
36 | image,
37 | fit: BoxFit.cover,
38 | width: 70,
39 | height: 70,
40 | ),
41 | ),
42 | const SizedBox(
43 | width: 10,
44 | ),
45 | const Expanded(
46 | child: Text(
47 | 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF'
48 | 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF'
49 | 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF',
50 | maxLines: 3,
51 | overflow: TextOverflow.ellipsis,
52 | style: TextStyle(fontSize: 15),
53 | )),
54 | ],
55 | ),
56 | ),
57 | );
58 | }
59 |
60 | renderImageRatio(context, image) {
61 | ///大概是屏幕 6 分之一的宽度
62 | double itemHeight = MediaQuery.sizeOf(context).width / 6;
63 |
64 | /// iphone xs max 的比例是 2688 * 1242; 拿到的 size 是 896.0 * 414.0
65 | double textSize = 15.0 * MediaQuery.sizeOf(context).width / 414.0;
66 |
67 | /// 注意,这是在 data.textScaleFactor = 1 的情况下
68 | //var data = MediaQueryData.fromWindow(WidgetsBinding.instance.window);
69 |
70 | return Card(
71 | margin: const EdgeInsets.all(5),
72 | child: Container(
73 | margin: const EdgeInsets.only(right: 10),
74 | child: Row(
75 | crossAxisAlignment: CrossAxisAlignment.center,
76 | children: [
77 | ClipRRect(
78 | borderRadius: const BorderRadius.only(
79 | topLeft: Radius.circular(4.0),
80 | bottomLeft: Radius.circular(4.0),
81 | ),
82 | child: Image.asset(
83 | image,
84 | fit: BoxFit.cover,
85 | height: itemHeight,
86 | width: itemHeight,
87 | ),
88 | ),
89 | const SizedBox(
90 | width: 10,
91 | ),
92 | Expanded(
93 | child: Text(
94 | 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF'
95 | 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF'
96 | 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF',
97 | maxLines: 3,
98 | overflow: TextOverflow.ellipsis,
99 | style: TextStyle(fontSize: textSize),
100 | )),
101 | ],
102 | ),
103 | ),
104 | );
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/lib/widget/clip_demo_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | /// 圆角效果处理实现
4 | class ClipDemoPage extends StatelessWidget {
5 | const ClipDemoPage({super.key});
6 |
7 | @override
8 | Widget build(BuildContext context) {
9 | return Scaffold(
10 | appBar: AppBar(
11 | title: const Text("ClipDemoPage"),
12 | ),
13 | body: Container(
14 | alignment: Alignment.center,
15 | margin: const EdgeInsets.all(10),
16 | child: Column(
17 | crossAxisAlignment: CrossAxisAlignment.center,
18 | mainAxisAlignment: MainAxisAlignment.center,
19 | children: [
20 | const Text("BoxDecoration 圆角"),
21 | Container(
22 | width: 100,
23 | height: 100,
24 | decoration: const BoxDecoration(
25 | color: Colors.red,
26 | image: DecorationImage(
27 | fit: BoxFit.cover,
28 | image: AssetImage("static/gsy_cat.png"),
29 | ),
30 | borderRadius: BorderRadius.all(Radius.circular(5.0))),
31 | ),
32 | const SizedBox(
33 | height: 10,
34 | ),
35 | const Text("BoxDecoration 圆角对 child"),
36 | Container(
37 | width: 100,
38 | height: 100,
39 | decoration: const BoxDecoration(
40 | color: Colors.red,
41 | borderRadius: BorderRadius.all(Radius.circular(5.0))),
42 | child: Image.asset(
43 | "static/gsy_cat.png",
44 | fit: BoxFit.cover,
45 | width: 100,
46 | height: 100,
47 | ),
48 | ),
49 | const SizedBox(
50 | height: 10,
51 | ),
52 | const Text("ClipRRect 圆角对 child"),
53 | ClipRRect(
54 | borderRadius: const BorderRadius.all(Radius.circular(5.0)),
55 | child: Image.asset(
56 | "static/gsy_cat.png",
57 | fit: BoxFit.cover,
58 | width: 100,
59 | height: 100,
60 | ),
61 | )
62 | ],
63 | ),
64 | ),
65 | );
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/lib/widget/cloud/cloud_widget.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | import 'cloud_render.dart';
4 |
5 | class CloudWidget extends MultiChildRenderObjectWidget {
6 | final Clip overflow;
7 | final double ratio;
8 |
9 | const CloudWidget({
10 | super.key,
11 | this.ratio = 1,
12 | this.overflow = Clip.none,
13 | super.children,
14 | });
15 |
16 | @override
17 | RenderObject createRenderObject(BuildContext context) {
18 | return RenderCloudWidget(
19 | ratio: ratio,
20 | overflow: overflow,
21 | );
22 | }
23 |
24 | @override
25 | void updateRenderObject(
26 | BuildContext context, RenderCloudWidget renderObject) {
27 | renderObject
28 | ..ratio = ratio
29 | ..overflow = overflow;
30 | }
31 | }
--------------------------------------------------------------------------------
/lib/widget/controller_demo_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart';
2 | import 'package:flutter/material.dart';
3 |
4 | /// 在 Flutter 中有很多内置的 Controller
5 | /// 大部分内置控件都可以通过 Controller 设置和获取控件参数
6 | /// 比如 TextField 的 TextEditingController
7 | /// 比如 ListView 的 ScrollController
8 | /// 一般想对控件做 OOXX 的事情,先找个 Controller 就对了。
9 | class ControllerDemoPage extends StatelessWidget {
10 | const ControllerDemoPage({super.key});
11 |
12 | @override
13 | Widget build(BuildContext context) {
14 | if (kDebugMode) {
15 | print("######### MyHomePage ${MediaQuery.of(context).size}");
16 | }
17 | return Scaffold(
18 | appBar: AppBar(),
19 | body: const DefaultTabController(
20 | length: 3,
21 | child: TabBarView(
22 | //physics: const BouncingScrollPhysics(),
23 | children: [
24 | EditPage(),
25 | EditPage(),
26 | EditPage(),
27 | ],
28 | )),
29 | );
30 | }
31 | }
32 |
33 | class EditPage extends StatelessWidget {
34 | const EditPage({super.key});
35 |
36 | @override
37 | Widget build(BuildContext context) {
38 | return ListView(
39 | children: [
40 | const ListTile(
41 | title: Text("Title"),
42 | subtitle: Text("Subtitle"),
43 | trailing: Icon(Icons.arrow_forward_ios),
44 | ),
45 | Container(
46 | color: Colors.red,
47 | alignment: Alignment.center,
48 | height: 40,
49 | child: const Text("FFFF"),
50 | ),
51 | Container(
52 | color: Colors.red,
53 | alignment: Alignment.center,
54 | height: 40,
55 | child: const Text("FFFF"),
56 | )
57 | ],
58 | );
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/lib/widget/custom_shader_path_demo_page.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 | import 'dart:math';
3 | import 'dart:ui' as ui;
4 | import 'package:flutter/material.dart';
5 |
6 | class CustomShaderPathDemoPage extends StatefulWidget {
7 | const CustomShaderPathDemoPage({super.key});
8 |
9 | @override
10 | State createState() =>
11 | _CustomShaderPathDemoPageState();
12 | }
13 |
14 | class _CustomShaderPathDemoPageState extends State {
15 | Future _loadAssetImage() {
16 | Completer completer = Completer();
17 |
18 | const AssetImage("static/gsy_cat.png")
19 | .resolve(const ImageConfiguration())
20 | .addListener(
21 | ImageStreamListener((ImageInfo image, bool synchronousCall) {
22 | ui.Image img;
23 | img = image.image;
24 | completer.complete(img);
25 | }));
26 |
27 | return completer.future;
28 | }
29 |
30 | @override
31 | Widget build(BuildContext context) {
32 | return Scaffold(
33 | appBar: AppBar(
34 | title: const Text("CustomShaderPathDemoPage"),
35 | ),
36 | body: Center(
37 | child: FutureBuilder(
38 | future: _loadAssetImage(),
39 | builder: (c, s) {
40 | if (s.data == null) {
41 | return Container();
42 | }
43 | return Container(
44 | height: 200,
45 | width: 200,
46 | color: Colors.greenAccent,
47 | child: CustomPaint(
48 | ///直接使用值做动画
49 | foregroundPainter: _AnimationPainter(s.data!),
50 | ),
51 | );
52 | },
53 | ),
54 | ));
55 | }
56 | }
57 |
58 | class _AnimationPainter extends CustomPainter {
59 | final Paint _paint = Paint();
60 |
61 | final ui.Image img;
62 |
63 | _AnimationPainter(this.img);
64 |
65 | @override
66 | void paint(Canvas canvas, Size size) {
67 |
68 | var y1 = sin(50);
69 | var y2 = sin(50 + pi / 2);
70 | var y3 = sin(50 + pi);
71 |
72 | final startPointY = size.height * (0.5 + 0.4 * y1);
73 | final controlPointY = size.height * (0.5 + 0.4 * y2);
74 | final endPointY = size.height * (0.5 + 0.4 * y3);
75 | var path = Path();
76 | path.moveTo(size.width * 0, startPointY);
77 | path.quadraticBezierTo(
78 | size.width * 0.5, controlPointY, size.width, endPointY);
79 | path.lineTo(size.width, size.height);
80 | path.lineTo(0, size.height);
81 | path.close();
82 | canvas.drawPath(
83 | path,
84 | _paint
85 | ..shader = ImageShader(img, TileMode.repeated, TileMode.repeated,
86 | Matrix4.identity().scaled(0.2).storage)
87 | ..strokeWidth = 20
88 | ..style = PaintingStyle.stroke);
89 |
90 |
91 |
92 | ///网格
93 | var step = 1;
94 | var b = path.getBounds();
95 | canvas.save();
96 | canvas.clipPath(path);
97 | for (int i = step; i < b.width; i = i + step) {
98 | canvas.drawLine(
99 | Offset(b.left + i, b.top), Offset(b.left + i, b.bottom), Paint());
100 | }
101 |
102 | for (int i = step; i < b.height; i = i + step) {
103 | canvas.drawLine(
104 | Offset(b.left, b.top + i), Offset(b.right, b.top + i), Paint());
105 | }
106 |
107 | canvas.restore();
108 | }
109 |
110 | @override
111 | bool shouldRepaint(CustomPainter oldDelegate) => true;
112 | }
113 |
--------------------------------------------------------------------------------
/lib/widget/custom_viewport/custom_viewport.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 | import 'package:flutter/foundation.dart';
3 | import 'package:flutter/rendering.dart';
4 | import 'package:flutter/widgets.dart';
5 |
6 | class CustomViewport extends Viewport {
7 | /// 需要最后渲染的widget的RenderObject类列表
8 | final List highestChildInPaintOrderClassList;
9 |
10 | CustomViewport({
11 | super.key,
12 | super.axisDirection,
13 | super.crossAxisDirection,
14 | super.anchor,
15 | required super.offset,
16 | super.center,
17 | super.cacheExtent,
18 | super.slivers,
19 | this.highestChildInPaintOrderClassList = const [],
20 | });
21 |
22 | @override
23 | RenderViewport createRenderObject(BuildContext context) {
24 | return _RenderExpandedViewport(
25 | axisDirection: axisDirection,
26 | crossAxisDirection: crossAxisDirection ??
27 | Viewport.getDefaultCrossAxisDirection(context, axisDirection),
28 | anchor: anchor,
29 | offset: offset,
30 | cacheExtent: cacheExtent,
31 | highestChildInPaintOrderClassList: highestChildInPaintOrderClassList,
32 | );
33 | }
34 | }
35 |
36 | class _RenderExpandedViewport extends RenderViewport {
37 | final List highestChildInPaintOrderClassList;
38 |
39 | _RenderExpandedViewport({
40 | super.axisDirection,
41 | required super.crossAxisDirection,
42 | required super.offset,
43 | super.anchor,
44 | super.cacheExtent,
45 | this.highestChildInPaintOrderClassList = const [],
46 | });
47 |
48 | @override
49 | Iterable get childrenInPaintOrder {
50 | if (firstChild == null) return [];
51 | final children = _getChildrenPaintOrder();
52 | return children;
53 | }
54 |
55 | @override
56 | Iterable get childrenInHitTestOrder {
57 | if (firstChild == null) return [];
58 | final children = _getChildrenPaintOrder();
59 | return children.reversed.toList();
60 | }
61 |
62 | List _getChildrenPaintOrder() {
63 | final List children = [];
64 | var child = firstChild;
65 | while (child != null) {
66 | children.add(child);
67 | child = childAfter(child);
68 | }
69 | if (highestChildInPaintOrderClassList.isNotEmpty) {
70 | for (var clazz in highestChildInPaintOrderClassList) {
71 | try {
72 | final renderSliver =
73 | children.firstWhere((child) => child.runtimeType == clazz);
74 | children.remove(renderSliver);
75 | children.add(renderSliver);
76 | } catch (e) {
77 | if (kDebugMode) {
78 | print(e);
79 | }
80 | }
81 | }
82 | return children;
83 | } else {
84 | return children.reversed.toList();
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/lib/widget/custom_viewport/custom_viewport_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter/rendering.dart';
3 | import 'package:gsy_flutter_demo/widget/custom_viewport/custom_viewport.dart';
4 | import 'package:gsy_flutter_demo/widget/custom_viewport/first_tab_bar.dart';
5 | import 'package:gsy_flutter_demo/widget/custom_viewport/first_tab_bar_render_sliver.dart';
6 | import 'package:gsy_flutter_demo/widget/custom_viewport/secondary_tab_bar.dart';
7 | import 'package:gsy_flutter_demo/widget/custom_viewport/secondary_tab_bar_render_sliver.dart';
8 |
9 | class CustomViewportPage extends StatelessWidget {
10 | const CustomViewportPage({super.key});
11 |
12 | @override
13 | Widget build(BuildContext context) {
14 | return Scaffold(
15 | appBar: AppBar(),
16 | body: Scrollable(
17 | viewportBuilder: (BuildContext context, ViewportOffset position) {
18 | return CustomViewport(
19 | offset: position,
20 | highestChildInPaintOrderClassList: const [
21 | FirstTabBarRenderSliver,
22 | SecondaryTabBarRenderSliver,
23 | ],
24 | slivers: [
25 | const FirstTabBar(),
26 | SliverGrid(
27 | gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
28 | maxCrossAxisExtent: 200.0,
29 | mainAxisSpacing: 10.0,
30 | crossAxisSpacing: 10.0,
31 | childAspectRatio: 4.0,
32 | ),
33 | delegate: SliverChildBuilderDelegate(
34 | (BuildContext context, int index) {
35 | return Container(
36 | alignment: Alignment.center,
37 | color: Colors.teal[100 * (index % 9)],
38 | child: Text('Grid Item $index'),
39 | );
40 | },
41 | childCount: 20,
42 | ),
43 | ),
44 | const SecondaryTabBar(),
45 | SliverFixedExtentList(
46 | itemExtent: 50.0,
47 | delegate: SliverChildBuilderDelegate(
48 | (BuildContext context, int index) {
49 | return Container(
50 | alignment: Alignment.center,
51 | color: Colors.lightBlue[100 * (index % 9)],
52 | child: Text('List Item $index'),
53 | );
54 | },
55 | ),
56 | ),
57 | ],
58 | );
59 | },
60 | ),
61 | );
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/lib/widget/custom_viewport/first_tab_bar.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:gsy_flutter_demo/widget/custom_viewport/first_tab_bar_sliver_widget.dart';
3 |
4 | class FirstTabBar extends StatelessWidget {
5 | const FirstTabBar({
6 | super.key,
7 | });
8 |
9 | @override
10 | Widget build(BuildContext context) {
11 | return FirstTabBarSliverWidget(
12 | child: _buildChild(),
13 | );
14 | }
15 |
16 | Widget _buildChild() {
17 | return Container(
18 | height: 66,
19 | color: Colors.deepPurpleAccent,
20 | child: const Center(
21 | child: Text('一级tab'),
22 | ),
23 | );
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/lib/widget/custom_viewport/first_tab_bar_render_sliver.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math';
2 |
3 | import 'package:flutter/rendering.dart';
4 |
5 | class FirstTabBarRenderSliver extends RenderSliverSingleBoxAdapter {
6 | @override
7 | void performLayout() {
8 | if (child == null) {
9 | geometry = SliverGeometry.zero;
10 | return;
11 | }
12 | child!.layout(constraints.asBoxConstraints(), parentUsesSize: true);
13 | double childExtent;
14 | switch (constraints.axis) {
15 | case Axis.horizontal:
16 | childExtent = child!.size.width;
17 | break;
18 | case Axis.vertical:
19 | childExtent = child!.size.height;
20 | break;
21 | }
22 | final paintedChildExtent = min(
23 | childExtent,
24 | constraints.remainingPaintExtent - constraints.overlap,
25 | );
26 | double paintOrigin = constraints.overlap;
27 | paintOrigin = paintOrigin < 0 ? 0 : paintOrigin;
28 | geometry = SliverGeometry(
29 | paintExtent: paintedChildExtent,
30 | maxPaintExtent: childExtent,
31 | maxScrollObstructionExtent: childExtent,
32 | paintOrigin: paintOrigin,
33 | scrollExtent: childExtent,
34 | layoutExtent: max(0.0, paintedChildExtent - constraints.scrollOffset),
35 | hasVisualOverflow: paintedChildExtent < childExtent,
36 | );
37 | }
38 |
39 | @override
40 | double childMainAxisPosition(RenderBox child) {
41 | return 0;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/lib/widget/custom_viewport/first_tab_bar_sliver_widget.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:gsy_flutter_demo/widget/custom_viewport/first_tab_bar_render_sliver.dart';
3 |
4 | class FirstTabBarSliverWidget extends SingleChildRenderObjectWidget {
5 | const FirstTabBarSliverWidget({super.child, super.key});
6 |
7 | @override
8 | RenderObject createRenderObject(BuildContext context) {
9 | return FirstTabBarRenderSliver();
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/lib/widget/custom_viewport/secondary_tab_bar.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:gsy_flutter_demo/widget/custom_viewport/secondary_tab_bar_sliver_widget.dart';
3 |
4 | class SecondaryTabBar extends StatelessWidget {
5 | const SecondaryTabBar({
6 | super.key,
7 | });
8 |
9 | @override
10 | Widget build(BuildContext context) {
11 | return SecondaryTabBarSliverWidget(
12 | child: _buildChild(),
13 | );
14 | }
15 |
16 | Widget _buildChild() {
17 | return Container(
18 | height: 66,
19 | color: Colors.red.withValues(alpha: 0.8),
20 | child: const Center(child: Text('二级tab')),
21 | );
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/lib/widget/custom_viewport/secondary_tab_bar_render_sliver.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math';
2 |
3 | import 'package:flutter/rendering.dart';
4 |
5 | class SecondaryTabBarRenderSliver extends RenderSliverSingleBoxAdapter {
6 | @override
7 | void performLayout() {
8 | if (child == null) {
9 | geometry = SliverGeometry.zero;
10 | return;
11 | }
12 | child!.layout(constraints.asBoxConstraints(), parentUsesSize: true);
13 | double childExtent;
14 | switch (constraints.axis) {
15 | case Axis.horizontal:
16 | childExtent = child!.size.width;
17 | break;
18 | case Axis.vertical:
19 | childExtent = child!.size.height;
20 | break;
21 | }
22 | final paintedChildExtent = min(
23 | childExtent,
24 | constraints.remainingPaintExtent - constraints.overlap,
25 | );
26 | double paintOrigin = constraints.overlap - 44;
27 | paintOrigin = paintOrigin < 0 ? 0 : paintOrigin;
28 | geometry = SliverGeometry(
29 | paintExtent: paintedChildExtent,
30 | maxPaintExtent: childExtent,
31 | maxScrollObstructionExtent: childExtent,
32 | paintOrigin: paintOrigin,
33 | scrollExtent: childExtent,
34 | layoutExtent: max(0.0, paintedChildExtent - constraints.scrollOffset),
35 | hasVisualOverflow: paintedChildExtent < childExtent,
36 | );
37 | }
38 |
39 | @override
40 | double childMainAxisPosition(RenderBox child) {
41 | return 0;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/lib/widget/custom_viewport/secondary_tab_bar_sliver_widget.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | import 'secondary_tab_bar_render_sliver.dart';
4 |
5 | class SecondaryTabBarSliverWidget extends SingleChildRenderObjectWidget {
6 | const SecondaryTabBarSliverWidget({super.child, super.key});
7 |
8 | @override
9 | RenderObject createRenderObject(BuildContext context) {
10 | return SecondaryTabBarRenderSliver();
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/lib/widget/drag_img_demo_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:matrix_gesture_detector/matrix_gesture_detector.dart';
3 |
4 | class DragImgDemoPage extends StatefulWidget {
5 | const DragImgDemoPage({super.key});
6 |
7 | @override
8 | _DragImgDemoPageState createState() => _DragImgDemoPageState();
9 | }
10 |
11 | class _DragImgDemoPageState extends State {
12 |
13 | Matrix4 transform = Matrix4.diagonal3Values(1, 1, 1.0);
14 |
15 | @override
16 | Widget build(BuildContext context) {
17 | return Scaffold(
18 | appBar: AppBar(
19 | title: const Text("DragImgDemoPage"),
20 | ),
21 | body: MatrixGestureDetector(
22 | onMatrixUpdate: (m, tm, sm, rm) {
23 | setState(() {
24 | transform = m;
25 | });
26 | },
27 | child: Transform(
28 | transform: transform,
29 | child: Image.asset(
30 | "static/gsy_cat.png",
31 | fit: BoxFit.fitWidth,
32 | width: MediaQuery
33 | .of(context)
34 | .size
35 | .width,
36 | height: MediaQuery
37 | .of(context)
38 | .size
39 | .height,
40 | )
41 | ),
42 | ),
43 | );
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/lib/widget/drop_select_menu/drop_rect_tween.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class DropRectTween extends Tween {
4 | /// Creates a [Rect] tween.
5 | ///
6 | /// The [begin] and [end] properties may be null; the null value
7 | /// is treated as an empty rect at the top left corner.
8 | DropRectTween({required Rect begin, required Rect end})
9 | : super(begin: begin, end: end);
10 |
11 | /// Returns the value this variable has at the given animation clock value.
12 | @override
13 | Rect lerp(double t) => Rect.lerp(begin, end, t)!;
14 | }
15 |
--------------------------------------------------------------------------------
/lib/widget/drop_select_menu/drop_select_controller.dart:
--------------------------------------------------------------------------------
1 |
2 |
3 | import 'package:flutter/material.dart';
4 |
5 | class DropSelectController extends ChangeNotifier {
6 |
7 | DropSelectEvent? event;
8 |
9 | int? menuIndex;
10 |
11 | int? index;
12 |
13 | dynamic data;
14 |
15 | void hide() {
16 | event = DropSelectEvent.HIDE;
17 | notifyListeners();
18 | }
19 |
20 | void show(int index) {
21 | event = DropSelectEvent.ACTIVE;
22 | menuIndex = index;
23 | notifyListeners();
24 | }
25 |
26 | void select(dynamic data, {int? index}) {
27 | event = DropSelectEvent.SELECT;
28 | this.data = data;
29 | this.index = index;
30 | notifyListeners();
31 | }
32 | }
33 |
34 |
35 | enum DropSelectEvent {
36 | SELECT,
37 | // ignore: constant_identifier_names
38 | ACTIVE,
39 | HIDE,
40 | }
41 |
--------------------------------------------------------------------------------
/lib/widget/drop_select_menu/drop_select_demo_data.dart:
--------------------------------------------------------------------------------
1 | import 'package:gsy_flutter_demo/widget/drop_select_menu/drop_select_object.dart';
2 |
3 | final selectNormal = getSelectList();
4 | final selectExpand = getSelectChildExpandList();
5 | final selectChildGrid = getSelectChildList();
6 |
7 | getSelectList() {
8 | return [
9 | DropSelectObject(title: "全部", selectedCleanOther: true, selected: true),
10 | DropSelectObject(title: "选择2"),
11 | DropSelectObject(title: "选择3"),
12 | DropSelectObject(title: "选择4"),
13 | DropSelectObject(title: "选择5"),
14 | DropSelectObject(title: "选择6"),
15 | DropSelectObject(title: "选择7"),
16 | DropSelectObject(title: "选择7"),
17 | ];
18 | }
19 |
20 | getSelectChildList() {
21 | List children1 = [
22 | DropSelectObject(title: "全部", selectedCleanOther: true, selected: true),
23 | DropSelectObject(title: "问题1"),
24 | DropSelectObject(title: "问题2"),
25 | DropSelectObject(title: "问题3"),
26 | DropSelectObject(title: "问题4"),
27 | DropSelectObject(title: "问题5"),
28 | DropSelectObject(title: "问题6"),
29 | DropSelectObject(title: "问题7"),
30 | DropSelectObject(title: "问题8"),
31 | ];
32 |
33 | List children2 = [
34 | DropSelectObject(title: "全部", selectedCleanOther: true, selected: true),
35 | DropSelectObject(title: "测试1"),
36 | DropSelectObject(title: "测试2"),
37 | DropSelectObject(title: "测试3"),
38 | DropSelectObject(title: "测试4"),
39 | DropSelectObject(title: "测试5"),
40 | DropSelectObject(title: "测试6"),
41 | ];
42 |
43 | return [
44 | DropSelectObject(title: "选择1", children: children1),
45 | DropSelectObject(title: "选择2", children: children2),
46 | ];
47 | }
48 |
49 | getSelectChildExpandList() {
50 | List children1 = [
51 | DropSelectObject(title: "全部", selectedCleanOther: true, selected: true),
52 | DropSelectObject(title: "距离1"),
53 | DropSelectObject(title: "距离2"),
54 | DropSelectObject(title: "距离3"),
55 | DropSelectObject(title: "距离4"),
56 | DropSelectObject(title: "距离5"),
57 | DropSelectObject(title: "距离6"),
58 | DropSelectObject(title: "距离7"),
59 | ];
60 |
61 | List children2 = [
62 | DropSelectObject(title: "全部", selectedCleanOther: true, selected: true),
63 | DropSelectObject(title: "范围1"),
64 | DropSelectObject(title: "范围2"),
65 | DropSelectObject(title: "范围3"),
66 | DropSelectObject(title: "范围4"),
67 | DropSelectObject(title: "范围5"),
68 | DropSelectObject(title: "范围6"),
69 | DropSelectObject(title: "范围7"),
70 | DropSelectObject(title: "范围8"),
71 | ];
72 |
73 | List children3 = [
74 | DropSelectObject(title: "全部", selectedCleanOther: true, selected: true),
75 | DropSelectObject(title: "路径1"),
76 | DropSelectObject(title: "路径2"),
77 | DropSelectObject(title: "路径3"),
78 | DropSelectObject(title: "路径4"),
79 | DropSelectObject(title: "路径5"),
80 | ];
81 |
82 | List children4 = [
83 | DropSelectObject(title: "全部", selectedCleanOther: true, selected: true),
84 | DropSelectObject(title: "回家1"),
85 | DropSelectObject(title: "回家2"),
86 | DropSelectObject(title: "回家3"),
87 | ];
88 |
89 | return [
90 | DropSelectObject(title: "距离", children: children1),
91 | DropSelectObject(title: "范围", children: children2),
92 | DropSelectObject(title: "路径", children: children3),
93 | DropSelectObject(title: "回家", children: children4),
94 | ];
95 | }
96 |
--------------------------------------------------------------------------------
/lib/widget/drop_select_menu/drop_select_list_menu.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter/widgets.dart';
3 | import 'package:gsy_flutter_demo/widget/drop_select_menu/drop_select_object.dart';
4 |
5 | import 'drop_select_controller.dart';
6 | import 'drop_select_widget.dart';
7 |
8 | typedef MenuItemBuilder = Widget Function(
9 | BuildContext context, T data);
10 |
11 | const double kDropSelectMenuItemHeight = 45.0;
12 |
13 | class DropSelectListMenu extends DropSelectWidget {
14 | final List? data;
15 | final MenuItemBuilder? itemBuilder;
16 | final bool singleSelected;
17 | final double itemExtent;
18 |
19 | const DropSelectListMenu(
20 | {super.key, this.data,
21 | this.singleSelected = false,
22 | this.itemBuilder,
23 | this.itemExtent = kDropSelectMenuItemHeight});
24 |
25 | @override
26 | DropSelectState createState() {
27 | return _MenuListState();
28 | }
29 | }
30 |
31 | class _MenuListState
32 | extends DropSelectState> {
33 | @override
34 | void initState() {
35 | super.initState();
36 | }
37 |
38 | Widget buildItem(BuildContext context, int index) {
39 | final List list = widget.data!;
40 |
41 | final T data = list[index];
42 | return GestureDetector(
43 | behavior: HitTestBehavior.opaque,
44 | child: widget.itemBuilder!(context, data),
45 | onTap: () {
46 | if (widget.singleSelected) {
47 | for (var item in widget.data!) {
48 | item.selected = false;
49 | }
50 | }
51 | if(data.selectedCleanOther) {
52 | for (var item in widget.data!) {
53 | item.selected = false;
54 | }
55 | }
56 | setState(() {
57 | data.selected = !data.selected;
58 | });
59 | controller?.select(data, index: index);
60 | },
61 | );
62 | }
63 |
64 | @override
65 | Widget build(BuildContext context) {
66 | return ListView.builder(
67 | itemExtent: widget.itemExtent,
68 | itemBuilder: buildItem,
69 | itemCount: widget.data!.length,
70 | );
71 | }
72 |
73 | @override
74 | void onEvent(DropSelectEvent? event) {
75 | switch (event) {
76 | case DropSelectEvent.SELECT:
77 | case DropSelectEvent.HIDE:
78 | {}
79 | break;
80 | case DropSelectEvent.ACTIVE:
81 | default:
82 | {}
83 | break;
84 | }
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/lib/widget/drop_select_menu/drop_select_object.dart:
--------------------------------------------------------------------------------
1 | class DropSelectObject {
2 | String? title;
3 | List? children;
4 | bool selected;
5 | bool selectedCleanOther;
6 |
7 | DropSelectObject(
8 | {this.title,
9 | this.children,
10 | this.selected = false,
11 | this.selectedCleanOther = false});
12 |
13 | DropSelectObject clone() {
14 | DropSelectObject newData = DropSelectObject();
15 | newData.title = title;
16 | newData.children = [];
17 | children?.forEach((item) {
18 | newData.children!.add(item.clone());
19 | });
20 |
21 | newData.selected = selected;
22 | newData.selectedCleanOther = selectedCleanOther;
23 | return newData;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/lib/widget/floating_touch_demo_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | ///全局悬浮按键
4 | class FloatingTouchDemoPage extends StatefulWidget {
5 | const FloatingTouchDemoPage({super.key});
6 |
7 | @override
8 | _FloatingTouchDemoPageState createState() => _FloatingTouchDemoPageState();
9 | }
10 |
11 | class _FloatingTouchDemoPageState extends State {
12 | Offset offset = const Offset(200, 200);
13 |
14 | final double height = 80;
15 |
16 | ///显示悬浮控件
17 | _showFloating() {
18 | var overlayState = Overlay.of(context);
19 | OverlayEntry? overlayEntry;
20 | overlayEntry = OverlayEntry(builder: (context) {
21 | return Stack(
22 | children: [
23 | Positioned(
24 | left: offset.dx,
25 | top: offset.dy,
26 | child: _buildFloating(overlayEntry),
27 | ),
28 | ],
29 | );
30 | });
31 |
32 | ///插入全局悬浮控件
33 | overlayState.insert(overlayEntry);
34 | }
35 |
36 | ///绘制悬浮控件
37 | _buildFloating(OverlayEntry? overlayEntry) {
38 | return GestureDetector(
39 | behavior: HitTestBehavior.deferToChild,
40 | onPanDown: (details) {
41 | offset = details.globalPosition - Offset(height / 2, height / 2);
42 | overlayEntry!.markNeedsBuild();
43 | },
44 | onPanUpdate: (DragUpdateDetails details) {
45 | ///根据触摸修改悬浮控件偏移
46 | offset = offset + details.delta;
47 | overlayEntry!.markNeedsBuild();
48 | },
49 | onLongPress: () {
50 | overlayEntry!.remove();
51 | },
52 | child: Material(
53 | color: Colors.transparent,
54 | child: Container(
55 | height: height,
56 | width: height,
57 | alignment: Alignment.center,
58 | decoration: BoxDecoration(
59 | color: Colors.redAccent,
60 | borderRadius: BorderRadius.all(Radius.circular(height / 2))),
61 | child: const Text(
62 | "长按\n移除",
63 | style: TextStyle(color: Colors.white),
64 | ),
65 | ),
66 | ),
67 | );
68 | }
69 |
70 | @override
71 | Widget build(BuildContext context) {
72 | return Scaffold(
73 | appBar: AppBar(
74 | title: const Text("FloatingTouchDemoPage"),
75 | ),
76 | body: Center(
77 | child: TextButton(
78 | onPressed: () {
79 | _showFloating();
80 | },
81 | child: const Text("显示悬浮")),
82 | ),
83 | );
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/lib/widget/gesture_password/gesture_password_demo_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:gsy_flutter_demo/widget/gesture_password/gesture_password_view.dart';
3 |
4 | class GesturePasswordDemoPage extends StatefulWidget {
5 | const GesturePasswordDemoPage({super.key});
6 |
7 | @override
8 | _GesturePasswordDemoState createState() => _GesturePasswordDemoState();
9 | }
10 |
11 | class _GesturePasswordDemoState extends State {
12 | String _pwd = '';
13 |
14 | @override
15 | Widget build(BuildContext context) {
16 | return Scaffold(
17 | appBar: AppBar(
18 | title: const Text("手势密码"),
19 | ),
20 | body: Center(
21 | child: Column(
22 | mainAxisAlignment: MainAxisAlignment.center,
23 | children: [
24 | SizedBox(
25 | width: 300,
26 | height: 300,
27 | child: GesturePasswordView(
28 | pathWidth: 6,
29 | frameRadius: 30,
30 | onDone: (value) {
31 | setState(() {
32 | _pwd = value.join();
33 | });
34 | },
35 | ),
36 | ),
37 | Text("当前密码: $_pwd"),
38 | ],
39 | ),
40 | ),
41 | );
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/lib/widget/gesture_password/gesture_password_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | import 'src/gesture_view_controller.dart';
4 | import 'src/gesture_view_path.dart';
5 | import 'src/gesture_view_point.dart';
6 |
7 | class GesturePasswordView extends StatefulWidget {
8 |
9 | /// 圆圈半径
10 | final double frameRadius;
11 |
12 | /// 圆圈中心点半径
13 | final double pointRadius;
14 |
15 | /// 圆圈普通状态下颜色
16 | final Color color;
17 |
18 | /// 圆圈选中颜色
19 | final Color highlightColor;
20 |
21 | /// 连线颜色
22 | final Color pathColor;
23 |
24 | /// 连线半径
25 | final double pathWidth;
26 |
27 | /// 手势结果
28 | final Function(List)? onDone;
29 |
30 | const GesturePasswordView({
31 | super.key,
32 | this.pointRadius = 10,
33 | this.frameRadius = 40,
34 | this.color = Colors.grey,
35 | this.highlightColor = Colors.blue,
36 | this.pathColor = Colors.blue,
37 | this.onDone,
38 | this.pathWidth = 5,
39 | });
40 |
41 | @override
42 | State createState() => _GesturePasswordState();
43 | }
44 |
45 | class _GesturePasswordState extends State {
46 | final GestureViewController controller = GestureViewController();
47 |
48 | @override
49 | void initState() {
50 | controller.initParameters(
51 | pointRadius: widget.pointRadius,
52 | frameRadius: widget.frameRadius,
53 | color: widget.color,
54 | highlightColor: widget.highlightColor,
55 | pathColor: widget.pathColor,
56 | onFinishGesture: widget.onDone,
57 | pathWidth: widget.pathWidth,
58 | updateView: (){
59 | setState(() {});
60 | }
61 | );
62 | WidgetsBinding.instance.addPostFrameCallback((_) => controller.setPointValues());
63 | super.initState();
64 | }
65 |
66 | @override
67 | Widget build(BuildContext context) {
68 | return SizedBox(
69 | key: controller.globalKey,
70 | width: double.infinity,
71 | height: double.infinity,
72 | child: Stack(
73 | children: [
74 | GestureDotsPanelWidget(points: controller.point),
75 | GestureViewPathWidget(
76 | points: controller.pathPoint,
77 | pathWidth: controller.pathWidth,
78 | color: controller.pathColor,
79 | onPanDown: controller.onPanDown,
80 | onPanEnd: controller.onPanEnd,
81 | onPanUpdate: controller.onPanUpdate,
82 | ),
83 | ],
84 | ),
85 | );
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/lib/widget/gesture_password/src/gesture_view_controller.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | import 'gesture_view_model.dart';
4 |
5 | class GestureViewController {
6 | final List _points = [];
7 | final List _pathPoint = [];
8 | final GlobalKey globalKey = GlobalKey();
9 | double _frameRadius = 0.0;
10 | double _pointRadius = 0.0;
11 | Color _color = Colors.grey;
12 | Color _highlightColor = Colors.blue;
13 | Color _pathColor = Colors.blue;
14 | Function(List)? _onFinishGesture;
15 | double _pathWidth = 5;
16 | Function()? _updateView;
17 | Offset? _firstPoint;
18 | Offset? _movePoint;
19 | // ignore: prefer_final_fields
20 | List _result = [];
21 | }
22 |
23 | extension Data on GestureViewController {
24 | List get point => _points;
25 |
26 | List get pathPoint {
27 | List tempPathPoint = [];
28 | tempPathPoint.addAll(_pathPoint);
29 | if (_movePoint != null) {
30 | tempPathPoint.add(_movePoint!);
31 | }
32 | return tempPathPoint;
33 | }
34 |
35 | Color get pathColor => _pathColor;
36 |
37 | double get pathWidth => _pathWidth;
38 |
39 | }
40 |
41 | extension Private on GestureViewController {
42 | void _initPoint() {
43 | _points.addAll(List.generate(
44 | 9,
45 | (index) => GesturePasswordPointModel(
46 | index: index,
47 | frameRadius: _frameRadius,
48 | pointRadius: _pointRadius,
49 | color: _color,
50 | highlightColor: _highlightColor,
51 | pathColor: _pathColor,
52 | ),
53 | ));
54 | }
55 |
56 | double _getPointWidth(double width) => width / 3;
57 | }
58 |
59 | extension Public on GestureViewController {
60 | void initParameters({
61 | double frameRadius = 0.0,
62 | double pointRadius = 0.0,
63 | Color color = Colors.grey,
64 | Color highlightColor = Colors.blue,
65 | Color pathColor = Colors.blue,
66 | Function(List)? onFinishGesture,
67 | Function()? updateView,
68 | double pathWidth = 5,
69 | }) {
70 | _frameRadius = frameRadius;
71 | _pointRadius = pointRadius;
72 | _color = color;
73 | _highlightColor = highlightColor;
74 | _pathColor = pathColor;
75 | _onFinishGesture = onFinishGesture;
76 | _updateView = updateView;
77 | _pathWidth = pathWidth;
78 | _initPoint();
79 | }
80 |
81 | void setPointValues() {
82 | try {
83 | Size size = globalKey.currentContext?.size ?? Size.zero;
84 | double pointWidth = _getPointWidth(size.width);
85 | List pointCenter = [];
86 | for (int x = 1; x <= 3; x++) {
87 | for (int y = 1; y <= 3; y++) {
88 | Offset center = Offset((y - 1) * pointWidth + pointWidth / 2,
89 | (x - 1) * pointWidth + pointWidth / 2);
90 | pointCenter.add(center);
91 | }
92 | }
93 | for (int index = 0; index < pointCenter.length; index++) {
94 | _points[index].centerPoint = pointCenter[index];
95 | }
96 | } catch (_) {}
97 | }
98 | }
99 |
100 | extension Tap on GestureViewController {
101 | void onPanDown(DragDownDetails e) {
102 | for (var item in _points) {
103 | if (item.containPoint(e.localPosition)) {
104 | item.selected = true;
105 | _firstPoint = e.localPosition;
106 | _pathPoint.add(item.centerPoint);
107 | _updateView?.call();
108 | _result.add(item.index);
109 | break;
110 | }
111 | }
112 | }
113 |
114 | void onPanUpdate(DragUpdateDetails e) {
115 | if (_firstPoint == null) return;
116 | _movePoint = e.localPosition;
117 | for (var item in _points) {
118 | if (item.containPoint(e.localPosition)) {
119 | if (!item.selected) {
120 | item.selected = true;
121 | _pathPoint.add(item.centerPoint);
122 | _result.add(item.index);
123 | }
124 | break;
125 | }
126 | }
127 | _updateView?.call();
128 | }
129 |
130 | void onPanEnd(DragEndDetails e) {
131 | _firstPoint = null;
132 | _movePoint = null;
133 |
134 | _onFinishGesture?.call(_result);
135 | _result.clear();
136 | for (var element in _points) {
137 | element.selected = false;
138 | }
139 | _pathPoint.clear();
140 | _updateView?.call();
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/lib/widget/gesture_password/src/gesture_view_model.dart:
--------------------------------------------------------------------------------
1 |
2 | import 'dart:math';
3 | import 'package:flutter/material.dart';
4 |
5 | class GesturePasswordPointModel {
6 | Offset centerPoint = Offset.zero;
7 | bool selected = false;
8 | double frameRadius;
9 | double pointRadius;
10 | Color color = Colors.grey;
11 | Color highlightColor = Colors.blue;
12 | Color pathColor = Colors.blue;
13 | int index = 0;
14 |
15 | GesturePasswordPointModel({
16 | this.index = 0,
17 | this.centerPoint = Offset.zero,
18 | this.frameRadius = 0.0,
19 | this.pointRadius = 0.0,
20 | this.selected = false,
21 | this.color = Colors.grey,
22 | this.highlightColor = Colors.blue,
23 | this.pathColor = Colors.blue,
24 | });
25 |
26 |
27 | Color get pointColor{
28 | return selected ? highlightColor : color;
29 | }
30 | Color get frameColor => selected ? highlightColor : color;
31 |
32 | bool containPoint(Offset offset){
33 | return distanceTo(offset, centerPoint) <= frameRadius;
34 | }
35 |
36 |
37 | double distanceTo(Offset f1, Offset f2){
38 | var dx= f1.dx - f2.dx;
39 | var dy= f1.dy - f2.dy;
40 | return sqrt(dx * dx + dy * dy);
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/lib/widget/gesture_password/src/gesture_view_path.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 |
4 | class GestureViewPathWidget extends StatelessWidget {
5 | final Function(DragDownDetails)? onPanDown;
6 | final Function(DragUpdateDetails)? onPanUpdate;
7 | final Function(DragEndDetails)? onPanEnd;
8 | final List points;
9 | final Color color;
10 | final double pathWidth;
11 |
12 | const GestureViewPathWidget({
13 | super.key,
14 | required this.points,
15 | this.onPanDown,
16 | this.onPanUpdate,
17 | this.onPanEnd,
18 | this.color = Colors.blue,
19 | this.pathWidth = 4,
20 | });
21 |
22 | @override
23 | Widget build(BuildContext context) {
24 | return GestureDetector(
25 | behavior: HitTestBehavior.opaque,
26 | onPanDown: _onPanDown,
27 | onPanUpdate: _onPanUpdate,
28 | onPanEnd: _onPanEnd,
29 | child: CustomPaint(
30 | size: const Size(double.infinity, double.infinity),
31 | painter: _PathPainter(points, color: color, pathWidth: pathWidth),
32 | ),
33 | );
34 | }
35 |
36 | _onPanDown(DragDownDetails e) {
37 | onPanDown?.call(e);
38 | }
39 |
40 | _onPanUpdate(DragUpdateDetails e) {
41 | onPanUpdate?.call(e);
42 | }
43 |
44 | _onPanEnd(DragEndDetails e) {
45 | onPanEnd?.call(e);
46 | }
47 | }
48 |
49 | class _PathPainter extends CustomPainter {
50 | final List points;
51 | final Color color;
52 | final double pathWidth;
53 |
54 | final pathPainter = Paint()
55 | ..style = PaintingStyle.stroke
56 | ..strokeCap = StrokeCap.round;
57 |
58 | _PathPainter(
59 | this.points, {
60 | this.color = Colors.blue,
61 | this.pathWidth = 4,
62 | }){
63 | pathPainter.color = color;
64 | pathPainter.strokeWidth = pathWidth;
65 | }
66 |
67 | @override
68 | void paint(Canvas canvas, Size size) {
69 | if (points.length > 1) {
70 | for (int index = 0; index < points.length - 1; index++) {
71 | Offset p1 = points[index];
72 | Offset p2 = points[index + 1];
73 | canvas.drawLine(p1, p2, pathPainter);
74 | }
75 | }
76 | }
77 |
78 | @override
79 | bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
80 | }
81 |
--------------------------------------------------------------------------------
/lib/widget/gesture_password/src/gesture_view_point.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'gesture_view_model.dart';
3 |
4 | class GestureDotsPanelWidget extends StatelessWidget {
5 | final List points;
6 |
7 | const GestureDotsPanelWidget({super.key, required this.points});
8 |
9 | @override
10 | Widget build(BuildContext context) {
11 | return GridView.builder(
12 | gridDelegate:
13 | const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3),
14 | itemCount: points.length,
15 | physics: const NeverScrollableScrollPhysics(),
16 | itemBuilder: (context, index) {
17 | return GestureViewPoint(
18 | data: points[index],
19 | );
20 | },
21 | );
22 | }
23 | }
24 |
25 | class GestureViewPoint extends StatelessWidget {
26 | final GesturePasswordPointModel data;
27 |
28 | const GestureViewPoint({super.key, required this.data});
29 |
30 | @override
31 | Widget build(BuildContext context) {
32 | return Container(
33 | width: double.infinity,
34 | height: double.infinity,
35 | // color: random,
36 | alignment: Alignment.center,
37 | child: Stack(
38 | children: [
39 | CustomPaint(
40 | painter: LinePainter(
41 | radius: data.frameRadius,
42 | style: PaintingStyle.stroke,
43 | color: data.frameColor,
44 | ),
45 | ),
46 | CustomPaint(
47 | painter: LinePainter(
48 | radius: data.pointRadius,
49 | color: data.pointColor,
50 | ),
51 | ),
52 | ],
53 | ),
54 | );
55 | }
56 | }
57 |
58 | class LinePainter extends CustomPainter {
59 | final double radius;
60 | final PaintingStyle style;
61 | final Color color;
62 |
63 | LinePainter({
64 | required this.radius,
65 | this.style = PaintingStyle.fill,
66 | this.color = Colors.grey,
67 | });
68 |
69 | final Paint _paint = Paint()
70 | ..color = Colors.grey
71 | ..strokeCap = StrokeCap.square
72 | ..isAntiAlias = true
73 | ..strokeWidth = 3.0;
74 |
75 | @override
76 | void paint(Canvas canvas, Size size) {
77 | _paint.style = style;
78 | _paint.color = color;
79 | canvas.drawCircle(Offset.zero, radius, _paint);
80 | }
81 |
82 | @override
83 | bool shouldRepaint(CustomPainter oldDelegate) => true;
84 | }
85 |
86 |
--------------------------------------------------------------------------------
/lib/widget/gradient_text_demo_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart';
2 | import 'package:flutter/material.dart';
3 |
4 | class GradientTextDemoPage extends StatelessWidget {
5 | const GradientTextDemoPage({super.key});
6 |
7 | @override
8 | Widget build(BuildContext context) {
9 | return Scaffold(
10 | appBar: AppBar(
11 | title: const Text("GradientTextDemoPage"),
12 | ),
13 | body: Center(
14 | child: Stack(
15 | alignment: Alignment.center,
16 | children: [
17 | if (kIsWeb)
18 | const Align(
19 | alignment: Alignment.topCenter,
20 | child: Padding(
21 | padding: EdgeInsets.only(top: 30),
22 | child: Text("当前效果不支持 Web ,请在 App 查看"),
23 | ),
24 | ),
25 | Text(
26 | '8',
27 | style: TextStyle(
28 | fontSize: 100,
29 |
30 | /// 2.10 下因为有 shader (Gradient) , web 下会用 canvas
31 | ///编译文本,此时会有 _applySpanStyleToCanvas 时 setUpPaint 的 Rect 为 nul 的问题
32 | ///所以添加 fontFeatures 可以在底层渲染时切换回 p+span 标签
33 | ///但是目前 p+span 不支持 foreground 的 Paint
34 | fontFeatures:
35 | kIsWeb ? [const FontFeature.enable("tnum")] : null,
36 | foreground: Paint()
37 | ..style = PaintingStyle.fill
38 | ..strokeWidth = 3
39 | ..shader = const LinearGradient(
40 | begin: Alignment.bottomLeft,
41 | end: Alignment.topRight,
42 | colors: [Colors.yellow, Colors.black])
43 | .createShader(const Rect.fromLTWH(0, 0, 200, 100))),
44 | ),
45 | Text(
46 | '8',
47 | style: TextStyle(
48 | fontSize: 100,
49 |
50 | /// 2.10 下因为有 shader (Gradient) , web 下会用 canvas
51 | ///编译文本,此时会有 _applySpanStyleToCanvas 时 setUpPaint 的 Rect 为 nul 的问题
52 | ///所以添加 fontFeatures 可以在底层渲染时切换回 p+span 标签
53 | ///但是目前 p+span 不支持 foreground 的 Paint
54 | fontFeatures:
55 | kIsWeb ? [const FontFeature.enable("tnum")] : null,
56 | foreground: Paint()
57 | ..style = PaintingStyle.stroke
58 | ..strokeWidth = 2
59 | ..shader = const LinearGradient(
60 | begin: Alignment.bottomLeft,
61 | end: Alignment.topRight,
62 | colors: [Colors.limeAccent, Colors.cyanAccent])
63 | .createShader(const Rect.fromLTWH(0, 0, 200, 100))),
64 | ),
65 | ],
66 | ),
67 | ),
68 | );
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/lib/widget/honor_demo_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | ///共性元素动画
4 | class HonorDemoPage extends StatelessWidget {
5 | const HonorDemoPage({super.key});
6 |
7 | @override
8 | Widget build(BuildContext context) {
9 | return Scaffold(
10 | appBar: AppBar(
11 | title: const Text("HonorDemoPage"),
12 | ),
13 | body: Center(
14 | child: InkWell(
15 | onTap: () {
16 | Navigator.of(context).push(MaterialPageRoute(
17 | builder: (context) {
18 | return const HonorPage();
19 | },
20 | fullscreenDialog: true));
21 | },
22 |
23 | /// Hero tag 共享
24 | child: Hero(
25 | tag: "image",
26 | child: Image.asset(
27 | "static/gsy_cat.png",
28 | fit: BoxFit.cover,
29 | width: 100,
30 | height: 100,
31 | ),
32 | ),
33 | ),
34 | ),
35 | );
36 | }
37 | }
38 |
39 | class HonorPage extends StatelessWidget {
40 | const HonorPage({super.key});
41 |
42 | @override
43 | Widget build(BuildContext context) {
44 | return Scaffold(
45 | backgroundColor: Colors.transparent,
46 | body: InkWell(
47 | onTap: () {
48 | Navigator.of(context).pop();
49 | },
50 | child: Container(
51 | alignment: Alignment.center,
52 | child: Hero(
53 | tag: "image",
54 | child: Image.asset(
55 | "static/gsy_cat.png",
56 | fit: BoxFit.cover,
57 | width: MediaQuery.sizeOf(context).width,
58 | height: MediaQuery.sizeOf(context).width,
59 | ),
60 | ),
61 | ),
62 | ),
63 | );
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/lib/widget/juejin_3d_logo_demo_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:zflutter/zflutter.dart';
3 |
4 | class JueJin3DLogoDemoPage extends StatefulWidget {
5 | const JueJin3DLogoDemoPage({super.key});
6 |
7 | @override
8 | State createState() => _JueJin3DLogoDemoPageState();
9 | }
10 |
11 | class _JueJin3DLogoDemoPageState extends State {
12 | @override
13 | Widget build(BuildContext context) {
14 | return Scaffold(
15 | appBar: AppBar(
16 | title: const Text("JueJin3DLogoDemoPage"),
17 | ),
18 | body: Center(
19 | child: ZDragDetector(builder: (context, controller) {
20 | return ZIllustration(
21 | zoom: 10,
22 | children: [
23 | ZPositioned(
24 | rotate: controller.rotate,
25 | child: ZGroup(
26 | children: [
27 | ZPositioned(
28 | translate: const ZVector(-17.5865, -23.2854 / 2, 0),
29 | child: ZShape(
30 | color: const Color(0xFF1E80FF),
31 | stroke: 2,
32 | fill: true,
33 | path: [
34 | const ZMove.vector(ZVector(17.5872, 6.77268, 0)),
35 | ZLine.vector(const ZVector(21.823, 3.40505, 0)),
36 | ZLine.vector(const ZVector(17.58723, 0.00748237, 0)),
37 | ZLine.vector(const ZVector(17.5835, 0, 0)),
38 | ZLine.vector(const ZVector(13.3552, 3.39757, 0)),
39 | ZLine.vector(const ZVector(17.5835, 6.76894, 0)),
40 | ZLine.vector(const ZVector(17.5872, 6.77268, 0)),
41 | ],
42 | ),
43 | ),
44 | ZPositioned(
45 | translate: const ZVector(-17.5865, -23.2854 / 2, 0),
46 | child: ZShape(
47 | color: const Color(0xFF1E80FF),
48 | stroke: 2,
49 | fill: true,
50 | path: [
51 | const ZMove.vector(ZVector(17.5865, 17.3955, 0)),
52 | ZLine.vector(const ZVector(28.5163, 8.77432, 0)),
53 | ZLine.vector(const ZVector(25.5528, 6.39453, 0)),
54 | ZLine.vector(const ZVector(17.5902, 12.6808, 0)),
55 | ZLine.vector(const ZVector(17.5865, 12.6808, 0)),
56 | ZLine.vector(const ZVector(9.62018, 6.40201, 0)),
57 | ZLine.vector(const ZVector(6.6604, 8.78181, 0)),
58 | ZLine.vector(const ZVector(17.5828, 17.39928, 0)),
59 | ZLine.vector(const ZVector(17.5865, 17.3955, 0)),
60 | ],
61 | ),
62 | ),
63 | ZPositioned(
64 | translate: const ZVector(-17.5865, -23.2854 / 2, 0),
65 | child: ZShape(
66 | color: const Color(0xFF1E80FF),
67 | stroke: 2,
68 | fill: true,
69 | path: [
70 | const ZMove.vector(ZVector(17.5865, 23.2854, 0)),
71 | ZLine.vector(const ZVector(17.5828, 23.2891, 0)),
72 | ZLine.vector(const ZVector(2.95977, 11.7531, 0)),
73 | ZLine.vector(const ZVector(0, 14.1291, 0)),
74 | ZLine.vector(const ZVector(0.284376, 14.3574, 0)),
75 | ZLine.vector(const ZVector(17.5865, 28, 0)),
76 | ZLine.vector(const ZVector(28.5238, 19.3752, 0)),
77 | ZLine.vector(const ZVector(35.1768, 14.12542, 0)),
78 | ZLine.vector(const ZVector(32.2133, 11.7456, 0)),
79 | ZLine.vector(const ZVector(17.5865, 23.2854, 0)),
80 | ],
81 | ),
82 | ),
83 | ],
84 | ))
85 | ],
86 | );
87 | }),
88 | ),
89 | );
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/lib/widget/keyboard_demo_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | ///键盘相关Demo
4 | ///键盘是否弹起等
5 | class KeyBoardDemoPage extends StatefulWidget {
6 | const KeyBoardDemoPage({super.key});
7 |
8 | @override
9 | _KeyBoardDemoPageState createState() => _KeyBoardDemoPageState();
10 | }
11 |
12 | class _KeyBoardDemoPageState extends State {
13 | bool isKeyboardShowing = false;
14 |
15 | final FocusNode _focusNode = FocusNode();
16 |
17 | @override
18 | Widget build(BuildContext context) {
19 | ///必须嵌套在外层
20 | return KeyboardDetector(
21 | keyboardShowCallback: (isKeyboardShowing) {
22 | ///当前键盘是否可见
23 | setState(() {
24 | this.isKeyboardShowing = isKeyboardShowing;
25 | });
26 | },
27 | content: Scaffold(
28 | appBar: AppBar(
29 | title: const Text("KeyBoardDemoPage"),
30 | ),
31 | body: GestureDetector(
32 | ///透明可以触摸
33 | behavior: HitTestBehavior.translucent,
34 | onTap: () {
35 | /// 触摸收起键盘
36 | FocusScope.of(context).requestFocus(FocusNode());
37 | },
38 | child: Column(
39 | mainAxisSize: MainAxisSize.max,
40 | crossAxisAlignment: CrossAxisAlignment.center,
41 | children: [
42 | Expanded(
43 | flex: 2,
44 | child: Container(
45 | alignment: Alignment.center,
46 | child: Text(
47 | isKeyboardShowing ? "键盘弹起" : "键盘未弹起",
48 | style: TextStyle(
49 | color: isKeyboardShowing
50 | ? Colors.redAccent
51 | : Colors.greenAccent),
52 | ),
53 | ),
54 | ),
55 | Expanded(
56 | child: Center(
57 | child: TextButton(
58 | onPressed: () {
59 | if (!isKeyboardShowing) {
60 | /// 触摸收起键盘
61 | FocusScope.of(context).requestFocus(_focusNode);
62 | }
63 | },
64 | child: const Text("弹出键盘"),
65 | ),
66 | ),
67 | ),
68 | Expanded(
69 | flex: 2,
70 | child: Container(
71 | margin: const EdgeInsets.symmetric(horizontal: 10),
72 | child: TextField(
73 | focusNode: _focusNode,
74 | maxLines: 7,
75 | minLines: 1,
76 | decoration:
77 | const InputDecoration(border: OutlineInputBorder()),
78 | ),
79 | ),
80 | )
81 | ],
82 | ),
83 | ),
84 | ),
85 | );
86 | }
87 | }
88 |
89 | typedef KeyboardShowCallback = void Function(bool isKeyboardShowing);
90 |
91 | ///监听键盘弹出收起
92 | class KeyboardDetector extends StatefulWidget {
93 | final KeyboardShowCallback? keyboardShowCallback;
94 |
95 | final Widget content;
96 |
97 | const KeyboardDetector({super.key, this.keyboardShowCallback, required this.content});
98 |
99 | @override
100 | _KeyboardDetectorState createState() => _KeyboardDetectorState();
101 | }
102 |
103 | class _KeyboardDetectorState extends State
104 | with WidgetsBindingObserver {
105 | @override
106 | void initState() {
107 | WidgetsBinding.instance.addObserver(this);
108 | super.initState();
109 | }
110 |
111 | @override
112 | void didChangeMetrics() {
113 | super.didChangeMetrics();
114 | WidgetsBinding.instance.addPostFrameCallback((_) {
115 | setState(() {
116 | widget.keyboardShowCallback
117 | ?.call(MediaQuery.viewInsetsOf(context).bottom > 0);
118 | });
119 | });
120 | }
121 |
122 | @override
123 | void dispose() {
124 | WidgetsBinding.instance.removeObserver(this);
125 | super.dispose();
126 | }
127 |
128 | @override
129 | Widget build(BuildContext context) {
130 | return widget.content;
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/lib/widget/link_sliver/link_sliver_demo_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:gsy_flutter_demo/widget/link_sliver/link_flexible_space_bar.dart';
3 |
4 | import 'link_sliver_header.dart';
5 |
6 | class LinkSliverDemoPage extends StatefulWidget {
7 | const LinkSliverDemoPage({super.key});
8 |
9 | @override
10 | _LinkSliverDemoPageState createState() => _LinkSliverDemoPageState();
11 | }
12 |
13 | class _LinkSliverDemoPageState extends State {
14 | renderBottomItem() {
15 | return Expanded(
16 | child: Container(
17 | alignment: Alignment.centerLeft,
18 | child: const Center(
19 | child: Text(
20 | "FFFF",
21 | style: TextStyle(fontSize: 18, color: Colors.white),
22 | ),
23 | ),
24 | ),
25 | );
26 | }
27 |
28 | @override
29 | Widget build(BuildContext context) {
30 | return Scaffold(
31 | body: NestedScrollView(
32 | headerSliverBuilder: (context, innerBoxIsScrolled) {
33 | return [
34 | SliverAppBar(
35 | automaticallyImplyLeading: false,
36 | leading: Container(),
37 | expandedHeight: 260.0,
38 | flexibleSpace: LinkFlexibleSpaceBar(
39 | centerTitle: false,
40 | title: Container(
41 | margin: const EdgeInsets.only(left: 20, top: 30, bottom: 20),
42 | child: const Text("GSY"),
43 | ),
44 | image: "static/gsy_cat.png",
45 | bottom: List.generate(4, (index) {
46 | return renderBottomItem();
47 | }),
48 | titlePadding: const EdgeInsets.all(0),
49 | ),
50 | pinned: true,
51 | actions: [
52 | IconButton(
53 | icon: const Icon(Icons.settings_overscan),
54 | tooltip: 'Add new entry',
55 | onPressed: () {},
56 | ),
57 | IconButton(
58 | icon: const Icon(Icons.settings),
59 | tooltip: 'Add new entry',
60 | onPressed: () {},
61 | ),
62 | ],
63 | ),
64 | ];
65 | },
66 | body: MediaQuery.removePadding(
67 | removeTop: true,
68 | context: context,
69 | child: CustomScrollView(
70 | ///回弹效果
71 | physics: const BouncingScrollPhysics(
72 | parent: AlwaysScrollableScrollPhysics()),
73 | slivers: [
74 | const LinkSliverHeader(
75 | initLayoutExtent: 60,
76 | containerExtent: 120,
77 | triggerPullDistance: 120,
78 | pinned: false,
79 | ),
80 |
81 | ///列表区域
82 | SliverSafeArea(
83 | sliver: SliverList(
84 | ///代理显示
85 | delegate: SliverChildBuilderDelegate(
86 | (BuildContext context, int index) {
87 | return Card(
88 | child: Container(
89 | height: 60,
90 | alignment: Alignment.centerLeft,
91 | child: Text("Item $index"),
92 | ),
93 | );
94 | },
95 | childCount: 100,
96 | ),
97 | ),
98 | ),
99 | ],
100 | ),
101 | ),
102 | ),
103 | );
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/lib/widget/list_anim/header_appbar.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class HeaderAppBar extends StatelessWidget {
4 | final int alphaBg;
5 | final bool showStickItem;
6 |
7 | const HeaderAppBar({super.key, this.alphaBg = 0, this.showStickItem = false});
8 |
9 | @override
10 | Widget build(BuildContext context) {
11 | double statusBarHeight = MediaQueryData.fromView(
12 | WidgetsBinding.instance.platformDispatcher.views.first)
13 | .padding
14 | .top;
15 |
16 | double reactHeight = 30;
17 |
18 | ///总高度 = appbar 高度 + statusBar 高度 + 底部停靠区域高度
19 | double containerHeight = kToolbarHeight + statusBarHeight + reactHeight;
20 |
21 | var color = Theme.of(context).primaryColor.withAlpha(alphaBg);
22 |
23 | return Material(
24 | color: Colors.transparent,
25 | child: Container(
26 | alignment: Alignment.centerLeft,
27 | height: containerHeight,
28 | child: Column(
29 | mainAxisSize: MainAxisSize.max,
30 | children: [
31 | ///撑满状态栏颜色
32 | Container(
33 | height: statusBarHeight,
34 | color: color,
35 | ),
36 | Container(
37 | color: color,
38 | height: kToolbarHeight,
39 | child: Row(
40 | mainAxisAlignment: MainAxisAlignment.center,
41 | children: [
42 | Container(
43 | width: 36,
44 | height: 36,
45 | alignment: Alignment.center,
46 | margin: const EdgeInsets.only(right: 10, left: 10),
47 | decoration: BoxDecoration(
48 | color: Colors.white.withAlpha(125),
49 | borderRadius: const BorderRadius.all(Radius.circular(18))),
50 | child: InkWell(
51 | onTap: () {
52 | Navigator.of(context).pop();
53 | },
54 | child: const Icon(
55 | Icons.arrow_back_ios,
56 | color: Colors.white,
57 | ),
58 | ),
59 | ),
60 | Expanded(
61 | child: Container(
62 | height: kToolbarHeight - 15,
63 | margin: const EdgeInsets.only(right: 20, left: 20),
64 | decoration: BoxDecoration(
65 | color: Colors.white.withAlpha(125),
66 | borderRadius: const BorderRadius.all(Radius.circular(10))),
67 | ),
68 | ),
69 | ],
70 | ),
71 | ),
72 | showStickItem
73 | ? Container(
74 | alignment: Alignment.centerLeft,
75 | width: MediaQuery.sizeOf(context).width,
76 | padding: const EdgeInsets.only(left: 10),
77 | height: reactHeight,
78 | color: Colors.amber,
79 | child: const Row(
80 | children: [
81 | Icon(Icons.ac_unit, color: Colors.white, size: 13),
82 | SizedBox(
83 | width: 10,
84 | ),
85 | Text(
86 | "StickText",
87 | style: TextStyle(color: Colors.white),
88 | ),
89 | ],
90 | ),
91 | )
92 | : Container()
93 | ],
94 | ),
95 | ),
96 | );
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/lib/widget/matrix_custom_painter_page.dart:
--------------------------------------------------------------------------------
1 | import 'dart:ui' as ui;
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:matrix_gesture_detector/matrix_gesture_detector.dart';
5 |
6 | class MatrixCustomPainterDemo extends StatefulWidget {
7 | const MatrixCustomPainterDemo({super.key});
8 |
9 | @override
10 | _MatrixCustomPainterDemoState createState() => _MatrixCustomPainterDemoState();
11 | }
12 |
13 | class _MatrixCustomPainterDemoState extends State {
14 | Matrix4 matrix = Matrix4.identity();
15 | ValueNotifier? notifier;
16 |
17 | @override
18 | void initState() {
19 | super.initState();
20 | notifier = ValueNotifier(matrix);
21 | }
22 |
23 | @override
24 | Widget build(BuildContext context) {
25 | return Scaffold(
26 | appBar: AppBar(
27 | title: const Text('MatrixCustomPainterDemo Demo'),
28 | ),
29 | body: MatrixGestureDetector(
30 | onMatrixUpdate: (m, tm, sm, rm) => notifier!.value = m,
31 | child: CustomPaint(
32 | foregroundPainter: TestCustomPainter(context, notifier),
33 | child: Container(
34 | color: Colors.blueGrey,
35 | ),
36 | ),
37 | ),
38 | );
39 | }
40 | }
41 |
42 | class TestCustomPainter extends CustomPainter {
43 | ValueNotifier? notifier;
44 | Paint shapesPaint = Paint();
45 | Paint backgroundPaint = Paint();
46 | late Path path;
47 | late ui.Paragraph paragraph;
48 | Size currentSize = Size.zero;
49 |
50 | TestCustomPainter(BuildContext context, this.notifier)
51 | : super(repaint: notifier) {
52 | var _ = MediaQuery.sizeOf(context).shortestSide / 40;
53 | shapesPaint.strokeWidth = _;
54 | shapesPaint.style = PaintingStyle.stroke;
55 | ui.ParagraphBuilder builder = ui.ParagraphBuilder(ui.ParagraphStyle(
56 | textAlign: TextAlign.center,
57 | fontSize: Theme.of(context).textTheme.displaySmall!.fontSize!,
58 | ))
59 | ..pushStyle(ui.TextStyle(
60 | color: Colors.white,
61 | shadows: [
62 | ui.Shadow(
63 | color: Colors.grey, blurRadius: _ / 8, offset: Offset(0, _ / 4)),
64 | ],
65 | ))
66 | ..addText('use your two fingers to translate / rotate / scale ...')
67 | ..pop();
68 | paragraph = builder.build();
69 | }
70 |
71 | @override
72 | void paint(Canvas canvas, Size size) {
73 | if (size != currentSize) {
74 | currentSize = size;
75 | RRect rr = RRect.fromLTRBR(size.width * 0.2, 100, size.width * 0.8,
76 | 100 + size.height / 3, Radius.circular(shapesPaint.strokeWidth * 2));
77 | Offset offset = Offset(0, 100 + size.height / 3);
78 | path = Path();
79 | for (int i = 0; i < 3; i++) {
80 | path.addRRect(rr.shift(offset * i.toDouble()));
81 | }
82 | backgroundPaint.shader = const LinearGradient(
83 | colors: [
84 | Color(0xff000044),
85 | Color(0xff000022),
86 | ],
87 | stops: [0.5, 1.0],
88 | begin: Alignment.topCenter,
89 | end: Alignment.bottomCenter,
90 | ).createShader(Offset.zero & size);
91 | }
92 |
93 | canvas.drawPaint(backgroundPaint);
94 |
95 | shapesPaint.color = const Color(0xff880000);
96 | canvas.drawPath(path, shapesPaint);
97 |
98 | shapesPaint.color = const Color(0xffbb6600);
99 | Matrix4 inverted = Matrix4.zero();
100 | inverted.copyInverse(notifier!.value);
101 | canvas.save();
102 | canvas.transform(inverted.storage);
103 | canvas.drawPath(path, shapesPaint);
104 | canvas.restore();
105 |
106 | shapesPaint.color = const Color(0xff008800);
107 | canvas.drawPath(path.transform(notifier!.value.storage), shapesPaint);
108 |
109 | paragraph.layout(ui.ParagraphConstraints(width: size.width - 64));
110 | canvas.drawParagraph(paragraph, Offset(32, size.height * 0.3));
111 | }
112 |
113 | @override
114 | bool shouldRepaint(CustomPainter oldDelegate) => true;
115 | }
--------------------------------------------------------------------------------
/lib/widget/overflow_image_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | /// 圆角效果处理实现
4 | class OverflowImagePage extends StatelessWidget {
5 | const OverflowImagePage({super.key});
6 |
7 | @override
8 | Widget build(BuildContext context) {
9 | return Scaffold(
10 | appBar: AppBar(
11 | title: const Text("OverflowImagePage"),
12 | ),
13 | body: ListView.builder(
14 | physics: const ClampingScrollPhysics(),
15 | itemBuilder: (context, index) {
16 | ///第二个Item
17 | if (index == 1) {
18 | return Container(
19 | color: Colors.blue,
20 | height: MediaQuery.sizeOf(context).height,
21 | );
22 | }
23 |
24 | ///广告图 Item
25 | return SizedBox(
26 | height: 100,
27 | child: OverflowBox(
28 | alignment: Alignment.center,
29 | maxHeight: MediaQuery.sizeOf(context).height,
30 | child: Image(
31 | width: MediaQuery.sizeOf(context).width,
32 | height: MediaQuery.sizeOf(context).width * 220 / 247,
33 | image: const AssetImage("static/gsy_cat.png"),
34 | fit: BoxFit.fill,
35 | )),
36 | );
37 | },
38 | itemCount: 2,
39 | ),
40 | );
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/lib/widget/particle/particle_model.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math';
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:simple_animations/simple_animations.dart';
5 | import 'package:supercharged/supercharged.dart';
6 |
7 | enum ParticleOffsetProps { x, y }
8 |
9 | class ParticleModel {
10 | late MovieTween tween;
11 | late double size;
12 | late Duration duration;
13 | late Duration startTime;
14 | Random random;
15 |
16 | ParticleModel(this.random) {
17 | _restart();
18 | _shuffle();
19 | }
20 |
21 | _restart() {
22 | final startPosition = Offset(-0.2 + 1.4 * random.nextDouble(), 1.2);
23 | final endPosition = Offset(-0.2 + 1.4 * random.nextDouble(), -0.2);
24 |
25 | tween = MovieTween()
26 | ..tween(ParticleOffsetProps.x, startPosition.dx.tweenTo(endPosition.dx),
27 | duration: const Duration(milliseconds: 20))
28 | ..tween(ParticleOffsetProps.y, startPosition.dy.tweenTo(endPosition.dy),
29 | duration: const Duration(milliseconds: 20));
30 |
31 | duration = 3000.milliseconds + random.nextInt(6000).milliseconds;
32 | startTime = DateTime.now().duration();
33 | size = 0.2 + random.nextDouble() * 0.4;
34 | }
35 |
36 | void _shuffle() {
37 | startTime -= (random.nextDouble() * duration.inMilliseconds)
38 | .round()
39 | .milliseconds;
40 | }
41 |
42 | checkIfParticleNeedsToBeRestarted() {
43 | if (progress() == 1.0) {
44 | _restart();
45 | }
46 | }
47 |
48 | double progress() {
49 | return ((DateTime.now().duration() - startTime) / duration)
50 | .clamp(0.0, 1.0)
51 | .toDouble();
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/lib/widget/particle/particle_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:gsy_flutter_demo/widget/particle/particle_widget.dart';
3 | import 'package:simple_animations/simple_animations.dart';
4 | import 'package:supercharged/supercharged.dart';
5 |
6 | enum _ColorTween { color1, color2 }
7 |
8 | class ParticlePage extends StatelessWidget {
9 | const ParticlePage({super.key});
10 |
11 | @override
12 | Widget build(BuildContext context) {
13 | return Scaffold(
14 | appBar: AppBar(
15 | title: const Text("ParticlePage"),
16 | ),
17 | backgroundColor: Colors.black,
18 | body: const Stack(children: [
19 | Positioned.fill(child: AnimatedBackground()),
20 | Positioned.fill(child: ParticlesWidget(30)),
21 | Positioned.fill(
22 | child: Center(
23 | child: Text(
24 | "GSY Flutter Demo",
25 | style: TextStyle(
26 | fontSize: 30,
27 | fontWeight: FontWeight.bold,
28 | color: Colors.white),
29 | ),
30 | ),
31 | ),
32 | ]),
33 | );
34 | }
35 | }
36 |
37 | class AnimatedBackground extends StatelessWidget {
38 | const AnimatedBackground({super.key});
39 |
40 | @override
41 | Widget build(BuildContext context) {
42 | final tween = MovieTween()
43 | ..tween(
44 | _ColorTween.color1,
45 | const Color(0xffD38312).tweenTo(Colors.lightBlue.shade900),
46 | duration: 3.seconds,
47 | )
48 | ..tween(
49 | _ColorTween.color2,
50 | const Color(0xffA83279).tweenTo(Colors.blue.shade600),
51 | duration: 3.seconds,
52 | );
53 |
54 | return MirrorAnimationBuilder(
55 | tween: tween,
56 | duration: tween.duration,
57 | builder: (context, value, child) {
58 | return Container(
59 | decoration: BoxDecoration(
60 | gradient: LinearGradient(
61 | begin: Alignment.topCenter,
62 | end: Alignment.bottomCenter,
63 | colors: [
64 | value.get(_ColorTween.color1),
65 | value.get(_ColorTween.color2)
66 | ])),
67 | );
68 | },
69 | );
70 | }
71 | }
--------------------------------------------------------------------------------
/lib/widget/particle/particle_painter.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:gsy_flutter_demo/widget/particle/particle_model.dart';
3 | import 'package:simple_animations/simple_animations.dart';
4 |
5 | class ParticlePainter extends CustomPainter {
6 | List particles;
7 |
8 | ParticlePainter(this.particles);
9 |
10 | @override
11 | void paint(Canvas canvas, Size size) {
12 | final paint = Paint()..color = Colors.white.withAlpha(50);
13 |
14 | for (var particle in particles) {
15 | final progress = particle.progress();
16 | final Movie animation =
17 | particle.tween.transform(progress);
18 | final position = Offset(
19 | animation.get(ParticleOffsetProps.x) * size.width,
20 | animation.get(ParticleOffsetProps.y) * size.height,
21 | );
22 | canvas.drawCircle(position, size.width * 0.2 * particle.size, paint);
23 | }
24 | }
25 |
26 | @override
27 | bool shouldRepaint(CustomPainter oldDelegate) => true;
28 | }
--------------------------------------------------------------------------------
/lib/widget/particle/particle_widget.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math';
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:gsy_flutter_demo/widget/particle/particle_model.dart';
5 | import 'package:gsy_flutter_demo/widget/particle/particle_painter.dart';
6 | import 'package:simple_animations/simple_animations.dart';
7 | import 'package:supercharged/supercharged.dart';
8 |
9 | class ParticlesWidget extends StatefulWidget {
10 | final int numberOfParticles;
11 |
12 | const ParticlesWidget(this.numberOfParticles, {super.key});
13 |
14 | @override
15 | _ParticlesWidgetState createState() => _ParticlesWidgetState();
16 | }
17 |
18 | class _ParticlesWidgetState extends State {
19 | final Random random = Random();
20 |
21 | final List particles = [];
22 |
23 | @override
24 | void initState() {
25 | widget.numberOfParticles.times(() => particles.add(ParticleModel(random)));
26 | super.initState();
27 | }
28 |
29 | @override
30 | Widget build(BuildContext context) {
31 | return LoopAnimationBuilder(
32 | duration: const Duration(seconds: 1),
33 | tween: ConstantTween(1),
34 | builder: (context, child, dynamic _) {
35 | _simulateParticles();
36 | return CustomPaint(
37 | painter: ParticlePainter(particles),
38 | );
39 | },
40 | );
41 | }
42 |
43 | _simulateParticles() {
44 | for (var particle in particles) {
45 | particle.checkIfParticleNeedsToBeRestarted();
46 | }
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/lib/widget/png_shadow_demo_page.dart:
--------------------------------------------------------------------------------
1 | import 'dart:ui';
2 |
3 | import 'package:flutter/material.dart';
4 |
5 | ///from https://pub.flutter-io.cn/packages/drop_shadow
6 | class PngShadowDemoPage extends StatefulWidget {
7 | const PngShadowDemoPage({super.key});
8 |
9 | @override
10 | State createState() => _PngShadowDemoPageState();
11 | }
12 |
13 | class _PngShadowDemoPageState extends State {
14 | @override
15 | Widget build(BuildContext context) {
16 | return Scaffold(
17 | appBar: AppBar(
18 | title: const Text("PngShadowDemoPage"),
19 | ),
20 | body: Container(
21 | alignment: Alignment.center,
22 | child: DropShadow(
23 | child: Image.asset(
24 | 'static/test_logo.png',
25 | width: 250,
26 | ),
27 | ),
28 | ),
29 | );
30 | }
31 | }
32 |
33 | class DropShadow extends StatelessWidget {
34 | final Widget child;
35 | final double blurRadius;
36 | final double borderRadius;
37 | final Offset offset;
38 | final double opacity;
39 | final double spread;
40 |
41 | const DropShadow({
42 | super.key,
43 | required this.child,
44 | this.blurRadius = 10.0,
45 | this.borderRadius = 0.0,
46 | this.offset = const Offset(0.0, 8.0),
47 | this.opacity = 1.0,
48 | this.spread = 1.0,
49 | });
50 |
51 | @override
52 | Widget build(BuildContext context) {
53 | double left = 0;
54 | double right = 0;
55 | double top = 0;
56 | double bottom = 0;
57 |
58 | left = (offset.dx.abs() + (blurRadius * 2)) * spread;
59 | right = (offset.dx + (blurRadius * 2)) * spread;
60 | top = (offset.dy.abs() + (blurRadius * 2)) * spread;
61 | bottom = (offset.dy + (blurRadius * 2)) * spread;
62 |
63 | /// [ClipRRect] to isolate [BackDropFilter] from other widgets
64 | return ClipRRect(
65 | child: Padding(
66 | /// Calculate Shadow's effect field
67 | padding: EdgeInsets.fromLTRB(left, top, right, bottom),
68 | child: Stack(
69 | children: [
70 | /// Arrange shadow position
71 | Transform.translate(
72 | offset: offset,
73 |
74 | /// Apply [BorderRadius] to the shadow
75 | child: ClipRRect(
76 | borderRadius: BorderRadius.circular(borderRadius),
77 |
78 | /// Apply [Opacity] to the shadow
79 | child: Opacity(
80 | opacity: opacity,
81 | child: child,
82 | ),
83 | ),
84 | ),
85 |
86 | /// Apply filter the whole [Stack] space
87 | Positioned.fill(
88 | /// Apply blur effect to the layer
89 | child: BackdropFilter(
90 | filter: ImageFilter.blur(
91 | sigmaX: blurRadius,
92 | sigmaY: blurRadius,
93 | ),
94 |
95 | /// Filter effect field
96 | child: Container(color: Colors.transparent),
97 | ),
98 | ),
99 |
100 | /// [Widget] itself with given [BorderRadius]
101 | ClipRRect(
102 | borderRadius: BorderRadius.circular(borderRadius),
103 | child: child,
104 | ),
105 | ],
106 | ),
107 | ),
108 | );
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/lib/widget/positioned_demo_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | ///Stack + Positioned例子
4 | class PositionedDemoPage extends StatelessWidget {
5 | const PositionedDemoPage({super.key});
6 |
7 | @override
8 | Widget build(BuildContext context) {
9 | return Scaffold(
10 | appBar: AppBar(
11 | title: const Text("PositionedDemoPage"),
12 | ),
13 | body: Container(
14 | width: MediaQuery.sizeOf(context).width,
15 | height: MediaQuery.sizeOf(context).height,
16 | margin: const EdgeInsets.all(15),
17 | child: Stack(
18 | children: [
19 | MaterialButton(
20 | onPressed: () {},
21 | color: Colors.blue,
22 | ),
23 | Positioned(
24 | left: MediaQuery.sizeOf(context).width / 2,
25 | child: MaterialButton(
26 | onPressed: () {},
27 | color: Colors.greenAccent,
28 | )),
29 | Positioned(
30 | left: MediaQuery.sizeOf(context).width / 5,
31 | top: MediaQuery.sizeOf(context).height / 4 * 3,
32 | child: MaterialButton(
33 | onPressed: () {},
34 | color: Colors.yellow,
35 | ),
36 | ),
37 | Positioned(
38 | left: MediaQuery.sizeOf(context).width / 2 - Theme.of(context).buttonTheme.minWidth / 2,
39 | top: MediaQuery.sizeOf(context).height / 2 -
40 | MediaQuery.paddingOf(context).top -
41 | kToolbarHeight,
42 | child: MaterialButton(
43 | onPressed: () {},
44 | color: Colors.redAccent,
45 | ),
46 | ),
47 | ],
48 | ),
49 | ),
50 | );
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/lib/widget/refrsh_demo_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | ///刷新演示
4 | ///比较粗略,没有做互斥等
5 | ///详细使用还请查看 https://github.com/CarGuo/GSYGithubAppFlutter
6 | class RefreshDemoPage extends StatefulWidget {
7 | const RefreshDemoPage({super.key});
8 |
9 | @override
10 | _RefreshDemoPageState createState() => _RefreshDemoPageState();
11 | }
12 |
13 | class _RefreshDemoPageState extends State {
14 | final int pageSize = 30;
15 |
16 | bool disposed = false;
17 |
18 | List dataList = [];
19 |
20 | final ScrollController _scrollController = ScrollController();
21 |
22 | final GlobalKey refreshKey = GlobalKey();
23 |
24 | Future onRefresh() async {
25 | await Future.delayed(const Duration(seconds: 2));
26 | dataList.clear();
27 | for (int i = 0; i < pageSize; i++) {
28 | dataList.add("refresh");
29 | }
30 | if(disposed) {
31 | return;
32 | }
33 | setState(() {});
34 | }
35 |
36 | Future loadMore() async {
37 | await Future.delayed(const Duration(seconds: 2));
38 | for (int i = 0; i < pageSize; i++) {
39 | dataList.add("loadmore");
40 | }
41 | if(disposed) {
42 | return;
43 | }
44 | setState(() {});
45 | }
46 |
47 | @override
48 | void initState() {
49 | super.initState();
50 | _scrollController.addListener(() {
51 | ///判断当前滑动位置是不是到达底部,触发加载更多回调
52 | if (_scrollController.position.pixels ==
53 | _scrollController.position.maxScrollExtent) {
54 | loadMore();
55 | }
56 | });
57 | Future.delayed(const Duration(seconds: 0), (){
58 | refreshKey.currentState!.show();
59 | });
60 | }
61 |
62 | @override
63 | void dispose() {
64 | disposed = true;
65 | super.dispose();
66 | }
67 |
68 | @override
69 | Widget build(BuildContext context) {
70 | return Scaffold(
71 | appBar: AppBar(
72 | title: const Text("RefreshDemoPage"),
73 | ),
74 | body: RefreshIndicator(
75 | ///GlobalKey,用户外部获取RefreshIndicator的State,做显示刷新
76 | key: refreshKey,
77 |
78 | ///下拉刷新触发,返回的是一个Future
79 | onRefresh: onRefresh,
80 | child: ListView.builder(
81 | ///保持ListView任何情况都能滚动,解决在RefreshIndicator的兼容问题。
82 | physics: const AlwaysScrollableScrollPhysics(),
83 |
84 | ///根据状态返回
85 | itemBuilder: (context, index) {
86 | if (index == dataList.length) {
87 | return Container(
88 | margin: const EdgeInsets.all(10),
89 | child: const Align(
90 | child: CircularProgressIndicator(),
91 | ),
92 | );
93 | }
94 | return Card(
95 | child: Container(
96 | height: 60,
97 | alignment: Alignment.centerLeft,
98 | child: Text("Item ${dataList[index]} $index"),
99 | ),
100 | );
101 | },
102 |
103 | ///根据状态返回数量
104 | itemCount: (dataList.length >= pageSize)
105 | ? dataList.length + 1
106 | : dataList.length,
107 |
108 | ///滑动监听
109 | controller: _scrollController,
110 | ),
111 | ),
112 | );
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/lib/widget/refrsh_demo_page2.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 | import 'package:flutter/material.dart';
3 |
4 | ///刷新演示2
5 | ///比较粗略,没有做互斥等
6 | ///详细使用还请查看 https://github.com/CarGuo/GSYGithubAppFlutter
7 | class RefreshDemoPage2 extends StatefulWidget {
8 | const RefreshDemoPage2({super.key});
9 |
10 | @override
11 | _RefreshDemoPageState2 createState() => _RefreshDemoPageState2();
12 | }
13 |
14 | class _RefreshDemoPageState2 extends State {
15 | final int pageSize = 30;
16 |
17 | bool disposed = false;
18 |
19 | List dataList = [];
20 |
21 | final ScrollController _scrollController = ScrollController();
22 |
23 | Future onRefresh() async {
24 | await Future.delayed(const Duration(seconds: 2));
25 | dataList.clear();
26 | for (int i = 0; i < pageSize; i++) {
27 | dataList.add("refresh");
28 | }
29 | if(disposed) {
30 | return;
31 | }
32 | setState(() {});
33 | }
34 |
35 | Future loadMore() async {
36 | await Future.delayed(const Duration(seconds: 2));
37 | for (int i = 0; i < pageSize; i++) {
38 | dataList.add("loadmore");
39 | }
40 | if(disposed) {
41 | return;
42 | }
43 | setState(() {});
44 | }
45 |
46 | @override
47 | void didChangeDependencies() {
48 | super.didChangeDependencies();
49 |
50 | ///直接触发下拉
51 | Future.delayed(const Duration(milliseconds: 500), () {
52 | _scrollController.animateTo(-141,
53 | duration: const Duration(milliseconds: 600), curve: Curves.linear);
54 | return true;
55 | });
56 | }
57 |
58 | @override
59 | void dispose() {
60 | disposed = true;
61 | super.dispose();
62 | }
63 |
64 | @override
65 | Widget build(BuildContext context) {
66 | return Scaffold(
67 | appBar: AppBar(
68 | title: const Text("RefreshDemoPage"),
69 | ),
70 | body: NotificationListener(
71 | onNotification: (ScrollNotification notification) {
72 | ///判断当前滑动位置是不是到达底部,触发加载更多回调
73 | if (notification is ScrollEndNotification) {
74 | if (_scrollController.position.pixels > 0 &&
75 | _scrollController.position.pixels ==
76 | _scrollController.position.maxScrollExtent) {
77 | loadMore();
78 | }
79 | }
80 | return false;
81 | },
82 | child: CustomScrollView(
83 | controller: _scrollController,
84 |
85 | ///回弹效果
86 | physics: const BouncingScrollPhysics(
87 | parent: AlwaysScrollableScrollPhysics()),
88 | slivers: [
89 | ///控制显示刷新的 CupertinoSliverRefreshControl
90 | CupertinoSliverRefreshControl(
91 | refreshIndicatorExtent: 100,
92 | refreshTriggerPullDistance: 140,
93 | onRefresh: onRefresh,
94 | ),
95 |
96 | ///列表区域
97 | SliverSafeArea(
98 | sliver: SliverList(
99 | ///代理显示
100 | delegate: SliverChildBuilderDelegate(
101 | (BuildContext context, int index) {
102 | if (index == dataList.length) {
103 | return Container(
104 | margin: const EdgeInsets.all(10),
105 | child: const Align(
106 | child: CircularProgressIndicator(),
107 | ),
108 | );
109 | }
110 | return Card(
111 | child: Container(
112 | height: 60,
113 | alignment: Alignment.centerLeft,
114 | child: Text("Item ${dataList[index]} $index"),
115 | ),
116 | );
117 | },
118 | childCount: (dataList.length >= pageSize)
119 | ? dataList.length + 1
120 | : dataList.length,
121 | ),
122 | ),
123 | ),
124 | ],
125 | ),
126 | ),
127 | );
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/lib/widget/rich_text_demo_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/gestures.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:gsy_flutter_demo/widget/rich/real_rich_text.dart';
4 |
5 | class RichTextDemoPage extends StatefulWidget {
6 | const RichTextDemoPage({super.key});
7 |
8 | @override
9 | _RichTextDemoState createState() => _RichTextDemoState();
10 | }
11 |
12 | class _RichTextDemoState extends State {
13 | @override
14 | Widget build(BuildContext mainContext) {
15 | return Scaffold(
16 | appBar: AppBar(
17 | title: const Text("RichTextDemoPage"),
18 | ),
19 | body: Container(
20 | margin: const EdgeInsets.all(10),
21 | child: Builder(builder: (context) {
22 | return Center(
23 | child: RealRichText([
24 | TextSpan(
25 | text: "A Text Link",
26 | style: const TextStyle(color: Colors.red, fontSize: 14),
27 | recognizer: TapGestureRecognizer()
28 | ..onTap = () {
29 | show(context, "Link Clicked.");
30 | },
31 | ),
32 | ImageSpan(
33 | const AssetImage("static/gsy_cat.png"),
34 | imageWidth: 24,
35 | imageHeight: 24,
36 | ),
37 | ImageSpan(const AssetImage("static/gsy_cat.png"),
38 | imageWidth: 24,
39 | imageHeight: 24,
40 | margin: const EdgeInsets.symmetric(horizontal: 10)),
41 | const TextSpan(
42 | text: "哈哈哈",
43 | style: TextStyle(color: Colors.yellow, fontSize: 14),
44 | ),
45 | TextSpan(
46 | text: "@Somebody",
47 | style: const TextStyle(
48 | color: Colors.black,
49 | fontSize: 14,
50 | fontWeight: FontWeight.bold),
51 | recognizer: TapGestureRecognizer()
52 | ..onTap = () {
53 | show(context, "Link Clicked.");
54 | },
55 | ),
56 | TextSpan(
57 | text: " #RealRichText# ",
58 | style: const TextStyle(color: Colors.blue, fontSize: 14),
59 | recognizer: TapGestureRecognizer()
60 | ..onTap = () {
61 | show(context, "Link Clicked.");
62 | },
63 | ),
64 | const TextSpan(
65 | text: "showing a bigger image",
66 | style: TextStyle(color: Colors.black, fontSize: 14),
67 | ),
68 | ImageSpan(const AssetImage("static/gsy_cat.png"),
69 | imageWidth: 24,
70 | imageHeight: 24,
71 | margin: const EdgeInsets.symmetric(horizontal: 5)),
72 | const TextSpan(
73 | text: "and seems working perfect……",
74 | style: TextStyle(color: Colors.black, fontSize: 14),
75 | ),
76 | ]),
77 | );
78 | }),
79 | ),
80 | );
81 | }
82 |
83 | show(context, text) {
84 | ScaffoldMessenger.of(context).showSnackBar(SnackBar(
85 | content: Text(text),
86 | action: SnackBarAction(
87 | label: 'ACTION',
88 | onPressed: () {
89 | ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
90 | content: Text('You pressed snackbar\'s action.'),
91 | ));
92 | },
93 | ),
94 | ));
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/lib/widget/rich_text_demo_page2.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class RichTextDemoPage2 extends StatefulWidget {
4 | const RichTextDemoPage2({super.key});
5 |
6 | @override
7 | _RichTextDemoState2 createState() => _RichTextDemoState2();
8 | }
9 |
10 | class _RichTextDemoState2 extends State {
11 | double size = 50;
12 |
13 | @override
14 | Widget build(BuildContext mainContext) {
15 | return Scaffold(
16 | appBar: AppBar(
17 | title: const Text("RichTextDemoPage"),
18 | actions: [
19 | IconButton(
20 | onPressed: () {
21 | setState(() {
22 | size += 10;
23 | });
24 | },
25 | icon: const Icon(Icons.add_circle_outline),
26 | ),
27 | IconButton(
28 | onPressed: () {
29 | setState(() {
30 | size -= 10;
31 | });
32 | },
33 | icon: const Icon(Icons.remove_circle_outline),
34 | )
35 | ],
36 | ),
37 | body: SelectionArea(
38 | child: Container(
39 | margin: const EdgeInsets.all(10),
40 | child: Builder(builder: (context) {
41 | return Center(
42 | child: Text.rich(TextSpan(
43 | children: [
44 | const TextSpan(text: 'Flutter is'),
45 | const WidgetSpan(
46 | child: SizedBox(
47 | width: 120,
48 | height: 50,
49 | child: Card(
50 | color: Colors.blue,
51 | child: Center(child: Text('Hello World!'))),
52 | )),
53 | WidgetSpan(
54 | child: SizedBox(
55 | width: size > 0 ? size : 0,
56 | height: size > 0 ? size : 0,
57 | child: Image.asset(
58 | "static/gsy_cat.png",
59 | fit: BoxFit.cover,
60 | ),
61 | )),
62 | const TextSpan(text: 'the best!'),
63 | const WidgetSpan(
64 | child: SelectionContainer.disabled(
65 | child: Text(' not copy'),
66 | ),
67 | ),
68 | ],
69 | )),
70 | );
71 | }),
72 | ),
73 | ),
74 | );
75 | }
76 |
77 | show(context, text) {
78 | ScaffoldMessenger.of(context).showSnackBar(SnackBar(
79 | content: Text(text),
80 | action: SnackBarAction(
81 | label: 'ACTION',
82 | onPressed: () {
83 | ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
84 | content: Text('You pressed snackbar\'s action.'),
85 | ));
86 | },
87 | ),
88 | ));
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/lib/widget/route_demo_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 | import 'package:flutter/material.dart';
3 |
4 | class RouteDemoPage extends StatefulWidget {
5 | const RouteDemoPage({super.key});
6 |
7 | @override
8 | State createState() => _RouteDemoPageState();
9 | }
10 |
11 | class _RouteDemoPageState extends State {
12 | final GlobalKey _navigator = GlobalKey();
13 |
14 | getRouter(index) {
15 | return CupertinoPageRoute(
16 | builder: (context) {
17 | return RoutePage(index);
18 | },
19 | maintainState: false,
20 | fullscreenDialog: true);
21 | }
22 |
23 | @override
24 | Widget build(BuildContext context) {
25 | return Scaffold(
26 | appBar: AppBar(
27 | title: const Text("RouteDemoPage"),
28 | ),
29 | body: Row(
30 | children: [
31 | Expanded(
32 | flex: 1,
33 | child: Container(
34 | color: Colors.blue,
35 | child: Column(
36 | children: List.generate(10, (index) {
37 | return InkWell(
38 | onTap: () {
39 | _navigator.currentState!.push(getRouter(index));
40 | },
41 | child: Container(
42 | height: 30,
43 | margin: const EdgeInsets.symmetric(vertical: 10),
44 | color: Colors.amberAccent,
45 | alignment: Alignment.center,
46 | child: Text("click $index")));
47 | }),
48 | ),
49 | ),
50 | ),
51 | const Divider(
52 | color: Colors.grey,
53 | ),
54 | const SizedBox(
55 | width: 30,
56 | ),
57 | Expanded(
58 | flex: 3,
59 | child: Container(
60 | color: Colors.grey,
61 | child: Navigator(
62 | restorationScopeId: 'nav2',
63 | key: _navigator,
64 | onGenerateInitialRoutes:
65 | (NavigatorState navigator, String initialRoute) {
66 | return [
67 | getRouter(0),
68 | ];
69 | },
70 | reportsRouteUpdateToEngine: true,
71 | ),
72 | ),
73 | ),
74 | ],
75 | ),
76 | );
77 | }
78 | }
79 |
80 | class RoutePage extends StatefulWidget {
81 | final int index;
82 |
83 | const RoutePage(this.index, {super.key});
84 |
85 | @override
86 | State createState() => _RoutePageState();
87 | }
88 |
89 | class _RoutePageState extends State {
90 | @override
91 | Widget build(BuildContext context) {
92 | return Container(
93 | alignment: Alignment.center,
94 | color: Colors.white,
95 | child: InkWell(
96 | onTap: () {
97 | if (Navigator.of(context).canPop()) Navigator.of(context).pop();
98 | },
99 | child: Container(
100 | width: 200,
101 | height: 200,
102 | alignment: Alignment.center,
103 | color: Colors.amber,
104 | child: Text(
105 | "${widget.index}",
106 | style: const TextStyle(fontSize: 100, color: Colors.red),
107 | ),
108 | ),
109 | ),
110 | );
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/lib/widget/scroll_listener_demo_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | ///滑动监听
4 | class ScrollListenerDemoPage extends StatefulWidget {
5 | const ScrollListenerDemoPage({super.key});
6 |
7 | @override
8 | _ScrollListenerDemoPageState createState() => _ScrollListenerDemoPageState();
9 | }
10 |
11 | class _ScrollListenerDemoPageState extends State {
12 | final ScrollController _scrollController = ScrollController();
13 |
14 | bool isEnd = false;
15 |
16 | double offset = 0;
17 |
18 | String notify = "";
19 |
20 | @override
21 | void initState() {
22 | super.initState();
23 | _scrollController.addListener(() {
24 | setState(() {
25 | offset = _scrollController.offset;
26 | isEnd = _scrollController.position.pixels ==
27 | _scrollController.position.maxScrollExtent;
28 | });
29 | });
30 | }
31 |
32 | @override
33 | Widget build(BuildContext context) {
34 | return Scaffold(
35 | appBar: AppBar(
36 | title: const Text("ScrollListenerDemoPage"),
37 | ),
38 | body: NotificationListener(
39 | onNotification: (dynamic notification) {
40 | String notify = "";
41 | if (notification is ScrollEndNotification) {
42 | notify = "ScrollEnd";
43 | } else if (notification is ScrollStartNotification) {
44 | notify = "ScrollStart";
45 | } else if (notification is UserScrollNotification) {
46 | notify = " UserScroll";
47 | } else if (notification is ScrollUpdateNotification) {
48 | notify = "ScrollUpdate";
49 | }
50 | setState(() {
51 | this.notify = notify;
52 | });
53 | return false;
54 | },
55 | child: ListView.builder(
56 | controller: _scrollController,
57 | itemBuilder: (context, index) {
58 | return Card(
59 | child: Container(
60 | height: 60,
61 | alignment: Alignment.centerLeft,
62 | child: Text("Item $index"),
63 | ),
64 | );
65 | },
66 | itemCount: 100,
67 | ),
68 | ),
69 | persistentFooterButtons: [
70 | TextButton(
71 | onPressed: () {
72 | _scrollController.animateTo(0,
73 | duration: const Duration(seconds: 1), curve: Curves.bounceInOut);
74 | },
75 | child: Text("position: ${offset.floor()}"),
76 | ),
77 | const SizedBox(width: 0.3, height: 30.0),
78 | TextButton(
79 | onPressed: () {},
80 | child: Text(notify),
81 | ),
82 | Visibility(
83 | visible: isEnd,
84 | child: TextButton(
85 | onPressed: () {},
86 | child: const Text("到达底部"),
87 | ),
88 | )
89 | ],
90 | );
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/lib/widget/scroll_to_index_demo_page.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math' as math;
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:scroll_to_index/scroll_to_index.dart';
5 |
6 | /// 滑动到指定位置
7 | /// 因为官方一直未支持滑动都执行 item
8 | /// 所有有第三方库另辟蹊径
9 | class ScrollToIndexDemoPage extends StatefulWidget {
10 | const ScrollToIndexDemoPage({super.key});
11 |
12 | @override
13 | _ScrollToIndexDemoPageState createState() => _ScrollToIndexDemoPageState();
14 | }
15 |
16 | class _ScrollToIndexDemoPageState extends State {
17 |
18 | static const maxCount = 100;
19 |
20 | /// pub scroll_to_index 项目的 controller
21 | AutoScrollController? controller;
22 |
23 | final random = math.Random();
24 |
25 | final scrollDirection = Axis.vertical;
26 |
27 | late List> randomList;
28 |
29 | @override
30 | void initState() {
31 | super.initState();
32 | controller = AutoScrollController(
33 | viewportBoundaryGetter: () =>
34 | Rect.fromLTRB(0, 0, 0, MediaQuery.paddingOf(context).bottom),
35 | axis: scrollDirection);
36 | ///一个 index 和 item 高度的数组
37 | randomList = List.generate(maxCount,
38 | (index) => [index, (1000 * random.nextDouble()).toInt()]);
39 | }
40 |
41 | Widget _getRow(int index, double height) {
42 | return _wrapScrollTag(
43 | index: index,
44 | child: Container(
45 | padding: const EdgeInsets.all(8),
46 | alignment: Alignment.topCenter,
47 | height: height,
48 | decoration: BoxDecoration(
49 | border: Border.all(color: Colors.lightBlue, width: 4),
50 | borderRadius: BorderRadius.circular(12)),
51 | child: Text('index: $index, height: $height'),
52 | ));
53 | }
54 |
55 | Widget _wrapScrollTag({required int index, required Widget child}) => AutoScrollTag(
56 | key: ValueKey(index),
57 | controller: controller!,
58 | index: index,
59 | highlightColor: Colors.black..withValues(alpha: 0.1),
60 | child: child,
61 | );
62 |
63 | @override
64 | Widget build(BuildContext context) {
65 | return Scaffold(
66 | appBar: AppBar(
67 | title: const Text("ScrollToIndexDemoPage"),
68 | ),
69 | body: ListView(
70 | scrollDirection: scrollDirection,
71 | controller: controller,
72 | children: randomList.map((data) {
73 | return Padding(
74 | padding: const EdgeInsets.all(8),
75 | child: _getRow(data[0], math.max(data[1].toDouble(), 50.0)),
76 | );
77 | }).toList(),
78 | ),
79 | persistentFooterButtons: [
80 | TextButton(
81 | onPressed: () async {
82 | ///滑动到第13个的位置
83 | await controller!.scrollToIndex(13,
84 | preferPosition: AutoScrollPosition.begin);
85 | controller!.highlight(13);
86 | },
87 | child: const Text("Scroll to 13"),
88 | ),
89 | ],
90 | );
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/lib/widget/scroll_to_index_demo_page2.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math' as math;
2 |
3 | import 'package:flutter/foundation.dart';
4 | import 'package:flutter/material.dart';
5 |
6 | /// 滑动到指定位置 GlobalKey 版本
7 | /// 基于 SingleChildScrollView 和 Column
8 | class ScrollToIndexDemoPage2 extends StatefulWidget {
9 | const ScrollToIndexDemoPage2({super.key});
10 |
11 | @override
12 | _ScrollToIndexDemoPageState2 createState() => _ScrollToIndexDemoPageState2();
13 | }
14 |
15 | class _ScrollToIndexDemoPageState2 extends State {
16 | GlobalKey scrollKey = GlobalKey();
17 |
18 | ScrollController controller = ScrollController();
19 |
20 | List dataList = [];
21 |
22 | @override
23 | void initState() {
24 | dataList.clear();
25 | for (int i = 0; i < 100; i++) {
26 | dataList.add(ItemModel(i));
27 | }
28 | super.initState();
29 | }
30 |
31 | _scrollToIndex() {
32 | var key = dataList[12];
33 |
34 | ///获取 renderBox
35 | RenderBox renderBox =
36 | key.globalKey.currentContext!.findRenderObject() as RenderBox;
37 |
38 | ///获取位置偏移,基于 ancestor: SingleChildScrollView 的 RenderObject()
39 | double dy = renderBox
40 | .localToGlobal(Offset.zero,
41 | ancestor: scrollKey.currentContext!.findRenderObject())
42 | .dy;
43 |
44 | ///计算真实位移
45 | var offset = dy + controller.offset;
46 |
47 | if (kDebugMode) {
48 | print("*******$offset");
49 | }
50 |
51 | controller.animateTo(offset,
52 | duration: const Duration(milliseconds: 500), curve: Curves.linear);
53 | }
54 |
55 | @override
56 | Widget build(BuildContext context) {
57 | return Scaffold(
58 | appBar: AppBar(
59 | title: const Text("ScrollToIndexDemoPage2"),
60 | ),
61 | body: SingleChildScrollView(
62 | key: scrollKey,
63 | controller: controller,
64 | child: Column(
65 | children: dataList.map((data) {
66 | return CardItem(data, key: dataList[data.index].globalKey);
67 | }).toList(),
68 | ),
69 | ),
70 | persistentFooterButtons: [
71 | TextButton(
72 | onPressed: () async {
73 | _scrollToIndex();
74 | },
75 | child: const Text("Scroll to 12"),
76 | ),
77 | ],
78 | );
79 | }
80 | }
81 |
82 | class CardItem extends StatelessWidget {
83 | final random = math.Random();
84 |
85 | final ItemModel data;
86 |
87 | CardItem(this.data, {super.key});
88 |
89 | @override
90 | Widget build(BuildContext context) {
91 | return Card(
92 | child: Container(
93 | height: (300 * random.nextDouble()),
94 | alignment: Alignment.centerLeft,
95 | child: Container(
96 | margin: const EdgeInsets.all(5),
97 | child: Text("Item ${data.index}"),
98 | ),
99 | ),
100 | );
101 | }
102 | }
103 |
104 | class ItemModel {
105 | ///这个key是关键
106 | GlobalKey globalKey = GlobalKey();
107 |
108 | ///可以添加你的代码
109 | final int index;
110 |
111 | ItemModel(this.index);
112 | }
113 |
--------------------------------------------------------------------------------
/lib/widget/shader_canvas_demo_page.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math';
2 | import 'dart:ui' as ui;
3 |
4 | import 'package:flutter/material.dart';
5 |
6 | class ShaderCanvasDemoPage extends StatelessWidget {
7 | const ShaderCanvasDemoPage({super.key});
8 |
9 | @override
10 | Widget build(BuildContext context) {
11 | return Scaffold(
12 | appBar: AppBar(
13 | title: const Text("ShaderCanvasDemoPage"),
14 | ),
15 | extendBody: true,
16 | body: const Column(
17 | children: [
18 | ///增加 CustomWidget
19 | CanvasWidgetA(),
20 | SizedBox(
21 | height: 5,
22 | ),
23 | CanvasWidgetB(),
24 | ],
25 | ),
26 | );
27 | }
28 | }
29 |
30 | class CanvasWidgetA extends StatelessWidget {
31 | const CanvasWidgetA({super.key});
32 |
33 | @override
34 | Widget build(BuildContext context) {
35 | return LayoutBuilder(builder: (context, constraints) {
36 | return SizedBox(
37 | height: 100,
38 | width: constraints.biggest.width,
39 | child: CustomPaint(
40 | foregroundPainter: CurvePainterA(50),
41 | ));
42 | });
43 | }
44 | }
45 |
46 | class CurvePainterA extends CustomPainter {
47 | final double value;
48 |
49 | CurvePainterA(this.value);
50 |
51 | @override
52 | void paint(Canvas canvas, Size size) {
53 | final shader = Paint()
54 | ..maskFilter = const MaskFilter.blur(BlurStyle.solid, 5.0);
55 |
56 | final paint = Paint()..color = Colors.blue.withAlpha(60);
57 |
58 | var radientColors = [const Color(0x01333333), const Color(0x44333333)]; //渐变颜色数组
59 |
60 | ui.Gradient gradient = ui.Gradient.linear(
61 | const Offset(0, 0), Offset(size.width, size.height), radientColors);
62 | shader.shader = gradient;
63 |
64 | final path = Path();
65 |
66 | final y1 = sin(value);
67 | final y2 = sin(value + pi / 2);
68 | final y3 = sin(value + pi);
69 |
70 | final startPointY = size.height * (0.5 + 0.4 * y1);
71 | final controlPointY = size.height * (0.5 + 0.4 * y2);
72 | final endPointY = size.height * (0.5 + 0.4 * y3);
73 |
74 | path.moveTo(size.width * 0, startPointY);
75 | path.quadraticBezierTo(
76 | size.width * 0.5, controlPointY, size.width, endPointY);
77 | path.lineTo(size.width, size.height);
78 | path.lineTo(0, size.height);
79 | path.close();
80 | canvas.drawPath(path, shader);
81 |
82 | canvas.drawPath(path, paint);
83 | }
84 |
85 | @override
86 | bool shouldRepaint(CustomPainter oldDelegate) {
87 | return true;
88 | }
89 | }
90 |
91 | class CanvasWidgetB extends StatelessWidget {
92 | const CanvasWidgetB({super.key});
93 |
94 | @override
95 | Widget build(BuildContext context) {
96 | return LayoutBuilder(builder: (context, constraints) {
97 | return SizedBox(
98 | height: 100,
99 | width: constraints.biggest.width,
100 | child: CustomPaint(
101 | foregroundPainter: CurvePainterB(50),
102 | ));
103 | });
104 | }
105 | }
106 |
107 | class CurvePainterB extends CustomPainter {
108 | final double value;
109 |
110 | CurvePainterB(this.value);
111 |
112 | @override
113 | void paint(Canvas canvas, Size size) {
114 | final paint = Paint()
115 | ..color = Colors.black.withAlpha(60)
116 | ..maskFilter = const MaskFilter.blur(BlurStyle.outer, 2.0);
117 |
118 | final path = Path();
119 |
120 | final y1 = sin(value);
121 | final y2 = sin(value + pi / 2);
122 | final y3 = sin(value + pi);
123 |
124 | final startPointY = size.height * (0.5 + 0.4 * y1);
125 | final controlPointY = size.height * (0.5 + 0.4 * y2);
126 | final endPointY = size.height * (0.5 + 0.4 * y3);
127 |
128 | path.moveTo(size.width * 0, startPointY);
129 | path.quadraticBezierTo(
130 | size.width * 0.5, controlPointY, size.width, endPointY);
131 | path.lineTo(size.width, size.height);
132 | path.lineTo(0, size.height);
133 | path.close();
134 | canvas.drawPath(path, paint);
135 | }
136 |
137 | @override
138 | bool shouldRepaint(CustomPainter oldDelegate) {
139 | return true;
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/lib/widget/sliver_tab/sliver_tab_child_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:gsy_flutter_demo/widget/custom_sliver/custom_sliver.dart';
3 | import 'package:gsy_flutter_demo/widget/sliver_tab/sliver_tab_sliver.dart';
4 |
5 | class SliverTabChildPage extends StatefulWidget {
6 | final List pageList;
7 | final int tabIndex;
8 |
9 | const SliverTabChildPage(this.tabIndex, this.pageList, {super.key});
10 |
11 | @override
12 | SliverTabChildPageState createState() => SliverTabChildPageState();
13 | }
14 |
15 | class SliverTabChildPageState extends State
16 | with AutomaticKeepAliveClientMixin {
17 | GlobalKey globalKey = GlobalKey();
18 |
19 | double initLayoutExtent = 100;
20 | double showPullDistance = 150;
21 | final double indicatorExtent = 200;
22 | final double triggerPullDistance = 300;
23 | final ScrollController scrollController =
24 | ScrollController(initialScrollOffset: -100);
25 |
26 | renderListByIndex(tabIndex, pageList) {
27 | return CustomScrollView(
28 | controller: scrollController,
29 | physics: const BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()),
30 | slivers: [
31 | SliverTabSliver(
32 | key: globalKey,
33 | initLayoutExtent: initLayoutExtent,
34 | containerExtent: indicatorExtent,
35 | triggerPullDistance: triggerPullDistance,
36 | pinned: false,
37 | ),
38 | SliverPadding(
39 | padding: const EdgeInsets.all(10.0),
40 | sliver: SliverFixedExtentList(
41 | itemExtent: 50.0, //item高度或宽度,取决于滑动方向
42 | delegate: SliverChildBuilderDelegate(
43 | (BuildContext context, int index) {
44 | return ListTile(
45 | title: Text('Tab $tabIndex Item $index'),
46 | );
47 | },
48 | childCount: pageList.length,
49 | ),
50 | ),
51 | ),
52 | ],
53 | );
54 | }
55 |
56 | @override
57 | bool get wantKeepAlive => false;
58 |
59 | @override
60 | Widget build(BuildContext context) {
61 | super.build(context);
62 | return renderListByIndex(widget.tabIndex, widget.pageList);
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/lib/widget/statusbar_demo_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter/services.dart';
3 |
4 | ///状态栏颜色
5 | class StatusBarDemoPage extends StatefulWidget {
6 | const StatusBarDemoPage({super.key});
7 |
8 | @override
9 | _StatusBarDemoPageState createState() => _StatusBarDemoPageState();
10 | }
11 |
12 | class _StatusBarDemoPageState extends State {
13 | bool customSystemUIOverlayStyle = false;
14 |
15 | @override
16 | Widget build(BuildContext context) {
17 | var body = getBody();
18 | ///如果手动设置过状态栏,就不可以用 AnnotatedRegion ,会影响
19 | if (customSystemUIOverlayStyle) {
20 | return body;
21 | }
22 | ///如果没有手动设置过状态栏,就可以用 AnnotatedRegion 直接嵌套显示
23 | return AnnotatedRegion(
24 | value: SystemUiOverlayStyle.dark,
25 | child: body,
26 | );
27 | }
28 |
29 | getBody() {
30 | return Scaffold(
31 | appBar: const ImageAppBar(),
32 | body: Center(
33 | child: Row(
34 | mainAxisAlignment: MainAxisAlignment.center,
35 | crossAxisAlignment: CrossAxisAlignment.center,
36 | children: [
37 | TextButton(
38 | onPressed: () {
39 | ///手动修改
40 | setState(() {
41 | customSystemUIOverlayStyle = true;
42 | });
43 | SystemChrome.setSystemUIOverlayStyle(
44 | SystemUiOverlayStyle.light);
45 | },
46 | style: ButtonStyle(
47 | backgroundColor: ButtonStyleButton.allOrNull(
48 | Colors.yellowAccent,
49 | ),
50 | ),
51 | child: const Text("Light"),
52 | ),
53 | const SizedBox(
54 | width: 10,
55 | ),
56 | TextButton(
57 | onPressed: () {
58 | setState(() {
59 | customSystemUIOverlayStyle = true;
60 | });
61 | SystemChrome.setSystemUIOverlayStyle(
62 | SystemUiOverlayStyle.dark);
63 | },
64 | style: ButtonStyle(
65 | backgroundColor: ButtonStyleButton.allOrNull(
66 | Colors.greenAccent,
67 | ),
68 | ),
69 | child: const Text("Dart"),
70 | ),
71 | ],
72 | ),
73 | ),
74 | );
75 | }
76 | }
77 |
78 | ///自定义 PreferredSizeWidget 做 AppBar
79 | class ImageAppBar extends StatelessWidget implements PreferredSizeWidget {
80 | const ImageAppBar({super.key});
81 |
82 | @override
83 | Widget build(BuildContext context) {
84 | return Stack(
85 | children: [
86 | Image.asset(
87 | "static/gsy_cat.png",
88 | fit: BoxFit.cover,
89 | width: MediaQuery.sizeOf(context).width,
90 | height: kToolbarHeight * 3,
91 | ),
92 | SafeArea(
93 | child: IconButton(
94 | color: Colors.white,
95 | icon: const Icon(Icons.arrow_back_ios),
96 | onPressed: () {
97 | Navigator.of(context).pop();
98 | }),
99 | )
100 | ],
101 | );
102 | }
103 |
104 | @override
105 | Size get preferredSize => const Size.fromHeight(kToolbarHeight * 3);
106 | }
107 |
--------------------------------------------------------------------------------
/lib/widget/stick/stick_demo_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:gsy_flutter_demo/widget/stick/stick_widget.dart';
4 |
5 | class StickDemoPage extends StatefulWidget {
6 | const StickDemoPage({super.key});
7 |
8 | @override
9 | _StickDemoPageState createState() => _StickDemoPageState();
10 | }
11 |
12 | class _StickDemoPageState extends State {
13 | @override
14 | Widget build(_) {
15 | return Scaffold(
16 | appBar: AppBar(
17 | title: const Text("StickDemoPage"),
18 | ),
19 | body: ListView.builder(
20 | physics: const AlwaysScrollableScrollPhysics(),
21 | itemCount: 100,
22 | itemBuilder: (context, index) {
23 | return Container(
24 | height: 200,
25 | color: Colors.deepOrange,
26 | child: StickWidget(
27 | ///header
28 | stickHeader: Container(
29 | height: 50.0,
30 | color: Colors.deepPurple,
31 | padding: const EdgeInsets.only(left: 10.0),
32 | alignment: Alignment.centerLeft,
33 | child: InkWell(
34 | onTap: () {
35 | if (kDebugMode) {
36 | print("header");
37 | }
38 | },
39 | child: Text(
40 | '我的 $index 头啊',
41 | style: const TextStyle(color: Colors.white),
42 | ),
43 | ),
44 | ),
45 |
46 | ///content
47 | stickContent: InkWell(
48 | onTap: () {
49 | if (kDebugMode) {
50 | print("content");
51 | }
52 | },
53 | child: Container(
54 | margin: const EdgeInsets.only(left: 10),
55 | color: Colors.pinkAccent,
56 | height: 150,
57 | child: Center(
58 | child: Text(
59 | '我的$index 内容 啊',
60 | style: const TextStyle(color: Colors.white),
61 | ),
62 | ),
63 | ),
64 | ),
65 | ),
66 | );
67 | }),
68 | );
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/lib/widget/stick/stick_render.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math' as math;
2 |
3 | import 'package:flutter/foundation.dart';
4 | import 'package:flutter/material.dart';
5 | import 'package:flutter/rendering.dart';
6 |
7 | class StickRender extends RenderBox
8 | with
9 | ContainerRenderObjectMixin,
10 | RenderBoxContainerDefaultsMixin {
11 | StickRender({required ScrollableState? scrollable}) {
12 | _scrollable = scrollable;
13 | }
14 |
15 | ScrollableState? _scrollable;
16 |
17 | set scrollable(ScrollableState? scrollableState) {
18 | if (_scrollable == scrollableState) {
19 | return;
20 | }
21 | final ScrollableState? preScroll = _scrollable;
22 | _scrollable = scrollableState;
23 | if (attached) {
24 | ///这里触发更新
25 | preScroll!.position.removeListener(markNeedsLayout);
26 | scrollableState!.position.addListener(markNeedsLayout);
27 | }
28 | markNeedsLayout();
29 | }
30 |
31 | double getScrollAbleDy() {
32 | RenderObject renderObject = _scrollable!.context.findRenderObject()!;
33 | if (!renderObject.attached) {
34 | return 0;
35 | }
36 | try {
37 | return localToGlobal(Offset.zero, ancestor: renderObject).dy;
38 | } catch (e) {
39 | if (kDebugMode) {
40 | print(e);
41 | }
42 | }
43 | return 0;
44 | }
45 |
46 | @override
47 | void attach(owner) {
48 | ///设置监听
49 | _scrollable!.position.addListener(markNeedsLayout);
50 | super.attach(owner);
51 | }
52 |
53 | @override
54 | void detach() {
55 | _scrollable!.position.removeListener(markNeedsLayout);
56 | super.detach();
57 | }
58 |
59 | ///设置为 isRepaintBoundary 或者性能会好一些。
60 | ///@override
61 | ///bool get isRepaintBoundary => true;
62 |
63 | @override
64 | double computeMinIntrinsicHeight(double width) {
65 | return (lastChild!.getMinIntrinsicHeight(width) +
66 | firstChild!.getMinIntrinsicHeight(width));
67 | }
68 |
69 | @override
70 | double computeMaxIntrinsicHeight(double width) {
71 | return (lastChild!.getMaxIntrinsicHeight(width) +
72 | firstChild!.getMaxIntrinsicHeight(width));
73 | }
74 |
75 | @override
76 | double? computeDistanceToActualBaseline(TextBaseline baseline) {
77 | return defaultComputeDistanceToHighestActualBaseline(baseline);
78 | }
79 |
80 | ///设置绘制默认
81 | @override
82 | void paint(PaintingContext context, Offset offset) {
83 | defaultPaint(context, offset);
84 | }
85 |
86 |
87 | ///设置我们的 StickParentData
88 | @override
89 | void setupParentData(RenderObject child) {
90 | super.setupParentData(child);
91 | if (child.parentData is! StickParentData) {
92 | child.parentData = StickParentData();
93 | }
94 | }
95 |
96 | @override
97 | void performLayout() {
98 | var header = lastChild!;
99 | var content = firstChild!;
100 |
101 | ///取消最小宽高
102 | var loosenConstraints = constraints.loosen();
103 | content.layout(loosenConstraints, parentUsesSize: true);
104 | header.layout(loosenConstraints, parentUsesSize: true);
105 |
106 | ///获取各自的高度用户计算
107 | var contentHeight = content.size.height;
108 | var headerHeight = header.size.height;
109 |
110 | ///对于当前布局,用内容作为宽高
111 | var width = content.size.width;
112 | var height = headerHeight + contentHeight;
113 | size = Size(width, height);
114 |
115 | ///内容的初始化位置
116 | (content.parentData as StickParentData).offset =
117 | Offset(0, headerHeight);
118 |
119 | /// 计算出 header 需要的整体偏移量,用于反方向
120 | var headerOffset = height - headerHeight;
121 |
122 | ///判断当前 item 在 ScrollAble 里的偏移
123 | var scrollAbleDy = getScrollAbleDy();
124 |
125 | ///是滑动的多还是偏移量
126 | var realHeaderOffset = math.min(-scrollAbleDy, headerOffset);
127 | (header.parentData as StickParentData).offset =
128 | Offset(0, math.max(0, realHeaderOffset));
129 | }
130 |
131 | @override
132 | bool hitTestChildren(HitTestResult result, {required Offset position}) {
133 | return defaultHitTestChildren(result as BoxHitTestResult, position: position);
134 | }
135 |
136 | }
137 |
138 | class StickParentData extends ContainerBoxParentData {}
139 |
--------------------------------------------------------------------------------
/lib/widget/stick/stick_widget.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:gsy_flutter_demo/widget/stick/stick_render.dart';
3 |
4 | class StickWidget extends MultiChildRenderObjectWidget {
5 | ///顺序添加 stickHeader 和 stickContent
6 | StickWidget({super.key,
7 | required stickHeader,
8 | required stickContent,
9 | }) : super(
10 | ///如果反过来,会有意想不到的效果哦
11 | children: [stickContent, stickHeader],
12 | );
13 |
14 | @override
15 | StickRender createRenderObject(BuildContext context) {
16 | ///传入 ScrollableState
17 | return StickRender(scrollable: Scrollable.of(context));
18 | }
19 |
20 | @override
21 | void updateRenderObject(BuildContext context, StickRender renderObject) {
22 | renderObject.scrollable = Scrollable.of(context);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/lib/widget/tag_demo_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class TagDemoPage extends StatelessWidget {
4 | const TagDemoPage({super.key});
5 |
6 | @override
7 | Widget build(BuildContext context) {
8 | return Scaffold(
9 | appBar: AppBar(
10 | title: const Text("TagDemoPage"),
11 | ),
12 | body: Wrap(children: [
13 | const TagItem("Start"),
14 | for (var item in tags) TagItem(item),
15 | const TagItem("End"),
16 | ]),
17 | );
18 | }
19 | }
20 |
21 | class TagItem extends StatelessWidget {
22 | final String text;
23 |
24 | const TagItem(this.text, {super.key});
25 |
26 | @override
27 | Widget build(BuildContext context) {
28 | return Container(
29 | padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 5),
30 | margin: const EdgeInsets.symmetric(vertical: 5, horizontal: 10),
31 | decoration: BoxDecoration(
32 | color: Colors.blueAccent.withAlpha(60),
33 | borderRadius: const BorderRadius.all(Radius.circular(5))),
34 | child: Text(text),
35 | );
36 | }
37 | }
38 |
39 | const List tags = [
40 | "FFFFFFF",
41 | "TTTTTT",
42 | "LL",
43 | "JJJJJJJJ",
44 | "PPPPP",
45 | "OOOOOOOOOOOO",
46 | "9999999",
47 | "*&",
48 | "5%%%%%",
49 | "¥¥¥¥¥¥",
50 | "UUUUUUUUUU",
51 | "))@@@@@@"
52 | ];
53 |
--------------------------------------------------------------------------------
/lib/widget/test_center_sliver/test_center_sliver_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:gsy_flutter_demo/widget/test_center_sliver/test_center_sliver.dart';
3 |
4 |
5 | class TestCenterSliverPage extends StatefulWidget {
6 | const TestCenterSliverPage({super.key});
7 |
8 | @override
9 | TestCenterSliverPageState createState() => TestCenterSliverPageState();
10 | }
11 |
12 | class TestCenterSliverPageState extends State {
13 |
14 |
15 | @override
16 | Widget build(BuildContext context) {
17 | return Scaffold(
18 | appBar: AppBar(
19 | title: const Text("TestCenterSliverPage"),
20 | ),
21 | body: const CustomScrollView(
22 | anchor: 0.5,
23 | ///回弹效果
24 | physics: BouncingScrollPhysics(
25 | parent: AlwaysScrollableScrollPhysics()),
26 | slivers: [
27 | TestCenterSliver(
28 | initLayoutExtent: 100,
29 | containerExtent: 100,
30 | triggerPullDistance: 100,
31 | pinned: false,
32 | ),
33 |
34 | ],
35 | ),
36 | );
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/lib/widget/text_line_height_demo_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | /// Text 行间距的设置方案
4 | /// 因为 Flutter 没有 Line Space ,只有字体权重
5 | /// 这里利用了 fontSize 和 leading 的特性去模拟行高
6 | class TextLineHeightDemoPage extends StatelessWidget {
7 |
8 | final double leading = 0.9;
9 |
10 | final double fontSize = 16;
11 |
12 | const TextLineHeightDemoPage({super.key});
13 |
14 | @override
15 | Widget build(BuildContext context) {
16 | return Scaffold(
17 | appBar: AppBar(
18 | title: const Text("TextLineHeightDemoPage"),
19 | ),
20 | body: Container(
21 | color: Colors.blueGrey,
22 | margin: const EdgeInsets.all(20),
23 |
24 | ///利用 Transform 偏移将对应权重部分位置
25 | child: Transform.translate(
26 | offset: Offset(0, -fontSize * leading / 2),
27 | child: Text(
28 | textContent,
29 | strutStyle:
30 | StrutStyle(forceStrutHeight: true, height: 1, leading: leading),
31 | style: TextStyle(
32 | fontSize: fontSize,
33 | color: Colors.black,
34 | //backgroundColor: Colors.greenAccent),
35 | ),
36 | ),
37 | ),
38 | ),
39 | );
40 | }
41 | }
42 |
43 | const textContent =
44 | "Today I was amazed to see the usually positive and friendly VueJS community descend into a bitter war. Two weeks ago Vue creator Evan You released a Request for Comment (RFC) for a new function-based way of writing Vue components in the upcoming Vue 3.0. Today a critical "
45 | "Reddit thread followed by similarly "
46 | "critical comments in a Hacker News thread caused a "
47 | "flood of developers to flock to the original RFC to "
48 | "voice their outrage, some of which were borderline abusive. "
49 | "It was claimed in various places that";
50 |
--------------------------------------------------------------------------------
/lib/widget/text_size_demo_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class TextSizeDemoPage extends StatefulWidget {
4 | const TextSizeDemoPage({super.key});
5 |
6 | @override
7 | _TextSizeDemoPageState createState() => _TextSizeDemoPageState();
8 | }
9 |
10 | class _TextSizeDemoPageState extends State {
11 | TextScaler textScaler = TextScaler.noScaling;
12 | int scale = 1;
13 |
14 | @override
15 | void initState() {
16 | super.initState();
17 | }
18 |
19 | @override
20 | Widget build(BuildContext context) {
21 | return MediaQuery(
22 | data: MediaQueryData.fromView(
23 | WidgetsBinding.instance.platformDispatcher.views.first)
24 | .copyWith(textScaler: textScaler),
25 | child: Scaffold(
26 | appBar: AppBar(
27 | title: const Text("TextLineHeightDemoPage"),
28 | ),
29 | body: Stack(
30 | children: [
31 | Container(
32 | color: Colors.blueGrey,
33 | margin: const EdgeInsets.all(20),
34 |
35 | ///利用 Transform 偏移将对应权重部分位置
36 | child: const Text(
37 | textContent,
38 | style: TextStyle(color: Colors.black),
39 | ),
40 | ),
41 | Align(
42 | alignment: Alignment.bottomCenter,
43 | child: Padding(
44 | padding: const EdgeInsets.only(bottom: 50),
45 | child: Row(
46 | mainAxisAlignment: MainAxisAlignment.center,
47 | children: [
48 | TextButton(
49 | onPressed: () {
50 | if (scale > 1) {
51 | setState(() {
52 | textScaler.scale(scale - 1);
53 | scale--;
54 | });
55 | }
56 | },
57 | style: TextButton.styleFrom(
58 | backgroundColor: Colors.redAccent),
59 | child: const Text("-"),
60 | ),
61 | const SizedBox(
62 | width: 10,
63 | ),
64 | TextButton(
65 | onPressed: () {
66 | setState(() {
67 | textScaler.scale(scale + 1);
68 | scale++;
69 | });
70 | },
71 | style: TextButton.styleFrom(
72 | backgroundColor: Colors.greenAccent),
73 | child: const Text("+"),
74 | )
75 | ],
76 | ),
77 | ),
78 | )
79 | ],
80 | ),
81 | ));
82 | }
83 | }
84 |
85 | const textContent =
86 | "Today I was amazed to see the usually positive and friendly VueJS community descend into a bitter war. Two weeks ago Vue creator Evan You released a Request for Comment (RFC) for a new function-based way of writing Vue components in the upcoming Vue 3.0. Today a critical "
87 | "Reddit thread followed by similarly "
88 | "critical comments in a Hacker News thread caused a "
89 | "flood of developers to flock to the original RFC to "
90 | "voice their outrage, some of which were borderline abusive. "
91 | "It was claimed in various places that";
92 |
--------------------------------------------------------------------------------
/lib/widget/transform_demo_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class TransformDemoPage extends StatelessWidget {
4 | const TransformDemoPage({super.key});
5 |
6 |
7 | ///头像
8 | getHeader(context) {
9 | ///向上偏移 -30 位置
10 | return Transform.translate(
11 | offset: const Offset(0, -30),
12 | child: Container(
13 | width: 72.0,
14 | height: 72.0,
15 | decoration: BoxDecoration(
16 | ///阴影
17 | boxShadow: [
18 | BoxShadow(color: Theme.of(context).cardColor, blurRadius: 4.0)
19 | ],
20 |
21 | ///形状
22 | shape: BoxShape.circle,
23 |
24 | ///图片
25 | image: const DecorationImage(
26 | fit: BoxFit.cover,
27 | image: AssetImage(
28 | "static/gsy_cat.png",
29 | ),
30 | ),
31 | ),
32 | ),
33 | );
34 |
35 | ///圆形头像还可以 CircleAvatar, ClipOval等实现
36 | }
37 |
38 | @override
39 | Widget build(BuildContext context) {
40 | return Scaffold(
41 | backgroundColor: Theme.of(context).primaryColorDark,
42 | appBar: AppBar(
43 | title: const Text("TransformDemoPage"),
44 | ),
45 | body: Container(
46 | alignment: Alignment.center,
47 | child: Card(
48 | margin: const EdgeInsets.all(10),
49 | child: Container(
50 | height: 150,
51 | padding: const EdgeInsets.all(10),
52 | child: Column(
53 | crossAxisAlignment: CrossAxisAlignment.center,
54 | children: [
55 | getHeader(context),
56 | const Text(
57 | "Flutter is Google's portable UI toolkit for crafting "
58 | "beautiful, natively compiled applications for mobile, "
59 | "web, and desktop from a single codebase. ",
60 | overflow: TextOverflow.ellipsis,
61 | softWrap: true,
62 | maxLines: 3,
63 | style: TextStyle(),
64 | )
65 | ],
66 | ),
67 | ),
68 | ),
69 | ),
70 | );
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/lib/widget/wrap_content_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | ///展示如何在 Flutter 里实现 WrapContent 的状态
4 | class WrapContentPage extends StatelessWidget {
5 | const WrapContentPage({super.key});
6 |
7 | @override
8 | Widget build(BuildContext context) {
9 | return Scaffold(
10 | appBar: AppBar(
11 | title: const Text(
12 | "WrapContentPage",
13 | ),
14 | ),
15 | body: SingleChildScrollView(
16 | child: Container(
17 | constraints:
18 |
19 | ///关键就是 minHeight 和 double.infinity
20 | ///这样就可以由内部 children 来支撑决定外部大小
21 | const BoxConstraints(minHeight: 100, maxHeight: double.infinity),
22 | child: Column(
23 | ///min而不是max
24 | mainAxisSize: MainAxisSize.min,
25 | children: [
26 | Container(
27 | ///关键就是 minHeight 和 double.infinity
28 | constraints: const BoxConstraints(
29 | minHeight: 100,
30 | maxHeight: double.infinity,
31 | ),
32 |
33 | /// Stack 默认是 StackFit.loose, 需要内部一个固定的最大大小来支撑
34 | child: Stack(
35 | children: [
36 | Container(
37 | height: 400,
38 | color: Colors.yellow,
39 | ),
40 | Container(
41 | height: 50,
42 | color: Colors.red,
43 | ),
44 | Positioned(
45 | left: 0,
46 | right: 0,
47 | top: 0,
48 | child: Container(
49 | height: 56,
50 | alignment: Alignment.centerLeft,
51 | color: Colors.blueGrey,
52 | child: Container(
53 | width: 33,
54 | height: 33,
55 | color: Colors.black,
56 | ),
57 | ),
58 | ),
59 | ],
60 | ),
61 | ),
62 | Container(
63 | margin: const EdgeInsets.only(top: 20),
64 |
65 | ///关键就是 minHeight 和 double.infinity
66 | constraints:
67 | const BoxConstraints(minHeight: 100, maxHeight: double.infinity),
68 | child: Column(
69 | mainAxisSize: MainAxisSize.min,
70 | children: [
71 | Container(
72 | height: 600,
73 | color: Colors.green,
74 | ),
75 | Container(
76 | height: 50,
77 | color: Colors.amber,
78 | ),
79 | ],
80 | ),
81 | ),
82 | ],
83 | ),
84 | ),
85 | ),
86 | );
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/privacy.html:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/e315536718e4ccb98624373ed0b656f9745af299/privacy.html
--------------------------------------------------------------------------------
/static/card_down.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/e315536718e4ccb98624373ed0b656f9745af299/static/card_down.png
--------------------------------------------------------------------------------
/static/card_down_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/e315536718e4ccb98624373ed0b656f9745af299/static/card_down_2.png
--------------------------------------------------------------------------------
/static/card_up.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/e315536718e4ccb98624373ed0b656f9745af299/static/card_up.png
--------------------------------------------------------------------------------
/static/card_up_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/e315536718e4ccb98624373ed0b656f9745af299/static/card_up_2.png
--------------------------------------------------------------------------------
/static/gsy_cat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/e315536718e4ccb98624373ed0b656f9745af299/static/gsy_cat.png
--------------------------------------------------------------------------------
/static/juejin.riv:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/e315536718e4ccb98624373ed0b656f9745af299/static/juejin.riv
--------------------------------------------------------------------------------
/static/test.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/e315536718e4ccb98624373ed0b656f9745af299/static/test.jpeg
--------------------------------------------------------------------------------
/static/test_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/e315536718e4ccb98624373ed0b656f9745af299/static/test_logo.png
--------------------------------------------------------------------------------
/test/widget_test.dart:
--------------------------------------------------------------------------------
1 | // This is a basic Flutter widget test.
2 | //
3 | // To perform an interaction with a widget in your test, use the WidgetTester
4 | // utility that Flutter provides. For example, you can send tap and scroll
5 | // gestures. You can also use WidgetTester to find child widgets in the widget
6 | // tree, read text, and verify that the values of widget properties are correct.
7 |
8 | import 'package:flutter/material.dart';
9 | import 'package:flutter_test/flutter_test.dart';
10 |
11 | import 'package:gsy_flutter_demo/main.dart';
12 |
13 | void main() {
14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async {
15 | // Build our app and trigger a frame.
16 | await tester.pumpWidget(const MyApp());
17 |
18 | // Verify that our counter starts at 0.
19 | expect(find.text('0'), findsOneWidget);
20 | expect(find.text('1'), findsNothing);
21 |
22 | // Tap the '+' icon and trigger a frame.
23 | await tester.tap(find.byIcon(Icons.add));
24 | await tester.pump();
25 |
26 | // Verify that our counter has incremented.
27 | expect(find.text('0'), findsNothing);
28 | expect(find.text('1'), findsOneWidget);
29 | });
30 | }
31 |
--------------------------------------------------------------------------------
/thanks.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/e315536718e4ccb98624373ed0b656f9745af299/thanks.jpg
--------------------------------------------------------------------------------
/web.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/e315536718e4ccb98624373ed0b656f9745af299/web.jpg
--------------------------------------------------------------------------------
/web/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | gsy_flutter_demo
6 |
51 |
52 |
53 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------