├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── .gitignore ├── .metadata ├── LICENSE ├── README.md ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── build.gradle │ ├── build.gradle.kts │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── gsy │ │ │ │ └── shuyu │ │ │ │ └── gsy_flutter_demo │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable-v21 │ │ │ └── launch_background.xml │ │ │ ├── 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-night │ │ │ └── styles.xml │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── build.gradle.kts ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── settings.gradle └── settings.gradle.kts ├── assets ├── Agne.otf ├── Bobbers.ttf ├── Canterbury.ttf ├── HelloStockholm.otf ├── Horizon.otf ├── background.png └── bglbt.ttf ├── demo.jpg ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── ephemeral │ │ ├── flutter_lldb_helper.py │ │ └── flutter_lldbinit ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings ├── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ └── Runner-Bridging-Header.h └── RunnerTests │ └── RunnerTests.swift ├── 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 │ ├── glass_demo_page.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_scroll_page.dart │ ├── link_sliver │ ├── link_flexible_space_bar.dart │ ├── link_sliver_demo_page.dart │ └── link_sliver_header.dart │ ├── liquid_glass_demo.dart │ ├── liquid_glass_demo2.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 ├── linux ├── .gitignore ├── CMakeLists.txt ├── flutter │ ├── CMakeLists.txt │ ├── generated_plugin_registrant.cc │ ├── generated_plugin_registrant.h │ └── generated_plugins.cmake └── runner │ ├── CMakeLists.txt │ ├── main.cc │ ├── my_application.cc │ └── my_application.h ├── macos ├── .gitignore ├── Flutter │ ├── Flutter-Debug.xcconfig │ ├── Flutter-Release.xcconfig │ └── GeneratedPluginRegistrant.swift ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── app_icon_1024.png │ │ │ ├── app_icon_128.png │ │ │ ├── app_icon_16.png │ │ │ ├── app_icon_256.png │ │ │ ├── app_icon_32.png │ │ │ ├── app_icon_512.png │ │ │ └── app_icon_64.png │ ├── Base.lproj │ │ └── MainMenu.xib │ ├── Configs │ │ ├── AppInfo.xcconfig │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ └── Warnings.xcconfig │ ├── DebugProfile.entitlements │ ├── Info.plist │ ├── MainFlutterWindow.swift │ └── Release.entitlements └── RunnerTests │ └── RunnerTests.swift ├── privacy.html ├── pubspec.lock ├── pubspec.yaml ├── shaders ├── liquid_glass.frag └── liquid_glass2.frag ├── static ├── card_down.png ├── card_down_2.png ├── card_up.png ├── card_up_2.png ├── gsy_cat.png ├── iOS26.png ├── juejin.riv ├── person.jpg ├── test.jpeg └── test_logo.png ├── test └── widget_test.dart ├── thanks.jpg ├── web.jpg ├── web ├── favicon.png ├── icons │ ├── Icon-192.png │ ├── Icon-512.png │ ├── Icon-maskable-192.png │ └── Icon-maskable-512.png ├── index.html └── manifest.json └── windows ├── .gitignore ├── CMakeLists.txt ├── flutter ├── CMakeLists.txt ├── generated_plugin_registrant.cc ├── generated_plugin_registrant.h └── generated_plugins.cmake └── runner ├── CMakeLists.txt ├── Runner.rc ├── flutter_window.cpp ├── flutter_window.h ├── main.cpp ├── resource.h ├── resources └── app_icon.ico ├── runner.exe.manifest ├── utils.cpp ├── utils.h ├── win32_window.cpp └── win32_window.h /.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 -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: "8defaa71a77c16e8547abdbfad2053ce3a6e2d5b" 8 | channel: "[user-branch]" 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: 8defaa71a77c16e8547abdbfad2053ce3a6e2d5b 17 | base_revision: 8defaa71a77c16e8547abdbfad2053ce3a6e2d5b 18 | - platform: android 19 | create_revision: 8defaa71a77c16e8547abdbfad2053ce3a6e2d5b 20 | base_revision: 8defaa71a77c16e8547abdbfad2053ce3a6e2d5b 21 | - platform: ios 22 | create_revision: 8defaa71a77c16e8547abdbfad2053ce3a6e2d5b 23 | base_revision: 8defaa71a77c16e8547abdbfad2053ce3a6e2d5b 24 | - platform: linux 25 | create_revision: 8defaa71a77c16e8547abdbfad2053ce3a6e2d5b 26 | base_revision: 8defaa71a77c16e8547abdbfad2053ce3a6e2d5b 27 | - platform: macos 28 | create_revision: 8defaa71a77c16e8547abdbfad2053ce3a6e2d5b 29 | base_revision: 8defaa71a77c16e8547abdbfad2053ce3a6e2d5b 30 | - platform: web 31 | create_revision: 8defaa71a77c16e8547abdbfad2053ce3a6e2d5b 32 | base_revision: 8defaa71a77c16e8547abdbfad2053ce3a6e2d5b 33 | - platform: windows 34 | create_revision: 8defaa71a77c16e8547abdbfad2053ce3a6e2d5b 35 | base_revision: 8defaa71a77c16e8547abdbfad2053ce3a6e2d5b 36 | 37 | # User provided section 38 | 39 | # List of Local paths (relative to this file) that should be 40 | # ignored by the migrate tool. 41 | # 42 | # Files that are not part of the templates will be ignored by default. 43 | unmanaged_files: 44 | - 'lib/main.dart' 45 | - 'ios/Runner.xcodeproj/project.pbxproj' 46 | -------------------------------------------------------------------------------- /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/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | .cxx/ 9 | 10 | # Remember to never publicly share your keystore. 11 | # See https://flutter.dev/to/reference-keystore 12 | key.properties 13 | **/*.keystore 14 | **/*.jks 15 | -------------------------------------------------------------------------------- /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 | compileOptions { 61 | sourceCompatibility JavaVersion.VERSION_17 62 | targetCompatibility JavaVersion.VERSION_17 63 | } 64 | kotlinOptions { 65 | jvmTarget = '17' 66 | } 67 | } 68 | 69 | flutter { 70 | source '../..' 71 | } 72 | 73 | dependencies {} 74 | -------------------------------------------------------------------------------- /android/app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.application") 3 | id("kotlin-android") 4 | // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. 5 | id("dev.flutter.flutter-gradle-plugin") 6 | } 7 | 8 | android { 9 | namespace = "com.gsy.shuyu.gsy_flutter_demo" 10 | compileSdk = flutter.compileSdkVersion 11 | ndkVersion = flutter.ndkVersion 12 | 13 | compileOptions { 14 | sourceCompatibility = JavaVersion.VERSION_11 15 | targetCompatibility = JavaVersion.VERSION_11 16 | } 17 | 18 | kotlinOptions { 19 | jvmTarget = JavaVersion.VERSION_11.toString() 20 | } 21 | 22 | defaultConfig { 23 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 24 | applicationId = "com.gsy.shuyu.gsy_flutter_demo" 25 | // You can update the following values to match your application needs. 26 | // For more information, see: https://flutter.dev/to/review-gradle-config. 27 | minSdk = flutter.minSdkVersion 28 | targetSdk = flutter.targetSdkVersion 29 | versionCode = flutter.versionCode 30 | versionName = flutter.versionName 31 | } 32 | 33 | buildTypes { 34 | release { 35 | // TODO: Add your own signing config for the release build. 36 | // Signing with the debug keys for now, so `flutter run --release` works. 37 | signingConfig = signingConfigs.getByName("debug") 38 | } 39 | } 40 | } 41 | 42 | flutter { 43 | source = "../.." 44 | } 45 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | <manifest xmlns:android="http://schemas.android.com/apk/res/android" 2 | package="com.gsy.shuyu.gsy_flutter_demo"> 3 | <!-- Flutter needs it to communicate with the running application 4 | to allow setting breakpoints, to provide hot reload, etc. 5 | --> 6 | <uses-permission android:name="android.permission.INTERNET"/> 7 | </manifest> 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | <manifest xmlns:android="http://schemas.android.com/apk/res/android" 2 | package="com.gsy.shuyu.gsy_flutter_demo"> 3 | 4 | <!-- io.flutter.app.FlutterApplication is an android.app.Application that 5 | calls FlutterMain.startInitialization(this); in its onCreate method. 6 | In most cases you can leave this as-is, but you if you want to provide 7 | additional functionality it is fine to subclass or reimplement 8 | FlutterApplication and put your custom class here. --> 9 | 10 | <uses-permission android:name="android.permission.INTERNET"/> 11 | <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> 12 | <uses-permission android:name="android.permission.WAKE_LOCK" /> 13 | 14 | <application 15 | android:label="gsy_flutter_demo" 16 | android:icon="@mipmap/ic_launcher"> 17 | <activity 18 | android:name=".MainActivity" 19 | android:launchMode="singleTop" 20 | android:theme="@style/LaunchTheme" 21 | android:exported="true" 22 | android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" 23 | android:hardwareAccelerated="true" 24 | android:windowSoftInputMode="adjustResize"> 25 | <!-- This keeps the window background of the activity showing 26 | until Flutter renders its first frame. It can be removed if 27 | there is no splash screen (such as the default splash screen 28 | defined in @style/LaunchTheme). --> 29 | <meta-data 30 | android:name="io.flutter.app.android.SplashScreenUntilFirstFrame" 31 | android:value="true" /> 32 | <intent-filter> 33 | <action android:name="android.intent.action.MAIN"/> 34 | <category android:name="android.intent.category.LAUNCHER"/> 35 | </intent-filter> 36 | </activity> 37 | <!-- Don't delete the meta-data below. 38 | This is used by the Flutter tool to generate GeneratedPluginRegistrant.java --> 39 | <meta-data 40 | android:name="flutterEmbedding" 41 | android:value="2" /> 42 | </application> 43 | </manifest> 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-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <!-- Modify this file to customize your launch splash screen --> 3 | <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> 4 | <item android:drawable="?android:colorBackground" /> 5 | 6 | <!-- You can insert your own image assets here --> 7 | <!-- <item> 8 | <bitmap 9 | android:gravity="center" 10 | android:src="@mipmap/launch_image" /> 11 | </item> --> 12 | </layer-list> 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <!-- Modify this file to customize your launch splash screen --> 3 | <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> 4 | <item android:drawable="@android:color/white" /> 5 | 6 | <!-- You can insert your own image assets here --> 7 | <!-- <item> 8 | <bitmap 9 | android:gravity="center" 10 | android:src="@mipmap/launch_image" /> 11 | </item> --> 12 | </layer-list> 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/fcdec77ed84711197dc1371a2cdd9a02c85c3e4b/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/fcdec77ed84711197dc1371a2cdd9a02c85c3e4b/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/fcdec77ed84711197dc1371a2cdd9a02c85c3e4b/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/fcdec77ed84711197dc1371a2cdd9a02c85c3e4b/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/fcdec77ed84711197dc1371a2cdd9a02c85c3e4b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <resources> 3 | <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on --> 4 | <style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar"> 5 | <!-- Show a splash screen on the activity. Automatically removed when 6 | the Flutter engine draws its first frame --> 7 | <item name="android:windowBackground">@drawable/launch_background</item> 8 | </style> 9 | <!-- Theme applied to the Android Window as soon as the process has started. 10 | This theme determines the color of the Android Window while your 11 | Flutter UI initializes, as well as behind your Flutter UI while its 12 | running. 13 | 14 | This Theme is only used starting with V2 of Flutter's Android embedding. --> 15 | <style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar"> 16 | <item name="android:windowBackground">?android:colorBackground</item> 17 | </style> 18 | </resources> 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <resources> 3 | <style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar"> 4 | <!-- Show a splash screen on the activity. Automatically removed when 5 | Flutter draws its first frame --> 6 | <item name="android:windowBackground">@drawable/launch_background</item> 7 | </style> 8 | </resources> 9 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | <manifest xmlns:android="http://schemas.android.com/apk/res/android" 2 | package="com.gsy.shuyu.gsy_flutter_demo"> 3 | <!-- Flutter needs it to communicate with the running application 4 | to allow setting breakpoints, to provide hot reload, etc. 5 | --> 6 | <uses-permission android:name="android.permission.INTERNET"/> 7 | </manifest> 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/build.gradle.kts: -------------------------------------------------------------------------------- 1 | allprojects { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | } 6 | } 7 | 8 | val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get() 9 | rootProject.layout.buildDirectory.value(newBuildDir) 10 | 11 | subprojects { 12 | val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) 13 | project.layout.buildDirectory.value(newSubprojectBuildDir) 14 | } 15 | subprojects { 16 | project.evaluationDependsOn(":app") 17 | } 18 | 19 | tasks.register<Delete>("clean") { 20 | delete(rootProject.layout.buildDirectory) 21 | } 22 | -------------------------------------------------------------------------------- /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" -------------------------------------------------------------------------------- /android/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | val flutterSdkPath = run { 3 | val properties = java.util.Properties() 4 | file("local.properties").inputStream().use { properties.load(it) } 5 | val flutterSdkPath = properties.getProperty("flutter.sdk") 6 | require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } 7 | 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.7.3" apply false 22 | id("org.jetbrains.kotlin.android") version "2.1.0" apply false 23 | } 24 | 25 | include(":app") 26 | -------------------------------------------------------------------------------- /assets/Agne.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/fcdec77ed84711197dc1371a2cdd9a02c85c3e4b/assets/Agne.otf -------------------------------------------------------------------------------- /assets/Bobbers.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/fcdec77ed84711197dc1371a2cdd9a02c85c3e4b/assets/Bobbers.ttf -------------------------------------------------------------------------------- /assets/Canterbury.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/fcdec77ed84711197dc1371a2cdd9a02c85c3e4b/assets/Canterbury.ttf -------------------------------------------------------------------------------- /assets/HelloStockholm.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/fcdec77ed84711197dc1371a2cdd9a02c85c3e4b/assets/HelloStockholm.otf -------------------------------------------------------------------------------- /assets/Horizon.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/fcdec77ed84711197dc1371a2cdd9a02c85c3e4b/assets/Horizon.otf -------------------------------------------------------------------------------- /assets/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/fcdec77ed84711197dc1371a2cdd9a02c85c3e4b/assets/background.png -------------------------------------------------------------------------------- /assets/bglbt.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/fcdec77ed84711197dc1371a2cdd9a02c85c3e4b/assets/bglbt.ttf -------------------------------------------------------------------------------- /demo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/fcdec77ed84711197dc1371a2cdd9a02c85c3e4b/demo.jpg -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 3 | <plist version="1.0"> 4 | <dict> 5 | <key>CFBundleDevelopmentRegion</key> 6 | <string>en</string> 7 | <key>CFBundleExecutable</key> 8 | <string>App</string> 9 | <key>CFBundleIdentifier</key> 10 | <string>io.flutter.flutter.app</string> 11 | <key>CFBundleInfoDictionaryVersion</key> 12 | <string>6.0</string> 13 | <key>CFBundleName</key> 14 | <string>App</string> 15 | <key>CFBundlePackageType</key> 16 | <string>FMWK</string> 17 | <key>CFBundleShortVersionString</key> 18 | <string>1.0</string> 19 | <key>CFBundleSignature</key> 20 | <string>????</string> 21 | <key>CFBundleVersion</key> 22 | <string>1.0</string> 23 | <key>MinimumOSVersion</key> 24 | <string>12.0</string> 25 | </dict> 26 | </plist> 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Flutter/ephemeral/flutter_lldb_helper.py: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | import lldb 6 | 7 | def handle_new_rx_page(frame: lldb.SBFrame, bp_loc, extra_args, intern_dict): 8 | """Intercept NOTIFY_DEBUGGER_ABOUT_RX_PAGES and touch the pages.""" 9 | base = frame.register["x0"].GetValueAsAddress() 10 | page_len = frame.register["x1"].GetValueAsUnsigned() 11 | 12 | # Note: NOTIFY_DEBUGGER_ABOUT_RX_PAGES will check contents of the 13 | # first page to see if handled it correctly. This makes diagnosing 14 | # misconfiguration (e.g. missing breakpoint) easier. 15 | data = bytearray(page_len) 16 | data[0:8] = b'IHELPED!' 17 | 18 | error = lldb.SBError() 19 | frame.GetThread().GetProcess().WriteMemory(base, data, error) 20 | if not error.Success(): 21 | print(f'Failed to write into {base}[+{page_len}]', error) 22 | return 23 | 24 | def __lldb_init_module(debugger: lldb.SBDebugger, _): 25 | target = debugger.GetDummyTarget() 26 | # Caveat: must use BreakpointCreateByRegEx here and not 27 | # BreakpointCreateByName. For some reasons callback function does not 28 | # get carried over from dummy target for the later. 29 | bp = target.BreakpointCreateByRegex("^NOTIFY_DEBUGGER_ABOUT_RX_PAGESquot;) 30 | bp.SetScriptCallbackFunction('{}.handle_new_rx_page'.format(__name__)) 31 | bp.SetAutoContinue(True) 32 | print("-- LLDB integration loaded --") 33 | -------------------------------------------------------------------------------- /ios/Flutter/ephemeral/flutter_lldbinit: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | command script import --relative-to-command-file flutter_lldb_helper.py 6 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <Workspace 3 | version = "1.0"> 4 | <FileRef 5 | location = "self:"> 6 | </FileRef> 7 | </Workspace> 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 3 | <plist version="1.0"> 4 | <dict> 5 | <key>IDEDidComputeMac32BitWarning</key> 6 | <true/> 7 | </dict> 8 | </plist> 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 3 | <plist version="1.0"> 4 | <dict> 5 | <key>PreviewsEnabled</key> 6 | <false/> 7 | </dict> 8 | </plist> 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <Workspace 3 | version = "1.0"> 4 | <FileRef 5 | location = "group:Runner.xcodeproj"> 6 | </FileRef> 7 | </Workspace> 8 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 3 | <plist version="1.0"> 4 | <dict> 5 | <key>IDEDidComputeMac32BitWarning</key> 6 | <true/> 7 | </dict> 8 | </plist> 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 3 | <plist version="1.0"> 4 | <dict> 5 | <key>PreviewsEnabled</key> 6 | <false/> 7 | </dict> 8 | </plist> 9 | -------------------------------------------------------------------------------- /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/fcdec77ed84711197dc1371a2cdd9a02c85c3e4b/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/fcdec77ed84711197dc1371a2cdd9a02c85c3e4b/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/fcdec77ed84711197dc1371a2cdd9a02c85c3e4b/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/fcdec77ed84711197dc1371a2cdd9a02c85c3e4b/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/fcdec77ed84711197dc1371a2cdd9a02c85c3e4b/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/fcdec77ed84711197dc1371a2cdd9a02c85c3e4b/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/fcdec77ed84711197dc1371a2cdd9a02c85c3e4b/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/fcdec77ed84711197dc1371a2cdd9a02c85c3e4b/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/fcdec77ed84711197dc1371a2cdd9a02c85c3e4b/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/fcdec77ed84711197dc1371a2cdd9a02c85c3e4b/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/fcdec77ed84711197dc1371a2cdd9a02c85c3e4b/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/fcdec77ed84711197dc1371a2cdd9a02c85c3e4b/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/fcdec77ed84711197dc1371a2cdd9a02c85c3e4b/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/fcdec77ed84711197dc1371a2cdd9a02c85c3e4b/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/fcdec77ed84711197dc1371a2cdd9a02c85c3e4b/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/fcdec77ed84711197dc1371a2cdd9a02c85c3e4b/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/fcdec77ed84711197dc1371a2cdd9a02c85c3e4b/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/fcdec77ed84711197dc1371a2cdd9a02c85c3e4b/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 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 | <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM"> 3 | <dependencies> 4 | <deployment identifier="iOS"/> 5 | <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/> 6 | </dependencies> 7 | <scenes> 8 | <!--View Controller--> 9 | <scene sceneID="EHf-IW-A2E"> 10 | <objects> 11 | <viewController id="01J-lp-oVM" sceneMemberID="viewController"> 12 | <layoutGuides> 13 | <viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/> 14 | <viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/> 15 | </layoutGuides> 16 | <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3"> 17 | <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> 18 | <subviews> 19 | <imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4"> 20 | </imageView> 21 | </subviews> 22 | <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> 23 | <constraints> 24 | <constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/> 25 | <constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/> 26 | </constraints> 27 | </view> 28 | </viewController> 29 | <placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/> 30 | </objects> 31 | <point key="canvasLocation" x="53" y="375"/> 32 | </scene> 33 | </scenes> 34 | <resources> 35 | <image name="LaunchImage" width="168" height="185"/> 36 | </resources> 37 | </document> 38 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 | <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r"> 3 | <dependencies> 4 | <deployment identifier="iOS"/> 5 | <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/> 6 | </dependencies> 7 | <scenes> 8 | <!--Flutter View Controller--> 9 | <scene sceneID="tne-QT-ifu"> 10 | <objects> 11 | <viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController"> 12 | <layoutGuides> 13 | <viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/> 14 | <viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/> 15 | </layoutGuides> 16 | <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC"> 17 | <rect key="frame" x="0.0" y="0.0" width="600" height="600"/> 18 | <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> 19 | <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/> 20 | </view> 21 | </viewController> 22 | <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/> 23 | </objects> 24 | </scene> 25 | </scenes> 26 | </document> 27 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 3 | <plist version="1.0"> 4 | <dict> 5 | <key>CFBundleDevelopmentRegion</key> 6 | <string>en</string> 7 | <key>CFBundleExecutable</key> 8 | <string>$(EXECUTABLE_NAME)</string> 9 | <key>CFBundleIdentifier</key> 10 | <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> 11 | <key>CFBundleInfoDictionaryVersion</key> 12 | <string>6.0</string> 13 | <key>CFBundleName</key> 14 | <string>gsy_flutter_demo</string> 15 | <key>CFBundlePackageType</key> 16 | <string>APPL</string> 17 | <key>CFBundleShortVersionString</key> 18 | <string>$(FLUTTER_BUILD_NAME)</string> 19 | <key>CFBundleSignature</key> 20 | <string>????</string> 21 | <key>CFBundleVersion</key> 22 | <string>$(FLUTTER_BUILD_NUMBER)</string> 23 | <key>LSRequiresIPhoneOS</key> 24 | <true/> 25 | <key>UILaunchStoryboardName</key> 26 | <string>LaunchScreen</string> 27 | <key>UIMainStoryboardFile</key> 28 | <string>Main</string> 29 | <key>UISupportedInterfaceOrientations</key> 30 | <array> 31 | <string>UIInterfaceOrientationPortrait</string> 32 | <string>UIInterfaceOrientationLandscapeLeft</string> 33 | <string>UIInterfaceOrientationLandscapeRight</string> 34 | </array> 35 | <key>UISupportedInterfaceOrientations~ipad</key> 36 | <array> 37 | <string>UIInterfaceOrientationPortrait</string> 38 | <string>UIInterfaceOrientationPortraitUpsideDown</string> 39 | <string>UIInterfaceOrientationLandscapeLeft</string> 40 | <string>UIInterfaceOrientationLandscapeRight</string> 41 | </array> 42 | <key>UIViewControllerBasedStatusBarAppearance</key> 43 | <false/> 44 | <key>CADisableMinimumFrameDurationOnPhone</key> 45 | <true/> 46 | <key>UIApplicationSupportsIndirectInputEvents</key> 47 | <true/> 48 | </dict> 49 | </plist> 50 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" -------------------------------------------------------------------------------- /ios/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /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<AlignDemoPage> 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<AnimButtonDemoPage> { 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: <Widget>[ 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<AnimJueJinLogoDemoPage> createState() => _AnimJueJinLogoDemoPageState(); 9 | } 10 | 11 | class _AnimJueJinLogoDemoPageState extends State<AnimJueJinLogoDemoPage> { 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<AnimTipDemoPage> { 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<Offset>( 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: <Widget>[ 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<AnimaDemoPage> 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<double>, 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_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<AnimaDemoPage4> { 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: <Widget>[ 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/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<AnimationContainerDemoPage> { 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<AsyncToSyncCallPage> createState() => _AsyncToSyncCallPageState(); 11 | } 12 | 13 | class _AsyncToSyncCallPageState extends State<AsyncToSyncCallPage> { 14 | Completer<void>? _syncCompleter; 15 | 16 | Future<void> _syncWait(Future Function() fn) async { 17 | var currentAsync = _syncCompleter; 18 | final completer = Completer<void>(); 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<void>(); 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: <Widget>[ 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: <Widget>[ 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<Path> { 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<Path> 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/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: <Widget>[ 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<CustomShaderPathDemoPage> createState() => 11 | _CustomShaderPathDemoPageState(); 12 | } 13 | 14 | class _CustomShaderPathDemoPageState extends State<CustomShaderPathDemoPage> { 15 | Future<ui.Image> _loadAssetImage() { 16 | Completer<ui.Image> completer = Completer<ui.Image>(); 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<ui.Image>( 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<Type> 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 <Type>[], 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<Type> 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 <Type>[], 46 | }); 47 | 48 | @override 49 | Iterable<RenderSliver> get childrenInPaintOrder { 50 | if (firstChild == null) return []; 51 | final children = _getChildrenPaintOrder(); 52 | return children; 53 | } 54 | 55 | @override 56 | Iterable<RenderSliver> get childrenInHitTestOrder { 57 | if (firstChild == null) return []; 58 | final children = _getChildrenPaintOrder(); 59 | return children.reversed.toList(); 60 | } 61 | 62 | List<RenderSliver> _getChildrenPaintOrder() { 63 | final List<RenderSliver> 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: <Widget>[ 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<DragImgDemoPage> { 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<Rect> { 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<DropSelectObject> 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<DropSelectObject> 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<DropSelectObject> 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<DropSelectObject> 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<DropSelectObject> 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<DropSelectObject> 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<T extends DropSelectObject> = Widget Function( 9 | BuildContext context, T data); 10 | 11 | const double kDropSelectMenuItemHeight = 45.0; 12 | 13 | class DropSelectListMenu<T extends DropSelectObject> extends DropSelectWidget { 14 | final List<T>? 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<DropSelectWidget> createState() { 27 | return _MenuListState<T>(); 28 | } 29 | } 30 | 31 | class _MenuListState<T extends DropSelectObject> 32 | extends DropSelectState<DropSelectListMenu<T>> { 33 | @override 34 | void initState() { 35 | super.initState(); 36 | } 37 | 38 | Widget buildItem(BuildContext context, int index) { 39 | final List<T> 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<DropSelectObject>? 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<FloatingTouchDemoPage> { 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: <Widget>[ 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<GesturePasswordDemoPage> { 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<int>)? 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<StatefulWidget> createState() => _GesturePasswordState(); 43 | } 44 | 45 | class _GesturePasswordState extends State<GesturePasswordView> { 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_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<Offset> 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<Offset> 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<GesturePasswordPointModel> 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/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: <Widget>[ 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<Movie>( 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<Color>(_ColorTween.color1), 65 | value.get<Color>(_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<ParticleModel> 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<double>(ParticleOffsetProps.x) * size.width, 20 | animation.get<double>(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<ParticlesWidget> { 19 | final Random random = Random(); 20 | 21 | final List<ParticleModel> 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<PngShadowDemoPage> createState() => _PngShadowDemoPageState(); 11 | } 12 | 13 | class _PngShadowDemoPageState extends State<PngShadowDemoPage> { 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: <Widget>[ 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<RefreshDemoPage> { 14 | final int pageSize = 30; 15 | 16 | bool disposed = false; 17 | 18 | List<String> dataList = []; 19 | 20 | final ScrollController _scrollController = ScrollController(); 21 | 22 | final GlobalKey<RefreshIndicatorState> refreshKey = GlobalKey(); 23 | 24 | Future<void> 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<void> 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/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<RichTextDemoPage> { 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<RichTextDemoPage2> { 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: <Widget>[ 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: <InlineSpan>[ 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<RouteDemoPage> createState() => _RouteDemoPageState(); 9 | } 10 | 11 | class _RouteDemoPageState extends State<RouteDemoPage> { 12 | final GlobalKey<NavigatorState> _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<RoutePage> createState() => _RoutePageState(); 87 | } 88 | 89 | class _RoutePageState extends State<RoutePage> { 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<ScrollListenerDemoPage> { 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: <Widget>[ 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<ScrollToIndexDemoPage> { 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<List<int>> 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) => <int>[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<Widget>((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: <Widget>[ 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<ScrollToIndexDemoPage2> { 16 | GlobalKey scrollKey = GlobalKey(); 17 | 18 | ScrollController controller = ScrollController(); 19 | 20 | List<ItemModel> 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<Widget>((data) { 66 | return CardItem(data, key: dataList[data.index].globalKey); 67 | }).toList(), 68 | ), 69 | ), 70 | persistentFooterButtons: <Widget>[ 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/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<SliverTabChildPage> 16 | with AutomaticKeepAliveClientMixin { 17 | GlobalKey<CustomSliverState> 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: <Widget>[ 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<StatusBarDemoPage> { 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<SystemUiOverlayStyle>( 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: <Widget>[ 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<Color>( 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<Color>( 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: <Widget>[ 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<StickDemoPage> { 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_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: <Widget>[ 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<String> 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<TestCenterSliverPage> { 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: <Widget>[ 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<TextSizeDemoPage> { 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: <Widget>[ 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: <Widget>[ 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: <Widget>[ 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 | -------------------------------------------------------------------------------- /linux/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral 2 | -------------------------------------------------------------------------------- /linux/flutter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file controls Flutter-level build steps. It should not be edited. 2 | cmake_minimum_required(VERSION 3.10) 3 | 4 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") 5 | 6 | # Configuration provided via flutter tool. 7 | include(${EPHEMERAL_DIR}/generated_config.cmake) 8 | 9 | # TODO: Move the rest of this into files in ephemeral. See 10 | # https://github.com/flutter/flutter/issues/57146. 11 | 12 | # Serves the same purpose as list(TRANSFORM ... PREPEND ...), 13 | # which isn't available in 3.10. 14 | function(list_prepend LIST_NAME PREFIX) 15 | set(NEW_LIST "") 16 | foreach(element ${${LIST_NAME}}) 17 | list(APPEND NEW_LIST "${PREFIX}${element}") 18 | endforeach(element) 19 | set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) 20 | endfunction() 21 | 22 | # === Flutter Library === 23 | # System-level dependencies. 24 | find_package(PkgConfig REQUIRED) 25 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) 26 | pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) 27 | pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) 28 | 29 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") 30 | 31 | # Published to parent scope for install step. 32 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) 33 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) 34 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) 35 | set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) 36 | 37 | list(APPEND FLUTTER_LIBRARY_HEADERS 38 | "fl_basic_message_channel.h" 39 | "fl_binary_codec.h" 40 | "fl_binary_messenger.h" 41 | "fl_dart_project.h" 42 | "fl_engine.h" 43 | "fl_json_message_codec.h" 44 | "fl_json_method_codec.h" 45 | "fl_message_codec.h" 46 | "fl_method_call.h" 47 | "fl_method_channel.h" 48 | "fl_method_codec.h" 49 | "fl_method_response.h" 50 | "fl_plugin_registrar.h" 51 | "fl_plugin_registry.h" 52 | "fl_standard_message_codec.h" 53 | "fl_standard_method_codec.h" 54 | "fl_string_codec.h" 55 | "fl_value.h" 56 | "fl_view.h" 57 | "flutter_linux.h" 58 | ) 59 | list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") 60 | add_library(flutter INTERFACE) 61 | target_include_directories(flutter INTERFACE 62 | "${EPHEMERAL_DIR}" 63 | ) 64 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") 65 | target_link_libraries(flutter INTERFACE 66 | PkgConfig::GTK 67 | PkgConfig::GLIB 68 | PkgConfig::GIO 69 | ) 70 | add_dependencies(flutter flutter_assemble) 71 | 72 | # === Flutter tool backend === 73 | # _phony_ is a non-existent file to force this command to run every time, 74 | # since currently there's no way to get a full input/output list from the 75 | # flutter tool. 76 | add_custom_command( 77 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} 78 | ${CMAKE_CURRENT_BINARY_DIR}/_phony_ 79 | COMMAND ${CMAKE_COMMAND} -E env 80 | ${FLUTTER_TOOL_ENVIRONMENT} 81 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" 82 | ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} 83 | VERBATIM 84 | ) 85 | add_custom_target(flutter_assemble DEPENDS 86 | "${FLUTTER_LIBRARY}" 87 | ${FLUTTER_LIBRARY_HEADERS} 88 | ) 89 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include <rive_common/rive_plugin.h> 10 | 11 | void fl_register_plugins(FlPluginRegistry* registry) { 12 | g_autoptr(FlPluginRegistrar) rive_common_registrar = 13 | fl_plugin_registry_get_registrar_for_plugin(registry, "RivePlugin"); 14 | rive_plugin_register_with_registrar(rive_common_registrar); 15 | } 16 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include <flutter_linux/flutter_linux.h> 11 | 12 | // Registers Flutter plugins. 13 | void fl_register_plugins(FlPluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | rive_common 7 | ) 8 | 9 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 10 | ) 11 | 12 | set(PLUGIN_BUNDLED_LIBRARIES) 13 | 14 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 15 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) 16 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 17 | list(APPEND PLUGIN_BUNDLED_LIBRARIES lt;TARGET_FILE:${plugin}_plugin>) 18 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 19 | endforeach(plugin) 20 | 21 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 22 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) 23 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 24 | endforeach(ffi_plugin) 25 | -------------------------------------------------------------------------------- /linux/runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | project(runner LANGUAGES CXX) 3 | 4 | # Define the application target. To change its name, change BINARY_NAME in the 5 | # top-level CMakeLists.txt, not the value here, or `flutter run` will no longer 6 | # work. 7 | # 8 | # Any new source files that you add to the application should be added here. 9 | add_executable(${BINARY_NAME} 10 | "main.cc" 11 | "my_application.cc" 12 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 13 | ) 14 | 15 | # Apply the standard set of build settings. This can be removed for applications 16 | # that need different build settings. 17 | apply_standard_settings(${BINARY_NAME}) 18 | 19 | # Add preprocessor definitions for the application ID. 20 | add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") 21 | 22 | # Add dependency libraries. Add any application-specific dependencies here. 23 | target_link_libraries(${BINARY_NAME} PRIVATE flutter) 24 | target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) 25 | 26 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") 27 | -------------------------------------------------------------------------------- /linux/runner/main.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | int main(int argc, char** argv) { 4 | g_autoptr(MyApplication) app = my_application_new(); 5 | return g_application_run(G_APPLICATION(app), argc, argv); 6 | } 7 | -------------------------------------------------------------------------------- /linux/runner/my_application.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_MY_APPLICATION_H_ 2 | #define FLUTTER_MY_APPLICATION_H_ 3 | 4 | #include <gtk/gtk.h> 5 | 6 | G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, 7 | GtkApplication) 8 | 9 | /** 10 | * my_application_new: 11 | * 12 | * Creates a new Flutter-based application. 13 | * 14 | * Returns: a new #MyApplication. 15 | */ 16 | MyApplication* my_application_new(); 17 | 18 | #endif // FLUTTER_MY_APPLICATION_H_ 19 | -------------------------------------------------------------------------------- /macos/.gitignore: -------------------------------------------------------------------------------- 1 | # Flutter-related 2 | **/Flutter/ephemeral/ 3 | **/Pods/ 4 | 5 | # Xcode-related 6 | **/dgph 7 | **/xcuserdata/ 8 | -------------------------------------------------------------------------------- /macos/Flutter/Flutter-Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "ephemeral/Flutter-Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /macos/Flutter/Flutter-Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "ephemeral/Flutter-Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /macos/Flutter/GeneratedPluginRegistrant.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | import FlutterMacOS 6 | import Foundation 7 | 8 | import rive_common 9 | 10 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 11 | RivePlugin.register(with: registry.registrar(forPlugin: "RivePlugin")) 12 | } 13 | -------------------------------------------------------------------------------- /macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 3 | <plist version="1.0"> 4 | <dict> 5 | <key>IDEDidComputeMac32BitWarning</key> 6 | <true/> 7 | </dict> 8 | </plist> 9 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <Workspace 3 | version = "1.0"> 4 | <FileRef 5 | location = "group:Runner.xcodeproj"> 6 | </FileRef> 7 | </Workspace> 8 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 3 | <plist version="1.0"> 4 | <dict> 5 | <key>IDEDidComputeMac32BitWarning</key> 6 | <true/> 7 | </dict> 8 | </plist> 9 | -------------------------------------------------------------------------------- /macos/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | @main 5 | class AppDelegate: FlutterAppDelegate { 6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 7 | return true 8 | } 9 | 10 | override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { 11 | return true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "app_icon_16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "app_icon_32.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "app_icon_32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "app_icon_64.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "app_icon_128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "app_icon_256.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "app_icon_256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "app_icon_512.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "app_icon_512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "app_icon_1024.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/fcdec77ed84711197dc1371a2cdd9a02c85c3e4b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/fcdec77ed84711197dc1371a2cdd9a02c85c3e4b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/fcdec77ed84711197dc1371a2cdd9a02c85c3e4b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/fcdec77ed84711197dc1371a2cdd9a02c85c3e4b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/fcdec77ed84711197dc1371a2cdd9a02c85c3e4b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/fcdec77ed84711197dc1371a2cdd9a02c85c3e4b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/fcdec77ed84711197dc1371a2cdd9a02c85c3e4b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png -------------------------------------------------------------------------------- /macos/Runner/Configs/AppInfo.xcconfig: -------------------------------------------------------------------------------- 1 | // Application-level settings for the Runner target. 2 | // 3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the 4 | // future. If not, the values below would default to using the project name when this becomes a 5 | // 'flutter create' template. 6 | 7 | // The application's name. By default this is also the title of the Flutter window. 8 | PRODUCT_NAME = gsy_flutter_demo 9 | 10 | // The application's bundle identifier 11 | PRODUCT_BUNDLE_IDENTIFIER = com.gsy.shuyu.gsyFlutterDemo 12 | 13 | // The copyright displayed in application information 14 | PRODUCT_COPYRIGHT = Copyright © 2025 com.gsy.shuyu. All rights reserved. 15 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Debug.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Release.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Warnings.xcconfig: -------------------------------------------------------------------------------- 1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings 2 | GCC_WARN_UNDECLARED_SELECTOR = YES 3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES 4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE 5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES 6 | CLANG_WARN_PRAGMA_PACK = YES 7 | CLANG_WARN_STRICT_PROTOTYPES = YES 8 | CLANG_WARN_COMMA = YES 9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES 10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES 11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES 12 | GCC_WARN_SHADOW = YES 13 | CLANG_WARN_UNREACHABLE_CODE = YES 14 | -------------------------------------------------------------------------------- /macos/Runner/DebugProfile.entitlements: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 3 | <plist version="1.0"> 4 | <dict> 5 | <key>com.apple.security.app-sandbox</key> 6 | <true/> 7 | <key>com.apple.security.cs.allow-jit</key> 8 | <true/> 9 | <key>com.apple.security.network.server</key> 10 | <true/> 11 | </dict> 12 | </plist> 13 | -------------------------------------------------------------------------------- /macos/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 3 | <plist version="1.0"> 4 | <dict> 5 | <key>CFBundleDevelopmentRegion</key> 6 | <string>$(DEVELOPMENT_LANGUAGE)</string> 7 | <key>CFBundleExecutable</key> 8 | <string>$(EXECUTABLE_NAME)</string> 9 | <key>CFBundleIconFile</key> 10 | <string></string> 11 | <key>CFBundleIdentifier</key> 12 | <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> 13 | <key>CFBundleInfoDictionaryVersion</key> 14 | <string>6.0</string> 15 | <key>CFBundleName</key> 16 | <string>$(PRODUCT_NAME)</string> 17 | <key>CFBundlePackageType</key> 18 | <string>APPL</string> 19 | <key>CFBundleShortVersionString</key> 20 | <string>$(FLUTTER_BUILD_NAME)</string> 21 | <key>CFBundleVersion</key> 22 | <string>$(FLUTTER_BUILD_NUMBER)</string> 23 | <key>LSMinimumSystemVersion</key> 24 | <string>$(MACOSX_DEPLOYMENT_TARGET)</string> 25 | <key>NSHumanReadableCopyright</key> 26 | <string>$(PRODUCT_COPYRIGHT)</string> 27 | <key>NSMainNibFile</key> 28 | <string>MainMenu</string> 29 | <key>NSPrincipalClass</key> 30 | <string>NSApplication</string> 31 | </dict> 32 | </plist> 33 | -------------------------------------------------------------------------------- /macos/Runner/MainFlutterWindow.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | class MainFlutterWindow: NSWindow { 5 | override func awakeFromNib() { 6 | let flutterViewController = FlutterViewController() 7 | let windowFrame = self.frame 8 | self.contentViewController = flutterViewController 9 | self.setFrame(windowFrame, display: true) 10 | 11 | RegisterGeneratedPlugins(registry: flutterViewController) 12 | 13 | super.awakeFromNib() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /macos/Runner/Release.entitlements: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 3 | <plist version="1.0"> 4 | <dict> 5 | <key>com.apple.security.app-sandbox</key> 6 | <true/> 7 | </dict> 8 | </plist> 9 | -------------------------------------------------------------------------------- /macos/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /privacy.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/fcdec77ed84711197dc1371a2cdd9a02c85c3e4b/privacy.html -------------------------------------------------------------------------------- /shaders/liquid_glass.frag: -------------------------------------------------------------------------------- 1 | #version 460 core 2 | #include <flutter/runtime_effect.glsl> 3 | 4 | // 5 | // ========================= 核心修正 ========================= 6 | // 7 | // 明确声明片段着色器的输出变量。这是必需的。 8 | out vec4 fragColor; 9 | // 10 | // ========================================================== 11 | // 12 | 13 | // 输入变量,由 Flutter 的 Dart 代码传入 14 | uniform vec2 uResolution; // 画面分辨率 (宽, 高) 15 | uniform vec4 uMouse; // 鼠标/触摸位置 (x, y, z, w),z > 0 表示按下 16 | uniform sampler2D uTexture; // 输入的纹理 (iChannel0) 17 | 18 | // 全局宏定义 19 | #define R uResolution.xy 20 | #define PI 3.14159265 21 | #define S smoothstep 22 | #define PX(a) a/R.y 23 | 24 | // 旋转矩阵 25 | mat2 Rot(float a) { 26 | return mat2(cos(a), -sin(a), sin(a), cos(a)); 27 | } 28 | 29 | // 矩形距离函数 30 | float Box(vec2 p, vec2 b) { 31 | vec2 d = abs(p) - b; 32 | return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0); 33 | } 34 | 35 | // “照片”图标的形状 36 | float IconPhoto(vec2 uv) { 37 | float c = 0.0; 38 | for (float i = 0.0; i < 1.0; i += 1.0/8.0) { 39 | vec2 u = uv; 40 | u *= Rot(i * 2.0 * PI); 41 | u += vec2(0.0, PX(40.0)); 42 | float b = Box(u, vec2(PX(0.0), PX(13.0))); 43 | c += S(PX(1.5), 0.0, b - PX(15.0)) * 0.2; 44 | } 45 | return c; 46 | } 47 | 48 | // 液态玻璃模糊效果 49 | vec4 LiquidGlass(sampler2D tex, vec2 uv, float direction, float quality, float size) { 50 | vec2 radius = size / R; 51 | vec4 color = texture(tex, uv); 52 | 53 | for (float d = 0.0; d < PI; d += PI / direction) { 54 | for (float i = 1.0 / quality; i <= 1.0; i += 1.0 / quality) { 55 | color += texture(tex, uv + vec2(cos(d), sin(d)) * radius * i); 56 | } 57 | } 58 | 59 | color /= (quality * direction + 1.0); // +1.0 for the initial color 60 | return color; 61 | } 62 | 63 | // 主图标逻辑 64 | vec4 Icon(vec2 uv) { 65 | float box = Box(uv, vec2(PX(50.0))); 66 | float boxShape = S(PX(1.5), 0.0, box - PX(50.0)); 67 | float boxDisp = S(PX(35.0), 0.0, box - PX(25.0)); 68 | float boxLight = boxShape * S(0.0, PX(30.0), box - PX(40.0)); 69 | float icon = IconPhoto(uv); 70 | return vec4(boxShape, boxDisp, boxLight, icon); 71 | } 72 | 73 | // Flutter 的主函数 74 | void main() { 75 | vec2 uv = FlutterFragCoord().xy / uResolution.xy; 76 | vec2 st = (FlutterFragCoord().xy - 0.5 * R) / R.y; 77 | vec2 M = uMouse.z > 0.0 ? (uMouse.xy - 0.5 * R) / R.y : vec2(0.0); 78 | 79 | vec4 icon = Icon(st - M); 80 | 81 | vec2 uv2 = uv - uMouse.xy / R; 82 | uv2 *= 0.5 + 0.5 * S(0.5, 1.0, icon.y); 83 | uv2 += uMouse.xy / R; 84 | 85 | vec3 col = mix( 86 | texture(uTexture, uv).rgb * 0.8, 87 | 0.2 + LiquidGlass(uTexture, uv2, 10.0, 10.0, 20.0).rgb * 0.7, 88 | icon.x 89 | ); 90 | col += icon.z * 0.9 + icon.w; 91 | 92 | col *= 1.0 - 0.2 * S(PX(80.0), 0.0, Box(st - M + vec2(0.0, PX(40.0)), vec2(PX(50.0)))); 93 | 94 | // 输出最终颜色 (现在这行代码可以正确工作了) 95 | fragColor = vec4(col, 1.0); 96 | } -------------------------------------------------------------------------------- /static/card_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/fcdec77ed84711197dc1371a2cdd9a02c85c3e4b/static/card_down.png -------------------------------------------------------------------------------- /static/card_down_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/fcdec77ed84711197dc1371a2cdd9a02c85c3e4b/static/card_down_2.png -------------------------------------------------------------------------------- /static/card_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/fcdec77ed84711197dc1371a2cdd9a02c85c3e4b/static/card_up.png -------------------------------------------------------------------------------- /static/card_up_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/fcdec77ed84711197dc1371a2cdd9a02c85c3e4b/static/card_up_2.png -------------------------------------------------------------------------------- /static/gsy_cat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/fcdec77ed84711197dc1371a2cdd9a02c85c3e4b/static/gsy_cat.png -------------------------------------------------------------------------------- /static/iOS26.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/fcdec77ed84711197dc1371a2cdd9a02c85c3e4b/static/iOS26.png -------------------------------------------------------------------------------- /static/juejin.riv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/fcdec77ed84711197dc1371a2cdd9a02c85c3e4b/static/juejin.riv -------------------------------------------------------------------------------- /static/person.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/fcdec77ed84711197dc1371a2cdd9a02c85c3e4b/static/person.jpg -------------------------------------------------------------------------------- /static/test.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/fcdec77ed84711197dc1371a2cdd9a02c85c3e4b/static/test.jpeg -------------------------------------------------------------------------------- /static/test_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/fcdec77ed84711197dc1371a2cdd9a02c85c3e4b/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/fcdec77ed84711197dc1371a2cdd9a02c85c3e4b/thanks.jpg -------------------------------------------------------------------------------- /web.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/fcdec77ed84711197dc1371a2cdd9a02c85c3e4b/web.jpg -------------------------------------------------------------------------------- /web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/fcdec77ed84711197dc1371a2cdd9a02c85c3e4b/web/favicon.png -------------------------------------------------------------------------------- /web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/fcdec77ed84711197dc1371a2cdd9a02c85c3e4b/web/icons/Icon-192.png -------------------------------------------------------------------------------- /web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/fcdec77ed84711197dc1371a2cdd9a02c85c3e4b/web/icons/Icon-512.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/fcdec77ed84711197dc1371a2cdd9a02c85c3e4b/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/fcdec77ed84711197dc1371a2cdd9a02c85c3e4b/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | <!DOCTYPE html> 2 | <html> 3 | <head> 4 | <meta charset="UTF-8"> 5 | <title>gsy_flutter_demo</title> 6 | <style> 7 | .loading { 8 | display: flex; 9 | justify-content: center; 10 | align-items: center; 11 | margin: 0; 12 | position: absolute; 13 | top: 50%; 14 | left: 50%; 15 | -ms-transform: translate(-50%, -50%); 16 | transform: translate(-50%, -50%); 17 | } 18 | 19 | .loader { 20 | border: 16px solid #f3f3f3; 21 | border-radius: 50%; 22 | border: 15px solid ; 23 | border-top: 16px solid blue; 24 | border-right: 16px solid white; 25 | border-bottom: 16px solid blue; 26 | border-left: 16px solid white; 27 | width: 120px; 28 | height: 120px; 29 | -webkit-animation: spin 2s linear infinite; 30 | animation: spin 2s linear infinite; 31 | } 32 | 33 | @-webkit-keyframes spin { 34 | 0% { 35 | -webkit-transform: rotate(0deg); 36 | } 37 | 100% { 38 | -webkit-transform: rotate(360deg); 39 | } 40 | } 41 | 42 | @keyframes spin { 43 | 0% { 44 | transform: rotate(0deg); 45 | } 46 | 100% { 47 | transform: rotate(360deg); 48 | } 49 | } 50 | </style> 51 | </head> 52 | <body> 53 | <div class="loading"> 54 | <div class="loader"></div> 55 | </div> 56 | <script src="main.dart.js" type="application/javascript"></script> 57 | </body> 58 | </html> 59 | -------------------------------------------------------------------------------- /web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gsy_flutter_demo", 3 | "short_name": "gsy_flutter_demo", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "src": "icons/Icon-maskable-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /windows/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral/ 2 | 3 | # Visual Studio user-specific files. 4 | *.suo 5 | *.user 6 | *.userosscache 7 | *.sln.docstates 8 | 9 | # Visual Studio build-related files. 10 | x64/ 11 | x86/ 12 | 13 | # Visual Studio cache files 14 | # files ending in .cache can be ignored 15 | *.[Cc]ache 16 | # but keep track of directories ending in .cache 17 | !*.[Cc]ache/ 18 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include <rive_common/rive_plugin.h> 10 | 11 | void RegisterPlugins(flutter::PluginRegistry* registry) { 12 | RivePluginRegisterWithRegistrar( 13 | registry->GetRegistrarForPlugin("RivePlugin")); 14 | } 15 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include <flutter/plugin_registry.h> 11 | 12 | // Registers Flutter plugins. 13 | void RegisterPlugins(flutter::PluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | rive_common 7 | ) 8 | 9 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 10 | ) 11 | 12 | set(PLUGIN_BUNDLED_LIBRARIES) 13 | 14 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 15 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) 16 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 17 | list(APPEND PLUGIN_BUNDLED_LIBRARIES lt;TARGET_FILE:${plugin}_plugin>) 18 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 19 | endforeach(plugin) 20 | 21 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 22 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) 23 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 24 | endforeach(ffi_plugin) 25 | -------------------------------------------------------------------------------- /windows/runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | project(runner LANGUAGES CXX) 3 | 4 | # Define the application target. To change its name, change BINARY_NAME in the 5 | # top-level CMakeLists.txt, not the value here, or `flutter run` will no longer 6 | # work. 7 | # 8 | # Any new source files that you add to the application should be added here. 9 | add_executable(${BINARY_NAME} WIN32 10 | "flutter_window.cpp" 11 | "main.cpp" 12 | "utils.cpp" 13 | "win32_window.cpp" 14 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 15 | "Runner.rc" 16 | "runner.exe.manifest" 17 | ) 18 | 19 | # Apply the standard set of build settings. This can be removed for applications 20 | # that need different build settings. 21 | apply_standard_settings(${BINARY_NAME}) 22 | 23 | # Add preprocessor definitions for the build version. 24 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") 25 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") 26 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") 27 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") 28 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") 29 | 30 | # Disable Windows macros that collide with C++ standard library functions. 31 | target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") 32 | 33 | # Add dependency libraries and include directories. Add any application-specific 34 | # dependencies here. 35 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) 36 | target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") 37 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") 38 | 39 | # Run the Flutter tool portions of the build. This must not be removed. 40 | add_dependencies(${BINARY_NAME} flutter_assemble) 41 | -------------------------------------------------------------------------------- /windows/runner/flutter_window.cpp: -------------------------------------------------------------------------------- 1 | #include "flutter_window.h" 2 | 3 | #include <optional> 4 | 5 | #include "flutter/generated_plugin_registrant.h" 6 | 7 | FlutterWindow::FlutterWindow(const flutter::DartProject& project) 8 | : project_(project) {} 9 | 10 | FlutterWindow::~FlutterWindow() {} 11 | 12 | bool FlutterWindow::OnCreate() { 13 | if (!Win32Window::OnCreate()) { 14 | return false; 15 | } 16 | 17 | RECT frame = GetClientArea(); 18 | 19 | // The size here must match the window dimensions to avoid unnecessary surface 20 | // creation / destruction in the startup path. 21 | flutter_controller_ = std::make_unique<flutter::FlutterViewController>( 22 | frame.right - frame.left, frame.bottom - frame.top, project_); 23 | // Ensure that basic setup of the controller was successful. 24 | if (!flutter_controller_->engine() || !flutter_controller_->view()) { 25 | return false; 26 | } 27 | RegisterPlugins(flutter_controller_->engine()); 28 | SetChildContent(flutter_controller_->view()->GetNativeWindow()); 29 | 30 | flutter_controller_->engine()->SetNextFrameCallback([&]() { 31 | this->Show(); 32 | }); 33 | 34 | // Flutter can complete the first frame before the "show window" callback is 35 | // registered. The following call ensures a frame is pending to ensure the 36 | // window is shown. It is a no-op if the first frame hasn't completed yet. 37 | flutter_controller_->ForceRedraw(); 38 | 39 | return true; 40 | } 41 | 42 | void FlutterWindow::OnDestroy() { 43 | if (flutter_controller_) { 44 | flutter_controller_ = nullptr; 45 | } 46 | 47 | Win32Window::OnDestroy(); 48 | } 49 | 50 | LRESULT 51 | FlutterWindow::MessageHandler(HWND hwnd, UINT const message, 52 | WPARAM const wparam, 53 | LPARAM const lparam) noexcept { 54 | // Give Flutter, including plugins, an opportunity to handle window messages. 55 | if (flutter_controller_) { 56 | std::optional<LRESULT> result = 57 | flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, 58 | lparam); 59 | if (result) { 60 | return *result; 61 | } 62 | } 63 | 64 | switch (message) { 65 | case WM_FONTCHANGE: 66 | flutter_controller_->engine()->ReloadSystemFonts(); 67 | break; 68 | } 69 | 70 | return Win32Window::MessageHandler(hwnd, message, wparam, lparam); 71 | } 72 | -------------------------------------------------------------------------------- /windows/runner/flutter_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_FLUTTER_WINDOW_H_ 2 | #define RUNNER_FLUTTER_WINDOW_H_ 3 | 4 | #include <flutter/dart_project.h> 5 | #include <flutter/flutter_view_controller.h> 6 | 7 | #include <memory> 8 | 9 | #include "win32_window.h" 10 | 11 | // A window that does nothing but host a Flutter view. 12 | class FlutterWindow : public Win32Window { 13 | public: 14 | // Creates a new FlutterWindow hosting a Flutter view running |project|. 15 | explicit FlutterWindow(const flutter::DartProject& project); 16 | virtual ~FlutterWindow(); 17 | 18 | protected: 19 | // Win32Window: 20 | bool OnCreate() override; 21 | void OnDestroy() override; 22 | LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, 23 | LPARAM const lparam) noexcept override; 24 | 25 | private: 26 | // The project to run. 27 | flutter::DartProject project_; 28 | 29 | // The Flutter instance hosted by this window. 30 | std::unique_ptr<flutter::FlutterViewController> flutter_controller_; 31 | }; 32 | 33 | #endif // RUNNER_FLUTTER_WINDOW_H_ 34 | -------------------------------------------------------------------------------- /windows/runner/main.cpp: -------------------------------------------------------------------------------- 1 | #include <flutter/dart_project.h> 2 | #include <flutter/flutter_view_controller.h> 3 | #include <windows.h> 4 | 5 | #include "flutter_window.h" 6 | #include "utils.h" 7 | 8 | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, 9 | _In_ wchar_t *command_line, _In_ int show_command) { 10 | // Attach to console when present (e.g., 'flutter run') or create a 11 | // new console when running with a debugger. 12 | if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { 13 | CreateAndAttachConsole(); 14 | } 15 | 16 | // Initialize COM, so that it is available for use in the library and/or 17 | // plugins. 18 | ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); 19 | 20 | flutter::DartProject project(L"data"); 21 | 22 | std::vector<std::string> command_line_arguments = 23 | GetCommandLineArguments(); 24 | 25 | project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); 26 | 27 | FlutterWindow window(project); 28 | Win32Window::Point origin(10, 10); 29 | Win32Window::Size size(1280, 720); 30 | if (!window.Create(L"gsy_flutter_demo", origin, size)) { 31 | return EXIT_FAILURE; 32 | } 33 | window.SetQuitOnClose(true); 34 | 35 | ::MSG msg; 36 | while (::GetMessage(&msg, nullptr, 0, 0)) { 37 | ::TranslateMessage(&msg); 38 | ::DispatchMessage(&msg); 39 | } 40 | 41 | ::CoUninitialize(); 42 | return EXIT_SUCCESS; 43 | } 44 | -------------------------------------------------------------------------------- /windows/runner/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by Runner.rc 4 | // 5 | #define IDI_APP_ICON 101 6 | 7 | // Next default values for new objects 8 | // 9 | #ifdef APSTUDIO_INVOKED 10 | #ifndef APSTUDIO_READONLY_SYMBOLS 11 | #define _APS_NEXT_RESOURCE_VALUE 102 12 | #define _APS_NEXT_COMMAND_VALUE 40001 13 | #define _APS_NEXT_CONTROL_VALUE 1001 14 | #define _APS_NEXT_SYMED_VALUE 101 15 | #endif 16 | #endif 17 | -------------------------------------------------------------------------------- /windows/runner/resources/app_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/gsy_flutter_demo/fcdec77ed84711197dc1371a2cdd9a02c85c3e4b/windows/runner/resources/app_icon.ico -------------------------------------------------------------------------------- /windows/runner/runner.exe.manifest: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8" standalone="yes"?> 2 | <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> 3 | <application xmlns="urn:schemas-microsoft-com:asm.v3"> 4 | <windowsSettings> 5 | <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness> 6 | </windowsSettings> 7 | </application> 8 | <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> 9 | <application> 10 | <!-- Windows 10 and Windows 11 --> 11 | <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/> 12 | </application> 13 | </compatibility> 14 | </assembly> 15 | -------------------------------------------------------------------------------- /windows/runner/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | #include <flutter_windows.h> 4 | #include <io.h> 5 | #include <stdio.h> 6 | #include <windows.h> 7 | 8 | #include <iostream> 9 | 10 | void CreateAndAttachConsole() { 11 | if (::AllocConsole()) { 12 | FILE *unused; 13 | if (freopen_s(&unused, "CONOUTquot;, "w", stdout)) { 14 | _dup2(_fileno(stdout), 1); 15 | } 16 | if (freopen_s(&unused, "CONOUTquot;, "w", stderr)) { 17 | _dup2(_fileno(stdout), 2); 18 | } 19 | std::ios::sync_with_stdio(); 20 | FlutterDesktopResyncOutputStreams(); 21 | } 22 | } 23 | 24 | std::vector<std::string> GetCommandLineArguments() { 25 | // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. 26 | int argc; 27 | wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); 28 | if (argv == nullptr) { 29 | return std::vector<std::string>(); 30 | } 31 | 32 | std::vector<std::string> command_line_arguments; 33 | 34 | // Skip the first argument as it's the binary name. 35 | for (int i = 1; i < argc; i++) { 36 | command_line_arguments.push_back(Utf8FromUtf16(argv[i])); 37 | } 38 | 39 | ::LocalFree(argv); 40 | 41 | return command_line_arguments; 42 | } 43 | 44 | std::string Utf8FromUtf16(const wchar_t* utf16_string) { 45 | if (utf16_string == nullptr) { 46 | return std::string(); 47 | } 48 | unsigned int target_length = ::WideCharToMultiByte( 49 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 50 | -1, nullptr, 0, nullptr, nullptr) 51 | -1; // remove the trailing null character 52 | int input_length = (int)wcslen(utf16_string); 53 | std::string utf8_string; 54 | if (target_length == 0 || target_length > utf8_string.max_size()) { 55 | return utf8_string; 56 | } 57 | utf8_string.resize(target_length); 58 | int converted_length = ::WideCharToMultiByte( 59 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 60 | input_length, utf8_string.data(), target_length, nullptr, nullptr); 61 | if (converted_length == 0) { 62 | return std::string(); 63 | } 64 | return utf8_string; 65 | } 66 | -------------------------------------------------------------------------------- /windows/runner/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_UTILS_H_ 2 | #define RUNNER_UTILS_H_ 3 | 4 | #include <string> 5 | #include <vector> 6 | 7 | // Creates a console for the process, and redirects stdout and stderr to 8 | // it for both the runner and the Flutter library. 9 | void CreateAndAttachConsole(); 10 | 11 | // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string 12 | // encoded in UTF-8. Returns an empty std::string on failure. 13 | std::string Utf8FromUtf16(const wchar_t* utf16_string); 14 | 15 | // Gets the command line arguments passed in as a std::vector<std::string>, 16 | // encoded in UTF-8. Returns an empty std::vector<std::string> on failure. 17 | std::vector<std::string> GetCommandLineArguments(); 18 | 19 | #endif // RUNNER_UTILS_H_ 20 | --------------------------------------------------------------------------------