├── .github └── FUNDING.yml ├── .gitignore ├── .metadata ├── .pubignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── docs └── images │ ├── aligned.png │ ├── aligned_example.png │ ├── masonry.png │ ├── masonry_example.png │ ├── quilted.png │ ├── quilted_example.png │ ├── staggered.png │ ├── staggered_example.png │ ├── staired.png │ ├── staired_example.png │ ├── woven.png │ └── woven_example.png ├── example └── README.md ├── examples ├── .gitignore ├── .metadata ├── README.md ├── analysis_options.yaml ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── example │ │ │ │ │ └── examples │ │ │ │ │ └── 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 ├── assets │ ├── aligned.png │ ├── masonry.png │ ├── quilted.png │ ├── staggered.png │ ├── staired.png │ └── woven.png ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── 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 │ ├── common.dart │ ├── examples │ │ ├── aligned.dart │ │ ├── masonry.dart │ │ ├── quilted.dart │ │ ├── staggered.dart │ │ ├── staired.dart │ │ └── woven.dart │ ├── main.dart │ ├── main_examples.dart │ └── pages │ │ ├── aligned.dart │ │ ├── masonry.dart │ │ ├── quilted.dart │ │ ├── staggered.dart │ │ ├── staired.dart │ │ └── woven.dart ├── macos │ ├── .gitignore │ ├── 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.yaml └── web │ ├── favicon.png │ ├── icons │ ├── Icon-192.png │ ├── Icon-512.png │ ├── Icon-maskable-192.png │ └── Icon-maskable-512.png │ ├── index.html │ └── manifest.json ├── lib ├── flutter_staggered_grid_view.dart └── src │ ├── foundation │ ├── constants.dart │ └── extensions.dart │ ├── layouts │ ├── quilted.dart │ ├── sliver_patterned_grid_delegate.dart │ ├── staired.dart │ └── woven.dart │ ├── rendering │ ├── sliver_masonry_grid.dart │ ├── sliver_simple_grid_delegate.dart │ ├── staggered_grid.dart │ └── uniform_track.dart │ └── widgets │ ├── aligned_grid_view.dart │ ├── masonry_grid_view.dart │ ├── sliver_aligned_grid.dart │ ├── sliver_masonry_grid.dart │ ├── staggered_grid.dart │ ├── staggered_grid_tile.dart │ └── uniform_track.dart ├── pubspec.yaml └── test ├── common.dart └── src ├── layouts ├── quilted_test.dart ├── staired_test.dart └── woven_test.dart └── widgets ├── aligned_grid_view_test.dart ├── masonry_grid_view_test.dart ├── staggered_grid_test.dart ├── staggered_grid_tile_test.dart └── uniform_track_test.dart /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: letsar 4 | patreon: romainrastel 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 | custom: ['https://www.buymeacoffee.com/romainrastel', 'paypal.me/RomainRastel'] 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.lock 4 | *.log 5 | *.pyc 6 | *.swp 7 | .DS_Store 8 | .atom/ 9 | .buildlog/ 10 | .history 11 | .svn/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # Visual Studio Code related 20 | .classpath 21 | .project 22 | .settings/ 23 | .vscode/ 24 | 25 | # Flutter repo-specific 26 | /bin/cache/ 27 | /bin/mingit/ 28 | /dev/benchmarks/mega_gallery/ 29 | /dev/bots/.recipe_deps 30 | /dev/bots/android_tools/ 31 | /dev/docs/doc/ 32 | /dev/docs/flutter.docs.zip 33 | /dev/docs/lib/ 34 | /dev/docs/pubspec.yaml 35 | /dev/integration_tests/**/xcuserdata 36 | /dev/integration_tests/**/Pods 37 | /packages/flutter/coverage/ 38 | version 39 | # packages file containing multi-root paths 40 | .packages.generated 41 | 42 | # Flutter/Dart/Pub related 43 | **/doc/api/ 44 | .dart_tool/ 45 | .flutter-plugins 46 | .flutter-plugins-dependencies 47 | .packages 48 | .pub-cache/ 49 | .pub/ 50 | build/ 51 | flutter_*.png 52 | linked_*.ds 53 | unlinked.ds 54 | unlinked_spec.ds 55 | 56 | # Web related 57 | lib/generated_plugin_registrant.dart 58 | 59 | # Symbolication related 60 | app.*.symbols 61 | 62 | # Obfuscation related 63 | app.*.map.json 64 | 65 | # Android related 66 | **/android/**/gradle-wrapper.jar 67 | **/android/.gradle 68 | **/android/captures/ 69 | **/android/gradlew 70 | **/android/gradlew.bat 71 | **/android/local.properties 72 | **/android/**/GeneratedPluginRegistrant.java 73 | **/android/key.properties 74 | *.jks 75 | 76 | # iOS/XCode related 77 | **/ios/**/*.mode1v3 78 | **/ios/**/*.mode2v3 79 | **/ios/**/*.moved-aside 80 | **/ios/**/*.pbxuser 81 | **/ios/**/*.perspectivev3 82 | **/ios/**/*sync/ 83 | **/ios/**/.sconsign.dblite 84 | **/ios/**/.tags* 85 | **/ios/**/.vagrant/ 86 | **/ios/**/DerivedData/ 87 | **/ios/**/Icon? 88 | **/ios/**/Pods/ 89 | **/ios/**/.symlinks/ 90 | **/ios/**/profile 91 | **/ios/**/xcuserdata 92 | **/ios/.generated/ 93 | **/ios/Flutter/App.framework 94 | **/ios/Flutter/Flutter.framework 95 | **/ios/Flutter/Flutter.podspec 96 | **/ios/Flutter/Generated.xcconfig 97 | **/ios/Flutter/app.flx 98 | **/ios/Flutter/app.zip 99 | **/ios/Flutter/flutter_assets/ 100 | **/ios/Flutter/flutter_export_environment.sh 101 | **/ios/ServiceDefinitions.json 102 | **/ios/Runner/GeneratedPluginRegistrant.* 103 | **/ios/Flutter/.last_build_id 104 | 105 | 106 | # macOS 107 | **/macos/Flutter/GeneratedPluginRegistrant.swift 108 | **/macos/Flutter/Flutter-Debug.xcconfig 109 | **/macos/Flutter/Flutter-Release.xcconfig 110 | **/macos/Flutter/Flutter-Profile.xcconfig 111 | 112 | # Coverage 113 | coverage/ 114 | 115 | # Symbols 116 | app.*.symbols 117 | 118 | # Exceptions to above rules. 119 | !**/ios/**/default.mode1v3 120 | !**/ios/**/default.mode2v3 121 | !**/ios/**/default.pbxuser 122 | !**/ios/**/default.perspectivev3 123 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 124 | !/dev/ci/**/Gemfile.lock 125 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 18116933e77adc82f80866c928266a5b4f1ed645 8 | channel: stable 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /.pubignore: -------------------------------------------------------------------------------- 1 | docs/ 2 | examples/ 3 | build/ 4 | *.iml -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.7.0 2 | ### Changed 3 | * Bump minimal Flutter version to 3.7.0 4 | ### Fixed 5 | * Warning with Scrollable.of 6 | 7 | ## 0.6.2 8 | ### Fixed 9 | * Issue with Quilted layout algorithm. (https://github.com/letsar/flutter_staggered_grid_view/issues/232) 10 | 11 | ## 0.6.1 12 | ### Fixed 13 | * Issue when childCount is 0 with Quilted layout. 14 | 15 | ## 0.6.0 16 | ### Added 17 | * SliverAlignedGrid and AlignedGridView widgets. 18 | ### Changed 19 | * Renamed SliverMasonryGridDelegate to SliverSimpleGridDelegate. 20 | 21 | ## 0.5.1 22 | ### Added 23 | * StaggeredTile.fit constructor. 24 | 25 | ## 0.5.0 26 | ### Changed 27 | * Stable release 28 | 29 | ## 0.5.0-dev.8 30 | ### Changed 31 | * Make the `childCount` parameter of `SliverMasonryGrid` constructors, nullable and not required. 32 | 33 | ## 0.5.0-dev.7 34 | ### Fixed 35 | * Remove position issue with staired pattern. 36 | 37 | ## 0.5.0-dev.6 38 | ### Fixed 39 | * Remove extra space on fixed woven grid. 40 | 41 | ## 0.5.0-dev.5 42 | ### Fixed 43 | * Remove extra space on fixed quilted grid (#216). 44 | 45 | ## 0.5.0-dev.4 46 | ### Fixed 47 | * Issue with Woven pattern layout flow in second run. 48 | 49 | ## 0.5.0-dev.3 50 | ### Fixed 51 | * Issue with Woven pattern and text direction. 52 | 53 | ## 0.5.0-dev.2 54 | ### Fixed 55 | * Issue with Quilted pattern. 56 | 57 | ## 0.5.0-dev.1 58 | ### Changed 59 | * Complete rewriting of the package. 60 | It comes now with 5 differents grid layouts (Staggered, Masonry, Quilted, Woven, Staired). 61 | 62 | ## 0.4.1 63 | ### Changed 64 | * Add option to disable keepAlives 65 | 66 | ## 0.4.0 67 | ### Changed 68 | * Stable null safety version 69 | 70 | ## 0.4.0-nullsafety.3 71 | ### Fixed 72 | * LateInitializationError: Local `firstIndex` has not been initialized. (https://github.com/letsar/flutter_staggered_grid_view/issues/151) 73 | 74 | ## 0.4.0-nullsafety.2 75 | ### Added 76 | * Support for state restoration 77 | 78 | ## 0.4.0-nullsafety.1 79 | ### Added 80 | * Null Safety Support 81 | 82 | ## 0.3.4 83 | ### Fixed 84 | * KeepAliveBucket logic, should improve performances 85 | 86 | ## 0.3.3 87 | ### Added 88 | * Support for state restoration. 89 | 90 | ## 0.3.2 91 | ### Fixed 92 | * Flutter version dependency. 93 | 94 | ## 0.3.1 95 | ### Fixed 96 | * Static analysis issues. 97 | 98 | ## 0.3.0 99 | ### Fixed 100 | * Upgrade to AndroidX and fixes the BoxHitTestResult exception (https://github.com/letsar/flutter_staggered_grid_view/issues/49) 101 | 102 | ## 0.2.7 103 | ### Fixed 104 | * Better fix for the bug where items are built only once. 105 | 106 | ## 0.2.6 107 | ### Fixed 108 | * Fix a bug where items are built only once. 109 | 110 | ## 0.2.5 111 | ### Changed 112 | * Use the new SliverWithKeepAliveWidget. 113 | 114 | ## 0.2.4 115 | ### Fixed 116 | * Dart 2.1 mixin support. 117 | 118 | ## 0.2.3 119 | ### Fixed 120 | * Fix the rtl support (https://github.com/letsar/flutter_staggered_grid_view/issues/17). 121 | 122 | ## 0.2.2 123 | * Add Dart 2 support. 124 | 125 | ## 0.2.1 126 | * Fix #10 `StatefulWidget.createState must return a subtype of State`. 127 | 128 | ## 0.2.0 129 | * Add a way to let the tile's content to define the tile's extent in the main axis. 130 | * Add `fit` constructor to `StaggeredTile`. 131 | 132 | ## 0.1.4 133 | * Add `countBuilder` and `extendBuilder` constructors to `SliverStaggeredGrid` 134 | 135 | ## 0.1.3 136 | * Remove Flutter SDK constraint 137 | 138 | ## 0.1.2 139 | * Remove update Flutter SDK constraint 140 | 141 | ## 0.1.1 142 | * Fix images in readme 143 | * Add dynamic resizing demo 144 | 145 | ## 0.1.0 146 | * Initial Open Source release -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Romain Rastel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | analyzer: 2 | strong-mode: 3 | implicit-casts: false 4 | implicit-dynamic: false 5 | errors: 6 | missing_required_param: error 7 | missing_return: error 8 | todo: warning 9 | include_file_not_found: ignore 10 | 11 | linter: 12 | rules: 13 | - annotate_overrides 14 | - avoid_empty_else 15 | - avoid_function_literals_in_foreach_calls 16 | - avoid_init_to_null 17 | - avoid_null_checks_in_equality_operators 18 | - avoid_relative_lib_imports 19 | - avoid_renaming_method_parameters 20 | - avoid_return_types_on_setters 21 | - avoid_returning_null 22 | - avoid_types_as_parameter_names 23 | - avoid_unused_constructor_parameters 24 | - await_only_futures 25 | - camel_case_types 26 | - cancel_subscriptions 27 | - comment_references 28 | - constant_identifier_names 29 | - control_flow_in_finally 30 | - directives_ordering 31 | - empty_catches 32 | - empty_constructor_bodies 33 | - empty_statements 34 | - hash_and_equals 35 | - implementation_imports 36 | - invariant_booleans 37 | - iterable_contains_unrelated_type 38 | - library_names 39 | - library_prefixes 40 | - list_remove_unrelated_type 41 | - no_adjacent_strings_in_list 42 | - no_duplicate_case_values 43 | - non_constant_identifier_names 44 | - null_closures 45 | - only_throw_errors 46 | - overridden_fields 47 | - package_api_docs 48 | - package_names 49 | - package_prefixed_library_names 50 | - prefer_adjacent_string_concatenation 51 | - prefer_collection_literals 52 | - prefer_conditional_assignment 53 | - prefer_const_constructors 54 | - prefer_contains 55 | - prefer_equal_for_default_values 56 | - prefer_final_fields 57 | - prefer_initializing_formals 58 | - prefer_interpolation_to_compose_strings 59 | - prefer_is_empty 60 | - prefer_is_not_empty 61 | - prefer_single_quotes 62 | - prefer_typing_uninitialized_variables 63 | - public_member_api_docs 64 | - recursive_getters 65 | - require_trailing_commas 66 | - slash_for_doc_comments 67 | - test_types_in_equals 68 | - throw_in_finally 69 | - type_init_formals 70 | - type_annotate_public_apis 71 | - unawaited_futures 72 | - unnecessary_brace_in_string_interps 73 | - unnecessary_const 74 | - unnecessary_getters_setters 75 | - unnecessary_lambdas 76 | - unnecessary_new 77 | - unnecessary_null_aware_assignments 78 | - unnecessary_statements 79 | - unnecessary_this 80 | - unrelated_type_equality_checks 81 | - use_rethrow_when_possible 82 | - valid_regexps -------------------------------------------------------------------------------- /docs/images/aligned.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/flutter_staggered_grid_view/0bfd05f285a6ecf390a8927e6ad4e67f7a39979b/docs/images/aligned.png -------------------------------------------------------------------------------- /docs/images/aligned_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/flutter_staggered_grid_view/0bfd05f285a6ecf390a8927e6ad4e67f7a39979b/docs/images/aligned_example.png -------------------------------------------------------------------------------- /docs/images/masonry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/flutter_staggered_grid_view/0bfd05f285a6ecf390a8927e6ad4e67f7a39979b/docs/images/masonry.png -------------------------------------------------------------------------------- /docs/images/masonry_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/flutter_staggered_grid_view/0bfd05f285a6ecf390a8927e6ad4e67f7a39979b/docs/images/masonry_example.png -------------------------------------------------------------------------------- /docs/images/quilted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/flutter_staggered_grid_view/0bfd05f285a6ecf390a8927e6ad4e67f7a39979b/docs/images/quilted.png -------------------------------------------------------------------------------- /docs/images/quilted_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/flutter_staggered_grid_view/0bfd05f285a6ecf390a8927e6ad4e67f7a39979b/docs/images/quilted_example.png -------------------------------------------------------------------------------- /docs/images/staggered.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/flutter_staggered_grid_view/0bfd05f285a6ecf390a8927e6ad4e67f7a39979b/docs/images/staggered.png -------------------------------------------------------------------------------- /docs/images/staggered_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/flutter_staggered_grid_view/0bfd05f285a6ecf390a8927e6ad4e67f7a39979b/docs/images/staggered_example.png -------------------------------------------------------------------------------- /docs/images/staired.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/flutter_staggered_grid_view/0bfd05f285a6ecf390a8927e6ad4e67f7a39979b/docs/images/staired.png -------------------------------------------------------------------------------- /docs/images/staired_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/flutter_staggered_grid_view/0bfd05f285a6ecf390a8927e6ad4e67f7a39979b/docs/images/staired_example.png -------------------------------------------------------------------------------- /docs/images/woven.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/flutter_staggered_grid_view/0bfd05f285a6ecf390a8927e6ad4e67f7a39979b/docs/images/woven.png -------------------------------------------------------------------------------- /docs/images/woven_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/flutter_staggered_grid_view/0bfd05f285a6ecf390a8927e6ad4e67f7a39979b/docs/images/woven_example.png -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/flutter_staggered_grid_view/0bfd05f285a6ecf390a8927e6ad4e67f7a39979b/example/README.md -------------------------------------------------------------------------------- /examples/.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 | -------------------------------------------------------------------------------- /examples/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 18116933e77adc82f80866c928266a5b4f1ed645 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /examples/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. 17 | -------------------------------------------------------------------------------- /examples/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 | -------------------------------------------------------------------------------- /examples/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 | -------------------------------------------------------------------------------- /examples/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 30 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.examples" 47 | minSdkVersion 16 48 | targetSdkVersion 30 49 | versionCode flutterVersionCode.toInteger() 50 | versionName flutterVersionName 51 | } 52 | 53 | buildTypes { 54 | release { 55 | // TODO: Add your own signing config for the release build. 56 | // Signing with the debug keys for now, so `flutter run --release` works. 57 | signingConfig signingConfigs.debug 58 | } 59 | } 60 | } 61 | 62 | flutter { 63 | source '../..' 64 | } 65 | 66 | dependencies { 67 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 68 | } 69 | -------------------------------------------------------------------------------- /examples/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /examples/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 13 | 17 | 21 | 26 | 30 | 31 | 32 | 33 | 34 | 35 | 37 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /examples/android/app/src/main/kotlin/com/example/examples/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.examples 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /examples/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /examples/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /examples/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/flutter_staggered_grid_view/0bfd05f285a6ecf390a8927e6ad4e67f7a39979b/examples/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /examples/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/flutter_staggered_grid_view/0bfd05f285a6ecf390a8927e6ad4e67f7a39979b/examples/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /examples/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/flutter_staggered_grid_view/0bfd05f285a6ecf390a8927e6ad4e67f7a39979b/examples/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /examples/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/flutter_staggered_grid_view/0bfd05f285a6ecf390a8927e6ad4e67f7a39979b/examples/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /examples/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/flutter_staggered_grid_view/0bfd05f285a6ecf390a8927e6ad4e67f7a39979b/examples/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /examples/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /examples/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /examples/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /examples/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:4.1.0' 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 | project.evaluationDependsOn(':app') 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /examples/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /examples/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip 7 | -------------------------------------------------------------------------------- /examples/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 | -------------------------------------------------------------------------------- /examples/assets/aligned.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/flutter_staggered_grid_view/0bfd05f285a6ecf390a8927e6ad4e67f7a39979b/examples/assets/aligned.png -------------------------------------------------------------------------------- /examples/assets/masonry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/flutter_staggered_grid_view/0bfd05f285a6ecf390a8927e6ad4e67f7a39979b/examples/assets/masonry.png -------------------------------------------------------------------------------- /examples/assets/quilted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/flutter_staggered_grid_view/0bfd05f285a6ecf390a8927e6ad4e67f7a39979b/examples/assets/quilted.png -------------------------------------------------------------------------------- /examples/assets/staggered.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/flutter_staggered_grid_view/0bfd05f285a6ecf390a8927e6ad4e67f7a39979b/examples/assets/staggered.png -------------------------------------------------------------------------------- /examples/assets/staired.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/flutter_staggered_grid_view/0bfd05f285a6ecf390a8927e6ad4e67f7a39979b/examples/assets/staired.png -------------------------------------------------------------------------------- /examples/assets/woven.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/flutter_staggered_grid_view/0bfd05f285a6ecf390a8927e6ad4e67f7a39979b/examples/assets/woven.png -------------------------------------------------------------------------------- /examples/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 | -------------------------------------------------------------------------------- /examples/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 | 11.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /examples/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /examples/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /examples/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /examples/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /examples/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /examples/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 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 | -------------------------------------------------------------------------------- /examples/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 | -------------------------------------------------------------------------------- /examples/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/flutter_staggered_grid_view/0bfd05f285a6ecf390a8927e6ad4e67f7a39979b/examples/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /examples/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/flutter_staggered_grid_view/0bfd05f285a6ecf390a8927e6ad4e67f7a39979b/examples/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /examples/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/flutter_staggered_grid_view/0bfd05f285a6ecf390a8927e6ad4e67f7a39979b/examples/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /examples/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/flutter_staggered_grid_view/0bfd05f285a6ecf390a8927e6ad4e67f7a39979b/examples/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /examples/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/flutter_staggered_grid_view/0bfd05f285a6ecf390a8927e6ad4e67f7a39979b/examples/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /examples/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/flutter_staggered_grid_view/0bfd05f285a6ecf390a8927e6ad4e67f7a39979b/examples/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /examples/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/flutter_staggered_grid_view/0bfd05f285a6ecf390a8927e6ad4e67f7a39979b/examples/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /examples/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/flutter_staggered_grid_view/0bfd05f285a6ecf390a8927e6ad4e67f7a39979b/examples/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /examples/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/flutter_staggered_grid_view/0bfd05f285a6ecf390a8927e6ad4e67f7a39979b/examples/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /examples/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/flutter_staggered_grid_view/0bfd05f285a6ecf390a8927e6ad4e67f7a39979b/examples/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /examples/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/flutter_staggered_grid_view/0bfd05f285a6ecf390a8927e6ad4e67f7a39979b/examples/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /examples/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/flutter_staggered_grid_view/0bfd05f285a6ecf390a8927e6ad4e67f7a39979b/examples/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /examples/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/flutter_staggered_grid_view/0bfd05f285a6ecf390a8927e6ad4e67f7a39979b/examples/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /examples/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/flutter_staggered_grid_view/0bfd05f285a6ecf390a8927e6ad4e67f7a39979b/examples/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /examples/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/flutter_staggered_grid_view/0bfd05f285a6ecf390a8927e6ad4e67f7a39979b/examples/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /examples/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 | -------------------------------------------------------------------------------- /examples/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/flutter_staggered_grid_view/0bfd05f285a6ecf390a8927e6ad4e67f7a39979b/examples/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /examples/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/flutter_staggered_grid_view/0bfd05f285a6ecf390a8927e6ad4e67f7a39979b/examples/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /examples/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/flutter_staggered_grid_view/0bfd05f285a6ecf390a8927e6ad4e67f7a39979b/examples/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /examples/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. -------------------------------------------------------------------------------- /examples/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 | -------------------------------------------------------------------------------- /examples/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 | -------------------------------------------------------------------------------- /examples/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | examples 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | CADisableMinimumFrameDurationOnPhone 45 | 46 | UIApplicationSupportsIndirectInputEvents 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /examples/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /examples/lib/common.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | const _defaultColor = Color(0xFF34568B); 4 | 5 | class AppScaffold extends StatelessWidget { 6 | const AppScaffold({ 7 | Key? key, 8 | required this.title, 9 | this.topPadding = 0, 10 | required this.child, 11 | }) : super(key: key); 12 | 13 | final String title; 14 | final Widget child; 15 | final double topPadding; 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return Scaffold( 20 | appBar: AppBar( 21 | title: Text(title), 22 | ), 23 | body: Padding( 24 | padding: EdgeInsets.only(top: topPadding), 25 | child: child, 26 | ), 27 | ); 28 | } 29 | } 30 | 31 | class Tile extends StatelessWidget { 32 | const Tile({ 33 | Key? key, 34 | required this.index, 35 | this.extent, 36 | this.backgroundColor, 37 | this.bottomSpace, 38 | }) : super(key: key); 39 | 40 | final int index; 41 | final double? extent; 42 | final double? bottomSpace; 43 | final Color? backgroundColor; 44 | 45 | @override 46 | Widget build(BuildContext context) { 47 | final child = Container( 48 | color: backgroundColor ?? _defaultColor, 49 | height: extent, 50 | child: Center( 51 | child: CircleAvatar( 52 | minRadius: 20, 53 | maxRadius: 20, 54 | backgroundColor: Colors.white, 55 | foregroundColor: Colors.black, 56 | child: Text('$index', style: const TextStyle(fontSize: 20)), 57 | ), 58 | ), 59 | ); 60 | 61 | if (bottomSpace == null) { 62 | return child; 63 | } 64 | 65 | return Column( 66 | children: [ 67 | Expanded(child: child), 68 | Container( 69 | height: bottomSpace, 70 | color: Colors.green, 71 | ) 72 | ], 73 | ); 74 | } 75 | } 76 | 77 | class ImageTile extends StatelessWidget { 78 | const ImageTile({ 79 | Key? key, 80 | required this.index, 81 | required this.width, 82 | required this.height, 83 | }) : super(key: key); 84 | 85 | final int index; 86 | final int width; 87 | final int height; 88 | 89 | @override 90 | Widget build(BuildContext context) { 91 | return Image.network( 92 | 'https://picsum.photos/$width/$height?random=$index', 93 | width: width.toDouble(), 94 | height: height.toDouble(), 95 | fit: BoxFit.cover, 96 | ); 97 | } 98 | } 99 | 100 | class InteractiveTile extends StatefulWidget { 101 | const InteractiveTile({ 102 | Key? key, 103 | required this.index, 104 | this.extent, 105 | this.bottomSpace, 106 | }) : super(key: key); 107 | 108 | final int index; 109 | final double? extent; 110 | final double? bottomSpace; 111 | 112 | @override 113 | _InteractiveTileState createState() => _InteractiveTileState(); 114 | } 115 | 116 | class _InteractiveTileState extends State { 117 | Color color = _defaultColor; 118 | 119 | @override 120 | Widget build(BuildContext context) { 121 | return GestureDetector( 122 | onTap: () { 123 | setState(() { 124 | if (color == _defaultColor) { 125 | color = Colors.red; 126 | } else { 127 | color = _defaultColor; 128 | } 129 | }); 130 | }, 131 | child: Tile( 132 | index: widget.index, 133 | extent: widget.extent, 134 | backgroundColor: color, 135 | bottomSpace: widget.bottomSpace, 136 | ), 137 | ); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /examples/lib/examples/aligned.dart: -------------------------------------------------------------------------------- 1 | import 'package:examples/common.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; 4 | 5 | class AlignedPage extends StatelessWidget { 6 | const AlignedPage({ 7 | Key? key, 8 | }) : super(key: key); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return AppScaffold( 13 | title: 'Aligned', 14 | child: AlignedGridView.count( 15 | crossAxisCount: 4, 16 | mainAxisSpacing: 4, 17 | crossAxisSpacing: 4, 18 | itemBuilder: (context, index) { 19 | return Tile( 20 | index: index, 21 | extent: (index % 7 + 1) * 30, 22 | ); 23 | }, 24 | ), 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /examples/lib/examples/masonry.dart: -------------------------------------------------------------------------------- 1 | import 'package:examples/common.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/widgets.dart'; 4 | import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; 5 | 6 | class MasonryPage extends StatelessWidget { 7 | const MasonryPage({ 8 | Key? key, 9 | }) : super(key: key); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return AppScaffold( 14 | title: 'Masonry', 15 | child: MasonryGridView.count( 16 | crossAxisCount: 4, 17 | mainAxisSpacing: 4, 18 | crossAxisSpacing: 4, 19 | itemBuilder: (context, index) { 20 | return Tile( 21 | index: index, 22 | extent: (index % 5 + 1) * 100, 23 | ); 24 | }, 25 | ), 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/lib/examples/quilted.dart: -------------------------------------------------------------------------------- 1 | import 'package:examples/common.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; 4 | 5 | class QuiltedPage extends StatelessWidget { 6 | const QuiltedPage({ 7 | Key? key, 8 | }) : super(key: key); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return AppScaffold( 13 | title: 'Quilted', 14 | child: GridView.custom( 15 | gridDelegate: SliverQuiltedGridDelegate( 16 | crossAxisCount: 4, 17 | mainAxisSpacing: 4, 18 | crossAxisSpacing: 4, 19 | repeatPattern: QuiltedGridRepeatPattern.inverted, 20 | pattern: const [ 21 | QuiltedGridTile(2, 2), 22 | QuiltedGridTile(1, 1), 23 | QuiltedGridTile(1, 1), 24 | QuiltedGridTile(1, 2), 25 | ], 26 | ), 27 | childrenDelegate: SliverChildBuilderDelegate( 28 | (context, index) => Tile(index: index), 29 | ), 30 | ), 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/lib/examples/staggered.dart: -------------------------------------------------------------------------------- 1 | import 'package:examples/common.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; 4 | 5 | class StaggeredPage extends StatelessWidget { 6 | const StaggeredPage({ 7 | Key? key, 8 | }) : super(key: key); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return AppScaffold( 13 | title: 'Staggered', 14 | child: SingleChildScrollView( 15 | child: StaggeredGrid.count( 16 | crossAxisCount: 4, 17 | mainAxisSpacing: 4, 18 | crossAxisSpacing: 4, 19 | children: const [ 20 | StaggeredGridTile.count( 21 | crossAxisCellCount: 2, 22 | mainAxisCellCount: 2, 23 | child: Tile(index: 0), 24 | ), 25 | StaggeredGridTile.count( 26 | crossAxisCellCount: 2, 27 | mainAxisCellCount: 1, 28 | child: Tile(index: 1), 29 | ), 30 | StaggeredGridTile.count( 31 | crossAxisCellCount: 1, 32 | mainAxisCellCount: 1, 33 | child: Tile(index: 2), 34 | ), 35 | StaggeredGridTile.count( 36 | crossAxisCellCount: 1, 37 | mainAxisCellCount: 1, 38 | child: Tile(index: 3), 39 | ), 40 | StaggeredGridTile.count( 41 | crossAxisCellCount: 4, 42 | mainAxisCellCount: 2, 43 | child: Tile(index: 4), 44 | ), 45 | ], 46 | ), 47 | ), 48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /examples/lib/examples/staired.dart: -------------------------------------------------------------------------------- 1 | import 'package:examples/common.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; 4 | 5 | class StairedPage extends StatelessWidget { 6 | const StairedPage({ 7 | Key? key, 8 | }) : super(key: key); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return AppScaffold( 13 | title: 'Staired', 14 | child: GridView.custom( 15 | gridDelegate: SliverStairedGridDelegate( 16 | crossAxisSpacing: 48, 17 | mainAxisSpacing: 24, 18 | startCrossAxisDirectionReversed: true, 19 | pattern: const [ 20 | StairedGridTile(0.5, 1), 21 | StairedGridTile(0.5, 3 / 4), 22 | StairedGridTile(1.0, 10 / 4), 23 | ], 24 | ), 25 | childrenDelegate: SliverChildBuilderDelegate( 26 | (context, index) => Tile(index: index), 27 | ), 28 | ), 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /examples/lib/examples/woven.dart: -------------------------------------------------------------------------------- 1 | import 'package:examples/common.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; 4 | 5 | class WovenPage extends StatelessWidget { 6 | const WovenPage({ 7 | Key? key, 8 | }) : super(key: key); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return AppScaffold( 13 | title: 'Woven', 14 | child: GridView.custom( 15 | gridDelegate: SliverWovenGridDelegate.count( 16 | crossAxisCount: 2, 17 | mainAxisSpacing: 8, 18 | crossAxisSpacing: 8, 19 | pattern: const [ 20 | WovenGridTile(1), 21 | WovenGridTile( 22 | 5 / 7, 23 | crossAxisRatio: 0.9, 24 | alignment: AlignmentDirectional.centerEnd, 25 | ), 26 | ], 27 | ), 28 | childrenDelegate: SliverChildBuilderDelegate( 29 | (context, index) => Tile(index: index), 30 | ), 31 | ), 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /examples/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:examples/common.dart'; 2 | import 'package:examples/pages/aligned.dart'; 3 | import 'package:examples/pages/masonry.dart'; 4 | import 'package:examples/pages/quilted.dart'; 5 | import 'package:examples/pages/staggered.dart'; 6 | import 'package:examples/pages/staired.dart'; 7 | import 'package:examples/pages/woven.dart'; 8 | import 'package:flutter/material.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: 'Staggered Grid View Demo', 21 | theme: ThemeData( 22 | primarySwatch: Colors.blue, 23 | ), 24 | home: const HomePage(), 25 | ); 26 | } 27 | } 28 | 29 | class HomePage extends StatelessWidget { 30 | const HomePage({ 31 | Key? key, 32 | }) : super(key: key); 33 | 34 | @override 35 | Widget build(BuildContext context) { 36 | return AppScaffold( 37 | title: 'Staggered Grid View Demo', 38 | child: GridView( 39 | gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( 40 | crossAxisCount: 2, 41 | mainAxisSpacing: 16, 42 | crossAxisSpacing: 16, 43 | ), 44 | children: const [ 45 | MenuEntry( 46 | title: 'Staggered', 47 | imageName: 'staggered', 48 | destination: StaggeredPage(), 49 | ), 50 | MenuEntry( 51 | title: 'Masonry', 52 | imageName: 'masonry', 53 | destination: MasonryPage(), 54 | ), 55 | MenuEntry( 56 | title: 'Quilted', 57 | imageName: 'quilted', 58 | destination: QuiltedPage(), 59 | ), 60 | MenuEntry( 61 | title: 'Woven', 62 | imageName: 'woven', 63 | destination: WovenPage(), 64 | ), 65 | MenuEntry( 66 | title: 'Staired', 67 | imageName: 'staired', 68 | destination: StairedPage(), 69 | ), 70 | MenuEntry( 71 | title: 'Aligned', 72 | imageName: 'aligned', 73 | destination: AlignedPage(), 74 | ), 75 | ], 76 | ), 77 | ); 78 | } 79 | } 80 | 81 | class MenuEntry extends StatelessWidget { 82 | const MenuEntry({ 83 | Key? key, 84 | required this.title, 85 | required this.imageName, 86 | required this.destination, 87 | }) : super(key: key); 88 | 89 | final String title; 90 | final Widget destination; 91 | final String imageName; 92 | 93 | @override 94 | Widget build(BuildContext context) { 95 | return Card( 96 | elevation: 4, 97 | child: InkWell( 98 | onTap: () { 99 | Navigator.push( 100 | context, 101 | MaterialPageRoute( 102 | builder: (context) => destination, 103 | ), 104 | ); 105 | }, 106 | child: Stack( 107 | children: [ 108 | Image.asset( 109 | 'assets/$imageName.png', 110 | fit: BoxFit.fill, 111 | ), 112 | Positioned.fill( 113 | child: FractionallySizedBox( 114 | heightFactor: 0.25, 115 | alignment: Alignment.bottomCenter, 116 | child: ColoredBox( 117 | color: Colors.black.withOpacity(0.75), 118 | child: Center( 119 | child: Text( 120 | title, 121 | overflow: TextOverflow.ellipsis, 122 | textAlign: TextAlign.center, 123 | style: Theme.of(context) 124 | .textTheme 125 | .headline6! 126 | .copyWith(color: Colors.white), 127 | ), 128 | ), 129 | ), 130 | ), 131 | ), 132 | ], 133 | ), 134 | ), 135 | ); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /examples/lib/main_examples.dart: -------------------------------------------------------------------------------- 1 | import 'package:examples/common.dart'; 2 | import 'package:examples/examples/aligned.dart'; 3 | import 'package:examples/examples/masonry.dart'; 4 | import 'package:examples/examples/quilted.dart'; 5 | import 'package:examples/examples/staggered.dart'; 6 | import 'package:examples/examples/staired.dart'; 7 | import 'package:examples/examples/woven.dart'; 8 | import 'package:flutter/material.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: 'Staggered Grid View Demo', 21 | theme: ThemeData( 22 | primarySwatch: Colors.blue, 23 | ), 24 | home: const HomePage(), 25 | ); 26 | } 27 | } 28 | 29 | class HomePage extends StatelessWidget { 30 | const HomePage({ 31 | Key? key, 32 | }) : super(key: key); 33 | 34 | @override 35 | Widget build(BuildContext context) { 36 | return AppScaffold( 37 | title: 'Staggered Grid View Demo', 38 | child: GridView( 39 | gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( 40 | crossAxisCount: 2, 41 | mainAxisSpacing: 16, 42 | crossAxisSpacing: 16, 43 | ), 44 | children: const [ 45 | MenuEntry( 46 | title: 'Staggered', 47 | imageName: 'staggered', 48 | destination: StaggeredPage(), 49 | ), 50 | MenuEntry( 51 | title: 'Masonry', 52 | imageName: 'masonry', 53 | destination: MasonryPage(), 54 | ), 55 | MenuEntry( 56 | title: 'Quilted', 57 | imageName: 'quilted', 58 | destination: QuiltedPage(), 59 | ), 60 | MenuEntry( 61 | title: 'Woven', 62 | imageName: 'woven', 63 | destination: WovenPage(), 64 | ), 65 | MenuEntry( 66 | title: 'Staired', 67 | imageName: 'staired', 68 | destination: StairedPage(), 69 | ), 70 | MenuEntry( 71 | title: 'Aligned', 72 | imageName: 'aligned', 73 | destination: AlignedPage(), 74 | ), 75 | ], 76 | ), 77 | ); 78 | } 79 | } 80 | 81 | class MenuEntry extends StatelessWidget { 82 | const MenuEntry({ 83 | Key? key, 84 | required this.title, 85 | required this.imageName, 86 | required this.destination, 87 | }) : super(key: key); 88 | 89 | final String title; 90 | final Widget destination; 91 | final String imageName; 92 | 93 | @override 94 | Widget build(BuildContext context) { 95 | return Card( 96 | elevation: 4, 97 | child: InkWell( 98 | onTap: () { 99 | Navigator.push( 100 | context, 101 | MaterialPageRoute( 102 | builder: (context) => destination, 103 | ), 104 | ); 105 | }, 106 | child: Stack( 107 | children: [ 108 | Image.asset( 109 | 'assets/$imageName.png', 110 | fit: BoxFit.cover, 111 | ), 112 | Positioned.fill( 113 | child: FractionallySizedBox( 114 | heightFactor: 0.25, 115 | alignment: Alignment.bottomCenter, 116 | child: ColoredBox( 117 | color: Colors.black.withOpacity(0.75), 118 | child: Center( 119 | child: Text( 120 | title, 121 | overflow: TextOverflow.ellipsis, 122 | textAlign: TextAlign.center, 123 | style: Theme.of(context) 124 | .textTheme 125 | .headline6! 126 | .copyWith(color: Colors.white), 127 | ), 128 | ), 129 | ), 130 | ), 131 | ), 132 | ], 133 | ), 134 | ), 135 | ); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /examples/lib/pages/aligned.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:examples/common.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter/widgets.dart'; 6 | import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; 7 | 8 | class AlignedPage extends StatefulWidget { 9 | const AlignedPage({ 10 | Key? key, 11 | }) : super(key: key); 12 | 13 | @override 14 | State createState() => _AlignedPageState(); 15 | } 16 | 17 | class _AlignedPageState extends State { 18 | final rnd = Random(); 19 | late List extents; 20 | int crossAxisCount = 4; 21 | 22 | @override 23 | void initState() { 24 | super.initState(); 25 | extents = List.generate(10000, (int index) => rnd.nextInt(7) + 1); 26 | } 27 | 28 | @override 29 | Widget build(BuildContext context) { 30 | return AppScaffold( 31 | title: 'Aligned', 32 | child: AlignedGridView.count( 33 | crossAxisCount: crossAxisCount, 34 | mainAxisSpacing: 4, 35 | crossAxisSpacing: 4, 36 | itemBuilder: (context, index) { 37 | final height = extents[index] * 40; 38 | return ImageTile( 39 | index: index, 40 | width: 100, 41 | height: height, 42 | ); 43 | }, 44 | itemCount: extents.length, 45 | ), 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /examples/lib/pages/masonry.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:examples/common.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter/widgets.dart'; 6 | import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; 7 | 8 | class MasonryPage extends StatefulWidget { 9 | const MasonryPage({ 10 | Key? key, 11 | }) : super(key: key); 12 | 13 | @override 14 | State createState() => _MasonryPageState(); 15 | } 16 | 17 | class _MasonryPageState extends State { 18 | final rnd = Random(); 19 | late List extents; 20 | int crossAxisCount = 4; 21 | 22 | @override 23 | void initState() { 24 | super.initState(); 25 | extents = List.generate(10000, (int index) => rnd.nextInt(5) + 1); 26 | } 27 | 28 | @override 29 | Widget build(BuildContext context) { 30 | return AppScaffold( 31 | title: 'Masonry', 32 | child: MasonryGridView.count( 33 | crossAxisCount: crossAxisCount, 34 | mainAxisSpacing: 4, 35 | crossAxisSpacing: 4, 36 | itemBuilder: (context, index) { 37 | final height = extents[index] * 100; 38 | return ImageTile( 39 | index: index, 40 | width: 100, 41 | height: height, 42 | ); 43 | }, 44 | itemCount: extents.length, 45 | ), 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /examples/lib/pages/quilted.dart: -------------------------------------------------------------------------------- 1 | import 'package:examples/common.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; 4 | 5 | class QuiltedPage extends StatelessWidget { 6 | const QuiltedPage({ 7 | Key? key, 8 | }) : super(key: key); 9 | 10 | static const pattern = [ 11 | QuiltedGridTile(2, 2), 12 | QuiltedGridTile(1, 1), 13 | QuiltedGridTile(1, 1), 14 | QuiltedGridTile(1, 2), 15 | ]; 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return AppScaffold( 20 | title: 'Quilted', 21 | child: GridView.custom( 22 | gridDelegate: SliverQuiltedGridDelegate( 23 | crossAxisCount: 4, 24 | mainAxisSpacing: 4, 25 | crossAxisSpacing: 4, 26 | repeatPattern: QuiltedGridRepeatPattern.inverted, 27 | pattern: pattern, 28 | ), 29 | childrenDelegate: SliverChildBuilderDelegate( 30 | (context, index) { 31 | final tile = pattern[index % pattern.length]; 32 | return ImageTile( 33 | index: index, 34 | width: tile.crossAxisCount * 100, 35 | height: tile.mainAxisCount * 100, 36 | ); 37 | }, 38 | ), 39 | ), 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /examples/lib/pages/staggered.dart: -------------------------------------------------------------------------------- 1 | import 'package:collection/collection.dart'; 2 | import 'package:examples/common.dart'; 3 | import 'package:flutter/widgets.dart'; 4 | import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; 5 | 6 | class StaggeredPage extends StatelessWidget { 7 | const StaggeredPage({ 8 | Key? key, 9 | }) : super(key: key); 10 | 11 | static const tiles = [ 12 | GridTile(2, 2), 13 | GridTile(2, 1), 14 | GridTile(1, 2), 15 | GridTile(1, 1), 16 | GridTile(2, 2), 17 | GridTile(1, 2), 18 | GridTile(1, 1), 19 | GridTile(3, 1), 20 | GridTile(1, 1), 21 | GridTile(4, 1), 22 | ]; 23 | 24 | @override 25 | Widget build(BuildContext context) { 26 | return AppScaffold( 27 | title: 'Staggered', 28 | child: SingleChildScrollView( 29 | child: StaggeredGrid.count( 30 | crossAxisCount: 4, 31 | mainAxisSpacing: 4, 32 | crossAxisSpacing: 4, 33 | children: [ 34 | ...tiles.mapIndexed((index, tile) { 35 | return StaggeredGridTile.count( 36 | crossAxisCellCount: tile.crossAxisCount, 37 | mainAxisCellCount: tile.mainAxisCount, 38 | child: ImageTile( 39 | index: index, 40 | width: tile.crossAxisCount * 100, 41 | height: tile.mainAxisCount * 100, 42 | ), 43 | ); 44 | }), 45 | ], 46 | ), 47 | ), 48 | ); 49 | } 50 | } 51 | 52 | class GridTile { 53 | const GridTile(this.crossAxisCount, this.mainAxisCount); 54 | final int crossAxisCount; 55 | final int mainAxisCount; 56 | } 57 | -------------------------------------------------------------------------------- /examples/lib/pages/staired.dart: -------------------------------------------------------------------------------- 1 | import 'package:examples/common.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; 4 | 5 | class StairedPage extends StatelessWidget { 6 | const StairedPage({ 7 | Key? key, 8 | }) : super(key: key); 9 | 10 | static const pattern = [ 11 | StairedGridTile(0.5, 1), 12 | StairedGridTile(0.5, 3 / 4), 13 | StairedGridTile(1.0, 10 / 4), 14 | ]; 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return AppScaffold( 19 | title: 'Staired', 20 | child: Directionality( 21 | textDirection: TextDirection.ltr, 22 | child: GridView.custom( 23 | scrollDirection: Axis.vertical, 24 | gridDelegate: SliverStairedGridDelegate( 25 | mainAxisSpacing: 24, 26 | crossAxisSpacing: 48, 27 | startCrossAxisDirectionReversed: true, 28 | pattern: pattern, 29 | ), 30 | childrenDelegate: SliverChildBuilderDelegate( 31 | (context, index) { 32 | final tile = pattern[index % pattern.length]; 33 | return ImageTile( 34 | index: index, 35 | width: (tile.aspectRatio * 200).ceil(), 36 | height: 200, 37 | ); 38 | }, 39 | ), 40 | ), 41 | ), 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /examples/lib/pages/woven.dart: -------------------------------------------------------------------------------- 1 | import 'package:examples/common.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; 4 | 5 | class WovenPage extends StatelessWidget { 6 | const WovenPage({ 7 | Key? key, 8 | }) : super(key: key); 9 | 10 | static const pattern = [ 11 | WovenGridTile(1), 12 | WovenGridTile( 13 | 5 / 7, 14 | crossAxisRatio: 0.9, 15 | alignment: AlignmentDirectional.centerEnd, 16 | ), 17 | ]; 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return AppScaffold( 22 | title: 'Woven', 23 | child: GridView.custom( 24 | gridDelegate: SliverWovenGridDelegate.count( 25 | crossAxisCount: 2, 26 | mainAxisSpacing: 8, 27 | crossAxisSpacing: 8, 28 | pattern: pattern, 29 | ), 30 | childrenDelegate: SliverChildBuilderDelegate( 31 | (context, index) { 32 | final tile = pattern[index % pattern.length]; 33 | return ImageTile( 34 | index: index, 35 | width: (200 * tile.aspectRatio).ceil(), 36 | height: 200, 37 | ); 38 | }, 39 | ), 40 | ), 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /examples/macos/.gitignore: -------------------------------------------------------------------------------- 1 | # Flutter-related 2 | **/Flutter/ephemeral/ 3 | **/Pods/ 4 | 5 | # Xcode-related 6 | **/dgph 7 | **/xcuserdata/ 8 | -------------------------------------------------------------------------------- /examples/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/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 | 64 | 65 | 71 | 73 | 79 | 80 | 81 | 82 | 84 | 85 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /examples/macos/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /examples/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/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 | -------------------------------------------------------------------------------- /examples/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 | -------------------------------------------------------------------------------- /examples/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/flutter_staggered_grid_view/0bfd05f285a6ecf390a8927e6ad4e67f7a39979b/examples/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png -------------------------------------------------------------------------------- /examples/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/flutter_staggered_grid_view/0bfd05f285a6ecf390a8927e6ad4e67f7a39979b/examples/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png -------------------------------------------------------------------------------- /examples/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/flutter_staggered_grid_view/0bfd05f285a6ecf390a8927e6ad4e67f7a39979b/examples/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png -------------------------------------------------------------------------------- /examples/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/flutter_staggered_grid_view/0bfd05f285a6ecf390a8927e6ad4e67f7a39979b/examples/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png -------------------------------------------------------------------------------- /examples/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/flutter_staggered_grid_view/0bfd05f285a6ecf390a8927e6ad4e67f7a39979b/examples/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png -------------------------------------------------------------------------------- /examples/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/flutter_staggered_grid_view/0bfd05f285a6ecf390a8927e6ad4e67f7a39979b/examples/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png -------------------------------------------------------------------------------- /examples/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/flutter_staggered_grid_view/0bfd05f285a6ecf390a8927e6ad4e67f7a39979b/examples/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png -------------------------------------------------------------------------------- /examples/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 = examples 9 | 10 | // The application's bundle identifier 11 | PRODUCT_BUNDLE_IDENTIFIER = com.example.examples 12 | 13 | // The copyright displayed in application information 14 | PRODUCT_COPYRIGHT = Copyright © 2021 com.example. All rights reserved. 15 | -------------------------------------------------------------------------------- /examples/macos/Runner/Configs/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Debug.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /examples/macos/Runner/Configs/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Release.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /examples/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 | -------------------------------------------------------------------------------- /examples/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 | -------------------------------------------------------------------------------- /examples/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 | -------------------------------------------------------------------------------- /examples/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 | -------------------------------------------------------------------------------- /examples/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 | -------------------------------------------------------------------------------- /examples/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: examples 2 | description: A new Flutter project. 3 | publish_to: 'none' 4 | version: 1.0.0+1 5 | 6 | environment: 7 | sdk: ">=2.12.0 <3.0.0" 8 | 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | cupertino_icons: ^1.0.2 13 | flutter_lints: any 14 | flutter_staggered_grid_view: 15 | path: ../ 16 | 17 | dev_dependencies: 18 | flutter_test: 19 | sdk: flutter 20 | 21 | flutter: 22 | assets: 23 | - assets/ 24 | uses-material-design: true -------------------------------------------------------------------------------- /examples/web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/flutter_staggered_grid_view/0bfd05f285a6ecf390a8927e6ad4e67f7a39979b/examples/web/favicon.png -------------------------------------------------------------------------------- /examples/web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/flutter_staggered_grid_view/0bfd05f285a6ecf390a8927e6ad4e67f7a39979b/examples/web/icons/Icon-192.png -------------------------------------------------------------------------------- /examples/web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/flutter_staggered_grid_view/0bfd05f285a6ecf390a8927e6ad4e67f7a39979b/examples/web/icons/Icon-512.png -------------------------------------------------------------------------------- /examples/web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/flutter_staggered_grid_view/0bfd05f285a6ecf390a8927e6ad4e67f7a39979b/examples/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /examples/web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/flutter_staggered_grid_view/0bfd05f285a6ecf390a8927e6ad4e67f7a39979b/examples/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /examples/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | examples 30 | 31 | 32 | 33 | 36 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /examples/web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "examples", 3 | "short_name": "examples", 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 | -------------------------------------------------------------------------------- /lib/flutter_staggered_grid_view.dart: -------------------------------------------------------------------------------- 1 | library flutter_staggered_grid_view; 2 | 3 | export 'src/layouts/quilted.dart'; 4 | export 'src/layouts/staired.dart'; 5 | export 'src/layouts/woven.dart'; 6 | export 'src/rendering/sliver_masonry_grid.dart'; 7 | export 'src/rendering/sliver_simple_grid_delegate.dart'; 8 | export 'src/widgets/aligned_grid_view.dart'; 9 | export 'src/widgets/masonry_grid_view.dart'; 10 | export 'src/widgets/sliver_aligned_grid.dart'; 11 | export 'src/widgets/sliver_masonry_grid.dart'; 12 | export 'src/widgets/staggered_grid.dart'; 13 | export 'src/widgets/staggered_grid_tile.dart'; 14 | -------------------------------------------------------------------------------- /lib/src/foundation/constants.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/rendering.dart'; 2 | 3 | /// An empty [SliverGridGeometry]. 4 | const kZeroGeometry = SliverGridGeometry( 5 | scrollOffset: 0, 6 | crossAxisOffset: 0, 7 | mainAxisExtent: 0, 8 | crossAxisExtent: 0, 9 | ); 10 | -------------------------------------------------------------------------------- /lib/src/foundation/extensions.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: public_member_api_docs 2 | 3 | extension ListNumExtensions on List { 4 | int findSmallestIndexWithMinimumValue() { 5 | int index = 0; 6 | for (int i = 1; i < length; i++) { 7 | if (this[i] < this[index]) { 8 | index = i; 9 | } 10 | } 11 | return index; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/src/layouts/sliver_patterned_grid_delegate.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math' as math; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter/rendering.dart'; 5 | 6 | /// Represents the geometries of each tile of a pattern. 7 | @immutable 8 | class SliverPatternGridGeometries { 9 | /// Creates a [SliverPatternGridGeometries]. 10 | const SliverPatternGridGeometries({ 11 | required this.tiles, 12 | required this.bounds, 13 | }) : assert(tiles.length == bounds.length); 14 | 15 | /// The visible geometries of each tile. 16 | final List tiles; 17 | 18 | /// The available space of each tile. 19 | final List bounds; 20 | } 21 | 22 | /// Controls the layout of a grid which layout a pattern over and over. 23 | abstract class SliverPatternGridDelegate extends SliverGridDelegate { 24 | const SliverPatternGridDelegate._({ 25 | required this.pattern, 26 | this.mainAxisSpacing = 0, 27 | this.crossAxisSpacing = 0, 28 | this.crossAxisCount, 29 | this.maxCrossAxisExtent, 30 | }) : assert(mainAxisSpacing >= 0), 31 | assert(crossAxisSpacing >= 0), 32 | assert(crossAxisCount == null || crossAxisCount > 0), 33 | assert(maxCrossAxisExtent == null || maxCrossAxisExtent > 0), 34 | assert(crossAxisCount != null || maxCrossAxisExtent != null), 35 | tileCount = pattern.length; 36 | 37 | /// Creates a [SliverPatternGridDelegate] with a [crossAxisCount]. 38 | const SliverPatternGridDelegate.count({ 39 | required List pattern, 40 | required int crossAxisCount, 41 | double mainAxisSpacing = 0, 42 | double crossAxisSpacing = 0, 43 | }) : this._( 44 | pattern: pattern, 45 | mainAxisSpacing: mainAxisSpacing, 46 | crossAxisSpacing: crossAxisSpacing, 47 | crossAxisCount: crossAxisCount, 48 | ); 49 | 50 | /// Creates a [SliverPatternGridDelegate] with a [maxCrossAxisExtent]. 51 | const SliverPatternGridDelegate.extent({ 52 | required List pattern, 53 | required double maxCrossAxisExtent, 54 | double mainAxisSpacing = 0, 55 | double crossAxisSpacing = 0, 56 | }) : this._( 57 | pattern: pattern, 58 | mainAxisSpacing: mainAxisSpacing, 59 | crossAxisSpacing: crossAxisSpacing, 60 | maxCrossAxisExtent: maxCrossAxisExtent, 61 | ); 62 | 63 | /// {@macro fsgv.global.mainAxisSpacing} 64 | final double mainAxisSpacing; 65 | 66 | /// {@macro fsgv.global.crossAxisSpacing} 67 | final double crossAxisSpacing; 68 | 69 | /// The tiles representing the pattern to be repeated. 70 | final List pattern; 71 | 72 | /// The number of tiles in the pattern. 73 | final int tileCount; 74 | 75 | /// {@macro fsgv.global.crossAxisCount} 76 | final int? crossAxisCount; 77 | 78 | /// {@macro fsgv.global.maxCrossAxisExtent} 79 | final double? maxCrossAxisExtent; 80 | 81 | /// Returns the geometries of each tiles in the pattern. 82 | @protected 83 | SliverPatternGridGeometries getGeometries( 84 | SliverConstraints constraints, 85 | int crossAxisCount, 86 | ); 87 | 88 | @override 89 | _SliverPatternGridLayout getLayout(SliverConstraints constraints) { 90 | final crossAxisCount = this.crossAxisCount ?? 91 | (constraints.crossAxisExtent / (maxCrossAxisExtent! + crossAxisSpacing)) 92 | .ceil(); 93 | final geometries = getGeometries(constraints, crossAxisCount); 94 | return _SliverPatternGridLayout( 95 | mainAxisSpacing: mainAxisSpacing, 96 | crossAxisExtent: constraints.crossAxisExtent, 97 | reverseCrossAxis: axisDirectionIsReversed(constraints.crossAxisDirection), 98 | tiles: geometries.tiles, 99 | bounds: geometries.bounds, 100 | ); 101 | } 102 | 103 | @override 104 | bool shouldRelayout(SliverPatternGridDelegate oldDelegate) { 105 | return oldDelegate.pattern != pattern || 106 | oldDelegate.mainAxisSpacing != mainAxisSpacing || 107 | oldDelegate.crossAxisSpacing != crossAxisSpacing; 108 | } 109 | } 110 | 111 | class _SliverPatternGridLayout extends SliverGridLayout { 112 | _SliverPatternGridLayout({ 113 | required this.mainAxisSpacing, 114 | required this.tiles, 115 | required this.bounds, 116 | required this.crossAxisExtent, 117 | this.reverseCrossAxis = false, 118 | }) : tileCount = tiles.length, 119 | patternMainAxisExtent = 120 | bounds.last.trailingScrollOffset + mainAxisSpacing; 121 | 122 | final double mainAxisSpacing; 123 | final double crossAxisExtent; 124 | final bool reverseCrossAxis; 125 | final List tiles; 126 | final List bounds; 127 | final int tileCount; 128 | final double patternMainAxisExtent; 129 | 130 | @override 131 | double computeMaxScrollOffset(int childCount) { 132 | if (childCount == 0) { 133 | return 0; 134 | } 135 | 136 | final lastFilledPatternTrailingScrollOffset = 137 | (childCount ~/ tileCount) * patternMainAxisExtent; 138 | 139 | if (childCount % tileCount == 0) { 140 | return lastFilledPatternTrailingScrollOffset - mainAxisSpacing; 141 | } 142 | 143 | // We have to get the max scroll offset for the track where the tile with 144 | // index, childCount - 1, is. 145 | // TODO(romain): Can be optimized. 146 | final maxIndex = (childCount - 1) % tileCount; 147 | final maxRemainingScrollOffset = tiles 148 | .take(maxIndex + 1) 149 | .map((x) => x.trailingScrollOffset) 150 | .reduce(math.max); 151 | return lastFilledPatternTrailingScrollOffset + maxRemainingScrollOffset; 152 | } 153 | 154 | @override 155 | SliverGridGeometry getGeometryForChildIndex(int index) { 156 | final startMainAxisOffset = (index ~/ tileCount) * patternMainAxisExtent; 157 | final rect = tileRectAt(index); 158 | final realRect = rect.translate(startMainAxisOffset); 159 | 160 | if (reverseCrossAxis) { 161 | return SliverGridGeometry( 162 | scrollOffset: realRect.scrollOffset, 163 | crossAxisOffset: crossAxisExtent - 164 | realRect.crossAxisOffset - 165 | realRect.crossAxisExtent, 166 | mainAxisExtent: realRect.mainAxisExtent, 167 | crossAxisExtent: realRect.crossAxisExtent, 168 | ); 169 | } 170 | 171 | return realRect; 172 | } 173 | 174 | @override 175 | int getMinChildIndexForScrollOffset(double scrollOffset) { 176 | final patternCount = (scrollOffset ~/ patternMainAxisExtent); 177 | final mainAxisOffset = scrollOffset - patternCount * patternMainAxisExtent; 178 | for (int i = 0; i < tileCount; i++) { 179 | if (_isRectVisibleAtMainAxisOffset(bounds[i], mainAxisOffset)) { 180 | return i + patternCount * tileCount; 181 | } 182 | } 183 | 184 | return 0; 185 | } 186 | 187 | @override 188 | int getMaxChildIndexForScrollOffset(double scrollOffset) { 189 | final patternCount = (scrollOffset ~/ patternMainAxisExtent); 190 | 191 | final mainAxisOffset = scrollOffset - patternCount * patternMainAxisExtent; 192 | for (int i = tileCount - 1; i >= 0; i--) { 193 | if (_isRectVisibleAtMainAxisOffset(bounds[i], mainAxisOffset)) { 194 | return i + patternCount * tileCount; 195 | } 196 | } 197 | 198 | return 0; 199 | } 200 | 201 | bool _isRectVisibleAtMainAxisOffset( 202 | SliverGridGeometry rect, 203 | double mainAxisOffset, 204 | ) { 205 | return rect.scrollOffset <= mainAxisOffset && 206 | rect.trailingScrollOffset >= (mainAxisOffset - mainAxisSpacing); 207 | } 208 | 209 | SliverGridGeometry tileRectAt(int index) { 210 | return tiles[index % tileCount]; 211 | } 212 | } 213 | 214 | extension on SliverGridGeometry { 215 | SliverGridGeometry translate(double translation) { 216 | return SliverGridGeometry( 217 | scrollOffset: scrollOffset + translation, 218 | crossAxisOffset: crossAxisOffset, 219 | mainAxisExtent: mainAxisExtent, 220 | crossAxisExtent: crossAxisExtent, 221 | ); 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /lib/src/layouts/staired.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/rendering.dart'; 3 | import 'package:flutter_staggered_grid_view/src/foundation/constants.dart'; 4 | import 'package:flutter_staggered_grid_view/src/layouts/sliver_patterned_grid_delegate.dart'; 5 | 6 | /// A tile of a staired pattern. 7 | @immutable 8 | class StairedGridTile { 9 | /// Creates a [StairedGridTile]. 10 | const StairedGridTile( 11 | this.crossAxisRatio, 12 | this.aspectRatio, 13 | ) : assert(crossAxisRatio > 0 && crossAxisRatio <= 1), 14 | assert(aspectRatio > 0); 15 | 16 | /// The amount of extent this tile is taking in the cross axis, according to 17 | /// the usable cross axis extent. 18 | /// For exemple if [crossAxisRatio] is 0.5, and the usable cross axis extent 19 | /// is 100. The the tile will have a cross axis extent of 50. 20 | /// 21 | /// Must be between 0 and 1. 22 | final double crossAxisRatio; 23 | 24 | /// The ratio of the cross-axis to the main-axis extent of the tile. 25 | /// 26 | /// Must be greater than 0. 27 | final double aspectRatio; 28 | 29 | @override 30 | String toString() { 31 | return 'StairedGridTile($crossAxisRatio, $aspectRatio)'; 32 | } 33 | } 34 | 35 | /// Controls the layout of tiles in a staired grid. 36 | class SliverStairedGridDelegate 37 | extends SliverPatternGridDelegate { 38 | /// Creates a [SliverStairedGridDelegate]. 39 | const SliverStairedGridDelegate({ 40 | required List pattern, 41 | double mainAxisSpacing = 0, 42 | double crossAxisSpacing = 0, 43 | this.tileBottomSpace = 0, 44 | this.startCrossAxisDirectionReversed = false, 45 | }) : assert(tileBottomSpace >= 0), 46 | super.count( 47 | pattern: pattern, 48 | crossAxisCount: 1, 49 | mainAxisSpacing: mainAxisSpacing, 50 | crossAxisSpacing: crossAxisSpacing, 51 | ); 52 | 53 | /// {@template fsgv.global.tileBottomSpace} 54 | /// The number of logical pixels of the space below each tile. 55 | /// {@endtemplate} 56 | final double tileBottomSpace; 57 | 58 | /// Indicates whether we should start to place the tile in the reverse cross 59 | /// axis direction. 60 | final bool startCrossAxisDirectionReversed; 61 | 62 | @override 63 | SliverPatternGridGeometries getGeometries( 64 | SliverConstraints constraints, 65 | int crossAxisCount, 66 | ) { 67 | final maxCrossAxisExtent = constraints.crossAxisExtent; 68 | final List geometries = List.filled( 69 | pattern.length, 70 | kZeroGeometry, 71 | ); 72 | int i = 0; 73 | double mainAxisOffset = 0; 74 | double crossAxisOffset = 75 | startCrossAxisDirectionReversed ? maxCrossAxisExtent : 0; 76 | bool reversed = startCrossAxisDirectionReversed; 77 | while (i < tileCount) { 78 | int startIndex = i; 79 | double crossAxisRatioSum = 0; 80 | while (crossAxisRatioSum < 1 && i < tileCount) { 81 | crossAxisRatioSum += pattern[i].crossAxisRatio; 82 | i++; 83 | } 84 | if (crossAxisRatioSum > 1) { 85 | // The last ratio is too high. We remove it from this track. 86 | i--; 87 | } 88 | final tileBottomSpaceSum = tileBottomSpace * (i - startIndex); 89 | final isHorizontal = constraints.axis == Axis.horizontal; 90 | final usableCrossAxisExtent = ((startIndex == 0 91 | ? maxCrossAxisExtent 92 | : maxCrossAxisExtent - crossAxisSpacing) - 93 | (i - startIndex - 1) * crossAxisSpacing - 94 | (i == tileCount ? crossAxisSpacing : 0) - 95 | (isHorizontal ? tileBottomSpaceSum : 0)) 96 | .clamp(0, maxCrossAxisExtent); 97 | 98 | double targetMainAxisOffset = 0; 99 | for (int j = startIndex; j < i; j++) { 100 | final tile = pattern[j]; 101 | final crossAxisExtent = usableCrossAxisExtent * tile.crossAxisRatio + 102 | (isHorizontal ? tileBottomSpace : 0); 103 | final mainAxisExtent = crossAxisExtent / tile.aspectRatio + 104 | (isHorizontal ? 0 : tileBottomSpace); 105 | crossAxisOffset = 106 | reversed ? crossAxisOffset - crossAxisExtent : crossAxisOffset; 107 | final tileRect = SliverGridGeometry( 108 | scrollOffset: mainAxisOffset, 109 | crossAxisOffset: crossAxisOffset, 110 | mainAxisExtent: mainAxisExtent, 111 | crossAxisExtent: crossAxisExtent, 112 | ); 113 | final endMainAxisOffset = mainAxisOffset + mainAxisExtent; 114 | 115 | crossAxisOffset = reversed 116 | ? crossAxisOffset - crossAxisSpacing 117 | : crossAxisOffset + crossAxisExtent + crossAxisSpacing; 118 | mainAxisOffset += mainAxisSpacing; 119 | geometries[j] = tileRect; 120 | if (endMainAxisOffset > targetMainAxisOffset) { 121 | targetMainAxisOffset = endMainAxisOffset; 122 | } 123 | } 124 | 125 | mainAxisOffset = targetMainAxisOffset + mainAxisSpacing; 126 | reversed = !reversed; 127 | crossAxisOffset = 128 | reversed ? maxCrossAxisExtent - crossAxisSpacing : crossAxisSpacing; 129 | } 130 | 131 | return SliverPatternGridGeometries(tiles: geometries, bounds: geometries); 132 | } 133 | 134 | @override 135 | bool shouldRelayout(SliverStairedGridDelegate oldDelegate) { 136 | return super.shouldRelayout(oldDelegate) || 137 | oldDelegate.tileBottomSpace != tileBottomSpace || 138 | oldDelegate.startCrossAxisDirectionReversed != 139 | startCrossAxisDirectionReversed; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /lib/src/layouts/woven.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math' as math; 2 | 3 | import 'package:flutter/rendering.dart'; 4 | import 'package:flutter/widgets.dart'; 5 | import 'package:flutter_staggered_grid_view/src/foundation/constants.dart'; 6 | import 'package:flutter_staggered_grid_view/src/layouts/sliver_patterned_grid_delegate.dart'; 7 | 8 | /// A tile of a woven pattern. 9 | @immutable 10 | class WovenGridTile { 11 | /// Creates a [WovenGridTile]. 12 | const WovenGridTile( 13 | this.aspectRatio, { 14 | this.crossAxisRatio = 1, 15 | this.alignment = AlignmentDirectional.center, 16 | }) : assert(aspectRatio > 0), 17 | assert(crossAxisRatio > 0 && crossAxisRatio <= 1); 18 | 19 | /// The ratio of the cross-axis to the main-axis extent of the tile. 20 | /// 21 | /// Must be greater than 0. 22 | final double aspectRatio; 23 | 24 | /// The ratio taken by this tile in the cross-axis. 25 | /// 26 | /// Must be between 0 (exclusive) and 1 (inclusive). 27 | final double crossAxisRatio; 28 | 29 | /// The alignment of the tile within the available space. 30 | final AlignmentDirectional alignment; 31 | 32 | @override 33 | String toString() { 34 | return 'WovenGridTile($aspectRatio${crossAxisRatio > 1 ? ', $crossAxisRatio' : ''}${alignment != AlignmentDirectional.center ? ', $alignment' : ''})'; 35 | } 36 | } 37 | 38 | /// Controls the layout of tiles in a woven grid. 39 | class SliverWovenGridDelegate extends SliverPatternGridDelegate { 40 | /// Creates a [SliverWovenGridDelegate]. 41 | const SliverWovenGridDelegate.count({ 42 | required List pattern, 43 | required int crossAxisCount, 44 | double mainAxisSpacing = 0, 45 | double crossAxisSpacing = 0, 46 | this.tileBottomSpace = 0, 47 | }) : assert(pattern.length <= crossAxisCount), 48 | super.count( 49 | pattern: pattern, 50 | crossAxisCount: crossAxisCount, 51 | mainAxisSpacing: mainAxisSpacing, 52 | crossAxisSpacing: crossAxisSpacing, 53 | ); 54 | 55 | /// Creates a [SliverWovenGridDelegate]. 56 | const SliverWovenGridDelegate.extent({ 57 | required List pattern, 58 | required double maxCrossAxisExtent, 59 | double mainAxisSpacing = 0, 60 | double crossAxisSpacing = 0, 61 | this.tileBottomSpace = 0, 62 | }) : super.extent( 63 | pattern: pattern, 64 | maxCrossAxisExtent: maxCrossAxisExtent, 65 | mainAxisSpacing: mainAxisSpacing, 66 | crossAxisSpacing: crossAxisSpacing, 67 | ); 68 | 69 | /// {@macro fsgv.global.tileBottomSpace} 70 | final double tileBottomSpace; 71 | 72 | @override 73 | SliverPatternGridGeometries getGeometries( 74 | SliverConstraints constraints, 75 | int crossAxisCount, 76 | ) { 77 | final isHorizontal = constraints.axis == Axis.horizontal; 78 | final usableCrossAxisExtent = isHorizontal 79 | ? constraints.crossAxisExtent - crossAxisCount * tileBottomSpace 80 | : constraints.crossAxisExtent; 81 | final crossAxisExtent = 82 | (usableCrossAxisExtent + crossAxisSpacing) / crossAxisCount - 83 | crossAxisSpacing; 84 | final crossAxisStride = crossAxisExtent + crossAxisSpacing; 85 | final patternCount = pattern.length; 86 | // The minimum aspect ratio give us the main axis extent of a track. 87 | final maxMainAxisExtentRatio = 88 | pattern.map((t) => t.crossAxisRatio / t.aspectRatio).reduce(math.max); 89 | final mainAxisExtent = crossAxisExtent * maxMainAxisExtentRatio + 90 | (isHorizontal ? 0 : tileBottomSpace); 91 | 92 | // We always provide 2 tracks where the layout follow this pattern: 93 | // A B A || A B A B || A B C || A B C A 94 | // B A B || B A B A || C B A || B A C B 95 | 96 | final count = crossAxisCount * 2; 97 | final tiles = List.filled(count, kZeroGeometry); 98 | final bounds = List.filled(count, kZeroGeometry); 99 | for (int i = 0; i < count; i++) { 100 | final startScrollOffset = 101 | i < crossAxisCount ? 0.0 : mainAxisExtent + mainAxisSpacing; 102 | final tilePatternIndex = i < crossAxisCount 103 | ? i % patternCount 104 | : (count - 1 - (i % crossAxisCount)) % patternCount; 105 | final tilePattern = pattern[tilePatternIndex]; 106 | final tileCrossAxisExtent = crossAxisExtent * tilePattern.crossAxisRatio + 107 | (isHorizontal ? tileBottomSpace : 0); 108 | final tileMainAxisExtent = tileCrossAxisExtent / tilePattern.aspectRatio + 109 | (isHorizontal ? 0 : tileBottomSpace); 110 | final effectiveTextDirection = 111 | i < crossAxisCount ? TextDirection.ltr : TextDirection.rtl; 112 | final effectiveAlignment = 113 | tilePattern.alignment.resolve(effectiveTextDirection); 114 | final rect = effectiveAlignment.inscribe( 115 | Size(tileCrossAxisExtent, tileMainAxisExtent), 116 | Rect.fromLTWH(0, 0, crossAxisExtent, mainAxisExtent), 117 | ); 118 | final startCrossAxisOffset = (i % crossAxisCount) * crossAxisStride; 119 | tiles[i] = SliverGridGeometry( 120 | scrollOffset: startScrollOffset + rect.top, 121 | crossAxisOffset: startCrossAxisOffset + rect.left, 122 | mainAxisExtent: tileMainAxisExtent, 123 | crossAxisExtent: tileCrossAxisExtent, 124 | ); 125 | bounds[i] = SliverGridGeometry( 126 | scrollOffset: startScrollOffset, 127 | crossAxisOffset: startCrossAxisOffset, 128 | mainAxisExtent: mainAxisExtent, 129 | crossAxisExtent: crossAxisExtent, 130 | ); 131 | } 132 | 133 | return SliverPatternGridGeometries(tiles: tiles, bounds: bounds); 134 | } 135 | 136 | @override 137 | bool shouldRelayout(SliverWovenGridDelegate oldDelegate) { 138 | return super.shouldRelayout(oldDelegate) || 139 | oldDelegate.tileBottomSpace != tileBottomSpace || 140 | oldDelegate.crossAxisCount != crossAxisCount; 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /lib/src/rendering/sliver_simple_grid_delegate.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: comment_references 2 | 3 | import 'package:flutter/rendering.dart'; 4 | 5 | /// Controls the layout of tiles in a some slivers. 6 | /// 7 | /// See also: 8 | /// 9 | /// * [SliverSimpleGridDelegateWithFixedCrossAxisCount], which creates a 10 | /// layout with a fixed number of tiles in the cross axis. 11 | /// * [SliverSimpleGridDelegateWithMaxCrossAxisExtent], which creates a layout 12 | /// with tiles that have a maximum cross-axis extent. 13 | /// * [RenderSliverMasonryGrid], which uses this delegate to control the layout 14 | /// of its tiles. 15 | abstract class SliverSimpleGridDelegate { 16 | /// Abstract const constructor. This constructor enables subclasses to provide 17 | /// const constructors so that they can be used in const expressions. 18 | const SliverSimpleGridDelegate(); 19 | 20 | /// Returns the number of children than can be layout in the cross axis. 21 | int getCrossAxisCount(SliverConstraints constraints, double crossAxisSpacing); 22 | 23 | /// Override this method to return true when the children need to be 24 | /// laid out. 25 | /// 26 | /// This should compare the fields of the current delegate and the given 27 | /// `oldDelegate` and return true if the fields are such that the layout would 28 | /// be different. 29 | bool shouldRelayout(covariant SliverSimpleGridDelegate oldDelegate); 30 | } 31 | 32 | /// Creates grid layouts with a fixed number of tiles in the cross axis. 33 | /// 34 | /// For example, if the grid is vertical, this delegate will create a layout 35 | /// with a fixed number of columns. If the grid is horizontal, this delegate 36 | /// will create a layout with a fixed number of rows. 37 | /// 38 | /// This delegate creates grids with equally sized and spaced tiles. 39 | class SliverSimpleGridDelegateWithFixedCrossAxisCount 40 | extends SliverSimpleGridDelegate { 41 | /// Creates a delegate that makes grid layouts with a fixed number of tiles in 42 | /// the cross axis. 43 | /// 44 | /// The [crossAxisCount] argument must be greater than zero. 45 | const SliverSimpleGridDelegateWithFixedCrossAxisCount({ 46 | required this.crossAxisCount, 47 | }) : assert(crossAxisCount > 0); 48 | 49 | /// {@template fsgv.global.crossAxisCount} 50 | /// The number of children in the cross axis. 51 | /// {@endtemplate} 52 | final int crossAxisCount; 53 | 54 | @override 55 | int getCrossAxisCount( 56 | SliverConstraints constraints, 57 | double crossAxisSpacing, 58 | ) { 59 | return crossAxisCount; 60 | } 61 | 62 | @override 63 | bool shouldRelayout( 64 | SliverSimpleGridDelegateWithFixedCrossAxisCount oldDelegate, 65 | ) { 66 | return oldDelegate.crossAxisCount != crossAxisCount; 67 | } 68 | } 69 | 70 | /// Creates grid layouts with tiles that each have a maximum cross-axis extent. 71 | /// 72 | /// This delegate will select a cross-axis extent for the tiles that is as 73 | /// large as possible subject to the following conditions: 74 | /// 75 | /// - The extent evenly divides the cross-axis extent of the grid. 76 | /// - The extent is at most [maxCrossAxisExtent]. 77 | /// 78 | /// For example, if the grid is vertical, the grid is 500.0 pixels wide, and 79 | /// [maxCrossAxisExtent] is 150.0, this delegate will create a grid with 4 80 | /// columns that are 125.0 pixels wide. 81 | /// 82 | /// This delegate creates grids with equally sized and spaced tiles. 83 | /// 84 | /// See also: 85 | /// 86 | /// * [SliverSimpleGridDelegateWithFixedCrossAxisCount], which creates a 87 | /// layout with a fixed number of tiles in the cross axis. 88 | /// * [SliverSimpleGridDelegate], which creates arbitrary layouts. 89 | /// * [RenderSliverMasonryGrid], which can use this delegate to control the 90 | /// layout of its tiles. 91 | class SliverSimpleGridDelegateWithMaxCrossAxisExtent 92 | extends SliverSimpleGridDelegate { 93 | /// Creates a delegate that makes grid layouts with tiles that have a maximum 94 | /// cross-axis extent. 95 | /// 96 | /// The [maxCrossAxisExtent] argument must be greater than zero. 97 | const SliverSimpleGridDelegateWithMaxCrossAxisExtent({ 98 | required this.maxCrossAxisExtent, 99 | }) : assert(maxCrossAxisExtent > 0); 100 | 101 | /// {@template fsgv.global.maxCrossAxisExtent} 102 | /// The maximum extent of tiles in the cross axis. 103 | /// 104 | /// This delegate will select a cross-axis extent for the tiles that is as 105 | /// large as possible subject to the following conditions: 106 | /// 107 | /// - The extent evenly divides the cross-axis extent of the grid. 108 | /// - The extent is at most [maxCrossAxisExtent]. 109 | /// 110 | /// For example, if the grid is vertical, the grid is 500.0 pixels wide, and 111 | /// [maxCrossAxisExtent] is 150.0, this delegate will create a grid with 4 112 | /// columns that are 125.0 pixels wide. 113 | /// {@endtemplate} 114 | final double maxCrossAxisExtent; 115 | 116 | @override 117 | int getCrossAxisCount( 118 | SliverConstraints constraints, 119 | double crossAxisSpacing, 120 | ) { 121 | return (constraints.crossAxisExtent / 122 | (maxCrossAxisExtent + crossAxisSpacing)) 123 | .ceil(); 124 | } 125 | 126 | @override 127 | bool shouldRelayout( 128 | SliverSimpleGridDelegateWithMaxCrossAxisExtent oldDelegate, 129 | ) { 130 | return oldDelegate.maxCrossAxisExtent != maxCrossAxisExtent; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /lib/src/rendering/uniform_track.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: public_member_api_docs 2 | 3 | import 'dart:math' as math; 4 | 5 | import 'package:flutter/rendering.dart'; 6 | 7 | class UniformTrackParentData extends ContainerBoxParentData {} 8 | 9 | class RenderUniformTrack extends RenderBox 10 | with 11 | ContainerRenderObjectMixin, 12 | RenderBoxContainerDefaultsMixin { 13 | RenderUniformTrack({ 14 | List? children, 15 | double spacing = 0, 16 | required int division, 17 | required AxisDirection direction, 18 | }) : assert(spacing >= 0), 19 | assert(division > 0), 20 | _spacing = spacing, 21 | _direction = direction, 22 | _isHorizontal = axisDirectionToAxis(direction) == Axis.horizontal, 23 | _isDirectionReversed = axisDirectionIsReversed(direction), 24 | _division = division { 25 | addAll(children); 26 | } 27 | 28 | double get spacing => _spacing; 29 | double _spacing; 30 | set spacing(double value) { 31 | assert(value >= 0); 32 | if (_spacing == value) { 33 | return; 34 | } 35 | _spacing = value; 36 | markNeedsLayout(); 37 | } 38 | 39 | AxisDirection get direction => _direction; 40 | AxisDirection _direction; 41 | set direction(AxisDirection value) { 42 | if (_direction == value) { 43 | return; 44 | } 45 | _direction = value; 46 | _isHorizontal = axisDirectionToAxis(value) == Axis.horizontal; 47 | _isDirectionReversed = axisDirectionIsReversed(value); 48 | markNeedsLayout(); 49 | } 50 | 51 | bool _isHorizontal; 52 | bool _isDirectionReversed; 53 | 54 | int get division => _division; 55 | int _division; 56 | set division(int value) { 57 | assert(value > 0); 58 | if (_division == value) { 59 | return; 60 | } 61 | _division = value; 62 | markNeedsLayout(); 63 | } 64 | 65 | @override 66 | void setupParentData(RenderBox child) { 67 | if (child.parentData is! UniformTrackParentData) 68 | child.parentData = UniformTrackParentData(); 69 | } 70 | 71 | UniformTrackParentData _getParentData(RenderBox child) { 72 | return child.parentData as UniformTrackParentData; 73 | } 74 | 75 | @override 76 | bool hitTestChildren(BoxHitTestResult result, {required Offset position}) { 77 | return defaultHitTestChildren(result, position: position); 78 | } 79 | 80 | @override 81 | Size computeDryLayout(BoxConstraints constraints) { 82 | return _computeSize(constraints, ChildLayoutHelper.dryLayoutChild); 83 | } 84 | 85 | Size _computeSize(BoxConstraints constraints, ChildLayouter layoutChild) { 86 | final mainAxisExtent = 87 | _isHorizontal ? constraints.maxWidth : constraints.maxHeight; 88 | final childMainAxisExtent = 89 | ((mainAxisExtent + spacing) / division) - spacing; 90 | final childConstraints = _isHorizontal 91 | ? BoxConstraints.tightFor(width: childMainAxisExtent) 92 | : BoxConstraints.tightFor(height: childMainAxisExtent); 93 | RenderBox? child = firstChild; 94 | 95 | double getChildCrossAxisExtent(Size size) { 96 | return _isHorizontal ? size.height : size.width; 97 | } 98 | 99 | double maxChildCrossAxisExtent = 0; 100 | 101 | // First pass to get the maximum child cross axis extent. 102 | while (child != null) { 103 | final size = layoutChild(child, childConstraints); 104 | maxChildCrossAxisExtent = math.max( 105 | maxChildCrossAxisExtent, 106 | getChildCrossAxisExtent(size), 107 | ); 108 | child = childAfter(child); 109 | } 110 | 111 | return size = constraints.constrain( 112 | _isHorizontal 113 | ? Size(mainAxisExtent, maxChildCrossAxisExtent) 114 | : Size(maxChildCrossAxisExtent, mainAxisExtent), 115 | ); 116 | } 117 | 118 | @override 119 | void performLayout() { 120 | size = _computeSize(constraints, ChildLayoutHelper.layoutChild); 121 | final mainAxisExtent = 122 | _isHorizontal ? constraints.maxWidth : constraints.maxHeight; 123 | final childMainAxisExtent = 124 | ((mainAxisExtent + spacing) / division) - spacing; 125 | final maxChildCrossAxisExtent = _isHorizontal ? size.height : size.width; 126 | 127 | double getChildCrossAxisExtent(RenderBox child) { 128 | return _isHorizontal ? child.size.height : child.size.width; 129 | } 130 | 131 | // Second pass to position the children and relayout those who have not the 132 | // maximum cross axis extent. 133 | final secondPassChildConstraints = _isHorizontal 134 | ? BoxConstraints.tightFor( 135 | width: childMainAxisExtent, 136 | height: maxChildCrossAxisExtent, 137 | ) 138 | : BoxConstraints.tightFor( 139 | height: childMainAxisExtent, 140 | width: maxChildCrossAxisExtent, 141 | ); 142 | 143 | RenderBox? child = firstChild; 144 | final stride = childMainAxisExtent + spacing; 145 | int index = 0; 146 | 147 | double getMainAxisPosition(int index) { 148 | final effectiveIndex = 149 | _isDirectionReversed ? division - index - 1 : index; 150 | return effectiveIndex * stride; 151 | } 152 | 153 | while (child != null) { 154 | if (getChildCrossAxisExtent(child) != maxChildCrossAxisExtent) { 155 | child.layout(secondPassChildConstraints, parentUsesSize: true); 156 | } 157 | final childParentData = _getParentData(child); 158 | final childMainAxisPosition = getMainAxisPosition(index); 159 | childParentData.offset = _isHorizontal 160 | ? Offset(childMainAxisPosition, 0) 161 | : Offset(0, childMainAxisPosition); 162 | index++; 163 | child = childParentData.nextSibling; 164 | } 165 | } 166 | 167 | @override 168 | void paint(PaintingContext context, Offset offset) { 169 | defaultPaint(context, offset); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /lib/src/widgets/sliver_aligned_grid.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:flutter_staggered_grid_view/src/rendering/sliver_simple_grid_delegate.dart'; 3 | import 'package:flutter_staggered_grid_view/src/widgets/uniform_track.dart'; 4 | 5 | /// A sliver that places multiple box children in a two dimensional arrangement. 6 | /// 7 | /// [SliverAlignedGrid] places its children in a grid where each track has the 8 | /// main axis extent of the widest child. Each child is stretched to match the 9 | /// track main axis extent. 10 | class SliverAlignedGrid extends StatelessWidget { 11 | /// Creates a custom [SliverAlignedGrid]. 12 | const SliverAlignedGrid({ 13 | Key? key, 14 | required this.itemBuilder, 15 | this.itemCount, 16 | required this.gridDelegate, 17 | this.mainAxisSpacing = 0, 18 | this.crossAxisSpacing = 0, 19 | this.addAutomaticKeepAlives = true, 20 | this.addRepaintBoundaries = true, 21 | }) : super(key: key); 22 | 23 | /// Creates a sliver that places multiple box children in an aligned 24 | /// arrangement with a fixed number of tiles in the cross axis. 25 | /// 26 | /// The [crossAxisCount], [mainAxisSpacing] and [crossAxisSpacing] arguments 27 | /// must be greater than zero. 28 | SliverAlignedGrid.count({ 29 | Key? key, 30 | required NullableIndexedWidgetBuilder itemBuilder, 31 | int? itemCount, 32 | required int crossAxisCount, 33 | double mainAxisSpacing = 0, 34 | double crossAxisSpacing = 0, 35 | bool addAutomaticKeepAlives = true, 36 | bool addRepaintBoundaries = true, 37 | }) : this( 38 | key: key, 39 | itemBuilder: itemBuilder, 40 | itemCount: itemCount, 41 | gridDelegate: SliverSimpleGridDelegateWithFixedCrossAxisCount( 42 | crossAxisCount: crossAxisCount, 43 | ), 44 | mainAxisSpacing: mainAxisSpacing, 45 | crossAxisSpacing: crossAxisSpacing, 46 | addAutomaticKeepAlives: addAutomaticKeepAlives, 47 | addRepaintBoundaries: addRepaintBoundaries, 48 | ); 49 | 50 | /// Creates a sliver that places multiple box children in an aligned 51 | /// arrangement with tiles that each have a maximum cross-axis extent. 52 | /// 53 | /// The [maxCrossAxisExtent], [mainAxisSpacing] and [crossAxisSpacing] 54 | /// arguments must be greater than zero. 55 | SliverAlignedGrid.extent({ 56 | Key? key, 57 | required NullableIndexedWidgetBuilder itemBuilder, 58 | int? itemCount, 59 | required double maxCrossAxisExtent, 60 | double mainAxisSpacing = 0, 61 | double crossAxisSpacing = 0, 62 | bool addAutomaticKeepAlives = true, 63 | bool addRepaintBoundaries = true, 64 | }) : this( 65 | key: key, 66 | itemBuilder: itemBuilder, 67 | itemCount: itemCount, 68 | gridDelegate: SliverSimpleGridDelegateWithMaxCrossAxisExtent( 69 | maxCrossAxisExtent: maxCrossAxisExtent, 70 | ), 71 | mainAxisSpacing: mainAxisSpacing, 72 | crossAxisSpacing: crossAxisSpacing, 73 | addAutomaticKeepAlives: addAutomaticKeepAlives, 74 | addRepaintBoundaries: addRepaintBoundaries, 75 | ); 76 | 77 | /// {@macro fsgv.global.mainAxisSpacing} 78 | final double mainAxisSpacing; 79 | 80 | /// {@macro fsgv.global.crossAxisSpacing} 81 | final double crossAxisSpacing; 82 | 83 | /// {@macro fsgv.global.gridDelegate} 84 | final SliverSimpleGridDelegate gridDelegate; 85 | 86 | /// Called to build children for the sliver. 87 | /// 88 | /// Will be called only for indices greater than or equal to zero and less 89 | /// than [itemCount] (if [itemCount] is non-null). 90 | /// 91 | /// Should return null if asked to build a widget with a greater index than 92 | /// exists. 93 | /// 94 | /// The delegate wraps the children returned by this builder in 95 | /// [RepaintBoundary] widgets. 96 | final NullableIndexedWidgetBuilder itemBuilder; 97 | 98 | /// The total number of children this delegate can provide. 99 | /// 100 | /// If null, the number of children is determined by the least index for which 101 | /// [itemBuilder] returns null. 102 | final int? itemCount; 103 | 104 | /// Whether to wrap each child in an [AutomaticKeepAlive]. 105 | /// 106 | /// Typically, children in lazy list are wrapped in [AutomaticKeepAlive] 107 | /// widgets so that children can use [KeepAliveNotification]s to preserve 108 | /// their state when they would otherwise be garbage collected off-screen. 109 | /// 110 | /// This feature (and [addRepaintBoundaries]) must be disabled if the children 111 | /// are going to manually maintain their [KeepAlive] state. It may also be 112 | /// more efficient to disable this feature if it is known ahead of time that 113 | /// none of the children will ever try to keep themselves alive. 114 | /// 115 | /// Defaults to true. 116 | final bool addAutomaticKeepAlives; 117 | 118 | /// Whether to wrap each child in a [RepaintBoundary]. 119 | /// 120 | /// Typically, children in a scrolling container are wrapped in repaint 121 | /// boundaries so that they do not need to be repainted as the list scrolls. 122 | /// If the children are easy to repaint (e.g., solid color blocks or a short 123 | /// snippet of text), it might be more efficient to not add a repaint boundary 124 | /// and simply repaint the children during scrolling. 125 | /// 126 | /// Defaults to true. 127 | final bool addRepaintBoundaries; 128 | 129 | @override 130 | Widget build(BuildContext context) { 131 | final localItemCount = itemCount; 132 | return SliverLayoutBuilder( 133 | builder: (context, constraints) { 134 | final crossAxisCount = gridDelegate.getCrossAxisCount( 135 | constraints, 136 | crossAxisSpacing, 137 | ); 138 | final listItemCount = localItemCount == null 139 | ? null 140 | : ((localItemCount + crossAxisCount - 1) ~/ crossAxisCount) * 2 - 1; 141 | return SliverList( 142 | delegate: SliverChildBuilderDelegate( 143 | (context, index) { 144 | if (index.isOdd) { 145 | return _Gap(mainAxisExtent: mainAxisSpacing); 146 | } 147 | 148 | final startIndex = (index ~/ 2) * crossAxisCount; 149 | final children = [ 150 | for (int i = 0; i < crossAxisCount; i++) 151 | _buildItem(context, startIndex + i, itemCount), 152 | ].whereType(); 153 | 154 | if (children.isEmpty) { 155 | return null; 156 | } 157 | 158 | return UniformTrack( 159 | direction: constraints.crossAxisDirection, 160 | division: crossAxisCount, 161 | spacing: crossAxisSpacing, 162 | children: [...children], 163 | ); 164 | }, 165 | childCount: listItemCount, 166 | ), 167 | ); 168 | }, 169 | ); 170 | } 171 | 172 | Widget? _buildItem(BuildContext context, int index, int? childCount) { 173 | if (index < 0 || (childCount != null && index >= childCount)) { 174 | return null; 175 | } 176 | 177 | return itemBuilder(context, index); 178 | } 179 | } 180 | 181 | class _Gap extends StatelessWidget { 182 | const _Gap({ 183 | Key? key, 184 | required this.mainAxisExtent, 185 | }) : super(key: key); 186 | 187 | final double mainAxisExtent; 188 | 189 | @override 190 | Widget build(BuildContext context) { 191 | final axis = axisDirectionToAxis(Scrollable.of(context).axisDirection); 192 | return axis == Axis.vertical 193 | ? SizedBox(height: mainAxisExtent) 194 | : SizedBox(width: mainAxisExtent); 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /lib/src/widgets/sliver_masonry_grid.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_staggered_grid_view/src/rendering/sliver_masonry_grid.dart'; 3 | import 'package:flutter_staggered_grid_view/src/rendering/sliver_simple_grid_delegate.dart'; 4 | 5 | /// A sliver that places multiple box children in a two dimensional arrangement. 6 | /// 7 | /// [SliverMasonryGrid] places each child the nearest as possible at the 8 | /// start of the main axis and then at the start of the cross axis. 9 | /// For example, in a vertical list, with left-to-right text direction, a child 10 | /// will be placed as close as possible at the top of the grid, and then as 11 | /// close as possible to the left side of the grid. 12 | /// 13 | /// The [gridDelegate] determines how many children can be placed in the cross 14 | /// axis. 15 | class SliverMasonryGrid extends SliverMultiBoxAdaptorWidget { 16 | /// Creates a sliver that places its children in a Masonry arrangement. 17 | /// 18 | /// The [mainAxisSpacing] and [crossAxisSpacing] arguments must be greater 19 | /// than zero. 20 | const SliverMasonryGrid({ 21 | Key? key, 22 | required SliverChildDelegate delegate, 23 | required this.gridDelegate, 24 | this.mainAxisSpacing = 0, 25 | this.crossAxisSpacing = 0, 26 | }) : assert(mainAxisSpacing >= 0), 27 | assert(crossAxisSpacing >= 0), 28 | super(key: key, delegate: delegate); 29 | 30 | /// Creates a sliver that places multiple box children in a Masonry 31 | /// arrangement with a fixed number of tiles in the cross axis. 32 | /// 33 | /// Uses a [SliverSimpleGridDelegateWithFixedCrossAxisCount] as the 34 | /// [gridDelegate] and a [SliverChildBuilderDelegate] as the [delegate]. 35 | /// 36 | /// The [crossAxisCount], [mainAxisSpacing] and [crossAxisSpacing] arguments 37 | /// must be greater than zero. 38 | SliverMasonryGrid.count({ 39 | Key? key, 40 | required int crossAxisCount, 41 | required IndexedWidgetBuilder itemBuilder, 42 | int? childCount, 43 | double mainAxisSpacing = 0, 44 | double crossAxisSpacing = 0, 45 | }) : this( 46 | key: key, 47 | delegate: SliverChildBuilderDelegate( 48 | itemBuilder, 49 | childCount: childCount, 50 | ), 51 | gridDelegate: SliverSimpleGridDelegateWithFixedCrossAxisCount( 52 | crossAxisCount: crossAxisCount, 53 | ), 54 | mainAxisSpacing: mainAxisSpacing, 55 | crossAxisSpacing: crossAxisSpacing, 56 | ); 57 | 58 | /// Creates a sliver that places multiple box children in a Masonry 59 | /// arrangement with tiles that each have a maximum cross-axis extent. 60 | /// 61 | /// Uses a [SliverSimpleGridDelegateWithMaxCrossAxisExtent] as the 62 | /// [gridDelegate] and a [SliverChildBuilderDelegate] as the [delegate]. 63 | /// 64 | /// The [maxCrossAxisExtent], [mainAxisSpacing] and [crossAxisSpacing] 65 | /// arguments must be greater than zero. 66 | SliverMasonryGrid.extent({ 67 | Key? key, 68 | required double maxCrossAxisExtent, 69 | required IndexedWidgetBuilder itemBuilder, 70 | int? childCount, 71 | double mainAxisSpacing = 0, 72 | double crossAxisSpacing = 0, 73 | }) : this( 74 | key: key, 75 | delegate: SliverChildBuilderDelegate( 76 | itemBuilder, 77 | childCount: childCount, 78 | ), 79 | gridDelegate: SliverSimpleGridDelegateWithMaxCrossAxisExtent( 80 | maxCrossAxisExtent: maxCrossAxisExtent, 81 | ), 82 | mainAxisSpacing: mainAxisSpacing, 83 | crossAxisSpacing: crossAxisSpacing, 84 | ); 85 | 86 | /// {@macro fsgv.global.gridDelegate} 87 | final SliverSimpleGridDelegate gridDelegate; 88 | 89 | /// {@macro fsgv.global.mainAxisSpacing} 90 | final double mainAxisSpacing; 91 | 92 | /// {@macro fsgv.global.crossAxisSpacing} 93 | final double crossAxisSpacing; 94 | 95 | @override 96 | RenderSliverMasonryGrid createRenderObject(BuildContext context) { 97 | final SliverMultiBoxAdaptorElement element = 98 | context as SliverMultiBoxAdaptorElement; 99 | return RenderSliverMasonryGrid( 100 | childManager: element, 101 | gridDelegate: gridDelegate, 102 | mainAxisSpacing: mainAxisSpacing, 103 | crossAxisSpacing: crossAxisSpacing, 104 | ); 105 | } 106 | 107 | @override 108 | void updateRenderObject( 109 | BuildContext context, 110 | RenderSliverMasonryGrid renderObject, 111 | ) { 112 | renderObject 113 | ..gridDelegate = gridDelegate 114 | ..mainAxisSpacing = mainAxisSpacing 115 | ..crossAxisSpacing = crossAxisSpacing; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /lib/src/widgets/staggered_grid.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_staggered_grid_view/src/rendering/staggered_grid.dart'; 3 | import 'package:flutter_staggered_grid_view/src/widgets/staggered_grid_tile.dart'; 4 | 5 | /// A grid which lays out children in a staggered arrangement. 6 | /// Each child can have a different size. 7 | /// Wrap your children with a [StaggeredGridTile] to specify their size if it's 8 | /// different from a 1x1 tile. 9 | class StaggeredGrid extends MultiChildRenderObjectWidget { 10 | /// Creates a [StaggeredGrid] with a custom [delegate]. 11 | StaggeredGrid.custom({ 12 | Key? key, 13 | required this.delegate, 14 | this.mainAxisSpacing = 0, 15 | this.crossAxisSpacing = 0, 16 | this.axisDirection, 17 | List children = const [], 18 | }) : assert(mainAxisSpacing >= 0), 19 | assert(crossAxisSpacing >= 0), 20 | super(key: key, children: children); 21 | 22 | /// Creates a [StaggeredGrid] using a custom 23 | /// [StaggeredGridDelegateWithFixedCrossAxisCount] as [delegate]. 24 | /// 25 | /// The grid will have a fixed number of tiles in the cross axis. 26 | StaggeredGrid.count({ 27 | Key? key, 28 | required int crossAxisCount, 29 | double mainAxisSpacing = 0, 30 | double crossAxisSpacing = 0, 31 | AxisDirection? axisDirection, 32 | List children = const [], 33 | }) : this.custom( 34 | key: key, 35 | delegate: StaggeredGridDelegateWithFixedCrossAxisCount( 36 | crossAxisCount: crossAxisCount, 37 | ), 38 | mainAxisSpacing: mainAxisSpacing, 39 | crossAxisSpacing: crossAxisSpacing, 40 | axisDirection: axisDirection, 41 | children: children, 42 | ); 43 | 44 | /// Creates a [StaggeredGrid] using a custom 45 | /// [StaggeredGridDelegateWithMaxCrossAxisExtent] as [delegate]. 46 | /// 47 | /// The grid will have tiles that each have a maximum cross-axis extent. 48 | StaggeredGrid.extent({ 49 | Key? key, 50 | required double maxCrossAxisExtent, 51 | double mainAxisSpacing = 0, 52 | double crossAxisSpacing = 0, 53 | AxisDirection? axisDirection, 54 | List children = const [], 55 | }) : this.custom( 56 | key: key, 57 | delegate: StaggeredGridDelegateWithMaxCrossAxisExtent( 58 | maxCrossAxisExtent: maxCrossAxisExtent, 59 | ), 60 | mainAxisSpacing: mainAxisSpacing, 61 | crossAxisSpacing: crossAxisSpacing, 62 | axisDirection: axisDirection, 63 | children: children, 64 | ); 65 | 66 | /// The delegate that controls the layout of the children. 67 | final StaggeredGridDelegate delegate; 68 | 69 | /// {@macro fsgv.global.mainAxisSpacing} 70 | final double mainAxisSpacing; 71 | 72 | /// {@macro fsgv.global.crossAxisSpacing} 73 | final double crossAxisSpacing; 74 | 75 | /// {@macro fsgv.global.axisDirection} 76 | final AxisDirection? axisDirection; 77 | 78 | @override 79 | RenderStaggeredGrid createRenderObject(BuildContext context) { 80 | return RenderStaggeredGrid( 81 | delegate: delegate, 82 | mainAxisSpacing: mainAxisSpacing, 83 | crossAxisSpacing: crossAxisSpacing, 84 | axisDirection: axisDirection ?? 85 | Scrollable.maybeOf(context)?.axisDirection ?? 86 | AxisDirection.down, 87 | textDirection: Directionality.of(context), 88 | ); 89 | } 90 | 91 | @override 92 | void updateRenderObject( 93 | BuildContext context, 94 | RenderStaggeredGrid renderObject, 95 | ) { 96 | renderObject 97 | ..delegate = delegate 98 | ..mainAxisSpacing = mainAxisSpacing 99 | ..crossAxisSpacing = crossAxisSpacing 100 | ..axisDirection = axisDirection ?? 101 | Scrollable.maybeOf(context)?.axisDirection ?? 102 | AxisDirection.down 103 | ..textDirection = Directionality.of(context); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /lib/src/widgets/staggered_grid_tile.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_staggered_grid_view/src/rendering/staggered_grid.dart'; 3 | import 'package:flutter_staggered_grid_view/src/widgets/staggered_grid.dart'; 4 | 5 | /// Represents the size of a [StaggeredGrid]'s tile. 6 | class StaggeredGridTile extends ParentDataWidget { 7 | const StaggeredGridTile._({ 8 | Key? key, 9 | required this.crossAxisCellCount, 10 | required this.mainAxisCellCount, 11 | required this.mainAxisExtent, 12 | required Widget child, 13 | }) : assert(crossAxisCellCount > 0), 14 | assert(mainAxisCellCount == null || mainAxisCellCount > 0), 15 | assert(mainAxisExtent == null || mainAxisExtent > 0), 16 | super(key: key, child: child); 17 | 18 | /// Creates a [StaggeredGrid]'s tile that takes a fixed number of cells along 19 | /// the main axis. 20 | const StaggeredGridTile.count({ 21 | Key? key, 22 | required int crossAxisCellCount, 23 | required num mainAxisCellCount, 24 | required Widget child, 25 | }) : this._( 26 | key: key, 27 | crossAxisCellCount: crossAxisCellCount, 28 | mainAxisCellCount: mainAxisCellCount, 29 | mainAxisExtent: null, 30 | child: child, 31 | ); 32 | 33 | /// Creates a [StaggeredGrid]'s tile that takes a specific amount of space 34 | /// along the main axis. 35 | const StaggeredGridTile.extent({ 36 | Key? key, 37 | required int crossAxisCellCount, 38 | required double mainAxisExtent, 39 | required Widget child, 40 | }) : this._( 41 | key: key, 42 | crossAxisCellCount: crossAxisCellCount, 43 | mainAxisCellCount: null, 44 | mainAxisExtent: mainAxisExtent, 45 | child: child, 46 | ); 47 | 48 | /// Creates a [StaggeredGrid]'s tile that fits its main axis extent to its 49 | /// [child]'s content 50 | const StaggeredGridTile.fit({ 51 | Key? key, 52 | required int crossAxisCellCount, 53 | required Widget child, 54 | }) : this._( 55 | key: key, 56 | crossAxisCellCount: crossAxisCellCount, 57 | mainAxisCellCount: null, 58 | mainAxisExtent: null, 59 | child: child, 60 | ); 61 | 62 | /// The number of cells that this tile takes along the cross axis. 63 | final int crossAxisCellCount; 64 | 65 | /// The number of cells that this tile takes along the main axis. 66 | final num? mainAxisCellCount; 67 | 68 | /// The amount of space that this tile takes along the main axis. 69 | final double? mainAxisExtent; 70 | 71 | @override 72 | void applyParentData(RenderObject renderObject) { 73 | final parentData = renderObject.parentData; 74 | if (parentData is StaggeredGridParentData) { 75 | bool needsLayout = false; 76 | 77 | if (parentData.crossAxisCellCount != crossAxisCellCount) { 78 | parentData.crossAxisCellCount = crossAxisCellCount; 79 | needsLayout = true; 80 | } 81 | 82 | if (parentData.mainAxisCellCount != mainAxisCellCount) { 83 | parentData.mainAxisCellCount = mainAxisCellCount; 84 | needsLayout = true; 85 | } 86 | 87 | if (parentData.mainAxisExtent != mainAxisExtent) { 88 | parentData.mainAxisExtent = mainAxisExtent; 89 | needsLayout = true; 90 | } 91 | 92 | if (needsLayout) { 93 | final targetParent = renderObject.parent; 94 | if (targetParent is RenderStaggeredGrid) { 95 | targetParent.markNeedsLayout(); 96 | } 97 | } 98 | } 99 | } 100 | 101 | @override 102 | Type get debugTypicalAncestorWidgetClass => StaggeredGrid; 103 | } 104 | -------------------------------------------------------------------------------- /lib/src/widgets/uniform_track.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: public_member_api_docs 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_staggered_grid_view/src/rendering/uniform_track.dart'; 5 | 6 | class UniformTrack extends MultiChildRenderObjectWidget { 7 | UniformTrack({ 8 | Key? key, 9 | required this.division, 10 | this.spacing = 0, 11 | required this.direction, 12 | required List children, 13 | }) : assert(spacing >= 0), 14 | assert(division > 0), 15 | assert(children.length <= division), 16 | super(key: key, children: children); 17 | 18 | final double spacing; 19 | final int division; 20 | final AxisDirection direction; 21 | 22 | @override 23 | RenderUniformTrack createRenderObject(BuildContext context) { 24 | return RenderUniformTrack( 25 | direction: direction, 26 | division: division, 27 | spacing: spacing, 28 | ); 29 | } 30 | 31 | @override 32 | void updateRenderObject( 33 | BuildContext context, 34 | covariant RenderUniformTrack renderObject, 35 | ) { 36 | renderObject 37 | ..direction = direction 38 | ..division = division 39 | ..spacing = spacing; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_staggered_grid_view 2 | description: Provides a collection of Flutter grids layouts (staggered, masonry, quilted, woven, etc.). 3 | version: 0.7.0 4 | homepage: https://github.com/letsar/flutter_staggered_grid_view 5 | 6 | environment: 7 | sdk: ">=2.12.0 <3.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 -------------------------------------------------------------------------------- /test/common.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class Tile extends StatelessWidget { 4 | const Tile({ 5 | Key? key, 6 | required this.index, 7 | this.height, 8 | this.width, 9 | this.onTap, 10 | }) : super(key: key); 11 | 12 | final double? height; 13 | final double? width; 14 | final int index; 15 | final VoidCallback? onTap; 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return GestureDetector( 20 | onTap: onTap, 21 | child: Container( 22 | color: Colors.red, 23 | height: height, 24 | width: width, 25 | child: Text('$index'), 26 | ), 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/src/layouts/quilted_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/rendering.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; 4 | import 'package:flutter_test/flutter_test.dart'; 5 | 6 | import '../../common.dart'; 7 | 8 | void main() { 9 | final binding = TestWidgetsFlutterBinding.ensureInitialized(); 10 | testWidgets('Quilted Grid same control test', (WidgetTester tester) async { 11 | await binding.setSurfaceSize(const Size(800, 800)); 12 | await tester.pumpWidget( 13 | Directionality( 14 | textDirection: TextDirection.ltr, 15 | child: GridView.custom( 16 | gridDelegate: SliverQuiltedGridDelegate( 17 | crossAxisCount: 4, 18 | mainAxisSpacing: 4, 19 | crossAxisSpacing: 4, 20 | repeatPattern: QuiltedGridRepeatPattern.same, 21 | pattern: const [ 22 | QuiltedGridTile(2, 2), 23 | QuiltedGridTile(1, 1), 24 | QuiltedGridTile(1, 1), 25 | QuiltedGridTile(1, 2), 26 | ], 27 | ), 28 | childrenDelegate: SliverChildBuilderDelegate( 29 | (context, index) => Tile(index: index), 30 | ), 31 | ), 32 | ), 33 | ); 34 | 35 | void _expectSize(int index, Size size) { 36 | expect(tester.getSize(find.text('$index')), equals(size)); 37 | } 38 | 39 | void _expectTopLeft(int index, Offset topLeft) { 40 | expect(tester.getTopLeft(find.text('$index')), equals(topLeft)); 41 | } 42 | 43 | const s1 = 197.0; 44 | const s2 = 398.0; 45 | const s3 = 599.0; 46 | _expectSize(0, const Size(s2, s2)); 47 | _expectSize(1, const Size(s1, s1)); 48 | _expectSize(2, const Size(s1, s1)); 49 | _expectSize(3, const Size(s2, s1)); 50 | _expectSize(4, const Size(s2, s2)); 51 | _expectSize(5, const Size(s1, s1)); 52 | _expectSize(6, const Size(s1, s1)); 53 | _expectSize(7, const Size(s2, s1)); 54 | 55 | _expectTopLeft(0, const Offset(0, 0)); 56 | _expectTopLeft(1, const Offset(s2 + 4, 0)); 57 | _expectTopLeft(2, const Offset(s3 + 4, 0)); 58 | _expectTopLeft(3, const Offset(s2 + 4, s1 + 4)); 59 | _expectTopLeft(4, const Offset(0, s2 + 4)); 60 | _expectTopLeft(5, const Offset(s2 + 4, s2 + 4)); 61 | _expectTopLeft(6, const Offset(s3 + 4, s2 + 4)); 62 | _expectTopLeft(7, const Offset(s2 + 4, s3 + 4)); 63 | }); 64 | 65 | testWidgets('Quilted Grid inverted control test', 66 | (WidgetTester tester) async { 67 | await binding.setSurfaceSize(const Size(800, 800)); 68 | await tester.pumpWidget( 69 | Directionality( 70 | textDirection: TextDirection.ltr, 71 | child: GridView.custom( 72 | gridDelegate: SliverQuiltedGridDelegate( 73 | crossAxisCount: 4, 74 | mainAxisSpacing: 4, 75 | crossAxisSpacing: 4, 76 | repeatPattern: QuiltedGridRepeatPattern.inverted, 77 | pattern: const [ 78 | QuiltedGridTile(2, 2), 79 | QuiltedGridTile(1, 1), 80 | QuiltedGridTile(1, 1), 81 | QuiltedGridTile(1, 2), 82 | ], 83 | ), 84 | childrenDelegate: SliverChildBuilderDelegate( 85 | (context, index) => Tile(index: index), 86 | ), 87 | ), 88 | ), 89 | ); 90 | 91 | void _expectSize(int index, Size size) { 92 | expect(tester.getSize(find.text('$index')), equals(size)); 93 | } 94 | 95 | void _expectTopLeft(int index, Offset topLeft) { 96 | expect(tester.getTopLeft(find.text('$index')), equals(topLeft)); 97 | } 98 | 99 | const s1 = 197.0; 100 | const s2 = 398.0; 101 | const s3 = 599.0; 102 | _expectSize(0, const Size(s2, s2)); 103 | _expectSize(1, const Size(s1, s1)); 104 | _expectSize(2, const Size(s1, s1)); 105 | _expectSize(3, const Size(s2, s1)); 106 | _expectSize(4, const Size(s2, s1)); 107 | _expectSize(5, const Size(s2, s2)); 108 | _expectSize(6, const Size(s1, s1)); 109 | _expectSize(7, const Size(s1, s1)); 110 | 111 | _expectTopLeft(0, const Offset(0, 0)); 112 | _expectTopLeft(1, const Offset(s2 + 4, 0)); 113 | _expectTopLeft(2, const Offset(s3 + 4, 0)); 114 | _expectTopLeft(3, const Offset(s2 + 4, s1 + 4)); 115 | _expectTopLeft(4, const Offset(0, s2 + 4)); 116 | _expectTopLeft(5, const Offset(s2 + 4, s2 + 4)); 117 | _expectTopLeft(6, const Offset(0, s3 + 4)); 118 | _expectTopLeft(7, const Offset(s1 + 4, s3 + 4)); 119 | }); 120 | 121 | testWidgets('Quilted Grid mirrored control test', 122 | (WidgetTester tester) async { 123 | await binding.setSurfaceSize(const Size(800, 800)); 124 | await tester.pumpWidget( 125 | Directionality( 126 | textDirection: TextDirection.ltr, 127 | child: GridView.custom( 128 | gridDelegate: SliverQuiltedGridDelegate( 129 | crossAxisCount: 4, 130 | mainAxisSpacing: 4, 131 | crossAxisSpacing: 4, 132 | repeatPattern: QuiltedGridRepeatPattern.mirrored, 133 | pattern: const [ 134 | QuiltedGridTile(2, 2), 135 | QuiltedGridTile(1, 1), 136 | QuiltedGridTile(1, 1), 137 | QuiltedGridTile(1, 2), 138 | ], 139 | ), 140 | childrenDelegate: SliverChildBuilderDelegate( 141 | (context, index) => Tile(index: index), 142 | ), 143 | ), 144 | ), 145 | ); 146 | 147 | void _expectSize(int index, Size size) { 148 | expect(tester.getSize(find.text('$index')), equals(size)); 149 | } 150 | 151 | void _expectTopLeft(int index, Offset topLeft) { 152 | expect(tester.getTopLeft(find.text('$index')), equals(topLeft)); 153 | } 154 | 155 | const s1 = 197.0; 156 | const s2 = 398.0; 157 | const s3 = 599.0; 158 | _expectSize(0, const Size(s2, s2)); 159 | _expectSize(1, const Size(s1, s1)); 160 | _expectSize(2, const Size(s1, s1)); 161 | _expectSize(3, const Size(s2, s1)); 162 | _expectSize(4, const Size(s2, s2)); 163 | _expectSize(5, const Size(s2, s1)); 164 | _expectSize(6, const Size(s1, s1)); 165 | _expectSize(7, const Size(s1, s1)); 166 | 167 | _expectTopLeft(0, const Offset(0, 0)); 168 | _expectTopLeft(1, const Offset(s2 + 4, 0)); 169 | _expectTopLeft(2, const Offset(s3 + 4, 0)); 170 | _expectTopLeft(3, const Offset(s2 + 4, s1 + 4)); 171 | _expectTopLeft(4, const Offset(0, s2 + 4)); 172 | _expectTopLeft(5, const Offset(s2 + 4, s2 + 4)); 173 | _expectTopLeft(6, const Offset(s2 + 4, s3 + 4)); 174 | _expectTopLeft(7, const Offset(s3 + 4, s3 + 4)); 175 | }); 176 | 177 | testWidgets('Quilted Grid different pattern control test', 178 | (WidgetTester tester) async { 179 | await binding.setSurfaceSize(const Size(200, 268)); 180 | await tester.pumpWidget( 181 | Directionality( 182 | textDirection: TextDirection.ltr, 183 | child: GridView.custom( 184 | gridDelegate: SliverQuiltedGridDelegate( 185 | crossAxisCount: 3, 186 | mainAxisSpacing: 4, 187 | crossAxisSpacing: 4, 188 | repeatPattern: QuiltedGridRepeatPattern.inverted, 189 | pattern: const [ 190 | QuiltedGridTile(1, 1), 191 | QuiltedGridTile(1, 1), 192 | QuiltedGridTile(1, 1), 193 | QuiltedGridTile(1, 1), 194 | QuiltedGridTile(2, 2), 195 | QuiltedGridTile(1, 1), 196 | QuiltedGridTile(1, 1), 197 | QuiltedGridTile(1, 1), 198 | QuiltedGridTile(1, 1), 199 | ], 200 | ), 201 | childrenDelegate: SliverChildBuilderDelegate( 202 | (context, index) => Tile(index: index), 203 | ), 204 | ), 205 | ), 206 | ); 207 | 208 | void _expectSize(int index, Size size) { 209 | expect(tester.getSize(find.text('$index')), equals(size)); 210 | } 211 | 212 | void _expectTopLeft(int index, Offset topLeft) { 213 | expect(tester.getTopLeft(find.text('$index')), equals(topLeft)); 214 | } 215 | 216 | const s1 = 64.0; 217 | const s2 = 132.0; 218 | const s3 = 200.0; 219 | _expectSize(0, const Size(s1, s1)); 220 | _expectSize(1, const Size(s1, s1)); 221 | _expectSize(2, const Size(s1, s1)); 222 | _expectSize(3, const Size(s1, s1)); 223 | _expectSize(4, const Size(s2, s2)); 224 | _expectSize(5, const Size(s1, s1)); 225 | _expectSize(6, const Size(s1, s1)); 226 | _expectSize(7, const Size(s1, s1)); 227 | _expectSize(7, const Size(s1, s1)); 228 | 229 | _expectTopLeft(0, const Offset(0, 0)); 230 | _expectTopLeft(1, const Offset(s1 + 4, 0)); 231 | _expectTopLeft(2, const Offset(s2 + 4, 0)); 232 | _expectTopLeft(3, const Offset(0, s1 + 4)); 233 | _expectTopLeft(4, const Offset(s1 + 4, s1 + 4)); 234 | _expectTopLeft(5, const Offset(0, s2 + 4)); 235 | _expectTopLeft(6, const Offset(0, s3 + 4)); 236 | _expectTopLeft(7, const Offset(s1 + 4, s3 + 4)); 237 | _expectTopLeft(8, const Offset(s2 + 4, s3 + 4)); 238 | }); 239 | 240 | test('computeMaxScrollOffset should be right', () { 241 | final delegate = SliverQuiltedGridDelegate( 242 | crossAxisCount: 4, 243 | mainAxisSpacing: 4, 244 | crossAxisSpacing: 4, 245 | repeatPattern: QuiltedGridRepeatPattern.same, 246 | pattern: const [ 247 | QuiltedGridTile(2, 2), 248 | QuiltedGridTile(1, 1), 249 | QuiltedGridTile(1, 1), 250 | QuiltedGridTile(1, 2), 251 | ], 252 | ); 253 | 254 | final layout = delegate.getLayout( 255 | const SliverConstraints( 256 | axisDirection: AxisDirection.down, 257 | cacheOrigin: 0, 258 | crossAxisDirection: AxisDirection.right, 259 | crossAxisExtent: 412, 260 | growthDirection: GrowthDirection.forward, 261 | scrollOffset: 0, 262 | overlap: 0, 263 | viewportMainAxisExtent: 400, 264 | precedingScrollExtent: 0, 265 | remainingCacheExtent: 400, 266 | remainingPaintExtent: 400, 267 | userScrollDirection: ScrollDirection.idle, 268 | ), 269 | ); 270 | 271 | expect(layout.computeMaxScrollOffset(12), 620); 272 | expect(layout.computeMaxScrollOffset(16), 828); 273 | }); 274 | } 275 | -------------------------------------------------------------------------------- /test/src/layouts/staired_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | import 'package:flutter_staggered_grid_view/src/layouts/staired.dart'; 4 | import 'package:flutter_test/flutter_test.dart'; 5 | 6 | import '../../common.dart'; 7 | 8 | void main() { 9 | final binding = TestWidgetsFlutterBinding.ensureInitialized(); 10 | testWidgets('Staired Grid control test', (WidgetTester tester) async { 11 | await binding.setSurfaceSize(const Size(800, 800)); 12 | await tester.pumpWidget( 13 | Directionality( 14 | textDirection: TextDirection.ltr, 15 | child: GridView.custom( 16 | gridDelegate: SliverStairedGridDelegate( 17 | crossAxisSpacing: 48, 18 | mainAxisSpacing: 24, 19 | startCrossAxisDirectionReversed: true, 20 | pattern: const [ 21 | StairedGridTile(0.5, 1), 22 | StairedGridTile(0.5, 3 / 4), 23 | StairedGridTile(1.0, 10 / 4), 24 | ], 25 | ), 26 | childrenDelegate: SliverChildBuilderDelegate( 27 | (context, index) => Tile(index: index), 28 | ), 29 | ), 30 | ), 31 | ); 32 | 33 | void _expectSize(int index, Size size) { 34 | expect(tester.getSize(find.text('$index')), equals(size)); 35 | } 36 | 37 | void _expectTopLeft(int index, Offset topLeft) { 38 | final actualOffset = tester.getTopLeft(find.text('$index')); 39 | expect( 40 | actualOffset.dx, 41 | moreOrLessEquals(topLeft.dx, epsilon: precisionErrorTolerance), 42 | ); 43 | expect( 44 | actualOffset.dy, 45 | moreOrLessEquals(topLeft.dy, epsilon: precisionErrorTolerance), 46 | ); 47 | } 48 | 49 | const s1 = 376.0; 50 | 51 | _expectSize(0, const Size(s1, s1)); 52 | _expectSize(1, const Size(s1, s1 / (3 / 4))); 53 | _expectSize(2, const Size(704, 704 / (10 / 4))); 54 | 55 | _expectTopLeft(0, const Offset(s1 + 48, 0)); 56 | _expectTopLeft(1, const Offset(0, 24)); 57 | _expectTopLeft(2, const Offset(48, s1 / (3 / 4) + 48)); 58 | }); 59 | } 60 | -------------------------------------------------------------------------------- /test/src/layouts/woven_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/rendering.dart'; 3 | import 'package:flutter/widgets.dart'; 4 | import 'package:flutter_staggered_grid_view/src/layouts/woven.dart'; 5 | import 'package:flutter_test/flutter_test.dart'; 6 | 7 | import '../../common.dart'; 8 | 9 | void main() { 10 | final binding = TestWidgetsFlutterBinding.ensureInitialized(); 11 | testWidgets('Woven Grid control test', (WidgetTester tester) async { 12 | await binding.setSurfaceSize(const Size(800, 800)); 13 | await tester.pumpWidget( 14 | Directionality( 15 | textDirection: TextDirection.ltr, 16 | child: GridView.custom( 17 | gridDelegate: SliverWovenGridDelegate.count( 18 | crossAxisCount: 2, 19 | mainAxisSpacing: 8, 20 | crossAxisSpacing: 8, 21 | pattern: const [ 22 | WovenGridTile(1), 23 | WovenGridTile( 24 | 5 / 7, 25 | crossAxisRatio: 0.9, 26 | alignment: AlignmentDirectional.centerEnd, 27 | ), 28 | ], 29 | ), 30 | childrenDelegate: SliverChildBuilderDelegate( 31 | (context, index) => Tile(index: index), 32 | ), 33 | ), 34 | ), 35 | ); 36 | 37 | void _expectSize(int index, Size size) { 38 | expect(tester.getSize(find.text('$index')), equals(size)); 39 | } 40 | 41 | void _expectTopLeft(int index, Offset topLeft) { 42 | final actualOffset = tester.getTopLeft(find.text('$index')); 43 | expect( 44 | actualOffset.dx, 45 | moreOrLessEquals(topLeft.dx, epsilon: precisionErrorTolerance), 46 | ); 47 | expect( 48 | actualOffset.dy, 49 | moreOrLessEquals(topLeft.dy, epsilon: precisionErrorTolerance), 50 | ); 51 | } 52 | 53 | const s1 = 396.0; 54 | const s2 = s1 * 0.9; 55 | const s3 = s2 * 7 / 5; 56 | 57 | _expectSize(0, const Size(s1, s1)); 58 | _expectSize(1, const Size(s2, s3)); 59 | _expectSize(2, const Size(s2, s3)); 60 | _expectSize(3, const Size(s1, s1)); 61 | 62 | _expectTopLeft(0, const Offset(0, (s3 - s1) / 2)); 63 | _expectTopLeft(1, const Offset(s1 + 8 + 0.1 * s1, 0)); 64 | _expectTopLeft(2, const Offset(0, s3 + 8)); 65 | _expectTopLeft(3, const Offset(s1 + 8, s3 + 8 + (s3 - s1) / 2)); 66 | }); 67 | 68 | testWidgets('Woven layout should follow an opposite flow', 69 | (WidgetTester tester) async { 70 | await binding.setSurfaceSize(const Size(412, 800)); 71 | await tester.pumpWidget( 72 | Directionality( 73 | textDirection: TextDirection.ltr, 74 | child: GridView.custom( 75 | gridDelegate: SliverWovenGridDelegate.count( 76 | crossAxisCount: 4, 77 | mainAxisSpacing: 4, 78 | crossAxisSpacing: 4, 79 | pattern: const [ 80 | WovenGridTile(1), 81 | WovenGridTile( 82 | 6 / 10, 83 | crossAxisRatio: 0.9, 84 | ), 85 | WovenGridTile( 86 | 3 / 4, 87 | crossAxisRatio: 0.9, 88 | ), 89 | ], 90 | ), 91 | childrenDelegate: SliverChildBuilderDelegate( 92 | (context, index) => Tile(index: index), 93 | ), 94 | ), 95 | ), 96 | ); 97 | 98 | void _expectSize(int index, Size size) { 99 | expect(tester.getSize(find.text('$index')), equals(size)); 100 | } 101 | 102 | void _expectTopLeft(int index, Offset topLeft) { 103 | final actualOffset = tester.getTopLeft(find.text('$index')); 104 | expect( 105 | actualOffset.dx, 106 | moreOrLessEquals(topLeft.dx, epsilon: precisionErrorTolerance), 107 | ); 108 | expect( 109 | actualOffset.dy, 110 | moreOrLessEquals(topLeft.dy, epsilon: precisionErrorTolerance), 111 | ); 112 | } 113 | 114 | const s1 = 100.0; 115 | const s2 = s1 * 0.9; 116 | const s3 = s2 * 10 / 6; 117 | const s4 = s2 * 4 / 3; 118 | 119 | _expectSize(0, const Size(s1, s1)); 120 | _expectSize(1, const Size(s2, s3)); 121 | _expectSize(2, const Size(s2, s4)); 122 | _expectSize(3, const Size(s1, s1)); 123 | _expectSize(4, const Size(s2, s3)); 124 | _expectSize(5, const Size(s1, s1)); 125 | _expectSize(6, const Size(s2, s4)); 126 | _expectSize(7, const Size(s2, s3)); 127 | 128 | _expectTopLeft(0, const Offset(0, 25)); 129 | _expectTopLeft(1, const Offset(104 + 5, 0)); 130 | _expectTopLeft(2, const Offset(208 + 5, 15)); 131 | _expectTopLeft(3, const Offset(312, 25)); 132 | _expectTopLeft(4, const Offset(5, 154)); 133 | _expectTopLeft(5, const Offset(104, 154 + 25)); 134 | _expectTopLeft(6, const Offset(208 + 5, 154 + 15)); 135 | _expectTopLeft(7, const Offset(312 + 5, 154)); 136 | }); 137 | 138 | test('computeMaxScrollOffset should be right', () { 139 | final delegate = SliverWovenGridDelegate.count( 140 | crossAxisCount: 3, 141 | mainAxisSpacing: 8, 142 | crossAxisSpacing: 8, 143 | pattern: const [ 144 | WovenGridTile(1), 145 | WovenGridTile( 146 | 5 / 7, 147 | crossAxisRatio: 0.9, 148 | alignment: AlignmentDirectional.centerEnd, 149 | ), 150 | ], 151 | ); 152 | 153 | final layout = delegate.getLayout( 154 | const SliverConstraints( 155 | axisDirection: AxisDirection.down, 156 | cacheOrigin: 0, 157 | crossAxisDirection: AxisDirection.right, 158 | crossAxisExtent: 412, 159 | growthDirection: GrowthDirection.forward, 160 | scrollOffset: 0, 161 | overlap: 0, 162 | viewportMainAxisExtent: 400, 163 | precedingScrollExtent: 0, 164 | remainingCacheExtent: 400, 165 | remainingPaintExtent: 400, 166 | userScrollDirection: ScrollDirection.idle, 167 | ), 168 | ); 169 | 170 | expect(layout.computeMaxScrollOffset(13), 846.4399999999999); 171 | expect(layout.computeMaxScrollOffset(14), 863.5999999999999); 172 | expect(layout.computeMaxScrollOffset(16), 1037.92); 173 | }); 174 | } 175 | -------------------------------------------------------------------------------- /test/src/widgets/aligned_grid_view_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/gestures.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | import 'package:flutter_staggered_grid_view/src/widgets/aligned_grid_view.dart'; 4 | import 'package:flutter_test/flutter_test.dart'; 5 | 6 | import '../../common.dart'; 7 | 8 | void main() { 9 | final binding = TestWidgetsFlutterBinding.ensureInitialized(); 10 | 11 | testWidgets('Empty AlignedGridView', (WidgetTester tester) async { 12 | await tester.pumpWidget( 13 | Directionality( 14 | textDirection: TextDirection.ltr, 15 | child: AlignedGridView.count( 16 | dragStartBehavior: DragStartBehavior.down, 17 | crossAxisCount: 4, 18 | itemBuilder: (contex, index) => const SizedBox(), 19 | itemCount: 0, 20 | ), 21 | ), 22 | ); 23 | }); 24 | 25 | testWidgets('Should only layout the number of requested items', 26 | (WidgetTester tester) async { 27 | await tester.pumpWidget( 28 | Directionality( 29 | textDirection: TextDirection.ltr, 30 | child: AlignedGridView.count( 31 | dragStartBehavior: DragStartBehavior.down, 32 | crossAxisCount: 4, 33 | itemBuilder: (contex, index) { 34 | return Tile( 35 | index: index, 36 | height: 100, 37 | ); 38 | }, 39 | itemCount: 4, 40 | ), 41 | ), 42 | ); 43 | 44 | expect(find.text('0'), findsOneWidget); 45 | expect(find.text('4'), findsNothing); 46 | 47 | await tester.pumpWidget( 48 | Directionality( 49 | textDirection: TextDirection.ltr, 50 | child: AlignedGridView.count( 51 | dragStartBehavior: DragStartBehavior.down, 52 | crossAxisCount: 4, 53 | itemBuilder: (contex, index) { 54 | return Tile( 55 | index: index, 56 | height: 100, 57 | ); 58 | }, 59 | itemCount: 5, 60 | ), 61 | ), 62 | ); 63 | 64 | expect(find.text('0'), findsOneWidget); 65 | expect(find.text('4'), findsOneWidget); 66 | expect(find.text('5'), findsNothing); 67 | }); 68 | 69 | testWidgets('AlignedGridView.count control test', 70 | (WidgetTester tester) async { 71 | // Screen size is 800x600 in the test environment. 72 | final itemCount = 12; 73 | 74 | await tester.pumpWidget( 75 | Directionality( 76 | textDirection: TextDirection.ltr, 77 | child: AlignedGridView.count( 78 | cacheExtent: 0, 79 | dragStartBehavior: DragStartBehavior.down, 80 | crossAxisCount: 4, 81 | itemBuilder: (context, index) { 82 | return Tile( 83 | index: index, 84 | height: ((index % 5) + 1) * 100, 85 | ); 86 | }, 87 | itemCount: itemCount, 88 | ), 89 | ), 90 | ); 91 | 92 | void _expectSize(int index, Size size) { 93 | expect(tester.getSize(find.text('$index')), equals(size)); 94 | } 95 | 96 | void _expectTopLeft(int index, Offset topLeft) { 97 | expect(tester.getTopLeft(find.text('$index')), equals(topLeft)); 98 | } 99 | 100 | _expectSize(0, const Size(200, 400)); 101 | _expectSize(1, const Size(200, 400)); 102 | _expectSize(2, const Size(200, 400)); 103 | _expectSize(3, const Size(200, 400)); 104 | _expectSize(4, const Size(200, 500)); 105 | _expectSize(5, const Size(200, 500)); 106 | _expectSize(6, const Size(200, 500)); 107 | _expectSize(7, const Size(200, 500)); 108 | 109 | _expectTopLeft(0, const Offset(0, 0)); 110 | _expectTopLeft(1, const Offset(200, 0)); 111 | _expectTopLeft(2, const Offset(400, 0)); 112 | _expectTopLeft(3, const Offset(600, 0)); 113 | _expectTopLeft(4, const Offset(0, 400)); 114 | _expectTopLeft(5, const Offset(200, 400)); 115 | _expectTopLeft(6, const Offset(400, 400)); 116 | _expectTopLeft(7, const Offset(600, 400)); 117 | }); 118 | 119 | testWidgets('AlignedGridView.extent control test', 120 | (WidgetTester tester) async { 121 | // Screen size is 800x600 in the test environment. 122 | final itemCount = 12; 123 | 124 | await tester.pumpWidget( 125 | Directionality( 126 | textDirection: TextDirection.ltr, 127 | child: AlignedGridView.extent( 128 | cacheExtent: 0, 129 | dragStartBehavior: DragStartBehavior.down, 130 | maxCrossAxisExtent: 200, 131 | itemBuilder: (context, index) { 132 | return Tile( 133 | index: index, 134 | height: ((index % 5) + 1) * 100, 135 | ); 136 | }, 137 | itemCount: itemCount, 138 | ), 139 | ), 140 | ); 141 | 142 | void _expectSize(int index, Size size) { 143 | expect(tester.getSize(find.text('$index')), equals(size)); 144 | } 145 | 146 | void _expectTopLeft(int index, Offset topLeft) { 147 | expect(tester.getTopLeft(find.text('$index')), equals(topLeft)); 148 | } 149 | 150 | _expectSize(0, const Size(200, 400)); 151 | _expectSize(1, const Size(200, 400)); 152 | _expectSize(2, const Size(200, 400)); 153 | _expectSize(3, const Size(200, 400)); 154 | _expectSize(4, const Size(200, 500)); 155 | _expectSize(5, const Size(200, 500)); 156 | _expectSize(6, const Size(200, 500)); 157 | _expectSize(7, const Size(200, 500)); 158 | 159 | _expectTopLeft(0, const Offset(0, 0)); 160 | _expectTopLeft(1, const Offset(200, 0)); 161 | _expectTopLeft(2, const Offset(400, 0)); 162 | _expectTopLeft(3, const Offset(600, 0)); 163 | _expectTopLeft(4, const Offset(0, 400)); 164 | _expectTopLeft(5, const Offset(200, 400)); 165 | _expectTopLeft(6, const Offset(400, 400)); 166 | _expectTopLeft(7, const Offset(600, 400)); 167 | 168 | // Change orientation to portrait. 169 | await binding.setSurfaceSize(const Size(600, 800)); 170 | await tester.pump(); 171 | 172 | _expectSize(0, const Size(200, 300)); 173 | _expectSize(1, const Size(200, 300)); 174 | _expectSize(2, const Size(200, 300)); 175 | _expectSize(3, const Size(200, 500)); 176 | _expectSize(4, const Size(200, 500)); 177 | _expectSize(5, const Size(200, 500)); 178 | 179 | _expectTopLeft(0, const Offset(0, 0)); 180 | _expectTopLeft(1, const Offset(200, 0)); 181 | _expectTopLeft(2, const Offset(400, 0)); 182 | _expectTopLeft(3, const Offset(0, 300)); 183 | _expectTopLeft(4, const Offset(200, 300)); 184 | _expectTopLeft(5, const Offset(400, 300)); 185 | 186 | expect(find.text('6'), findsNothing); 187 | expect(find.text('7'), findsNothing); 188 | }); 189 | } 190 | -------------------------------------------------------------------------------- /test/src/widgets/masonry_grid_view_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/gestures.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | import 'package:flutter_staggered_grid_view/src/widgets/masonry_grid_view.dart'; 4 | import 'package:flutter_test/flutter_test.dart'; 5 | 6 | import '../../common.dart'; 7 | 8 | void main() { 9 | final binding = TestWidgetsFlutterBinding.ensureInitialized(); 10 | 11 | testWidgets('Empty MasonryGridView', (WidgetTester tester) async { 12 | await tester.pumpWidget( 13 | Directionality( 14 | textDirection: TextDirection.ltr, 15 | child: MasonryGridView.count( 16 | dragStartBehavior: DragStartBehavior.down, 17 | crossAxisCount: 4, 18 | itemBuilder: (contex, index) => const SizedBox(), 19 | itemCount: 0, 20 | ), 21 | ), 22 | ); 23 | }); 24 | 25 | testWidgets('MasonryGridView.count control test', 26 | (WidgetTester tester) async { 27 | // Screen size is 800x600 in the test environment. 28 | final List log = []; 29 | final itemCount = 12; 30 | 31 | await tester.pumpWidget( 32 | Directionality( 33 | textDirection: TextDirection.ltr, 34 | child: MasonryGridView.count( 35 | cacheExtent: 0, 36 | dragStartBehavior: DragStartBehavior.down, 37 | crossAxisCount: 4, 38 | itemBuilder: (context, index) { 39 | return Tile( 40 | key: ValueKey(index), 41 | index: index, 42 | height: ((index % 4) + 1) * 100, 43 | onTap: () => log.add('$index'), 44 | ); 45 | }, 46 | itemCount: itemCount, 47 | ), 48 | ), 49 | ); 50 | 51 | for (var i = 0; i < itemCount; i++) { 52 | final double h = ((i % 4) + 1) * 100; 53 | expect(tester.getSize(find.byKey(ValueKey(i))), equals(Size(200.0, h))); 54 | } 55 | 56 | for (int i = 0; i < 10; ++i) { 57 | await tester.tap(find.byKey((ValueKey(i)))); 58 | expect(log, equals(['$i'])); 59 | log.clear(); 60 | } 61 | 62 | expect(find.text('12'), findsNothing); 63 | 64 | await tester.drag(find.text('5'), const Offset(0.0, -200.0)); 65 | await tester.pump(); 66 | 67 | expect(find.text('0'), findsNothing); 68 | expect(find.text('1'), findsNothing); 69 | expect(find.text('4'), findsNothing); 70 | }); 71 | 72 | testWidgets('MasonryGridView.extent control test', 73 | (WidgetTester tester) async { 74 | // Screen size is 800x600 in the test environment. 75 | final List log = []; 76 | final itemCount = 12; 77 | 78 | await tester.pumpWidget( 79 | Directionality( 80 | textDirection: TextDirection.ltr, 81 | child: MasonryGridView.extent( 82 | cacheExtent: 0, 83 | dragStartBehavior: DragStartBehavior.down, 84 | maxCrossAxisExtent: 200, 85 | itemBuilder: (context, index) { 86 | return Tile( 87 | key: ValueKey(index), 88 | index: index, 89 | height: ((index % 4) + 1) * 100, 90 | onTap: () => log.add('$index'), 91 | ); 92 | }, 93 | itemCount: itemCount, 94 | ), 95 | ), 96 | ); 97 | 98 | for (var i = 0; i < itemCount; i++) { 99 | final double h = ((i % 4) + 1) * 100; 100 | expect(tester.getSize(find.byKey(ValueKey(i))), equals(Size(200.0, h))); 101 | } 102 | 103 | for (int i = 0; i < 10; ++i) { 104 | await tester.tap(find.byKey((ValueKey(i)))); 105 | expect(log, equals(['$i'])); 106 | log.clear(); 107 | } 108 | 109 | expect(find.text('12'), findsNothing); 110 | 111 | await tester.drag(find.text('5'), const Offset(0.0, -200.0)); 112 | await tester.pump(); 113 | 114 | expect(find.text('0'), findsNothing); 115 | expect(find.text('1'), findsNothing); 116 | expect(find.text('4'), findsNothing); 117 | 118 | // Change orientation to portrait. 119 | await binding.setSurfaceSize(const Size(600, 800)); 120 | await tester.pump(); 121 | 122 | for (var i = 0; i < 10; i++) { 123 | final double h = ((i % 4) + 1) * 100; 124 | expect(tester.getSize(find.byKey(ValueKey(i))), equals(Size(200.0, h))); 125 | } 126 | 127 | for (int i = 0; i < 10; ++i) { 128 | await tester.tap(find.byKey((ValueKey(i)))); 129 | expect(log, equals(['$i'])); 130 | log.clear(); 131 | } 132 | 133 | expect(find.text('11'), findsNothing); 134 | 135 | await tester.drag(find.text('5'), const Offset(0.0, -200.0)); 136 | await tester.pump(); 137 | 138 | expect(find.text('0'), findsNothing); 139 | }); 140 | } 141 | -------------------------------------------------------------------------------- /test/src/widgets/staggered_grid_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:flutter_staggered_grid_view/src/widgets/staggered_grid.dart'; 3 | import 'package:flutter_staggered_grid_view/src/widgets/staggered_grid_tile.dart'; 4 | import 'package:flutter_test/flutter_test.dart'; 5 | 6 | import '../../common.dart'; 7 | 8 | void main() { 9 | final binding = TestWidgetsFlutterBinding.ensureInitialized(); 10 | 11 | testWidgets('Empty StaggeredGrid', (WidgetTester tester) async { 12 | await tester.pumpWidget( 13 | Directionality( 14 | textDirection: TextDirection.ltr, 15 | child: StaggeredGrid.count( 16 | crossAxisCount: 4, 17 | ), 18 | ), 19 | ); 20 | }); 21 | 22 | testWidgets('StaggeredGrid.count control test', (WidgetTester tester) async { 23 | // Screen size is 800x600 in the test environment. 24 | await tester.pumpWidget( 25 | Directionality( 26 | textDirection: TextDirection.ltr, 27 | child: StaggeredGrid.count( 28 | crossAxisCount: 4, 29 | mainAxisSpacing: 4, 30 | crossAxisSpacing: 4, 31 | children: const [ 32 | StaggeredGridTile.count( 33 | crossAxisCellCount: 2, 34 | mainAxisCellCount: 2, 35 | child: Tile(index: 0), 36 | ), 37 | StaggeredGridTile.count( 38 | crossAxisCellCount: 2, 39 | mainAxisCellCount: 1, 40 | child: Tile(index: 1), 41 | ), 42 | StaggeredGridTile.count( 43 | crossAxisCellCount: 1, 44 | mainAxisCellCount: 1, 45 | child: Tile(index: 2), 46 | ), 47 | StaggeredGridTile.count( 48 | crossAxisCellCount: 1, 49 | mainAxisCellCount: 1, 50 | child: Tile(index: 3), 51 | ), 52 | StaggeredGridTile.count( 53 | crossAxisCellCount: 4, 54 | mainAxisCellCount: 2, 55 | child: Tile(index: 4), 56 | ), 57 | ], 58 | ), 59 | ), 60 | ); 61 | 62 | void _expectSize(int index, Size size) { 63 | expect(tester.getSize(find.text('$index')), equals(size)); 64 | } 65 | 66 | void _expectTopLeft(int index, Offset topLeft) { 67 | expect(tester.getTopLeft(find.text('$index')), equals(topLeft)); 68 | } 69 | 70 | const s1 = 197.0; 71 | const s2 = 398.0; 72 | const s3 = 599.0; 73 | const s4 = 800.0; 74 | _expectSize(0, const Size(s2, s2)); 75 | _expectSize(1, const Size(s2, s1)); 76 | _expectSize(2, const Size(s1, s1)); 77 | _expectSize(3, const Size(s1, s1)); 78 | _expectSize(4, const Size(s4, s2)); 79 | 80 | _expectTopLeft(0, const Offset(0, 0)); 81 | _expectTopLeft(1, const Offset(s2 + 4, 0)); 82 | _expectTopLeft(2, const Offset(s2 + 4, s1 + 4)); 83 | _expectTopLeft(3, const Offset(s3 + 4, s1 + 4)); 84 | _expectTopLeft(4, const Offset(0, s2 + 4)); 85 | }); 86 | 87 | testWidgets('StaggeredGrid.extent control test', (WidgetTester tester) async { 88 | // Screen size is 800x600 in the test environment. 89 | await tester.pumpWidget( 90 | Directionality( 91 | textDirection: TextDirection.ltr, 92 | child: StaggeredGrid.extent( 93 | maxCrossAxisExtent: 197, 94 | mainAxisSpacing: 4, 95 | crossAxisSpacing: 4, 96 | children: const [ 97 | StaggeredGridTile.count( 98 | crossAxisCellCount: 2, 99 | mainAxisCellCount: 2, 100 | child: Tile(index: 0), 101 | ), 102 | StaggeredGridTile.count( 103 | crossAxisCellCount: 2, 104 | mainAxisCellCount: 1, 105 | child: Tile(index: 1), 106 | ), 107 | StaggeredGridTile.count( 108 | crossAxisCellCount: 1, 109 | mainAxisCellCount: 1, 110 | child: Tile(index: 2), 111 | ), 112 | StaggeredGridTile.count( 113 | crossAxisCellCount: 1, 114 | mainAxisCellCount: 1, 115 | child: Tile(index: 3), 116 | ), 117 | StaggeredGridTile.count( 118 | crossAxisCellCount: 4, 119 | mainAxisCellCount: 2, 120 | child: Tile(index: 4), 121 | ), 122 | ], 123 | ), 124 | ), 125 | ); 126 | 127 | void _expectSize(int index, Size size) { 128 | expect(tester.getSize(find.text('$index')), equals(size)); 129 | } 130 | 131 | void _expectTopLeft(int index, Offset topLeft) { 132 | expect(tester.getTopLeft(find.text('$index')), equals(topLeft)); 133 | } 134 | 135 | const s1 = 197.0; 136 | const s2 = 398.0; 137 | const s3 = 599.0; 138 | const s4 = 800.0; 139 | _expectSize(0, const Size(s2, s2)); 140 | _expectSize(1, const Size(s2, s1)); 141 | _expectSize(2, const Size(s1, s1)); 142 | _expectSize(3, const Size(s1, s1)); 143 | _expectSize(4, const Size(s4, s2)); 144 | 145 | _expectTopLeft(0, const Offset(0, 0)); 146 | _expectTopLeft(1, const Offset(s2 + 4, 0)); 147 | _expectTopLeft(2, const Offset(s2 + 4, s1 + 4)); 148 | _expectTopLeft(3, const Offset(s3 + 4, s1 + 4)); 149 | _expectTopLeft(4, const Offset(0, s2 + 4)); 150 | 151 | // Change orientation to portrait. 152 | await binding.setSurfaceSize(const Size(599, 800)); 153 | await tester.pump(); 154 | 155 | _expectSize(0, const Size(s2, s2)); 156 | _expectSize(1, const Size(s2, s1)); 157 | _expectSize(2, const Size(s1, s1)); 158 | _expectSize(3, const Size(s1, s1)); 159 | _expectSize(4, const Size(s3, s2)); // The crossAxisCellCount is reduced. 160 | 161 | // New layout: 162 | // 002 163 | // 003 164 | // 11 165 | // 444 166 | // 444 167 | _expectTopLeft(0, const Offset(0.0, 0.0)); 168 | _expectTopLeft(1, const Offset(0, s2 + 4)); 169 | _expectTopLeft(2, const Offset(s2 + 4, 0)); 170 | _expectTopLeft(3, const Offset(s2 + 4, s1 + 4)); 171 | _expectTopLeft(4, const Offset(0, s3 + 4)); 172 | }); 173 | } 174 | -------------------------------------------------------------------------------- /test/src/widgets/staggered_grid_tile_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:flutter_staggered_grid_view/src/widgets/staggered_grid.dart'; 3 | import 'package:flutter_staggered_grid_view/src/widgets/staggered_grid_tile.dart'; 4 | import 'package:flutter_test/flutter_test.dart'; 5 | 6 | import '../../common.dart'; 7 | 8 | void main() { 9 | testWidgets('StaggeredGridTile.extent control test', 10 | (WidgetTester tester) async { 11 | // Screen size is 800x600 in the test environment. 12 | await tester.pumpWidget( 13 | Directionality( 14 | textDirection: TextDirection.ltr, 15 | child: StaggeredGrid.count( 16 | crossAxisCount: 4, 17 | mainAxisSpacing: 4, 18 | crossAxisSpacing: 4, 19 | children: const [ 20 | StaggeredGridTile.extent( 21 | crossAxisCellCount: 2, 22 | mainAxisExtent: 200, 23 | child: Tile(index: 0), 24 | ), 25 | StaggeredGridTile.extent( 26 | crossAxisCellCount: 2, 27 | mainAxisExtent: 100, 28 | child: Tile(index: 1), 29 | ), 30 | StaggeredGridTile.extent( 31 | crossAxisCellCount: 1, 32 | mainAxisExtent: 100, 33 | child: Tile(index: 2), 34 | ), 35 | StaggeredGridTile.extent( 36 | crossAxisCellCount: 1, 37 | mainAxisExtent: 100, 38 | child: Tile(index: 3), 39 | ), 40 | StaggeredGridTile.extent( 41 | crossAxisCellCount: 4, 42 | mainAxisExtent: 200, 43 | child: Tile(index: 4), 44 | ), 45 | ], 46 | ), 47 | ), 48 | ); 49 | 50 | void _expectSize(int index, Size size) { 51 | expect(tester.getSize(find.text('$index')), equals(size)); 52 | } 53 | 54 | void _expectTopLeft(int index, Offset topLeft) { 55 | expect(tester.getTopLeft(find.text('$index')), equals(topLeft)); 56 | } 57 | 58 | const s1 = 197.0; 59 | const s2 = 398.0; 60 | const s3 = 599.0; 61 | const s4 = 800.0; 62 | _expectSize(0, const Size(s2, 200)); 63 | _expectSize(1, const Size(s2, 100)); 64 | _expectSize(2, const Size(s1, 100)); 65 | _expectSize(3, const Size(s1, 100)); 66 | _expectSize(4, const Size(s4, 200)); 67 | 68 | _expectTopLeft(0, const Offset(0, 0)); 69 | _expectTopLeft(1, const Offset(s2 + 4, 0)); 70 | _expectTopLeft(2, const Offset(s2 + 4, 104)); 71 | _expectTopLeft(3, const Offset(s3 + 4, 104)); 72 | _expectTopLeft(4, const Offset(0, 208)); 73 | }); 74 | 75 | testWidgets('StaggeredGridTile.fit control test', 76 | (WidgetTester tester) async { 77 | // Screen size is 800x600 in the test environment. 78 | await tester.pumpWidget( 79 | Directionality( 80 | textDirection: TextDirection.ltr, 81 | child: StaggeredGrid.count( 82 | crossAxisCount: 4, 83 | mainAxisSpacing: 4, 84 | crossAxisSpacing: 4, 85 | children: const [ 86 | StaggeredGridTile.fit( 87 | crossAxisCellCount: 2, 88 | child: SizedBox( 89 | height: 200, 90 | child: Tile(index: 0), 91 | ), 92 | ), 93 | StaggeredGridTile.fit( 94 | crossAxisCellCount: 2, 95 | child: SizedBox( 96 | height: 100, 97 | child: Tile(index: 1), 98 | ), 99 | ), 100 | StaggeredGridTile.fit( 101 | crossAxisCellCount: 1, 102 | child: SizedBox( 103 | height: 100, 104 | child: Tile(index: 2), 105 | ), 106 | ), 107 | StaggeredGridTile.fit( 108 | crossAxisCellCount: 1, 109 | child: SizedBox( 110 | height: 100, 111 | child: Tile(index: 3), 112 | ), 113 | ), 114 | StaggeredGridTile.fit( 115 | crossAxisCellCount: 4, 116 | child: SizedBox( 117 | height: 200, 118 | child: Tile(index: 4), 119 | ), 120 | ), 121 | ], 122 | ), 123 | ), 124 | ); 125 | 126 | void _expectSize(int index, Size size) { 127 | expect(tester.getSize(find.text('$index')), equals(size)); 128 | } 129 | 130 | void _expectTopLeft(int index, Offset topLeft) { 131 | expect(tester.getTopLeft(find.text('$index')), equals(topLeft)); 132 | } 133 | 134 | const s1 = 197.0; 135 | const s2 = 398.0; 136 | const s3 = 599.0; 137 | const s4 = 800.0; 138 | _expectSize(0, const Size(s2, 200)); 139 | _expectSize(1, const Size(s2, 100)); 140 | _expectSize(2, const Size(s1, 100)); 141 | _expectSize(3, const Size(s1, 100)); 142 | _expectSize(4, const Size(s4, 200)); 143 | 144 | _expectTopLeft(0, const Offset(0, 0)); 145 | _expectTopLeft(1, const Offset(s2 + 4, 0)); 146 | _expectTopLeft(2, const Offset(s2 + 4, 104)); 147 | _expectTopLeft(3, const Offset(s3 + 4, 104)); 148 | _expectTopLeft(4, const Offset(0, 208)); 149 | }); 150 | } 151 | -------------------------------------------------------------------------------- /test/src/widgets/uniform_track_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_staggered_grid_view/src/widgets/uniform_track.dart'; 3 | import 'package:flutter_test/flutter_test.dart'; 4 | 5 | import '../../common.dart'; 6 | 7 | void main() { 8 | testWidgets('Should layout children following its direction', (tester) async { 9 | // Screen size is 800x600 in the test environment. 10 | await tester.pumpWidget( 11 | Align( 12 | alignment: Alignment.topLeft, 13 | child: Directionality( 14 | textDirection: TextDirection.ltr, 15 | child: UniformTrack( 16 | division: 4, 17 | direction: AxisDirection.right, 18 | children: const [ 19 | Tile(index: 0, height: 100), 20 | Tile(index: 1, height: 200), 21 | Tile(index: 2, height: 50), 22 | Tile(index: 3, height: 200), 23 | ], 24 | ), 25 | ), 26 | ), 27 | ); 28 | 29 | void _expectTopLeft(int index, Offset topLeft) { 30 | expect(tester.getTopLeft(find.text('$index')), equals(topLeft)); 31 | } 32 | 33 | _expectTopLeft(0, const Offset(0, 0)); 34 | _expectTopLeft(1, const Offset(200, 0)); 35 | _expectTopLeft(2, const Offset(400, 0)); 36 | _expectTopLeft(3, const Offset(600, 0)); 37 | 38 | await tester.pumpWidget( 39 | Align( 40 | alignment: Alignment.topLeft, 41 | child: Directionality( 42 | textDirection: TextDirection.ltr, 43 | child: UniformTrack( 44 | division: 4, 45 | direction: AxisDirection.down, 46 | children: const [ 47 | Tile(index: 0, width: 100), 48 | Tile(index: 1, width: 200), 49 | Tile(index: 2, width: 50), 50 | Tile(index: 3, width: 200), 51 | ], 52 | ), 53 | ), 54 | ), 55 | ); 56 | 57 | _expectTopLeft(0, const Offset(0, 0)); 58 | _expectTopLeft(1, const Offset(0, 150)); 59 | _expectTopLeft(2, const Offset(0, 300)); 60 | _expectTopLeft(3, const Offset(0, 450)); 61 | }); 62 | 63 | testWidgets('Children should be of the maximum size', (tester) async { 64 | // Screen size is 800x600 in the test environment. 65 | await tester.pumpWidget( 66 | Align( 67 | alignment: Alignment.topLeft, 68 | child: Directionality( 69 | textDirection: TextDirection.ltr, 70 | child: UniformTrack( 71 | division: 4, 72 | direction: AxisDirection.right, 73 | children: const [ 74 | Tile(index: 0, height: 100), 75 | Tile(index: 1, height: 200), 76 | Tile(index: 2, height: 50), 77 | Tile(index: 3, height: 200), 78 | ], 79 | ), 80 | ), 81 | ), 82 | ); 83 | 84 | void _expectSize(int index, Size size) { 85 | expect(tester.getSize(find.text('$index')), equals(size)); 86 | } 87 | 88 | for (int i = 0; i < 4; i++) { 89 | _expectSize(i, const Size(200, 200)); 90 | } 91 | 92 | await tester.pumpWidget( 93 | Align( 94 | alignment: Alignment.topLeft, 95 | child: Directionality( 96 | textDirection: TextDirection.ltr, 97 | child: UniformTrack( 98 | division: 4, 99 | direction: AxisDirection.down, 100 | children: const [ 101 | Tile(index: 0, width: 100), 102 | Tile(index: 1, width: 200), 103 | Tile(index: 2, width: 50), 104 | Tile(index: 3, width: 200), 105 | ], 106 | ), 107 | ), 108 | ), 109 | ); 110 | 111 | for (int i = 0; i < 4; i++) { 112 | _expectSize(i, const Size(200, 150)); 113 | } 114 | }); 115 | 116 | testWidgets('Should have a gap of the specified size between children', 117 | (tester) async { 118 | // Screen size is 800x600 in the test environment. 119 | await tester.pumpWidget( 120 | Align( 121 | alignment: Alignment.topLeft, 122 | child: Directionality( 123 | textDirection: TextDirection.ltr, 124 | child: UniformTrack( 125 | division: 4, 126 | spacing: 4, 127 | direction: AxisDirection.right, 128 | children: const [ 129 | Tile(index: 0, height: 100), 130 | Tile(index: 1, height: 200), 131 | Tile(index: 2, height: 50), 132 | Tile(index: 3, height: 200), 133 | ], 134 | ), 135 | ), 136 | ), 137 | ); 138 | 139 | void _expectTopLeft(int index, Offset topLeft) { 140 | expect(tester.getTopLeft(find.text('$index')), equals(topLeft)); 141 | } 142 | 143 | _expectTopLeft(0, const Offset(0, 0)); 144 | _expectTopLeft(1, const Offset(201, 0)); 145 | _expectTopLeft(2, const Offset(402, 0)); 146 | _expectTopLeft(3, const Offset(603, 0)); 147 | 148 | await tester.pumpWidget( 149 | Align( 150 | alignment: Alignment.topLeft, 151 | child: Directionality( 152 | textDirection: TextDirection.ltr, 153 | child: UniformTrack( 154 | division: 4, 155 | spacing: 4, 156 | direction: AxisDirection.down, 157 | children: const [ 158 | Tile(index: 0, width: 100), 159 | Tile(index: 1, width: 200), 160 | Tile(index: 2, width: 50), 161 | Tile(index: 3, width: 200), 162 | ], 163 | ), 164 | ), 165 | ), 166 | ); 167 | 168 | _expectTopLeft(0, const Offset(0, 0)); 169 | _expectTopLeft(1, const Offset(0, 151)); 170 | _expectTopLeft(2, const Offset(0, 302)); 171 | _expectTopLeft(3, const Offset(0, 453)); 172 | }); 173 | 174 | testWidgets( 175 | 'Should divide the available space following the division parameter', 176 | (tester) async { 177 | // Screen size is 800x600 in the test environment. 178 | await tester.pumpWidget( 179 | Align( 180 | alignment: Alignment.topLeft, 181 | child: Directionality( 182 | textDirection: TextDirection.ltr, 183 | child: UniformTrack( 184 | division: 4, 185 | direction: AxisDirection.right, 186 | children: const [ 187 | Tile(index: 0, height: 100), 188 | Tile(index: 1, height: 200), 189 | ], 190 | ), 191 | ), 192 | ), 193 | ); 194 | 195 | void _expectTopLeft(int index, Offset topLeft) { 196 | expect(tester.getTopLeft(find.text('$index')), equals(topLeft)); 197 | } 198 | 199 | void _expectSize(int index, Size size) { 200 | expect(tester.getSize(find.text('$index')), equals(size)); 201 | } 202 | 203 | for (int i = 0; i < 2; i++) { 204 | _expectSize(i, const Size(200, 200)); 205 | } 206 | 207 | _expectTopLeft(0, const Offset(0, 0)); 208 | _expectTopLeft(1, const Offset(200, 0)); 209 | 210 | await tester.pumpWidget( 211 | Align( 212 | alignment: Alignment.topLeft, 213 | child: Directionality( 214 | textDirection: TextDirection.ltr, 215 | child: UniformTrack( 216 | division: 4, 217 | direction: AxisDirection.down, 218 | children: const [ 219 | Tile(index: 0, width: 100), 220 | Tile(index: 1, width: 200), 221 | ], 222 | ), 223 | ), 224 | ), 225 | ); 226 | 227 | for (int i = 0; i < 2; i++) { 228 | _expectSize(i, const Size(200, 150)); 229 | } 230 | 231 | _expectTopLeft(0, const Offset(0, 0)); 232 | _expectTopLeft(1, const Offset(0, 150)); 233 | }); 234 | } 235 | --------------------------------------------------------------------------------