├── .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 |
54 |
55 |
56 | 57 | 58 | 59 | --------------------------------------------------------------------------------