├── .github ├── FUNDING.yml └── workflows │ ├── assign-issue.yml │ ├── code-analysis.yml │ └── deploy.yml ├── .gitignore ├── .packages ├── CHANGELOG.md ├── CODEOWNERS ├── LICENSE ├── README-zh.md ├── README.md ├── analysis_options.yaml ├── example ├── .gitignore ├── .metadata ├── README.md ├── analysis_options.yaml ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── example │ │ │ │ │ └── example │ │ │ │ │ └── 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 │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Podfile │ ├── Podfile.lock │ ├── 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 ├── lib │ ├── features │ │ ├── custom_scrollview │ │ │ ├── custom_scrollview_demo │ │ │ │ ├── custom_scrollview_center_demo_page.dart │ │ │ │ ├── custom_scrollview_demo_page.dart │ │ │ │ └── multi_sliver_demo_page.dart │ │ │ └── sliver_appbar_demo │ │ │ │ └── sliver_appbar_demo_page.dart │ │ ├── gridview │ │ │ ├── gridview_ctx_demo │ │ │ │ └── gridview_ctx_demo_page.dart │ │ │ ├── gridview_custom_demo │ │ │ │ └── gridview_custom_demo_page.dart │ │ │ ├── gridview_demo │ │ │ │ └── gridview_demo_page.dart │ │ │ ├── gridview_fixed_height_demo │ │ │ │ └── gridview_fixed_height_demo_page.dart │ │ │ ├── horizontal_gridview_demo │ │ │ │ └── horizontal_gridview_demo_page.dart │ │ │ └── sliver_grid_demo │ │ │ │ └── sliver_grid_demo_page.dart │ │ ├── home │ │ │ └── home_page.dart │ │ ├── listview │ │ │ ├── horizontal_listview_demo │ │ │ │ └── horizontal_listview_page.dart │ │ │ ├── infinite_listview_demo │ │ │ │ └── infinite_listview_page.dart │ │ │ ├── listview_ctx_demo │ │ │ │ └── listview_ctx_demo_page.dart │ │ │ ├── listview_custom_demo │ │ │ │ └── listview_custom_demo_page.dart │ │ │ ├── listview_demo │ │ │ │ └── listview_demo_page.dart │ │ │ ├── listview_dynamic_offset │ │ │ │ └── listview_dynamic_offset_page.dart │ │ │ ├── listview_fixed_height_demo │ │ │ │ └── listview_fixed_height_demo_page.dart │ │ │ └── sliver_list_demo │ │ │ │ └── sliver_list_demo_page.dart │ │ ├── nested_scrollview │ │ │ └── nested_scrollview_demo │ │ │ │ └── nested_scrollview_demo_page.dart │ │ ├── pageview │ │ │ └── pageview_demo │ │ │ │ ├── pageview_demo_page.dart │ │ │ │ ├── pageview_parallax_item_listener_page.dart │ │ │ │ └── pageview_parallax_page.dart │ │ └── scene │ │ │ ├── anchor_demo │ │ │ ├── anchor_page.dart │ │ │ └── anchor_waterfall_page.dart │ │ │ ├── azlist_demo │ │ │ ├── azlist_cursor.dart │ │ │ ├── azlist_index_bar.dart │ │ │ ├── azlist_item_view.dart │ │ │ ├── azlist_model.dart │ │ │ └── azlist_page.dart │ │ │ ├── chat_demo │ │ │ ├── helper │ │ │ │ └── chat_data_helper.dart │ │ │ ├── model │ │ │ │ └── chat_model.dart │ │ │ ├── page │ │ │ │ ├── chat_gpt_page.dart │ │ │ │ └── chat_page.dart │ │ │ └── widget │ │ │ │ ├── chat_item_widget.dart │ │ │ │ └── chat_unread_tip_view.dart │ │ │ ├── expandable_carousel_slider_demo │ │ │ └── expandable_carousel_slider_demo.dart │ │ │ ├── image_tab_demo │ │ │ └── image_tab_page.dart │ │ │ ├── scrollview_form_demo │ │ │ └── scrollview_form_demo_page.dart │ │ │ ├── video_auto_play_list │ │ │ ├── video_list_auto_play_page.dart │ │ │ └── widgets │ │ │ │ └── video_widget.dart │ │ │ ├── visibility_demo │ │ │ ├── mixin │ │ │ │ └── visibility_exposure_mixin.dart │ │ │ └── page │ │ │ │ ├── visibility_listview_page.dart │ │ │ │ └── visibility_scrollview_page.dart │ │ │ ├── waterfall_flow_demo │ │ │ ├── waterfall_flow_grid_item_view.dart │ │ │ ├── waterfall_flow_page.dart │ │ │ ├── waterfall_flow_swipe_view.dart │ │ │ └── waterfall_flow_type.dart │ │ │ └── waterfall_flow_fixed_height_demo │ │ │ └── waterfall_flow_fixed_height_page.dart │ ├── main.dart │ ├── typedefs.dart │ ├── utils │ │ ├── keyboard.dart │ │ ├── random.dart │ │ └── snackbar.dart │ └── widgets │ │ ├── animation.dart │ │ └── sliver.dart ├── linux │ └── flutter │ │ ├── generated_plugin_registrant.cc │ │ ├── generated_plugin_registrant.h │ │ └── generated_plugins.cmake ├── macos │ ├── .gitignore │ ├── Flutter │ │ ├── Flutter-Debug.xcconfig │ │ ├── Flutter-Release.xcconfig │ │ └── GeneratedPluginRegistrant.swift │ ├── Podfile │ ├── Podfile.lock │ ├── 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 ├── pubspec.lock ├── pubspec.yaml ├── test │ └── widget_test.dart ├── 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 ├── lib ├── scrollview_observer.dart └── src │ ├── common │ ├── models │ │ ├── observe_displaying_child_model.dart │ │ ├── observe_displaying_child_model_mixin.dart │ │ ├── observe_find_child_model.dart │ │ ├── observe_model.dart │ │ ├── observe_scroll_child_model.dart │ │ ├── observe_scroll_to_index_result_model.dart │ │ ├── observer_handle_contexts_result_model.dart │ │ └── observer_index_position_model.dart │ ├── observer_controller.dart │ ├── observer_listener.dart │ ├── observer_notification_result.dart │ ├── observer_typedef.dart │ ├── observer_widget.dart │ ├── observer_widget_scope.dart │ ├── observer_widget_tag_manager.dart │ └── typedefs.dart │ ├── gridview │ ├── grid_observer_controller.dart │ ├── grid_observer_notification_result.dart │ ├── grid_observer_view.dart │ └── models │ │ ├── gridview_observe_displaying_child_model.dart │ │ └── gridview_observe_model.dart │ ├── listview │ ├── list_observer_controller.dart │ ├── list_observer_notification_result.dart │ ├── list_observer_view.dart │ └── models │ │ ├── listview_observe_displaying_child_model.dart │ │ └── listview_observe_model.dart │ ├── notification.dart │ ├── observer_core.dart │ ├── sliver │ ├── models │ │ ├── sliver_observer_observe_result_model.dart │ │ ├── sliver_viewport_observe_displaying_child_model.dart │ │ └── sliver_viewport_observe_model.dart │ ├── sliver_observer_controller.dart │ ├── sliver_observer_listener.dart │ ├── sliver_observer_notification_result.dart │ └── sliver_observer_view.dart │ └── utils │ ├── observer_utils.dart │ └── src │ ├── chat │ ├── chat_observer_scroll_physics.dart │ ├── chat_observer_scroll_physics_mixin.dart │ ├── chat_scroll_observer.dart │ ├── chat_scroll_observer_model.dart │ └── chat_scroll_observer_typedefs.dart │ ├── extends.dart │ ├── log.dart │ ├── nested_scroll_util.dart │ ├── observer_utils.dart │ └── slivers.dart ├── listview_observer.iml ├── pubspec.lock ├── pubspec.yaml └── test ├── chat_scroll_observer_test.dart ├── grid_observer_test.dart ├── list_observer_test.dart ├── observer_utils_test.dart └── sliver_observer_test.dart /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | # github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | # patreon: # Replace with a single Patreon username 5 | # open_collective: # Replace with a single Open Collective username 6 | # ko_fi: # Replace with a single Ko-fi username 7 | # tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | # community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | # liberapay: # Replace with a single Liberapay username 10 | # issuehunt: # Replace with a single IssueHunt username 11 | # otechie: # Replace with a single Otechie username 12 | # lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | # custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | 15 | ko_fi: linxunfeng 16 | -------------------------------------------------------------------------------- /.github/workflows/assign-issue.yml: -------------------------------------------------------------------------------- 1 | name: Issue assignment 2 | 3 | on: 4 | issues: 5 | types: [opened] 6 | 7 | jobs: 8 | auto-assign: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: 'Auto-assign issue' 12 | uses: pozil/auto-assign-issue@v1 13 | with: 14 | repo-token: ${{ secrets.GITHUB_TOKEN }} 15 | assignees: LinXunFeng -------------------------------------------------------------------------------- /.github/workflows/code-analysis.yml: -------------------------------------------------------------------------------- 1 | name: Code Analysis 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - main 8 | paths-ignore: 9 | - '**.md' 10 | pull_request: 11 | branches: 12 | - main 13 | paths-ignore: 14 | - '**.md' 15 | 16 | jobs: 17 | code-analysis: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v4 21 | - uses: subosito/flutter-action@v2 22 | with: 23 | channel: 'stable' 24 | - name: Prepare dependencies 25 | run: | 26 | flutter --version 27 | flutter pub get 28 | - name: Check Dart code formatting 29 | run: | 30 | dart format . -o none --set-exit-if-changed 31 | - name: Analyze Dart code 32 | run: | 33 | flutter analyze . 34 | - name: Generate dartdoc 35 | run: | 36 | dart pub global activate dartdoc 37 | dart pub global run dartdoc . 38 | 39 | test: 40 | needs: [code-analysis] 41 | runs-on: ubuntu-latest 42 | strategy: 43 | matrix: 44 | flutter-version: [''] 45 | steps: 46 | - uses: actions/checkout@v4 47 | - uses: subosito/flutter-action@v2 48 | with: 49 | channel: 'stable' 50 | flutter-version: ${{ matrix.flutter-version }} 51 | - name: Prepare dependencies 52 | run: | 53 | flutter --version 54 | flutter pub get 55 | - name: Test 56 | run: flutter test -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to GitHub Pages 2 | on: 3 | push: 4 | branches: 5 | - main 6 | # - develop 7 | jobs: 8 | build: 9 | name: Build Web 10 | env: 11 | my_secret: ${{secrets.commit_secret}} 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: subosito/flutter-action@v2 16 | with: 17 | # flutter-version: '3.16.9' 18 | channel: 'stable' 19 | - name: Flutter build web 20 | working-directory: ./example 21 | run: | 22 | flutter config --enable-web 23 | flutter pub get 24 | flutter build web --release --base-href /flutter_scrollview_observer/ 25 | - name: Deploy 26 | uses: peaceiris/actions-gh-pages@v3 27 | with: 28 | github_token: ${{ secrets.GITHUB_TOKEN }} 29 | publish_dir: ./example/build/web 30 | force_orphan: true 31 | user_name: 'github-ci[bot]' 32 | user_email: 'github-actions[bot]@users.noreply.github.com' 33 | commit_message: 'Publish to gh-pages' -------------------------------------------------------------------------------- /.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 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | # .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | 43 | # Android Studio will place build artifacts here 44 | /android/app/debug 45 | /android/app/profile 46 | /android/app/release 47 | -------------------------------------------------------------------------------- /.packages: -------------------------------------------------------------------------------- 1 | # This file is deprecated. Tools should instead consume 2 | # `.dart_tool/package_config.json`. 3 | # 4 | # For more info see: https://dart.dev/go/dot-packages-deprecation 5 | # 6 | # Generated by pub on 2023-03-02 22:51:15.844107. 7 | async:file:///Users/lxf/.pub-cache/hosted/pub.dartlang.org/async-2.9.0/lib/ 8 | boolean_selector:file:///Users/lxf/.pub-cache/hosted/pub.dartlang.org/boolean_selector-2.1.0/lib/ 9 | characters:file:///Users/lxf/.pub-cache/hosted/pub.dartlang.org/characters-1.2.0/lib/ 10 | charcode:file:///Users/lxf/.pub-cache/hosted/pub.dartlang.org/charcode-1.3.1/lib/ 11 | clock:file:///Users/lxf/.pub-cache/hosted/pub.dartlang.org/clock-1.1.0/lib/ 12 | collection:file:///Users/lxf/.pub-cache/hosted/pub.dartlang.org/collection-1.16.0/lib/ 13 | fake_async:file:///Users/lxf/.pub-cache/hosted/pub.dartlang.org/fake_async-1.3.0/lib/ 14 | flutter:file:///Users/lxf/fvm/versions/3.1.0/packages/flutter/lib/ 15 | flutter_lints:file:///Users/lxf/.pub-cache/hosted/pub.dartlang.org/flutter_lints-1.0.4/lib/ 16 | flutter_test:file:///Users/lxf/fvm/versions/3.1.0/packages/flutter_test/lib/ 17 | lints:file:///Users/lxf/.pub-cache/hosted/pub.dartlang.org/lints-1.0.1/lib/ 18 | matcher:file:///Users/lxf/.pub-cache/hosted/pub.dartlang.org/matcher-0.12.11/lib/ 19 | material_color_utilities:file:///Users/lxf/.pub-cache/hosted/pub.dartlang.org/material_color_utilities-0.1.4/lib/ 20 | meta:file:///Users/lxf/.pub-cache/hosted/pub.dartlang.org/meta-1.7.0/lib/ 21 | path:file:///Users/lxf/.pub-cache/hosted/pub.dartlang.org/path-1.8.1/lib/ 22 | sky_engine:file:///Users/lxf/fvm/versions/3.1.0/bin/cache/pkg/sky_engine/lib/ 23 | source_span:file:///Users/lxf/.pub-cache/hosted/pub.dartlang.org/source_span-1.8.2/lib/ 24 | stack_trace:file:///Users/lxf/.pub-cache/hosted/pub.dartlang.org/stack_trace-1.10.0/lib/ 25 | stream_channel:file:///Users/lxf/.pub-cache/hosted/pub.dartlang.org/stream_channel-2.1.0/lib/ 26 | string_scanner:file:///Users/lxf/.pub-cache/hosted/pub.dartlang.org/string_scanner-1.1.0/lib/ 27 | term_glyph:file:///Users/lxf/.pub-cache/hosted/pub.dartlang.org/term_glyph-1.2.0/lib/ 28 | test_api:file:///Users/lxf/.pub-cache/hosted/pub.dartlang.org/test_api-0.4.9/lib/ 29 | vector_math:file:///Users/lxf/.pub-cache/hosted/pub.dartlang.org/vector_math-2.1.2/lib/ 30 | scrollview_observer:lib/ 31 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @LinXunFeng 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 LinXunFeng 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. -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml 2 | 3 | # Additional information about this file can be found at 4 | # https://dart.dev/guides/language/analysis-options 5 | -------------------------------------------------------------------------------- /example/.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 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | 43 | # Android Studio will place build artifacts here 44 | /android/app/debug 45 | /android/app/profile 46 | /android/app/release 47 | -------------------------------------------------------------------------------- /example/.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. 5 | 6 | version: 7 | revision: bcea432bce54a83306b3c00a7ad0ed98f777348d 8 | channel: beta 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: bcea432bce54a83306b3c00a7ad0ed98f777348d 17 | base_revision: bcea432bce54a83306b3c00a7ad0ed98f777348d 18 | - platform: macos 19 | create_revision: bcea432bce54a83306b3c00a7ad0ed98f777348d 20 | base_revision: bcea432bce54a83306b3c00a7ad0ed98f777348d 21 | 22 | # User provided section 23 | 24 | # List of Local paths (relative to this file) that should be 25 | # ignored by the migrate tool. 26 | # 27 | # Files that are not part of the templates will be ignored by default. 28 | unmanaged_files: 29 | - 'lib/main.dart' 30 | - 'ios/Runner.xcodeproj/project.pbxproj' 31 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # example 2 | 3 | A new Flutter project. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) 13 | 14 | For help getting started with Flutter, view our 15 | [online documentation](https://flutter.dev/docs), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. -------------------------------------------------------------------------------- /example/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at 17 | # https://dart-lang.github.io/linter/lints/index.html. 18 | # 19 | # Instead of disabling a lint rule for the entire project in the 20 | # section below, it can also be suppressed for a single line of code 21 | # or a specific dart file by using the `// ignore: name_of_lint` and 22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 23 | # producing the lint. 24 | rules: 25 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 26 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 27 | 28 | # Additional information about this file can be found at 29 | # https://dart.dev/guides/language/analysis-options 30 | -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion flutter.compileSdkVersion 30 | 31 | compileOptions { 32 | sourceCompatibility JavaVersion.VERSION_1_8 33 | targetCompatibility JavaVersion.VERSION_1_8 34 | } 35 | 36 | kotlinOptions { 37 | jvmTarget = '1.8' 38 | } 39 | 40 | sourceSets { 41 | main.java.srcDirs += 'src/main/kotlin' 42 | } 43 | 44 | defaultConfig { 45 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 46 | applicationId "com.example.example" 47 | namespace("com.example.example") 48 | minSdkVersion flutter.minSdkVersion 49 | targetSdkVersion flutter.targetSdkVersion 50 | versionCode flutterVersionCode.toInteger() 51 | versionName flutterVersionName 52 | } 53 | 54 | buildTypes { 55 | release { 56 | // TODO: Add your own signing config for the release build. 57 | // Signing with the debug keys for now, so `flutter run --release` works. 58 | signingConfig signingConfigs.debug 59 | } 60 | } 61 | } 62 | 63 | flutter { 64 | source '../..' 65 | } 66 | 67 | dependencies { 68 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 69 | } 70 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 15 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 30 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/com/example/example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.example 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_scrollview_observer/4a8f9ca5abf87fa55ab0a71a1558bb309b2bc5fc/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_scrollview_observer/4a8f9ca5abf87fa55ab0a71a1558bb309b2bc5fc/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_scrollview_observer/4a8f9ca5abf87fa55ab0a71a1558bb309b2bc5fc/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_scrollview_observer/4a8f9ca5abf87fa55ab0a71a1558bb309b2bc5fc/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_scrollview_observer/4a8f9ca5abf87fa55ab0a71a1558bb309b2bc5fc/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.7.10' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:8.5.2' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | mavenCentral() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | tasks.register("clean", Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 --add-exports=java.base/sun.nio.ch=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-exports=jdk.unsupported/sun.misc=ALL-UNNAMEDz -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Jan 27 23:32:07 CST 2025 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /example/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 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 12.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '12.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | end 36 | 37 | post_install do |installer| 38 | installer.pods_project.targets.each do |target| 39 | flutter_additional_ios_build_settings(target) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /example/ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Flutter (1.0.0) 3 | - video_player_avfoundation (0.0.1): 4 | - Flutter 5 | - FlutterMacOS 6 | 7 | DEPENDENCIES: 8 | - Flutter (from `Flutter`) 9 | - video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`) 10 | 11 | EXTERNAL SOURCES: 12 | Flutter: 13 | :path: Flutter 14 | video_player_avfoundation: 15 | :path: ".symlinks/plugins/video_player_avfoundation/darwin" 16 | 17 | SPEC CHECKSUMS: 18 | Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 19 | video_player_avfoundation: 2cef49524dd1f16c5300b9cd6efd9611ce03639b 20 | 21 | PODFILE CHECKSUM: c4c93c5f6502fe2754f48404d3594bf779584011 22 | 23 | COCOAPODS: 1.16.2 24 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/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: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/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 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_scrollview_observer/4a8f9ca5abf87fa55ab0a71a1558bb309b2bc5fc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_scrollview_observer/4a8f9ca5abf87fa55ab0a71a1558bb309b2bc5fc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_scrollview_observer/4a8f9ca5abf87fa55ab0a71a1558bb309b2bc5fc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_scrollview_observer/4a8f9ca5abf87fa55ab0a71a1558bb309b2bc5fc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_scrollview_observer/4a8f9ca5abf87fa55ab0a71a1558bb309b2bc5fc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_scrollview_observer/4a8f9ca5abf87fa55ab0a71a1558bb309b2bc5fc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_scrollview_observer/4a8f9ca5abf87fa55ab0a71a1558bb309b2bc5fc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_scrollview_observer/4a8f9ca5abf87fa55ab0a71a1558bb309b2bc5fc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_scrollview_observer/4a8f9ca5abf87fa55ab0a71a1558bb309b2bc5fc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_scrollview_observer/4a8f9ca5abf87fa55ab0a71a1558bb309b2bc5fc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_scrollview_observer/4a8f9ca5abf87fa55ab0a71a1558bb309b2bc5fc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_scrollview_observer/4a8f9ca5abf87fa55ab0a71a1558bb309b2bc5fc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_scrollview_observer/4a8f9ca5abf87fa55ab0a71a1558bb309b2bc5fc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_scrollview_observer/4a8f9ca5abf87fa55ab0a71a1558bb309b2bc5fc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_scrollview_observer/4a8f9ca5abf87fa55ab0a71a1558bb309b2bc5fc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/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 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_scrollview_observer/4a8f9ca5abf87fa55ab0a71a1558bb309b2bc5fc/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_scrollview_observer/4a8f9ca5abf87fa55ab0a71a1558bb309b2bc5fc/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_scrollview_observer/4a8f9ca5abf87fa55ab0a71a1558bb309b2bc5fc/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/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. -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | Example 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | example 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(FLUTTER_BUILD_NUMBER) 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UIViewControllerBasedStatusBarAppearance 45 | 46 | CADisableMinimumFrameDurationOnPhone 47 | 48 | UIApplicationSupportsIndirectInputEvents 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /example/lib/features/gridview/gridview_ctx_demo/gridview_ctx_demo_page.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/LinXunFeng/flutter_scrollview_observer 4 | * @Date: 2022-08-08 00:20:03 5 | */ 6 | import 'package:flutter/material.dart'; 7 | import 'package:scrollview_observer/scrollview_observer.dart'; 8 | import 'package:scrollview_observer_example/typedefs.dart'; 9 | 10 | class GridViewCtxDemoPage extends StatefulWidget { 11 | const GridViewCtxDemoPage({Key? key}) : super(key: key); 12 | 13 | @override 14 | State createState() => _GridViewCtxDemoPageState(); 15 | } 16 | 17 | class _GridViewCtxDemoPageState extends State { 18 | BuildContext? _sliverGridViewContext; 19 | 20 | List _hitIndexs = []; 21 | 22 | @override 23 | void initState() { 24 | super.initState(); 25 | 26 | // Trigger an observation manually 27 | ambiguate(WidgetsBinding.instance)?.addPostFrameCallback((timeStamp) { 28 | GridViewOnceObserveNotification().dispatch(_sliverGridViewContext); 29 | }); 30 | } 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | return Scaffold( 35 | appBar: AppBar(title: const Text("GridView")), 36 | body: GridViewObserver( 37 | sliverGridContexts: () { 38 | return [if (_sliverGridViewContext != null) _sliverGridViewContext!]; 39 | }, 40 | onObserveAll: (resultMap) { 41 | final model = resultMap[_sliverGridViewContext]; 42 | if (model == null) return; 43 | setState(() { 44 | _hitIndexs = model.firstGroupChildList.map((e) => e.index).toList(); 45 | }); 46 | 47 | debugPrint( 48 | 'firstGroupChildList -- ${model.firstGroupChildList.map((e) => e.index)}'); 49 | debugPrint('displaying -- ${model.displayingChildIndexList}'); 50 | }, 51 | child: _buildGridView(), 52 | ), 53 | ); 54 | } 55 | 56 | Widget _buildGridView() { 57 | return GridView.builder( 58 | padding: const EdgeInsets.only(top: 1000, bottom: 1000), 59 | controller: ScrollController(initialScrollOffset: 1000), 60 | gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( 61 | crossAxisCount: 2, 62 | crossAxisSpacing: 2, 63 | mainAxisSpacing: 5, 64 | ), 65 | // gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( 66 | // maxCrossAxisExtent: 140.0, 67 | // childAspectRatio: 0.6, 68 | // crossAxisSpacing: 2, 69 | // mainAxisSpacing: 5, 70 | // ), 71 | itemBuilder: (context, index) { 72 | if (_sliverGridViewContext != context) { 73 | _sliverGridViewContext = context; 74 | } 75 | return Container( 76 | color: (_hitIndexs.contains(index)) ? Colors.red : Colors.blue[100], 77 | child: Center( 78 | child: Text('index -- $index'), 79 | ), 80 | ); 81 | }, 82 | itemCount: 50, 83 | ); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /example/lib/features/gridview/gridview_demo/gridview_demo_page.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/LinXunFeng/flutter_scrollview_observer 4 | * @Date: 2022-08-08 00:20:03 5 | */ 6 | import 'package:flutter/material.dart'; 7 | import 'package:scrollview_observer/scrollview_observer.dart'; 8 | import 'package:scrollview_observer_example/typedefs.dart'; 9 | import 'package:scrollview_observer_example/utils/snackbar.dart'; 10 | 11 | class GridViewDemoPage extends StatefulWidget { 12 | const GridViewDemoPage({Key? key}) : super(key: key); 13 | 14 | @override 15 | State createState() => _GridViewDemoPageState(); 16 | } 17 | 18 | class _GridViewDemoPageState extends State { 19 | static const double _leadingPadding = 1000; 20 | static const double _trailingPadding = 100; 21 | static const EdgeInsets _padding = EdgeInsets.only( 22 | top: _leadingPadding, 23 | bottom: _trailingPadding, 24 | ); 25 | 26 | List _hitIndexs = [0, 1]; 27 | 28 | ScrollController scrollController = 29 | ScrollController(initialScrollOffset: _leadingPadding); 30 | 31 | late GridObserverController observerController; 32 | 33 | @override 34 | void initState() { 35 | super.initState(); 36 | 37 | observerController = GridObserverController(controller: scrollController); 38 | 39 | // Trigger an observation manually 40 | ambiguate(WidgetsBinding.instance)?.endOfFrame.then( 41 | (_) { 42 | if (mounted) { 43 | // After layout 44 | observerController.dispatchOnceObserve(); 45 | } 46 | }, 47 | ); 48 | } 49 | 50 | @override 51 | void dispose() { 52 | observerController.controller?.dispose(); 53 | super.dispose(); 54 | } 55 | 56 | @override 57 | Widget build(BuildContext context) { 58 | return Scaffold( 59 | appBar: AppBar(title: const Text("GridView")), 60 | body: GridViewObserver( 61 | controller: observerController, 62 | onObserve: (result) { 63 | final model = result; 64 | setState(() { 65 | _hitIndexs = model.firstGroupChildList.map((e) => e.index).toList(); 66 | }); 67 | 68 | debugPrint( 69 | 'firstGroupChildList -- ${model.firstGroupChildList.map((e) => e.index)}'); 70 | debugPrint('displaying -- ${model.displayingChildIndexList}'); 71 | }, 72 | child: _buildGridView(), 73 | ), 74 | floatingActionButton: FloatingActionButton( 75 | child: const Icon(Icons.airline_stops_outlined), 76 | onPressed: () { 77 | SnackBarUtil.showSnackBar( 78 | context: context, 79 | text: 'Jump to item 49', 80 | ); 81 | observerController.jumpTo( 82 | index: 49, 83 | padding: _padding, 84 | ); 85 | // observerController.animateTo( 86 | // index: 49, 87 | // duration: const Duration(seconds: 1), 88 | // curve: Curves.ease, 89 | // ); 90 | }, 91 | ), 92 | ); 93 | } 94 | 95 | Widget _buildGridView() { 96 | return GridView.builder( 97 | padding: _padding, 98 | controller: scrollController, 99 | gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( 100 | crossAxisCount: 2, 101 | crossAxisSpacing: 2, 102 | mainAxisSpacing: 5, 103 | ), 104 | itemBuilder: (context, index) { 105 | return Container( 106 | color: (_hitIndexs.contains(index)) ? Colors.red : Colors.blue[100], 107 | child: Center( 108 | child: Text('index -- $index'), 109 | ), 110 | ); 111 | }, 112 | itemCount: 50, 113 | ); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /example/lib/features/gridview/gridview_fixed_height_demo/gridview_fixed_height_demo_page.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/LinXunFeng/flutter_scrollview_observer 4 | * @Date: 2022-08-08 00:20:03 5 | */ 6 | import 'package:flutter/material.dart'; 7 | import 'package:scrollview_observer/scrollview_observer.dart'; 8 | import 'package:scrollview_observer_example/typedefs.dart'; 9 | import 'package:scrollview_observer_example/utils/snackbar.dart'; 10 | 11 | class GridViewFixedHeightDemoPage extends StatefulWidget { 12 | const GridViewFixedHeightDemoPage({Key? key}) : super(key: key); 13 | 14 | @override 15 | State createState() => 16 | _GridViewFixedHeightDemoPageState(); 17 | } 18 | 19 | class _GridViewFixedHeightDemoPageState 20 | extends State { 21 | static const double _leadingPadding = 1000; 22 | static const double _trailingPadding = 100; 23 | static const EdgeInsets _padding = EdgeInsets.only( 24 | top: _leadingPadding, 25 | bottom: _trailingPadding, 26 | left: 30, 27 | right: 30, 28 | ); 29 | 30 | List _hitIndexs = [0, 1]; 31 | 32 | ScrollController scrollController = 33 | ScrollController(initialScrollOffset: _leadingPadding); 34 | 35 | late GridObserverController observerController; 36 | 37 | @override 38 | void initState() { 39 | super.initState(); 40 | 41 | observerController = GridObserverController(controller: scrollController); 42 | 43 | // Trigger an observation manually 44 | ambiguate(WidgetsBinding.instance)?.endOfFrame.then( 45 | (_) { 46 | if (mounted) { 47 | // After layout 48 | observerController.dispatchOnceObserve(); 49 | } 50 | }, 51 | ); 52 | } 53 | 54 | @override 55 | void dispose() { 56 | observerController.controller?.dispose(); 57 | super.dispose(); 58 | } 59 | 60 | @override 61 | Widget build(BuildContext context) { 62 | return Scaffold( 63 | appBar: AppBar(title: const Text("GridView")), 64 | body: GridViewObserver( 65 | controller: observerController, 66 | onObserve: (result) { 67 | final model = result; 68 | setState(() { 69 | _hitIndexs = model.firstGroupChildList.map((e) => e.index).toList(); 70 | }); 71 | 72 | debugPrint( 73 | 'firstGroupChildList -- ${model.firstGroupChildList.map((e) => e.index)}'); 74 | debugPrint('displaying -- ${model.displayingChildIndexList}'); 75 | }, 76 | child: _buildGridView(), 77 | ), 78 | floatingActionButton: FloatingActionButton( 79 | child: const Icon(Icons.airline_stops_outlined), 80 | onPressed: () { 81 | SnackBarUtil.showSnackBar( 82 | context: context, 83 | text: 'Jump to item 21', 84 | ); 85 | observerController.jumpTo( 86 | index: 21, 87 | padding: _padding, 88 | isFixedHeight: true, 89 | ); 90 | }, 91 | ), 92 | ); 93 | } 94 | 95 | Widget _buildGridView() { 96 | return GridView.builder( 97 | padding: _padding, 98 | controller: scrollController, 99 | gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( 100 | crossAxisCount: 2, 101 | crossAxisSpacing: 2, 102 | mainAxisSpacing: 5, 103 | ), 104 | itemBuilder: (context, index) { 105 | return Container( 106 | color: (_hitIndexs.contains(index)) ? Colors.red : Colors.blue[100], 107 | child: Center( 108 | child: Text('index -- $index'), 109 | ), 110 | ); 111 | }, 112 | itemCount: 50, 113 | ); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /example/lib/features/listview/horizontal_listview_demo/horizontal_listview_page.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/LinXunFeng/flutter_scrollview_observer 4 | * @Date: 2022-08-08 00:20:03 5 | */ 6 | import 'package:flutter/material.dart'; 7 | import 'package:scrollview_observer/scrollview_observer.dart'; 8 | import 'package:scrollview_observer_example/typedefs.dart'; 9 | 10 | class HorizontalListViewPage extends StatefulWidget { 11 | const HorizontalListViewPage({Key? key}) : super(key: key); 12 | 13 | @override 14 | State createState() => _HorizontalListViewPageState(); 15 | } 16 | 17 | class _HorizontalListViewPageState extends State { 18 | BuildContext? _sliverListViewContext; 19 | 20 | int _hitIndex = 0; 21 | 22 | @override 23 | void initState() { 24 | super.initState(); 25 | 26 | // Trigger an observation manually 27 | ambiguate(WidgetsBinding.instance)?.addPostFrameCallback((timeStamp) { 28 | ListViewOnceObserveNotification().dispatch(_sliverListViewContext); 29 | }); 30 | } 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | return Scaffold( 35 | appBar: AppBar(title: const Text("ListView")), 36 | body: ListViewObserver( 37 | child: _buildListView(), 38 | sliverListContexts: () { 39 | return [if (_sliverListViewContext != null) _sliverListViewContext!]; 40 | }, 41 | onObserveAll: (resultMap) { 42 | final model = resultMap[_sliverListViewContext]; 43 | if (model == null) return; 44 | 45 | debugPrint('firstChild.index -- ${model.firstChild?.index ?? 0}'); 46 | debugPrint('displaying -- ${model.displayingChildIndexList}'); 47 | setState(() { 48 | _hitIndex = model.firstChild?.index ?? 0; 49 | }); 50 | }, 51 | ), 52 | ); 53 | } 54 | 55 | ListView _buildListView() { 56 | return ListView.separated( 57 | itemBuilder: (ctx, index) { 58 | if (_sliverListViewContext != ctx) { 59 | _sliverListViewContext = ctx; 60 | } 61 | return _buildListItemView(index); 62 | }, 63 | separatorBuilder: (ctx, index) { 64 | return _buildSeparatorView(); 65 | }, 66 | itemCount: 50, 67 | scrollDirection: Axis.horizontal, 68 | ); 69 | } 70 | 71 | Widget _buildListItemView(int index) { 72 | return Container( 73 | width: (index % 2 == 0) ? 180 : 150, 74 | color: _hitIndex == index ? Colors.red : Colors.black12, 75 | child: Center( 76 | child: Text( 77 | "index -- $index", 78 | style: TextStyle( 79 | color: _hitIndex == index ? Colors.white : Colors.black, 80 | ), 81 | ), 82 | ), 83 | ); 84 | } 85 | 86 | Container _buildSeparatorView() { 87 | return Container( 88 | color: Colors.white, 89 | width: 5, 90 | ); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /example/lib/features/listview/listview_ctx_demo/listview_ctx_demo_page.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/LinXunFeng/flutter_scrollview_observer 4 | * @Date: 2022-08-08 00:20:03 5 | */ 6 | import 'package:flutter/material.dart'; 7 | import 'package:scrollview_observer/scrollview_observer.dart'; 8 | import 'package:scrollview_observer_example/typedefs.dart'; 9 | 10 | class ListViewCtxDemoPage extends StatefulWidget { 11 | const ListViewCtxDemoPage({Key? key}) : super(key: key); 12 | 13 | @override 14 | State createState() => _ListViewCtxDemoPageState(); 15 | } 16 | 17 | class _ListViewCtxDemoPageState extends State { 18 | BuildContext? _sliverListViewContext; 19 | 20 | int _hitIndex = 0; 21 | 22 | @override 23 | void initState() { 24 | super.initState(); 25 | 26 | // Trigger an observation manually 27 | ambiguate(WidgetsBinding.instance)?.addPostFrameCallback((timeStamp) { 28 | ListViewOnceObserveNotification().dispatch(_sliverListViewContext); 29 | }); 30 | } 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | return Scaffold( 35 | appBar: AppBar(title: const Text("ListView")), 36 | body: ListViewObserver( 37 | child: _buildListView(), 38 | sliverListContexts: () { 39 | return [if (_sliverListViewContext != null) _sliverListViewContext!]; 40 | }, 41 | onObserveAll: (resultMap) { 42 | final model = resultMap[_sliverListViewContext]; 43 | if (model == null) return; 44 | 45 | debugPrint('visible -- ${model.visible}'); 46 | debugPrint('firstChild.index -- ${model.firstChild?.index}'); 47 | debugPrint('displaying -- ${model.displayingChildIndexList}'); 48 | setState(() { 49 | _hitIndex = model.firstChild?.index ?? 0; 50 | }); 51 | }, 52 | ), 53 | ); 54 | } 55 | 56 | ListView _buildListView() { 57 | // return ListView.builder( 58 | // padding: EdgeInsets.zero, 59 | // itemCount: 200, 60 | // itemBuilder: (ctx, index) { 61 | // if (_sliverListViewContext != ctx) { 62 | // _sliverListViewContext = ctx; 63 | // } 64 | // return _buildListItemView(index); 65 | // }, 66 | // ); 67 | 68 | return ListView.separated( 69 | padding: const EdgeInsets.only(top: 1000, bottom: 1000), 70 | controller: ScrollController(initialScrollOffset: 1000), 71 | itemBuilder: (ctx, index) { 72 | if (_sliverListViewContext != ctx) { 73 | _sliverListViewContext = ctx; 74 | } 75 | return _buildListItemView(index); 76 | }, 77 | separatorBuilder: (ctx, index) { 78 | return _buildSeparatorView(); 79 | }, 80 | itemCount: 50, 81 | ); 82 | } 83 | 84 | Widget _buildListItemView(int index) { 85 | return Container( 86 | height: (index % 2 == 0) ? 80 : 50, 87 | color: _hitIndex == index ? Colors.red : Colors.black12, 88 | child: Center( 89 | child: Text( 90 | "index -- $index", 91 | style: TextStyle( 92 | color: _hitIndex == index ? Colors.white : Colors.black, 93 | ), 94 | ), 95 | ), 96 | ); 97 | } 98 | 99 | Container _buildSeparatorView() { 100 | return Container( 101 | color: Colors.white, 102 | height: 5, 103 | ); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /example/lib/features/listview/listview_custom_demo/listview_custom_demo_page.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/LinXunFeng/flutter_scrollview_observer 4 | * @Date: 2023-05-21 10:31:44 5 | */ 6 | // ignore: implementation_imports 7 | import 'package:extended_list/src/rendering/sliver_list.dart'; 8 | import 'package:flutter/material.dart'; 9 | import 'package:loading_more_list/loading_more_list.dart'; 10 | import 'package:scrollview_observer/scrollview_observer.dart'; 11 | import 'package:scrollview_observer_example/utils/snackbar.dart'; 12 | 13 | class ListViewCustomDemoPage extends StatefulWidget { 14 | const ListViewCustomDemoPage({Key? key}) : super(key: key); 15 | 16 | @override 17 | State createState() => _ListViewCustomDemoPageState(); 18 | } 19 | 20 | class _ListViewCustomDemoPageState extends State { 21 | ScrollController scrollController = ScrollController(); 22 | 23 | late ListObserverController observerController; 24 | 25 | @override 26 | void initState() { 27 | observerController = ListObserverController( 28 | controller: scrollController, 29 | ); 30 | super.initState(); 31 | } 32 | 33 | @override 34 | Widget build(BuildContext context) { 35 | return Scaffold( 36 | appBar: AppBar(title: const Text('Custom')), 37 | body: ListViewObserver( 38 | child: _buildListView(), 39 | controller: observerController, 40 | customTargetRenderSliverType: (renderObj) { 41 | // Here you tell the package what type of RenderObject it needs to observe. 42 | return renderObj is ExtendedRenderSliverList; 43 | }, 44 | // customHandleObserve: (context) { 45 | // // Here you can customize the observation logic. 46 | // return ObserverCore.handleListObserve( 47 | // context: context, 48 | // fetchLeadingOffset: () => 100, 49 | // ); 50 | // }, 51 | onObserve: (resultModel) { 52 | debugPrint('firstChild.index -- ${resultModel.firstChild?.index}'); 53 | debugPrint('displaying -- ${resultModel.displayingChildIndexList}'); 54 | }, 55 | ), 56 | floatingActionButton: FloatingActionButton( 57 | child: const Icon(Icons.airline_stops_sharp), 58 | onPressed: () { 59 | SnackBarUtil.showSnackBar( 60 | context: context, 61 | text: 'Jump to row 10', 62 | ); 63 | observerController.jumpTo( 64 | index: 10, 65 | isFixedHeight: true, 66 | ); 67 | }, 68 | ), 69 | ); 70 | } 71 | 72 | Widget _buildListView() { 73 | return LoadingMoreList( 74 | ListConfig( 75 | controller: scrollController, 76 | itemBuilder: (context, item, index) { 77 | if (scrollController.hasClients && 78 | (observerController.sliverContexts.isEmpty || 79 | observerController.sliverContexts.first != context)) { 80 | observerController.reattach(); 81 | } 82 | return ListTile( 83 | title: Text('index - $index'), 84 | ); 85 | }, 86 | sourceList: SourceList(), 87 | ), 88 | ); 89 | } 90 | } 91 | 92 | class SourceList extends LoadingMoreBase { 93 | @override 94 | Future loadData([bool isloadMoreAction = false]) async { 95 | await Future.delayed(const Duration(seconds: 2)); 96 | for (var i = 0; i < 30; i++) { 97 | add(i); 98 | } 99 | return true; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /example/lib/features/scene/anchor_demo/anchor_page.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/LinXunFeng/flutter_scrollview_observer 4 | * @Date: 2022-08-08 00:20:03 5 | */ 6 | import 'package:flutter/material.dart'; 7 | import 'package:scrollview_observer/scrollview_observer.dart'; 8 | 9 | class AnchorListPage extends StatefulWidget { 10 | const AnchorListPage({Key? key}) : super(key: key); 11 | 12 | @override 13 | State createState() => _AnchorListPageState(); 14 | } 15 | 16 | class _AnchorListPageState extends State 17 | with SingleTickerProviderStateMixin { 18 | ScrollController scrollController = ScrollController(); 19 | 20 | late ListObserverController observerController; 21 | late TabController _tabController; 22 | List tabs = ["News(0)", "History(5)", "Picture(10)"]; 23 | List tabIndexs = [0, 5, 10]; 24 | 25 | @override 26 | void initState() { 27 | super.initState(); 28 | observerController = ListObserverController(controller: scrollController); 29 | _tabController = TabController(length: tabs.length, vsync: this); 30 | } 31 | 32 | @override 33 | void dispose() { 34 | observerController.controller?.dispose(); 35 | _tabController.dispose(); 36 | super.dispose(); 37 | } 38 | 39 | @override 40 | Widget build(BuildContext context) { 41 | return Scaffold( 42 | appBar: AppBar( 43 | title: const Text("Anchor ListView"), 44 | bottom: PreferredSize( 45 | preferredSize: const Size(double.infinity, 44), 46 | child: TabBar( 47 | controller: _tabController, 48 | tabs: tabs.map((e) => Tab(text: e)).toList(), 49 | onTap: (index) { 50 | observerController.animateTo( 51 | index: tabIndexs[index], 52 | duration: const Duration(milliseconds: 250), 53 | curve: Curves.ease, 54 | ); 55 | }, 56 | ), 57 | ), 58 | ), 59 | body: ListViewObserver( 60 | controller: observerController, 61 | child: _buildListView(), 62 | onObserve: (resultModel) { 63 | _tabController.index = ObserverUtils.calcAnchorTabIndex( 64 | observeModel: resultModel, 65 | tabIndexs: tabIndexs, 66 | currentTabIndex: _tabController.index, 67 | ); 68 | }, 69 | ), 70 | ); 71 | } 72 | 73 | ListView _buildListView() { 74 | return ListView.separated( 75 | controller: scrollController, 76 | itemBuilder: (ctx, index) { 77 | return _buildListItemView(index); 78 | }, 79 | separatorBuilder: (ctx, index) { 80 | return _buildSeparatorView(); 81 | }, 82 | itemCount: 50, 83 | ); 84 | } 85 | 86 | Widget _buildListItemView(int index) { 87 | return Container( 88 | height: 300, 89 | color: Colors.black12, 90 | child: Center( 91 | child: Text( 92 | "index -- $index", 93 | style: const TextStyle( 94 | color: Colors.black, 95 | ), 96 | ), 97 | ), 98 | ); 99 | } 100 | 101 | Container _buildSeparatorView() { 102 | return Container( 103 | color: Colors.white, 104 | height: 5, 105 | ); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /example/lib/features/scene/anchor_demo/anchor_waterfall_page.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/LinXunFeng/flutter_scrollview_observer 4 | * @Date: 2023-05-27 11:51:24 5 | */ 6 | 7 | import 'package:flutter/material.dart'; 8 | import 'package:scrollview_observer/scrollview_observer.dart'; 9 | import 'package:waterfall_flow/waterfall_flow.dart'; 10 | 11 | class AnchorWaterfallPage extends StatefulWidget { 12 | const AnchorWaterfallPage({Key? key}) : super(key: key); 13 | 14 | @override 15 | State createState() => _AnchorWaterfallPageState(); 16 | } 17 | 18 | class _AnchorWaterfallPageState extends State 19 | with SingleTickerProviderStateMixin { 20 | ScrollController scrollController = ScrollController(); 21 | 22 | late GridObserverController observerController; 23 | late TabController _tabController; 24 | List tabs = ["News(0)", "History(5)", "Picture(10)"]; 25 | List tabIndexs = [0, 5, 10]; 26 | 27 | @override 28 | void initState() { 29 | super.initState(); 30 | observerController = GridObserverController(controller: scrollController); 31 | _tabController = TabController(length: tabs.length, vsync: this); 32 | } 33 | 34 | @override 35 | void dispose() { 36 | observerController.controller?.dispose(); 37 | _tabController.dispose(); 38 | super.dispose(); 39 | } 40 | 41 | @override 42 | Widget build(BuildContext context) { 43 | return Scaffold( 44 | appBar: AppBar( 45 | title: const Text("Anchor Waterfall"), 46 | bottom: PreferredSize( 47 | preferredSize: const Size(double.infinity, 44), 48 | child: TabBar( 49 | controller: _tabController, 50 | tabs: tabs.map((e) => Tab(text: e)).toList(), 51 | onTap: (index) { 52 | observerController.animateTo( 53 | index: tabIndexs[index], 54 | duration: const Duration(milliseconds: 250), 55 | curve: Curves.ease, 56 | ); 57 | }, 58 | ), 59 | ), 60 | ), 61 | body: GridViewObserver( 62 | child: _buildGridView(), 63 | controller: observerController, 64 | customTargetRenderSliverType: (renderObject) { 65 | return renderObject is RenderSliverWaterfallFlow; 66 | }, 67 | onObserve: (resultModel) { 68 | debugPrint( 69 | 'firstGroupChildIndexList -- ${resultModel.firstGroupChildList.map((e) => e.index).toList()}'); 70 | debugPrint( 71 | 'displayingChildIndexList -- ${resultModel.displayingChildIndexList}'); 72 | 73 | _tabController.index = ObserverUtils.calcAnchorTabIndex( 74 | observeModel: resultModel, 75 | tabIndexs: tabIndexs, 76 | currentTabIndex: _tabController.index, 77 | ); 78 | }, 79 | ), 80 | ); 81 | } 82 | 83 | Widget _buildGridView() { 84 | return WaterfallFlow.builder( 85 | controller: scrollController, 86 | gridDelegate: const SliverWaterfallFlowDelegateWithFixedCrossAxisCount( 87 | crossAxisCount: 2, 88 | mainAxisSpacing: 15, 89 | crossAxisSpacing: 10, 90 | ), 91 | itemBuilder: (BuildContext context, int index) { 92 | return Container( 93 | alignment: Alignment.center, 94 | color: Colors.teal[100 * (index % 9)], 95 | child: Text('grid item $index'), 96 | height: 50.0 + 100.0 * (index % 9), 97 | ); 98 | }, 99 | itemCount: 20, 100 | ); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /example/lib/features/scene/azlist_demo/azlist_cursor.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/fluttercandies/flutter_scrollview_observer 4 | * @Date: 2023-10-28 15:56:01 5 | 6 | */ 7 | import 'package:flutter/material.dart'; 8 | 9 | class AzListCursor extends StatelessWidget { 10 | final double size; 11 | 12 | final String title; 13 | 14 | final double arrowSize = 30; 15 | 16 | const AzListCursor({ 17 | Key? key, 18 | required this.size, 19 | required this.title, 20 | }) : super(key: key); 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | return Stack( 25 | clipBehavior: Clip.none, 26 | children: [ 27 | _buildTitle(), 28 | Positioned( 29 | right: -arrowSize * 0.5 - 2.5, 30 | top: (size - arrowSize) * 0.5, 31 | child: _buildArrow(), 32 | ), 33 | ], 34 | ); 35 | } 36 | 37 | Widget _buildArrow() { 38 | Widget resultWidget = Icon( 39 | Icons.arrow_right, 40 | color: Colors.black54, 41 | size: arrowSize, 42 | ); 43 | return resultWidget; 44 | } 45 | 46 | Widget _buildTitle() { 47 | Widget resultWidget = Text( 48 | title, 49 | style: const TextStyle(color: Colors.white, fontSize: 32), 50 | ); 51 | resultWidget = Container( 52 | width: size, 53 | height: size, 54 | alignment: Alignment.center, 55 | decoration: BoxDecoration( 56 | color: Colors.black54, 57 | borderRadius: BorderRadius.circular(5), 58 | ), 59 | child: resultWidget, 60 | ); 61 | return resultWidget; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /example/lib/features/scene/azlist_demo/azlist_item_view.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/fluttercandies/flutter_scrollview_observer 4 | * @Date: 2023-10-28 19:43:04 5 | */ 6 | import 'package:flutter/material.dart'; 7 | 8 | class AzListItemView extends StatelessWidget { 9 | const AzListItemView({ 10 | Key? key, 11 | required this.name, 12 | this.isShowSeparator = true, 13 | }) : super(key: key); 14 | 15 | final String name; 16 | 17 | final bool isShowSeparator; 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return Container( 22 | color: Colors.white, 23 | child: Container( 24 | height: 50, 25 | decoration: BoxDecoration( 26 | border: isShowSeparator 27 | ? Border( 28 | bottom: BorderSide( 29 | color: Colors.grey[300]!, 30 | width: 0.5, 31 | ), 32 | ) 33 | : null, 34 | ), 35 | alignment: Alignment.centerLeft, 36 | margin: const EdgeInsets.only(left: 16.0), 37 | child: Text( 38 | name, 39 | style: const TextStyle(color: Colors.black), 40 | ), 41 | ), 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /example/lib/features/scene/azlist_demo/azlist_model.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/fluttercandies/flutter_scrollview_observer 4 | * @Date: 2023-10-28 10:19:23 5 | */ 6 | 7 | import 'package:flutter/material.dart'; 8 | 9 | class AzListContactModel { 10 | final String section; 11 | final List names; 12 | 13 | AzListContactModel({ 14 | required this.section, 15 | required this.names, 16 | }); 17 | } 18 | 19 | class AzListCursorInfoModel { 20 | final String title; 21 | final Offset offset; 22 | 23 | AzListCursorInfoModel({ 24 | required this.title, 25 | required this.offset, 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /example/lib/features/scene/chat_demo/helper/chat_data_helper.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/LinXunFeng/flutter_scrollview_observer 4 | * @Date: 2022-09-27 22:49:41 5 | */ 6 | 7 | import 'dart:math'; 8 | import 'package:scrollview_observer_example/features/scene/chat_demo/model/chat_model.dart'; 9 | 10 | class ChatDataHelper { 11 | static List chatContents = [ 12 | 'My name is LinXunFeng', 13 | 'Twitter: https://twitter.com/xunfenghellolo' 14 | 'Github: https://github.com/LinXunFeng', 15 | 'Blog: https://fullstackaction.com/', 16 | 'Juejin: https://juejin.cn/user/1820446984512392/posts', 17 | 'Artile: Flutter-获取ListView当前正在显示的Widget信息\nhttps://juejin.cn/post/7103058155692621837', 18 | 'Artile: Flutter-列表滚动定位超强辅助库,墙裂推荐!🔥\nhttps://juejin.cn/post/7129888644290068487', 19 | 'A widget for observing data related to the child widgets being displayed in a scrollview.\nhttps://github.com/LinXunFeng/flutter_scrollview_observer', 20 | '📱 Swifty screen adaptation solution (Support Objective-C and Swift)\nhttps://github.com/LinXunFeng/SwiftyFitsize' 21 | ]; 22 | 23 | static ChatModel createChatModel({ 24 | bool? isOwn, 25 | }) { 26 | final random = Random(); 27 | final content = 28 | ChatDataHelper.chatContents[random.nextInt(chatContents.length)]; 29 | return ChatModel( 30 | isOwn: isOwn ?? random.nextBool(), 31 | content: content, 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /example/lib/features/scene/chat_demo/model/chat_model.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/LinXunFeng/flutter_scrollview_observer 4 | * @Date: 2022-09-25 21:41:13 5 | */ 6 | 7 | class ChatModel { 8 | ChatModel({ 9 | required this.isOwn, 10 | required this.content, 11 | }); 12 | final bool isOwn; 13 | final String content; 14 | } 15 | -------------------------------------------------------------------------------- /example/lib/features/scene/chat_demo/widget/chat_item_widget.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/LinXunFeng/flutter_scrollview_observer 4 | * @Date: 2022-09-27 22:46:36 5 | */ 6 | 7 | import 'package:flutter/material.dart'; 8 | import 'package:scrollview_observer_example/features/scene/chat_demo/model/chat_model.dart'; 9 | 10 | class ChatItemWidget extends StatelessWidget { 11 | const ChatItemWidget({ 12 | Key? key, 13 | required this.chatModel, 14 | required this.index, 15 | required this.itemCount, 16 | this.onRemove, 17 | }) : super(key: key); 18 | 19 | final ChatModel chatModel; 20 | final int index; 21 | final int itemCount; 22 | final Function? onRemove; 23 | 24 | @override 25 | Widget build(BuildContext context) { 26 | final isOwn = chatModel.isOwn; 27 | final nickName = isOwn ? 'LXF' : 'LQR'; 28 | Widget resultWidget = Row( 29 | textDirection: isOwn ? TextDirection.ltr : TextDirection.rtl, 30 | crossAxisAlignment: CrossAxisAlignment.start, 31 | children: [ 32 | Container( 33 | width: 40, 34 | height: 40, 35 | decoration: BoxDecoration( 36 | borderRadius: BorderRadius.circular(20), 37 | color: isOwn ? Colors.blue : Colors.white30, 38 | ), 39 | child: Center( 40 | child: Text( 41 | nickName, 42 | style: const TextStyle( 43 | color: Colors.white, 44 | ), 45 | ), 46 | ), 47 | ), 48 | const SizedBox(width: 10), 49 | Expanded( 50 | child: Container( 51 | padding: const EdgeInsets.all(10), 52 | decoration: BoxDecoration( 53 | color: isOwn 54 | ? const Color.fromARGB(255, 21, 125, 200) 55 | : const Color.fromARGB(255, 39, 39, 38), 56 | borderRadius: BorderRadius.circular(4), 57 | ), 58 | child: Text( 59 | '------------ ${itemCount - index} ------------ \n ${chatModel.content}', 60 | style: const TextStyle( 61 | fontSize: 15, 62 | fontWeight: FontWeight.w500, 63 | color: Colors.white, 64 | ), 65 | ), 66 | ), 67 | ), 68 | const SizedBox(width: 50), 69 | ], 70 | ); 71 | resultWidget = Column( 72 | children: [ 73 | resultWidget, 74 | const SizedBox(height: 15), 75 | ], 76 | ); 77 | resultWidget = Dismissible( 78 | key: UniqueKey(), 79 | child: resultWidget, 80 | onDismissed: (_) { 81 | onRemove?.call(); 82 | }, 83 | ); 84 | return resultWidget; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /example/lib/features/scene/chat_demo/widget/chat_unread_tip_view.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/LinXunFeng/flutter_scrollview_observer 4 | * @Date: 2022-10-31 15:40:04 5 | */ 6 | 7 | import 'package:flutter/material.dart'; 8 | 9 | class ChatUnreadTipView extends StatelessWidget { 10 | ChatUnreadTipView({ 11 | Key? key, 12 | required this.unreadMsgCount, 13 | this.onTap, 14 | }) : super(key: key); 15 | 16 | final int unreadMsgCount; 17 | 18 | final Color primaryColor = Colors.green[100]!; 19 | 20 | final GestureTapCallback? onTap; 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | if (unreadMsgCount == 0) return const SizedBox.shrink(); 25 | Widget resultWidget = Stack( 26 | children: [ 27 | const Icon( 28 | Icons.mode_comment, 29 | size: 50, 30 | color: Colors.white, 31 | ), 32 | Container( 33 | margin: const EdgeInsets.only(top: 12), 34 | width: 50, 35 | child: Center( 36 | child: Text( 37 | '$unreadMsgCount', 38 | style: const TextStyle( 39 | color: Colors.blue, 40 | fontSize: 17, 41 | fontWeight: FontWeight.bold, 42 | ), 43 | ), 44 | ), 45 | ), 46 | ], 47 | ); 48 | resultWidget = GestureDetector( 49 | child: resultWidget, 50 | onTap: onTap, 51 | ); 52 | return resultWidget; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /example/lib/features/scene/video_auto_play_list/video_list_auto_play_page.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/LinXunFeng/flutter_scrollview_observer 4 | * @Date: 2022-07-03 15:46:45 5 | */ 6 | import 'package:flutter/material.dart'; 7 | import 'package:scrollview_observer/scrollview_observer.dart'; 8 | import 'package:scrollview_observer_example/features/scene/video_auto_play_list/widgets/video_widget.dart'; 9 | 10 | class VideoListAutoPlayPage extends StatefulWidget { 11 | const VideoListAutoPlayPage({Key? key}) : super(key: key); 12 | 13 | @override 14 | State createState() => _VideoListAutoPlayPageState(); 15 | } 16 | 17 | class _VideoListAutoPlayPageState extends State { 18 | BuildContext? _ctx1; 19 | 20 | int _hitIndex = 0; 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | return Scaffold( 25 | appBar: AppBar(title: const Text("Video Auto Play")), 26 | body: ListViewObserver( 27 | child: _buildListView(), 28 | sliverListContexts: () { 29 | return [if (_ctx1 != null) _ctx1!]; 30 | }, 31 | onObserveAll: (resultMap) { 32 | final model = resultMap[_ctx1]; 33 | if (model == null) return; 34 | 35 | if (_hitIndex != model.firstChild?.index) { 36 | _hitIndex = model.firstChild?.index ?? 0; 37 | setState(() {}); 38 | } 39 | }, 40 | leadingOffset: 200, 41 | ), 42 | ); 43 | } 44 | 45 | ListView _buildListView() { 46 | return ListView.separated( 47 | itemBuilder: (ctx, index) { 48 | _ctx1 = ctx; 49 | return _buildListItemView(index); 50 | }, 51 | separatorBuilder: (ctx, index) { 52 | return _buildSeparatorView(); 53 | }, 54 | itemCount: 50, 55 | ); 56 | } 57 | 58 | Widget _buildListItemView(int index) { 59 | return SizedBox( 60 | height: 300, 61 | child: _hitIndex == index 62 | ? const VideoWidget( 63 | url: 'https://www.w3schools.com/html/movie.mp4', 64 | ) 65 | : Container( 66 | color: Colors.blue[100], 67 | child: const Center(child: Text('placeholder')), 68 | ), 69 | ); 70 | } 71 | 72 | Container _buildSeparatorView() { 73 | return Container( 74 | color: Colors.white, 75 | height: 8, 76 | ); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /example/lib/features/scene/video_auto_play_list/widgets/video_widget.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/LinXunFeng/flutter_scrollview_observer 4 | * @Date: 2022-05-28 14:08:53 5 | */ 6 | import 'package:flutter/material.dart'; 7 | import 'package:video_player/video_player.dart'; 8 | 9 | class VideoWidget extends StatefulWidget { 10 | final String url; 11 | 12 | const VideoWidget({ 13 | Key? key, 14 | required this.url, 15 | }) : super(key: key); 16 | 17 | @override 18 | State createState() => _VideoWidgetState(); 19 | } 20 | 21 | class _VideoWidgetState extends State { 22 | late VideoPlayerController _controller; 23 | late Future _initializeVideoPlayerFuture; 24 | 25 | @override 26 | void initState() { 27 | super.initState(); 28 | _controller = VideoPlayerController.networkUrl(Uri.parse(widget.url)); 29 | _initializeVideoPlayerFuture = _controller.initialize().then((_) { 30 | setState(() {}); 31 | }); 32 | 33 | _controller.play(); 34 | _controller.setLooping(true); 35 | } 36 | 37 | @override 38 | void dispose() { 39 | _controller.dispose(); 40 | super.dispose(); 41 | } 42 | 43 | @override 44 | void didUpdateWidget(VideoWidget oldWidget) { 45 | _controller.play(); 46 | _controller.setLooping(true); 47 | super.didUpdateWidget(oldWidget); 48 | } 49 | 50 | @override 51 | Widget build(BuildContext context) { 52 | return FutureBuilder( 53 | future: _initializeVideoPlayerFuture, 54 | builder: (ctx, snapshot) { 55 | if (snapshot.connectionState == ConnectionState.done) { 56 | return VideoPlayer(_controller); 57 | } 58 | return const Center(child: CircularProgressIndicator()); 59 | }, 60 | ); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /example/lib/features/scene/visibility_demo/mixin/visibility_exposure_mixin.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/LinXunFeng/flutter_scrollview_observer 4 | * @Date: 2023-08-24 22:55:19 5 | */ 6 | 7 | import 'package:scrollview_observer/scrollview_observer.dart'; 8 | 9 | mixin VisibilityExposureMixin { 10 | // Exposure record 11 | Map exposureRecordMap = {}; 12 | 13 | /// Reset exposure record 14 | resetExposureRecordMap() { 15 | exposureRecordMap.clear(); 16 | } 17 | 18 | /// Handling the exposure logic of ScrollView item 19 | /// 20 | /// [resultModel] Observation result (the super class is ObserveModel, 21 | /// pass the value obtained in the onObserve callback, or the value obtained 22 | /// according to BuildContext in onObserveAll). 23 | /// [toExposeDisplayPercent] When the self-display ratio exceeds this value, 24 | /// it is regarded as exposure and recorded, otherwise the exposure record 25 | /// is reset. 26 | /// [recordKeyCallback] Return the key used to record the exposure, if not 27 | /// implemented, use index as the key. 28 | /// [needExposeCallback] Whether to participate in the callback of exposure 29 | /// calculation. 30 | /// [toExposeCallback] Callback for exposure conditions met. 31 | handleExposure({ 32 | required dynamic resultModel, 33 | double toExposeDisplayPercent = 0.5, 34 | dynamic Function(int index)? recordKeyCallback, 35 | bool Function(int index)? needExposeCallback, 36 | required Function(int index) toExposeCallback, 37 | }) { 38 | List displayingChildModelList = []; 39 | if (resultModel is ListViewObserveModel) { 40 | displayingChildModelList = resultModel.displayingChildModelList; 41 | } else if (resultModel is GridViewObserveModel) { 42 | displayingChildModelList = resultModel.displayingChildModelList; 43 | } 44 | for (var displayingChildModel in displayingChildModelList) { 45 | final index = displayingChildModel.index; 46 | final recordKey = recordKeyCallback?.call(index) ?? index; 47 | // By letting the outside tell us whether ScrollView item need to 48 | // participate in the exposure calculation logic 49 | final needExpose = needExposeCallback?.call(index) ?? true; 50 | if (!needExpose) continue; 51 | // debugPrint('item : $index - ${displayingChildModel.displayPercentage}'); 52 | // Determine whether the percentage displayed by the item exceeds 53 | // [toExposeDisplayPercent] 54 | if (displayingChildModel.displayPercentage < toExposeDisplayPercent) { 55 | // Does not meet the exposure conditions, reset exposure record 56 | exposureRecordMap[recordKey] = false; 57 | } else { 58 | // Meet the exposure conditions 59 | final haveExposure = exposureRecordMap[recordKey] ?? false; 60 | if (haveExposure) continue; 61 | toExposeCallback(index); 62 | exposureRecordMap[recordKey] = true; 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /example/lib/features/scene/visibility_demo/page/visibility_listview_page.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/LinXunFeng/flutter_scrollview_observer 4 | * @Date: 2023-08-25 23:14:20 5 | */ 6 | 7 | import 'package:flutter/material.dart'; 8 | import 'package:scrollview_observer/scrollview_observer.dart'; 9 | import 'package:scrollview_observer_example/features/scene/visibility_demo/mixin/visibility_exposure_mixin.dart'; 10 | 11 | class VisibilityListViewPage extends StatefulWidget { 12 | const VisibilityListViewPage({Key? key}) : super(key: key); 13 | 14 | @override 15 | State createState() => _VisibilityListViewPageState(); 16 | } 17 | 18 | class _VisibilityListViewPageState extends State 19 | with VisibilityExposureMixin { 20 | int needExposeIndex = 6; 21 | 22 | final observerController = ListObserverController(); 23 | 24 | @override 25 | Widget build(BuildContext context) { 26 | return Scaffold( 27 | appBar: AppBar(title: const Text("Visibility ListView")), 28 | body: ListViewObserver( 29 | child: _buildListView(), 30 | triggerOnObserveType: ObserverTriggerOnObserveType.directly, 31 | controller: observerController, 32 | onObserve: (resultModel) { 33 | // final models = resultModel.displayingChildModelList; 34 | // final indexList = models.map((e) => e.index).toList(); 35 | // final displayPercentageList = 36 | // models.map((e) => e.displayPercentage).toList(); 37 | // debugPrint('index -- $indexList -- $displayPercentageList'); 38 | 39 | handleExposure( 40 | resultModel: resultModel, 41 | needExposeCallback: (index) { 42 | // Only the item whose index is 6 needs to calculate whether it 43 | // has been exposed. 44 | return index == needExposeIndex; // 6 45 | }, 46 | toExposeCallback: (index) { 47 | // Meet the conditions, you can report exposure. 48 | debugPrint('Exposure -- $index'); 49 | }, 50 | ); 51 | }, 52 | ), 53 | floatingActionButton: FloatingActionButton( 54 | onPressed: () { 55 | // Trigger an observation manually 56 | observerController.dispatchOnceObserve(); 57 | }, 58 | ), 59 | ); 60 | } 61 | 62 | Widget _buildListView() { 63 | return ListView.separated( 64 | itemBuilder: (ctx, index) { 65 | return _buildListItemView(index); 66 | }, 67 | separatorBuilder: (ctx, index) { 68 | return _buildSeparatorView(); 69 | }, 70 | itemCount: 100, 71 | ); 72 | } 73 | 74 | Widget _buildListItemView(int index) { 75 | final isEven = index % 2 == 0; 76 | final needExpose = needExposeIndex == index; 77 | return Container( 78 | height: isEven ? 200 : 100, 79 | color: needExpose 80 | ? Colors.red 81 | : isEven 82 | ? Colors.orange[300] 83 | : Colors.black12, 84 | child: Center( 85 | child: Text( 86 | "index -- $index", 87 | style: TextStyle( 88 | color: needExpose ? Colors.white : Colors.black, 89 | ), 90 | ), 91 | ), 92 | ); 93 | } 94 | 95 | Container _buildSeparatorView() { 96 | return Container( 97 | color: Colors.white, 98 | height: 5, 99 | ); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /example/lib/features/scene/waterfall_flow_demo/waterfall_flow_grid_item_view.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/LinXunFeng/flutter_scrollview_observer 4 | * @Date: 2023-06-08 21:59:07 5 | */ 6 | import 'package:flutter/material.dart'; 7 | import 'package:scrollview_observer_example/features/scene/video_auto_play_list/widgets/video_widget.dart'; 8 | import 'package:scrollview_observer_example/features/scene/waterfall_flow_demo/waterfall_flow_type.dart'; 9 | 10 | class WaterfallFlowGridItemView extends StatelessWidget { 11 | final int selfIndex; 12 | final WaterFlowHitType selfType; 13 | 14 | final int hitIndex; 15 | final WaterFlowHitType hitType; 16 | 17 | bool get isHit => selfType == hitType && selfIndex == hitIndex; 18 | 19 | const WaterfallFlowGridItemView({ 20 | Key? key, 21 | required this.selfIndex, 22 | required this.selfType, 23 | required this.hitIndex, 24 | required this.hitType, 25 | }) : super(key: key); 26 | 27 | @override 28 | Widget build(BuildContext context) { 29 | return Container( 30 | alignment: Alignment.center, 31 | color: isHit ? Colors.amber : Colors.amber[100], 32 | child: _buildBody(), // Text('grid item $selfIndex'), 33 | // height: 300, 34 | ); 35 | } 36 | 37 | Widget _buildBody() { 38 | return Column( 39 | crossAxisAlignment: CrossAxisAlignment.start, 40 | children: [ 41 | isHit ? _buildVideo() : _buildCover(), 42 | const SizedBox(height: 10), 43 | Text('grid item $selfIndex'), 44 | SizedBox( 45 | height: 50.0 + 50.0 * (selfIndex % 2), 46 | ), 47 | ], 48 | ); 49 | } 50 | 51 | Widget _buildCover() { 52 | return Image.network( 53 | 'https://images.unsplash.com/photo-1660139099083-03e0777ac6a7?auto=format&fit=crop&w=375&q=100', 54 | fit: BoxFit.fitWidth, 55 | width: double.infinity, 56 | height: 100, 57 | loadingBuilder: (context, child, loadingProgress) { 58 | if (loadingProgress == null) return child; 59 | 60 | return Container( 61 | height: 50, 62 | alignment: Alignment.center, 63 | child: SizedBox.square( 64 | dimension: 20, 65 | child: CircularProgressIndicator( 66 | value: loadingProgress.expectedTotalBytes != null 67 | ? loadingProgress.cumulativeBytesLoaded / 68 | loadingProgress.expectedTotalBytes! 69 | : null, 70 | ), 71 | ), 72 | ); 73 | }, 74 | ); 75 | } 76 | 77 | Widget _buildVideo() { 78 | Widget resultWidget = const VideoWidget( 79 | url: 'https://www.w3schools.com/html/movie.mp4', 80 | ); 81 | resultWidget = SizedBox( 82 | width: double.infinity, 83 | height: 100, 84 | child: resultWidget, 85 | ); 86 | return resultWidget; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /example/lib/features/scene/waterfall_flow_demo/waterfall_flow_swipe_view.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/LinXunFeng/flutter_scrollview_observer 4 | * @Date: 2023-06-08 22:03:17 5 | */ 6 | import 'package:flutter/material.dart'; 7 | import 'package:scrollview_observer_example/features/scene/video_auto_play_list/widgets/video_widget.dart'; 8 | import 'package:scrollview_observer_example/features/scene/waterfall_flow_demo/waterfall_flow_type.dart'; 9 | 10 | class WaterfallFlowSwipeView extends StatefulWidget { 11 | final WaterFlowHitType hitType; 12 | 13 | const WaterfallFlowSwipeView({ 14 | Key? key, 15 | required this.hitType, 16 | }) : super(key: key); 17 | 18 | @override 19 | State createState() => _WaterfallFlowSwipeViewState(); 20 | } 21 | 22 | class _WaterfallFlowSwipeViewState extends State { 23 | PageController pageController = PageController(viewportFraction: 0.9); 24 | 25 | int currentIndex = 0; 26 | 27 | @override 28 | Widget build(BuildContext context) { 29 | Widget resultWidget = PageView.builder( 30 | controller: pageController, 31 | padEnds: false, 32 | itemBuilder: (context, index) { 33 | final isHit = 34 | WaterFlowHitType.swipe == widget.hitType && currentIndex == index; 35 | return Padding( 36 | padding: const EdgeInsets.only(right: 10), 37 | child: Container( 38 | color: Colors.blue, 39 | child: isHit ? _buildVideo() : const SizedBox.shrink(), 40 | ), 41 | ); 42 | }, 43 | itemCount: 4, 44 | onPageChanged: (index) { 45 | if (currentIndex == index) return; 46 | setState(() { 47 | currentIndex = index; 48 | }); 49 | }, 50 | ); 51 | resultWidget = SizedBox(height: 200, child: resultWidget); 52 | return resultWidget; 53 | } 54 | 55 | Widget _buildVideo() { 56 | Widget resultWidget = const VideoWidget( 57 | url: 'https://www.w3schools.com/html/movie.mp4', 58 | ); 59 | resultWidget = SizedBox( 60 | width: double.infinity, 61 | // height: 100, 62 | child: resultWidget, 63 | ); 64 | return resultWidget; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /example/lib/features/scene/waterfall_flow_demo/waterfall_flow_type.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/LinXunFeng/flutter_scrollview_observer 4 | * @Date: 2023-06-06 22:16:27 5 | */ 6 | enum WaterFlowHitType { 7 | firstGrid, 8 | swipe, 9 | secondGrid, 10 | } 11 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/LinXunFeng/flutter_scrollview_observer 4 | * @Date: 2022-05-28 12:32:34 5 | */ 6 | import 'package:flutter/material.dart'; 7 | 8 | import 'features/home/home_page.dart'; 9 | 10 | void main() { 11 | runApp(const MyApp()); 12 | } 13 | 14 | class MyApp extends StatelessWidget { 15 | const MyApp({Key? key}) : super(key: key); 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return MaterialApp( 20 | title: 'Flutter Demo', 21 | theme: ThemeData( 22 | primarySwatch: Colors.blue, 23 | ), 24 | home: const HomePage(), 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /example/lib/typedefs.dart: -------------------------------------------------------------------------------- 1 | /// This allows a value of type T or T? 2 | /// to be treated as a value of type T?. 3 | /// 4 | /// We use this so that APIs that have become 5 | /// non-nullable can still be used with `!` and `?` 6 | /// to support older versions of the API as well. 7 | T? ambiguate(T? value) => value; 8 | -------------------------------------------------------------------------------- /example/lib/utils/keyboard.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/fluttercandies/flutter_scrollview_observer 4 | * @Date: 2023-11-08 21:48:11 5 | */ 6 | 7 | import 'package:flutter/material.dart'; 8 | 9 | class KeyboardTool { 10 | static void dismissKeyboard(BuildContext context) { 11 | FocusScopeNode currentFocus = FocusScope.of(context); 12 | if (!currentFocus.hasPrimaryFocus && currentFocus.focusedChild != null) { 13 | FocusManager.instance.primaryFocus?.unfocus(); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /example/lib/utils/random.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/LinXunFeng/flutter_scrollview_observer 4 | * @Date: 2023-05-13 12:29:56 5 | */ 6 | 7 | import 'dart:math'; 8 | import 'dart:ui'; 9 | 10 | class RandomTool { 11 | static int genInt({int min = 0, int max = 100}) { 12 | var x = Random().nextInt(max) + min; 13 | return x.floor(); 14 | } 15 | 16 | static Color color() { 17 | final random = Random(); 18 | return Color.fromRGBO( 19 | random.nextInt(255), 20 | random.nextInt(255), 21 | random.nextInt(255), 22 | 1, 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /example/lib/utils/snackbar.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/fluttercandies/flutter_scrollview_observer 4 | * @Date: 2023-10-29 12:44:03 5 | */ 6 | 7 | import 'package:flutter/material.dart'; 8 | 9 | class SnackBarUtil { 10 | static showSnackBar({ 11 | required BuildContext context, 12 | required String text, 13 | }) { 14 | ScaffoldMessenger.of(context).clearSnackBars(); 15 | ScaffoldMessenger.of(context).showSnackBar( 16 | SnackBar( 17 | content: Text(text), 18 | duration: const Duration(milliseconds: 2000), 19 | ), 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /example/lib/widgets/animation.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/fluttercandies/flutter_scrollview_observer 4 | * @Date: 2024-05-29 22:22:07 5 | */ 6 | 7 | import 'package:flutter/material.dart'; 8 | 9 | class SlideAnimation extends StatelessWidget { 10 | final Curve curve; 11 | 12 | final double verticalOffset; 13 | 14 | final double horizontalOffset; 15 | 16 | final Widget child; 17 | 18 | final AnimationController controller; 19 | 20 | const SlideAnimation({ 21 | Key? key, 22 | this.curve = Curves.ease, 23 | required this.controller, 24 | double? verticalOffset, 25 | double? horizontalOffset, 26 | required this.child, 27 | }) : verticalOffset = verticalOffset ?? 0.0, 28 | horizontalOffset = horizontalOffset ?? 0.0, 29 | super(key: key); 30 | 31 | @override 32 | Widget build(BuildContext context) { 33 | return AnimationExecutor( 34 | controller: controller, 35 | builder: (context, animationController) => 36 | _slideAnimation(animationController!), 37 | ); 38 | } 39 | 40 | Widget _slideAnimation(Animation animation) { 41 | Animation offsetAnimation( 42 | double offset, 43 | Animation animation, 44 | ) { 45 | return Tween(begin: offset, end: 0.0).animate( 46 | CurvedAnimation( 47 | parent: animation, 48 | curve: Interval(0.0, 1.0, curve: curve), 49 | ), 50 | ); 51 | } 52 | 53 | return Transform.translate( 54 | offset: Offset( 55 | horizontalOffset == 0.0 56 | ? 0.0 57 | : offsetAnimation(horizontalOffset, animation).value, 58 | verticalOffset == 0.0 59 | ? 0.0 60 | : offsetAnimation(verticalOffset, animation).value, 61 | ), 62 | child: child, 63 | ); 64 | } 65 | } 66 | 67 | class FadeInAnimation extends StatelessWidget { 68 | final Curve curve; 69 | 70 | final Widget child; 71 | 72 | final AnimationController controller; 73 | 74 | const FadeInAnimation({ 75 | Key? key, 76 | this.curve = Curves.ease, 77 | required this.controller, 78 | required this.child, 79 | }) : super(key: key); 80 | 81 | @override 82 | Widget build(BuildContext context) { 83 | return AnimationExecutor( 84 | controller: controller, 85 | builder: (context, animationController) => 86 | _fadeInAnimation(animationController!), 87 | ); 88 | } 89 | 90 | Widget _fadeInAnimation(Animation animation) { 91 | final opacityAnimation = Tween(begin: 0.0, end: 1.0).animate( 92 | CurvedAnimation( 93 | parent: animation, 94 | curve: Interval(0.0, 1.0, curve: curve), 95 | ), 96 | ); 97 | 98 | return Opacity( 99 | opacity: opacityAnimation.value, 100 | child: child, 101 | ); 102 | } 103 | } 104 | 105 | typedef Builder = Widget Function( 106 | BuildContext context, 107 | AnimationController? animationController, 108 | ); 109 | 110 | class AnimationExecutor extends StatefulWidget { 111 | final Builder builder; 112 | final AnimationController controller; 113 | 114 | const AnimationExecutor({ 115 | Key? key, 116 | required this.builder, 117 | required this.controller, 118 | }) : super(key: key); 119 | 120 | @override 121 | State createState() => _AnimationExecutorState(); 122 | } 123 | 124 | class _AnimationExecutorState extends State 125 | with TickerProviderStateMixin { 126 | @override 127 | Widget build(BuildContext context) { 128 | return AnimatedBuilder( 129 | builder: _buildAnimation, 130 | animation: widget.controller, 131 | ); 132 | } 133 | 134 | Widget _buildAnimation(BuildContext context, Widget? child) { 135 | return widget.builder(context, widget.controller); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /example/lib/widgets/sliver.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/fluttercandies/flutter_scrollview_observer 4 | * @Date: 2024-05-29 22:20:12 5 | */ 6 | 7 | import 'package:flutter/material.dart'; 8 | 9 | typedef SliverHeaderBuilder = Widget Function( 10 | BuildContext context, 11 | double shrinkOffset, 12 | bool overlapsContent, 13 | ); 14 | 15 | class SliverHeaderDelegate extends SliverPersistentHeaderDelegate { 16 | SliverHeaderDelegate({ 17 | required this.maxHeight, 18 | this.minHeight = 0, 19 | required Widget child, 20 | }) : builder = ((a, b, c) => child), 21 | assert(minHeight <= maxHeight && minHeight >= 0); 22 | 23 | SliverHeaderDelegate.fixedHeight({ 24 | required double height, 25 | required Widget child, 26 | }) : builder = ((a, b, c) => child), 27 | maxHeight = height, 28 | minHeight = height; 29 | 30 | SliverHeaderDelegate.builder({ 31 | required this.maxHeight, 32 | this.minHeight = 0, 33 | required this.builder, 34 | }); 35 | 36 | final double maxHeight; 37 | final double minHeight; 38 | final SliverHeaderBuilder builder; 39 | 40 | @override 41 | Widget build( 42 | BuildContext context, 43 | double shrinkOffset, 44 | bool overlapsContent, 45 | ) { 46 | Widget child = builder(context, shrinkOffset, overlapsContent); 47 | return SizedBox.expand(child: child); 48 | } 49 | 50 | @override 51 | double get maxExtent => maxHeight; 52 | 53 | @override 54 | double get minExtent => minHeight; 55 | 56 | @override 57 | bool shouldRebuild(SliverHeaderDelegate oldDelegate) { 58 | return oldDelegate.maxExtent != maxExtent || 59 | oldDelegate.minExtent != minExtent; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /example/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 | 10 | void fl_register_plugins(FlPluginRegistry* registry) { 11 | } 12 | -------------------------------------------------------------------------------- /example/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 11 | 12 | // Registers Flutter plugins. 13 | void fl_register_plugins(FlPluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /example/linux/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | ) 7 | 8 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 9 | ) 10 | 11 | set(PLUGIN_BUNDLED_LIBRARIES) 12 | 13 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 14 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) 15 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 16 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 17 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 18 | endforeach(plugin) 19 | 20 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 21 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) 22 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 23 | endforeach(ffi_plugin) 24 | -------------------------------------------------------------------------------- /example/macos/.gitignore: -------------------------------------------------------------------------------- 1 | # Flutter-related 2 | **/Flutter/ephemeral/ 3 | **/Pods/ 4 | 5 | # Xcode-related 6 | **/dgph 7 | **/xcuserdata/ 8 | -------------------------------------------------------------------------------- /example/macos/Flutter/Flutter-Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/macos/Flutter/Flutter-Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/macos/Flutter/GeneratedPluginRegistrant.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | import FlutterMacOS 6 | import Foundation 7 | 8 | import video_player_avfoundation 9 | 10 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 11 | FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin")) 12 | } 13 | -------------------------------------------------------------------------------- /example/macos/Podfile: -------------------------------------------------------------------------------- 1 | platform :osx, '10.14' 2 | 3 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 4 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 5 | 6 | project 'Runner', { 7 | 'Debug' => :debug, 8 | 'Profile' => :release, 9 | 'Release' => :release, 10 | } 11 | 12 | def flutter_root 13 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) 14 | unless File.exist?(generated_xcode_build_settings_path) 15 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" 16 | end 17 | 18 | File.foreach(generated_xcode_build_settings_path) do |line| 19 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 20 | return matches[1].strip if matches 21 | end 22 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" 23 | end 24 | 25 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 26 | 27 | flutter_macos_podfile_setup 28 | 29 | target 'Runner' do 30 | use_frameworks! 31 | use_modular_headers! 32 | 33 | flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) 34 | end 35 | 36 | post_install do |installer| 37 | installer.pods_project.targets.each do |target| 38 | flutter_additional_macos_build_settings(target) 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /example/macos/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - FlutterMacOS (1.0.0) 3 | 4 | DEPENDENCIES: 5 | - FlutterMacOS (from `Flutter/ephemeral`) 6 | 7 | EXTERNAL SOURCES: 8 | FlutterMacOS: 9 | :path: Flutter/ephemeral 10 | 11 | SPEC CHECKSUMS: 12 | FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 13 | 14 | PODFILE CHECKSUM: 353c8bcc5d5b0994e508d035b5431cfe18c1dea7 15 | 16 | COCOAPODS: 1.11.3 17 | -------------------------------------------------------------------------------- /example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /example/macos/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/macos/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | @NSApplicationMain 5 | class AppDelegate: FlutterAppDelegate { 6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 7 | return true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /example/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 | -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_scrollview_observer/4a8f9ca5abf87fa55ab0a71a1558bb309b2bc5fc/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_scrollview_observer/4a8f9ca5abf87fa55ab0a71a1558bb309b2bc5fc/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_scrollview_observer/4a8f9ca5abf87fa55ab0a71a1558bb309b2bc5fc/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_scrollview_observer/4a8f9ca5abf87fa55ab0a71a1558bb309b2bc5fc/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_scrollview_observer/4a8f9ca5abf87fa55ab0a71a1558bb309b2bc5fc/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_scrollview_observer/4a8f9ca5abf87fa55ab0a71a1558bb309b2bc5fc/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_scrollview_observer/4a8f9ca5abf87fa55ab0a71a1558bb309b2bc5fc/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png -------------------------------------------------------------------------------- /example/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 = example 9 | 10 | // The application's bundle identifier 11 | PRODUCT_BUNDLE_IDENTIFIER = com.lxf.scrollviewobserver.example 12 | 13 | // The copyright displayed in application information 14 | PRODUCT_COPYRIGHT = Copyright © 2022 com.lxf.scrollviewobserver. All rights reserved. 15 | -------------------------------------------------------------------------------- /example/macos/Runner/Configs/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Debug.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /example/macos/Runner/Configs/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Release.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /example/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 | -------------------------------------------------------------------------------- /example/macos/Runner/DebugProfile.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.cs.allow-jit 8 | 9 | com.apple.security.network.server 10 | 11 | com.apple.security.network.client 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /example/macos/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | $(PRODUCT_COPYRIGHT) 27 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /example/macos/Runner/MainFlutterWindow.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | class MainFlutterWindow: NSWindow { 5 | override func awakeFromNib() { 6 | let flutterViewController = FlutterViewController.init() 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 | -------------------------------------------------------------------------------- /example/macos/Runner/Release.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.network.client 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/LinXunFeng/flutter_scrollview_observer 4 | * @Date: 2022-05-28 12:32:34 5 | */ 6 | // This is a basic Flutter widget test. 7 | // 8 | // To perform an interaction with a widget in your test, use the WidgetTester 9 | // utility that Flutter provides. For example, you can send tap and scroll 10 | // gestures. You can also use WidgetTester to find child widgets in the widget 11 | // tree, read text, and verify that the values of widget properties are correct. 12 | 13 | import 'package:flutter/material.dart'; 14 | import 'package:flutter_test/flutter_test.dart'; 15 | 16 | import 'package:scrollview_observer_example/main.dart'; 17 | 18 | void main() { 19 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 20 | // Build our app and trigger a frame. 21 | await tester.pumpWidget(const MyApp()); 22 | 23 | // Verify that our counter starts at 0. 24 | expect(find.text('0'), findsOneWidget); 25 | expect(find.text('1'), findsNothing); 26 | 27 | // Tap the '+' icon and trigger a frame. 28 | await tester.tap(find.byIcon(Icons.add)); 29 | await tester.pump(); 30 | 31 | // Verify that our counter has incremented. 32 | expect(find.text('0'), findsNothing); 33 | expect(find.text('1'), findsOneWidget); 34 | }); 35 | } 36 | -------------------------------------------------------------------------------- /example/web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_scrollview_observer/4a8f9ca5abf87fa55ab0a71a1558bb309b2bc5fc/example/web/favicon.png -------------------------------------------------------------------------------- /example/web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_scrollview_observer/4a8f9ca5abf87fa55ab0a71a1558bb309b2bc5fc/example/web/icons/Icon-192.png -------------------------------------------------------------------------------- /example/web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_scrollview_observer/4a8f9ca5abf87fa55ab0a71a1558bb309b2bc5fc/example/web/icons/Icon-512.png -------------------------------------------------------------------------------- /example/web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_scrollview_observer/4a8f9ca5abf87fa55ab0a71a1558bb309b2bc5fc/example/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /example/web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_scrollview_observer/4a8f9ca5abf87fa55ab0a71a1558bb309b2bc5fc/example/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /example/web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "short_name": "example", 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 | -------------------------------------------------------------------------------- /example/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 | -------------------------------------------------------------------------------- /example/windows/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | project(example LANGUAGES CXX) 3 | 4 | set(BINARY_NAME "example") 5 | 6 | cmake_policy(SET CMP0063 NEW) 7 | 8 | set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") 9 | 10 | # Configure build options. 11 | get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) 12 | if(IS_MULTICONFIG) 13 | set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" 14 | CACHE STRING "" FORCE) 15 | else() 16 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 17 | set(CMAKE_BUILD_TYPE "Debug" CACHE 18 | STRING "Flutter build mode" FORCE) 19 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS 20 | "Debug" "Profile" "Release") 21 | endif() 22 | endif() 23 | 24 | set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") 25 | set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") 26 | set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") 27 | set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") 28 | 29 | # Use Unicode for all projects. 30 | add_definitions(-DUNICODE -D_UNICODE) 31 | 32 | # Compilation settings that should be applied to most targets. 33 | function(APPLY_STANDARD_SETTINGS TARGET) 34 | target_compile_features(${TARGET} PUBLIC cxx_std_17) 35 | target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") 36 | target_compile_options(${TARGET} PRIVATE /EHsc) 37 | target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") 38 | target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") 39 | endfunction() 40 | 41 | set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") 42 | 43 | # Flutter library and tool build rules. 44 | add_subdirectory(${FLUTTER_MANAGED_DIR}) 45 | 46 | # Application build 47 | add_subdirectory("runner") 48 | 49 | # Generated plugin build rules, which manage building the plugins and adding 50 | # them to the application. 51 | include(flutter/generated_plugins.cmake) 52 | 53 | 54 | # === Installation === 55 | # Support files are copied into place next to the executable, so that it can 56 | # run in place. This is done instead of making a separate bundle (as on Linux) 57 | # so that building and running from within Visual Studio will work. 58 | set(BUILD_BUNDLE_DIR "$") 59 | # Make the "install" step default, as it's required to run. 60 | set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) 61 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) 62 | set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) 63 | endif() 64 | 65 | set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") 66 | set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") 67 | 68 | install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" 69 | COMPONENT Runtime) 70 | 71 | install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 72 | COMPONENT Runtime) 73 | 74 | install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 75 | COMPONENT Runtime) 76 | 77 | if(PLUGIN_BUNDLED_LIBRARIES) 78 | install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" 79 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 80 | COMPONENT Runtime) 81 | endif() 82 | 83 | # Fully re-copy the assets directory on each build to avoid having stale files 84 | # from a previous install. 85 | set(FLUTTER_ASSET_DIR_NAME "flutter_assets") 86 | install(CODE " 87 | file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") 88 | " COMPONENT Runtime) 89 | install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" 90 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) 91 | 92 | # Install the AOT library on non-Debug builds only. 93 | install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 94 | CONFIGURATIONS Profile;Release 95 | COMPONENT Runtime) 96 | -------------------------------------------------------------------------------- /example/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 | 10 | void RegisterPlugins(flutter::PluginRegistry* registry) { 11 | } 12 | -------------------------------------------------------------------------------- /example/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 11 | 12 | // Registers Flutter plugins. 13 | void RegisterPlugins(flutter::PluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /example/windows/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | ) 7 | 8 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 9 | ) 10 | 11 | set(PLUGIN_BUNDLED_LIBRARIES) 12 | 13 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 14 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) 15 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 16 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 17 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 18 | endforeach(plugin) 19 | 20 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 21 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) 22 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 23 | endforeach(ffi_plugin) 24 | -------------------------------------------------------------------------------- /example/windows/runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | project(runner LANGUAGES CXX) 3 | 4 | add_executable(${BINARY_NAME} WIN32 5 | "flutter_window.cpp" 6 | "main.cpp" 7 | "utils.cpp" 8 | "win32_window.cpp" 9 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 10 | "Runner.rc" 11 | "runner.exe.manifest" 12 | ) 13 | apply_standard_settings(${BINARY_NAME}) 14 | target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") 15 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) 16 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") 17 | add_dependencies(${BINARY_NAME} flutter_assemble) 18 | -------------------------------------------------------------------------------- /example/windows/runner/Runner.rc: -------------------------------------------------------------------------------- 1 | // Microsoft Visual C++ generated resource script. 2 | // 3 | #pragma code_page(65001) 4 | #include "resource.h" 5 | 6 | #define APSTUDIO_READONLY_SYMBOLS 7 | ///////////////////////////////////////////////////////////////////////////// 8 | // 9 | // Generated from the TEXTINCLUDE 2 resource. 10 | // 11 | #include "winres.h" 12 | 13 | ///////////////////////////////////////////////////////////////////////////// 14 | #undef APSTUDIO_READONLY_SYMBOLS 15 | 16 | ///////////////////////////////////////////////////////////////////////////// 17 | // English (United States) resources 18 | 19 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) 20 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US 21 | 22 | #ifdef APSTUDIO_INVOKED 23 | ///////////////////////////////////////////////////////////////////////////// 24 | // 25 | // TEXTINCLUDE 26 | // 27 | 28 | 1 TEXTINCLUDE 29 | BEGIN 30 | "resource.h\0" 31 | END 32 | 33 | 2 TEXTINCLUDE 34 | BEGIN 35 | "#include ""winres.h""\r\n" 36 | "\0" 37 | END 38 | 39 | 3 TEXTINCLUDE 40 | BEGIN 41 | "\r\n" 42 | "\0" 43 | END 44 | 45 | #endif // APSTUDIO_INVOKED 46 | 47 | 48 | ///////////////////////////////////////////////////////////////////////////// 49 | // 50 | // Icon 51 | // 52 | 53 | // Icon with lowest ID value placed first to ensure application icon 54 | // remains consistent on all systems. 55 | IDI_APP_ICON ICON "resources\\app_icon.ico" 56 | 57 | 58 | ///////////////////////////////////////////////////////////////////////////// 59 | // 60 | // Version 61 | // 62 | 63 | #ifdef FLUTTER_BUILD_NUMBER 64 | #define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER 65 | #else 66 | #define VERSION_AS_NUMBER 1,0,0 67 | #endif 68 | 69 | #ifdef FLUTTER_BUILD_NAME 70 | #define VERSION_AS_STRING #FLUTTER_BUILD_NAME 71 | #else 72 | #define VERSION_AS_STRING "1.0.0" 73 | #endif 74 | 75 | VS_VERSION_INFO VERSIONINFO 76 | FILEVERSION VERSION_AS_NUMBER 77 | PRODUCTVERSION VERSION_AS_NUMBER 78 | FILEFLAGSMASK VS_FFI_FILEFLAGSMASK 79 | #ifdef _DEBUG 80 | FILEFLAGS VS_FF_DEBUG 81 | #else 82 | FILEFLAGS 0x0L 83 | #endif 84 | FILEOS VOS__WINDOWS32 85 | FILETYPE VFT_APP 86 | FILESUBTYPE 0x0L 87 | BEGIN 88 | BLOCK "StringFileInfo" 89 | BEGIN 90 | BLOCK "040904e4" 91 | BEGIN 92 | VALUE "CompanyName", "com.example" "\0" 93 | VALUE "FileDescription", "example" "\0" 94 | VALUE "FileVersion", VERSION_AS_STRING "\0" 95 | VALUE "InternalName", "example" "\0" 96 | VALUE "LegalCopyright", "Copyright (C) 2022 com.example. All rights reserved." "\0" 97 | VALUE "OriginalFilename", "example.exe" "\0" 98 | VALUE "ProductName", "example" "\0" 99 | VALUE "ProductVersion", VERSION_AS_STRING "\0" 100 | END 101 | END 102 | BLOCK "VarFileInfo" 103 | BEGIN 104 | VALUE "Translation", 0x409, 1252 105 | END 106 | END 107 | 108 | #endif // English (United States) resources 109 | ///////////////////////////////////////////////////////////////////////////// 110 | 111 | 112 | 113 | #ifndef APSTUDIO_INVOKED 114 | ///////////////////////////////////////////////////////////////////////////// 115 | // 116 | // Generated from the TEXTINCLUDE 3 resource. 117 | // 118 | 119 | 120 | ///////////////////////////////////////////////////////////////////////////// 121 | #endif // not APSTUDIO_INVOKED 122 | -------------------------------------------------------------------------------- /example/windows/runner/flutter_window.cpp: -------------------------------------------------------------------------------- 1 | #include "flutter_window.h" 2 | 3 | #include 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( 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 | return true; 30 | } 31 | 32 | void FlutterWindow::OnDestroy() { 33 | if (flutter_controller_) { 34 | flutter_controller_ = nullptr; 35 | } 36 | 37 | Win32Window::OnDestroy(); 38 | } 39 | 40 | LRESULT 41 | FlutterWindow::MessageHandler(HWND hwnd, UINT const message, 42 | WPARAM const wparam, 43 | LPARAM const lparam) noexcept { 44 | // Give Flutter, including plugins, an opportunity to handle window messages. 45 | if (flutter_controller_) { 46 | std::optional result = 47 | flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, 48 | lparam); 49 | if (result) { 50 | return *result; 51 | } 52 | } 53 | 54 | switch (message) { 55 | case WM_FONTCHANGE: 56 | flutter_controller_->engine()->ReloadSystemFonts(); 57 | break; 58 | } 59 | 60 | return Win32Window::MessageHandler(hwnd, message, wparam, lparam); 61 | } 62 | -------------------------------------------------------------------------------- /example/windows/runner/flutter_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_FLUTTER_WINDOW_H_ 2 | #define RUNNER_FLUTTER_WINDOW_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 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_controller_; 31 | }; 32 | 33 | #endif // RUNNER_FLUTTER_WINDOW_H_ 34 | -------------------------------------------------------------------------------- /example/windows/runner/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 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 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.CreateAndShow(L"example", 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 | -------------------------------------------------------------------------------- /example/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 | -------------------------------------------------------------------------------- /example/windows/runner/resources/app_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_scrollview_observer/4a8f9ca5abf87fa55ab0a71a1558bb309b2bc5fc/example/windows/runner/resources/app_icon.ico -------------------------------------------------------------------------------- /example/windows/runner/runner.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PerMonitorV2 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /example/windows/runner/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | void CreateAndAttachConsole() { 11 | if (::AllocConsole()) { 12 | FILE *unused; 13 | if (freopen_s(&unused, "CONOUT$", "w", stdout)) { 14 | _dup2(_fileno(stdout), 1); 15 | } 16 | if (freopen_s(&unused, "CONOUT$", "w", stderr)) { 17 | _dup2(_fileno(stdout), 2); 18 | } 19 | std::ios::sync_with_stdio(); 20 | FlutterDesktopResyncOutputStreams(); 21 | } 22 | } 23 | 24 | std::vector 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(); 30 | } 31 | 32 | std::vector 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 | int target_length = ::WideCharToMultiByte( 49 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 50 | -1, nullptr, 0, nullptr, nullptr); 51 | if (target_length == 0) { 52 | return std::string(); 53 | } 54 | std::string utf8_string; 55 | utf8_string.resize(target_length); 56 | int converted_length = ::WideCharToMultiByte( 57 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 58 | -1, utf8_string.data(), 59 | target_length, nullptr, nullptr); 60 | if (converted_length == 0) { 61 | return std::string(); 62 | } 63 | return utf8_string; 64 | } 65 | -------------------------------------------------------------------------------- /example/windows/runner/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_UTILS_H_ 2 | #define RUNNER_UTILS_H_ 3 | 4 | #include 5 | #include 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, 16 | // encoded in UTF-8. Returns an empty std::vector on failure. 17 | std::vector GetCommandLineArguments(); 18 | 19 | #endif // RUNNER_UTILS_H_ 20 | -------------------------------------------------------------------------------- /example/windows/runner/win32_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_WIN32_WINDOW_H_ 2 | #define RUNNER_WIN32_WINDOW_H_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | // A class abstraction for a high DPI-aware Win32 Window. Intended to be 11 | // inherited from by classes that wish to specialize with custom 12 | // rendering and input handling 13 | class Win32Window { 14 | public: 15 | struct Point { 16 | unsigned int x; 17 | unsigned int y; 18 | Point(unsigned int x, unsigned int y) : x(x), y(y) {} 19 | }; 20 | 21 | struct Size { 22 | unsigned int width; 23 | unsigned int height; 24 | Size(unsigned int width, unsigned int height) 25 | : width(width), height(height) {} 26 | }; 27 | 28 | Win32Window(); 29 | virtual ~Win32Window(); 30 | 31 | // Creates and shows a win32 window with |title| and position and size using 32 | // |origin| and |size|. New windows are created on the default monitor. Window 33 | // sizes are specified to the OS in physical pixels, hence to ensure a 34 | // consistent size to will treat the width height passed in to this function 35 | // as logical pixels and scale to appropriate for the default monitor. Returns 36 | // true if the window was created successfully. 37 | bool CreateAndShow(const std::wstring& title, 38 | const Point& origin, 39 | const Size& size); 40 | 41 | // Release OS resources associated with window. 42 | void Destroy(); 43 | 44 | // Inserts |content| into the window tree. 45 | void SetChildContent(HWND content); 46 | 47 | // Returns the backing Window handle to enable clients to set icon and other 48 | // window properties. Returns nullptr if the window has been destroyed. 49 | HWND GetHandle(); 50 | 51 | // If true, closing this window will quit the application. 52 | void SetQuitOnClose(bool quit_on_close); 53 | 54 | // Return a RECT representing the bounds of the current client area. 55 | RECT GetClientArea(); 56 | 57 | protected: 58 | // Processes and route salient window messages for mouse handling, 59 | // size change and DPI. Delegates handling of these to member overloads that 60 | // inheriting classes can handle. 61 | virtual LRESULT MessageHandler(HWND window, 62 | UINT const message, 63 | WPARAM const wparam, 64 | LPARAM const lparam) noexcept; 65 | 66 | // Called when CreateAndShow is called, allowing subclass window-related 67 | // setup. Subclasses should return false if setup fails. 68 | virtual bool OnCreate(); 69 | 70 | // Called when Destroy is called. 71 | virtual void OnDestroy(); 72 | 73 | private: 74 | friend class WindowClassRegistrar; 75 | 76 | // OS callback called by message pump. Handles the WM_NCCREATE message which 77 | // is passed when the non-client area is being created and enables automatic 78 | // non-client DPI scaling so that the non-client area automatically 79 | // responsponds to changes in DPI. All other messages are handled by 80 | // MessageHandler. 81 | static LRESULT CALLBACK WndProc(HWND const window, 82 | UINT const message, 83 | WPARAM const wparam, 84 | LPARAM const lparam) noexcept; 85 | 86 | // Retrieves a class instance pointer for |window| 87 | static Win32Window* GetThisFromHandle(HWND const window) noexcept; 88 | 89 | bool quit_on_close_ = false; 90 | 91 | // window handle for top level window. 92 | HWND window_handle_ = nullptr; 93 | 94 | // window handle for hosted content. 95 | HWND child_content_ = nullptr; 96 | }; 97 | 98 | #endif // RUNNER_WIN32_WINDOW_H_ 99 | -------------------------------------------------------------------------------- /lib/scrollview_observer.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/LinXunFeng/flutter_scrollview_observer 4 | * @Date: 2022-08-08 00:20:03 5 | */ 6 | 7 | library scrollview_observer; 8 | 9 | export 'src/notification.dart'; 10 | 11 | export 'src/common/models/observe_model.dart'; 12 | export 'src/common/models/observe_displaying_child_model.dart'; 13 | export 'src/common/models/observe_displaying_child_model_mixin.dart'; 14 | export 'src/common/models/observer_index_position_model.dart'; 15 | export 'src/common/observer_typedef.dart'; 16 | export 'src/common/observer_notification_result.dart'; 17 | 18 | export 'src/listview/list_observer_view.dart'; 19 | export 'src/listview/list_observer_controller.dart'; 20 | export 'src/listview/models/listview_observe_model.dart'; 21 | export 'src/listview/models/listview_observe_displaying_child_model.dart'; 22 | export 'src/listview/list_observer_notification_result.dart'; 23 | 24 | export 'src/gridview/grid_observer_view.dart'; 25 | export 'src/gridview/grid_observer_controller.dart'; 26 | export 'src/gridview/models/gridview_observe_model.dart'; 27 | export 'src/gridview/models/gridview_observe_displaying_child_model.dart'; 28 | export 'src/gridview/grid_observer_notification_result.dart'; 29 | 30 | export 'src/sliver/sliver_observer_view.dart'; 31 | export 'src/sliver/sliver_observer_controller.dart'; 32 | export 'src/sliver/models/sliver_viewport_observe_model.dart'; 33 | export 'src/sliver/models/sliver_viewport_observe_displaying_child_model.dart'; 34 | export 'src/sliver/sliver_observer_notification_result.dart'; 35 | 36 | export 'src/utils/observer_utils.dart'; 37 | export 'src/observer_core.dart'; 38 | -------------------------------------------------------------------------------- /lib/src/common/models/observe_displaying_child_model.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/LinXunFeng/flutter_scrollview_observer 4 | * @Date: 2022-08-08 00:20:03 5 | */ 6 | import 'package:flutter/rendering.dart'; 7 | 8 | abstract class ObserveDisplayingChildModel { 9 | /// The target sliverList. 10 | RenderSliver sliver; 11 | 12 | /// The viewport of sliver. 13 | RenderViewportBase viewport; 14 | 15 | /// The index of child widget. 16 | int index; 17 | 18 | /// The renderObject [RenderBox] of child widget. 19 | RenderBox renderObject; 20 | 21 | ObserveDisplayingChildModel({ 22 | required this.sliver, 23 | required this.viewport, 24 | required this.index, 25 | required this.renderObject, 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /lib/src/common/models/observe_find_child_model.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/LinXunFeng/flutter_scrollview_observer 4 | * @Date: 2022-09-26 23:05:33 5 | */ 6 | 7 | import 'package:flutter/rendering.dart'; 8 | 9 | /// [ObserveFindChildModel] is used to pass data internally. 10 | class ObserveFindChildModel { 11 | ObserveFindChildModel({ 12 | required this.sliver, 13 | required this.viewport, 14 | required this.index, 15 | required this.renderObject, 16 | }); 17 | 18 | /// The target sliverList. 19 | RenderSliver sliver; 20 | 21 | /// The viewport of sliver. 22 | RenderViewportBase viewport; 23 | 24 | /// The index of child widget. 25 | int index; 26 | 27 | /// The renderObject [RenderIndexedSemantics] of child widget. 28 | RenderIndexedSemantics renderObject; 29 | } 30 | -------------------------------------------------------------------------------- /lib/src/common/models/observe_model.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/LinXunFeng/flutter_scrollview_observer 4 | * @Date: 2022-08-08 00:20:03 5 | */ 6 | import 'package:flutter/rendering.dart'; 7 | 8 | import 'observe_displaying_child_model.dart'; 9 | 10 | abstract class ObserveModel { 11 | /// Whether this sliver should be painted. 12 | bool visible; 13 | 14 | /// The target sliver. 15 | RenderSliver sliver; 16 | 17 | /// The viewport of sliver. 18 | RenderViewportBase viewport; 19 | 20 | /// Stores model list for children widgets those are displaying. 21 | List innerDisplayingChildModelList; 22 | 23 | /// Stores model map for children widgets those are displaying. 24 | Map innerDisplayingChildModelMap; 25 | 26 | /// Stores index list for children widgets those are displaying. 27 | List get displayingChildIndexList => 28 | innerDisplayingChildModelList.map((e) => e.index).toList(); 29 | 30 | /// The axis of sliver. 31 | Axis get axis => sliver.constraints.axis; 32 | 33 | /// The scroll offset of sliver. 34 | double get scrollOffset => sliver.constraints.scrollOffset; 35 | 36 | ObserveModel({ 37 | required this.visible, 38 | required this.sliver, 39 | required this.viewport, 40 | required this.innerDisplayingChildModelList, 41 | required this.innerDisplayingChildModelMap, 42 | }); 43 | } 44 | -------------------------------------------------------------------------------- /lib/src/common/models/observe_scroll_child_model.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/LinXunFeng/flutter_scrollview_observer 4 | * @Date: 2022-07-20 00:32:40 5 | */ 6 | class ObserveScrollChildModel { 7 | /// The size of child widget. 8 | double size; 9 | 10 | /// The layout offset of child widget. 11 | double layoutOffset; 12 | 13 | ObserveScrollChildModel({ 14 | required this.size, 15 | required this.layoutOffset, 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /lib/src/common/models/observe_scroll_to_index_result_model.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/fluttercandies/flutter_scrollview_observer 4 | * @Date: 2023-10-25 21:29:37 5 | */ 6 | 7 | import 'package:flutter/rendering.dart'; 8 | 9 | class ObserveScrollToIndexFixedHeightResultModel { 10 | /// The size of item on the main axis. 11 | double childMainAxisSize; 12 | 13 | /// The separator size between items on the main axis. 14 | double itemSeparatorHeight; 15 | 16 | /// The number of rows for the target item. 17 | int indexOfLine; 18 | 19 | /// The offset of the target child widget on the main axis. 20 | double targetChildLayoutOffset; 21 | 22 | ObserveScrollToIndexFixedHeightResultModel({ 23 | required this.childMainAxisSize, 24 | required this.itemSeparatorHeight, 25 | required this.indexOfLine, 26 | required this.targetChildLayoutOffset, 27 | }); 28 | } 29 | 30 | class ObservePrepareScrollToIndexModel { 31 | /// The scroll distance that has been consumed by all [RenderSliver]s that 32 | /// came before this [RenderSliver]. 33 | double precedingScrollExtent; 34 | 35 | /// The target safety layout offset for scrolling to index. 36 | double calculateTargetLayoutOffset; 37 | 38 | /// The offset of the target child widget on the main axis. 39 | double targetChildLayoutOffset; 40 | 41 | ObservePrepareScrollToIndexModel({ 42 | required this.calculateTargetLayoutOffset, 43 | required this.precedingScrollExtent, 44 | required this.targetChildLayoutOffset, 45 | }); 46 | } 47 | -------------------------------------------------------------------------------- /lib/src/common/models/observer_handle_contexts_result_model.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/LinXunFeng/flutter_scrollview_observer 4 | * @Date: 2023-08-12 16:01:29 5 | */ 6 | 7 | import 'package:flutter/material.dart'; 8 | import 'package:scrollview_observer/src/common/models/observe_model.dart'; 9 | 10 | class ObserverHandleContextsResultModel { 11 | /// Observation result for first sliver. 12 | /// Corresponding to [onObserve] in [ObserverWidget]. 13 | final M? changeResultModel; 14 | 15 | /// Observation result map. 16 | /// Corresponding to [onObserveAll] in [ObserverWidget]. 17 | final Map changeResultMap; 18 | 19 | ObserverHandleContextsResultModel({ 20 | this.changeResultModel, 21 | this.changeResultMap = const {}, 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /lib/src/common/models/observer_index_position_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:scrollview_observer/src/common/typedefs.dart'; 3 | 4 | class ObserverIndexPositionModel { 5 | ObserverIndexPositionModel({ 6 | required this.index, 7 | this.sliverContext, 8 | this.isFixedHeight = false, 9 | this.alignment = 0, 10 | this.offset, 11 | this.padding = EdgeInsets.zero, 12 | }); 13 | 14 | /// The index position of the scrollView. 15 | int index; 16 | 17 | /// The target sliver [BuildContext]. 18 | BuildContext? sliverContext; 19 | 20 | /// If the height of the child widget and the height of the separator are 21 | /// fixed, please pass [true] to this property. 22 | bool isFixedHeight; 23 | 24 | /// The [alignment] specifies the desired position for the leading edge of the 25 | /// child widget. 26 | /// 27 | /// It must be a value in the range [0.0, 1.0]. 28 | double alignment; 29 | 30 | /// Use this property when locating position needs an offset. 31 | ObserverLocateIndexOffsetCallback? offset; 32 | 33 | /// This value is required when the scrollView is wrapped in the 34 | /// [SliverPadding]. 35 | /// 36 | /// For example: 37 | /// 1. ListView.separated(padding: _padding, ...) 38 | /// 2. GridView.builder(padding: _padding, ...) 39 | EdgeInsets padding; 40 | } 41 | -------------------------------------------------------------------------------- /lib/src/common/observer_listener.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/fluttercandies/flutter_scrollview_observer 4 | * @Date: 2024-10-27 17:16:57 5 | */ 6 | 7 | import 'dart:collection'; 8 | 9 | import 'package:flutter/material.dart'; 10 | 11 | import 'package:scrollview_observer/src/common/models/observe_model.dart'; 12 | import 'package:scrollview_observer/src/common/observer_typedef.dart'; 13 | 14 | class ObserverListenerEntry 15 | extends LinkedListEntry> { 16 | ObserverListenerEntry({ 17 | required this.context, 18 | required this.onObserve, 19 | required this.onObserveAll, 20 | }); 21 | 22 | /// The context of the listener. 23 | final BuildContext? context; 24 | 25 | /// The callback of getting observed result. 26 | final OnObserveCallback? onObserve; 27 | 28 | /// The callback of getting observed result map. 29 | final OnObserveAllCallback? onObserveAll; 30 | } 31 | -------------------------------------------------------------------------------- /lib/src/common/observer_notification_result.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/LinXunFeng/flutter_scrollview_observer 4 | * @Date: 2023-08-12 20:09:46 5 | */ 6 | 7 | import 'package:flutter/material.dart'; 8 | import 'package:scrollview_observer/src/common/models/observe_model.dart'; 9 | import 'package:scrollview_observer/src/common/models/observer_handle_contexts_result_model.dart'; 10 | import 'package:scrollview_observer/src/common/typedefs.dart'; 11 | 12 | class CommonOnceObserveNotificationResult> { 14 | bool get isSuccess => ObserverWidgetObserveResultType.success == type; 15 | 16 | /// Observation result type. 17 | final ObserverWidgetObserveResultType type; 18 | 19 | /// Observation result for first sliver. 20 | /// Corresponding to [onObserve] in [ObserverWidget]. 21 | final M? observeResult; 22 | 23 | /// Observation result map. 24 | /// Corresponding to [onObserveAll] in [ObserverWidget]. 25 | final Map observeAllResult; 26 | 27 | CommonOnceObserveNotificationResult({ 28 | required this.type, 29 | required this.observeResult, 30 | required this.observeAllResult, 31 | }); 32 | } 33 | -------------------------------------------------------------------------------- /lib/src/common/observer_typedef.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/LinXunFeng/flutter_scrollview_observer 4 | * @Date: 2022-12-04 15:57:38 5 | */ 6 | 7 | import 'package:flutter/material.dart'; 8 | import 'package:scrollview_observer/src/common/models/observe_model.dart'; 9 | import 'package:scrollview_observer/src/common/models/observe_scroll_to_index_result_model.dart'; 10 | import 'package:scrollview_observer/src/sliver/models/sliver_viewport_observe_model.dart'; 11 | 12 | /// Called when the ObserverController prepare to scroll to index with 13 | /// [ObservePrepareScrollToIndexModel]. 14 | typedef ObserverOnPrepareScrollToIndex = Future Function( 15 | ObservePrepareScrollToIndexModel); 16 | 17 | /// The callback type of getting observed result for first sliver. 18 | /// 19 | /// Corresponds to onObserve. 20 | typedef OnObserveCallback = void Function( 21 | M result, 22 | ); 23 | 24 | /// The callback type of getting observed result map. 25 | /// 26 | /// Corresponds to onObserveAll. 27 | typedef OnObserveAllCallback = void Function( 28 | Map resultMap, 29 | ); 30 | 31 | /// The callback type of getting all slivers those are displayed in viewport. 32 | /// 33 | /// Corresponds to onObserveViewport. 34 | typedef OnObserveViewportCallback = void Function( 35 | SliverViewportObserveModel result, 36 | ); 37 | 38 | /// Define type that auto trigger observe. 39 | enum ObserverAutoTriggerObserveType { 40 | scrollStart, 41 | scrollUpdate, 42 | scrollEnd, 43 | } 44 | 45 | /// Define type that trigger [onObserve] callback. 46 | enum ObserverTriggerOnObserveType { 47 | directly, 48 | displayingItemsChange, 49 | } 50 | 51 | /// Define type of the observed render sliver. 52 | enum ObserverRenderSliverType { 53 | /// listView 54 | list, 55 | 56 | /// gridView 57 | grid, 58 | } 59 | -------------------------------------------------------------------------------- /lib/src/common/observer_widget_scope.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/fluttercandies/flutter_scrollview_observer 4 | * @Date: 2024-10-19 11:49:39 5 | */ 6 | 7 | import 'package:flutter/material.dart'; 8 | 9 | import 'package:scrollview_observer/scrollview_observer.dart'; 10 | import 'package:scrollview_observer/src/common/observer_controller.dart'; 11 | import 'package:scrollview_observer/src/common/observer_widget.dart'; 12 | 13 | class ObserverWidgetScope< 14 | C extends ObserverController, 15 | M extends ObserveModel, 16 | N extends ScrollViewOnceObserveNotification, 17 | T extends ObserverWidget> extends InheritedWidget { 18 | const ObserverWidgetScope({ 19 | Key? key, 20 | required Widget child, 21 | required this.observerWidgetState, 22 | required this.onCreateElement, 23 | }) : super(key: key, child: child); 24 | 25 | /// The [ObserverWidgetState] instance. 26 | final ObserverWidgetState observerWidgetState; 27 | 28 | /// The callback of [createElement]. 29 | final Function(BuildContext) onCreateElement; 30 | 31 | @override 32 | InheritedElement createElement() { 33 | final element = super.createElement(); 34 | onCreateElement.call(element); 35 | return element; 36 | } 37 | 38 | @override 39 | bool updateShouldNotify(covariant ObserverWidgetScope oldWidget) { 40 | return observerWidgetState != oldWidget.observerWidgetState; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/src/common/observer_widget_tag_manager.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/fluttercandies/flutter_scrollview_observer 4 | * @Date: 2024-11-03 14:40:40 5 | */ 6 | 7 | import 'package:flutter/material.dart'; 8 | 9 | class ObserverWidgetTagManager extends InheritedWidget { 10 | final Map _tagMap = {}; 11 | 12 | ObserverWidgetTagManager({ 13 | Key? key, 14 | required Widget child, 15 | }) : super(key: key, child: child); 16 | 17 | /// Getting the [ObserverWidgetTagManager] instance. 18 | /// 19 | /// If the [ObserverWidgetTagManager] instance is not found, return null. 20 | static ObserverWidgetTagManager? maybeOf(BuildContext context) { 21 | return context 22 | .dependOnInheritedWidgetOfExactType(); 23 | } 24 | 25 | /// Setting the tag and context. 26 | void set( 27 | String tag, 28 | BuildContext context, 29 | ) { 30 | _tagMap[tag] = context; 31 | } 32 | 33 | /// Removing the tag. 34 | void remove(String tag) { 35 | _tagMap.remove(tag); 36 | } 37 | 38 | /// Getting the context by tag. 39 | BuildContext? context( 40 | String tag, 41 | ) { 42 | return _tagMap[tag]; 43 | } 44 | 45 | /// Getting all tags and contexts. 46 | @protected 47 | @visibleForTesting 48 | Map get tagMap { 49 | return _tagMap; 50 | } 51 | 52 | @override 53 | bool updateShouldNotify(covariant ObserverWidgetTagManager oldWidget) { 54 | return _tagMap != oldWidget._tagMap; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /lib/src/common/typedefs.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/LinXunFeng/flutter_scrollview_observer 4 | * @Date: 2022-08-21 12:50:50 5 | */ 6 | 7 | /// This allows a value of type T or T? 8 | /// to be treated as a value of type T?. 9 | /// 10 | /// We use this so that APIs that have become 11 | /// non-nullable can still be used with `!` and `?` 12 | /// to support older versions of the API as well. 13 | T? ambiguate(T? value) => value; 14 | 15 | /// Signature for the callback when scrolling to the specified index location 16 | /// with offset. 17 | /// For example, return the height of the sticky widget. 18 | /// 19 | /// The [targetOffset] property is the offset of the planned locate. 20 | typedef ObserverLocateIndexOffsetCallback = double Function( 21 | double targetOffset); 22 | 23 | /// Observation result types in ObserverWidget. 24 | enum ObserverWidgetObserveResultType { 25 | success, 26 | interrupted, 27 | } 28 | -------------------------------------------------------------------------------- /lib/src/gridview/grid_observer_notification_result.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/LinXunFeng/flutter_scrollview_observer 4 | * @Date: 2023-08-12 20:08:21 5 | */ 6 | 7 | import 'package:scrollview_observer/src/common/models/observer_handle_contexts_result_model.dart'; 8 | import 'package:scrollview_observer/src/common/observer_notification_result.dart'; 9 | import 'package:scrollview_observer/src/common/typedefs.dart'; 10 | import 'package:scrollview_observer/src/gridview/models/gridview_observe_model.dart'; 11 | 12 | class GridViewOnceObserveNotificationResult 13 | extends CommonOnceObserveNotificationResult> { 15 | GridViewOnceObserveNotificationResult({ 16 | required ObserverWidgetObserveResultType type, 17 | required ObserverHandleContextsResultModel 18 | observeResult, 19 | }) : super( 20 | type: type, 21 | observeResult: observeResult.changeResultModel, 22 | observeAllResult: observeResult.changeResultMap, 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /lib/src/gridview/models/gridview_observe_displaying_child_model.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/LinXunFeng/flutter_scrollview_observer 4 | * @Date: 2022-07-03 15:46:45 5 | */ 6 | import 'package:flutter/rendering.dart'; 7 | import 'package:scrollview_observer/src/common/models/observe_displaying_child_model.dart'; 8 | import 'package:scrollview_observer/src/common/models/observe_displaying_child_model_mixin.dart'; 9 | 10 | class GridViewObserveDisplayingChildModel extends ObserveDisplayingChildModel 11 | with ObserveDisplayingChildModelMixin { 12 | GridViewObserveDisplayingChildModel({ 13 | required this.sliverGrid, 14 | required RenderViewportBase viewport, 15 | required int index, 16 | required RenderBox renderObject, 17 | }) : super( 18 | sliver: sliverGrid, 19 | viewport: viewport, 20 | index: index, 21 | renderObject: renderObject, 22 | ); 23 | 24 | /// The target sliverGrid 25 | RenderSliverMultiBoxAdaptor sliverGrid; 26 | 27 | @override 28 | bool operator ==(Object other) { 29 | if (identical(this, other)) return true; 30 | if (other is GridViewObserveDisplayingChildModel) { 31 | return index == other.index && renderObject == other.renderObject; 32 | } else { 33 | return false; 34 | } 35 | } 36 | 37 | @override 38 | int get hashCode { 39 | return index + renderObject.hashCode; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/src/gridview/models/gridview_observe_model.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/LinXunFeng/flutter_scrollview_observer 4 | * @Date: 2022-08-08 00:20:03 5 | */ 6 | import 'package:flutter/foundation.dart'; 7 | import 'package:flutter/rendering.dart'; 8 | import 'package:scrollview_observer/src/common/models/observe_model.dart'; 9 | import 'package:scrollview_observer/src/gridview/models/gridview_observe_displaying_child_model.dart'; 10 | 11 | class GridViewObserveModel extends ObserveModel { 12 | GridViewObserveModel({ 13 | required this.sliverGrid, 14 | required RenderViewportBase viewport, 15 | required this.firstGroupChildList, 16 | required this.displayingChildModelList, 17 | required this.displayingChildModelMap, 18 | required bool visible, 19 | }) : super( 20 | visible: visible, 21 | sliver: sliverGrid, 22 | viewport: viewport, 23 | innerDisplayingChildModelList: displayingChildModelList, 24 | innerDisplayingChildModelMap: displayingChildModelMap, 25 | ); 26 | 27 | /// The target sliverGrid. 28 | RenderSliverMultiBoxAdaptor sliverGrid; 29 | 30 | /// The first group child widgets those are displaying. 31 | final List firstGroupChildList; 32 | 33 | /// Stores observing model list of displaying children widgets. 34 | final List displayingChildModelList; 35 | 36 | /// Stores observing model map of displaying children widgets. 37 | final Map displayingChildModelMap; 38 | 39 | @override 40 | bool operator ==(Object other) { 41 | if (identical(this, other)) return true; 42 | if (other is GridViewObserveModel) { 43 | return listEquals(firstGroupChildList, other.firstGroupChildList) && 44 | listEquals( 45 | displayingChildModelList, other.displayingChildModelList) && 46 | mapEquals(displayingChildModelMap, other.displayingChildModelMap); 47 | } else { 48 | return false; 49 | } 50 | } 51 | 52 | @override 53 | int get hashCode { 54 | return firstGroupChildList.hashCode + 55 | displayingChildModelList.hashCode + 56 | displayingChildModelMap.hashCode; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /lib/src/listview/list_observer_notification_result.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/LinXunFeng/flutter_scrollview_observer 4 | * @Date: 2023-08-12 20:07:18 5 | */ 6 | 7 | import 'package:scrollview_observer/src/common/models/observer_handle_contexts_result_model.dart'; 8 | import 'package:scrollview_observer/src/common/observer_notification_result.dart'; 9 | import 'package:scrollview_observer/src/common/typedefs.dart'; 10 | import 'package:scrollview_observer/src/listview/models/listview_observe_model.dart'; 11 | 12 | class ListViewOnceObserveNotificationResult 13 | extends CommonOnceObserveNotificationResult> { 15 | ListViewOnceObserveNotificationResult({ 16 | required ObserverWidgetObserveResultType type, 17 | required ObserverHandleContextsResultModel 18 | observeResult, 19 | }) : super( 20 | type: type, 21 | observeResult: observeResult.changeResultModel, 22 | observeAllResult: observeResult.changeResultMap, 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /lib/src/listview/models/listview_observe_displaying_child_model.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/LinXunFeng/flutter_scrollview_observer 4 | * @Date: 2022-07-03 15:46:45 5 | */ 6 | import 'package:flutter/rendering.dart'; 7 | import 'package:scrollview_observer/src/common/models/observe_displaying_child_model.dart'; 8 | import 'package:scrollview_observer/src/common/models/observe_displaying_child_model_mixin.dart'; 9 | 10 | class ListViewObserveDisplayingChildModel extends ObserveDisplayingChildModel 11 | with ObserveDisplayingChildModelMixin { 12 | ListViewObserveDisplayingChildModel({ 13 | required this.sliverList, 14 | required RenderViewportBase viewport, 15 | required int index, 16 | required RenderBox renderObject, 17 | }) : super( 18 | sliver: sliverList, 19 | viewport: viewport, 20 | index: index, 21 | renderObject: renderObject, 22 | ); 23 | 24 | /// The target sliverList. 25 | /// It would be [RenderSliverList] or [RenderSliverFixedExtentList]. 26 | RenderSliverMultiBoxAdaptor sliverList; 27 | 28 | @override 29 | bool operator ==(Object other) { 30 | if (identical(this, other)) return true; 31 | if (other is ListViewObserveDisplayingChildModel) { 32 | return index == other.index && renderObject == other.renderObject; 33 | } else { 34 | return false; 35 | } 36 | } 37 | 38 | @override 39 | int get hashCode { 40 | return index + renderObject.hashCode; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/src/listview/models/listview_observe_model.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/LinXunFeng/flutter_scrollview_observer 4 | * @Date: 2022-08-08 00:20:03 5 | */ 6 | import 'package:flutter/foundation.dart'; 7 | import 'package:flutter/rendering.dart'; 8 | import 'package:scrollview_observer/src/common/models/observe_model.dart'; 9 | 10 | import 'listview_observe_displaying_child_model.dart'; 11 | 12 | class ListViewObserveModel extends ObserveModel { 13 | ListViewObserveModel({ 14 | required this.sliverList, 15 | required RenderViewportBase viewport, 16 | required this.firstChild, 17 | required this.displayingChildModelList, 18 | required this.displayingChildModelMap, 19 | required bool visible, 20 | }) : super( 21 | visible: visible, 22 | sliver: sliverList, 23 | viewport: viewport, 24 | innerDisplayingChildModelList: displayingChildModelList, 25 | innerDisplayingChildModelMap: displayingChildModelMap, 26 | ); 27 | 28 | /// The target sliverList. 29 | /// It would be [RenderSliverList] or [RenderSliverFixedExtentList]. 30 | RenderSliverMultiBoxAdaptor sliverList; 31 | 32 | /// The observing data of the first child widget that is displaying. 33 | final ListViewObserveDisplayingChildModel? firstChild; 34 | 35 | /// Stores observing model list of displaying children widgets. 36 | final List displayingChildModelList; 37 | 38 | /// Stores observing model map of displaying children widgets. 39 | final Map displayingChildModelMap; 40 | 41 | @override 42 | bool operator ==(Object other) { 43 | if (identical(this, other)) return true; 44 | if (other is ListViewObserveModel) { 45 | return firstChild == other.firstChild && 46 | listEquals( 47 | displayingChildModelList, other.displayingChildModelList) && 48 | mapEquals(displayingChildModelMap, other.displayingChildModelMap); 49 | } else { 50 | return false; 51 | } 52 | } 53 | 54 | @override 55 | int get hashCode { 56 | return firstChild.hashCode + 57 | displayingChildModelList.hashCode + 58 | displayingChildModelMap.hashCode; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /lib/src/notification.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/LinXunFeng/flutter_scrollview_observer 4 | * @Date: 2022-05-28 12:37:41 5 | */ 6 | import 'package:flutter/material.dart'; 7 | import 'package:scrollview_observer/src/common/observer_controller.dart'; 8 | 9 | class ScrollViewOnceObserveNotification extends Notification { 10 | /// Whether to return the observation result directly without comparing. 11 | final bool isForce; 12 | 13 | /// Whether to depend on the observe callback. 14 | /// 15 | /// If true, the observe callback will be called when the observation result 16 | /// come out. 17 | final bool isDependObserveCallback; 18 | ScrollViewOnceObserveNotification({ 19 | this.isForce = false, 20 | this.isDependObserveCallback = true, 21 | }); 22 | } 23 | 24 | /// The Notification for Triggering an ListView observation 25 | class ListViewOnceObserveNotification 26 | extends ScrollViewOnceObserveNotification { 27 | ListViewOnceObserveNotification({ 28 | bool isForce = false, 29 | bool isDependObserveCallback = true, 30 | }) : super( 31 | isForce: isForce, 32 | isDependObserveCallback: isDependObserveCallback, 33 | ); 34 | } 35 | 36 | /// The Notification for Triggering an GridView observation 37 | class GridViewOnceObserveNotification 38 | extends ScrollViewOnceObserveNotification { 39 | GridViewOnceObserveNotification({ 40 | bool isForce = false, 41 | bool isDependObserveCallback = true, 42 | }) : super( 43 | isForce: isForce, 44 | isDependObserveCallback: isDependObserveCallback, 45 | ); 46 | } 47 | 48 | /// A notification of scrolling task. 49 | /// 50 | /// Sequence: 51 | /// [ObserverScrollStartNotification] -> [ObserverScrollDecisionNotification] 52 | /// -> [ObserverScrollEndNotification]. 53 | class ObserverScrollNotification extends Notification { 54 | @override 55 | void dispatch(BuildContext? target) { 56 | bool isMounted = target?.mounted ?? false; 57 | if (!isMounted) { 58 | return; 59 | } 60 | super.dispatch(target); 61 | } 62 | } 63 | 64 | /// A notification that a scrolling task has started due to calling the jumpTo 65 | /// or animateTo method of [ObserverController]. 66 | class ObserverScrollStartNotification extends ObserverScrollNotification {} 67 | 68 | /// A notification that a scrolling task has interrupted due to calling the 69 | /// jumpTo or animateTo method of [ObserverController]. 70 | class ObserverScrollInterruptionNotification 71 | extends ObserverScrollNotification {} 72 | 73 | /// A notification that the data of the specified index item is determined 74 | /// during the execution of the scrolling task. 75 | class ObserverScrollDecisionNotification extends ObserverScrollNotification {} 76 | 77 | /// A notification that a scrolling task has stopped due to calling the jumpTo 78 | /// or animateTo method of [ObserverController]. 79 | class ObserverScrollEndNotification extends ObserverScrollNotification {} 80 | -------------------------------------------------------------------------------- /lib/src/sliver/models/sliver_observer_observe_result_model.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/LinXunFeng/flutter_scrollview_observer 4 | * @Date: 2023-08-12 16:18:26 5 | */ 6 | 7 | import 'package:flutter/material.dart'; 8 | import 'package:scrollview_observer/src/common/models/observe_model.dart'; 9 | import 'package:scrollview_observer/src/common/models/observer_handle_contexts_result_model.dart'; 10 | import 'package:scrollview_observer/src/sliver/models/sliver_viewport_observe_model.dart'; 11 | 12 | class SliverObserverHandleContextsResultModel 13 | extends ObserverHandleContextsResultModel { 14 | /// Getting all slivers those are displayed in viewport. 15 | /// 16 | /// Corresponding to [onObserveViewport] in [SliverViewObserver]. 17 | final SliverViewportObserveModel? observeViewportResultModel; 18 | 19 | SliverObserverHandleContextsResultModel({ 20 | M? changeResultModel, 21 | Map changeResultMap = const {}, 22 | this.observeViewportResultModel, 23 | }) : super( 24 | changeResultModel: changeResultModel, 25 | changeResultMap: changeResultMap, 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /lib/src/sliver/models/sliver_viewport_observe_displaying_child_model.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/LinXunFeng/flutter_scrollview_observer 4 | * @Date: 2023-05-14 10:51:42 5 | */ 6 | import 'package:flutter/material.dart'; 7 | import 'package:flutter/rendering.dart'; 8 | 9 | class SliverViewportObserveDisplayingChildModel { 10 | /// The [BuildContext] object for the [sliver]. 11 | final BuildContext sliverContext; 12 | 13 | /// The [sliver] displayed in the current CustomScrollView. 14 | final RenderSliver sliver; 15 | 16 | SliverViewportObserveDisplayingChildModel({ 17 | required this.sliverContext, 18 | required this.sliver, 19 | }); 20 | 21 | @override 22 | bool operator ==(Object other) { 23 | if (identical(this, other)) return true; 24 | if (other is SliverViewportObserveDisplayingChildModel) { 25 | return sliverContext == other.sliverContext && sliver == other.sliver; 26 | } else { 27 | return false; 28 | } 29 | } 30 | 31 | @override 32 | int get hashCode { 33 | return sliverContext.hashCode + sliver.hashCode; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/src/sliver/models/sliver_viewport_observe_model.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/LinXunFeng/flutter_scrollview_observer 4 | * @Date: 2023-05-13 22:36:22 5 | */ 6 | import 'package:flutter/foundation.dart'; 7 | import 'package:flutter/rendering.dart'; 8 | 9 | import 'sliver_viewport_observe_displaying_child_model.dart'; 10 | 11 | class SliverViewportObserveModel { 12 | /// The viewport of the current CustomScrollView. 13 | final RenderViewportBase viewport; 14 | 15 | /// The observing data of the first child widget that is displaying. 16 | final SliverViewportObserveDisplayingChildModel firstChild; 17 | 18 | /// Stores observing model list of displaying children widgets. 19 | final List 20 | displayingChildModelList; 21 | 22 | SliverViewportObserveModel({ 23 | required this.viewport, 24 | required this.firstChild, 25 | required this.displayingChildModelList, 26 | }); 27 | 28 | @override 29 | bool operator ==(Object other) { 30 | if (identical(this, other)) return true; 31 | if (other is SliverViewportObserveModel) { 32 | return viewport == other.viewport && 33 | firstChild == other.firstChild && 34 | listEquals(displayingChildModelList, other.displayingChildModelList); 35 | } else { 36 | return false; 37 | } 38 | } 39 | 40 | @override 41 | int get hashCode { 42 | return viewport.hashCode + 43 | firstChild.hashCode + 44 | displayingChildModelList.hashCode; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lib/src/sliver/sliver_observer_listener.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/fluttercandies/flutter_scrollview_observer 4 | * @Date: 2024-10-27 20:47:11 5 | */ 6 | 7 | import 'dart:collection'; 8 | 9 | import 'package:flutter/material.dart'; 10 | 11 | import 'package:scrollview_observer/src/common/observer_typedef.dart'; 12 | 13 | class SliverObserverListenerEntry 14 | extends LinkedListEntry { 15 | SliverObserverListenerEntry({ 16 | required this.context, 17 | required this.onObserveViewport, 18 | }); 19 | 20 | /// The context of the listener. 21 | final BuildContext? context; 22 | 23 | /// The callback of getting all slivers those are displayed in viewport. 24 | final OnObserveViewportCallback? onObserveViewport; 25 | } 26 | -------------------------------------------------------------------------------- /lib/src/sliver/sliver_observer_notification_result.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/LinXunFeng/flutter_scrollview_observer 4 | * @Date: 2023-08-12 20:13:21 5 | */ 6 | 7 | import 'package:scrollview_observer/src/common/models/observe_model.dart'; 8 | import 'package:scrollview_observer/src/common/observer_notification_result.dart'; 9 | import 'package:scrollview_observer/src/common/typedefs.dart'; 10 | import 'package:scrollview_observer/src/sliver/models/sliver_observer_observe_result_model.dart'; 11 | import 'package:scrollview_observer/src/sliver/models/sliver_viewport_observe_model.dart'; 12 | 13 | class ScrollViewOnceObserveNotificationResult 14 | extends CommonOnceObserveNotificationResult> { 16 | ScrollViewOnceObserveNotificationResult({ 17 | required ObserverWidgetObserveResultType type, 18 | required SliverObserverHandleContextsResultModel 19 | observeResult, 20 | }) : super( 21 | type: type, 22 | observeResult: observeResult.changeResultModel, 23 | observeAllResult: observeResult.changeResultMap, 24 | ) { 25 | observeViewportResultModel = observeResult.observeViewportResultModel; 26 | } 27 | 28 | /// Getting all slivers those are displayed in viewport. 29 | /// 30 | /// Corresponding to [onObserveViewport] in [SliverViewObserver]. 31 | SliverViewportObserveModel? observeViewportResultModel; 32 | } 33 | -------------------------------------------------------------------------------- /lib/src/utils/observer_utils.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/LinXunFeng/flutter_scrollview_observer 4 | * @Date: 2022-08-21 01:07:16 5 | */ 6 | 7 | export 'src/extends.dart'; 8 | export 'src/slivers.dart'; 9 | export 'src/observer_utils.dart'; 10 | export 'src/nested_scroll_util.dart'; 11 | export 'src/chat/chat_observer_scroll_physics.dart'; 12 | export 'src/chat/chat_observer_scroll_physics_mixin.dart'; 13 | export 'src/chat/chat_scroll_observer.dart'; 14 | export 'src/chat/chat_scroll_observer_typedefs.dart'; 15 | export 'src/chat/chat_scroll_observer_model.dart'; 16 | -------------------------------------------------------------------------------- /lib/src/utils/src/chat/chat_observer_scroll_physics.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/LinXunFeng/flutter_scrollview_observer 4 | * @Date: 2022-09-27 23:12:45 5 | */ 6 | 7 | import 'package:flutter/material.dart'; 8 | import 'chat_observer_scroll_physics_mixin.dart'; 9 | import 'chat_scroll_observer.dart'; 10 | 11 | @Deprecated( 12 | 'It will be removed in version 2, please use [ChatObserverClampingScrollPhysics] instead') 13 | class ChatObserverClampinScrollPhysics 14 | extends ChatObserverClampingScrollPhysics { 15 | ChatObserverClampinScrollPhysics({ 16 | required ChatScrollObserver observer, 17 | }) : super(observer: observer); 18 | } 19 | 20 | class ChatObserverClampingScrollPhysics extends ClampingScrollPhysics 21 | with ChatObserverScrollPhysicsMixin { 22 | ChatObserverClampingScrollPhysics({ 23 | ScrollPhysics? parent, 24 | required ChatScrollObserver observer, 25 | }) : super(parent: parent) { 26 | this.observer = observer; 27 | } 28 | 29 | @override 30 | ChatObserverClampingScrollPhysics applyTo(ScrollPhysics? ancestor) { 31 | return ChatObserverClampingScrollPhysics( 32 | parent: buildParent(ancestor), 33 | observer: observer, 34 | ); 35 | } 36 | } 37 | 38 | class ChatObserverBouncingScrollPhysics extends BouncingScrollPhysics 39 | with ChatObserverScrollPhysicsMixin { 40 | ChatObserverBouncingScrollPhysics({ 41 | ScrollPhysics? parent, 42 | required ChatScrollObserver observer, 43 | }) : super(parent: parent) { 44 | this.observer = observer; 45 | } 46 | 47 | @override 48 | ChatObserverBouncingScrollPhysics applyTo(ScrollPhysics? ancestor) { 49 | return ChatObserverBouncingScrollPhysics( 50 | parent: buildParent(ancestor), 51 | observer: observer, 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/src/utils/src/chat/chat_scroll_observer_model.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/LinXunFeng/flutter_scrollview_observer 4 | * @Date: 2023-05-13 10:33:00 5 | */ 6 | import 'package:flutter/material.dart'; 7 | 8 | import 'package:scrollview_observer/src/common/models/observe_displaying_child_model_mixin.dart'; 9 | import 'package:scrollview_observer/src/utils/src/chat/chat_scroll_observer.dart'; 10 | import 'package:scrollview_observer/src/utils/src/chat/chat_scroll_observer_typedefs.dart'; 11 | 12 | class ChatScrollObserverHandlePositionResultModel { 13 | /// The type of processing location. 14 | final ChatScrollObserverHandlePositionType type; 15 | 16 | /// The mode of processing. 17 | final ChatScrollObserverHandleMode mode; 18 | 19 | /// The number of messages added. 20 | final int changeCount; 21 | 22 | ChatScrollObserverHandlePositionResultModel({ 23 | required this.type, 24 | required this.mode, 25 | required this.changeCount, 26 | }); 27 | } 28 | 29 | class ChatScrollObserverCustomAdjustPositionDeltaModel { 30 | /// The old position. 31 | final ScrollMetrics oldPosition; 32 | 33 | /// The new position. 34 | final ScrollMetrics newPosition; 35 | 36 | /// Whether the ScrollView is scrolling. 37 | final bool isScrolling; 38 | 39 | /// The current velocity of the scroll position. 40 | final double velocity; 41 | 42 | /// The scroll position should be given for new viewport dimensions. 43 | final double adjustPosition; 44 | 45 | /// The [ChatScrollObserver] instance. 46 | final ChatScrollObserver observer; 47 | 48 | /// The observation result of the current item. 49 | final ObserveDisplayingChildModelMixin currentItemModel; 50 | 51 | ChatScrollObserverCustomAdjustPositionDeltaModel({ 52 | required this.oldPosition, 53 | required this.newPosition, 54 | required this.isScrolling, 55 | required this.velocity, 56 | required this.adjustPosition, 57 | required this.observer, 58 | required this.currentItemModel, 59 | }); 60 | } 61 | 62 | class ChatScrollObserverCustomAdjustPositionModel { 63 | /// The old position. 64 | final ScrollMetrics oldPosition; 65 | 66 | /// The new position. 67 | final ScrollMetrics newPosition; 68 | 69 | /// Whether the ScrollView is scrolling. 70 | final bool isScrolling; 71 | 72 | /// The current velocity of the scroll position. 73 | final double velocity; 74 | 75 | /// The scroll position should be given for new viewport dimensions. 76 | final double adjustPosition; 77 | 78 | /// The [ChatScrollObserver] instance. 79 | final ChatScrollObserver observer; 80 | 81 | ChatScrollObserverCustomAdjustPositionModel({ 82 | required this.oldPosition, 83 | required this.newPosition, 84 | required this.isScrolling, 85 | required this.velocity, 86 | required this.adjustPosition, 87 | required this.observer, 88 | }); 89 | } 90 | -------------------------------------------------------------------------------- /lib/src/utils/src/chat/chat_scroll_observer_typedefs.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/LinXunFeng/flutter_scrollview_observer 4 | * @Date: 2022-10-31 14:57:45 5 | */ 6 | 7 | import 'package:scrollview_observer/src/utils/src/chat/chat_scroll_observer_model.dart'; 8 | 9 | /// Customize the delta of the adjustPosition. 10 | typedef ChatScrollObserverCustomAdjustPositionDelta = double? Function( 11 | ChatScrollObserverCustomAdjustPositionDeltaModel, 12 | ); 13 | 14 | /// Customize the scroll position should be given new viewport dimensions. 15 | typedef ChatScrollObserverCustomAdjustPosition = double? Function( 16 | ChatScrollObserverCustomAdjustPositionModel, 17 | ); 18 | 19 | enum ChatScrollObserverHandlePositionType { 20 | /// Nothing will be done. 21 | none, 22 | 23 | /// Keep the current chat location. 24 | keepPosition, 25 | } 26 | 27 | enum ChatScrollObserverHandleMode { 28 | /// Regular mode 29 | /// Such as inserting or deleting messages. 30 | normal, 31 | 32 | /// Generative mode 33 | /// Such as ChatGPT streaming messages. 34 | generative, 35 | 36 | /// Specified mode 37 | /// You can specify the index of the reference message in this mode. 38 | specified, 39 | } 40 | 41 | enum ChatScrollObserverRefIndexType { 42 | /// relativeIndex trailing 43 | /// 44 | /// 6 | item16 | cacheExtent 45 | /// ----------------- ----------------- 46 | /// 5 | item15 | 47 | /// 4 | item14 | 48 | /// 3 | item13 | displaying 49 | /// 2 | item12 | 50 | /// 1 | item11 | 51 | /// ----------------- ----------------- 52 | /// 0 | item10 | cacheExtent <---- start 53 | /// 54 | /// leading 55 | relativeIndexStartFromCacheExtent, 56 | 57 | /// relativeIndex trailing 58 | /// 59 | /// 5 | item16 | cacheExtent 60 | /// ----------------- ----------------- 61 | /// 4 | item15 | 62 | /// 3 | item14 | 63 | /// 2 | item13 | displaying 64 | /// 1 | item12 | 65 | /// 0 | item11 | <---- start 66 | /// ----------------- ----------------- 67 | /// -1 | item10 | cacheExtent 68 | /// 69 | /// leading 70 | relativeIndexStartFromDisplaying, 71 | 72 | /// Directly specify the index of item. 73 | itemIndex, 74 | } 75 | -------------------------------------------------------------------------------- /lib/src/utils/src/extends.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/fluttercandies/flutter_scrollview_observer 4 | * @Date: 2024-03-12 22:50:53 5 | */ 6 | 7 | import 'package:flutter/rendering.dart'; 8 | 9 | extension ObserverDouble on double { 10 | /// Rectify the value according to the current growthDirection of sliver. 11 | /// 12 | /// If the growthDirection is [GrowthDirection.forward], the value is 13 | /// returned directly, otherwise the opposite value is returned. 14 | double rectify( 15 | RenderSliver obj, 16 | ) { 17 | return obj.isForwardGrowthDirection ? this : -this; 18 | } 19 | } 20 | 21 | extension ObserverRenderSliverMultiBoxAdaptor on RenderSliver { 22 | /// Determine whether the current growthDirection of sliver is 23 | /// [GrowthDirection.forward]. 24 | bool get isForwardGrowthDirection { 25 | return GrowthDirection.forward == constraints.growthDirection; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/src/utils/src/log.dart: -------------------------------------------------------------------------------- 1 | import 'dart:developer' as developer; 2 | 3 | class Log { 4 | Log._(); 5 | 6 | static void info(String msg) { 7 | _log('\x1B[34m$msg\x1B[0m'); 8 | } 9 | 10 | static void success(String msg) { 11 | _log('\x1B[32m$msg\x1B[0m'); 12 | } 13 | 14 | static warning(String msg) { 15 | _log('\x1B[33m$msg\x1B[0m'); 16 | } 17 | 18 | static error(String msg) { 19 | _log('\x1B[31m$msg\x1B[0m'); 20 | } 21 | 22 | static _log(String message) { 23 | developer.log(message, name: 'scrollview_observer'); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/src/utils/src/slivers.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/rendering.dart'; 3 | 4 | class SliverObserveContext extends SliverPadding { 5 | final void Function(BuildContext) onObserve; 6 | const SliverObserveContext({ 7 | Key? key, 8 | Widget? child, 9 | required this.onObserve, 10 | }) : super( 11 | key: key, 12 | padding: EdgeInsets.zero, 13 | sliver: child, 14 | ); 15 | 16 | @override 17 | RenderSliverPadding createRenderObject(BuildContext context) { 18 | onObserve.call(context); 19 | return super.createRenderObject(context); 20 | } 21 | } 22 | 23 | class SliverObserveContextToBoxAdapter extends SliverToBoxAdapter { 24 | final void Function(BuildContext) onObserve; 25 | 26 | const SliverObserveContextToBoxAdapter({ 27 | Key? key, 28 | required Widget? child, 29 | required this.onObserve, 30 | }) : super(key: key, child: child); 31 | 32 | @override 33 | RenderSliverToBoxAdapter createRenderObject(BuildContext context) { 34 | onObserve.call(context); 35 | return super.createRenderObject(context); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /listview_observer.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: scrollview_observer 2 | description: A widget for observing data related to the child widgets being displayed in a ScrollView. 3 | version: 1.26.1 4 | homepage: https://github.com/fluttercandies/flutter_scrollview_observer 5 | 6 | environment: 7 | sdk: ">=2.12.0 <4.0.0" 8 | flutter: '>=3.7.0' 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | 14 | dev_dependencies: 15 | flutter_test: 16 | sdk: flutter 17 | flutter_lints: ^1.0.0 18 | -------------------------------------------------------------------------------- /test/observer_utils_test.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Author: LinXunFeng linxunfeng@yeah.net 3 | * @Repo: https://github.com/fluttercandies/flutter_scrollview_observer 4 | * @Date: 2024-05-20 22:19:27 5 | */ 6 | 7 | import 'package:flutter/material.dart'; 8 | import 'package:flutter_test/flutter_test.dart'; 9 | import 'package:scrollview_observer/scrollview_observer.dart'; 10 | 11 | void main() { 12 | testWidgets('check calcAnchorTabIndex', (tester) async { 13 | final scrollController = ScrollController(); 14 | final observerController = ListObserverController( 15 | controller: scrollController, 16 | ); 17 | ObserveModel? observeModel; 18 | Widget widget = Directionality( 19 | textDirection: TextDirection.ltr, 20 | child: ListView.separated( 21 | controller: scrollController, 22 | itemBuilder: (ctx, index) { 23 | return const SizedBox( 24 | width: double.infinity, 25 | height: 80, 26 | ); 27 | }, 28 | separatorBuilder: (ctx, index) { 29 | return const SizedBox(height: 10); 30 | }, 31 | itemCount: 100, 32 | ), 33 | ); 34 | widget = ListViewObserver( 35 | child: widget, 36 | controller: observerController, 37 | onObserve: (resultModel) => observeModel = resultModel, 38 | ); 39 | await tester.pumpWidget(widget); 40 | 41 | observerController.jumpTo(index: 5); 42 | await tester.pumpAndSettle(); 43 | expect(observeModel, isNotNull); 44 | List tabIndexes = [0, 5, 10]; 45 | int tabIndex = ObserverUtils.calcAnchorTabIndex( 46 | observeModel: observeModel!, 47 | tabIndexs: tabIndexes, 48 | currentTabIndex: 0, 49 | ); 50 | expect(tabIndex, 1); 51 | 52 | observerController.jumpTo(index: 9); 53 | await tester.pumpAndSettle(); 54 | tabIndex = ObserverUtils.calcAnchorTabIndex( 55 | observeModel: observeModel!, 56 | tabIndexs: tabIndexes, 57 | currentTabIndex: 1, 58 | ); 59 | expect(tabIndex, 1); 60 | 61 | scrollController.dispose(); 62 | }); 63 | } 64 | --------------------------------------------------------------------------------