├── .github ├── FUNDING.yml ├── stale.yml └── workflows │ ├── checker.yml │ └── pub_publish.yml ├── .gitignore ├── .metadata ├── .vscode └── settings.json ├── CHANGELOG.md ├── CODEOWNERS ├── LICENSE ├── README-ZH.md ├── README.md ├── analysis_options.yaml ├── example ├── .flutter-plugins-dependencies ├── .gitignore ├── .metadata ├── README.md ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── example │ │ │ │ │ └── example │ │ │ │ │ └── MainActivity.kt │ │ │ └── res │ │ │ │ ├── drawable │ │ │ │ └── launch_background.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ └── values │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── ff_annotation_route_commands ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── .last_build_id │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Podfile │ ├── Podfile.lock │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ └── WorkspaceSettings.xcsettings │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── Runner-Bridging-Header.h ├── lib │ ├── common │ │ └── widget_builder.dart │ ├── example_route.dart │ ├── example_routes.dart │ ├── main.dart │ └── pages │ │ ├── chat_list_demo.dart │ │ ├── gridview_demo.dart │ │ ├── listview_demo.dart │ │ └── main_page.dart ├── macos │ ├── .gitignore │ ├── Flutter │ │ ├── Flutter-Debug.xcconfig │ │ ├── Flutter-Release.xcconfig │ │ └── GeneratedPluginRegistrant.swift │ ├── Podfile │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ └── xcshareddata │ │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── app_icon_1024.png │ │ │ ├── app_icon_128.png │ │ │ ├── app_icon_16.png │ │ │ ├── app_icon_256.png │ │ │ ├── app_icon_32.png │ │ │ ├── app_icon_512.png │ │ │ └── app_icon_64.png │ │ ├── Base.lproj │ │ └── MainMenu.xib │ │ ├── Configs │ │ ├── AppInfo.xcconfig │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ └── Warnings.xcconfig │ │ ├── DebugProfile.entitlements │ │ ├── Info.plist │ │ ├── MainFlutterWindow.swift │ │ └── Release.entitlements ├── pubspec.lock ├── pubspec.yaml ├── test │ └── widget_test.dart └── web │ ├── favicon.png │ ├── icons │ ├── Icon-192.png │ └── Icon-512.png │ ├── index.html │ └── manifest.json ├── lib ├── extended_list.dart └── src │ ├── rendering │ ├── sliver_fixed_extend_list.dart │ ├── sliver_grid.dart │ └── sliver_list.dart │ └── widgets │ ├── scroll_view.dart │ └── sliver.dart ├── pubspec.lock └── pubspec.yaml /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | #github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | #patreon: # Replace with a single Patreon username 5 | #open_collective: # Replace with a single Open Collective username 6 | #ko_fi: # Replace with a single Ko-fi username 7 | #tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | #community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | #liberapay: zmtzawqlp 10 | #issuehunt: # Replace with a single IssueHunt username 11 | #otechie: # Replace with a single Otechie username 12 | custom: http://zmtzawqlp.gitee.io/my_images/images/qrcode.png 13 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 60 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | # Label to use when marking an issue as stale 10 | staleLabel: wontfix 11 | # Comment to post when marking an issue as stale. Set to `false` to disable 12 | markComment: > 13 | This issue has been automatically marked as stale because it has not had 14 | recent activity. It will be closed if no further activity occurs. Thank you 15 | for your contributions. 16 | # Comment to post when closing a stale issue. Set to `false` to disable 17 | closeComment: true -------------------------------------------------------------------------------- /.github/workflows/checker.yml: -------------------------------------------------------------------------------- 1 | name: No Free usage issue checker 2 | 3 | on: 4 | issues: 5 | types: [opened, reopened] 6 | 7 | jobs: 8 | build: 9 | 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v2 14 | - name: Check issue actor 15 | uses: ./ 16 | with: 17 | repo: $GITHUB_REPOSITORY 18 | user: $GITHUB_ACTOR 19 | token: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/pub_publish.yml: -------------------------------------------------------------------------------- 1 | name: Pub Publish plugin 2 | 3 | on: workflow_dispatch 4 | 5 | jobs: 6 | publish: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v1 13 | - name: Publish 14 | uses: sakebook/actions-flutter-pub-publisher@v1.3.0 15 | with: 16 | credential: ${{ secrets.CREDENTIAL_JSON }} 17 | flutter_package: true 18 | skip_test: true 19 | dry_run: false 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .packages 28 | .pub-cache/ 29 | .pub/ 30 | build/ 31 | 32 | # Android related 33 | **/android/**/gradle-wrapper.jar 34 | **/android/.gradle 35 | **/android/captures/ 36 | **/android/gradlew 37 | **/android/gradlew.bat 38 | **/android/local.properties 39 | **/android/**/GeneratedPluginRegistrant.java 40 | 41 | # iOS/XCode related 42 | **/ios/**/*.mode1v3 43 | **/ios/**/*.mode2v3 44 | **/ios/**/*.moved-aside 45 | **/ios/**/*.pbxuser 46 | **/ios/**/*.perspectivev3 47 | **/ios/**/*sync/ 48 | **/ios/**/.sconsign.dblite 49 | **/ios/**/.tags* 50 | **/ios/**/.vagrant/ 51 | **/ios/**/DerivedData/ 52 | **/ios/**/Icon? 53 | **/ios/**/Pods/ 54 | **/ios/**/.symlinks/ 55 | **/ios/**/profile 56 | **/ios/**/xcuserdata 57 | **/ios/.generated/ 58 | **/ios/Flutter/App.framework 59 | **/ios/Flutter/Flutter.framework 60 | **/ios/Flutter/Flutter.podspec 61 | **/ios/Flutter/Generated.xcconfig 62 | **/ios/Flutter/app.flx 63 | **/ios/Flutter/app.zip 64 | **/ios/Flutter/flutter_assets/ 65 | **/ios/Flutter/flutter_export_environment.sh 66 | **/ios/ServiceDefinitions.json 67 | **/ios/Runner/GeneratedPluginRegistrant.* 68 | 69 | # Exceptions to above rules. 70 | !**/ios/**/default.mode1v3 71 | !**/ios/**/default.mode2v3 72 | !**/ios/**/default.pbxuser 73 | !**/ios/**/default.perspectivev3 74 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 75 | -------------------------------------------------------------------------------- /.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: 802bd5514f75c0bd0cf9636c908eec438816b9c2 8 | channel: master 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "dart.flutterSdkPath": "/Users/roott/Documents/Tools/flutter/master" 3 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 3.0.2 2 | 3 | * fix issue that SliverGeometry is not valid: The "maxPaintExtent" is less than the "paintExtent".(#11) 4 | 5 | ## 3.0.1 6 | 7 | * fix closeToTrailing hittest issue. 8 | 9 | 10 | ## 3.0.0 11 | 12 | * support null-safety 13 | 14 | ## 2.0.2 15 | 16 | * fix closeToTrailing issue. extended_list#6 17 | 18 | ## 2.0.1 19 | 20 | * fix viewport wrong index for SliverGrid #4. 21 | 22 | ## 2.0.0 23 | 24 | * breaking change: fix typo(fullCrossAxisExtend => fullCrossAxisExtent) 25 | 26 | ## 1.0.1 27 | 28 | * add miss dragStartBehavior 29 | 30 | ## 1.0.0 31 | 32 | * fix analysis_options. 33 | * support ScrollViewKeyboardDismissBehavior(breaking change 1.17.0) 34 | 35 | ## 0.1.5 36 | 37 | * fix histest when closeToTrailing is true. 38 | 39 | ## 0.1.4 40 | 41 | * add web demo. 42 | 43 | ## 0.1.3 44 | 45 | * fix issue about last one as foot. 46 | 47 | ## 0.1.2 48 | 49 | * add license. 50 | 51 | ## 0.1.1 52 | 53 | * reduce description. 54 | 55 | ## 0.1.0 56 | 57 | * first release. 58 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @zmtzawqlp 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 zmtzawqlp 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. -------------------------------------------------------------------------------- /README-ZH.md: -------------------------------------------------------------------------------- 1 | # extended_list 2 | 3 | [![pub package](https://img.shields.io/pub/v/extended_list.svg)](https://pub.dartlang.org/packages/extended_list) [![GitHub stars](https://img.shields.io/github/stars/fluttercandies/extended_list)](https://github.com/fluttercandies/extended_list/stargazers) [![GitHub forks](https://img.shields.io/github/forks/fluttercandies/extended_list)](https://github.com/fluttercandies/extended_list/network) [![GitHub license](https://img.shields.io/github/license/fluttercandies/extended_list)](https://github.com/fluttercandies/extended_list/blob/master/LICENSE) [![GitHub issues](https://img.shields.io/github/issues/fluttercandies/extended_list)](https://github.com/fluttercandies/extended_list/issues) ![FlutterCandies QQ 群](https://img.shields.io/badge/dynamic/yaml?url=https%3A%2F%2Fraw.githubusercontent.com%2Ffluttercandies%2F.github%2Frefs%2Fheads%2Fmain%2Fdata.yml&query=%24.qq_group_number&label=QQ%E7%BE%A4&logo=qq&color=1DACE8) 4 | 5 | Language: [English](README.md) | 中文简体 6 | 7 | 扩展(ListView/GridView) 支持追踪列表元素回收/Viewport 的 index 改变,最后一个元素为 loadmore/no more 元素时候的特殊布局,以及针对 reverse 为 true 时候布局靠近底部的布局。 8 | 9 | [Web demo for ExtendedList](https://fluttercandies.github.io/extended_list/) 10 | 11 | - [extended_list](#extended_list) 12 | - [使用](#使用) 13 | - [列表元素回收](#列表元素回收) 14 | - [ViewportBuilder](#viewportbuilder) 15 | - [LastChildLayoutTypeBuilder](#lastchildlayouttypebuilder) 16 | - [CloseToTrailing](#closetotrailing) 17 | 18 | ## 使用 19 | 20 | - 在 pubspec.yaml 中增加库引用 21 | 22 | ```yaml 23 | dependencies: 24 | extended_list: any 25 | ``` 26 | 27 | - 导入库 28 | 29 | ```dart 30 | 31 | import 'package:extended_list/extended_list.dart'; 32 | 33 | ``` 34 | 35 | ## 列表元素回收 36 | 37 | 追踪列表元素回收,你可以在这个时刻回收一些内存,比如图片的内存缓存。 38 | 39 | [更多详情](https://github.com/fluttercandies/extended_image/blob/e1577bc4d0b57c725110a9d886703b98a72772b5/example/lib/pages/photo_view_demo.dart#L91) 40 | 41 | ```dart 42 | ExtendedListView.builder( 43 | extendedListDelegate: ExtendedListDelegate( 44 | collectGarbage: (List garbages) { 45 | print("collect garbage : $garbages"); 46 | },), 47 | ``` 48 | 49 | ## ViewportBuilder 50 | 51 | 追踪进入 Viewport 的列表元素的 index(即你看到的可视区域,并不包括缓存距离) 52 | 53 | ```dart 54 | ExtendedListView.builder( 55 | extendedListDelegate: ExtendedListDelegate( 56 | viewportBuilder: (int firstIndex, int lastIndex) { 57 | print("viewport : [$firstIndex,$lastIndex]"); 58 | }), 59 | ``` 60 | 61 | ## LastChildLayoutTypeBuilder 62 | 63 | 为最后一个元素创建特殊布局,这主要是用在将最后一个元素作为 loadmore/no more 的时候。 64 | 65 | ![img](https://github.com/fluttercandies/Flutter_Candies/blob/master/gif/extended_list/gridview.gif) 66 | 67 | ![img](https://github.com/fluttercandies/Flutter_Candies/blob/master/gif/extended_list/listview.gif) 68 | 69 | ```dart 70 | enum LastChildLayoutType { 71 | /// 普通的 72 | none, 73 | 74 | /// 将最后一个元素绘制在最大主轴Item之后,并且使用横轴大小最为layout size 75 | /// 主要使用在[ExtendedGridView] and [WaterfallFlow]中,最后一个元素作为loadmore/no more元素的时候。 76 | fullCrossAxisExtent, 77 | 78 | /// 将最后一个child绘制在trailing of viewport,并且使用横轴大小最为layout size 79 | /// 这种常用于最后一个元素作为loadmore/no more元素,并且列表元素没有充满整个viewport的时候 80 | /// 如果列表元素充满viewport,那么效果跟fullCrossAxisExtent一样 81 | foot, 82 | } 83 | 84 | ExtendedListView.builder( 85 | extendedListDelegate: ExtendedListDelegate( 86 | lastChildLayoutTypeBuilder: (index) => index == length 87 | ? LastChildLayoutType.foot 88 | : LastChildLayoutType.none, 89 | ), 90 | ``` 91 | 92 | ## CloseToTrailing 93 | 94 | 当 reverse 设置为 true 的时候,布局会变成如下。常用于聊天列表,新的会话会被插入 0 的位置,但是当会话没有充满 viewport 的时候,下面的布局不是我们想要的。 95 | 96 | ``` 97 | trailing 98 | ----------------- 99 | | | 100 | | | 101 | | item2 | 102 | | item1 | 103 | | item0 | 104 | ----------------- 105 | leading 106 | ``` 107 | 108 | 为了解决这个问题,你可以设置 closeToTrailing 为 true, 布局将变成如下 109 | 该属性同时支持[ExtendedGridView],[ExtendedList],[WaterfallFlow]。 110 | 当然如果 reverse 如果不为 ture,你设置这个属性依然会生效,没满 viewport 的时候布局会紧靠 trailing 111 | 112 | ``` 113 | trailing 114 | ----------------- 115 | | item2 | 116 | | item1 | 117 | | item0 | 118 | | | 119 | | | 120 | ----------------- 121 | leading 122 | ``` 123 | 124 | ```dart 125 | ExtendedListView.builder( 126 | reverse: true, 127 | extendedListDelegate: ExtendedListDelegate(closeToTrailing: true), 128 | ``` 129 | ## ☕️Buy me a coffee 130 | 131 | ![img](http://zmtzawqlp.gitee.io/my_images/images/qrcode.png) 132 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # extended_list 2 | 3 | [![pub package](https://img.shields.io/pub/v/extended_list.svg)](https://pub.dartlang.org/packages/extended_list) [![GitHub stars](https://img.shields.io/github/stars/fluttercandies/extended_list)](https://github.com/fluttercandies/extended_list/stargazers) [![GitHub forks](https://img.shields.io/github/forks/fluttercandies/extended_list)](https://github.com/fluttercandies/extended_list/network) [![GitHub license](https://img.shields.io/github/license/fluttercandies/extended_list)](https://github.com/fluttercandies/extended_list/blob/master/LICENSE) [![GitHub issues](https://img.shields.io/github/issues/fluttercandies/extended_list)](https://github.com/fluttercandies/extended_list/issues) ![FlutterCandies QQ 群](https://img.shields.io/badge/dynamic/yaml?url=https%3A%2F%2Fraw.githubusercontent.com%2Ffluttercandies%2F.github%2Frefs%2Fheads%2Fmain%2Fdata.yml&query=%24.qq_group_number&label=QQ%E7%BE%A4&logo=qq&color=1DACE8) 4 | 5 | Language: English | [中文简体](README-ZH.md) 6 | 7 | extended list(ListView/GridView) support track collect garbage of children/viewport indexes, build lastChild as special child in the case that it is loadmore/no more item and enable to layout close to trailing. 8 | 9 | [Web demo for ExtendedList](https://fluttercandies.github.io/extended_list/) 10 | 11 | - [extended_list](#extended_list) 12 | - [Use](#use) 13 | - [CollectGarbage](#collectgarbage) 14 | - [ViewportBuilder](#viewportbuilder) 15 | - [LastChildLayoutTypeBuilder](#lastchildlayouttypebuilder) 16 | - [CloseToTrailing](#closetotrailing) 17 | 18 | ## Use 19 | 20 | * add library to your pubspec.yaml 21 | 22 | ```yaml 23 | 24 | dependencies: 25 | extended_list: any 26 | 27 | ``` 28 | * import library in dart file 29 | 30 | ```dart 31 | 32 | import 'package:extended_list/extended_list.dart'; 33 | 34 | ``` 35 | 36 | ## CollectGarbage 37 | 38 | track the indexes are collect, you can collect garbage at that monment(for example Image cache) 39 | 40 | [more detail](https://github.com/fluttercandies/extended_image/blob/e1577bc4d0b57c725110a9d886703b98a72772b5/example/lib/pages/photo_view_demo.dart#L91) 41 | 42 | ```dart 43 | ExtendedListView.builder( 44 | extendedListDelegate: ExtendedListDelegate( 45 | collectGarbage: (List garbages) { 46 | print("collect garbage : $garbages"); 47 | },), 48 | ``` 49 | 50 | ## ViewportBuilder 51 | 52 | track the indexes go into the viewport, it's not include cache extent. 53 | 54 | ```dart 55 | ExtendedListView.builder( 56 | extendedListDelegate: ExtendedListDelegate( 57 | viewportBuilder: (int firstIndex, int lastIndex) { 58 | print("viewport : [$firstIndex,$lastIndex]"); 59 | }), 60 | ``` 61 | 62 | ## LastChildLayoutTypeBuilder 63 | 64 | build lastChild as special child in the case that it is loadmore/no more item. 65 | 66 | ![img](https://github.com/fluttercandies/Flutter_Candies/blob/master/gif/extended_list/gridview.gif) 67 | 68 | ![img](https://github.com/fluttercandies/Flutter_Candies/blob/master/gif/extended_list/listview.gif) 69 | 70 | ```dart 71 | enum LastChildLayoutType { 72 | /// as default child 73 | none, 74 | 75 | /// follow max child trailing layout offset and layout with full cross axis extent 76 | /// last child as loadmore item/no more item in [ExtendedGridView] and [WaterfallFlow] 77 | /// with full cross axis extend 78 | fullCrossAxisExtent, 79 | 80 | /// as foot at trailing and layout with full cross axis extend 81 | /// show no more item at trailing when children are not full of viewport 82 | /// if children is full of viewport, it's the same as fullCrossAxisExtent 83 | foot, 84 | } 85 | 86 | ExtendedListView.builder( 87 | extendedListDelegate: ExtendedListDelegate( 88 | lastChildLayoutTypeBuilder: (index) => index == length 89 | ? LastChildLayoutType.foot 90 | : LastChildLayoutType.none, 91 | ), 92 | ``` 93 | 94 | ## CloseToTrailing 95 | 96 | when reverse property of List is true, layout is as following. 97 | it likes chat list, and new session will insert to zero index. 98 | but it's not right when items are not full of viewport. 99 | 100 | ``` 101 | trailing 102 | ----------------- 103 | | | 104 | | | 105 | | item2 | 106 | | item1 | 107 | | item0 | 108 | ----------------- 109 | leading 110 | ``` 111 | 112 | to solve it, you could set closeToTrailing to true, layout is as following. 113 | support [ExtendedGridView],[ExtendedList],[WaterfallFlow]. 114 | and it also works when reverse is flase, layout will close to trailing. 115 | 116 | ``` 117 | trailing 118 | ----------------- 119 | | item2 | 120 | | item1 | 121 | | item0 | 122 | | | 123 | | | 124 | ----------------- 125 | leading 126 | ``` 127 | 128 | ```dart 129 | ExtendedListView.builder( 130 | reverse: true, 131 | extendedListDelegate: ExtendedListDelegate(closeToTrailing: true), 132 | ``` 133 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # Specify analysis options. 2 | # 3 | # Until there are meta linter rules, each desired lint must be explicitly enabled. 4 | # See: https://github.com/dart-lang/linter/issues/288 5 | # 6 | # For a list of lints, see: http://dart-lang.github.io/linter/lints/ 7 | # See the configuration guide for more 8 | # https://github.com/dart-lang/sdk/tree/master/pkg/analyzer#configuring-the-analyzer 9 | # 10 | # There are other similar analysis options files in the flutter repos, 11 | # which should be kept in sync with this file: 12 | # 13 | # - analysis_options.yaml (this file) 14 | # - packages/flutter/lib/analysis_options_user.yaml 15 | # - https://github.com/flutter/plugins/blob/master/analysis_options.yaml 16 | # - https://github.com/flutter/engine/blob/master/analysis_options.yaml 17 | # 18 | # This file contains the analysis options used by Flutter tools, such as IntelliJ, 19 | # Android Studio, and the `flutter analyze` command. 20 | 21 | analyzer: 22 | strong-mode: 23 | implicit-casts: false 24 | implicit-dynamic: false 25 | errors: 26 | # treat missing required parameters as a warning (not a hint) 27 | missing_required_param: warning 28 | # treat missing returns as a warning (not a hint) 29 | missing_return: warning 30 | # allow having TODOs in the code 31 | todo: ignore 32 | # Ignore analyzer hints for updating pubspecs when using Future or 33 | # Stream and not importing dart:async 34 | # Please see https://github.com/flutter/flutter/pull/24528 for details. 35 | sdk_version_async_exported_from_core: ignore 36 | # exclude: 37 | # - "bin/cache/**" 38 | # # the following two are relative to the stocks example and the flutter package respectively 39 | # # see https://github.com/dart-lang/sdk/issues/28463 40 | # - "lib/i18n/messages_*.dart" 41 | # - "lib/src/http/**" 42 | 43 | linter: 44 | rules: 45 | # these rules are documented on and in the same order as 46 | # the Dart Lint rules page to make maintenance easier 47 | # https://github.com/dart-lang/linter/blob/master/example/all.yaml 48 | - always_declare_return_types 49 | - always_put_control_body_on_new_line 50 | # - always_put_required_named_parameters_first # we prefer having parameters in the same order as fields https://github.com/flutter/flutter/issues/10219 51 | - always_require_non_null_named_parameters 52 | - always_specify_types 53 | - annotate_overrides 54 | # - avoid_annotating_with_dynamic # conflicts with always_specify_types 55 | # - avoid_as # required for implicit-casts: true 56 | - avoid_bool_literals_in_conditional_expressions 57 | # - avoid_catches_without_on_clauses # we do this commonly 58 | # - avoid_catching_errors # we do this commonly 59 | - avoid_classes_with_only_static_members 60 | # - avoid_double_and_int_checks # only useful when targeting JS runtime 61 | - avoid_empty_else 62 | # - avoid_equals_and_hash_code_on_mutable_classes # not yet tested 63 | - avoid_field_initializers_in_const_classes 64 | - avoid_function_literals_in_foreach_calls 65 | # - avoid_implementing_value_types # not yet tested 66 | - avoid_init_to_null 67 | # - avoid_js_rounded_ints # only useful when targeting JS runtime 68 | - avoid_null_checks_in_equality_operators 69 | # - avoid_positional_boolean_parameters # not yet tested 70 | # - avoid_print # not yet tested 71 | # - avoid_private_typedef_functions # we prefer having typedef (discussion in https://github.com/flutter/flutter/pull/16356) 72 | # - avoid_redundant_argument_values # not yet tested 73 | - avoid_relative_lib_imports 74 | - avoid_renaming_method_parameters 75 | - avoid_return_types_on_setters 76 | # - avoid_returning_null # there are plenty of valid reasons to return null 77 | # - avoid_returning_null_for_future # not yet tested 78 | - avoid_returning_null_for_void 79 | # - avoid_returning_this # there are plenty of valid reasons to return this 80 | # - avoid_setters_without_getters # not yet tested 81 | # - avoid_shadowing_type_parameters # not yet tested 82 | - avoid_single_cascade_in_expression_statements 83 | - avoid_slow_async_io 84 | - avoid_types_as_parameter_names 85 | # - avoid_types_on_closure_parameters # conflicts with always_specify_types 86 | # - avoid_unnecessary_containers # not yet tested 87 | - avoid_unused_constructor_parameters 88 | - avoid_void_async 89 | # - avoid_web_libraries_in_flutter # not yet tested 90 | - await_only_futures 91 | - camel_case_extensions 92 | - camel_case_types 93 | - cancel_subscriptions 94 | # - cascade_invocations # not yet tested 95 | # - close_sinks # not reliable enough 96 | # - comment_references # blocked on https://github.com/flutter/flutter/issues/20765 97 | # - constant_identifier_names # needs an opt-out https://github.com/dart-lang/linter/issues/204 98 | - control_flow_in_finally 99 | # - curly_braces_in_flow_control_structures # not yet tested 100 | # - diagnostic_describe_all_properties # not yet tested 101 | - directives_ordering 102 | - empty_catches 103 | - empty_constructor_bodies 104 | - empty_statements 105 | # - file_names # not yet tested 106 | - flutter_style_todos 107 | - hash_and_equals 108 | - implementation_imports 109 | # - invariant_booleans # too many false positives: https://github.com/dart-lang/linter/issues/811 110 | - iterable_contains_unrelated_type 111 | # - join_return_with_assignment # not yet tested 112 | - library_names 113 | - library_prefixes 114 | # - lines_longer_than_80_chars # not yet tested 115 | - list_remove_unrelated_type 116 | # - literal_only_boolean_expressions # too many false positives: https://github.com/dart-lang/sdk/issues/34181 117 | # - missing_whitespace_between_adjacent_strings # not yet tested 118 | - no_adjacent_strings_in_list 119 | - no_duplicate_case_values 120 | # - no_logic_in_create_state # not yet tested 121 | # - no_runtimeType_toString # not yet tested 122 | - non_constant_identifier_names 123 | # - null_closures # not yet tested 124 | # - omit_local_variable_types # opposite of always_specify_types 125 | # - one_member_abstracts # too many false positives 126 | # - only_throw_errors # https://github.com/flutter/flutter/issues/5792 127 | - overridden_fields 128 | - package_api_docs 129 | - package_names 130 | - package_prefixed_library_names 131 | # - parameter_assignments # we do this commonly 132 | - prefer_adjacent_string_concatenation 133 | - prefer_asserts_in_initializer_lists 134 | # - prefer_asserts_with_message # not yet tested 135 | - prefer_collection_literals 136 | - prefer_conditional_assignment 137 | - prefer_const_constructors 138 | - prefer_const_constructors_in_immutables 139 | - prefer_const_declarations 140 | - prefer_const_literals_to_create_immutables 141 | # - prefer_constructors_over_static_methods # not yet tested 142 | - prefer_contains 143 | # - prefer_double_quotes # opposite of prefer_single_quotes 144 | - prefer_equal_for_default_values 145 | # - prefer_expression_function_bodies # conflicts with https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#consider-using--for-short-functions-and-methods 146 | - prefer_final_fields 147 | - prefer_final_in_for_each 148 | - prefer_final_locals 149 | - prefer_for_elements_to_map_fromIterable 150 | - prefer_foreach 151 | # - prefer_function_declarations_over_variables # not yet tested 152 | - prefer_generic_function_type_aliases 153 | - prefer_if_elements_to_conditional_expressions 154 | - prefer_if_null_operators 155 | - prefer_initializing_formals 156 | - prefer_inlined_adds 157 | # - prefer_int_literals # not yet tested 158 | # - prefer_interpolation_to_compose_strings # not yet tested 159 | - prefer_is_empty 160 | - prefer_is_not_empty 161 | - prefer_is_not_operator 162 | - prefer_iterable_whereType 163 | # - prefer_mixin # https://github.com/dart-lang/language/issues/32 164 | # - prefer_null_aware_operators # disable until NNBD, see https://github.com/flutter/flutter/pull/32711#issuecomment-492930932 165 | # - prefer_relative_imports # not yet tested 166 | - prefer_single_quotes 167 | - prefer_spread_collections 168 | - prefer_typing_uninitialized_variables 169 | - prefer_void_to_null 170 | # - provide_deprecation_message # not yet tested 171 | # - public_member_api_docs # enabled on a case-by-case basis; see e.g. packages/analysis_options.yaml 172 | - recursive_getters 173 | - slash_for_doc_comments 174 | # - sort_child_properties_last # not yet tested 175 | - sort_constructors_first 176 | - sort_pub_dependencies 177 | - sort_unnamed_constructors_first 178 | - test_types_in_equals 179 | - throw_in_finally 180 | # - type_annotate_public_apis # subset of always_specify_types 181 | - type_init_formals 182 | # - unawaited_futures # too many false positives 183 | # - unnecessary_await_in_return # not yet tested 184 | - unnecessary_brace_in_string_interps 185 | - unnecessary_const 186 | # - unnecessary_final # conflicts with prefer_final_locals 187 | - unnecessary_getters_setters 188 | # - unnecessary_lambdas # has false positives: https://github.com/dart-lang/linter/issues/498 189 | - unnecessary_new 190 | - unnecessary_null_aware_assignments 191 | - unnecessary_null_in_if_null_operators 192 | - unnecessary_overrides 193 | - unnecessary_parenthesis 194 | - unnecessary_statements 195 | - unnecessary_string_interpolations 196 | - unnecessary_this 197 | - unrelated_type_equality_checks 198 | # - unsafe_html # not yet tested 199 | - use_full_hex_values_for_flutter_colors 200 | # - use_function_type_syntax_for_parameters # not yet tested 201 | # - use_key_in_widget_constructors # not yet tested 202 | - use_rethrow_when_possible 203 | # - use_setters_to_change_properties # not yet tested 204 | # - use_string_buffers # has false positives: https://github.com/dart-lang/sdk/issues/34182 205 | # - use_to_and_as_if_applicable # has false positives, so we prefer to catch this by code-review 206 | - valid_regexps 207 | - void_checks 208 | -------------------------------------------------------------------------------- /example/.flutter-plugins-dependencies: -------------------------------------------------------------------------------- 1 | {"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"url_launcher","path":"/Users/roott/.pub-cache/hosted/pub.flutter-io.cn/url_launcher-5.3.0/","dependencies":[]}],"android":[{"name":"url_launcher","path":"/Users/roott/.pub-cache/hosted/pub.flutter-io.cn/url_launcher-5.3.0/","dependencies":[]}],"macos":[],"linux":[],"windows":[],"web":[{"name":"url_launcher_web","path":"/Users/roott/.pub-cache/hosted/pub.flutter-io.cn/url_launcher_web-0.1.3+2/","dependencies":[]}]},"dependencyGraph":[{"name":"url_launcher","dependencies":["url_launcher_web"]},{"name":"url_launcher_web","dependencies":[]}],"date_created":"2020-09-11 16:17:49.497124","version":"1.22.0-2.0.pre.161"} -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .packages 28 | .pub-cache/ 29 | .pub/ 30 | /build/ 31 | 32 | # Web related 33 | lib/generated_plugin_registrant.dart 34 | 35 | # Exceptions to above rules. 36 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 37 | -------------------------------------------------------------------------------- /example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 802bd5514f75c0bd0cf9636c908eec438816b9c2 8 | channel: master 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # example 2 | 3 | A new Flutter project. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) 13 | 14 | For help getting started with Flutter, view our 15 | [online documentation](https://flutter.dev/docs), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 28 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | lintOptions { 36 | disable 'InvalidPackage' 37 | } 38 | 39 | defaultConfig { 40 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 41 | applicationId "com.example.example" 42 | minSdkVersion 16 43 | targetSdkVersion 28 44 | versionCode flutterVersionCode.toInteger() 45 | versionName flutterVersionName 46 | } 47 | 48 | buildTypes { 49 | release { 50 | // TODO: Add your own signing config for the release build. 51 | // Signing with the debug keys for now, so `flutter run --release` works. 52 | signingConfig signingConfigs.debug 53 | } 54 | } 55 | } 56 | 57 | flutter { 58 | source '../..' 59 | } 60 | 61 | dependencies { 62 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 63 | } 64 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 8 | 12 | 19 | 23 | 27 | 32 | 36 | 37 | 38 | 39 | 40 | 41 | 43 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/com/example/example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.example 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_list/b67fc3f72c798fbbfc44f0628eef53d549b72809/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_list/b67fc3f72c798fbbfc44f0628eef53d549b72809/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_list/b67fc3f72c798fbbfc44f0628eef53d549b72809/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_list/b67fc3f72c798fbbfc44f0628eef53d549b72809/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_list/b67fc3f72c798fbbfc44f0628eef53d549b72809/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.5.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | -------------------------------------------------------------------------------- /example/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-5.6.2-all.zip 7 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /example/ff_annotation_route_commands: -------------------------------------------------------------------------------- 1 | -s -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /example/ios/Flutter/.last_build_id: -------------------------------------------------------------------------------- 1 | 0dcce3b8c9ccb1eae156d2b2e510a627 -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 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 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | end 36 | 37 | post_install do |installer| 38 | installer.pods_project.targets.each do |target| 39 | flutter_additional_ios_build_settings(target) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /example/ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Flutter (1.0.0) 3 | - url_launcher (0.0.1): 4 | - Flutter 5 | 6 | DEPENDENCIES: 7 | - Flutter (from `Flutter`) 8 | - url_launcher (from `.symlinks/plugins/url_launcher/ios`) 9 | 10 | EXTERNAL SOURCES: 11 | Flutter: 12 | :path: Flutter 13 | url_launcher: 14 | :path: ".symlinks/plugins/url_launcher/ios" 15 | 16 | SPEC CHECKSUMS: 17 | Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c 18 | url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef 19 | 20 | PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c 21 | 22 | COCOAPODS: 1.10.0 23 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 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 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @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 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_list/b67fc3f72c798fbbfc44f0628eef53d549b72809/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_list/b67fc3f72c798fbbfc44f0628eef53d549b72809/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_list/b67fc3f72c798fbbfc44f0628eef53d549b72809/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_list/b67fc3f72c798fbbfc44f0628eef53d549b72809/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_list/b67fc3f72c798fbbfc44f0628eef53d549b72809/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_list/b67fc3f72c798fbbfc44f0628eef53d549b72809/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_list/b67fc3f72c798fbbfc44f0628eef53d549b72809/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_list/b67fc3f72c798fbbfc44f0628eef53d549b72809/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_list/b67fc3f72c798fbbfc44f0628eef53d549b72809/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_list/b67fc3f72c798fbbfc44f0628eef53d549b72809/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_list/b67fc3f72c798fbbfc44f0628eef53d549b72809/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_list/b67fc3f72c798fbbfc44f0628eef53d549b72809/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_list/b67fc3f72c798fbbfc44f0628eef53d549b72809/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_list/b67fc3f72c798fbbfc44f0628eef53d549b72809/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_list/b67fc3f72c798fbbfc44f0628eef53d549b72809/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_list/b67fc3f72c798fbbfc44f0628eef53d549b72809/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_list/b67fc3f72c798fbbfc44f0628eef53d549b72809/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_list/b67fc3f72c798fbbfc44f0628eef53d549b72809/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | example 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 | 45 | 46 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /example/lib/common/widget_builder.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | /// 5 | /// create by zmtzawqlp on 2019/11/23 6 | /// 7 | Widget buildLastWidget({required BuildContext context, required bool hasMore}) { 8 | return Container( 9 | alignment: Alignment.center, 10 | color: Colors.grey.withOpacity(0.2), 11 | //margin: EdgeInsets.only(top: 5.0), 12 | padding: EdgeInsets.symmetric(vertical: kIsWeb ? 10.0 : 5.0), 13 | child: Text( 14 | hasMore ? 'loading...' : 'no more', 15 | style: TextStyle(color: Theme.of(context).primaryColor), 16 | )); 17 | } 18 | -------------------------------------------------------------------------------- /example/lib/example_route.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY MANUALLY 2 | // ************************************************************************** 3 | // Auto generated by https://github.com/fluttercandies/ff_annotation_route 4 | // ************************************************************************** 5 | // ignore_for_file: prefer_const_literals_to_create_immutables,unused_local_variable,unused_import 6 | import 'package:ff_annotation_route_library/ff_annotation_route_library.dart'; 7 | import 'package:flutter/widgets.dart'; 8 | 9 | import 'pages/chat_list_demo.dart'; 10 | import 'pages/gridview_demo.dart'; 11 | import 'pages/listview_demo.dart'; 12 | import 'pages/main_page.dart'; 13 | 14 | FFRouteSettings getRouteSettings({ 15 | required String name, 16 | Map? arguments, 17 | PageBuilder? notFoundPageBuilder, 18 | }) { 19 | final Map safeArguments = 20 | arguments ?? const {}; 21 | switch (name) { 22 | case 'fluttercandies://chatlist': 23 | return FFRouteSettings( 24 | name: name, 25 | arguments: arguments, 26 | builder: () => ChatListDemo(), 27 | routeName: 'ChatList', 28 | description: 29 | 'how to build layout(reverse=true) close to trailing when children are not full of viewport.', 30 | ); 31 | case 'fluttercandies://gridview': 32 | return FFRouteSettings( 33 | name: name, 34 | arguments: arguments, 35 | builder: () => GridViewDemo(), 36 | routeName: 'GridView', 37 | description: 38 | 'show no more/loadmore at trailing when children are not full of viewport.', 39 | ); 40 | case 'fluttercandies://listview': 41 | return FFRouteSettings( 42 | name: name, 43 | arguments: arguments, 44 | builder: () => ListViewDemo(), 45 | routeName: 'ListView', 46 | description: 47 | 'show no more item at trailing when children are not full of viewport.', 48 | ); 49 | case 'fluttercandies://mainpage': 50 | return FFRouteSettings( 51 | name: name, 52 | arguments: arguments, 53 | builder: () => MainPage(), 54 | routeName: 'MainPage', 55 | ); 56 | default: 57 | return FFRouteSettings( 58 | name: FFRoute.notFoundName, 59 | routeName: FFRoute.notFoundRouteName, 60 | builder: notFoundPageBuilder ?? () => Container(), 61 | ); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /example/lib/example_routes.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY MANUALLY 2 | // ************************************************************************** 3 | // Auto generated by https://github.com/fluttercandies/ff_annotation_route 4 | // ************************************************************************** 5 | // ignore_for_file: prefer_const_literals_to_create_immutables,unused_local_variable,unused_import 6 | const List routeNames = [ 7 | 'fluttercandies://chatlist', 8 | 'fluttercandies://gridview', 9 | 'fluttercandies://listview', 10 | 'fluttercandies://mainpage', 11 | ]; 12 | 13 | class Routes { 14 | const Routes._(); 15 | 16 | /// 'how to build layout(reverse=true) close to trailing when children are not full of viewport.' 17 | /// 18 | /// [name] : 'fluttercandies://chatlist' 19 | /// 20 | /// [routeName] : 'ChatList' 21 | /// 22 | /// [description] : 'how to build layout(reverse=true) close to trailing when children are not full of viewport.' 23 | static const String fluttercandiesChatlist = 'fluttercandies://chatlist'; 24 | 25 | /// 'show no more/loadmore at trailing when children are not full of viewport.' 26 | /// 27 | /// [name] : 'fluttercandies://gridview' 28 | /// 29 | /// [routeName] : 'GridView' 30 | /// 31 | /// [description] : 'show no more/loadmore at trailing when children are not full of viewport.' 32 | static const String fluttercandiesGridview = 'fluttercandies://gridview'; 33 | 34 | /// 'show no more item at trailing when children are not full of viewport.' 35 | /// 36 | /// [name] : 'fluttercandies://listview' 37 | /// 38 | /// [routeName] : 'ListView' 39 | /// 40 | /// [description] : 'show no more item at trailing when children are not full of viewport.' 41 | static const String fluttercandiesListview = 'fluttercandies://listview'; 42 | 43 | /// 'MainPage' 44 | /// 45 | /// [name] : 'fluttercandies://mainpage' 46 | /// 47 | /// [routeName] : 'MainPage' 48 | /// 49 | /// [constructors] : 50 | /// 51 | /// MainPage : [] 52 | static const String fluttercandiesMainpage = 'fluttercandies://mainpage'; 53 | } 54 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:ff_annotation_route_library/ff_annotation_route_library.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import 'example_route.dart'; 5 | import 'example_routes.dart'; 6 | 7 | /// 8 | /// create by zmtzawqlp on 2019/11/23 9 | /// 10 | 11 | void main() => runApp(MyApp()); 12 | 13 | class MyApp extends StatelessWidget { 14 | // This widget is the root of your application. 15 | @override 16 | Widget build(BuildContext context) { 17 | return MaterialApp( 18 | title: 'ExtendedList Demo', 19 | debugShowCheckedModeBanner: false, 20 | theme: ThemeData( 21 | primarySwatch: Colors.blue, 22 | ), 23 | initialRoute: Routes.fluttercandiesMainpage, 24 | onGenerateRoute: (RouteSettings settings) { 25 | return onGenerateRoute( 26 | settings: settings, 27 | getRouteSettings: getRouteSettings, 28 | ); 29 | }, 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /example/lib/pages/chat_list_demo.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:ff_annotation_route_library/ff_annotation_route_library.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:extended_list/extended_list.dart'; 6 | 7 | /// 8 | /// create by zmtzawqlp on 2019/11/23 9 | /// 10 | 11 | @FFRoute( 12 | name: 'fluttercandies://chatlist', 13 | routeName: 'ChatList', 14 | description: 15 | 'how to build layout(reverse=true) close to trailing when children are not full of viewport.', 16 | ) 17 | class ChatListDemo extends StatefulWidget { 18 | @override 19 | _ChatListDemoState createState() => _ChatListDemoState(); 20 | } 21 | 22 | class _ChatListDemoState extends State { 23 | final List colors = []; 24 | final List sessions = [ 25 | 'I\'m session', 26 | 'I\'m session', 27 | 'I\'m session' 28 | ]; 29 | @override 30 | Widget build(BuildContext context) { 31 | return Scaffold( 32 | appBar: AppBar( 33 | title: const Text('ChatList'), 34 | ), 35 | body: ExtendedListView.builder( 36 | //itemExtent: 50.0, 37 | reverse: true, 38 | 39 | /// when reverse property of List is true, layout is as following. 40 | /// it likes chat list, and new session will insert to zero index 41 | /// but it's not right when items are not full of viewport. 42 | /// 43 | /// trailing 44 | /// ----------------- 45 | /// | | 46 | /// | | 47 | /// | item2 | 48 | /// | item1 | 49 | /// | item0 | 50 | /// ----------------- 51 | /// leading 52 | /// 53 | /// to solve it, you could set closeToTrailing to true, layout is as following. 54 | /// support [ExtendedGridView],[ExtendedList],[WaterfallFlow] 55 | /// it works not only reverse is true. 56 | /// 57 | /// trailing 58 | /// ----------------- 59 | /// | item2 | 60 | /// | item1 | 61 | /// | item0 | 62 | /// | | 63 | /// | | 64 | /// ----------------- 65 | /// leading 66 | /// 67 | extendedListDelegate: const ExtendedListDelegate(closeToTrailing: true), 68 | //gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2), 69 | itemBuilder: (BuildContext c, int index) { 70 | final Color color = getRandomColor(index); 71 | List children = [ 72 | Expanded( 73 | flex: 5, 74 | child: Container( 75 | height: 50.0, 76 | decoration: BoxDecoration( 77 | border: Border.all(color: Colors.black), 78 | color: getRandomColor(index)), 79 | alignment: Alignment.center, 80 | child: Text( 81 | '${sessions[index]} $index', 82 | style: TextStyle( 83 | color: color.computeLuminance() < 0.5 84 | ? Colors.white 85 | : Colors.black), 86 | ), 87 | ), 88 | ), 89 | Expanded(flex: 2, child: Container()), 90 | ]; 91 | if (index % 2 == 0) { 92 | children = children.reversed.toList(); 93 | } 94 | return GestureDetector( 95 | child: Row(children: children), 96 | onTap: () { 97 | print('click$index'); 98 | }, 99 | ); 100 | }, 101 | itemCount: sessions.length, 102 | ), 103 | floatingActionButton: FloatingActionButton( 104 | onPressed: () { 105 | setState(() { 106 | sessions.insert(0, 'I\'m session'); 107 | }); 108 | }, 109 | child: const Icon(Icons.add), 110 | ), 111 | ); 112 | } 113 | 114 | Color getRandomColor(int index) { 115 | if (index >= colors.length) { 116 | colors.add(Color.fromARGB(255, Random.secure().nextInt(255), 117 | Random.secure().nextInt(255), Random.secure().nextInt(255))); 118 | } 119 | 120 | return colors[index]; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /example/lib/pages/gridview_demo.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | import 'package:example/common/widget_builder.dart'; 3 | import 'package:ff_annotation_route_library/ff_annotation_route_library.dart'; 4 | 5 | import 'package:flutter/material.dart'; 6 | import 'package:extended_list/extended_list.dart'; 7 | 8 | /// 9 | /// create by zmtzawqlp on 2019/11/23 10 | /// 11 | 12 | @FFRoute( 13 | name: 'fluttercandies://gridview', 14 | routeName: 'GridView', 15 | description: 16 | 'show no more/loadmore at trailing when children are not full of viewport.', 17 | ) 18 | class GridViewDemo extends StatefulWidget { 19 | @override 20 | _GridViewDemoState createState() => _GridViewDemoState(); 21 | } 22 | 23 | class _GridViewDemoState extends State { 24 | final List colors = []; 25 | int length = 5; 26 | bool get hasMore => length < 100; 27 | @override 28 | Widget build(BuildContext context) { 29 | return Scaffold( 30 | appBar: AppBar( 31 | // Here we take the value from the MyHomePage object that was created by 32 | // the App.build method, and use it to set our appbar title. 33 | title: const Text('GridView'), 34 | ), 35 | body: ExtendedGridView.builder( 36 | gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( 37 | crossAxisCount: 4, 38 | crossAxisSpacing: 5.0, 39 | mainAxisSpacing: 5.0, 40 | ), 41 | extendedListDelegate: ExtendedListDelegate( 42 | /// follow max child trailing layout offset and layout with full cross axis extend 43 | /// last child as loadmore item/no more item in [ExtendedGridView] and [WaterfallFlow] 44 | /// with full cross axis extend 45 | // LastChildLayoutType.fullCrossAxisExtend, 46 | 47 | /// as foot at trailing and layout with full cross axis extend 48 | /// show no more item at trailing when children are not full of viewport 49 | /// if children is full of viewport, it's the same as fullCrossAxisExtend 50 | // LastChildLayoutType.foot, 51 | lastChildLayoutTypeBuilder: (int index) => index == length 52 | ? LastChildLayoutType.foot 53 | : LastChildLayoutType.none, 54 | collectGarbage: (List garbages) { 55 | print('collect garbage : $garbages'); 56 | }, 57 | viewportBuilder: (int firstIndex, int lastIndex) { 58 | print('viewport : [$firstIndex,$lastIndex]'); 59 | }, 60 | ), 61 | itemBuilder: (BuildContext c, int index) { 62 | if (index == length) { 63 | if (hasMore) { 64 | //delay 2 seconds,see loadmore clearly 65 | Future.delayed(const Duration(seconds: 2), () { 66 | setState(() { 67 | length += 30; 68 | }); 69 | }); 70 | } 71 | return buildLastWidget(context: context, hasMore: hasMore); 72 | } 73 | final Color color = getRandomColor(index); 74 | 75 | return Container( 76 | decoration: BoxDecoration( 77 | border: Border.all(color: Colors.black), 78 | color: getRandomColor(index)), 79 | alignment: Alignment.center, 80 | child: Text( 81 | '$index', 82 | style: TextStyle( 83 | color: color.computeLuminance() < 0.5 84 | ? Colors.white 85 | : Colors.black), 86 | ), 87 | ); 88 | }, 89 | itemCount: length + 1, 90 | ), 91 | ); 92 | } 93 | 94 | Color getRandomColor(int index) { 95 | if (index >= colors.length) { 96 | colors.add(Color.fromARGB(255, Random.secure().nextInt(255), 97 | Random.secure().nextInt(255), Random.secure().nextInt(255))); 98 | } 99 | 100 | return colors[index]; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /example/lib/pages/listview_demo.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | import 'package:example/common/widget_builder.dart'; 3 | import 'package:ff_annotation_route_library/ff_annotation_route_library.dart'; 4 | 5 | import 'package:flutter/material.dart'; 6 | import 'package:extended_list/extended_list.dart'; 7 | 8 | /// 9 | /// create by zmtzawqlp on 2019/11/23 10 | /// 11 | 12 | @FFRoute( 13 | name: 'fluttercandies://listview', 14 | routeName: 'ListView', 15 | description: 16 | 'show no more item at trailing when children are not full of viewport.', 17 | ) 18 | class ListViewDemo extends StatefulWidget { 19 | @override 20 | _ListViewDemoState createState() => _ListViewDemoState(); 21 | } 22 | 23 | class _ListViewDemoState extends State { 24 | final List colors = []; 25 | int length = 5; 26 | bool get hasMore => length < 100; 27 | @override 28 | Widget build(BuildContext context) { 29 | return Scaffold( 30 | appBar: AppBar( 31 | title: const Text('ListView'), 32 | ), 33 | body: ExtendedListView.builder( 34 | extendedListDelegate: ExtendedListDelegate( 35 | 36 | /// follow max child trailing layout offset and layout with full cross axis extend 37 | /// last child as loadmore item/no more item in [ExtendedGridView] and [WaterfallFlow] 38 | /// with full cross axis extend 39 | // LastChildLayoutType.fullCrossAxisExtend, 40 | 41 | /// as foot at trailing and layout with full cross axis extend 42 | /// show no more item at trailing when children are not full of viewport 43 | /// if children is full of viewport, it's the same as fullCrossAxisExtend 44 | // LastChildLayoutType.foot, 45 | lastChildLayoutTypeBuilder: (int index) => index == length 46 | ? LastChildLayoutType.foot 47 | : LastChildLayoutType.none, 48 | collectGarbage: (List garbages) { 49 | print('collect garbage : $garbages'); 50 | }, 51 | viewportBuilder: (int firstIndex, int lastIndex) { 52 | print('viewport : [$firstIndex,$lastIndex]'); 53 | }), 54 | //itemExtent: 50.0, 55 | itemBuilder: (BuildContext c, int index) { 56 | if (index == length) { 57 | if (hasMore) { 58 | //delay 2 seconds,see loadmore clearly 59 | Future.delayed(const Duration(seconds: 2), () { 60 | setState(() { 61 | length += 30; 62 | }); 63 | }); 64 | } 65 | return buildLastWidget(context: context, hasMore: hasMore); 66 | } 67 | final Color color = getRandomColor(index); 68 | 69 | return Container( 70 | height: 50.0, 71 | decoration: BoxDecoration( 72 | border: Border.all(color: Colors.black), 73 | color: getRandomColor(index)), 74 | alignment: Alignment.center, 75 | child: Text( 76 | '$index', 77 | style: TextStyle( 78 | color: color.computeLuminance() < 0.5 79 | ? Colors.white 80 | : Colors.black), 81 | ), 82 | ); 83 | }, 84 | itemCount: length + 1, 85 | ), 86 | ); 87 | } 88 | 89 | Color getRandomColor(int index) { 90 | if (index >= colors.length) { 91 | colors.add(Color.fromARGB(255, Random.secure().nextInt(255), 92 | Random.secure().nextInt(255), Random.secure().nextInt(255))); 93 | } 94 | 95 | return colors[index]; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /example/lib/pages/main_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:ff_annotation_route_library/ff_annotation_route_library.dart'; 2 | 3 | /// 4 | /// create by zmtzawqlp on 2019/11/23 5 | /// 6 | 7 | import 'package:flutter/material.dart'; 8 | import 'package:url_launcher/url_launcher.dart'; 9 | 10 | import '../example_route.dart'; 11 | import '../example_routes.dart' as example_routes; 12 | 13 | @FFRoute( 14 | name: 'fluttercandies://mainpage', 15 | routeName: 'MainPage', 16 | ) 17 | class MainPage extends StatelessWidget { 18 | MainPage() { 19 | final List routeNames = []; 20 | routeNames.addAll(example_routes.routeNames); 21 | routeNames.remove('fluttercandies://mainpage'); 22 | routes.addAll(routeNames 23 | .map((String name) => getRouteSettings(name: name))); 24 | } 25 | final List routes = []; 26 | @override 27 | Widget build(BuildContext context) { 28 | return Scaffold( 29 | appBar: AppBar( 30 | // Here we take the value from the MyHomePage object that was created by 31 | // the App.build method, and use it to set our appbar title. 32 | title: const Text('ExtendedList'), 33 | actions: [ 34 | ButtonTheme( 35 | minWidth: 0.0, 36 | padding: const EdgeInsets.symmetric(horizontal: 10.0), 37 | child: TextButton( 38 | child: const Text( 39 | 'Github', 40 | style: TextStyle( 41 | decorationStyle: TextDecorationStyle.solid, 42 | decoration: TextDecoration.underline, 43 | color: Colors.white, 44 | ), 45 | ), 46 | onPressed: () { 47 | launch('https://github.com/fluttercandies/extended_list'); 48 | }, 49 | ), 50 | ), 51 | ButtonTheme( 52 | padding: const EdgeInsets.only(right: 10.0), 53 | minWidth: 0.0, 54 | child: TextButton( 55 | child: 56 | Image.network('https://pub.idqqimg.com/wpa/images/group.png'), 57 | onPressed: () { 58 | launch('https://jq.qq.com/?_wv=1027&k=5bcc0gy'); 59 | }, 60 | ), 61 | ) 62 | ], 63 | ), 64 | body: ListView.builder( 65 | itemBuilder: (BuildContext c, int index) { 66 | final FFRouteSettings page = routes[index]; 67 | return Container( 68 | margin: const EdgeInsets.all(20.0), 69 | child: GestureDetector( 70 | behavior: HitTestBehavior.translucent, 71 | child: Column( 72 | crossAxisAlignment: CrossAxisAlignment.start, 73 | children: [ 74 | Text( 75 | (index + 1).toString() + '.' + page.routeName!, 76 | //style: TextStyle(inherit: false), 77 | ), 78 | Text( 79 | page.description!, 80 | style: const TextStyle(color: Colors.grey), 81 | ) 82 | ], 83 | ), 84 | onTap: () { 85 | Navigator.pushNamed(context, routes[index].name!); 86 | }, 87 | )); 88 | }, 89 | itemCount: routes.length, 90 | ), 91 | ); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /example/macos/.gitignore: -------------------------------------------------------------------------------- 1 | # Flutter-related 2 | **/Flutter/ephemeral/ 3 | **/Pods/ 4 | 5 | # Xcode-related 6 | **/xcuserdata/ 7 | -------------------------------------------------------------------------------- /example/macos/Flutter/Flutter-Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/macos/Flutter/Flutter-Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/macos/Flutter/GeneratedPluginRegistrant.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | import FlutterMacOS 6 | import Foundation 7 | 8 | import url_launcher_macos 9 | 10 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 11 | UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) 12 | } 13 | -------------------------------------------------------------------------------- /example/macos/Podfile: -------------------------------------------------------------------------------- 1 | platform :osx, '10.11' 2 | 3 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 4 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 5 | 6 | project 'Runner', { 7 | 'Debug' => :debug, 8 | 'Profile' => :release, 9 | 'Release' => :release, 10 | } 11 | 12 | def parse_KV_file(file, separator='=') 13 | file_abs_path = File.expand_path(file) 14 | if !File.exists? file_abs_path 15 | return []; 16 | end 17 | pods_ary = [] 18 | skip_line_start_symbols = ["#", "/"] 19 | File.foreach(file_abs_path) { |line| 20 | next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } 21 | plugin = line.split(pattern=separator) 22 | if plugin.length == 2 23 | podname = plugin[0].strip() 24 | path = plugin[1].strip() 25 | podpath = File.expand_path("#{path}", file_abs_path) 26 | pods_ary.push({:name => podname, :path => podpath}); 27 | else 28 | puts "Invalid plugin specification: #{line}" 29 | end 30 | } 31 | return pods_ary 32 | end 33 | 34 | def pubspec_supports_macos(file) 35 | file_abs_path = File.expand_path(file) 36 | if !File.exists? file_abs_path 37 | return false; 38 | end 39 | File.foreach(file_abs_path) { |line| 40 | return true if line =~ /^\s*macos:/ 41 | } 42 | return false 43 | end 44 | 45 | target 'Runner' do 46 | use_frameworks! 47 | use_modular_headers! 48 | 49 | # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock 50 | # referring to absolute paths on developers' machines. 51 | ephemeral_dir = File.join('Flutter', 'ephemeral') 52 | symlink_dir = File.join(ephemeral_dir, '.symlinks') 53 | symlink_plugins_dir = File.join(symlink_dir, 'plugins') 54 | system("rm -rf #{symlink_dir}") 55 | system("mkdir -p #{symlink_plugins_dir}") 56 | 57 | # Flutter Pods 58 | generated_xcconfig = parse_KV_file(File.join(ephemeral_dir, 'Flutter-Generated.xcconfig')) 59 | if generated_xcconfig.empty? 60 | puts "Flutter-Generated.xcconfig must exist. If you're running pod install manually, make sure flutter packages get is executed first." 61 | end 62 | generated_xcconfig.map { |p| 63 | if p[:name] == 'FLUTTER_FRAMEWORK_DIR' 64 | symlink = File.join(symlink_dir, 'flutter') 65 | File.symlink(File.dirname(p[:path]), symlink) 66 | pod 'FlutterMacOS', :path => File.join(symlink, File.basename(p[:path])) 67 | end 68 | } 69 | 70 | # Plugin Pods 71 | plugin_pods = parse_KV_file('../.flutter-plugins') 72 | plugin_pods.map { |p| 73 | symlink = File.join(symlink_plugins_dir, p[:name]) 74 | File.symlink(p[:path], symlink) 75 | if pubspec_supports_macos(File.join(symlink, 'pubspec.yaml')) 76 | pod p[:name], :path => File.join(symlink, 'macos') 77 | end 78 | } 79 | end 80 | 81 | # Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system. 82 | install! 'cocoapods', :disable_input_output_paths => true 83 | -------------------------------------------------------------------------------- /example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 66 | 72 | 73 | 74 | 75 | 76 | 77 | 83 | 85 | 91 | 92 | 93 | 94 | 96 | 97 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /example/macos/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/macos/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | @NSApplicationMain 5 | class AppDelegate: FlutterAppDelegate { 6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 7 | return true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "app_icon_16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "app_icon_32.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "app_icon_32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "app_icon_64.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "app_icon_128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "app_icon_256.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "app_icon_256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "app_icon_512.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "app_icon_512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "app_icon_1024.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_list/b67fc3f72c798fbbfc44f0628eef53d549b72809/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_list/b67fc3f72c798fbbfc44f0628eef53d549b72809/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_list/b67fc3f72c798fbbfc44f0628eef53d549b72809/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_list/b67fc3f72c798fbbfc44f0628eef53d549b72809/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_list/b67fc3f72c798fbbfc44f0628eef53d549b72809/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_list/b67fc3f72c798fbbfc44f0628eef53d549b72809/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_list/b67fc3f72c798fbbfc44f0628eef53d549b72809/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png -------------------------------------------------------------------------------- /example/macos/Runner/Base.lproj/MainMenu.xib: -------------------------------------------------------------------------------- 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 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | -------------------------------------------------------------------------------- /example/macos/Runner/Configs/AppInfo.xcconfig: -------------------------------------------------------------------------------- 1 | // Application-level settings for the Runner target. 2 | // 3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the 4 | // future. If not, the values below would default to using the project name when this becomes a 5 | // 'flutter create' template. 6 | 7 | // The application's name. By default this is also the title of the Flutter window. 8 | PRODUCT_NAME = example 9 | 10 | // The application's bundle identifier 11 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example 12 | 13 | // The copyright displayed in application information 14 | PRODUCT_COPYRIGHT = Copyright © 2020 com.example. All rights reserved. 15 | -------------------------------------------------------------------------------- /example/macos/Runner/Configs/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Debug.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /example/macos/Runner/Configs/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Release.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /example/macos/Runner/Configs/Warnings.xcconfig: -------------------------------------------------------------------------------- 1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings 2 | GCC_WARN_UNDECLARED_SELECTOR = YES 3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES 4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE 5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES 6 | CLANG_WARN_PRAGMA_PACK = YES 7 | CLANG_WARN_STRICT_PROTOTYPES = YES 8 | CLANG_WARN_COMMA = YES 9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES 10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES 11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES 12 | GCC_WARN_SHADOW = YES 13 | CLANG_WARN_UNREACHABLE_CODE = YES 14 | -------------------------------------------------------------------------------- /example/macos/Runner/DebugProfile.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.cs.allow-jit 8 | 9 | com.apple.security.network.server 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /example/macos/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | $(PRODUCT_COPYRIGHT) 27 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /example/macos/Runner/MainFlutterWindow.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | class MainFlutterWindow: NSWindow { 5 | override func awakeFromNib() { 6 | let flutterViewController = FlutterViewController.init() 7 | let windowFrame = self.frame 8 | self.contentViewController = flutterViewController 9 | self.setFrame(windowFrame, display: true) 10 | 11 | RegisterGeneratedPlugins(registry: flutterViewController) 12 | 13 | super.awakeFromNib() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /example/macos/Runner/Release.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | url: "https://pub.flutter-io.cn" 9 | source: hosted 10 | version: "2.8.2" 11 | boolean_selector: 12 | dependency: transitive 13 | description: 14 | name: boolean_selector 15 | url: "https://pub.flutter-io.cn" 16 | source: hosted 17 | version: "2.1.0" 18 | characters: 19 | dependency: transitive 20 | description: 21 | name: characters 22 | url: "https://pub.flutter-io.cn" 23 | source: hosted 24 | version: "1.2.0" 25 | charcode: 26 | dependency: transitive 27 | description: 28 | name: charcode 29 | url: "https://pub.flutter-io.cn" 30 | source: hosted 31 | version: "1.3.1" 32 | clock: 33 | dependency: transitive 34 | description: 35 | name: clock 36 | url: "https://pub.flutter-io.cn" 37 | source: hosted 38 | version: "1.1.0" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.flutter-io.cn" 44 | source: hosted 45 | version: "1.15.0" 46 | cupertino_icons: 47 | dependency: "direct main" 48 | description: 49 | name: cupertino_icons 50 | url: "https://pub.flutter-io.cn" 51 | source: hosted 52 | version: "1.0.2" 53 | extended_list: 54 | dependency: "direct main" 55 | description: 56 | path: ".." 57 | relative: true 58 | source: path 59 | version: "3.0.0" 60 | extended_list_library: 61 | dependency: transitive 62 | description: 63 | name: extended_list_library 64 | url: "https://pub.flutter-io.cn" 65 | source: hosted 66 | version: "3.0.0" 67 | fake_async: 68 | dependency: transitive 69 | description: 70 | name: fake_async 71 | url: "https://pub.flutter-io.cn" 72 | source: hosted 73 | version: "1.2.0" 74 | ff_annotation_route_core: 75 | dependency: transitive 76 | description: 77 | name: ff_annotation_route_core 78 | url: "https://pub.flutter-io.cn" 79 | source: hosted 80 | version: "2.0.4" 81 | ff_annotation_route_library: 82 | dependency: "direct main" 83 | description: 84 | name: ff_annotation_route_library 85 | url: "https://pub.flutter-io.cn" 86 | source: hosted 87 | version: "3.0.0" 88 | flutter: 89 | dependency: "direct main" 90 | description: flutter 91 | source: sdk 92 | version: "0.0.0" 93 | flutter_test: 94 | dependency: "direct dev" 95 | description: flutter 96 | source: sdk 97 | version: "0.0.0" 98 | flutter_web_plugins: 99 | dependency: transitive 100 | description: flutter 101 | source: sdk 102 | version: "0.0.0" 103 | js: 104 | dependency: transitive 105 | description: 106 | name: js 107 | url: "https://pub.flutter-io.cn" 108 | source: hosted 109 | version: "0.6.3" 110 | matcher: 111 | dependency: transitive 112 | description: 113 | name: matcher 114 | url: "https://pub.flutter-io.cn" 115 | source: hosted 116 | version: "0.12.11" 117 | meta: 118 | dependency: transitive 119 | description: 120 | name: meta 121 | url: "https://pub.flutter-io.cn" 122 | source: hosted 123 | version: "1.7.0" 124 | path: 125 | dependency: transitive 126 | description: 127 | name: path 128 | url: "https://pub.flutter-io.cn" 129 | source: hosted 130 | version: "1.8.0" 131 | plugin_platform_interface: 132 | dependency: transitive 133 | description: 134 | name: plugin_platform_interface 135 | url: "https://pub.flutter-io.cn" 136 | source: hosted 137 | version: "2.0.0" 138 | sky_engine: 139 | dependency: transitive 140 | description: flutter 141 | source: sdk 142 | version: "0.0.99" 143 | source_span: 144 | dependency: transitive 145 | description: 146 | name: source_span 147 | url: "https://pub.flutter-io.cn" 148 | source: hosted 149 | version: "1.8.1" 150 | stack_trace: 151 | dependency: transitive 152 | description: 153 | name: stack_trace 154 | url: "https://pub.flutter-io.cn" 155 | source: hosted 156 | version: "1.10.0" 157 | stream_channel: 158 | dependency: transitive 159 | description: 160 | name: stream_channel 161 | url: "https://pub.flutter-io.cn" 162 | source: hosted 163 | version: "2.1.0" 164 | string_scanner: 165 | dependency: transitive 166 | description: 167 | name: string_scanner 168 | url: "https://pub.flutter-io.cn" 169 | source: hosted 170 | version: "1.1.0" 171 | term_glyph: 172 | dependency: transitive 173 | description: 174 | name: term_glyph 175 | url: "https://pub.flutter-io.cn" 176 | source: hosted 177 | version: "1.2.0" 178 | test_api: 179 | dependency: transitive 180 | description: 181 | name: test_api 182 | url: "https://pub.flutter-io.cn" 183 | source: hosted 184 | version: "0.4.3" 185 | typed_data: 186 | dependency: transitive 187 | description: 188 | name: typed_data 189 | url: "https://pub.flutter-io.cn" 190 | source: hosted 191 | version: "1.3.0" 192 | url_launcher: 193 | dependency: "direct main" 194 | description: 195 | name: url_launcher 196 | url: "https://pub.flutter-io.cn" 197 | source: hosted 198 | version: "6.0.2" 199 | url_launcher_linux: 200 | dependency: transitive 201 | description: 202 | name: url_launcher_linux 203 | url: "https://pub.flutter-io.cn" 204 | source: hosted 205 | version: "2.0.0" 206 | url_launcher_macos: 207 | dependency: transitive 208 | description: 209 | name: url_launcher_macos 210 | url: "https://pub.flutter-io.cn" 211 | source: hosted 212 | version: "2.0.0" 213 | url_launcher_platform_interface: 214 | dependency: transitive 215 | description: 216 | name: url_launcher_platform_interface 217 | url: "https://pub.flutter-io.cn" 218 | source: hosted 219 | version: "2.0.2" 220 | url_launcher_web: 221 | dependency: transitive 222 | description: 223 | name: url_launcher_web 224 | url: "https://pub.flutter-io.cn" 225 | source: hosted 226 | version: "2.0.0" 227 | url_launcher_windows: 228 | dependency: transitive 229 | description: 230 | name: url_launcher_windows 231 | url: "https://pub.flutter-io.cn" 232 | source: hosted 233 | version: "2.0.0" 234 | vector_math: 235 | dependency: transitive 236 | description: 237 | name: vector_math 238 | url: "https://pub.flutter-io.cn" 239 | source: hosted 240 | version: "2.1.1" 241 | sdks: 242 | dart: ">=2.14.0 <3.0.0" 243 | flutter: ">=2.0.0" 244 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: example 2 | description: A new Flutter project. 3 | 4 | # The following defines the version and build number for your application. 5 | # A version number is three numbers separated by dots, like 1.2.43 6 | # followed by an optional build number separated by a +. 7 | # Both the version and the builder number may be overridden in flutter 8 | # build by specifying --build-name and --build-number, respectively. 9 | # In Android, build-name is used as versionName while build-number used as versionCode. 10 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 11 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 12 | # Read more about iOS versioning at 13 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 14 | version: 1.0.0+1 15 | publish_to: none 16 | environment: 17 | sdk: '>=2.12.0 <3.0.0' 18 | flutter: ">=2.0.0" 19 | 20 | dependencies: 21 | 22 | 23 | # The following adds the Cupertino Icons font to your application. 24 | # Use with the CupertinoIcons class for iOS style icons. 25 | cupertino_icons: ^1.0.2 26 | extended_list: 27 | path: ../ 28 | ff_annotation_route_library: ^3.0.0 29 | flutter: 30 | sdk: flutter 31 | url_launcher: any 32 | dev_dependencies: 33 | flutter_test: 34 | sdk: flutter 35 | 36 | # For information on the generic Dart part of this file, see the 37 | # following page: https://dart.dev/tools/pub/pubspec 38 | 39 | # The following section is specific to Flutter. 40 | flutter: 41 | 42 | # The following line ensures that the Material Icons font is 43 | # included with your application, so that you can use the icons in 44 | # the material Icons class. 45 | uses-material-design: true 46 | 47 | # To add assets to your application, add an assets section, like this: 48 | # assets: 49 | # - images/a_dot_burr.jpeg 50 | # - images/a_dot_ham.jpeg 51 | 52 | # An image asset can refer to one or more resolution-specific "variants", see 53 | # https://flutter.dev/assets-and-images/#resolution-aware. 54 | 55 | # For details regarding adding assets from package dependencies, see 56 | # https://flutter.dev/assets-and-images/#from-packages 57 | 58 | # To add custom fonts to your application, add a fonts section here, 59 | # in this "flutter" section. Each entry in this list should have a 60 | # "family" key with the font family name, and a "fonts" key with a 61 | # list giving the asset and other descriptors for the font. For 62 | # example: 63 | # fonts: 64 | # - family: Schyler 65 | # fonts: 66 | # - asset: fonts/Schyler-Regular.ttf 67 | # - asset: fonts/Schyler-Italic.ttf 68 | # style: italic 69 | # - family: Trajan Pro 70 | # fonts: 71 | # - asset: fonts/TrajanPro.ttf 72 | # - asset: fonts/TrajanPro_Bold.ttf 73 | # weight: 700 74 | # 75 | # For details regarding fonts from package dependencies, 76 | # see https://flutter.dev/custom-fonts/#from-packages 77 | -------------------------------------------------------------------------------- /example/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:example/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MyApp()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /example/web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_list/b67fc3f72c798fbbfc44f0628eef53d549b72809/example/web/favicon.png -------------------------------------------------------------------------------- /example/web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_list/b67fc3f72c798fbbfc44f0628eef53d549b72809/example/web/icons/Icon-192.png -------------------------------------------------------------------------------- /example/web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_list/b67fc3f72c798fbbfc44f0628eef53d549b72809/example/web/icons/Icon-512.png -------------------------------------------------------------------------------- /example/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | example 15 | 16 | 17 | 18 | 21 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /example/web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "short_name": "example", 4 | "start_url": ".", 5 | "display": "minimal-ui", 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 | } 24 | -------------------------------------------------------------------------------- /lib/extended_list.dart: -------------------------------------------------------------------------------- 1 | library extended_list; 2 | 3 | /// 4 | /// create by zmtzawqlp on 2019/11/23 5 | /// 6 | export 'package:extended_list_library/extended_list_library.dart'; 7 | export 'src/widgets/scroll_view.dart'; 8 | export 'src/widgets/sliver.dart'; 9 | -------------------------------------------------------------------------------- /lib/src/rendering/sliver_fixed_extend_list.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math' as math; 2 | import 'package:extended_list_library/extended_list_library.dart'; 3 | import 'package:flutter/foundation.dart'; 4 | import 'package:flutter/rendering.dart'; 5 | 6 | /// 7 | /// create by zmtzawqlp on 2019/11/23 8 | /// 9 | 10 | /// A sliver that contains multiple box children that have the same extent in 11 | /// the main axis. 12 | /// 13 | /// [ExtendedRenderSliverFixedExtentBoxAdaptor] places its children in a linear array 14 | /// along the main axis. Each child is forced to have the [itemExtent] in the 15 | /// main axis and the [SliverConstraints.crossAxisExtent] in the cross axis. 16 | /// 17 | /// Subclasses should override [itemExtent] to control the size of the children 18 | /// in the main axis. For a concrete subclass with a configurable [itemExtent], 19 | /// see [ExtendedRenderSliverFixedExtentList]. 20 | /// 21 | /// [ExtendedRenderSliverFixedExtentBoxAdaptor] is more efficient than 22 | /// [RenderSliverList] because [ExtendedRenderSliverFixedExtentBoxAdaptor] does not need 23 | /// to perform layout on its children to obtain their extent in the main axis. 24 | /// 25 | /// See also: 26 | /// 27 | /// * [ExtendedRenderSliverFixedExtentList], which has a configurable [itemExtent]. 28 | /// * [RenderSliverFillViewport], which determines the [itemExtent] based on 29 | /// [SliverConstraints.viewportMainAxisExtent]. 30 | /// * [RenderSliverFillRemaining], which determines the [itemExtent] based on 31 | /// [SliverConstraints.remainingPaintExtent]. 32 | /// * [RenderSliverList], which does not require its children to have the same 33 | /// extent in the main axis. 34 | abstract class ExtendedRenderSliverFixedExtentBoxAdaptor 35 | extends RenderSliverMultiBoxAdaptor with ExtendedRenderObjectMixin { 36 | /// Creates a sliver that contains multiple box children that have the same 37 | /// extent in the main axis. 38 | /// 39 | /// The [childManager] argument must not be null. 40 | ExtendedRenderSliverFixedExtentBoxAdaptor({ 41 | required RenderSliverBoxChildManager childManager, 42 | required ExtendedListDelegate extendedListDelegate, 43 | }) : _extendedListDelegate = extendedListDelegate, 44 | super(childManager: childManager); 45 | 46 | ExtendedListDelegate _extendedListDelegate; 47 | 48 | /// A delegate that provides extensions within the [ExtendedGridView/ExtendedList/WaterfallFlow]. 49 | @override 50 | ExtendedListDelegate get extendedListDelegate => _extendedListDelegate; 51 | set extendedListDelegate(ExtendedListDelegate value) { 52 | if (_extendedListDelegate == value) { 53 | return; 54 | } 55 | if (_extendedListDelegate.closeToTrailing != value.closeToTrailing) { 56 | markNeedsLayout(); 57 | } 58 | _extendedListDelegate = value; 59 | } 60 | 61 | /// The main-axis extent of each item. 62 | double get itemExtent; 63 | 64 | /// The layout offset for the child with the given index. 65 | /// 66 | /// This function is given the [itemExtent] as an argument to avoid 67 | /// recomputing [itemExtent] repeatedly during layout. 68 | /// 69 | /// By default, places the children in order, without gaps, starting from 70 | /// layout offset zero. 71 | @protected 72 | double indexToLayoutOffset(double itemExtent, int index) => 73 | itemExtent * index; 74 | 75 | /// The minimum child index that is visible at the given scroll offset. 76 | /// 77 | /// This function is given the [itemExtent] as an argument to avoid 78 | /// recomputing [itemExtent] repeatedly during layout. 79 | /// 80 | /// By default, returns a value consistent with the children being placed in 81 | /// order, without gaps, starting from layout offset zero. 82 | @protected 83 | int getMinChildIndexForScrollOffset(double scrollOffset, double itemExtent) { 84 | if (itemExtent > 0.0) { 85 | final double actual = scrollOffset / itemExtent; 86 | final int round = actual.round(); 87 | if ((actual - round).abs() < precisionErrorTolerance) { 88 | return round; 89 | } 90 | return actual.floor(); 91 | } 92 | return 0; 93 | } 94 | 95 | /// The maximum child index that is visible at the given scroll offset. 96 | /// 97 | /// This function is given the [itemExtent] as an argument to avoid 98 | /// recomputing [itemExtent] repeatedly during layout. 99 | /// 100 | /// By default, returns a value consistent with the children being placed in 101 | /// order, without gaps, starting from layout offset zero. 102 | @protected 103 | int getMaxChildIndexForScrollOffset(double scrollOffset, double itemExtent) { 104 | if (itemExtent > 0.0) { 105 | final double actual = scrollOffset / itemExtent - 1; 106 | final int round = actual.round(); 107 | if (_isWithinPrecisionErrorTolerance(actual, round)) { 108 | return math.max(0, round); 109 | } 110 | return math.max(0, actual.ceil()); 111 | } 112 | return 0; 113 | } 114 | 115 | /// Called to estimate the total scrollable extents of this object. 116 | /// 117 | /// Must return the total distance from the start of the child with the 118 | /// earliest possible index to the end of the child with the last possible 119 | /// index. 120 | /// 121 | /// By default, defers to [RenderSliverBoxChildManager.estimateMaxScrollOffset]. 122 | /// 123 | /// See also: 124 | /// 125 | /// * [computeMaxScrollOffset], which is similar but must provide a precise 126 | /// value. 127 | @protected 128 | double estimateMaxScrollOffset( 129 | SliverConstraints constraints, { 130 | int? firstIndex, 131 | int? lastIndex, 132 | double? leadingScrollOffset, 133 | double? trailingScrollOffset, 134 | }) { 135 | return childManager.estimateMaxScrollOffset( 136 | constraints, 137 | firstIndex: firstIndex, 138 | lastIndex: lastIndex, 139 | leadingScrollOffset: leadingScrollOffset, 140 | trailingScrollOffset: trailingScrollOffset, 141 | ); 142 | } 143 | 144 | /// Called to obtain a precise measure of the total scrollable extents of this 145 | /// object. 146 | /// 147 | /// Must return the precise total distance from the start of the child with 148 | /// the earliest possible index to the end of the child with the last possible 149 | /// index. 150 | /// 151 | /// This is used when no child is available for the index corresponding to the 152 | /// current scroll offset, to determine the precise dimensions of the sliver. 153 | /// It must return a precise value. It will not be called if the 154 | /// [childManager] returns an infinite number of children for positive 155 | /// indices. 156 | /// 157 | /// By default, multiplies the [itemExtent] by the number of children reported 158 | /// by [RenderSliverBoxChildManager.childCount]. 159 | /// 160 | /// See also: 161 | /// 162 | /// * [estimateMaxScrollOffset], which is similar but may provide inaccurate 163 | /// values. 164 | @protected 165 | double computeMaxScrollOffset( 166 | SliverConstraints constraints, double itemExtent) { 167 | return childManager.childCount * itemExtent; 168 | } 169 | 170 | int _calculateLeadingGarbage(int firstIndex) { 171 | RenderBox? walker = firstChild; 172 | int leadingGarbage = 0; 173 | while (walker != null && indexOf(walker) < firstIndex) { 174 | leadingGarbage += 1; 175 | walker = childAfter(walker); 176 | } 177 | return leadingGarbage; 178 | } 179 | 180 | int _calculateTrailingGarbage(int? targetLastIndex) { 181 | RenderBox? walker = lastChild; 182 | int trailingGarbage = 0; 183 | while (walker != null && indexOf(walker) > targetLastIndex!) { 184 | trailingGarbage += 1; 185 | walker = childBefore(walker); 186 | } 187 | return trailingGarbage; 188 | } 189 | 190 | @override 191 | void performLayout() { 192 | childManager.didStartLayout(); 193 | childManager.setDidUnderflow(false); 194 | 195 | final double itemExtent = this.itemExtent; 196 | 197 | final double scrollOffset = 198 | constraints.scrollOffset + constraints.cacheOrigin; 199 | assert(scrollOffset >= 0.0); 200 | final double remainingExtent = constraints.remainingCacheExtent; 201 | assert(remainingExtent >= 0.0); 202 | final double targetEndScrollOffset = scrollOffset + remainingExtent; 203 | 204 | final BoxConstraints childConstraints = constraints.asBoxConstraints( 205 | minExtent: itemExtent, 206 | maxExtent: itemExtent, 207 | ); 208 | 209 | final int firstIndex = 210 | getMinChildIndexForScrollOffset(scrollOffset, itemExtent); 211 | final int? targetLastIndex = targetEndScrollOffset.isFinite 212 | ? getMaxChildIndexForScrollOffset(targetEndScrollOffset, itemExtent) 213 | : null; 214 | 215 | if (firstChild != null) { 216 | final int leadingGarbage = _calculateLeadingGarbage(firstIndex); 217 | final int trailingGarbage = targetLastIndex != null 218 | ? _calculateTrailingGarbage(targetLastIndex) 219 | : 0; 220 | collectGarbage(leadingGarbage, trailingGarbage); 221 | //zmt 222 | callCollectGarbage( 223 | collectGarbage: extendedListDelegate.collectGarbage, 224 | leadingGarbage: leadingGarbage, 225 | trailingGarbage: trailingGarbage, 226 | firstIndex: firstIndex, 227 | targetLastIndex: targetLastIndex, 228 | ); 229 | } else { 230 | collectGarbage(0, 0); 231 | } 232 | 233 | if (firstChild == null) { 234 | if (!addInitialChild( 235 | index: firstIndex, 236 | layoutOffset: indexToLayoutOffset(itemExtent, firstIndex))) { 237 | // There are either no children, or we are past the end of all our children. 238 | final double max; 239 | if (firstIndex <= 0) { 240 | max = 0.0; 241 | } else { 242 | max = computeMaxScrollOffset(constraints, itemExtent); 243 | } 244 | geometry = SliverGeometry( 245 | scrollExtent: max, 246 | maxPaintExtent: max, 247 | ); 248 | childManager.didFinishLayout(); 249 | return; 250 | } 251 | } 252 | 253 | // zmt 254 | handleCloseToTrailingBegin(closeToTrailing); 255 | 256 | RenderBox? trailingChildWithLayout; 257 | 258 | for (int index = indexOf(firstChild!) - 1; index >= firstIndex; --index) { 259 | final RenderBox? child = insertAndLayoutLeadingChild(childConstraints); 260 | if (child == null) { 261 | // Items before the previously first child are no longer present. 262 | // Reset the scroll offset to offset all items prior and up to the 263 | // missing item. Let parent re-layout everything. 264 | geometry = SliverGeometry(scrollOffsetCorrection: index * itemExtent); 265 | return; 266 | } 267 | final SliverMultiBoxAdaptorParentData childParentData = 268 | child.parentData as SliverMultiBoxAdaptorParentData; 269 | childParentData.layoutOffset = indexToLayoutOffset(itemExtent, index); 270 | assert(childParentData.index == index); 271 | trailingChildWithLayout ??= child; 272 | } 273 | 274 | if (trailingChildWithLayout == null) { 275 | firstChild!.layout(childConstraints); 276 | final SliverMultiBoxAdaptorParentData childParentData = 277 | firstChild!.parentData as SliverMultiBoxAdaptorParentData; 278 | childParentData.layoutOffset = 279 | indexToLayoutOffset(itemExtent, firstIndex); 280 | trailingChildWithLayout = firstChild; 281 | } 282 | 283 | double estimatedMaxScrollOffset = double.infinity; 284 | for (int index = indexOf(trailingChildWithLayout!) + 1; 285 | targetLastIndex == null || index <= targetLastIndex; 286 | ++index) { 287 | RenderBox? child = childAfter(trailingChildWithLayout!); 288 | if (child == null || indexOf(child) != index) { 289 | child = insertAndLayoutChild(childConstraints, 290 | after: trailingChildWithLayout); 291 | if (child == null) { 292 | // We have run out of children. 293 | estimatedMaxScrollOffset = index * itemExtent; 294 | break; 295 | } 296 | } else { 297 | child.layout(childConstraints); 298 | } 299 | trailingChildWithLayout = child; 300 | final SliverMultiBoxAdaptorParentData childParentData = 301 | child.parentData as SliverMultiBoxAdaptorParentData; 302 | assert(childParentData.index == index); 303 | childParentData.layoutOffset = 304 | indexToLayoutOffset(itemExtent, childParentData.index!); 305 | } 306 | 307 | final int lastIndex = indexOf(lastChild!); 308 | final double leadingScrollOffset = 309 | indexToLayoutOffset(itemExtent, firstIndex); 310 | double trailingScrollOffset = 311 | indexToLayoutOffset(itemExtent, lastIndex + 1); 312 | 313 | ///zmt 314 | final double result = 315 | handleCloseToTrailingEnd(closeToTrailing, trailingScrollOffset); 316 | if (result != trailingScrollOffset) { 317 | trailingScrollOffset = result; 318 | estimatedMaxScrollOffset = result; 319 | } 320 | 321 | ///zmt 322 | final bool lastChildIsFoot = (extendedListDelegate 323 | .lastChildLayoutTypeBuilder 324 | ?.call(indexOf(lastChild!)) ?? 325 | LastChildLayoutType.none) == 326 | LastChildLayoutType.foot; 327 | if (lastChildIsFoot) { 328 | //layout as normal constraints 329 | lastChild!.layout(constraints.asBoxConstraints(), parentUsesSize: true); 330 | final double paintExtend = paintExtentOf(lastChild!); 331 | trailingScrollOffset = childScrollOffset(lastChild!)! + paintExtend; 332 | if (trailingScrollOffset < constraints.remainingPaintExtent) { 333 | final SliverMultiBoxAdaptorParentData childParentData = 334 | lastChild!.parentData as SliverMultiBoxAdaptorParentData; 335 | childParentData.layoutOffset = 336 | constraints.remainingPaintExtent - paintExtend; 337 | trailingScrollOffset = constraints.remainingPaintExtent; 338 | } 339 | estimatedMaxScrollOffset = trailingScrollOffset; 340 | } 341 | 342 | assert(firstIndex == 0 || 343 | childScrollOffset(firstChild!)! - scrollOffset <= 344 | precisionErrorTolerance); 345 | assert(debugAssertChildListIsNonEmptyAndContiguous()); 346 | assert(indexOf(firstChild!) == firstIndex); 347 | assert(targetLastIndex == null || lastIndex <= targetLastIndex); 348 | 349 | estimatedMaxScrollOffset = math.min( 350 | estimatedMaxScrollOffset, 351 | estimateMaxScrollOffset( 352 | constraints, 353 | firstIndex: firstIndex, 354 | lastIndex: lastIndex, 355 | leadingScrollOffset: leadingScrollOffset, 356 | trailingScrollOffset: trailingScrollOffset, 357 | ), 358 | ); 359 | 360 | double paintExtent = calculatePaintOffset( 361 | constraints, 362 | from: leadingScrollOffset, 363 | to: trailingScrollOffset, 364 | ); 365 | 366 | final double cacheExtent = calculateCacheOffset( 367 | constraints, 368 | from: leadingScrollOffset, 369 | to: trailingScrollOffset, 370 | ); 371 | 372 | final double targetEndScrollOffsetForPaint = 373 | constraints.scrollOffset + constraints.remainingPaintExtent; 374 | final int? targetLastIndexForPaint = targetEndScrollOffsetForPaint.isFinite 375 | ? getMaxChildIndexForScrollOffset( 376 | targetEndScrollOffsetForPaint, itemExtent) 377 | : null; 378 | 379 | ///zmt 380 | callViewportBuilder( 381 | viewportBuilder: extendedListDelegate.viewportBuilder, 382 | getPaintExtend: (RenderBox? child) { 383 | final LastChildLayoutType lastChildLayoutType = extendedListDelegate 384 | .lastChildLayoutTypeBuilder 385 | ?.call(indexOf(child!)) ?? 386 | LastChildLayoutType.none; 387 | if (lastChildLayoutType != LastChildLayoutType.none) { 388 | return paintExtentOf(child!); 389 | } 390 | return itemExtent; 391 | }); 392 | 393 | // fix hittest 394 | if (closeToTrailing) { 395 | paintExtent += closeToTrailingDistance; 396 | } 397 | 398 | geometry = SliverGeometry( 399 | scrollExtent: estimatedMaxScrollOffset, 400 | paintExtent: paintExtent, 401 | cacheExtent: cacheExtent, 402 | maxPaintExtent: estimatedMaxScrollOffset, 403 | // Conservative to avoid flickering away the clip during scroll. 404 | hasVisualOverflow: (targetLastIndexForPaint != null && 405 | lastIndex >= targetLastIndexForPaint) || 406 | constraints.scrollOffset > 0.0, 407 | ); 408 | 409 | // We may have started the layout while scrolled to the end, which would not 410 | // expose a new child. 411 | if (estimatedMaxScrollOffset == trailingScrollOffset) { 412 | childManager.setDidUnderflow(true); 413 | } 414 | childManager.didFinishLayout(); 415 | } 416 | } 417 | 418 | /// A sliver that places multiple box children with the same main axis extent in 419 | /// a linear array. 420 | /// 421 | /// [ExtendedRenderSliverFixedExtentList] places its children in a linear array along 422 | /// the main axis starting at offset zero and without gaps. Each child is forced 423 | /// to have the [itemExtent] in the main axis and the 424 | /// [SliverConstraints.crossAxisExtent] in the cross axis. 425 | /// 426 | /// [ExtendedRenderSliverFixedExtentList] is more efficient than [RenderSliverList] 427 | /// because [ExtendedRenderSliverFixedExtentList] does not need to perform layout on its 428 | /// children to obtain their extent in the main axis. 429 | /// 430 | /// See also: 431 | /// 432 | /// * [RenderSliverList], which does not require its children to have the same 433 | /// extent in the main axis. 434 | /// * [RenderSliverFillViewport], which determines the [itemExtent] based on 435 | /// [SliverConstraints.viewportMainAxisExtent]. 436 | /// * [RenderSliverFillRemaining], which determines the [itemExtent] based on 437 | /// [SliverConstraints.remainingPaintExtent]. 438 | class ExtendedRenderSliverFixedExtentList 439 | extends ExtendedRenderSliverFixedExtentBoxAdaptor { 440 | /// Creates a sliver that contains multiple box children that have a given 441 | /// extent in the main axis. 442 | /// 443 | /// The [childManager] argument must not be null. 444 | ExtendedRenderSliverFixedExtentList( 445 | {required RenderSliverBoxChildManager childManager, 446 | required double itemExtent, 447 | required ExtendedListDelegate extendedListDelegate}) 448 | : _itemExtent = itemExtent, 449 | super( 450 | childManager: childManager, 451 | extendedListDelegate: extendedListDelegate, 452 | ); 453 | 454 | @override 455 | double get itemExtent => _itemExtent; 456 | double _itemExtent; 457 | set itemExtent(double value) { 458 | if (_itemExtent == value) { 459 | return; 460 | } 461 | _itemExtent = value; 462 | markNeedsLayout(); 463 | } 464 | } 465 | 466 | bool _isWithinPrecisionErrorTolerance(double actual, int round) { 467 | return (actual - round).abs() < precisionErrorTolerance; 468 | } 469 | -------------------------------------------------------------------------------- /lib/src/rendering/sliver_grid.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math' as math; 2 | import 'package:extended_list_library/extended_list_library.dart'; 3 | import 'package:flutter/rendering.dart'; 4 | 5 | /// 6 | /// create by zmtzawqlp on 2019/11/23 7 | /// 8 | 9 | /// A sliver that places multiple box children in a two dimensional arrangement. 10 | /// 11 | /// [ExtendedRenderSliverGrid] places its children in arbitrary positions determined by 12 | /// [gridDelegate]. Each child is forced to have the size specified by the 13 | /// [gridDelegate]. 14 | /// 15 | /// See also: 16 | /// 17 | /// * [RenderSliverList], which places its children in a linear 18 | /// array. 19 | /// * [RenderSliverFixedExtentList], which places its children in a linear 20 | /// array with a fixed extent in the main axis. 21 | class ExtendedRenderSliverGrid extends RenderSliverMultiBoxAdaptor 22 | with ExtendedRenderObjectMixin { 23 | /// Creates a sliver that contains multiple box children that whose size and 24 | /// position are determined by a delegate. 25 | /// 26 | /// The [childManager] and [gridDelegate] arguments must not be null. 27 | ExtendedRenderSliverGrid({ 28 | required RenderSliverBoxChildManager childManager, 29 | required SliverGridDelegate gridDelegate, 30 | required ExtendedListDelegate extendedListDelegate, 31 | }) : _gridDelegate = gridDelegate, 32 | _extendedListDelegate = extendedListDelegate, 33 | super(childManager: childManager); 34 | 35 | @override 36 | void setupParentData(RenderObject child) { 37 | if (child.parentData is! SliverGridParentData) { 38 | child.parentData = SliverGridParentData(); 39 | } 40 | } 41 | 42 | /// The delegate that controls the size and position of the children. 43 | SliverGridDelegate get gridDelegate => _gridDelegate; 44 | SliverGridDelegate _gridDelegate; 45 | set gridDelegate(SliverGridDelegate value) { 46 | if (_gridDelegate == value) { 47 | return; 48 | } 49 | if (value.runtimeType != _gridDelegate.runtimeType || 50 | value.shouldRelayout(_gridDelegate)) { 51 | markNeedsLayout(); 52 | } 53 | _gridDelegate = value; 54 | } 55 | 56 | ExtendedListDelegate _extendedListDelegate; 57 | 58 | /// A delegate that provides extensions within the [ExtendedGridView/ExtendedList/WaterfallFlow]. 59 | @override 60 | ExtendedListDelegate get extendedListDelegate => _extendedListDelegate; 61 | set extendedListDelegate(ExtendedListDelegate value) { 62 | if (_extendedListDelegate == value) { 63 | return; 64 | } 65 | if (_extendedListDelegate.closeToTrailing != value.closeToTrailing) { 66 | markNeedsLayout(); 67 | } 68 | _extendedListDelegate = value; 69 | } 70 | 71 | @override 72 | double childCrossAxisPosition(RenderBox child) { 73 | final SliverGridParentData childParentData = 74 | child.parentData as SliverGridParentData; 75 | return childParentData.crossAxisOffset!; 76 | } 77 | 78 | @override 79 | void performLayout() { 80 | childManager.didStartLayout(); 81 | childManager.setDidUnderflow(false); 82 | 83 | final double scrollOffset = 84 | constraints.scrollOffset + constraints.cacheOrigin; 85 | assert(scrollOffset >= 0.0); 86 | final double remainingExtent = constraints.remainingCacheExtent; 87 | assert(remainingExtent >= 0.0); 88 | final double targetEndScrollOffset = scrollOffset + remainingExtent; 89 | 90 | final SliverGridLayout layout = _gridDelegate.getLayout(constraints); 91 | 92 | final int firstIndex = layout.getMinChildIndexForScrollOffset(scrollOffset); 93 | final int? targetLastIndex = targetEndScrollOffset.isFinite 94 | ? layout.getMaxChildIndexForScrollOffset(targetEndScrollOffset) 95 | : null; 96 | 97 | if (firstChild != null) { 98 | final int oldFirstIndex = indexOf(firstChild!); 99 | final int oldLastIndex = indexOf(lastChild!); 100 | final int leadingGarbage = 101 | (firstIndex - oldFirstIndex).clamp(0, childCount); 102 | final int trailingGarbage = targetLastIndex == null 103 | ? 0 104 | : (oldLastIndex - targetLastIndex).clamp(0, childCount); 105 | collectGarbage(leadingGarbage, trailingGarbage); 106 | //zmt 107 | callCollectGarbage( 108 | collectGarbage: extendedListDelegate.collectGarbage, 109 | leadingGarbage: leadingGarbage, 110 | trailingGarbage: trailingGarbage, 111 | firstIndex: firstIndex, 112 | targetLastIndex: targetLastIndex, 113 | ); 114 | } else { 115 | collectGarbage(0, 0); 116 | } 117 | 118 | final SliverGridGeometry firstChildGridGeometry = 119 | layout.getGeometryForChildIndex(firstIndex); 120 | final double leadingScrollOffset = firstChildGridGeometry.scrollOffset; 121 | double trailingScrollOffset = firstChildGridGeometry.trailingScrollOffset; 122 | 123 | if (firstChild == null) { 124 | if (!addInitialChild( 125 | index: firstIndex, 126 | layoutOffset: firstChildGridGeometry.scrollOffset)) { 127 | // There are either no children, or we are past the end of all our children. 128 | final double max = 129 | layout.computeMaxScrollOffset(childManager.childCount); 130 | geometry = SliverGeometry( 131 | scrollExtent: max, 132 | maxPaintExtent: max, 133 | ); 134 | childManager.didFinishLayout(); 135 | return; 136 | } 137 | } 138 | 139 | // zmt 140 | handleCloseToTrailingBegin(closeToTrailing); 141 | 142 | RenderBox? trailingChildWithLayout; 143 | 144 | for (int index = indexOf(firstChild!) - 1; index >= firstIndex; --index) { 145 | final SliverGridGeometry gridGeometry = 146 | layout.getGeometryForChildIndex(index); 147 | final RenderBox child = insertAndLayoutLeadingChild( 148 | gridGeometry.getBoxConstraints(constraints), 149 | )!; 150 | final SliverGridParentData childParentData = 151 | child.parentData as SliverGridParentData; 152 | childParentData.layoutOffset = gridGeometry.scrollOffset; 153 | childParentData.crossAxisOffset = gridGeometry.crossAxisOffset; 154 | assert(childParentData.index == index); 155 | trailingChildWithLayout ??= child; 156 | trailingScrollOffset = 157 | math.max(trailingScrollOffset, gridGeometry.trailingScrollOffset); 158 | } 159 | 160 | if (trailingChildWithLayout == null) { 161 | firstChild!.layout(firstChildGridGeometry.getBoxConstraints(constraints)); 162 | final SliverGridParentData childParentData = 163 | firstChild!.parentData as SliverGridParentData; 164 | childParentData.layoutOffset = firstChildGridGeometry.scrollOffset; 165 | childParentData.crossAxisOffset = firstChildGridGeometry.crossAxisOffset; 166 | trailingChildWithLayout = firstChild; 167 | } 168 | 169 | for (int index = indexOf(trailingChildWithLayout!) + 1; 170 | targetLastIndex == null || index <= targetLastIndex; 171 | ++index) { 172 | final SliverGridGeometry gridGeometry = 173 | layout.getGeometryForChildIndex(index); 174 | final BoxConstraints childConstraints = 175 | gridGeometry.getBoxConstraints(constraints); 176 | RenderBox? child = childAfter(trailingChildWithLayout!); 177 | if (child == null || indexOf(child) != index) { 178 | child = insertAndLayoutChild(childConstraints, 179 | after: trailingChildWithLayout); 180 | if (child == null) { 181 | // We have run out of children. 182 | break; 183 | } 184 | } else { 185 | child.layout(childConstraints); 186 | } 187 | trailingChildWithLayout = child; 188 | final SliverGridParentData childParentData = 189 | child.parentData as SliverGridParentData; 190 | childParentData.layoutOffset = gridGeometry.scrollOffset; 191 | childParentData.crossAxisOffset = gridGeometry.crossAxisOffset; 192 | assert(childParentData.index == index); 193 | trailingScrollOffset = 194 | math.max(trailingScrollOffset, gridGeometry.trailingScrollOffset); 195 | } 196 | 197 | final int lastIndex = indexOf(lastChild!); 198 | 199 | assert(debugAssertChildListIsNonEmptyAndContiguous()); 200 | assert(indexOf(firstChild!) == firstIndex); 201 | assert(targetLastIndex == null || lastIndex <= targetLastIndex); 202 | 203 | double estimatedTotalExtent = childManager.estimateMaxScrollOffset( 204 | constraints, 205 | firstIndex: firstIndex, 206 | lastIndex: lastIndex, 207 | leadingScrollOffset: leadingScrollOffset, 208 | trailingScrollOffset: trailingScrollOffset, 209 | ); 210 | 211 | //zmt 212 | final SliverGridParentData data = 213 | lastChild!.parentData as SliverGridParentData; 214 | final LastChildLayoutType lastChildLayoutType = 215 | extendedListDelegate.lastChildLayoutTypeBuilder?.call(data.index!) ?? 216 | LastChildLayoutType.none; 217 | 218 | switch (lastChildLayoutType) { 219 | case LastChildLayoutType.fullCrossAxisExtent: 220 | case LastChildLayoutType.foot: 221 | data.crossAxisOffset = 0.0; 222 | //layout as normal constraints 223 | lastChild!.layout(constraints.asBoxConstraints(), parentUsesSize: true); 224 | final double size = paintExtentOf(lastChild!); 225 | trailingScrollOffset = data.index == 0 226 | ? size 227 | : layout 228 | .getGeometryForChildIndex(data.index! - 1) 229 | .trailingScrollOffset; 230 | if (lastChildLayoutType == LastChildLayoutType.fullCrossAxisExtent || 231 | trailingScrollOffset + size >= constraints.remainingPaintExtent || 232 | closeToTrailing) { 233 | data.layoutOffset = trailingScrollOffset; 234 | } else { 235 | data.layoutOffset = constraints.remainingPaintExtent - size; 236 | } 237 | trailingScrollOffset = data.layoutOffset! + size; 238 | estimatedTotalExtent = trailingScrollOffset; 239 | break; 240 | case LastChildLayoutType.none: 241 | break; 242 | } 243 | 244 | final double result = 245 | handleCloseToTrailingEnd(closeToTrailing, trailingScrollOffset); 246 | if (result != trailingScrollOffset) { 247 | trailingScrollOffset = result; 248 | estimatedTotalExtent = result; 249 | } 250 | 251 | double paintExtent = calculatePaintOffset( 252 | constraints, 253 | from: math.min(constraints.scrollOffset, leadingScrollOffset), 254 | to: trailingScrollOffset, 255 | ); 256 | final double cacheExtent = calculateCacheOffset( 257 | constraints, 258 | from: leadingScrollOffset, 259 | to: trailingScrollOffset, 260 | ); 261 | 262 | ///zmt 263 | /// 264 | if (extendedListDelegate.viewportBuilder != null) { 265 | double mainAxisSpacing = 0.0; 266 | if (_gridDelegate is SliverGridDelegateWithFixedCrossAxisCount) { 267 | mainAxisSpacing = 268 | (_gridDelegate as SliverGridDelegateWithFixedCrossAxisCount) 269 | .mainAxisSpacing; 270 | } else if (_gridDelegate is SliverGridDelegateWithMaxCrossAxisExtent) { 271 | mainAxisSpacing = 272 | (_gridDelegate as SliverGridDelegateWithMaxCrossAxisExtent) 273 | .mainAxisSpacing; 274 | } 275 | callViewportBuilder( 276 | viewportBuilder: extendedListDelegate.viewportBuilder, 277 | mainAxisSpacing: mainAxisSpacing, 278 | getPaintExtend: (RenderBox? child) { 279 | final SliverGridParentData childParentData = 280 | child!.parentData as SliverGridParentData; 281 | final LastChildLayoutType lastChildLayoutType = extendedListDelegate 282 | .lastChildLayoutTypeBuilder 283 | ?.call(childParentData.index!) ?? 284 | LastChildLayoutType.none; 285 | if (lastChildLayoutType != LastChildLayoutType.none) { 286 | return paintExtentOf(child); 287 | } 288 | 289 | final SliverGridGeometry gridGeometry = 290 | layout.getGeometryForChildIndex(childParentData.index!); 291 | return gridGeometry.trailingScrollOffset - 292 | childParentData.layoutOffset!; 293 | }); 294 | } 295 | 296 | //fix hittest 297 | if (closeToTrailing) { 298 | paintExtent += closeToTrailingDistance; 299 | paintExtent = math.min(paintExtent, estimatedTotalExtent); 300 | } 301 | 302 | geometry = SliverGeometry( 303 | scrollExtent: estimatedTotalExtent, 304 | paintExtent: paintExtent, 305 | maxPaintExtent: estimatedTotalExtent, 306 | cacheExtent: cacheExtent, 307 | // Conservative to avoid complexity. 308 | hasVisualOverflow: true, 309 | ); 310 | 311 | // We may have started the layout while scrolled to the end, which 312 | // would not expose a new child. 313 | if (estimatedTotalExtent == trailingScrollOffset) 314 | childManager.setDidUnderflow(true); 315 | childManager.didFinishLayout(); 316 | } 317 | } 318 | -------------------------------------------------------------------------------- /lib/src/rendering/sliver_list.dart: -------------------------------------------------------------------------------- 1 | import 'package:extended_list_library/extended_list_library.dart'; 2 | import 'package:flutter/foundation.dart'; 3 | import 'package:flutter/rendering.dart'; 4 | 5 | /// 6 | /// create by zmtzawqlp on 2019/11/23 7 | /// 8 | 9 | /// A sliver that places multiple box children in a linear array along the main 10 | /// axis. 11 | /// 12 | /// Each child is forced to have the [SliverConstraints.crossAxisExtent] in the 13 | /// cross axis but determines its own main axis extent. 14 | /// 15 | /// [ExtendedRenderSliverList] determines its scroll offset by "dead reckoning" because 16 | /// children outside the visible part of the sliver are not materialized, which 17 | /// means [ExtendedRenderSliverList] cannot learn their main axis extent. Instead, newly 18 | /// materialized children are placed adjacent to existing children. If this dead 19 | /// reckoning results in a logical inconsistency (e.g., attempting to place the 20 | /// zeroth child at a scroll offset other than zero), the [ExtendedRenderSliverList] 21 | /// generates a [SliverGeometry.scrollOffsetCorrection] to restore consistency. 22 | /// 23 | /// If the children have a fixed extent in the main axis, consider using 24 | /// [RenderSliverFixedExtentList] rather than [ExtendedRenderSliverList] because 25 | /// [RenderSliverFixedExtentList] does not need to perform layout on its 26 | /// children to obtain their extent in the main axis and is therefore more 27 | /// efficient. 28 | /// 29 | /// See also: 30 | /// 31 | /// * [RenderSliverFixedExtentList], which is more efficient for children with 32 | /// the same extent in the main axis. 33 | /// * [RenderSliverGrid], which places its children in arbitrary positions. 34 | class ExtendedRenderSliverList extends RenderSliverMultiBoxAdaptor 35 | with ExtendedRenderObjectMixin { 36 | /// Creates a sliver that places multiple box children in a linear array along 37 | /// the main axis. 38 | /// 39 | /// The [childManager] argument must not be null. 40 | ExtendedRenderSliverList({ 41 | required RenderSliverBoxChildManager childManager, 42 | required ExtendedListDelegate extendedListDelegate, 43 | }) : _extendedListDelegate = extendedListDelegate, 44 | super(childManager: childManager); 45 | 46 | ExtendedListDelegate _extendedListDelegate; 47 | 48 | /// A delegate that provides extensions within the [ExtendedGridView/ExtendedList/WaterfallFlow]. 49 | @override 50 | ExtendedListDelegate get extendedListDelegate => _extendedListDelegate; 51 | set extendedListDelegate(ExtendedListDelegate value) { 52 | if (_extendedListDelegate == value) { 53 | return; 54 | } 55 | if (_extendedListDelegate.closeToTrailing != value.closeToTrailing) { 56 | markNeedsLayout(); 57 | } 58 | _extendedListDelegate = value; 59 | } 60 | 61 | @override 62 | void performLayout() { 63 | childManager.didStartLayout(); 64 | childManager.setDidUnderflow(false); 65 | 66 | final double scrollOffset = 67 | constraints.scrollOffset + constraints.cacheOrigin; 68 | assert(scrollOffset >= 0.0); 69 | final double remainingExtent = constraints.remainingCacheExtent; 70 | assert(remainingExtent >= 0.0); 71 | final double targetEndScrollOffset = scrollOffset + remainingExtent; 72 | final BoxConstraints childConstraints = constraints.asBoxConstraints(); 73 | int leadingGarbage = 0; 74 | int trailingGarbage = 0; 75 | bool reachedEnd = false; 76 | 77 | // This algorithm in principle is straight-forward: find the first child 78 | // that overlaps the given scrollOffset, creating more children at the top 79 | // of the list if necessary, then walk down the list updating and laying out 80 | // each child and adding more at the end if necessary until we have enough 81 | // children to cover the entire viewport. 82 | // 83 | // It is complicated by one minor issue, which is that any time you update 84 | // or create a child, it's possible that the some of the children that 85 | // haven't yet been laid out will be removed, leaving the list in an 86 | // inconsistent state, and requiring that missing nodes be recreated. 87 | // 88 | // To keep this mess tractable, this algorithm starts from what is currently 89 | // the first child, if any, and then walks up and/or down from there, so 90 | // that the nodes that might get removed are always at the edges of what has 91 | // already been laid out. 92 | 93 | // Make sure we have at least one child to start from. 94 | if (firstChild == null) { 95 | if (!addInitialChild()) { 96 | // There are no children. 97 | geometry = SliverGeometry.zero; 98 | childManager.didFinishLayout(); 99 | return; 100 | } 101 | } 102 | 103 | // zmt 104 | handleCloseToTrailingBegin(closeToTrailing); 105 | 106 | // We have at least one child. 107 | 108 | // These variables track the range of children that we have laid out. Within 109 | // this range, the children have consecutive indices. Outside this range, 110 | // it's possible for a child to get removed without notice. 111 | RenderBox? leadingChildWithLayout, trailingChildWithLayout; 112 | 113 | RenderBox? earliestUsefulChild = firstChild; 114 | 115 | // A firstChild with null layout offset is likely a result of children 116 | // reordering. 117 | // 118 | // We rely on firstChild to have accurate layout offset. In the case of null 119 | // layout offset, we have to find the first child that has valid layout 120 | // offset. 121 | if (childScrollOffset(firstChild!) == null) { 122 | int leadingChildrenWithoutLayoutOffset = 0; 123 | while (earliestUsefulChild != null && 124 | childScrollOffset(earliestUsefulChild) == null) { 125 | earliestUsefulChild = childAfter(earliestUsefulChild); 126 | leadingChildrenWithoutLayoutOffset += 1; 127 | } 128 | // We should be able to destroy children with null layout offset safely, 129 | // because they are likely outside of viewport 130 | collectGarbage(leadingChildrenWithoutLayoutOffset, 0); 131 | // If can not find a valid layout offset, start from the initial child. 132 | if (firstChild == null) { 133 | if (!addInitialChild()) { 134 | // There are no children. 135 | geometry = SliverGeometry.zero; 136 | childManager.didFinishLayout(); 137 | return; 138 | } 139 | } 140 | } 141 | // Find the last child that is at or before the scrollOffset. 142 | earliestUsefulChild = firstChild; 143 | for (double earliestScrollOffset = childScrollOffset(earliestUsefulChild!)!; 144 | earliestScrollOffset > scrollOffset; 145 | earliestScrollOffset = childScrollOffset(earliestUsefulChild)!) { 146 | // We have to add children before the earliestUsefulChild. 147 | earliestUsefulChild = 148 | insertAndLayoutLeadingChild(childConstraints, parentUsesSize: true); 149 | 150 | if (earliestUsefulChild == null) { 151 | final SliverMultiBoxAdaptorParentData childParentData = 152 | firstChild!.parentData as SliverMultiBoxAdaptorParentData; 153 | childParentData.layoutOffset = 0.0; 154 | 155 | if (scrollOffset == 0.0) { 156 | // insertAndLayoutLeadingChild only lays out the children before 157 | // firstChild. In this case, nothing has been laid out. We have 158 | // to lay out firstChild manually. 159 | firstChild!.layout(childConstraints, parentUsesSize: true); 160 | earliestUsefulChild = firstChild; 161 | leadingChildWithLayout = earliestUsefulChild; 162 | trailingChildWithLayout ??= earliestUsefulChild; 163 | break; 164 | } else { 165 | // We ran out of children before reaching the scroll offset. 166 | // We must inform our parent that this sliver cannot fulfill 167 | // its contract and that we need a scroll offset correction. 168 | geometry = SliverGeometry( 169 | scrollOffsetCorrection: -scrollOffset, 170 | ); 171 | return; 172 | } 173 | } 174 | 175 | final double firstChildScrollOffset = 176 | earliestScrollOffset - paintExtentOf(firstChild!); 177 | // firstChildScrollOffset may contain double precision error 178 | if (firstChildScrollOffset < -precisionErrorTolerance) { 179 | // Let's assume there is no child before the first child. We will 180 | // correct it on the next layout if it is not. 181 | geometry = SliverGeometry( 182 | scrollOffsetCorrection: -firstChildScrollOffset, 183 | ); 184 | final SliverMultiBoxAdaptorParentData childParentData = 185 | firstChild!.parentData! as SliverMultiBoxAdaptorParentData; 186 | childParentData.layoutOffset = 0.0; 187 | return; 188 | } 189 | 190 | final SliverMultiBoxAdaptorParentData childParentData = 191 | earliestUsefulChild.parentData! as SliverMultiBoxAdaptorParentData; 192 | childParentData.layoutOffset = firstChildScrollOffset; 193 | assert(earliestUsefulChild == firstChild); 194 | leadingChildWithLayout = earliestUsefulChild; 195 | trailingChildWithLayout ??= earliestUsefulChild; 196 | } 197 | 198 | assert(childScrollOffset(firstChild!)! > -precisionErrorTolerance); 199 | 200 | // If the scroll offset is at zero, we should make sure we are 201 | // actually at the beginning of the list. 202 | if (scrollOffset < precisionErrorTolerance) { 203 | // We iterate from the firstChild in case the leading child has a 0 paint 204 | // extent. 205 | while (indexOf(firstChild!) > 0) { 206 | final double earliestScrollOffset = childScrollOffset(firstChild!)!; 207 | // We correct one child at a time. If there are more children before 208 | // the earliestUsefulChild, we will correct it once the scroll offset 209 | // reaches zero again. 210 | earliestUsefulChild = 211 | insertAndLayoutLeadingChild(childConstraints, parentUsesSize: true); 212 | assert(earliestUsefulChild != null); 213 | final double firstChildScrollOffset = 214 | earliestScrollOffset - paintExtentOf(firstChild!); 215 | final SliverMultiBoxAdaptorParentData childParentData = 216 | firstChild!.parentData! as SliverMultiBoxAdaptorParentData; 217 | childParentData.layoutOffset = 0.0; 218 | // We only need to correct if the leading child actually has a 219 | // paint extent. 220 | if (firstChildScrollOffset < -precisionErrorTolerance) { 221 | geometry = SliverGeometry( 222 | scrollOffsetCorrection: -firstChildScrollOffset, 223 | ); 224 | return; 225 | } 226 | } 227 | } 228 | 229 | // At this point, earliestUsefulChild is the first child, and is a child 230 | // whose scrollOffset is at or before the scrollOffset, and 231 | // leadingChildWithLayout and trailingChildWithLayout are either null or 232 | // cover a range of render boxes that we have laid out with the first being 233 | // the same as earliestUsefulChild and the last being either at or after the 234 | // scroll offset. 235 | 236 | assert(earliestUsefulChild == firstChild); 237 | assert(childScrollOffset(earliestUsefulChild!)! <= scrollOffset); 238 | 239 | // Make sure we've laid out at least one child. 240 | if (leadingChildWithLayout == null) { 241 | earliestUsefulChild!.layout(childConstraints, parentUsesSize: true); 242 | leadingChildWithLayout = earliestUsefulChild; 243 | trailingChildWithLayout = earliestUsefulChild; 244 | } 245 | 246 | // Here, earliestUsefulChild is still the first child, it's got a 247 | // scrollOffset that is at or before our actual scrollOffset, and it has 248 | // been laid out, and is in fact our leadingChildWithLayout. It's possible 249 | // that some children beyond that one have also been laid out. 250 | 251 | bool inLayoutRange = true; 252 | RenderBox? child = earliestUsefulChild; 253 | int index = indexOf(child!); 254 | double endScrollOffset = childScrollOffset(child)! + paintExtentOf(child); 255 | bool advance() { 256 | // returns true if we advanced, false if we have no more children 257 | // This function is used in two different places below, to avoid code duplication. 258 | assert(child != null); 259 | if (child == trailingChildWithLayout) { 260 | inLayoutRange = false; 261 | } 262 | child = childAfter(child!); 263 | if (child == null) { 264 | inLayoutRange = false; 265 | } 266 | index += 1; 267 | if (!inLayoutRange) { 268 | if (child == null || indexOf(child!) != index) { 269 | // We are missing a child. Insert it (and lay it out) if possible. 270 | child = insertAndLayoutChild( 271 | childConstraints, 272 | after: trailingChildWithLayout, 273 | parentUsesSize: true, 274 | ); 275 | if (child == null) { 276 | // We have run out of children. 277 | return false; 278 | } 279 | } else { 280 | // Lay out the child. 281 | child!.layout(childConstraints, parentUsesSize: true); 282 | } 283 | trailingChildWithLayout = child; 284 | } 285 | assert(child != null); 286 | final SliverMultiBoxAdaptorParentData childParentData = 287 | child!.parentData! as SliverMultiBoxAdaptorParentData; 288 | childParentData.layoutOffset = endScrollOffset; 289 | assert(childParentData.index == index); 290 | endScrollOffset = childScrollOffset(child!)! + paintExtentOf(child!); 291 | return true; 292 | } 293 | 294 | // Find the first child that ends after the scroll offset. 295 | while (endScrollOffset < scrollOffset) { 296 | leadingGarbage += 1; 297 | if (!advance()) { 298 | assert(leadingGarbage == childCount); 299 | assert(child == null); 300 | // we want to make sure we keep the last child around so we know the end scroll offset 301 | collectGarbage(leadingGarbage - 1, 0); 302 | assert(firstChild == lastChild); 303 | final double extent = 304 | childScrollOffset(lastChild!)! + paintExtentOf(lastChild!); 305 | geometry = SliverGeometry( 306 | scrollExtent: extent, 307 | paintExtent: 0.0, 308 | maxPaintExtent: extent, 309 | ); 310 | return; 311 | } 312 | } 313 | 314 | // Now find the first child that ends after our end. 315 | while (endScrollOffset < targetEndScrollOffset) { 316 | if (!advance()) { 317 | reachedEnd = true; 318 | break; 319 | } 320 | } 321 | 322 | // Finally count up all the remaining children and label them as garbage. 323 | if (child != null) { 324 | child = childAfter(child!); 325 | while (child != null) { 326 | trailingGarbage += 1; 327 | child = childAfter(child!); 328 | } 329 | } 330 | 331 | // At this point everything should be good to go, we just have to clean up 332 | // the garbage and report the geometry. 333 | 334 | collectGarbage(leadingGarbage, trailingGarbage); 335 | //zmt 336 | callCollectGarbage( 337 | collectGarbage: extendedListDelegate.collectGarbage, 338 | leadingGarbage: leadingGarbage, 339 | trailingGarbage: trailingGarbage, 340 | ); 341 | 342 | assert(debugAssertChildListIsNonEmptyAndContiguous()); 343 | double estimatedMaxScrollOffset; 344 | 345 | //zmt 346 | endScrollOffset = 347 | handleCloseToTrailingEnd(closeToTrailing, endScrollOffset); 348 | 349 | if (reachedEnd) { 350 | ///zmt 351 | final LastChildLayoutType layoutType = extendedListDelegate 352 | .lastChildLayoutTypeBuilder 353 | ?.call(indexOf(lastChild!)) ?? 354 | LastChildLayoutType.none; 355 | final double size = paintExtentOf(lastChild!); 356 | final double trailingLayoutOffset = childScrollOffset(lastChild!)! + size; 357 | if (layoutType == LastChildLayoutType.foot && 358 | trailingLayoutOffset < constraints.remainingPaintExtent) { 359 | final SliverMultiBoxAdaptorParentData childParentData = 360 | lastChild!.parentData as SliverMultiBoxAdaptorParentData; 361 | childParentData.layoutOffset = constraints.remainingPaintExtent - size; 362 | endScrollOffset = constraints.remainingPaintExtent; 363 | } 364 | estimatedMaxScrollOffset = endScrollOffset; 365 | } else { 366 | estimatedMaxScrollOffset = childManager.estimateMaxScrollOffset( 367 | constraints, 368 | firstIndex: indexOf(firstChild!), 369 | lastIndex: indexOf(lastChild!), 370 | leadingScrollOffset: childScrollOffset(firstChild!), 371 | trailingScrollOffset: endScrollOffset, 372 | ); 373 | assert(estimatedMaxScrollOffset >= 374 | endScrollOffset - childScrollOffset(firstChild!)!); 375 | } 376 | 377 | final double firstChildScrollOffset = childScrollOffset(firstChild!)!; 378 | double paintExtent = calculatePaintOffset( 379 | constraints, 380 | from: firstChildScrollOffset, 381 | to: endScrollOffset, 382 | ); 383 | final double cacheExtent = calculateCacheOffset( 384 | constraints, 385 | from: firstChildScrollOffset, 386 | to: endScrollOffset, 387 | ); 388 | final double targetEndScrollOffsetForPaint = 389 | constraints.scrollOffset + constraints.remainingPaintExtent; 390 | 391 | callViewportBuilder(viewportBuilder: extendedListDelegate.viewportBuilder); 392 | 393 | //fix hittest 394 | if (closeToTrailing) { 395 | paintExtent += closeToTrailingDistance; 396 | } 397 | 398 | geometry = SliverGeometry( 399 | scrollExtent: estimatedMaxScrollOffset, 400 | paintExtent: paintExtent, 401 | cacheExtent: cacheExtent, 402 | maxPaintExtent: estimatedMaxScrollOffset, 403 | // Conservative to avoid flickering away the clip during scroll. 404 | hasVisualOverflow: endScrollOffset > targetEndScrollOffsetForPaint || 405 | constraints.scrollOffset > 0.0, 406 | ); 407 | 408 | // We may have started the layout while scrolled to the end, which would not 409 | // expose a new child. 410 | if (estimatedMaxScrollOffset == endScrollOffset) 411 | childManager.setDidUnderflow(true); 412 | childManager.didFinishLayout(); 413 | } 414 | } 415 | -------------------------------------------------------------------------------- /lib/src/widgets/sliver.dart: -------------------------------------------------------------------------------- 1 | import 'package:extended_list/src/rendering/sliver_fixed_extend_list.dart'; 2 | import 'package:extended_list/src/rendering/sliver_grid.dart'; 3 | import 'package:extended_list/src/rendering/sliver_list.dart'; 4 | import 'package:extended_list_library/extended_list_library.dart'; 5 | import 'package:flutter/rendering.dart'; 6 | import 'package:flutter/widgets.dart'; 7 | 8 | /// 9 | /// create by zmtzawqlp on 2019/11/23 10 | /// 11 | 12 | /// A sliver that places multiple box children in a linear array along the main 13 | /// axis. 14 | /// 15 | /// Each child is forced to have the [SliverConstraints.crossAxisExtent] in the 16 | /// cross axis but determines its own main axis extent. 17 | /// 18 | /// [ExtendedSliverList] determines its scroll offset by "dead reckoning" because 19 | /// children outside the visible part of the sliver are not materialized, which 20 | /// means [ExtendedSliverList] cannot learn their main axis extent. Instead, newly 21 | /// materialized children are placed adjacent to existing children. 22 | /// 23 | /// {@youtube 560 315 https://www.youtube.com/watch?v=ORiTTaVY6mM} 24 | /// 25 | /// If the children have a fixed extent in the main axis, consider using 26 | /// [ExtendedSliverFixedExtentList] rather than [ExtendedSliverList] because 27 | /// [ExtendedSliverFixedExtentList] does not need to perform layout on its children to 28 | /// obtain their extent in the main axis and is therefore more efficient. 29 | /// 30 | /// {@macro flutter.widgets.sliverChildDelegate.lifecycle} 31 | /// 32 | /// See also: 33 | /// 34 | /// * [ExtendedSliverFixedExtentList], which is more efficient for children with 35 | /// the same extent in the main axis. 36 | /// * [SliverPrototypeExtentList], which is similar to [ExtendedSliverFixedExtentList] 37 | /// except that it uses a prototype list item instead of a pixel value to define 38 | /// the main axis extent of each item. 39 | /// * [ExtendedSliverGrid], which places its children in arbitrary positions. 40 | class ExtendedSliverList extends SliverMultiBoxAdaptorWidget { 41 | /// Creates a sliver that places box children in a linear array. 42 | const ExtendedSliverList({ 43 | Key? key, 44 | required SliverChildDelegate delegate, 45 | required this.extendedListDelegate, 46 | }) : super(key: key, delegate: delegate); 47 | 48 | /// A delegate that provides extensions within the [ExtendedGridView/ExtendedList/WaterfallFlow]. 49 | final ExtendedListDelegate extendedListDelegate; 50 | 51 | @override 52 | ExtendedRenderSliverList createRenderObject(BuildContext context) { 53 | final SliverMultiBoxAdaptorElement element = 54 | context as SliverMultiBoxAdaptorElement; 55 | return ExtendedRenderSliverList( 56 | childManager: element, 57 | extendedListDelegate: extendedListDelegate, 58 | ); 59 | } 60 | 61 | @override 62 | void updateRenderObject( 63 | BuildContext context, ExtendedRenderSliverList renderObject) { 64 | renderObject.extendedListDelegate = extendedListDelegate; 65 | } 66 | } 67 | 68 | /// A sliver that places multiple box children with the same main axis extent in 69 | /// a linear array. 70 | /// 71 | /// [ExtendedSliverFixedExtentList] places its children in a linear array along the main 72 | /// axis starting at offset zero and without gaps. Each child is forced to have 73 | /// the [itemExtent] in the main axis and the 74 | /// [SliverConstraints.crossAxisExtent] in the cross axis. 75 | /// 76 | /// [ExtendedSliverFixedExtentList] is more efficient than [ExtendedSliverList] because 77 | /// [ExtendedSliverFixedExtentList] does not need to perform layout on its children to 78 | /// obtain their extent in the main axis. 79 | /// 80 | /// {@tool sample} 81 | /// 82 | /// This example, which would be inserted into a [CustomScrollView.slivers] 83 | /// list, shows an infinite number of items in varying shades of blue: 84 | /// 85 | /// ```dart 86 | /// SliverFixedExtentList( 87 | /// itemExtent: 50.0, 88 | /// delegate: SliverChildBuilderDelegate( 89 | /// (BuildContext context, int index) { 90 | /// return Container( 91 | /// alignment: Alignment.center, 92 | /// color: Colors.lightBlue[100 * (index % 9)], 93 | /// child: Text('list item $index'), 94 | /// ); 95 | /// }, 96 | /// ), 97 | /// ) 98 | /// ``` 99 | /// {@end-tool} 100 | /// 101 | /// {@macro flutter.widgets.sliverChildDelegate.lifecycle} 102 | /// 103 | /// See also: 104 | /// 105 | /// * [SliverPrototypeExtentList], which is similar to [ExtendedSliverFixedExtentList] 106 | /// except that it uses a prototype list item instead of a pixel value to define 107 | /// the main axis extent of each item. 108 | /// * [SliverFillViewport], which determines the [itemExtent] based on 109 | /// [SliverConstraints.viewportMainAxisExtent]. 110 | /// * [ExtendedSliverList], which does not require its children to have the same 111 | /// extent in the main axis. 112 | class ExtendedSliverFixedExtentList extends SliverMultiBoxAdaptorWidget { 113 | /// Creates a sliver that places box children with the same main axis extent 114 | /// in a linear array. 115 | const ExtendedSliverFixedExtentList({ 116 | Key? key, 117 | required SliverChildDelegate delegate, 118 | required this.itemExtent, 119 | required this.extendedListDelegate, 120 | }) : super(key: key, delegate: delegate); 121 | 122 | /// The extent the children are forced to have in the main axis. 123 | final double itemExtent; 124 | 125 | /// A delegate that provides extensions within the [ExtendedGridView/ExtendedList/WaterfallFlow]. 126 | final ExtendedListDelegate extendedListDelegate; 127 | @override 128 | ExtendedRenderSliverFixedExtentList createRenderObject(BuildContext context) { 129 | final SliverMultiBoxAdaptorElement element = 130 | context as SliverMultiBoxAdaptorElement; 131 | return ExtendedRenderSliverFixedExtentList( 132 | childManager: element, 133 | itemExtent: itemExtent, 134 | extendedListDelegate: extendedListDelegate, 135 | ); 136 | } 137 | 138 | @override 139 | void updateRenderObject( 140 | BuildContext context, ExtendedRenderSliverFixedExtentList renderObject) { 141 | renderObject.itemExtent = itemExtent; 142 | renderObject.extendedListDelegate = extendedListDelegate; 143 | } 144 | } 145 | 146 | /// A sliver that places multiple box children in a two dimensional arrangement. 147 | /// 148 | /// [ExtendedSliverGrid] places its children in arbitrary positions determined by 149 | /// [gridDelegate]. Each child is forced to have the size specified by the 150 | /// [gridDelegate]. 151 | /// 152 | /// The main axis direction of a grid is the direction in which it scrolls; the 153 | /// cross axis direction is the orthogonal direction. 154 | /// 155 | /// {@youtube 560 315 https://www.youtube.com/watch?v=ORiTTaVY6mM} 156 | /// 157 | /// {@tool sample} 158 | /// 159 | /// This example, which would be inserted into a [CustomScrollView.slivers] 160 | /// list, shows twenty boxes in a pretty teal grid: 161 | /// 162 | /// ```dart 163 | /// SliverGrid( 164 | /// gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( 165 | /// maxCrossAxisExtent: 200.0, 166 | /// mainAxisSpacing: 10.0, 167 | /// crossAxisSpacing: 10.0, 168 | /// childAspectRatio: 4.0, 169 | /// ), 170 | /// delegate: SliverChildBuilderDelegate( 171 | /// (BuildContext context, int index) { 172 | /// return Container( 173 | /// alignment: Alignment.center, 174 | /// color: Colors.teal[100 * (index % 9)], 175 | /// child: Text('grid item $index'), 176 | /// ); 177 | /// }, 178 | /// childCount: 20, 179 | /// ), 180 | /// ) 181 | /// ``` 182 | /// {@end-tool} 183 | /// 184 | /// {@macro flutter.widgets.sliverChildDelegate.lifecycle} 185 | /// 186 | /// See also: 187 | /// 188 | /// * [ExtendedSliverList], which places its children in a linear array. 189 | /// * [ExtendedSliverFixedExtentList], which places its children in a linear 190 | /// array with a fixed extent in the main axis. 191 | /// * [SliverPrototypeExtentList], which is similar to [ExtendedSliverFixedExtentList] 192 | /// except that it uses a prototype list item instead of a pixel value to define 193 | /// the main axis extent of each item. 194 | class ExtendedSliverGrid extends SliverMultiBoxAdaptorWidget { 195 | /// Creates a sliver that places multiple box children in a two dimensional 196 | /// arrangement. 197 | const ExtendedSliverGrid({ 198 | Key? key, 199 | required SliverChildDelegate delegate, 200 | required this.gridDelegate, 201 | required this.extendedListDelegate, 202 | }) : super(key: key, delegate: delegate); 203 | 204 | /// Creates a sliver that places multiple box children in a two dimensional 205 | /// arrangement with a fixed number of tiles in the cross axis. 206 | /// 207 | /// Uses a [SliverGridDelegateWithFixedCrossAxisCount] as the [gridDelegate], 208 | /// and a [SliverChildListDelegate] as the [delegate]. 209 | /// 210 | /// See also: 211 | /// 212 | /// * [new GridView.count], the equivalent constructor for [GridView] widgets. 213 | ExtendedSliverGrid.count({ 214 | Key? key, 215 | required int crossAxisCount, 216 | double mainAxisSpacing = 0.0, 217 | double crossAxisSpacing = 0.0, 218 | double childAspectRatio = 1.0, 219 | List children = const [], 220 | required this.extendedListDelegate, 221 | }) : gridDelegate = SliverGridDelegateWithFixedCrossAxisCount( 222 | crossAxisCount: crossAxisCount, 223 | mainAxisSpacing: mainAxisSpacing, 224 | crossAxisSpacing: crossAxisSpacing, 225 | childAspectRatio: childAspectRatio, 226 | ), 227 | super(key: key, delegate: SliverChildListDelegate(children)); 228 | 229 | /// Creates a sliver that places multiple box children in a two dimensional 230 | /// arrangement with tiles that each have a maximum cross-axis extent. 231 | /// 232 | /// Uses a [SliverGridDelegateWithMaxCrossAxisExtent] as the [gridDelegate], 233 | /// and a [SliverChildListDelegate] as the [delegate]. 234 | /// 235 | /// See also: 236 | /// 237 | /// * [new GridView.extent], the equivalent constructor for [GridView] widgets. 238 | ExtendedSliverGrid.extent({ 239 | Key? key, 240 | required double maxCrossAxisExtent, 241 | double mainAxisSpacing = 0.0, 242 | double crossAxisSpacing = 0.0, 243 | double childAspectRatio = 1.0, 244 | List children = const [], 245 | required this.extendedListDelegate, 246 | }) : gridDelegate = SliverGridDelegateWithMaxCrossAxisExtent( 247 | maxCrossAxisExtent: maxCrossAxisExtent, 248 | mainAxisSpacing: mainAxisSpacing, 249 | crossAxisSpacing: crossAxisSpacing, 250 | childAspectRatio: childAspectRatio, 251 | ), 252 | super(key: key, delegate: SliverChildListDelegate(children)); 253 | 254 | /// The delegate that controls the size and position of the children. 255 | final SliverGridDelegate gridDelegate; 256 | 257 | /// A delegate that provides extensions within the [ExtendedGridView/ExtendedList/WaterfallFlow]. 258 | final ExtendedListDelegate extendedListDelegate; 259 | 260 | @override 261 | ExtendedRenderSliverGrid createRenderObject(BuildContext context) { 262 | final SliverMultiBoxAdaptorElement element = 263 | context as SliverMultiBoxAdaptorElement; 264 | return ExtendedRenderSliverGrid( 265 | childManager: element, 266 | gridDelegate: gridDelegate, 267 | extendedListDelegate: extendedListDelegate, 268 | ); 269 | } 270 | 271 | @override 272 | void updateRenderObject( 273 | BuildContext context, ExtendedRenderSliverGrid renderObject) { 274 | renderObject.gridDelegate = gridDelegate; 275 | renderObject.extendedListDelegate = extendedListDelegate; 276 | } 277 | 278 | @override 279 | double estimateMaxScrollOffset( 280 | SliverConstraints? constraints, 281 | int firstIndex, 282 | int lastIndex, 283 | double leadingScrollOffset, 284 | double trailingScrollOffset, 285 | ) { 286 | return super.estimateMaxScrollOffset( 287 | constraints, 288 | firstIndex, 289 | lastIndex, 290 | leadingScrollOffset, 291 | trailingScrollOffset, 292 | ) ?? 293 | gridDelegate 294 | .getLayout(constraints!) 295 | .computeMaxScrollOffset(delegate.estimatedChildCount!); 296 | } 297 | } 298 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | url: "https://pub.flutter-io.cn" 9 | source: hosted 10 | version: "2.5.0" 11 | boolean_selector: 12 | dependency: transitive 13 | description: 14 | name: boolean_selector 15 | url: "https://pub.flutter-io.cn" 16 | source: hosted 17 | version: "2.1.0" 18 | characters: 19 | dependency: transitive 20 | description: 21 | name: characters 22 | url: "https://pub.flutter-io.cn" 23 | source: hosted 24 | version: "1.1.0" 25 | charcode: 26 | dependency: transitive 27 | description: 28 | name: charcode 29 | url: "https://pub.flutter-io.cn" 30 | source: hosted 31 | version: "1.2.0" 32 | clock: 33 | dependency: transitive 34 | description: 35 | name: clock 36 | url: "https://pub.flutter-io.cn" 37 | source: hosted 38 | version: "1.1.0" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.flutter-io.cn" 44 | source: hosted 45 | version: "1.15.0" 46 | extended_list_library: 47 | dependency: "direct main" 48 | description: 49 | name: extended_list_library 50 | url: "https://pub.flutter-io.cn" 51 | source: hosted 52 | version: "3.0.0" 53 | fake_async: 54 | dependency: transitive 55 | description: 56 | name: fake_async 57 | url: "https://pub.flutter-io.cn" 58 | source: hosted 59 | version: "1.2.0" 60 | flutter: 61 | dependency: "direct main" 62 | description: flutter 63 | source: sdk 64 | version: "0.0.0" 65 | flutter_test: 66 | dependency: "direct dev" 67 | description: flutter 68 | source: sdk 69 | version: "0.0.0" 70 | matcher: 71 | dependency: transitive 72 | description: 73 | name: matcher 74 | url: "https://pub.flutter-io.cn" 75 | source: hosted 76 | version: "0.12.10" 77 | meta: 78 | dependency: transitive 79 | description: 80 | name: meta 81 | url: "https://pub.flutter-io.cn" 82 | source: hosted 83 | version: "1.3.0" 84 | path: 85 | dependency: transitive 86 | description: 87 | name: path 88 | url: "https://pub.flutter-io.cn" 89 | source: hosted 90 | version: "1.8.0" 91 | sky_engine: 92 | dependency: transitive 93 | description: flutter 94 | source: sdk 95 | version: "0.0.99" 96 | source_span: 97 | dependency: transitive 98 | description: 99 | name: source_span 100 | url: "https://pub.flutter-io.cn" 101 | source: hosted 102 | version: "1.8.0" 103 | stack_trace: 104 | dependency: transitive 105 | description: 106 | name: stack_trace 107 | url: "https://pub.flutter-io.cn" 108 | source: hosted 109 | version: "1.10.0" 110 | stream_channel: 111 | dependency: transitive 112 | description: 113 | name: stream_channel 114 | url: "https://pub.flutter-io.cn" 115 | source: hosted 116 | version: "2.1.0" 117 | string_scanner: 118 | dependency: transitive 119 | description: 120 | name: string_scanner 121 | url: "https://pub.flutter-io.cn" 122 | source: hosted 123 | version: "1.1.0" 124 | term_glyph: 125 | dependency: transitive 126 | description: 127 | name: term_glyph 128 | url: "https://pub.flutter-io.cn" 129 | source: hosted 130 | version: "1.2.0" 131 | test_api: 132 | dependency: transitive 133 | description: 134 | name: test_api 135 | url: "https://pub.flutter-io.cn" 136 | source: hosted 137 | version: "0.2.19" 138 | typed_data: 139 | dependency: transitive 140 | description: 141 | name: typed_data 142 | url: "https://pub.flutter-io.cn" 143 | source: hosted 144 | version: "1.3.0" 145 | vector_math: 146 | dependency: transitive 147 | description: 148 | name: vector_math 149 | url: "https://pub.flutter-io.cn" 150 | source: hosted 151 | version: "2.1.0" 152 | sdks: 153 | dart: ">=2.12.0 <3.0.0" 154 | flutter: ">=2.0.0" 155 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: extended_list 2 | description: extended list support track collect garbage/viewport indexes, build lastChild as special child and enable to layout close to trailing. 3 | version: 3.0.2 4 | homepage: https://github.com/fluttercandies/extended_list 5 | 6 | environment: 7 | sdk: '>=2.12.0 <3.0.0' 8 | flutter: ">=2.0.0" 9 | 10 | dependencies: 11 | extended_list_library: ^3.0.0 12 | flutter: 13 | sdk: flutter 14 | 15 | 16 | dev_dependencies: 17 | flutter_test: 18 | sdk: flutter 19 | 20 | # For information on the generic Dart part of this file, see the 21 | # following page: https://dart.dev/tools/pub/pubspec 22 | 23 | # The following section is specific to Flutter. 24 | flutter: 25 | 26 | # To add assets to your package, add an assets section, like this: 27 | # assets: 28 | # - images/a_dot_burr.jpeg 29 | # - images/a_dot_ham.jpeg 30 | # 31 | # For details regarding assets in packages, see 32 | # https://flutter.dev/assets-and-images/#from-packages 33 | # 34 | # An image asset can refer to one or more resolution-specific "variants", see 35 | # https://flutter.dev/assets-and-images/#resolution-aware. 36 | 37 | # To add custom fonts to your package, add a fonts section here, 38 | # in this "flutter" section. Each entry in this list should have a 39 | # "family" key with the font family name, and a "fonts" key with a 40 | # list giving the asset and other descriptors for the font. For 41 | # example: 42 | # fonts: 43 | # - family: Schyler 44 | # fonts: 45 | # - asset: fonts/Schyler-Regular.ttf 46 | # - asset: fonts/Schyler-Italic.ttf 47 | # style: italic 48 | # - family: Trajan Pro 49 | # fonts: 50 | # - asset: fonts/TrajanPro.ttf 51 | # - asset: fonts/TrajanPro_Bold.ttf 52 | # weight: 700 53 | # 54 | # For details regarding fonts in packages, see 55 | # https://flutter.dev/custom-fonts/#from-packages 56 | --------------------------------------------------------------------------------