├── .github ├── FUNDING.yml └── workflows │ └── pub_publish.yml ├── .gitignore ├── .vscode └── settings.json ├── CHANGELOG.md ├── CODEOWNERS ├── LICENSE ├── README-ZH.md ├── README.md ├── analysis_options.yaml ├── example ├── .gitignore ├── .metadata ├── README.md ├── analysis_options.yaml ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── example │ │ │ │ │ └── example │ │ │ │ │ └── MainActivity.kt │ │ │ └── res │ │ │ │ ├── drawable-v21 │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable │ │ │ │ └── launch_background.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── values-night │ │ │ │ └── styles.xml │ │ │ │ └── values │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── assets │ ├── cypridina.jpeg │ └── flutter_candies_logo.png ├── ff_annotation_route_commands ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Podfile │ ├── Podfile.lock │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ └── WorkspaceSettings.xcsettings │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── Runner-Bridging-Header.h ├── lib │ ├── example_route.dart │ ├── example_routes.dart │ ├── main.dart │ └── pages │ │ ├── complex │ │ └── home_page.dart │ │ ├── main_page.dart │ │ ├── nested │ │ └── webview.dart │ │ └── simple │ │ ├── pinned_box.dart │ │ ├── pinned_header.dart │ │ └── sliver_app_bar.dart └── pubspec.yaml ├── extended_sliver.iml ├── lib ├── extended_sliver.dart └── src │ ├── element.dart │ ├── rendering.dart │ └── widget.dart ├── pubspec.yaml └── test └── extended_sliver_test.dart /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | #github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | #patreon: # Replace with a single Patreon username 5 | #open_collective: # Replace with a single Open Collective username 6 | #ko_fi: # Replace with a single Ko-fi username 7 | #tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | #community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | #liberapay: 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/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 | # See https://www.dartlang.org/guides/libraries/private-files 2 | 3 | # Files and directories created by pub 4 | .dart_tool/ 5 | .packages 6 | build/ 7 | # If you're building an application, you may want to check-in your pubspec.lock 8 | pubspec.lock 9 | 10 | # Directory created by dartdoc 11 | # If you don't generate documentation locally you can remove this line. 12 | doc/api/ 13 | 14 | # Avoid committing generated Javascript files: 15 | *.dart.js 16 | *.info.json # Produced by the --dump-info flag. 17 | *.js # When generated by dart2js. Don't specify *.js if your 18 | # project includes source files written in JavaScript. 19 | *.js_ 20 | *.js.deps 21 | *.js.map 22 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "dart.flutterSdkPath": "/Users/roott/Documents/Tools/flutter/stable" 3 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 2.1.3 2 | 3 | * Improve the moment call onScrollOffsetChanged 4 | 5 | ## 2.1.2 6 | 7 | * Correct paintOffset when childExtent <= viewportMainAxisExtent (RenderSliverToNestedScrollBoxAdapter) 8 | 9 | ## 2.1.1 10 | 11 | * Correct assert in RenderSliverToNestedScrollBoxAdapter.setChildParentData 12 | 13 | ## 2.1.0 14 | 15 | * Add SliverToNestedScrollBoxAdapter/RenderSliverToNestedScrollBoxAdapter to support to nested scroll(like Webview in CustomScrollView/NestedScrollView) 16 | 17 | ## 2.0.1 18 | 19 | * Fix exception when unmount renderObject 20 | 21 | ## 2.0.0 22 | 23 | * Support null-safety 24 | 25 | ## 1.1.0 26 | 27 | * Correct statusbarHeight 28 | * Add isOpacityFadeWithToolbar(Whether do an opacity fade for toolbar) 29 | * Add isOpacityFadeWithTitle(Whether do an opacity fade for title) 30 | * Add mainAxisAlignment(MainAxisAlignment of toolbar) 31 | * Add crossAxisAlignment(CrossAxisAlignment of toolbar) 32 | 33 | ## 1.0.1 34 | 35 | * Correct shouldRebuild. 36 | 37 | ## 1.0.0 38 | 39 | * Change Flutter SDK version to 1.20.0 and provide 1.12.13 branch. 40 | 41 | ## 0.3.0 42 | 43 | * Auto Release. 44 | 45 | ## 0.2.0 46 | 47 | * Auto Release. 48 | 49 | ## 0.1.0 50 | 51 | * First Release. 52 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @zmtzawqlp 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 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_sliver 2 | 3 | [![pub package](https://img.shields.io/pub/v/extended_sliver.svg)](https://pub.dartlang.org/packages/extended_sliver) [![GitHub stars](https://img.shields.io/github/stars/fluttercandies/extended_sliver)](https://github.com/fluttercandies/extended_sliver/stargazers) [![GitHub forks](https://img.shields.io/github/forks/fluttercandies/extended_sliver)](https://github.com/fluttercandies/extended_sliver/network) [![GitHub license](https://img.shields.io/github/license/fluttercandies/extended_sliver)](https://github.com/fluttercandies/extended_sliver/blob/master/LICENSE) [![GitHub issues](https://img.shields.io/github/issues/fluttercandies/extended_sliver)](https://github.com/fluttercandies/extended_sliver/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 | 语言: [English](README.md) | 中文简体 6 | 7 | ## 描述 8 | 9 | 强大的Sliver扩展库, 包括 SliverToNestedScrollBoxAdapter, SliverPinnedPersistentHeader, SliverPinnedToBoxAdapter 和 ExtendedSliverAppbar. 10 | 11 | - [extended_sliver](#extended_sliver) 12 | - [描述](#描述) 13 | - [使用](#使用) 14 | - [添加引用](#添加引用) 15 | - [SliverPinnedPersistentHeader](#sliverpinnedpersistentheader) 16 | - [SliverPinnedToBoxAdapter](#sliverpinnedtoboxadapter) 17 | - [ExtendedSliverAppbar](#extendedsliverappbar) 18 | - [SliverToNestedScrollBoxAdapter](#slivertonestedscrollboxadapter) 19 | - [复杂的例子](#复杂的例子) 20 | 21 | 22 | ## 使用 23 | 24 | ### 添加引用 25 | 26 | 添加引用到 `pubspec.yaml` 下面的 `dependencies` 27 | 28 | ```yaml 29 | dependencies: 30 | extended_sliver: latest-version 31 | ``` 32 | 33 | 执行 `flutter packages get` 下载 34 | 35 | ## SliverPinnedPersistentHeader 36 | 37 | 跟官方的`SliverPersistentHeader(pinned: true)`一样, 不同的是你不需要去设置 minExtent 和 maxExtent。 38 | 39 | 它是通过设置 `minExtentProtoType` 和 `maxExtentProtoType` 来计算 minExtent 和 maxExtent。 40 | 41 | 当Widget没有layout之前,你没法知道Widget的实际大小,这将是非常有用的组件。 42 | 43 | ```dart 44 | SliverPinnedPersistentHeader( 45 | delegate: MySliverPinnedPersistentHeaderDelegate( 46 | minExtentProtoType: Container( 47 | height: 120.0, 48 | color: Colors.red.withOpacity(0.5), 49 | child: FlatButton( 50 | child: const Text('minProtoType'), 51 | onPressed: () { 52 | print('minProtoType'); 53 | }, 54 | ), 55 | alignment: Alignment.topCenter, 56 | ), 57 | maxExtentProtoType: Container( 58 | height: 200.0, 59 | color: Colors.blue, 60 | child: FlatButton( 61 | child: const Text('maxProtoType'), 62 | onPressed: () { 63 | print('maxProtoType'); 64 | }, 65 | ), 66 | alignment: Alignment.bottomCenter, 67 | ), 68 | ), 69 | ) 70 | ``` 71 | ## SliverPinnedToBoxAdapter 72 | 73 | 你可以轻松创建一个锁定的Sliver。 74 | 75 | 当child没有layout之前,你没法知道child的实际大小,这将是非常有用的组件。 76 | 77 | ```dart 78 | SliverPinnedToBoxAdapter( 79 | child: Container( 80 | padding: const EdgeInsets.all(20), 81 | color: Colors.blue.withOpacity(0.5), 82 | child: Column( 83 | children: [ 84 | const Text( 85 | '[love]Extended text help you to build rich text quickly. any special text you will have with extended text. ' 86 | '\n\nIt\'s my pleasure to invite you to join \$FlutterCandies\$ if you want to improve flutter .[love]' 87 | '\n\nif you meet any problem, please let me konw @zmtzawqlp .[sun_glasses]'), 88 | FlatButton( 89 | child: const Text('I\'m button. click me!'), 90 | onPressed: () { 91 | debugPrint('click'); 92 | }, 93 | ), 94 | ], 95 | ), 96 | ), 97 | ) 98 | ``` 99 | ## ExtendedSliverAppbar 100 | 101 | 你可以创建一个SliverAppbar,不用去设置expandedHeight。 102 | 103 | ```dart 104 | return CustomScrollView( 105 | slivers: [ 106 | ExtendedSliverAppbar( 107 | title: const Text( 108 | 'ExtendedSliverAppbar', 109 | style: TextStyle(color: Colors.white), 110 | ), 111 | leading: const BackButton( 112 | onPressed: null, 113 | color: Colors.white, 114 | ), 115 | background: Image.asset( 116 | 'assets/cypridina.jpeg', 117 | fit: BoxFit.cover, 118 | ), 119 | actions: Padding( 120 | padding: const EdgeInsets.all(10.0), 121 | child: Icon( 122 | Icons.more_horiz, 123 | color: Colors.white, 124 | ), 125 | ), 126 | ), 127 | ], 128 | ); 129 | ``` 130 | 131 | ## SliverToNestedScrollBoxAdapter 132 | 133 | 你可以在 CustomScrollView/NestedScrollView 中创建一个嵌套滚动的组件(比如 Webview). 134 | 135 | ```dart 136 | return CustomScrollView( 137 | slivers: [ 138 | SliverToBoxAdapter( 139 | child: Container( 140 | height: 100, 141 | color: Colors.red, 142 | child: const Center( 143 | child: Text( 144 | 'Header', 145 | style: TextStyle(color: Colors.white), 146 | ), 147 | ), 148 | ), 149 | ), 150 | ValueListenableBuilder( 151 | valueListenable: nestedWebviewController.scrollHeightNotifier, 152 | builder: ( 153 | BuildContext context, 154 | double scrollHeight, 155 | Widget? child, 156 | ) { 157 | return SliverToNestedScrollBoxAdapter( 158 | childExtent: scrollHeight, 159 | onScrollOffsetChanged: (double scrollOffset) { 160 | double y = scrollOffset; 161 | if (Platform.isAndroid) { 162 | // https://github.com/flutter/flutter/issues/75841 163 | y *= window.devicePixelRatio; 164 | 165 | nestedWebviewController.webviewController 166 | ?.scrollTo(0, y.ceil()); 167 | }, 168 | child: child, 169 | ); 170 | }, 171 | child: WebView( 172 | initialUrl: nestedWebviewController.initialUrl, 173 | onPageStarted: nestedWebviewController.onPageStarted, 174 | onPageFinished: nestedWebviewController.onPageFinished, 175 | onWebResourceError: 176 | nestedWebviewController.onWebResourceError, 177 | onWebViewCreated: nestedWebviewController.onWebViewCreated, 178 | onProgress: nestedWebviewController.onProgress, 179 | javascriptChannels: { 180 | nestedWebviewController 181 | .scrollHeightNotifierJavascriptChannel 182 | }, 183 | javascriptMode: JavascriptMode.unrestricted, 184 | ), 185 | ), 186 | SliverToBoxAdapter( 187 | child: Container( 188 | height: 300, 189 | color: Colors.green, 190 | child: const Center( 191 | child: Text( 192 | 'Footer', 193 | style: TextStyle(color: Colors.white), 194 | ), 195 | ), 196 | ), 197 | ), 198 | ], 199 | ); 200 | ``` 201 | 202 | ## 复杂的例子 203 | 204 | [例子地址](https://github.com/fluttercandies/extended_sliver/blob/master/example/lib/pages/complex/home_page.dart) 205 | 206 | ![image](http://zmtzawqlp.gitee.io/my_images/images/extended_sliver/extended_sliver.gif) 207 | 208 | 209 | 210 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # extended_sliver 2 | 3 | [![pub package](https://img.shields.io/pub/v/extended_sliver.svg)](https://pub.dartlang.org/packages/extended_sliver) [![GitHub stars](https://img.shields.io/github/stars/fluttercandies/extended_sliver)](https://github.com/fluttercandies/extended_sliver/stargazers) [![GitHub forks](https://img.shields.io/github/forks/fluttercandies/extended_sliver)](https://github.com/fluttercandies/extended_sliver/network) [![GitHub license](https://img.shields.io/github/license/fluttercandies/extended_sliver)](https://github.com/fluttercandies/extended_sliver/blob/master/LICENSE) [![GitHub issues](https://img.shields.io/github/issues/fluttercandies/extended_sliver)](https://github.com/fluttercandies/extended_sliver/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 | ## Description 8 | 9 | A powerful extension library of Sliver, which include SliverToNestedScrollBoxAdapter, SliverPinnedPersistentHeader, SliverPinnedToBoxAdapter and ExtendedSliverAppbar. 10 | 11 | - [extended_sliver](#extended_sliver) 12 | - [Description](#description) 13 | - [Usage](#usage) 14 | - [Add packages to dependencies](#add-packages-to-dependencies) 15 | - [SliverPinnedPersistentHeader](#sliverpinnedpersistentheader) 16 | - [SliverPinnedToBoxAdapter](#sliverpinnedtoboxadapter) 17 | - [ExtendedSliverAppbar](#extendedsliverappbar) 18 | - [SliverToNestedScrollBoxAdapter](#slivertonestedscrollboxadapter) 19 | - [Complex Demo](#complex-demo) 20 | 21 | 22 | ## Usage 23 | 24 | ### Add packages to dependencies 25 | 26 | Add the package to `pubspec.yaml` under `dependencies`. 27 | 28 | ```yaml 29 | dependencies: 30 | extended_sliver: latest-version 31 | ``` 32 | 33 | Download with `flutter packages get` 34 | 35 | ## SliverPinnedPersistentHeader 36 | 37 | It's the same as `SliverPersistentHeader(pinned: true)`, but you don't have to force values of minExtent and maxExtent. 38 | 39 | It provides `minExtentProtoType` and `maxExtentProtoType` to calculate minExtent and maxExtent automatically. 40 | 41 | It's useful that you don't know the final extent before the widgets are laid out. 42 | 43 | ```dart 44 | SliverPinnedPersistentHeader( 45 | delegate: MySliverPinnedPersistentHeaderDelegate( 46 | minExtentProtoType: Container( 47 | height: 120.0, 48 | color: Colors.red.withOpacity(0.5), 49 | child: FlatButton( 50 | child: const Text('minProtoType'), 51 | onPressed: () { 52 | print('minProtoType'); 53 | }, 54 | ), 55 | alignment: Alignment.topCenter, 56 | ), 57 | maxExtentProtoType: Container( 58 | height: 200.0, 59 | color: Colors.blue, 60 | child: FlatButton( 61 | child: const Text('maxProtoType'), 62 | onPressed: () { 63 | print('maxProtoType'); 64 | }, 65 | ), 66 | alignment: Alignment.bottomCenter, 67 | ), 68 | ), 69 | ) 70 | ``` 71 | ## SliverPinnedToBoxAdapter 72 | 73 | You can create a pinned Sliver easily with it. 74 | 75 | It's useful that you don't know the final extent before the child are laid out. 76 | 77 | ```dart 78 | SliverPinnedToBoxAdapter( 79 | child: Container( 80 | padding: const EdgeInsets.all(20), 81 | color: Colors.blue.withOpacity(0.5), 82 | child: Column( 83 | children: [ 84 | const Text( 85 | '[love]Extended text help you to build rich text quickly. any special text you will have with extended text. ' 86 | '\n\nIt\'s my pleasure to invite you to join \$FlutterCandies\$ if you want to improve flutter .[love]' 87 | '\n\nif you meet any problem, please let me konw @zmtzawqlp .[sun_glasses]'), 88 | FlatButton( 89 | child: const Text('I\'m button. click me!'), 90 | onPressed: () { 91 | debugPrint('click'); 92 | }, 93 | ), 94 | ], 95 | ), 96 | ), 97 | ) 98 | ``` 99 | ## ExtendedSliverAppbar 100 | 101 | You can create SliverAppbar with out force the expandedHeight. 102 | 103 | ```dart 104 | return CustomScrollView( 105 | slivers: [ 106 | ExtendedSliverAppbar( 107 | title: const Text( 108 | 'ExtendedSliverAppbar', 109 | style: TextStyle(color: Colors.white), 110 | ), 111 | leading: const BackButton( 112 | onPressed: null, 113 | color: Colors.white, 114 | ), 115 | background: Image.asset( 116 | 'assets/cypridina.jpeg', 117 | fit: BoxFit.cover, 118 | ), 119 | actions: Padding( 120 | padding: const EdgeInsets.all(10.0), 121 | child: Icon( 122 | Icons.more_horiz, 123 | color: Colors.white, 124 | ), 125 | ), 126 | ), 127 | ], 128 | ); 129 | ``` 130 | 131 | ## SliverToNestedScrollBoxAdapter 132 | 133 | You can create nested scrollable widget(like Webview) in CustomScrollView/NestedScrollView. 134 | 135 | ```dart 136 | return CustomScrollView( 137 | slivers: [ 138 | SliverToBoxAdapter( 139 | child: Container( 140 | height: 100, 141 | color: Colors.red, 142 | child: const Center( 143 | child: Text( 144 | 'Header', 145 | style: TextStyle(color: Colors.white), 146 | ), 147 | ), 148 | ), 149 | ), 150 | ValueListenableBuilder( 151 | valueListenable: nestedWebviewController.scrollHeightNotifier, 152 | builder: ( 153 | BuildContext context, 154 | double scrollHeight, 155 | Widget? child, 156 | ) { 157 | return SliverToNestedScrollBoxAdapter( 158 | childExtent: scrollHeight, 159 | onScrollOffsetChanged: (double scrollOffset) { 160 | double y = scrollOffset; 161 | if (Platform.isAndroid) { 162 | // https://github.com/flutter/flutter/issues/75841 163 | y *= window.devicePixelRatio; 164 | 165 | nestedWebviewController.webviewController 166 | ?.scrollTo(0, y.ceil()); 167 | }, 168 | child: child, 169 | ); 170 | }, 171 | child: WebView( 172 | initialUrl: nestedWebviewController.initialUrl, 173 | onPageStarted: nestedWebviewController.onPageStarted, 174 | onPageFinished: nestedWebviewController.onPageFinished, 175 | onWebResourceError: 176 | nestedWebviewController.onWebResourceError, 177 | onWebViewCreated: nestedWebviewController.onWebViewCreated, 178 | onProgress: nestedWebviewController.onProgress, 179 | javascriptChannels: { 180 | nestedWebviewController 181 | .scrollHeightNotifierJavascriptChannel 182 | }, 183 | javascriptMode: JavascriptMode.unrestricted, 184 | ), 185 | ), 186 | SliverToBoxAdapter( 187 | child: Container( 188 | height: 300, 189 | color: Colors.green, 190 | child: const Center( 191 | child: Text( 192 | 'Footer', 193 | style: TextStyle(color: Colors.white), 194 | ), 195 | ), 196 | ), 197 | ), 198 | ], 199 | ); 200 | ``` 201 | 202 | ## Complex Demo 203 | 204 | [Complex Demo](https://github.com/fluttercandies/extended_sliver/blob/master/example/lib/pages/complex/home_page.dart) 205 | 206 | ![image](https://github.com/fluttercandies/flutter_candies/blob/master/gif/extended_sliver/extended_sliver.gif) 207 | 208 | 209 | 210 | -------------------------------------------------------------------------------- /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 | # // ignore: xxx 22 | 23 | # ignore_for_file: argument_type_not_assignable 24 | 25 | # 或者使用这种方式取消某个检查 26 | # // ignore: xxx 27 | 28 | # 或者取消这些文件的这些检查 29 | # ignore_for_file: unused_import, unused_local_variable, omit_local_variable_types 30 | 31 | # 完整Lint支持列表 https://dart-lang.github.io/linter/lints/ 32 | 33 | analyzer: 34 | strong-mode: 35 | implicit-casts: false 36 | implicit-dynamic: false 37 | enable-experiment: 38 | - extension-methods 39 | errors: 40 | # treat missing required parameters as a warning (not a hint) 41 | missing_required_param: warning 42 | # treat missing returns as a warning (not a hint) 43 | missing_return: warning 44 | # allow having TODOs in the code 45 | todo: ignore 46 | # Ignore analyzer hints for updating pubspecs when using Future or 47 | # Stream and not importing dart:async 48 | # Please see https://github.com/flutter/flutter/pull/24528 for details. 49 | sdk_version_async_exported_from_core: ignore 50 | # exclude: 51 | # - "lib/emis_flutter_route.dart" 52 | 53 | linter: 54 | rules: 55 | # these rules are documented on and in the same order as 56 | # the Dart Lint rules page to make maintenance easier 57 | # https://github.com/dart-lang/linter/blob/master/example/all.yaml 58 | - always_declare_return_types 59 | - always_put_control_body_on_new_line 60 | # - always_put_required_named_parameters_first # we prefer having parameters in the same order as fields https://github.com/flutter/flutter/issues/10219 61 | - always_require_non_null_named_parameters 62 | - always_specify_types 63 | - annotate_overrides 64 | # - avoid_annotating_with_dynamic # conflicts with always_specify_types 65 | # - avoid_as # required for implicit-casts: true 66 | - avoid_bool_literals_in_conditional_expressions 67 | # - avoid_catches_without_on_clauses # we do this commonly 68 | # - avoid_catching_errors # we do this commonly 69 | # 过于严格 - avoid_classes_with_only_static_members 70 | # - avoid_double_and_int_checks # only useful when targeting JS runtime 71 | - avoid_empty_else 72 | - avoid_equals_and_hash_code_on_mutable_classes 73 | - avoid_field_initializers_in_const_classes 74 | - avoid_function_literals_in_foreach_calls 75 | # - avoid_implementing_value_types # not yet tested 76 | - avoid_init_to_null 77 | # - avoid_js_rounded_ints # only useful when targeting JS runtime 78 | - avoid_null_checks_in_equality_operators 79 | # - avoid_positional_boolean_parameters # not yet tested 80 | # - avoid_print # not yet tested 81 | # - avoid_private_typedef_functions # we prefer having typedef (discussion in https://github.com/flutter/flutter/pull/16356) 82 | # - avoid_redundant_argument_values # not yet tested 83 | - avoid_relative_lib_imports 84 | - avoid_renaming_method_parameters 85 | - avoid_return_types_on_setters 86 | # - avoid_returning_null # there are plenty of valid reasons to return null 87 | # - avoid_returning_null_for_future # not yet tested 88 | - avoid_returning_null_for_void 89 | # - avoid_returning_this # there are plenty of valid reasons to return this 90 | # - avoid_setters_without_getters # not yet tested 91 | # - avoid_shadowing_type_parameters # not yet tested 92 | - avoid_single_cascade_in_expression_statements 93 | - avoid_slow_async_io 94 | - avoid_types_as_parameter_names 95 | # - avoid_types_on_closure_parameters # conflicts with always_specify_types 96 | # - avoid_unnecessary_containers # not yet tested 97 | - avoid_unused_constructor_parameters 98 | - avoid_void_async 99 | # - avoid_web_libraries_in_flutter # not yet tested 100 | - await_only_futures 101 | - camel_case_extensions 102 | - camel_case_types 103 | - cancel_subscriptions 104 | # - cascade_invocations # not yet tested 105 | # - close_sinks # not reliable enough 106 | # - comment_references # blocked on https://github.com/flutter/flutter/issues/20765 107 | # - constant_identifier_names # needs an opt-out https://github.com/dart-lang/linter/issues/204 108 | - control_flow_in_finally 109 | # - curly_braces_in_flow_control_structures # not yet tested 110 | # - diagnostic_describe_all_properties # not yet tested 111 | - directives_ordering 112 | - empty_catches 113 | - empty_constructor_bodies 114 | - empty_statements 115 | # - file_names # not yet tested 116 | - flutter_style_todos 117 | - hash_and_equals 118 | - implementation_imports 119 | # - invariant_booleans # too many false positives: https://github.com/dart-lang/linter/issues/811 120 | - iterable_contains_unrelated_type 121 | # - join_return_with_assignment # not yet tested 122 | - library_names 123 | - library_prefixes 124 | # - lines_longer_than_80_chars # not yet tested 125 | - list_remove_unrelated_type 126 | # - literal_only_boolean_expressions # too many false positives: https://github.com/dart-lang/sdk/issues/34181 127 | # - missing_whitespace_between_adjacent_strings # not yet tested 128 | - no_adjacent_strings_in_list 129 | - no_duplicate_case_values 130 | # - no_logic_in_create_state # not yet tested 131 | # - no_runtimeType_toString # not yet tested 132 | - non_constant_identifier_names 133 | # - null_closures # not yet tested 134 | # - omit_local_variable_types # opposite of always_specify_types 135 | # - one_member_abstracts # too many false positives 136 | # - only_throw_errors # https://github.com/flutter/flutter/issues/5792 137 | - overridden_fields 138 | - package_api_docs 139 | - package_names 140 | - package_prefixed_library_names 141 | # - parameter_assignments # we do this commonly 142 | - prefer_adjacent_string_concatenation 143 | - prefer_asserts_in_initializer_lists 144 | # - prefer_asserts_with_message # not yet tested 145 | - prefer_collection_literals 146 | - prefer_conditional_assignment 147 | - prefer_const_constructors 148 | - prefer_const_constructors_in_immutables 149 | - prefer_const_declarations 150 | - prefer_const_literals_to_create_immutables 151 | # - prefer_constructors_over_static_methods # not yet tested 152 | - prefer_contains 153 | # - prefer_double_quotes # opposite of prefer_single_quotes 154 | - prefer_equal_for_default_values 155 | # - prefer_expression_function_bodies # conflicts with https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#consider-using--for-short-functions-and-methods 156 | - prefer_final_fields 157 | - prefer_final_in_for_each 158 | - prefer_final_locals 159 | - prefer_for_elements_to_map_fromIterable 160 | - prefer_foreach 161 | # - prefer_function_declarations_over_variables # not yet tested 162 | - prefer_generic_function_type_aliases 163 | - prefer_if_elements_to_conditional_expressions 164 | - prefer_if_null_operators 165 | - prefer_initializing_formals 166 | - prefer_inlined_adds 167 | # - prefer_int_literals # not yet tested 168 | # - prefer_interpolation_to_compose_strings # not yet tested 169 | - prefer_is_empty 170 | - prefer_is_not_empty 171 | - prefer_is_not_operator 172 | - prefer_iterable_whereType 173 | # - prefer_mixin # https://github.com/dart-lang/language/issues/32 174 | # - prefer_null_aware_operators # disable until NNBD, see https://github.com/flutter/flutter/pull/32711#issuecomment-492930932 175 | # - prefer_relative_imports # not yet tested 176 | - prefer_single_quotes 177 | - prefer_spread_collections 178 | - prefer_typing_uninitialized_variables 179 | - prefer_void_to_null 180 | # - provide_deprecation_message # not yet tested 181 | # - public_member_api_docs # enabled on a case-by-case basis; see e.g. packages/analysis_options.yaml 182 | - recursive_getters 183 | - slash_for_doc_comments 184 | # - sort_child_properties_last # not yet tested 185 | - sort_constructors_first 186 | - sort_pub_dependencies 187 | - sort_unnamed_constructors_first 188 | - test_types_in_equals 189 | - throw_in_finally 190 | # - type_annotate_public_apis # subset of always_specify_types 191 | - type_init_formals 192 | # - unawaited_futures # too many false positives 193 | # - unnecessary_await_in_return # not yet tested 194 | - unnecessary_brace_in_string_interps 195 | - unnecessary_const 196 | # - unnecessary_final # conflicts with prefer_final_locals 197 | - unnecessary_getters_setters 198 | # - unnecessary_lambdas # has false positives: https://github.com/dart-lang/linter/issues/498 199 | - unnecessary_new 200 | - unnecessary_null_aware_assignments 201 | - unnecessary_null_in_if_null_operators 202 | - unnecessary_overrides 203 | - unnecessary_parenthesis 204 | - unnecessary_statements 205 | #1.12不支持- unnecessary_string_interpolations 206 | - unnecessary_this 207 | - unrelated_type_equality_checks 208 | # - unsafe_html # not yet tested 209 | - use_full_hex_values_for_flutter_colors 210 | # - use_function_type_syntax_for_parameters # not yet tested 211 | # - use_key_in_widget_constructors # not yet tested 212 | - use_rethrow_when_possible 213 | # - use_setters_to_change_properties # not yet tested 214 | # - use_string_buffers # has false positives: https://github.com/dart-lang/sdk/issues/34182 215 | # - use_to_and_as_if_applicable # has false positives, so we prefer to catch this by code-review 216 | - valid_regexps 217 | - void_checks 218 | -------------------------------------------------------------------------------- /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 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | 33 | # Web related 34 | lib/generated_plugin_registrant.dart 35 | 36 | # Exceptions to above rules. 37 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 38 | -------------------------------------------------------------------------------- /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: e50a39e672183866055506e14cdf5e2d211d0e6a 8 | channel: v1.12.13-hotfixes 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/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 | # // ignore: xxx 22 | 23 | # ignore_for_file: argument_type_not_assignable 24 | 25 | # 或者使用这种方式取消某个检查 26 | # // ignore: xxx 27 | 28 | # 或者取消这些文件的这些检查 29 | # ignore_for_file: unused_import, unused_local_variable, omit_local_variable_types 30 | 31 | # 完整Lint支持列表 https://dart-lang.github.io/linter/lints/ 32 | 33 | analyzer: 34 | strong-mode: 35 | implicit-casts: false 36 | implicit-dynamic: false 37 | enable-experiment: 38 | - extension-methods 39 | errors: 40 | # treat missing required parameters as a warning (not a hint) 41 | missing_required_param: warning 42 | # treat missing returns as a warning (not a hint) 43 | missing_return: warning 44 | # allow having TODOs in the code 45 | todo: ignore 46 | # Ignore analyzer hints for updating pubspecs when using Future or 47 | # Stream and not importing dart:async 48 | # Please see https://github.com/flutter/flutter/pull/24528 for details. 49 | sdk_version_async_exported_from_core: ignore 50 | # exclude: 51 | # - "lib/emis_flutter_route.dart" 52 | 53 | linter: 54 | rules: 55 | # these rules are documented on and in the same order as 56 | # the Dart Lint rules page to make maintenance easier 57 | # https://github.com/dart-lang/linter/blob/master/example/all.yaml 58 | - always_declare_return_types 59 | - always_put_control_body_on_new_line 60 | # - always_put_required_named_parameters_first # we prefer having parameters in the same order as fields https://github.com/flutter/flutter/issues/10219 61 | - always_require_non_null_named_parameters 62 | - always_specify_types 63 | - annotate_overrides 64 | # - avoid_annotating_with_dynamic # conflicts with always_specify_types 65 | # - avoid_as # required for implicit-casts: true 66 | - avoid_bool_literals_in_conditional_expressions 67 | # - avoid_catches_without_on_clauses # we do this commonly 68 | # - avoid_catching_errors # we do this commonly 69 | # 过于严格 - avoid_classes_with_only_static_members 70 | # - avoid_double_and_int_checks # only useful when targeting JS runtime 71 | - avoid_empty_else 72 | - avoid_equals_and_hash_code_on_mutable_classes 73 | - avoid_field_initializers_in_const_classes 74 | - avoid_function_literals_in_foreach_calls 75 | # - avoid_implementing_value_types # not yet tested 76 | - avoid_init_to_null 77 | # - avoid_js_rounded_ints # only useful when targeting JS runtime 78 | - avoid_null_checks_in_equality_operators 79 | # - avoid_positional_boolean_parameters # not yet tested 80 | # - avoid_print # not yet tested 81 | # - avoid_private_typedef_functions # we prefer having typedef (discussion in https://github.com/flutter/flutter/pull/16356) 82 | # - avoid_redundant_argument_values # not yet tested 83 | - avoid_relative_lib_imports 84 | - avoid_renaming_method_parameters 85 | - avoid_return_types_on_setters 86 | # - avoid_returning_null # there are plenty of valid reasons to return null 87 | # - avoid_returning_null_for_future # not yet tested 88 | - avoid_returning_null_for_void 89 | # - avoid_returning_this # there are plenty of valid reasons to return this 90 | # - avoid_setters_without_getters # not yet tested 91 | # - avoid_shadowing_type_parameters # not yet tested 92 | - avoid_single_cascade_in_expression_statements 93 | - avoid_slow_async_io 94 | - avoid_types_as_parameter_names 95 | # - avoid_types_on_closure_parameters # conflicts with always_specify_types 96 | # - avoid_unnecessary_containers # not yet tested 97 | - avoid_unused_constructor_parameters 98 | - avoid_void_async 99 | # - avoid_web_libraries_in_flutter # not yet tested 100 | - await_only_futures 101 | - camel_case_extensions 102 | - camel_case_types 103 | - cancel_subscriptions 104 | # - cascade_invocations # not yet tested 105 | # - close_sinks # not reliable enough 106 | # - comment_references # blocked on https://github.com/flutter/flutter/issues/20765 107 | # - constant_identifier_names # needs an opt-out https://github.com/dart-lang/linter/issues/204 108 | - control_flow_in_finally 109 | # - curly_braces_in_flow_control_structures # not yet tested 110 | # - diagnostic_describe_all_properties # not yet tested 111 | - directives_ordering 112 | - empty_catches 113 | - empty_constructor_bodies 114 | - empty_statements 115 | # - file_names # not yet tested 116 | - flutter_style_todos 117 | - hash_and_equals 118 | - implementation_imports 119 | # - invariant_booleans # too many false positives: https://github.com/dart-lang/linter/issues/811 120 | - iterable_contains_unrelated_type 121 | # - join_return_with_assignment # not yet tested 122 | - library_names 123 | - library_prefixes 124 | # - lines_longer_than_80_chars # not yet tested 125 | - list_remove_unrelated_type 126 | # - literal_only_boolean_expressions # too many false positives: https://github.com/dart-lang/sdk/issues/34181 127 | # - missing_whitespace_between_adjacent_strings # not yet tested 128 | - no_adjacent_strings_in_list 129 | - no_duplicate_case_values 130 | # - no_logic_in_create_state # not yet tested 131 | # - no_runtimeType_toString # not yet tested 132 | - non_constant_identifier_names 133 | # - null_closures # not yet tested 134 | # - omit_local_variable_types # opposite of always_specify_types 135 | # - one_member_abstracts # too many false positives 136 | # - only_throw_errors # https://github.com/flutter/flutter/issues/5792 137 | - overridden_fields 138 | - package_api_docs 139 | - package_names 140 | - package_prefixed_library_names 141 | # - parameter_assignments # we do this commonly 142 | - prefer_adjacent_string_concatenation 143 | - prefer_asserts_in_initializer_lists 144 | # - prefer_asserts_with_message # not yet tested 145 | - prefer_collection_literals 146 | - prefer_conditional_assignment 147 | - prefer_const_constructors 148 | - prefer_const_constructors_in_immutables 149 | - prefer_const_declarations 150 | - prefer_const_literals_to_create_immutables 151 | # - prefer_constructors_over_static_methods # not yet tested 152 | - prefer_contains 153 | # - prefer_double_quotes # opposite of prefer_single_quotes 154 | - prefer_equal_for_default_values 155 | # - prefer_expression_function_bodies # conflicts with https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#consider-using--for-short-functions-and-methods 156 | - prefer_final_fields 157 | - prefer_final_in_for_each 158 | - prefer_final_locals 159 | - prefer_for_elements_to_map_fromIterable 160 | - prefer_foreach 161 | # - prefer_function_declarations_over_variables # not yet tested 162 | - prefer_generic_function_type_aliases 163 | - prefer_if_elements_to_conditional_expressions 164 | - prefer_if_null_operators 165 | - prefer_initializing_formals 166 | - prefer_inlined_adds 167 | # - prefer_int_literals # not yet tested 168 | # - prefer_interpolation_to_compose_strings # not yet tested 169 | - prefer_is_empty 170 | - prefer_is_not_empty 171 | - prefer_is_not_operator 172 | - prefer_iterable_whereType 173 | # - prefer_mixin # https://github.com/dart-lang/language/issues/32 174 | # - prefer_null_aware_operators # disable until NNBD, see https://github.com/flutter/flutter/pull/32711#issuecomment-492930932 175 | # - prefer_relative_imports # not yet tested 176 | - prefer_single_quotes 177 | - prefer_spread_collections 178 | - prefer_typing_uninitialized_variables 179 | - prefer_void_to_null 180 | # - provide_deprecation_message # not yet tested 181 | # - public_member_api_docs # enabled on a case-by-case basis; see e.g. packages/analysis_options.yaml 182 | - recursive_getters 183 | - slash_for_doc_comments 184 | # - sort_child_properties_last # not yet tested 185 | - sort_constructors_first 186 | #- sort_pub_dependencies 187 | - sort_unnamed_constructors_first 188 | - test_types_in_equals 189 | - throw_in_finally 190 | # - type_annotate_public_apis # subset of always_specify_types 191 | - type_init_formals 192 | # - unawaited_futures # too many false positives 193 | # - unnecessary_await_in_return # not yet tested 194 | - unnecessary_brace_in_string_interps 195 | - unnecessary_const 196 | # - unnecessary_final # conflicts with prefer_final_locals 197 | - unnecessary_getters_setters 198 | # - unnecessary_lambdas # has false positives: https://github.com/dart-lang/linter/issues/498 199 | - unnecessary_new 200 | - unnecessary_null_aware_assignments 201 | - unnecessary_null_in_if_null_operators 202 | - unnecessary_overrides 203 | - unnecessary_parenthesis 204 | - unnecessary_statements 205 | #1.12不支持- unnecessary_string_interpolations 206 | - unnecessary_this 207 | - unrelated_type_equality_checks 208 | # - unsafe_html # not yet tested 209 | - use_full_hex_values_for_flutter_colors 210 | # - use_function_type_syntax_for_parameters # not yet tested 211 | # - use_key_in_widget_constructors # not yet tested 212 | - use_rethrow_when_possible 213 | # - use_setters_to_change_properties # not yet tested 214 | # - use_string_buffers # has false positives: https://github.com/dart-lang/sdk/issues/34182 215 | # - use_to_and_as_if_applicable # has false positives, so we prefer to catch this by code-review 216 | - valid_regexps 217 | - void_checks 218 | -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion flutter.compileSdkVersion 30 | 31 | compileOptions { 32 | sourceCompatibility JavaVersion.VERSION_1_8 33 | targetCompatibility JavaVersion.VERSION_1_8 34 | } 35 | 36 | kotlinOptions { 37 | jvmTarget = '1.8' 38 | } 39 | 40 | sourceSets { 41 | main.java.srcDirs += 'src/main/kotlin' 42 | } 43 | 44 | defaultConfig { 45 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 46 | applicationId "com.example.example" 47 | minSdkVersion 19 48 | targetSdkVersion flutter.targetSdkVersion 49 | versionCode flutterVersionCode.toInteger() 50 | versionName flutterVersionName 51 | } 52 | 53 | buildTypes { 54 | release { 55 | // TODO: Add your own signing config for the release build. 56 | // Signing with the debug keys for now, so `flutter run --release` works. 57 | signingConfig signingConfigs.debug 58 | } 59 | } 60 | } 61 | 62 | flutter { 63 | source '../..' 64 | } 65 | 66 | dependencies { 67 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 68 | } 69 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 9 | 17 | 21 | 25 | 26 | 27 | 28 | 29 | 30 | 32 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/com/example/example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.example 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_sliver/27a0183bf3c1a377e715d63da139747acca3700a/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_sliver/27a0183bf3c1a377e715d63da139747acca3700a/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_sliver/27a0183bf3c1a377e715d63da139747acca3700a/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_sliver/27a0183bf3c1a377e715d63da139747acca3700a/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_sliver/27a0183bf3c1a377e715d63da139747acca3700a/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.6.10' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:4.1.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | mavenCentral() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 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.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /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-6.7-all.zip 7 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /example/assets/cypridina.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_sliver/27a0183bf3c1a377e715d63da139747acca3700a/example/assets/cypridina.jpeg -------------------------------------------------------------------------------- /example/assets/flutter_candies_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/extended_sliver/27a0183bf3c1a377e715d63da139747acca3700a/example/assets/flutter_candies_logo.png -------------------------------------------------------------------------------- /example/ff_annotation_route_commands: -------------------------------------------------------------------------------- 1 | -s -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 9.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_ios (0.0.1): 4 | - Flutter 5 | - webview_flutter_wkwebview (0.0.1): 6 | - Flutter 7 | 8 | DEPENDENCIES: 9 | - Flutter (from `Flutter`) 10 | - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) 11 | - webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/ios`) 12 | 13 | EXTERNAL SOURCES: 14 | Flutter: 15 | :path: Flutter 16 | url_launcher_ios: 17 | :path: ".symlinks/plugins/url_launcher_ios/ios" 18 | webview_flutter_wkwebview: 19 | :path: ".symlinks/plugins/webview_flutter_wkwebview/ios" 20 | 21 | SPEC CHECKSUMS: 22 | Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a 23 | url_launcher_ios: 839c58cdb4279282219f5e248c3321761ff3c4de 24 | webview_flutter_wkwebview: b7e70ef1ddded7e69c796c7390ee74180182971f 25 | 26 | PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c 27 | 28 | COCOAPODS: 1.11.2 29 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 11 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 12 | 44ED86DF3563187BAD071880 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FC0ED1D6B474519D408048C3 /* Pods_Runner.framework */; }; 13 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 14 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 15 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 16 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXCopyFilesBuildPhase section */ 20 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 21 | isa = PBXCopyFilesBuildPhase; 22 | buildActionMask = 2147483647; 23 | dstPath = ""; 24 | dstSubfolderSpec = 10; 25 | files = ( 26 | ); 27 | name = "Embed Frameworks"; 28 | runOnlyForDeploymentPostprocessing = 0; 29 | }; 30 | /* End PBXCopyFilesBuildPhase section */ 31 | 32 | /* Begin PBXFileReference section */ 33 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 34 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 35 | 378356C40BF60176F535EC85 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 36 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 37 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 38 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 39 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 40 | 8A8BAFBE2575D675286F5823 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 41 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 42 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 43 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 44 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 45 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 46 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 47 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 48 | BFDC950FB7E8094C920F6D93 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 49 | FC0ED1D6B474519D408048C3 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 50 | /* End PBXFileReference section */ 51 | 52 | /* Begin PBXFrameworksBuildPhase section */ 53 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 54 | isa = PBXFrameworksBuildPhase; 55 | buildActionMask = 2147483647; 56 | files = ( 57 | 44ED86DF3563187BAD071880 /* Pods_Runner.framework in Frameworks */, 58 | ); 59 | runOnlyForDeploymentPostprocessing = 0; 60 | }; 61 | /* End PBXFrameworksBuildPhase section */ 62 | 63 | /* Begin PBXGroup section */ 64 | 1A7BD180F60868F31E19BCEB /* Pods */ = { 65 | isa = PBXGroup; 66 | children = ( 67 | BFDC950FB7E8094C920F6D93 /* Pods-Runner.debug.xcconfig */, 68 | 8A8BAFBE2575D675286F5823 /* Pods-Runner.release.xcconfig */, 69 | 378356C40BF60176F535EC85 /* Pods-Runner.profile.xcconfig */, 70 | ); 71 | name = Pods; 72 | path = Pods; 73 | sourceTree = ""; 74 | }; 75 | 9740EEB11CF90186004384FC /* Flutter */ = { 76 | isa = PBXGroup; 77 | children = ( 78 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 79 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 80 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 81 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 82 | ); 83 | name = Flutter; 84 | sourceTree = ""; 85 | }; 86 | 97C146E51CF9000F007C117D = { 87 | isa = PBXGroup; 88 | children = ( 89 | 9740EEB11CF90186004384FC /* Flutter */, 90 | 97C146F01CF9000F007C117D /* Runner */, 91 | 97C146EF1CF9000F007C117D /* Products */, 92 | 1A7BD180F60868F31E19BCEB /* Pods */, 93 | C6AA4B76FA35991DD84C3B91 /* Frameworks */, 94 | ); 95 | sourceTree = ""; 96 | }; 97 | 97C146EF1CF9000F007C117D /* Products */ = { 98 | isa = PBXGroup; 99 | children = ( 100 | 97C146EE1CF9000F007C117D /* Runner.app */, 101 | ); 102 | name = Products; 103 | sourceTree = ""; 104 | }; 105 | 97C146F01CF9000F007C117D /* Runner */ = { 106 | isa = PBXGroup; 107 | children = ( 108 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 109 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 110 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 111 | 97C147021CF9000F007C117D /* Info.plist */, 112 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 113 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 114 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 115 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 116 | ); 117 | path = Runner; 118 | sourceTree = ""; 119 | }; 120 | C6AA4B76FA35991DD84C3B91 /* Frameworks */ = { 121 | isa = PBXGroup; 122 | children = ( 123 | FC0ED1D6B474519D408048C3 /* Pods_Runner.framework */, 124 | ); 125 | name = Frameworks; 126 | sourceTree = ""; 127 | }; 128 | /* End PBXGroup section */ 129 | 130 | /* Begin PBXNativeTarget section */ 131 | 97C146ED1CF9000F007C117D /* Runner */ = { 132 | isa = PBXNativeTarget; 133 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 134 | buildPhases = ( 135 | B4BC74D322B081A4C1CA3EA6 /* [CP] Check Pods Manifest.lock */, 136 | 9740EEB61CF901F6004384FC /* Run Script */, 137 | 97C146EA1CF9000F007C117D /* Sources */, 138 | 97C146EB1CF9000F007C117D /* Frameworks */, 139 | 97C146EC1CF9000F007C117D /* Resources */, 140 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 141 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 142 | 74E9B6DB107B70472B19867B /* [CP] Embed Pods Frameworks */, 143 | ); 144 | buildRules = ( 145 | ); 146 | dependencies = ( 147 | ); 148 | name = Runner; 149 | productName = Runner; 150 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 151 | productType = "com.apple.product-type.application"; 152 | }; 153 | /* End PBXNativeTarget section */ 154 | 155 | /* Begin PBXProject section */ 156 | 97C146E61CF9000F007C117D /* Project object */ = { 157 | isa = PBXProject; 158 | attributes = { 159 | LastUpgradeCheck = 1300; 160 | ORGANIZATIONNAME = ""; 161 | TargetAttributes = { 162 | 97C146ED1CF9000F007C117D = { 163 | CreatedOnToolsVersion = 7.3.1; 164 | LastSwiftMigration = 1100; 165 | }; 166 | }; 167 | }; 168 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 169 | compatibilityVersion = "Xcode 9.3"; 170 | developmentRegion = en; 171 | hasScannedForEncodings = 0; 172 | knownRegions = ( 173 | en, 174 | Base, 175 | ); 176 | mainGroup = 97C146E51CF9000F007C117D; 177 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 178 | projectDirPath = ""; 179 | projectRoot = ""; 180 | targets = ( 181 | 97C146ED1CF9000F007C117D /* Runner */, 182 | ); 183 | }; 184 | /* End PBXProject section */ 185 | 186 | /* Begin PBXResourcesBuildPhase section */ 187 | 97C146EC1CF9000F007C117D /* Resources */ = { 188 | isa = PBXResourcesBuildPhase; 189 | buildActionMask = 2147483647; 190 | files = ( 191 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 192 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 193 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 194 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 195 | ); 196 | runOnlyForDeploymentPostprocessing = 0; 197 | }; 198 | /* End PBXResourcesBuildPhase section */ 199 | 200 | /* Begin PBXShellScriptBuildPhase section */ 201 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 202 | isa = PBXShellScriptBuildPhase; 203 | buildActionMask = 2147483647; 204 | files = ( 205 | ); 206 | inputPaths = ( 207 | ); 208 | name = "Thin Binary"; 209 | outputPaths = ( 210 | ); 211 | runOnlyForDeploymentPostprocessing = 0; 212 | shellPath = /bin/sh; 213 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; 214 | }; 215 | 74E9B6DB107B70472B19867B /* [CP] Embed Pods Frameworks */ = { 216 | isa = PBXShellScriptBuildPhase; 217 | buildActionMask = 2147483647; 218 | files = ( 219 | ); 220 | inputFileListPaths = ( 221 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", 222 | ); 223 | name = "[CP] Embed Pods Frameworks"; 224 | outputFileListPaths = ( 225 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", 226 | ); 227 | runOnlyForDeploymentPostprocessing = 0; 228 | shellPath = /bin/sh; 229 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; 230 | showEnvVarsInLog = 0; 231 | }; 232 | 9740EEB61CF901F6004384FC /* Run Script */ = { 233 | isa = PBXShellScriptBuildPhase; 234 | buildActionMask = 2147483647; 235 | files = ( 236 | ); 237 | inputPaths = ( 238 | ); 239 | name = "Run Script"; 240 | outputPaths = ( 241 | ); 242 | runOnlyForDeploymentPostprocessing = 0; 243 | shellPath = /bin/sh; 244 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 245 | }; 246 | B4BC74D322B081A4C1CA3EA6 /* [CP] Check Pods Manifest.lock */ = { 247 | isa = PBXShellScriptBuildPhase; 248 | buildActionMask = 2147483647; 249 | files = ( 250 | ); 251 | inputFileListPaths = ( 252 | ); 253 | inputPaths = ( 254 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 255 | "${PODS_ROOT}/Manifest.lock", 256 | ); 257 | name = "[CP] Check Pods Manifest.lock"; 258 | outputFileListPaths = ( 259 | ); 260 | outputPaths = ( 261 | "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", 262 | ); 263 | runOnlyForDeploymentPostprocessing = 0; 264 | shellPath = /bin/sh; 265 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 266 | showEnvVarsInLog = 0; 267 | }; 268 | /* End PBXShellScriptBuildPhase section */ 269 | 270 | /* Begin PBXSourcesBuildPhase section */ 271 | 97C146EA1CF9000F007C117D /* Sources */ = { 272 | isa = PBXSourcesBuildPhase; 273 | buildActionMask = 2147483647; 274 | files = ( 275 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 276 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 277 | ); 278 | runOnlyForDeploymentPostprocessing = 0; 279 | }; 280 | /* End PBXSourcesBuildPhase section */ 281 | 282 | /* Begin PBXVariantGroup section */ 283 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 284 | isa = PBXVariantGroup; 285 | children = ( 286 | 97C146FB1CF9000F007C117D /* Base */, 287 | ); 288 | name = Main.storyboard; 289 | sourceTree = ""; 290 | }; 291 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 292 | isa = PBXVariantGroup; 293 | children = ( 294 | 97C147001CF9000F007C117D /* Base */, 295 | ); 296 | name = LaunchScreen.storyboard; 297 | sourceTree = ""; 298 | }; 299 | /* End PBXVariantGroup section */ 300 | 301 | /* Begin XCBuildConfiguration section */ 302 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 303 | isa = XCBuildConfiguration; 304 | buildSettings = { 305 | ALWAYS_SEARCH_USER_PATHS = NO; 306 | CLANG_ANALYZER_NONNULL = YES; 307 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 308 | CLANG_CXX_LIBRARY = "libc++"; 309 | CLANG_ENABLE_MODULES = YES; 310 | CLANG_ENABLE_OBJC_ARC = YES; 311 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 312 | CLANG_WARN_BOOL_CONVERSION = YES; 313 | CLANG_WARN_COMMA = YES; 314 | CLANG_WARN_CONSTANT_CONVERSION = YES; 315 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 316 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 317 | CLANG_WARN_EMPTY_BODY = YES; 318 | CLANG_WARN_ENUM_CONVERSION = YES; 319 | CLANG_WARN_INFINITE_RECURSION = YES; 320 | CLANG_WARN_INT_CONVERSION = YES; 321 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 322 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 323 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 324 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 325 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 326 | CLANG_WARN_STRICT_PROTOTYPES = YES; 327 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 328 | CLANG_WARN_UNREACHABLE_CODE = YES; 329 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 330 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 331 | COPY_PHASE_STRIP = NO; 332 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 333 | ENABLE_NS_ASSERTIONS = NO; 334 | ENABLE_STRICT_OBJC_MSGSEND = YES; 335 | GCC_C_LANGUAGE_STANDARD = gnu99; 336 | GCC_NO_COMMON_BLOCKS = YES; 337 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 338 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 339 | GCC_WARN_UNDECLARED_SELECTOR = YES; 340 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 341 | GCC_WARN_UNUSED_FUNCTION = YES; 342 | GCC_WARN_UNUSED_VARIABLE = YES; 343 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 344 | MTL_ENABLE_DEBUG_INFO = NO; 345 | SDKROOT = iphoneos; 346 | SUPPORTED_PLATFORMS = iphoneos; 347 | TARGETED_DEVICE_FAMILY = "1,2"; 348 | VALIDATE_PRODUCT = YES; 349 | }; 350 | name = Profile; 351 | }; 352 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 353 | isa = XCBuildConfiguration; 354 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 355 | buildSettings = { 356 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 357 | CLANG_ENABLE_MODULES = YES; 358 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 359 | DEVELOPMENT_TEAM = 5659C7CB4B; 360 | ENABLE_BITCODE = NO; 361 | INFOPLIST_FILE = Runner/Info.plist; 362 | LD_RUNPATH_SEARCH_PATHS = ( 363 | "$(inherited)", 364 | "@executable_path/Frameworks", 365 | ); 366 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example; 367 | PRODUCT_NAME = "$(TARGET_NAME)"; 368 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 369 | SWIFT_VERSION = 5.0; 370 | VERSIONING_SYSTEM = "apple-generic"; 371 | }; 372 | name = Profile; 373 | }; 374 | 97C147031CF9000F007C117D /* Debug */ = { 375 | isa = XCBuildConfiguration; 376 | buildSettings = { 377 | ALWAYS_SEARCH_USER_PATHS = NO; 378 | CLANG_ANALYZER_NONNULL = YES; 379 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 380 | CLANG_CXX_LIBRARY = "libc++"; 381 | CLANG_ENABLE_MODULES = YES; 382 | CLANG_ENABLE_OBJC_ARC = YES; 383 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 384 | CLANG_WARN_BOOL_CONVERSION = YES; 385 | CLANG_WARN_COMMA = YES; 386 | CLANG_WARN_CONSTANT_CONVERSION = YES; 387 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 388 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 389 | CLANG_WARN_EMPTY_BODY = YES; 390 | CLANG_WARN_ENUM_CONVERSION = YES; 391 | CLANG_WARN_INFINITE_RECURSION = YES; 392 | CLANG_WARN_INT_CONVERSION = YES; 393 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 394 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 395 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 396 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 397 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 398 | CLANG_WARN_STRICT_PROTOTYPES = YES; 399 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 400 | CLANG_WARN_UNREACHABLE_CODE = YES; 401 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 402 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 403 | COPY_PHASE_STRIP = NO; 404 | DEBUG_INFORMATION_FORMAT = dwarf; 405 | ENABLE_STRICT_OBJC_MSGSEND = YES; 406 | ENABLE_TESTABILITY = YES; 407 | GCC_C_LANGUAGE_STANDARD = gnu99; 408 | GCC_DYNAMIC_NO_PIC = NO; 409 | GCC_NO_COMMON_BLOCKS = YES; 410 | GCC_OPTIMIZATION_LEVEL = 0; 411 | GCC_PREPROCESSOR_DEFINITIONS = ( 412 | "DEBUG=1", 413 | "$(inherited)", 414 | ); 415 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 416 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 417 | GCC_WARN_UNDECLARED_SELECTOR = YES; 418 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 419 | GCC_WARN_UNUSED_FUNCTION = YES; 420 | GCC_WARN_UNUSED_VARIABLE = YES; 421 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 422 | MTL_ENABLE_DEBUG_INFO = YES; 423 | ONLY_ACTIVE_ARCH = YES; 424 | SDKROOT = iphoneos; 425 | TARGETED_DEVICE_FAMILY = "1,2"; 426 | }; 427 | name = Debug; 428 | }; 429 | 97C147041CF9000F007C117D /* Release */ = { 430 | isa = XCBuildConfiguration; 431 | buildSettings = { 432 | ALWAYS_SEARCH_USER_PATHS = NO; 433 | CLANG_ANALYZER_NONNULL = YES; 434 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 435 | CLANG_CXX_LIBRARY = "libc++"; 436 | CLANG_ENABLE_MODULES = YES; 437 | CLANG_ENABLE_OBJC_ARC = YES; 438 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 439 | CLANG_WARN_BOOL_CONVERSION = YES; 440 | CLANG_WARN_COMMA = YES; 441 | CLANG_WARN_CONSTANT_CONVERSION = YES; 442 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 443 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 444 | CLANG_WARN_EMPTY_BODY = YES; 445 | CLANG_WARN_ENUM_CONVERSION = YES; 446 | CLANG_WARN_INFINITE_RECURSION = YES; 447 | CLANG_WARN_INT_CONVERSION = YES; 448 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 449 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 450 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 451 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 452 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 453 | CLANG_WARN_STRICT_PROTOTYPES = YES; 454 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 455 | CLANG_WARN_UNREACHABLE_CODE = YES; 456 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 457 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 458 | COPY_PHASE_STRIP = NO; 459 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 460 | ENABLE_NS_ASSERTIONS = NO; 461 | ENABLE_STRICT_OBJC_MSGSEND = YES; 462 | GCC_C_LANGUAGE_STANDARD = gnu99; 463 | GCC_NO_COMMON_BLOCKS = YES; 464 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 465 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 466 | GCC_WARN_UNDECLARED_SELECTOR = YES; 467 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 468 | GCC_WARN_UNUSED_FUNCTION = YES; 469 | GCC_WARN_UNUSED_VARIABLE = YES; 470 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 471 | MTL_ENABLE_DEBUG_INFO = NO; 472 | SDKROOT = iphoneos; 473 | SUPPORTED_PLATFORMS = iphoneos; 474 | SWIFT_COMPILATION_MODE = wholemodule; 475 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 476 | TARGETED_DEVICE_FAMILY = "1,2"; 477 | VALIDATE_PRODUCT = YES; 478 | }; 479 | name = Release; 480 | }; 481 | 97C147061CF9000F007C117D /* Debug */ = { 482 | isa = XCBuildConfiguration; 483 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 484 | buildSettings = { 485 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 486 | CLANG_ENABLE_MODULES = YES; 487 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 488 | DEVELOPMENT_TEAM = 5659C7CB4B; 489 | ENABLE_BITCODE = NO; 490 | INFOPLIST_FILE = Runner/Info.plist; 491 | LD_RUNPATH_SEARCH_PATHS = ( 492 | "$(inherited)", 493 | "@executable_path/Frameworks", 494 | ); 495 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example; 496 | PRODUCT_NAME = "$(TARGET_NAME)"; 497 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 498 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 499 | SWIFT_VERSION = 5.0; 500 | VERSIONING_SYSTEM = "apple-generic"; 501 | }; 502 | name = Debug; 503 | }; 504 | 97C147071CF9000F007C117D /* Release */ = { 505 | isa = XCBuildConfiguration; 506 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 507 | buildSettings = { 508 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 509 | CLANG_ENABLE_MODULES = YES; 510 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 511 | DEVELOPMENT_TEAM = 5659C7CB4B; 512 | ENABLE_BITCODE = NO; 513 | INFOPLIST_FILE = Runner/Info.plist; 514 | LD_RUNPATH_SEARCH_PATHS = ( 515 | "$(inherited)", 516 | "@executable_path/Frameworks", 517 | ); 518 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example; 519 | PRODUCT_NAME = "$(TARGET_NAME)"; 520 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 521 | SWIFT_VERSION = 5.0; 522 | VERSIONING_SYSTEM = "apple-generic"; 523 | }; 524 | name = Release; 525 | }; 526 | /* End XCBuildConfiguration section */ 527 | 528 | /* Begin XCConfigurationList section */ 529 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 530 | isa = XCConfigurationList; 531 | buildConfigurations = ( 532 | 97C147031CF9000F007C117D /* Debug */, 533 | 97C147041CF9000F007C117D /* Release */, 534 | 249021D3217E4FDB00AE95B9 /* Profile */, 535 | ); 536 | defaultConfigurationIsVisible = 0; 537 | defaultConfigurationName = Release; 538 | }; 539 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 540 | isa = XCConfigurationList; 541 | buildConfigurations = ( 542 | 97C147061CF9000F007C117D /* Debug */, 543 | 97C147071CF9000F007C117D /* Release */, 544 | 249021D4217E4FDB00AE95B9 /* Profile */, 545 | ); 546 | defaultConfigurationIsVisible = 0; 547 | defaultConfigurationName = Release; 548 | }; 549 | /* End XCConfigurationList section */ 550 | }; 551 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 552 | } 553 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @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_sliver/27a0183bf3c1a377e715d63da139747acca3700a/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_sliver/27a0183bf3c1a377e715d63da139747acca3700a/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_sliver/27a0183bf3c1a377e715d63da139747acca3700a/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_sliver/27a0183bf3c1a377e715d63da139747acca3700a/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_sliver/27a0183bf3c1a377e715d63da139747acca3700a/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_sliver/27a0183bf3c1a377e715d63da139747acca3700a/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_sliver/27a0183bf3c1a377e715d63da139747acca3700a/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_sliver/27a0183bf3c1a377e715d63da139747acca3700a/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_sliver/27a0183bf3c1a377e715d63da139747acca3700a/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_sliver/27a0183bf3c1a377e715d63da139747acca3700a/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_sliver/27a0183bf3c1a377e715d63da139747acca3700a/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_sliver/27a0183bf3c1a377e715d63da139747acca3700a/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_sliver/27a0183bf3c1a377e715d63da139747acca3700a/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_sliver/27a0183bf3c1a377e715d63da139747acca3700a/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_sliver/27a0183bf3c1a377e715d63da139747acca3700a/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_sliver/27a0183bf3c1a377e715d63da139747acca3700a/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_sliver/27a0183bf3c1a377e715d63da139747acca3700a/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_sliver/27a0183bf3c1a377e715d63da139747acca3700a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | Example 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | example 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(FLUTTER_BUILD_NUMBER) 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UIViewControllerBasedStatusBarAppearance 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /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,unnecessary_import 6 | import 'package:ff_annotation_route_library/ff_annotation_route_library.dart'; 7 | import 'package:flutter/widgets.dart'; 8 | 9 | import 'pages/complex/home_page.dart'; 10 | import 'pages/main_page.dart'; 11 | import 'pages/nested/webview.dart'; 12 | import 'pages/simple/pinned_box.dart'; 13 | import 'pages/simple/pinned_header.dart'; 14 | import 'pages/simple/sliver_app_bar.dart'; 15 | 16 | FFRouteSettings getRouteSettings({ 17 | required String name, 18 | Map? arguments, 19 | PageBuilder? notFoundPageBuilder, 20 | }) { 21 | final Map safeArguments = 22 | arguments ?? const {}; 23 | switch (name) { 24 | case 'fluttercandies://NestedWebviewDemo': 25 | return FFRouteSettings( 26 | name: name, 27 | arguments: arguments, 28 | builder: () => NestedWebviewDemo( 29 | key: asT( 30 | safeArguments['key'], 31 | ), 32 | ), 33 | routeName: 'NestedWebviewDemo', 34 | description: 'show how to nested webview in customscrollview', 35 | exts: {'group': 'Nested', 'order': 0}, 36 | ); 37 | case 'fluttercandies://PinnedBox': 38 | return FFRouteSettings( 39 | name: name, 40 | arguments: arguments, 41 | builder: () => PinnedBox(), 42 | routeName: 'PinnedBox', 43 | description: 'simple pinned box.', 44 | exts: {'group': 'Simple', 'order': 1}, 45 | ); 46 | case 'fluttercandies://PinnedHeader': 47 | return FFRouteSettings( 48 | name: name, 49 | arguments: arguments, 50 | builder: () => PinnedHeader(), 51 | routeName: 'PinnedHeader', 52 | description: 'pinned header without minExtent and maxExtent.', 53 | exts: {'group': 'Simple', 'order': 0}, 54 | ); 55 | case 'fluttercandies://demogrouppage': 56 | return FFRouteSettings( 57 | name: name, 58 | arguments: arguments, 59 | builder: () => DemoGroupPage( 60 | keyValue: asT>>( 61 | safeArguments['keyValue'], 62 | )!, 63 | ), 64 | routeName: 'DemoGroupPage', 65 | ); 66 | case 'fluttercandies://homePage': 67 | return FFRouteSettings( 68 | name: name, 69 | arguments: arguments, 70 | builder: () => HomePage(), 71 | routeName: 'HomePage', 72 | description: 'A complex demo with extended_sliver.', 73 | exts: {'group': 'Complex', 'order': 0}, 74 | ); 75 | case 'fluttercandies://mainpage': 76 | return FFRouteSettings( 77 | name: name, 78 | arguments: arguments, 79 | builder: () => MainPage(), 80 | routeName: 'MainPage', 81 | ); 82 | case 'fluttercandies://sliverAppbar': 83 | return FFRouteSettings( 84 | name: name, 85 | arguments: arguments, 86 | builder: () => SliverAppbarDemo(), 87 | routeName: 'SliverAppbar', 88 | description: 'extended SliverAppbar.', 89 | exts: {'group': 'Simple', 'order': 2}, 90 | ); 91 | default: 92 | return FFRouteSettings( 93 | name: FFRoute.notFoundName, 94 | routeName: FFRoute.notFoundRouteName, 95 | builder: notFoundPageBuilder ?? () => Container(), 96 | ); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /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,unnecessary_import 6 | const List routeNames = [ 7 | 'fluttercandies://NestedWebviewDemo', 8 | 'fluttercandies://PinnedBox', 9 | 'fluttercandies://PinnedHeader', 10 | 'fluttercandies://demogrouppage', 11 | 'fluttercandies://homePage', 12 | 'fluttercandies://mainpage', 13 | 'fluttercandies://sliverAppbar', 14 | ]; 15 | 16 | class Routes { 17 | const Routes._(); 18 | 19 | /// 'show how to nested webview in customscrollview' 20 | /// 21 | /// [name] : 'fluttercandies://NestedWebviewDemo' 22 | /// 23 | /// [routeName] : 'NestedWebviewDemo' 24 | /// 25 | /// [description] : 'show how to nested webview in customscrollview' 26 | /// 27 | /// [constructors] : 28 | /// 29 | /// NestedWebviewDemo : [Key? key] 30 | /// 31 | /// [exts] : {group: Nested, order: 0} 32 | static const String fluttercandiesNestedWebviewDemo = 33 | 'fluttercandies://NestedWebviewDemo'; 34 | 35 | /// 'simple pinned box.' 36 | /// 37 | /// [name] : 'fluttercandies://PinnedBox' 38 | /// 39 | /// [routeName] : 'PinnedBox' 40 | /// 41 | /// [description] : 'simple pinned box.' 42 | /// 43 | /// [exts] : {group: Simple, order: 1} 44 | static const String fluttercandiesPinnedBox = 'fluttercandies://PinnedBox'; 45 | 46 | /// 'pinned header without minExtent and maxExtent.' 47 | /// 48 | /// [name] : 'fluttercandies://PinnedHeader' 49 | /// 50 | /// [routeName] : 'PinnedHeader' 51 | /// 52 | /// [description] : 'pinned header without minExtent and maxExtent.' 53 | /// 54 | /// [exts] : {group: Simple, order: 0} 55 | static const String fluttercandiesPinnedHeader = 56 | 'fluttercandies://PinnedHeader'; 57 | 58 | /// 'DemoGroupPage' 59 | /// 60 | /// [name] : 'fluttercandies://demogrouppage' 61 | /// 62 | /// [routeName] : 'DemoGroupPage' 63 | /// 64 | /// [constructors] : 65 | /// 66 | /// DemoGroupPage : [MapEntry>(required) keyValue] 67 | static const String fluttercandiesDemogrouppage = 68 | 'fluttercandies://demogrouppage'; 69 | 70 | /// 'A complex demo with extended_sliver.' 71 | /// 72 | /// [name] : 'fluttercandies://homePage' 73 | /// 74 | /// [routeName] : 'HomePage' 75 | /// 76 | /// [description] : 'A complex demo with extended_sliver.' 77 | /// 78 | /// [exts] : {group: Complex, order: 0} 79 | static const String fluttercandiesHomePage = 'fluttercandies://homePage'; 80 | 81 | /// 'MainPage' 82 | /// 83 | /// [name] : 'fluttercandies://mainpage' 84 | /// 85 | /// [routeName] : 'MainPage' 86 | /// 87 | /// [constructors] : 88 | /// 89 | /// MainPage : [] 90 | static const String fluttercandiesMainpage = 'fluttercandies://mainpage'; 91 | 92 | /// 'extended SliverAppbar.' 93 | /// 94 | /// [name] : 'fluttercandies://sliverAppbar' 95 | /// 96 | /// [routeName] : 'SliverAppbar' 97 | /// 98 | /// [description] : 'extended SliverAppbar.' 99 | /// 100 | /// [exts] : {group: Simple, order: 2} 101 | static const String fluttercandiesSliverAppbar = 102 | 'fluttercandies://sliverAppbar'; 103 | } 104 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:ff_annotation_route_library/ff_annotation_route_library.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'example_route.dart'; 4 | import 'example_routes.dart'; 5 | 6 | void main() => runApp(MyApp()); 7 | 8 | class MyApp extends StatelessWidget { 9 | // This widget is the root of your application. 10 | @override 11 | Widget build(BuildContext context) { 12 | return MaterialApp( 13 | title: 'extended_sliver demo', 14 | debugShowCheckedModeBanner: false, 15 | theme: ThemeData( 16 | primarySwatch: Colors.blue, 17 | ), 18 | initialRoute: Routes.fluttercandiesMainpage, 19 | onGenerateRoute: (RouteSettings settings) { 20 | return onGenerateRoute( 21 | settings: settings, 22 | getRouteSettings: getRouteSettings, 23 | routeSettingsWrapper: (FFRouteSettings settings) { 24 | if (settings.name == Routes.fluttercandiesMainpage || 25 | settings.name == Routes.fluttercandiesDemogrouppage || 26 | settings.name == Routes.fluttercandiesHomePage || 27 | settings.name == Routes.fluttercandiesSliverAppbar || 28 | settings.name == Routes.fluttercandiesNestedWebviewDemo) { 29 | return settings; 30 | } 31 | return settings.copyWith( 32 | builder: () => CommonWidget( 33 | child: settings.builder(), 34 | title: settings.routeName, 35 | )); 36 | }, 37 | ); 38 | }, 39 | ); 40 | } 41 | } 42 | 43 | class CommonWidget extends StatelessWidget { 44 | const CommonWidget({ 45 | this.child, 46 | this.title, 47 | }); 48 | final Widget? child; 49 | final String? title; 50 | 51 | @override 52 | Widget build(BuildContext context) { 53 | return Scaffold( 54 | appBar: AppBar( 55 | title: Text( 56 | title!, 57 | ), 58 | ), 59 | body: child, 60 | ); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /example/lib/pages/complex/home_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:math'; 3 | 4 | import 'package:extended_sliver/extended_sliver.dart'; 5 | import 'package:ff_annotation_route_library/ff_annotation_route_library.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:pull_to_refresh_notification/pull_to_refresh_notification.dart'; 8 | 9 | @FFRoute( 10 | name: 'fluttercandies://homePage', 11 | routeName: 'HomePage', 12 | description: 'A complex demo with extended_sliver.', 13 | exts: { 14 | 'group': 'Complex', 15 | 'order': 0, 16 | }, 17 | ) 18 | class HomePage extends StatefulWidget { 19 | @override 20 | _HomePageState createState() => _HomePageState(); 21 | } 22 | 23 | class _HomePageState extends State { 24 | final GlobalKey refreshKey = 25 | GlobalKey(); 26 | 27 | StreamController onBuildController = StreamController.broadcast(); 28 | StreamController followButtonController = StreamController(); 29 | int listlength = 100; 30 | double maxDragOffset = 100; 31 | final String description = 32 | 'This text can be short or long. It affects the final height.' * 33 | (Random().nextInt(3) + 1); 34 | bool showFollowButton = false; 35 | @override 36 | void dispose() { 37 | onBuildController.close(); 38 | followButtonController.close(); 39 | super.dispose(); 40 | } 41 | 42 | @override 43 | Widget build(BuildContext context) { 44 | final double statusBarHeight = MediaQuery.of(context).padding.top; 45 | return Scaffold( 46 | body: SafeArea( 47 | top: false, 48 | child: PullToRefreshNotification( 49 | pullBackOnRefresh: true, 50 | onRefresh: onRefresh, 51 | key: refreshKey, 52 | maxDragOffset: maxDragOffset, 53 | child: CustomScrollView( 54 | ///in case,list is not full screen and remove ios Bouncing 55 | physics: const AlwaysScrollableClampingScrollPhysics(), 56 | slivers: [ 57 | PullToRefreshContainer( 58 | (PullToRefreshScrollNotificationInfo? info) { 59 | final double offset = info?.dragOffset ?? 0.0; 60 | Widget actions = const Icon( 61 | Icons.more_horiz, 62 | color: Colors.white, 63 | ); 64 | if (info?.refreshWidget != null) { 65 | actions = const SizedBox( 66 | height: 20, 67 | width: 20, 68 | child: CircularProgressIndicator( 69 | backgroundColor: Colors.transparent, 70 | valueColor: AlwaysStoppedAnimation(Colors.white), 71 | strokeWidth: 3, 72 | ), 73 | ); 74 | } 75 | 76 | actions = Row( 77 | children: [ 78 | StreamBuilder( 79 | stream: followButtonController.stream, 80 | builder: 81 | (BuildContext context, AsyncSnapshot data) { 82 | //hide FollowButton 83 | if (!data.data!) { 84 | return Container(); 85 | } 86 | return OutlinedButton( 87 | child: const Text('Follow'), 88 | style: ButtonStyle( 89 | textStyle: MaterialStateProperty.all( 90 | const TextStyle(color: Colors.white), 91 | ), 92 | foregroundColor: 93 | MaterialStateProperty.all(Colors.white), 94 | side: MaterialStateProperty.all( 95 | const BorderSide( 96 | color: Colors.orange, 97 | ), 98 | ), 99 | ), 100 | onPressed: () {}, 101 | ); 102 | }, 103 | initialData: showFollowButton, 104 | ), 105 | const SizedBox( 106 | width: 10, 107 | ), 108 | actions, 109 | ], 110 | ); 111 | return ExtendedSliverAppbar( 112 | onBuild: ( 113 | BuildContext context, 114 | double shrinkOffset, 115 | double? minExtent, 116 | double maxExtent, 117 | bool overlapsContent, 118 | ) { 119 | if (shrinkOffset > 0) { 120 | onBuildController.sink.add(null); 121 | } 122 | }, 123 | title: const Text( 124 | 'ExtendedSliverAppbar', 125 | style: TextStyle(color: Colors.white), 126 | ), 127 | leading: const BackButton( 128 | onPressed: null, 129 | color: Colors.white, 130 | ), 131 | background: DefaultTextStyle( 132 | style: const TextStyle(color: Colors.white), 133 | child: Stack( 134 | children: [ 135 | Positioned.fill( 136 | child: Image.asset( 137 | 'assets/cypridina.jpeg', 138 | fit: BoxFit.cover, 139 | )), 140 | Padding( 141 | padding: EdgeInsets.only( 142 | top: kToolbarHeight + statusBarHeight, 143 | bottom: 20, 144 | ), 145 | child: Container( 146 | width: double.infinity, 147 | child: Column( 148 | mainAxisAlignment: MainAxisAlignment.center, 149 | crossAxisAlignment: CrossAxisAlignment.center, 150 | children: [ 151 | SizedBox( 152 | height: offset, 153 | ), 154 | Image.asset( 155 | 'assets/flutter_candies_logo.png', 156 | height: 100, 157 | width: 100, 158 | ), 159 | const Text( 160 | 'ExtendedSliverAppbar', 161 | ), 162 | Padding( 163 | padding: 164 | const EdgeInsets.only(top: 10, bottom: 5), 165 | child: FollowButton( 166 | onBuildController, 167 | followButtonController, 168 | ), 169 | ), 170 | Padding( 171 | padding: const EdgeInsets.only(left: 15), 172 | child: Text( 173 | description, 174 | maxLines: 3, 175 | overflow: TextOverflow.ellipsis, 176 | style: const TextStyle(fontSize: 16), 177 | ), 178 | ), 179 | ], 180 | ), 181 | ), 182 | ) 183 | ], 184 | ), 185 | ), 186 | actions: Padding( 187 | padding: const EdgeInsets.all(10.0), 188 | child: actions, 189 | ), 190 | ); 191 | }), 192 | //pinned box 193 | SliverPinnedToBoxAdapter( 194 | child: Container( 195 | color: Colors.yellow, 196 | height: 200.0, 197 | ), 198 | ), 199 | SliverList( 200 | delegate: SliverChildBuilderDelegate( 201 | (BuildContext context, int index) { 202 | return Container( 203 | decoration: BoxDecoration( 204 | border: Border.all(color: Colors.grey, width: 1)), 205 | child: MaterialButton( 206 | onPressed: () => debugPrint('$index'), 207 | child: Container( 208 | child: Text( 209 | '${listlength - index}', 210 | ), 211 | alignment: Alignment.center, 212 | margin: const EdgeInsets.all(50), 213 | ), 214 | ), 215 | ); 216 | }, 217 | childCount: listlength, 218 | ), 219 | ), 220 | ], 221 | ), 222 | ), 223 | ), 224 | floatingActionButton: FloatingActionButton( 225 | child: const Icon(Icons.refresh), 226 | onPressed: () { 227 | refreshKey.currentState!.show( 228 | notificationDragOffset: maxDragOffset, 229 | ); 230 | }, 231 | ), 232 | ); 233 | } 234 | 235 | Future onRefresh() { 236 | return Future.delayed(const Duration(seconds: 2), () { 237 | setState(() { 238 | listlength += 10; 239 | }); 240 | return true; 241 | }); 242 | } 243 | } 244 | 245 | class FollowButton extends StatefulWidget { 246 | const FollowButton(this.onBuildController, this.followButtonController); 247 | final StreamController onBuildController; 248 | final StreamController followButtonController; 249 | @override 250 | _FollowButtonState createState() => _FollowButtonState(); 251 | } 252 | 253 | class _FollowButtonState extends State { 254 | bool showFollowButton = false; 255 | @override 256 | void initState() { 257 | super.initState(); 258 | widget.onBuildController.stream.listen((_) { 259 | if (mounted) { 260 | final double statusBarHeight = MediaQuery.of(context).padding.top; 261 | final RenderBox renderBox = context.findRenderObject() as RenderBox; 262 | final Offset position = renderBox.localToGlobal(Offset.zero); 263 | final bool show = position.dy + renderBox.size.height < 264 | statusBarHeight + kToolbarHeight; 265 | if (showFollowButton != show) { 266 | showFollowButton = show; 267 | widget.followButtonController.sink.add(showFollowButton); 268 | } 269 | //print('${position.dy + renderBox.size.height} ----- ${statusBarHeight + kToolbarHeight}'); 270 | } 271 | }); 272 | } 273 | 274 | @override 275 | Widget build(BuildContext context) { 276 | return ButtonTheme( 277 | //MaterialTapTargetSize.padded min 48 278 | materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, 279 | child: OutlinedButton( 280 | child: const Text('Follow'), 281 | style: ButtonStyle( 282 | textStyle: MaterialStateProperty.all( 283 | const TextStyle(color: Colors.white), 284 | ), 285 | foregroundColor: MaterialStateProperty.all(Colors.white), 286 | side: MaterialStateProperty.all( 287 | const BorderSide( 288 | color: Colors.orange, 289 | ), 290 | ), 291 | ), 292 | onPressed: () {}, 293 | ), 294 | ); 295 | } 296 | } 297 | -------------------------------------------------------------------------------- /example/lib/pages/main_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:collection/collection.dart'; 2 | import 'package:example/example_routes.dart'; 3 | import 'package:ff_annotation_route_library/ff_annotation_route_library.dart'; 4 | import 'package:flutter/foundation.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:url_launcher/url_launcher.dart'; 7 | import '../example_route.dart'; 8 | import '../example_routes.dart' as example_routes; 9 | 10 | @FFRoute( 11 | name: 'fluttercandies://mainpage', 12 | routeName: 'MainPage', 13 | ) 14 | class MainPage extends StatelessWidget { 15 | MainPage() { 16 | final List routeNames = []; 17 | routeNames.addAll(example_routes.routeNames); 18 | routeNames.remove(Routes.fluttercandiesMainpage); 19 | routeNames.remove(Routes.fluttercandiesDemogrouppage); 20 | routesGroup.addAll(groupBy( 21 | routeNames 22 | .map((String name) => getRouteSettings(name: name)) 23 | .where((FFRouteSettings element) => element.exts != null) 24 | .map((FFRouteSettings e) => DemoRouteResult(e)) 25 | .toList() 26 | ..sort((DemoRouteResult a, DemoRouteResult b) => 27 | b.group.compareTo(a.group)), 28 | (DemoRouteResult x) => x.group)); 29 | } 30 | final Map> routesGroup = 31 | >{}; 32 | 33 | @override 34 | Widget build(BuildContext context) { 35 | return Scaffold( 36 | appBar: AppBar( 37 | // Here we take the value from the MyHomePage object that was created by 38 | // the App.build method, and use it to set our appbar title. 39 | title: const Text('extended_sliver'), 40 | actions: [ 41 | ButtonTheme( 42 | minWidth: 0.0, 43 | padding: const EdgeInsets.symmetric(horizontal: 10.0), 44 | child: TextButton( 45 | child: const Text( 46 | 'Github', 47 | style: TextStyle( 48 | decorationStyle: TextDecorationStyle.solid, 49 | decoration: TextDecoration.underline, 50 | color: Colors.white, 51 | ), 52 | ), 53 | onPressed: () { 54 | launchUrl(Uri.parse( 55 | 'https://github.com/fluttercandies/extended_sliver')); 56 | }, 57 | ), 58 | ), 59 | ButtonTheme( 60 | padding: const EdgeInsets.only(right: 10.0), 61 | minWidth: 0.0, 62 | child: TextButton( 63 | child: kIsWeb 64 | ? const Text('QQ') 65 | : Image.network( 66 | 'https://pub.idqqimg.com/wpa/images/group.png'), 67 | onPressed: () { 68 | launchUrl(Uri.parse('https://jq.qq.com/?_wv=1027&k=5bcc0gy')); 69 | }, 70 | ), 71 | ) 72 | ], 73 | ), 74 | body: ListView.builder( 75 | itemBuilder: (BuildContext c, int index) { 76 | // final RouteResult page = routes[index]; 77 | final String type = routesGroup.keys.toList()[index]; 78 | return Container( 79 | margin: const EdgeInsets.all(20.0), 80 | child: GestureDetector( 81 | behavior: HitTestBehavior.translucent, 82 | child: Column( 83 | crossAxisAlignment: CrossAxisAlignment.start, 84 | children: [ 85 | Text( 86 | (index + 1).toString() + '.' + type, 87 | //style: TextStyle(inherit: false), 88 | ), 89 | Text( 90 | '$type demos of extended_sliver', 91 | //page.description, 92 | style: const TextStyle(color: Colors.grey), 93 | ) 94 | ], 95 | ), 96 | onTap: () { 97 | Navigator.pushNamed( 98 | context, Routes.fluttercandiesDemogrouppage, 99 | arguments: { 100 | 'keyValue': routesGroup.entries.toList()[index], 101 | }); 102 | }, 103 | )); 104 | }, 105 | itemCount: routesGroup.length, 106 | ), 107 | ); 108 | } 109 | } 110 | 111 | @FFRoute( 112 | name: 'fluttercandies://demogrouppage', 113 | routeName: 'DemoGroupPage', 114 | argumentImports: ['import \'pages/main_page.dart\';'], 115 | ) 116 | class DemoGroupPage extends StatelessWidget { 117 | DemoGroupPage({required MapEntry> keyValue}) 118 | : routes = keyValue.value 119 | ..sort((DemoRouteResult a, DemoRouteResult b) => 120 | a.order.compareTo(b.order)), 121 | group = keyValue.key; 122 | final List routes; 123 | final String group; 124 | @override 125 | Widget build(BuildContext context) { 126 | return Scaffold( 127 | appBar: AppBar( 128 | title: Text('$group demos'), 129 | ), 130 | body: ListView.builder( 131 | itemBuilder: (BuildContext context, int index) { 132 | final DemoRouteResult page = routes[index]; 133 | return Container( 134 | margin: const EdgeInsets.all(20.0), 135 | child: GestureDetector( 136 | behavior: HitTestBehavior.translucent, 137 | child: Column( 138 | crossAxisAlignment: CrossAxisAlignment.start, 139 | children: [ 140 | Text( 141 | (index + 1).toString() + '.' + page.routeResult.routeName!, 142 | //style: TextStyle(inherit: false), 143 | ), 144 | Text( 145 | page.routeResult.description!, 146 | style: const TextStyle(color: Colors.grey), 147 | ) 148 | ], 149 | ), 150 | onTap: () { 151 | Navigator.pushNamed(context, page.routeResult.name!); 152 | }, 153 | ), 154 | ); 155 | }, 156 | itemCount: routes.length, 157 | ), 158 | ); 159 | } 160 | } 161 | 162 | class DemoRouteResult { 163 | DemoRouteResult( 164 | this.routeResult, 165 | ) : order = routeResult.exts!['order'] as int, 166 | group = routeResult.exts!['group'] as String; 167 | 168 | final int order; 169 | final String group; 170 | final FFRouteSettings routeResult; 171 | } 172 | -------------------------------------------------------------------------------- /example/lib/pages/nested/webview.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'dart:ui'; 3 | 4 | import 'package:extended_sliver/extended_sliver.dart'; 5 | import 'package:ff_annotation_route_library/ff_annotation_route_library.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:webview_flutter/webview_flutter.dart'; 8 | 9 | @FFRoute( 10 | name: 'fluttercandies://NestedWebviewDemo', 11 | routeName: 'NestedWebviewDemo', 12 | description: 'show how to nested webview in customscrollview', 13 | exts: { 14 | 'group': 'Nested', 15 | 'order': 0, 16 | }, 17 | ) 18 | class NestedWebviewDemo extends StatelessWidget { 19 | NestedWebviewDemo({Key? key}) : super(key: key); 20 | 21 | final NestedWebviewController nestedWebviewController = 22 | NestedWebviewController('https://flutter.dev' 23 | //'https://flutter.cn' 24 | ); 25 | final ScrollController scrollController = ScrollController(); 26 | @override 27 | Widget build(BuildContext context) { 28 | // return WebView( 29 | // initialUrl: nestedWebviewController.initialUrl, 30 | // ); 31 | return Scaffold( 32 | appBar: AppBar( 33 | title: const Text('NestedWebview'), 34 | actions: [ 35 | TextButton( 36 | onPressed: () { 37 | scrollController.animateTo( 38 | nestedWebviewController.scrollHeightNotifier.value, 39 | duration: const Duration(seconds: 1), 40 | curve: Curves.linear, 41 | ); 42 | }, 43 | child: const Text( 44 | 'animate to Webview bottom', 45 | style: TextStyle( 46 | color: Colors.white, 47 | ), 48 | )) 49 | ], 50 | ), 51 | body: ValueListenableBuilder( 52 | valueListenable: nestedWebviewController.webViewStatusNotifier, 53 | builder: 54 | (BuildContext context, WebViewStatus webViewStatus, Widget? child) { 55 | return Stack( 56 | children: [ 57 | CustomScrollView( 58 | controller: scrollController, 59 | slivers: [ 60 | if (webViewStatus == WebViewStatus.completed) 61 | SliverToBoxAdapter( 62 | child: Container( 63 | height: 100, 64 | color: Colors.red, 65 | child: const Center( 66 | child: Text( 67 | 'Header', 68 | style: TextStyle(color: Colors.white), 69 | ), 70 | ), 71 | ), 72 | ), 73 | ValueListenableBuilder( 74 | valueListenable: 75 | nestedWebviewController.scrollHeightNotifier, 76 | builder: ( 77 | BuildContext context, 78 | double scrollHeight, 79 | Widget? child, 80 | ) { 81 | // return SliverToBoxAdapter( 82 | // child: SizedBox( 83 | // height: scrollHeight, 84 | // child: child, 85 | // ), 86 | // ); 87 | return SliverToNestedScrollBoxAdapter( 88 | childExtent: scrollHeight, 89 | onScrollOffsetChanged: (double scrollOffset) { 90 | double y = scrollOffset; 91 | if (Platform.isAndroid) { 92 | // https://github.com/flutter/flutter/issues/75841 93 | y *= window.devicePixelRatio; 94 | } 95 | nestedWebviewController.webviewController 96 | ?.scrollTo(0, y.ceil()); 97 | }, 98 | child: child, 99 | ); 100 | }, 101 | child: WebView( 102 | initialUrl: nestedWebviewController.initialUrl, 103 | onPageStarted: nestedWebviewController.onPageStarted, 104 | onPageFinished: nestedWebviewController.onPageFinished, 105 | onWebResourceError: 106 | nestedWebviewController.onWebResourceError, 107 | onWebViewCreated: 108 | nestedWebviewController.onWebViewCreated, 109 | onProgress: nestedWebviewController.onProgress, 110 | javascriptChannels: { 111 | nestedWebviewController 112 | .scrollHeightNotifierJavascriptChannel 113 | }, 114 | javascriptMode: JavascriptMode.unrestricted, 115 | ), 116 | ), 117 | if (webViewStatus == WebViewStatus.completed) 118 | SliverToBoxAdapter( 119 | child: Container( 120 | height: 300, 121 | color: Colors.green, 122 | child: const Center( 123 | child: Text( 124 | 'Footer', 125 | style: TextStyle(color: Colors.white), 126 | ), 127 | ), 128 | ), 129 | ), 130 | ], 131 | ), 132 | Container( 133 | height: webViewStatus != WebViewStatus.completed 134 | ? double.infinity 135 | : 0, 136 | child: webViewStatus == WebViewStatus.loading 137 | ? Container( 138 | alignment: Alignment.center, 139 | child: Container( 140 | width: 120, 141 | height: 120, 142 | decoration: BoxDecoration( 143 | color: Colors.black.withOpacity(0.7), 144 | borderRadius: 145 | const BorderRadius.all(Radius.circular(8))), 146 | child: Column( 147 | mainAxisAlignment: MainAxisAlignment.center, 148 | crossAxisAlignment: CrossAxisAlignment.center, 149 | children: [ 150 | const SizedBox( 151 | width: 45.0, 152 | height: 45.0, 153 | child: CircularProgressIndicator( 154 | color: Colors.white, 155 | ), 156 | ), 157 | const SizedBox( 158 | height: 14.0, 159 | ), 160 | ValueListenableBuilder( 161 | valueListenable: 162 | nestedWebviewController.progressNotifier, 163 | builder: (BuildContext context, int progress, 164 | Widget? child) { 165 | return Text( 166 | '${(progress / 100 * 100).toInt()}%', 167 | style: const TextStyle( 168 | fontSize: 15, 169 | fontWeight: FontWeight.w500, 170 | color: Colors.white, 171 | ), 172 | ); 173 | }), 174 | ], 175 | ), 176 | ), 177 | ) 178 | : Container( 179 | alignment: Alignment.center, 180 | child: const Text('loading failed'), 181 | ), 182 | ), 183 | ], 184 | ); 185 | }, 186 | ), 187 | ); 188 | } 189 | } 190 | 191 | class NestedWebviewController { 192 | NestedWebviewController(this.initialUrl); 193 | 194 | final String initialUrl; 195 | 196 | WebViewController? _webviewController; 197 | 198 | WebViewController? get webviewController => _webviewController; 199 | 200 | ValueNotifier scrollHeightNotifier = ValueNotifier(1); 201 | ValueNotifier webViewStatusNotifier = 202 | ValueNotifier(WebViewStatus.loading); 203 | 204 | ValueNotifier progressNotifier = ValueNotifier(0); 205 | 206 | void onWebViewCreated(WebViewController controller) { 207 | _webviewController = controller; 208 | } 209 | 210 | void onPageStarted(String url) { 211 | if (url == initialUrl || 212 | webViewStatusNotifier.value == WebViewStatus.failed) { 213 | webViewStatusNotifier.value = WebViewStatus.loading; 214 | } 215 | } 216 | 217 | void onPageFinished(String url) { 218 | if (webViewStatusNotifier.value != WebViewStatus.failed) { 219 | //webViewStatusNotifier.value = WebViewStatus.completed; 220 | _webviewController?.runJavascript(scrollHeightJs); 221 | } 222 | } 223 | 224 | void onWebResourceError(WebResourceError error) { 225 | webViewStatusNotifier.value = WebViewStatus.failed; 226 | } 227 | 228 | void onProgress(int progress) { 229 | progressNotifier.value = progress; 230 | } 231 | 232 | JavascriptChannel get scrollHeightNotifierJavascriptChannel => 233 | JavascriptChannel( 234 | name: 'ScrollHeightNotifier', 235 | onMessageReceived: (JavascriptMessage message) { 236 | final String msg = message.message; 237 | final double? height = double.tryParse(msg); 238 | if (height != null) { 239 | scrollHeightNotifier.value = height; 240 | 241 | webViewStatusNotifier.value = WebViewStatus.completed; 242 | } 243 | }, 244 | ); 245 | } 246 | 247 | enum WebViewStatus { 248 | loading, 249 | failed, 250 | completed, 251 | } 252 | 253 | const String scrollHeightJs = '''(function() { 254 | var height = 0; 255 | var notifier = window.ScrollHeightNotifier || window.webkit.messageHandlers.ScrollHeightNotifier; 256 | if (!notifier) return; 257 | 258 | function checkAndNotify() { 259 | var curr = document.body.scrollHeight; 260 | if (curr !== height) { 261 | height = curr; 262 | notifier.postMessage(height.toString()); 263 | } 264 | } 265 | 266 | var timer; 267 | var ob; 268 | if (window.ResizeObserver) { 269 | ob = new ResizeObserver(checkAndNotify); 270 | ob.observe(document.body); 271 | } else { 272 | timer = setTimeout(checkAndNotify, 200); 273 | } 274 | window.onbeforeunload = function() { 275 | ob && ob.disconnect(); 276 | timer && clearTimeout(timer); 277 | }; 278 | })();'''; 279 | -------------------------------------------------------------------------------- /example/lib/pages/simple/pinned_box.dart: -------------------------------------------------------------------------------- 1 | import 'package:extended_sliver/extended_sliver.dart'; 2 | import 'package:ff_annotation_route_library/ff_annotation_route_library.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | @FFRoute( 6 | name: 'fluttercandies://PinnedBox', 7 | routeName: 'PinnedBox', 8 | description: 'simple pinned box.', 9 | exts: { 10 | 'group': 'Simple', 11 | 'order': 1, 12 | }, 13 | ) 14 | class PinnedBox extends StatelessWidget { 15 | @override 16 | Widget build(BuildContext context) { 17 | return CustomScrollView( 18 | slivers: [ 19 | SliverToBoxAdapter( 20 | child: Container( 21 | color: Colors.yellow, 22 | height: 200.0, 23 | ), 24 | ), 25 | SliverPinnedToBoxAdapter( 26 | child: Container( 27 | padding: const EdgeInsets.all(20), 28 | color: Colors.blue.withOpacity(0.5), 29 | child: Column( 30 | children: [ 31 | const Text( 32 | '[love]Extended text help you to build rich text quickly. any special text you will have with extended text. ' 33 | '\n\nIt\'s my pleasure to invite you to join \$FlutterCandies\$ if you want to improve flutter .[love]' 34 | '\n\nif you meet any problem, please let me konw @zmtzawqlp .[sun_glasses]'), 35 | TextButton( 36 | child: const Text('I\'m button. click me!'), 37 | onPressed: () { 38 | debugPrint('click'); 39 | }, 40 | ), 41 | ], 42 | ), 43 | ), 44 | ), 45 | SliverList( 46 | delegate: SliverChildBuilderDelegate( 47 | (BuildContext context, int index) { 48 | return Container( 49 | decoration: BoxDecoration( 50 | border: Border.all(color: Colors.grey, width: 1)), 51 | child: MaterialButton( 52 | onPressed: () => debugPrint('$index'), 53 | child: Container( 54 | child: Text( 55 | '$index', 56 | ), 57 | alignment: Alignment.center, 58 | margin: const EdgeInsets.all(50), 59 | ), 60 | ), 61 | ); 62 | }, 63 | childCount: 100, 64 | ), 65 | ), 66 | ], 67 | ); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /example/lib/pages/simple/pinned_header.dart: -------------------------------------------------------------------------------- 1 | import 'package:extended_sliver/extended_sliver.dart'; 2 | import 'package:ff_annotation_route_library/ff_annotation_route_library.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | @FFRoute( 6 | name: 'fluttercandies://PinnedHeader', 7 | routeName: 'PinnedHeader', 8 | description: 'pinned header without minExtent and maxExtent.', 9 | exts: { 10 | 'group': 'Simple', 11 | 'order': 0, 12 | }, 13 | ) 14 | class PinnedHeader extends StatelessWidget { 15 | @override 16 | Widget build(BuildContext context) { 17 | return CustomScrollView( 18 | slivers: [ 19 | SliverToBoxAdapter( 20 | child: Container( 21 | color: Colors.yellow, 22 | height: 200.0, 23 | ), 24 | ), 25 | SliverPinnedPersistentHeader( 26 | delegate: MySliverPinnedPersistentHeaderDelegate( 27 | minExtentProtoType: Container( 28 | height: 120.0, 29 | color: Colors.red.withOpacity(0.5), 30 | child: TextButton( 31 | child: const Text('minProtoType'), 32 | onPressed: () { 33 | print('minProtoType'); 34 | }, 35 | ), 36 | alignment: Alignment.topCenter, 37 | ), 38 | maxExtentProtoType: Container( 39 | height: 200.0, 40 | color: Colors.blue, 41 | child: TextButton( 42 | child: const Text('maxProtoType'), 43 | onPressed: () { 44 | print('maxProtoType'); 45 | }, 46 | ), 47 | alignment: Alignment.bottomCenter, 48 | ), 49 | ), 50 | ), 51 | SliverList( 52 | delegate: SliverChildBuilderDelegate( 53 | (BuildContext context, int index) { 54 | return Container( 55 | decoration: BoxDecoration( 56 | border: Border.all(color: Colors.grey, width: 1)), 57 | child: MaterialButton( 58 | onPressed: () => debugPrint('$index'), 59 | child: Container( 60 | child: Text( 61 | '$index', 62 | ), 63 | alignment: Alignment.center, 64 | margin: const EdgeInsets.all(50), 65 | ), 66 | ), 67 | ); 68 | }, 69 | childCount: 100, 70 | ), 71 | ), 72 | ], 73 | ); 74 | } 75 | } 76 | 77 | class MySliverPinnedPersistentHeaderDelegate 78 | extends SliverPinnedPersistentHeaderDelegate { 79 | MySliverPinnedPersistentHeaderDelegate({ 80 | required Widget minExtentProtoType, 81 | required Widget maxExtentProtoType, 82 | }) : super( 83 | minExtentProtoType: minExtentProtoType, 84 | maxExtentProtoType: maxExtentProtoType, 85 | ); 86 | @override 87 | Widget build(BuildContext context, double shrinkOffset, double? minExtent, 88 | double maxExtent, bool overlapsContent) { 89 | print(shrinkOffset); 90 | return Stack( 91 | children: [ 92 | Positioned( 93 | child: maxExtentProtoType, 94 | top: -shrinkOffset, 95 | bottom: 0, 96 | left: 0, 97 | right: 0, 98 | ), 99 | Positioned( 100 | child: minExtentProtoType, 101 | top: 0, 102 | left: 0, 103 | right: 0, 104 | ), 105 | ], 106 | ); 107 | } 108 | 109 | @override 110 | bool shouldRebuild(SliverPinnedPersistentHeaderDelegate oldDelegate) { 111 | return true; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /example/lib/pages/simple/sliver_app_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:extended_sliver/extended_sliver.dart'; 2 | import 'package:ff_annotation_route_library/ff_annotation_route_library.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | @FFRoute( 6 | name: 'fluttercandies://sliverAppbar', 7 | routeName: 'SliverAppbar', 8 | description: 'extended SliverAppbar.', 9 | exts: { 10 | 'group': 'Simple', 11 | 'order': 2, 12 | }, 13 | ) 14 | class SliverAppbarDemo extends StatelessWidget { 15 | @override 16 | Widget build(BuildContext context) { 17 | return Scaffold( 18 | body: SafeArea( 19 | top: false, 20 | child: CustomScrollView( 21 | slivers: [ 22 | ExtendedSliverAppbar( 23 | title: const Text( 24 | 'ExtendedSliverAppbar', 25 | style: TextStyle(color: Colors.white), 26 | ), 27 | leading: const BackButton( 28 | onPressed: null, 29 | color: Colors.white, 30 | ), 31 | background: Image.asset( 32 | 'assets/cypridina.jpeg', 33 | fit: BoxFit.cover, 34 | ), 35 | actions: const Padding( 36 | padding: EdgeInsets.all(10.0), 37 | child: Icon( 38 | Icons.more_horiz, 39 | color: Colors.white, 40 | ), 41 | ), 42 | ), 43 | SliverList( 44 | delegate: SliverChildBuilderDelegate( 45 | (BuildContext context, int index) { 46 | return Container( 47 | decoration: BoxDecoration( 48 | border: Border.all(color: Colors.grey, width: 1)), 49 | child: MaterialButton( 50 | onPressed: () => debugPrint('$index'), 51 | child: Container( 52 | child: Text( 53 | '${100 - index}', 54 | ), 55 | alignment: Alignment.center, 56 | margin: const EdgeInsets.all(50), 57 | ), 58 | ), 59 | ); 60 | }, 61 | childCount: 100, 62 | ), 63 | ), 64 | ], 65 | ), 66 | ), 67 | ); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: example 2 | description: A new Flutter project. 3 | 4 | version: 1.0.0+1 5 | publish_to: none 6 | environment: 7 | sdk: '>=2.12.0 <3.0.0' 8 | flutter: ">=2.0.0" 9 | 10 | dependencies: 11 | cupertino_icons: any 12 | webview_flutter: any 13 | extended_sliver: 14 | path: ../ 15 | ff_annotation_route_library: any 16 | flutter: 17 | sdk: flutter 18 | 19 | 20 | pull_to_refresh_notification: any 21 | url_launcher: any 22 | dev_dependencies: 23 | flutter_test: 24 | sdk: flutter 25 | 26 | 27 | flutter: 28 | 29 | # The following line ensures that the Material Icons font is 30 | # included with your application, so that you can use the icons in 31 | # the material Icons class. 32 | uses-material-design: true 33 | assets: 34 | - assets/ 35 | -------------------------------------------------------------------------------- /extended_sliver.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /lib/extended_sliver.dart: -------------------------------------------------------------------------------- 1 | library extended_sliver; 2 | 3 | export 'src/widget.dart'; 4 | -------------------------------------------------------------------------------- /lib/src/element.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | 3 | import 'rendering.dart'; 4 | import 'widget.dart'; 5 | 6 | class SliverPinnedPersistentHeaderElement extends RenderObjectElement { 7 | SliverPinnedPersistentHeaderElement( 8 | SliverPinnedPersistentHeaderRenderObjectWidget widget) 9 | : super(widget); 10 | 11 | @override 12 | SliverPinnedPersistentHeaderRenderObjectWidget get widget => 13 | super.widget as SliverPinnedPersistentHeaderRenderObjectWidget; 14 | 15 | @override 16 | RenderSliverPinnedPersistentHeader get renderObject => 17 | super.renderObject as RenderSliverPinnedPersistentHeader; 18 | 19 | Element? _minExtentPrototype; 20 | static final Object _minExtentPrototypeSlot = Object(); 21 | Element? _maxExtentPrototype; 22 | static final Object _maxExtentPrototypeSlot = Object(); 23 | 24 | @override 25 | void mount(Element? parent, dynamic newSlot) { 26 | super.mount(parent, newSlot); 27 | renderObject.element = this; 28 | _minExtentPrototype = updateChild(_minExtentPrototype, 29 | widget.delegate.minExtentProtoType, _minExtentPrototypeSlot); 30 | _maxExtentPrototype = updateChild(_maxExtentPrototype, 31 | widget.delegate.maxExtentProtoType, _maxExtentPrototypeSlot); 32 | } 33 | 34 | @override 35 | void unmount() { 36 | renderObject.element = null; 37 | super.unmount(); 38 | } 39 | 40 | @override 41 | void update(SliverPinnedPersistentHeaderRenderObjectWidget newWidget) { 42 | final SliverPinnedPersistentHeaderRenderObjectWidget oldWidget = widget; 43 | super.update(newWidget); 44 | final SliverPinnedPersistentHeaderDelegate newDelegate = newWidget.delegate; 45 | final SliverPinnedPersistentHeaderDelegate oldDelegate = oldWidget.delegate; 46 | if (newDelegate != oldDelegate && 47 | (newDelegate.runtimeType != oldDelegate.runtimeType || 48 | newDelegate.shouldRebuild(oldDelegate))) 49 | renderObject.triggerRebuild(); 50 | _minExtentPrototype = updateChild(_minExtentPrototype, 51 | widget.delegate.minExtentProtoType, _minExtentPrototypeSlot); 52 | _maxExtentPrototype = updateChild(_maxExtentPrototype, 53 | widget.delegate.maxExtentProtoType, _maxExtentPrototypeSlot); 54 | } 55 | 56 | @override 57 | void performRebuild() { 58 | super.performRebuild(); 59 | renderObject.triggerRebuild(); 60 | } 61 | 62 | Element? child; 63 | 64 | void build(double shrinkOffset, double? minExtent, double maxExtent, 65 | bool overlapsContent) { 66 | owner!.buildScope(this, () { 67 | child = updateChild( 68 | child, 69 | widget.delegate 70 | .build(this, shrinkOffset, minExtent, maxExtent, overlapsContent), 71 | null, 72 | ); 73 | }); 74 | } 75 | 76 | @override 77 | void forgetChild(Element child) { 78 | assert(child == this.child); 79 | this.child = null; 80 | // 1.20 @mustCallSuper. 81 | super.forgetChild(child); 82 | } 83 | 84 | @override 85 | void insertRenderObjectChild( 86 | covariant RenderObject child, covariant dynamic slot) { 87 | assert(renderObject.debugValidateChild(child)); 88 | 89 | assert(child is RenderBox); 90 | if (slot == _minExtentPrototypeSlot) { 91 | renderObject.minProtoType = child as RenderBox; 92 | } else if (slot == _maxExtentPrototypeSlot) { 93 | renderObject.maxProtoType = child as RenderBox; 94 | } else { 95 | renderObject.child = child as RenderBox; 96 | } 97 | } 98 | 99 | @override 100 | void moveRenderObjectChild( 101 | covariant RenderObject child, dynamic oldSlot, dynamic newSlot) { 102 | assert(false); 103 | } 104 | 105 | @override 106 | void removeRenderObjectChild(covariant RenderObject child, dynamic slot) { 107 | if (child == renderObject.minProtoType) { 108 | renderObject.minProtoType = null; 109 | } else if (child == renderObject.maxProtoType) { 110 | renderObject.maxProtoType = null; 111 | } else { 112 | renderObject.child = null; 113 | } 114 | } 115 | 116 | @override 117 | void visitChildren(ElementVisitor visitor) { 118 | if (child != null) { 119 | visitor(child!); 120 | } 121 | if (_minExtentPrototype != null) { 122 | visitor(_minExtentPrototype!); 123 | } 124 | if (_maxExtentPrototype != null) { 125 | visitor(_maxExtentPrototype!); 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /lib/src/rendering.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: unnecessary_null_comparison 2 | 3 | import 'dart:math' as math; 4 | import 'dart:math'; 5 | import 'package:flutter/rendering.dart'; 6 | import 'package:flutter/widgets.dart'; 7 | import 'element.dart'; 8 | 9 | typedef ScrollOffsetChanged = void Function(double offset); 10 | 11 | class RenderSliverPinnedPersistentHeader extends RenderSliver 12 | with RenderObjectWithChildMixin, RenderSliverHelpers { 13 | RenderSliverPinnedPersistentHeader({ 14 | RenderBox? child, 15 | RenderBox? minProtoType, 16 | RenderBox? maxProtoType, 17 | }) : _minProtoType = minProtoType, 18 | _maxProtoType = maxProtoType { 19 | this.child = child; 20 | } 21 | 22 | RenderBox? _minProtoType; 23 | RenderBox? get minProtoType => _minProtoType; 24 | set minProtoType(RenderBox? value) { 25 | if (_minProtoType != null) { 26 | dropChild(_minProtoType!); 27 | } 28 | _minProtoType = value; 29 | if (_minProtoType != null) { 30 | adoptChild(_minProtoType!); 31 | } 32 | markNeedsLayout(); 33 | } 34 | 35 | RenderBox? _maxProtoType; 36 | RenderBox? get maxProtoType => _maxProtoType; 37 | set maxProtoType(RenderBox? value) { 38 | if (_maxProtoType != null) { 39 | dropChild(_maxProtoType!); 40 | } 41 | _maxProtoType = value; 42 | if (_maxProtoType != null) { 43 | adoptChild(_maxProtoType!); 44 | } 45 | markNeedsLayout(); 46 | } 47 | 48 | double get minExtent => getChildExtend(minProtoType, constraints); 49 | double get maxExtent => getChildExtend(maxProtoType, constraints); 50 | 51 | bool _needsUpdateChild = true; 52 | double _lastShrinkOffset = 0.0; 53 | bool _lastOverlapsContent = false; 54 | 55 | @override 56 | void performLayout() { 57 | final SliverConstraints constraints = this.constraints; 58 | minProtoType!.layout(constraints.asBoxConstraints(), parentUsesSize: true); 59 | maxProtoType!.layout(constraints.asBoxConstraints(), parentUsesSize: true); 60 | final bool overlapsContent = constraints.overlap > 0.0; 61 | excludeFromSemanticsScrolling = 62 | overlapsContent || (constraints.scrollOffset > maxExtent - minExtent); 63 | layoutChild(constraints.scrollOffset, maxExtent, 64 | overlapsContent: overlapsContent); 65 | final double effectiveRemainingPaintExtent = 66 | math.max(0, constraints.remainingPaintExtent - constraints.overlap); 67 | final double layoutExtent = (maxExtent - constraints.scrollOffset) 68 | .clamp(0.0, effectiveRemainingPaintExtent); 69 | 70 | geometry = SliverGeometry( 71 | scrollExtent: maxExtent, 72 | paintOrigin: constraints.overlap, 73 | paintExtent: math.min(childExtent, effectiveRemainingPaintExtent), 74 | layoutExtent: layoutExtent, 75 | maxPaintExtent: maxExtent, 76 | maxScrollObstructionExtent: minExtent, 77 | cacheExtent: layoutExtent > 0.0 78 | ? -constraints.cacheOrigin + layoutExtent 79 | : layoutExtent, 80 | hasVisualOverflow: 81 | true, // Conservatively say we do have overflow to avoid complexity. 82 | ); 83 | } 84 | 85 | @override 86 | void markNeedsLayout() { 87 | // This is automatically called whenever the child's intrinsic dimensions 88 | // change, at which point we should remeasure them during the next layout. 89 | _needsUpdateChild = true; 90 | super.markNeedsLayout(); 91 | } 92 | 93 | @protected 94 | double get childExtent { 95 | return getChildExtend(child, constraints); 96 | } 97 | 98 | @protected 99 | void layoutChild(double scrollOffset, double maxExtent, 100 | {bool overlapsContent = false}) { 101 | final double shrinkOffset = math.min(scrollOffset, maxExtent); 102 | if (_needsUpdateChild || 103 | _lastShrinkOffset != shrinkOffset || 104 | _lastOverlapsContent != overlapsContent) { 105 | invokeLayoutCallback((SliverConstraints constraints) { 106 | assert(constraints == this.constraints); 107 | updateChild(shrinkOffset, minExtent, maxExtent, overlapsContent); 108 | }); 109 | _lastShrinkOffset = shrinkOffset; 110 | _lastOverlapsContent = overlapsContent; 111 | _needsUpdateChild = false; 112 | } 113 | 114 | assert(() { 115 | if (minExtent <= maxExtent) { 116 | return true; 117 | } 118 | throw FlutterError.fromParts([ 119 | ErrorSummary( 120 | 'The maxExtent for this $runtimeType is less than its minExtent.'), 121 | DoubleProperty('The specified maxExtent was', maxExtent), 122 | DoubleProperty('The specified minExtent was', minExtent), 123 | ]); 124 | }()); 125 | 126 | child?.layout( 127 | constraints.asBoxConstraints( 128 | maxExtent: math.max(minExtent, maxExtent - shrinkOffset), 129 | ), 130 | parentUsesSize: true, 131 | ); 132 | } 133 | 134 | /// Returns the distance from the leading _visible_ edge of the sliver to the 135 | /// side of the child closest to that edge, in the scroll axis direction. 136 | /// 137 | /// For example, if the [constraints] describe this sliver as having an axis 138 | /// direction of [AxisDirection.down], then this is the distance from the top 139 | /// of the visible portion of the sliver to the top of the child. If the child 140 | /// is scrolled partially off the top of the viewport, then this will be 141 | /// negative. On the other hand, if the [constraints] describe this sliver as 142 | /// having an axis direction of [AxisDirection.up], then this is the distance 143 | /// from the bottom of the visible portion of the sliver to the bottom of the 144 | /// child. In both cases, this is the direction of increasing 145 | /// [SliverConstraints.scrollOffset]. 146 | /// 147 | /// Calling this when the child is not visible is not valid. 148 | /// 149 | /// The argument must be the value of the [child] property. 150 | /// 151 | /// This must be implemented by [RenderSliverPersistentHeader] subclasses. 152 | /// 153 | /// If there is no child, this should return 0.0. 154 | @override 155 | double childMainAxisPosition(covariant RenderObject? child) => 0.0; 156 | 157 | @override 158 | bool hitTestChildren(SliverHitTestResult result, 159 | {required double mainAxisPosition, required double crossAxisPosition}) { 160 | assert(geometry!.hitTestExtent > 0.0); 161 | if (child != null) 162 | return hitTestBoxChild(BoxHitTestResult.wrap(result), child!, 163 | mainAxisPosition: mainAxisPosition, 164 | crossAxisPosition: crossAxisPosition); 165 | return false; 166 | } 167 | 168 | @override 169 | void applyPaintTransform(RenderObject child, Matrix4 transform) { 170 | if (child != minProtoType && child != maxProtoType) { 171 | applyPaintTransformForBoxChild(child as RenderBox, transform); 172 | } 173 | } 174 | 175 | @override 176 | void paint(PaintingContext context, Offset offset) { 177 | if (child != null && geometry!.visible) { 178 | switch (applyGrowthDirectionToAxisDirection( 179 | constraints.axisDirection, constraints.growthDirection)) { 180 | case AxisDirection.up: 181 | offset += Offset( 182 | 0.0, 183 | geometry!.paintExtent - 184 | childMainAxisPosition(child) - 185 | childExtent); 186 | break; 187 | case AxisDirection.down: 188 | offset += Offset(0.0, childMainAxisPosition(child)); 189 | break; 190 | case AxisDirection.left: 191 | offset += Offset( 192 | geometry!.paintExtent - 193 | childMainAxisPosition(child) - 194 | childExtent, 195 | 0.0); 196 | break; 197 | case AxisDirection.right: 198 | offset += Offset(childMainAxisPosition(child), 0.0); 199 | break; 200 | } 201 | context.paintChild(child!, offset); 202 | } 203 | } 204 | 205 | /// Whether the [SemanticsNode]s associated with this [RenderSliver] should 206 | /// be excluded from the semantic scrolling area. 207 | /// 208 | /// [RenderSliver]s that stay on the screen even though the user has scrolled 209 | /// past them (e.g. a pinned app bar) should set this to true. 210 | @protected 211 | bool get excludeFromSemanticsScrolling => _excludeFromSemanticsScrolling; 212 | bool _excludeFromSemanticsScrolling = false; 213 | set excludeFromSemanticsScrolling(bool value) { 214 | if (_excludeFromSemanticsScrolling == value) { 215 | return; 216 | } 217 | _excludeFromSemanticsScrolling = value; 218 | markNeedsSemanticsUpdate(); 219 | } 220 | 221 | @override 222 | void describeSemanticsConfiguration(SemanticsConfiguration config) { 223 | super.describeSemanticsConfiguration(config); 224 | 225 | if (_excludeFromSemanticsScrolling) 226 | config.addTagForChildren(RenderViewport.excludeFromScrolling); 227 | } 228 | 229 | @override 230 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { 231 | super.debugFillProperties(properties); 232 | //properties.add(DoubleProperty.lazy('maxExtent', () => maxExtent)); 233 | properties.add(DoubleProperty.lazy( 234 | 'child position', () => childMainAxisPosition(child))); 235 | } 236 | 237 | SliverPinnedPersistentHeaderElement? element; 238 | 239 | void updateChild(double shrinkOffset, double? minExtent, double maxExtent, 240 | bool overlapsContent) { 241 | assert(element != null); 242 | element!.build(shrinkOffset, minExtent, maxExtent, overlapsContent); 243 | } 244 | 245 | void triggerRebuild() { 246 | markNeedsLayout(); 247 | } 248 | 249 | @override 250 | void attach(PipelineOwner owner) { 251 | super.attach(owner); 252 | if (_minProtoType != null) { 253 | _minProtoType!.attach(owner); 254 | } 255 | if (_maxProtoType != null) { 256 | _maxProtoType!.attach(owner); 257 | } 258 | } 259 | 260 | @override 261 | void detach() { 262 | super.detach(); 263 | if (_minProtoType != null) { 264 | _minProtoType!.detach(); 265 | } 266 | if (_maxProtoType != null) { 267 | _maxProtoType!.detach(); 268 | } 269 | } 270 | 271 | @override 272 | void redepthChildren() { 273 | if (_minProtoType != null) { 274 | redepthChild(_minProtoType!); 275 | } 276 | if (_maxProtoType != null) { 277 | redepthChild(_maxProtoType!); 278 | } 279 | super.redepthChildren(); 280 | } 281 | 282 | @override 283 | void visitChildren(RenderObjectVisitor visitor) { 284 | super.visitChildren(visitor); 285 | if (_minProtoType != null) { 286 | visitor(_minProtoType!); 287 | } 288 | if (_maxProtoType != null) { 289 | visitor(_maxProtoType!); 290 | } 291 | } 292 | } 293 | 294 | /// A pinned [RenderSliver] that contains a single [RenderBox]. 295 | class RenderSliverPinnedToBoxAdapter extends RenderSliverSingleBoxAdapter { 296 | /// Creates a [RenderSliver] that wraps a [RenderBox]. 297 | RenderSliverPinnedToBoxAdapter({ 298 | RenderBox? child, 299 | }) : super(child: child); 300 | 301 | @override 302 | void performLayout() { 303 | if (child == null) { 304 | geometry = SliverGeometry.zero; 305 | return; 306 | } 307 | child!.layout(constraints.asBoxConstraints(), parentUsesSize: true); 308 | assert(childExtent != null); 309 | final double effectiveRemainingPaintExtent = 310 | math.max(0, constraints.remainingPaintExtent - constraints.overlap); 311 | final double layoutExtent = (childExtent! - constraints.scrollOffset) 312 | .clamp(0.0, effectiveRemainingPaintExtent); 313 | 314 | geometry = SliverGeometry( 315 | scrollExtent: childExtent!, 316 | paintOrigin: constraints.overlap, 317 | paintExtent: math.min(childExtent!, effectiveRemainingPaintExtent), 318 | layoutExtent: layoutExtent, 319 | maxPaintExtent: childExtent!, 320 | maxScrollObstructionExtent: childExtent!, 321 | cacheExtent: layoutExtent > 0.0 322 | ? -constraints.cacheOrigin + layoutExtent 323 | : layoutExtent, 324 | hasVisualOverflow: 325 | true, // Conservatively say we do have overflow to avoid complexity. 326 | ); 327 | setChildParentData(child!, constraints, geometry); 328 | } 329 | 330 | @override 331 | void setChildParentData(RenderObject child, SliverConstraints constraints, 332 | SliverGeometry? geometry) { 333 | final SliverPhysicalParentData? childParentData = 334 | child.parentData as SliverPhysicalParentData?; 335 | Offset offset = Offset.zero; 336 | switch (applyGrowthDirectionToAxisDirection( 337 | constraints.axisDirection, constraints.growthDirection)) { 338 | case AxisDirection.up: 339 | offset += Offset( 340 | 0.0, 341 | geometry!.paintExtent - 342 | childMainAxisPosition(child as RenderBox) - 343 | childExtent!); 344 | break; 345 | case AxisDirection.down: 346 | offset += Offset(0.0, childMainAxisPosition(child as RenderBox)); 347 | break; 348 | case AxisDirection.left: 349 | offset += Offset( 350 | geometry!.paintExtent - 351 | childMainAxisPosition(child as RenderBox) - 352 | childExtent!, 353 | 0.0); 354 | break; 355 | case AxisDirection.right: 356 | offset += Offset(childMainAxisPosition(child as RenderBox), 0.0); 357 | break; 358 | } 359 | childParentData!.paintOffset = offset; 360 | } 361 | 362 | @override 363 | double childMainAxisPosition(RenderBox child) => 0.0; 364 | 365 | double? get childExtent { 366 | return getChildExtend(child, constraints); 367 | } 368 | } 369 | 370 | double getChildExtend(RenderBox? child, SliverConstraints constraints) { 371 | if (child == null) { 372 | return 0.0; 373 | } 374 | assert(child.hasSize); 375 | switch (constraints.axis) { 376 | case Axis.vertical: 377 | return child.size.height; 378 | case Axis.horizontal: 379 | return child.size.width; 380 | } 381 | } 382 | 383 | /// Sliver BoxAdapter for nested scrollable (like webview) 384 | /// 385 | /// come form RenderSliverToBoxAdapter and RenderSliverFixedExtentBoxAdaptor 386 | class RenderSliverToNestedScrollBoxAdapter 387 | extends RenderSliverSingleBoxAdapter { 388 | /// Creates a [RenderSliver] that wraps a [RenderBox]. 389 | RenderSliverToNestedScrollBoxAdapter({ 390 | RenderBox? child, 391 | required double childExtent, 392 | required this.onScrollOffsetChanged, 393 | }) : _childExtent = childExtent, 394 | super(child: child); 395 | 396 | double get childExtent => _childExtent; 397 | double _childExtent; 398 | set childExtent(double value) { 399 | assert(value != null); 400 | if (_childExtent == value) { 401 | return; 402 | } 403 | _childExtent = value; 404 | markNeedsLayout(); 405 | } 406 | 407 | ScrollOffsetChanged onScrollOffsetChanged; 408 | 409 | @override 410 | void performLayout() { 411 | if (child == null) { 412 | geometry = SliverGeometry.zero; 413 | return; 414 | } 415 | final double childLayoutExtent = 416 | min(childExtent, constraints.viewportMainAxisExtent); 417 | 418 | final double scrollOffset = 419 | constraints.scrollOffset + constraints.cacheOrigin; 420 | assert(scrollOffset >= 0.0); 421 | final double remainingExtent = constraints.remainingCacheExtent; 422 | assert(remainingExtent >= 0.0); 423 | //final double targetEndScrollOffset = scrollOffset + remainingExtent; 424 | 425 | if (!child!.hasSize || child!.size.height != childLayoutExtent) { 426 | final BoxConstraints childConstraints = constraints.asBoxConstraints( 427 | minExtent: childLayoutExtent, 428 | maxExtent: childLayoutExtent, 429 | ); 430 | 431 | child!.layout(childConstraints, parentUsesSize: true); 432 | } 433 | 434 | // final double targetEndScrollOffsetForPaint = 435 | // constraints.scrollOffset + constraints.remainingPaintExtent; 436 | 437 | const double leadingScrollOffset = 0; 438 | final double trailingScrollOffset = childExtent; 439 | 440 | final double paintExtent = calculatePaintOffset( 441 | constraints, 442 | from: leadingScrollOffset, 443 | to: trailingScrollOffset, 444 | ); 445 | 446 | final double cacheExtent = calculateCacheOffset( 447 | constraints, 448 | from: leadingScrollOffset, 449 | to: trailingScrollOffset, 450 | ); 451 | final double estimatedMaxScrollOffset = childExtent; 452 | geometry = SliverGeometry( 453 | scrollExtent: estimatedMaxScrollOffset, 454 | paintExtent: paintExtent, 455 | cacheExtent: cacheExtent, 456 | maxPaintExtent: estimatedMaxScrollOffset, 457 | // Conservative to avoid flickering away the clip during scroll. 458 | hasVisualOverflow: constraints.scrollOffset > 0.0, 459 | ); 460 | 461 | setChildParentData(child!, constraints, geometry!); 462 | } 463 | 464 | @override 465 | void paint(PaintingContext context, Offset offset) { 466 | if (childExtent > constraints.viewportMainAxisExtent) { 467 | // maybe overscroll in ios 468 | onScrollOffsetChanged(math.min(constraints.scrollOffset, 469 | childExtent - constraints.viewportMainAxisExtent)); 470 | } 471 | super.paint(context, offset); 472 | } 473 | 474 | @override 475 | @protected 476 | void setChildParentData(RenderObject child, SliverConstraints constraints, 477 | SliverGeometry geometry) { 478 | final SliverPhysicalParentData childParentData = 479 | child.parentData! as SliverPhysicalParentData; 480 | final double targetEndScrollOffsetForPaint = 481 | constraints.scrollOffset + constraints.remainingPaintExtent; 482 | assert(constraints.axisDirection != null); 483 | assert(constraints.growthDirection != null); 484 | switch (applyGrowthDirectionToAxisDirection( 485 | constraints.axisDirection, constraints.growthDirection)) { 486 | case AxisDirection.up: 487 | assert(false, 'not support for RenderSliverToScrollableBoxAdapter'); 488 | // childParentData.paintOffset = Offset( 489 | // 0.0, 490 | // -(geometry.scrollExtent - 491 | // (geometry.paintExtent + constraints.scrollOffset))); 492 | break; 493 | case AxisDirection.right: 494 | assert(false, 'not support for RenderSliverToScrollableBoxAdapter'); 495 | //childParentData.paintOffset = Offset(-constraints.scrollOffset, 0.0); 496 | break; 497 | case AxisDirection.down: 498 | //childParentData.paintOffset = Offset(0.0, -constraints.scrollOffset); 499 | // zmtzawqlp 500 | 501 | childParentData.paintOffset = Offset( 502 | 0.0, 503 | childExtent <= constraints.viewportMainAxisExtent 504 | ? -constraints.scrollOffset 505 | : min(childExtent - targetEndScrollOffsetForPaint, 0)); 506 | break; 507 | case AxisDirection.left: 508 | assert(false, 'not support for RenderSliverToScrollableBoxAdapter'); 509 | // childParentData.paintOffset = Offset( 510 | // -(geometry.scrollExtent - 511 | // (geometry.paintExtent + constraints.scrollOffset)), 512 | // 0.0); 513 | break; 514 | } 515 | assert(childParentData.paintOffset != null); 516 | } 517 | 518 | @override 519 | bool hitTestBoxChild(BoxHitTestResult result, RenderBox child, 520 | {required double mainAxisPosition, required double crossAxisPosition}) { 521 | final bool rightWayUp = _getRightWayUp(constraints); 522 | double delta = childMainAxisPosition(child); 523 | final double crossAxisDelta = childCrossAxisPosition(child); 524 | double absolutePosition = mainAxisPosition - delta; 525 | final double absoluteCrossAxisPosition = crossAxisPosition - crossAxisDelta; 526 | Offset paintOffset, transformedPosition; 527 | assert(constraints.axis != null); 528 | switch (constraints.axis) { 529 | case Axis.horizontal: 530 | assert(true, 'not support for RenderSliverToScrollableBoxAdapter'); 531 | if (!rightWayUp) { 532 | absolutePosition = child.size.width - absolutePosition; 533 | delta = geometry!.paintExtent - child.size.width - delta; 534 | } 535 | paintOffset = Offset(delta, crossAxisDelta); 536 | transformedPosition = 537 | Offset(absolutePosition, absoluteCrossAxisPosition); 538 | break; 539 | case Axis.vertical: 540 | if (!rightWayUp) { 541 | absolutePosition = child.size.height - absolutePosition; 542 | delta = geometry!.paintExtent - child.size.height - delta; 543 | } 544 | paintOffset = Offset(crossAxisDelta, delta); 545 | transformedPosition = 546 | Offset(absoluteCrossAxisPosition, absolutePosition); 547 | break; 548 | } 549 | assert(paintOffset != null); 550 | assert(transformedPosition != null); 551 | return result.addWithOutOfBandPosition( 552 | paintOffset: paintOffset, 553 | hitTest: (BoxHitTestResult result) { 554 | // zmtzawqlp 555 | return child.hitTest(result, 556 | position: Offset(transformedPosition.dx, 557 | transformedPosition.dy - constraints.scrollOffset)); 558 | }, 559 | ); 560 | } 561 | 562 | bool _getRightWayUp(SliverConstraints constraints) { 563 | assert(constraints != null); 564 | assert(constraints.axisDirection != null); 565 | bool rightWayUp; 566 | switch (constraints.axisDirection) { 567 | case AxisDirection.up: 568 | case AxisDirection.left: 569 | rightWayUp = false; 570 | break; 571 | case AxisDirection.down: 572 | case AxisDirection.right: 573 | rightWayUp = true; 574 | break; 575 | } 576 | assert(constraints.growthDirection != null); 577 | switch (constraints.growthDirection) { 578 | case GrowthDirection.forward: 579 | break; 580 | case GrowthDirection.reverse: 581 | rightWayUp = !rightWayUp; 582 | break; 583 | } 584 | assert(rightWayUp != null); 585 | return rightWayUp; 586 | } 587 | } 588 | -------------------------------------------------------------------------------- /lib/src/widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'element.dart'; 4 | import 'rendering.dart'; 5 | 6 | /// Delegate for configuring a [SliverPinnedPersistentHeader]. 7 | abstract class SliverPinnedPersistentHeaderDelegate { 8 | SliverPinnedPersistentHeaderDelegate({ 9 | required this.minExtentProtoType, 10 | required this.maxExtentProtoType, 11 | }); 12 | 13 | /// The poroto type widget of min extent 14 | final Widget minExtentProtoType; 15 | 16 | /// The poroto type widget of max extent 17 | final Widget maxExtentProtoType; 18 | 19 | /// The widget to place inside the [SliverPinnedPersistentHeader]. 20 | /// 21 | /// The `context` is the [BuildContext] of the sliver. 22 | /// 23 | /// The `shrinkOffset` is a distance from [maxExtent] towards [minExtent] 24 | /// representing the current amount by which the sliver has been shrunk. When 25 | /// the `shrinkOffset` is zero, the contents will be rendered with a dimension 26 | /// of [maxExtent] in the main axis. When `shrinkOffset` equals the difference 27 | /// between [maxExtent] and [minExtent] (a positive number), the contents will 28 | /// be rendered with a dimension of [minExtent] in the main axis. The 29 | /// `shrinkOffset` will always be a positive number in that range. 30 | /// 31 | /// The `overlapsContent` argument is true if subsequent slivers (if any) will 32 | /// be rendered beneath this one, and false if the sliver will not have any 33 | /// contents below it. Typically this is used to decide whether to draw a 34 | /// shadow to simulate the sliver being above the contents below it. Typically 35 | /// this is true when `shrinkOffset` is at its greatest value and false 36 | /// otherwise, but that is not guaranteed. See [NestedScrollView] for an 37 | /// example of a case where `overlapsContent`'s value can be unrelated to 38 | /// `shrinkOffset`. 39 | /// 40 | /// The 'minExtent'is the smallest size to allow the header to reach, when it shrinks at the 41 | /// start of the viewport. 42 | /// 43 | /// This must return a value equal to or less than [maxExtent]. 44 | /// 45 | /// This value should not change over the lifetime of the delegate. It should 46 | /// be based entirely on the constructor arguments passed to the delegate. See 47 | /// [shouldRebuild], which must return true if a new delegate would return a 48 | /// different value. 49 | /// 50 | /// 51 | /// The `maxExtent` argument is the size of the header when it is not shrinking at the top of the 52 | /// viewport. 53 | /// 54 | /// This must return a value equal to or greater than [minExtent]. 55 | /// 56 | /// This value should not change over the lifetime of the delegate. It should 57 | /// be based entirely on the constructor arguments passed to the delegate. See 58 | /// [shouldRebuild], which must return true if a new delegate would return a 59 | /// different value. 60 | Widget build(BuildContext context, double shrinkOffset, double? minExtent, 61 | double maxExtent, bool overlapsContent); 62 | 63 | /// Whether this delegate is meaningfully different from the old delegate. 64 | /// 65 | /// If this returns false, then the header might not be rebuilt, even though 66 | /// the instance of the delegate changed. 67 | /// 68 | /// This must return true if `oldDelegate` and this object would return 69 | /// different values for [minExtent], [maxExtent], [snapConfiguration], or 70 | /// would return a meaningfully different widget tree from [build] for the 71 | /// same arguments. 72 | bool shouldRebuild( 73 | covariant SliverPinnedPersistentHeaderDelegate oldDelegate); 74 | } 75 | 76 | /// A sliver whose size varies when the sliver is scrolled to the leading edge 77 | /// of the viewport. 78 | /// 79 | /// This is the layout primitive that [ExtendedSliverAppbar] uses for its 80 | /// shrinking/growing effect. 81 | class SliverPinnedPersistentHeader extends StatelessWidget { 82 | /// Creates a sliver that varies its size when it is scrolled to the start of 83 | /// a viewport. 84 | /// 85 | /// The [delegate] must not be null. 86 | const SliverPinnedPersistentHeader({required this.delegate}); 87 | final SliverPinnedPersistentHeaderDelegate delegate; 88 | @override 89 | Widget build(BuildContext context) { 90 | return SliverPinnedPersistentHeaderRenderObjectWidget(delegate); 91 | } 92 | } 93 | 94 | class SliverPinnedPersistentHeaderRenderObjectWidget 95 | extends RenderObjectWidget { 96 | const SliverPinnedPersistentHeaderRenderObjectWidget(this.delegate); 97 | final SliverPinnedPersistentHeaderDelegate delegate; 98 | 99 | @override 100 | RenderObjectElement createElement() { 101 | return SliverPinnedPersistentHeaderElement(this); 102 | } 103 | 104 | @override 105 | RenderObject createRenderObject(BuildContext context) { 106 | return RenderSliverPinnedPersistentHeader(); 107 | } 108 | } 109 | 110 | /// A pinned sliver that contains a single box widget. 111 | /// 112 | /// Slivers are special-purpose widgets that can be combined using a 113 | /// [CustomScrollView] to create custom scroll effects. A [SliverToBoxAdapter] 114 | /// is a basic sliver that creates a bridge back to one of the usual box-based 115 | /// widgets. 116 | /// 117 | /// Rather than using multiple [SliverToBoxAdapter] widgets to display multiple 118 | /// box widgets in a [CustomScrollView], consider using [SliverList], 119 | /// [SliverFixedExtentList], [SliverPrototypeExtentList], or [SliverGrid], 120 | /// which are more efficient because they instantiate only those children that 121 | /// are actually visible through the scroll view's viewport. 122 | class SliverPinnedToBoxAdapter extends SingleChildRenderObjectWidget { 123 | /// Creates a pinned sliver that contains a single box widget. 124 | const SliverPinnedToBoxAdapter({ 125 | Key? key, 126 | Widget? child, 127 | }) : super(key: key, child: child); 128 | 129 | @override 130 | RenderSliverPinnedToBoxAdapter createRenderObject(BuildContext context) => 131 | RenderSliverPinnedToBoxAdapter(); 132 | } 133 | 134 | /// Sliver BoxAdapter for nested scrollable (like webview) 135 | /// 136 | class SliverToNestedScrollBoxAdapter extends SingleChildRenderObjectWidget { 137 | /// Creates a sliver that contains a single nested scrollable box widget. 138 | const SliverToNestedScrollBoxAdapter({ 139 | Key? key, 140 | Widget? child, 141 | required this.childExtent, 142 | required this.onScrollOffsetChanged, 143 | }) : super(key: key, child: child); 144 | 145 | final double childExtent; 146 | final ScrollOffsetChanged onScrollOffsetChanged; 147 | 148 | @override 149 | RenderSliverToNestedScrollBoxAdapter createRenderObject( 150 | BuildContext context) => 151 | RenderSliverToNestedScrollBoxAdapter( 152 | childExtent: childExtent, 153 | onScrollOffsetChanged: onScrollOffsetChanged, 154 | ); 155 | 156 | @override 157 | void updateRenderObject(BuildContext context, 158 | covariant RenderSliverToNestedScrollBoxAdapter renderObject) { 159 | renderObject.childExtent = childExtent; 160 | renderObject.onScrollOffsetChanged = onScrollOffsetChanged; 161 | } 162 | } 163 | 164 | /// A material design app bar that integrates with a [CustomScrollView]. 165 | /// See more [SliverPinnedPersistentHeader]. 166 | class ExtendedSliverAppbar extends StatelessWidget { 167 | const ExtendedSliverAppbar({ 168 | this.leading, 169 | this.title, 170 | this.actions, 171 | this.background, 172 | this.toolBarColor, 173 | this.onBuild, 174 | this.statusbarHeight, 175 | this.toolbarHeight, 176 | this.isOpacityFadeWithToolbar = true, 177 | this.isOpacityFadeWithTitle = true, 178 | this.mainAxisAlignment = MainAxisAlignment.spaceBetween, 179 | this.crossAxisAlignment = CrossAxisAlignment.center, 180 | }); 181 | 182 | /// A widget to display before the [title]. 183 | final Widget? leading; 184 | 185 | /// The primary widget displayed in the app bar. 186 | /// 187 | /// Typically a [Text] widget containing a description of the current contents 188 | /// of the app. 189 | final Widget? title; 190 | 191 | /// Widgets to display after the [title] widget. 192 | final Widget? actions; 193 | 194 | /// A Widget to display behind [leading],[title],[actions]. 195 | final Widget? background; 196 | 197 | /// Background color for Row(leading,title,background). 198 | final Color? toolBarColor; 199 | 200 | /// Call when re-build on scroll. 201 | final OnSliverPinnedPersistentHeaderDelegateBuild? onBuild; 202 | 203 | /// Height of Toolbar. Default value : kToolbarHeight 204 | final double? toolbarHeight; 205 | 206 | /// Height of Statusbar. Default value : MediaQuery.of(context).padding.top 207 | final double? statusbarHeight; 208 | 209 | /// Whether do an opacity fade for toolbar. 210 | /// 211 | /// By default, the value of isOpacityFadeWithToolbar is true. 212 | final bool isOpacityFadeWithToolbar; 213 | 214 | /// Whether do an opacity fade for title. 215 | /// 216 | /// By default, the value of isOpacityFadeWithTitle is true. 217 | final bool isOpacityFadeWithTitle; 218 | 219 | /// MainAxisAlignment of toolbar 220 | /// 221 | /// By default, the value of mainAxisAlignment is MainAxisAlignment.spaceBetween. 222 | final MainAxisAlignment mainAxisAlignment; 223 | 224 | /// CrossAxisAlignment of toolbar 225 | /// 226 | /// By default, the value of crossAxisAlignment is CrossAxisAlignment.center. 227 | final CrossAxisAlignment crossAxisAlignment; 228 | 229 | @override 230 | Widget build(BuildContext context) { 231 | final SafeArea? safeArea = 232 | context.findAncestorWidgetOfExactType(); 233 | double? statusbarHeight = this.statusbarHeight; 234 | final double toolbarHeight = this.toolbarHeight ?? kToolbarHeight; 235 | if (statusbarHeight == null && (safeArea == null || !safeArea.top)) { 236 | statusbarHeight = MediaQuery.of(context).padding.top; 237 | } 238 | statusbarHeight ??= 0; 239 | final Widget toolbar = SizedBox( 240 | height: toolbarHeight + statusbarHeight, 241 | ); 242 | 243 | return SliverPinnedPersistentHeader( 244 | delegate: _ExtendedSliverAppbarDelegate( 245 | minExtentProtoType: toolbar, 246 | maxExtentProtoType: background ?? toolbar, 247 | title: title, 248 | leading: leading, 249 | actions: actions, 250 | background: background, 251 | statusbarHeight: statusbarHeight, 252 | toolbarHeight: toolbarHeight, 253 | toolBarColor: toolBarColor, 254 | onBuild: onBuild, 255 | isOpacityFadeWithToolbar: isOpacityFadeWithToolbar, 256 | isOpacityFadeWithTitle: isOpacityFadeWithTitle, 257 | mainAxisAlignment: mainAxisAlignment, 258 | crossAxisAlignment: crossAxisAlignment, 259 | ), 260 | ); 261 | } 262 | } 263 | 264 | class _ExtendedSliverAppbarDelegate 265 | extends SliverPinnedPersistentHeaderDelegate { 266 | _ExtendedSliverAppbarDelegate({ 267 | required Widget minExtentProtoType, 268 | required Widget maxExtentProtoType, 269 | this.leading, 270 | this.title, 271 | this.actions, 272 | this.background, 273 | this.toolBarColor, 274 | this.onBuild, 275 | this.statusbarHeight, 276 | this.toolbarHeight, 277 | this.isOpacityFadeWithToolbar = true, 278 | this.isOpacityFadeWithTitle = true, 279 | this.mainAxisAlignment = MainAxisAlignment.spaceBetween, 280 | this.crossAxisAlignment = CrossAxisAlignment.center, 281 | }) : super( 282 | minExtentProtoType: minExtentProtoType, 283 | maxExtentProtoType: maxExtentProtoType, 284 | ); 285 | 286 | /// A widget to display before the [title]. 287 | final Widget? leading; 288 | 289 | /// The primary widget displayed in the app bar. 290 | /// 291 | /// Typically a [Text] widget containing a description of the current contents 292 | /// of the app. 293 | final Widget? title; 294 | 295 | /// Widgets to display after the [title] widget. 296 | final Widget? actions; 297 | 298 | /// A Widget to display behind [leading],[title],[actions]. 299 | final Widget? background; 300 | 301 | /// Background color for Row(leading,title,background). 302 | final Color? toolBarColor; 303 | 304 | /// Call when re-build on scroll. 305 | final OnSliverPinnedPersistentHeaderDelegateBuild? onBuild; 306 | 307 | /// Height of Toolbar. Default value : kToolbarHeight 308 | final double? toolbarHeight; 309 | 310 | /// Height of Statusbar. Default value : MediaQuery.of(context).padding.top 311 | final double? statusbarHeight; 312 | 313 | /// Whether do an opacity fade for toolbar. 314 | /// 315 | /// By default, the value of isOpacityFadeWithToolbar is true. 316 | final bool isOpacityFadeWithToolbar; 317 | 318 | /// Whether do an opacity fade for title. 319 | /// 320 | /// By default, the value of isOpacityFadeWithTitle is true. 321 | final bool isOpacityFadeWithTitle; 322 | 323 | /// MainAxisAlignment of toolbar 324 | /// 325 | /// By default, the value of mainAxisAlignment is MainAxisAlignment.spaceBetween. 326 | final MainAxisAlignment mainAxisAlignment; 327 | 328 | /// CrossAxisAlignment of toolbar 329 | /// 330 | /// By default, the value of crossAxisAlignment is CrossAxisAlignment.center. 331 | final CrossAxisAlignment crossAxisAlignment; 332 | 333 | @override 334 | Widget build( 335 | BuildContext context, 336 | double shrinkOffset, 337 | double? minExtent, 338 | double maxExtent, 339 | bool overlapsContent, 340 | ) { 341 | onBuild?.call(context, shrinkOffset, minExtent, maxExtent, overlapsContent); 342 | final double opacity = 343 | (shrinkOffset / (maxExtent - minExtent!)).clamp(0.0, 1.0); 344 | Widget? titleWidget = title; 345 | if (titleWidget != null) { 346 | if (isOpacityFadeWithTitle) { 347 | titleWidget = Opacity( 348 | opacity: opacity, 349 | child: titleWidget, 350 | ); 351 | } 352 | } else { 353 | titleWidget = Container(); 354 | } 355 | final ThemeData theme = Theme.of(context); 356 | 357 | Color toolBarColor = this.toolBarColor ?? theme.primaryColor; 358 | if (isOpacityFadeWithToolbar) { 359 | toolBarColor = toolBarColor.withOpacity(opacity); 360 | } 361 | 362 | final Widget toolbar = Container( 363 | height: toolbarHeight! + statusbarHeight!, 364 | padding: EdgeInsets.only(top: statusbarHeight!), 365 | color: toolBarColor, 366 | child: Row( 367 | mainAxisAlignment: mainAxisAlignment, 368 | crossAxisAlignment: crossAxisAlignment, 369 | children: [ 370 | leading ?? 371 | const BackButton( 372 | onPressed: null, 373 | ), 374 | titleWidget, 375 | actions ?? Container(), 376 | ], 377 | ), 378 | ); 379 | 380 | return Material( 381 | child: ClipRect( 382 | child: Stack( 383 | children: [ 384 | Positioned( 385 | child: maxExtentProtoType, 386 | top: -shrinkOffset, 387 | bottom: 0, 388 | left: 0, 389 | right: 0, 390 | ), 391 | Positioned( 392 | child: toolbar, 393 | top: 0, 394 | left: 0, 395 | right: 0, 396 | ), 397 | ], 398 | ), 399 | ), 400 | ); 401 | } 402 | 403 | @override 404 | bool shouldRebuild(SliverPinnedPersistentHeaderDelegate oldDelegate) { 405 | if (oldDelegate.runtimeType != runtimeType) { 406 | return true; 407 | } 408 | 409 | return oldDelegate is _ExtendedSliverAppbarDelegate && 410 | (oldDelegate.minExtentProtoType != minExtentProtoType || 411 | oldDelegate.maxExtentProtoType != maxExtentProtoType || 412 | oldDelegate.leading != leading || 413 | oldDelegate.title != title || 414 | oldDelegate.actions != actions || 415 | oldDelegate.background != background || 416 | oldDelegate.statusbarHeight != statusbarHeight || 417 | oldDelegate.toolBarColor != toolBarColor || 418 | oldDelegate.toolbarHeight != toolbarHeight || 419 | oldDelegate.onBuild != onBuild || 420 | oldDelegate.isOpacityFadeWithTitle != isOpacityFadeWithTitle || 421 | oldDelegate.isOpacityFadeWithToolbar != isOpacityFadeWithToolbar || 422 | oldDelegate.mainAxisAlignment != mainAxisAlignment || 423 | oldDelegate.crossAxisAlignment != crossAxisAlignment); 424 | } 425 | } 426 | 427 | /// Call when re-build on scroll 428 | typedef OnSliverPinnedPersistentHeaderDelegateBuild = void Function( 429 | BuildContext context, 430 | double shrinkOffset, 431 | double? minExtent, 432 | double maxExtent, 433 | bool overlapsContent, 434 | ); 435 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: extended_sliver 2 | description: A powerful extension library of Sliver, which include SliverPinnedPersistentHeader, SliverPinnedToBoxAdapter and ExtendedSliverAppbar. 3 | version: 2.1.3 4 | homepage: https://github.com/fluttercandies/extended_sliver 5 | 6 | environment: 7 | sdk: '>=2.12.0 <3.0.0' 8 | flutter: ">=2.0.0" 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | 14 | dev_dependencies: 15 | flutter_test: 16 | sdk: flutter 17 | 18 | # For information on the generic Dart part of this file, see the 19 | # following page: https://dart.dev/tools/pub/pubspec 20 | 21 | # The following section is specific to Flutter. 22 | flutter: 23 | 24 | # To add assets to your package, add an assets section, like this: 25 | # assets: 26 | # - images/a_dot_burr.jpeg 27 | # - images/a_dot_ham.jpeg 28 | # 29 | # For details regarding assets in packages, see 30 | # https://flutter.dev/assets-and-images/#from-packages 31 | # 32 | # An image asset can refer to one or more resolution-specific "variants", see 33 | # https://flutter.dev/assets-and-images/#resolution-aware. 34 | 35 | # To add custom fonts to your package, add a fonts section here, 36 | # in this "flutter" section. Each entry in this list should have a 37 | # "family" key with the font family name, and a "fonts" key with a 38 | # list giving the asset and other descriptors for the font. For 39 | # example: 40 | # fonts: 41 | # - family: Schyler 42 | # fonts: 43 | # - asset: fonts/Schyler-Regular.ttf 44 | # - asset: fonts/Schyler-Italic.ttf 45 | # style: italic 46 | # - family: Trajan Pro 47 | # fonts: 48 | # - asset: fonts/TrajanPro.ttf 49 | # - asset: fonts/TrajanPro_Bold.ttf 50 | # weight: 700 51 | # 52 | # For details regarding fonts in packages, see 53 | # https://flutter.dev/custom-fonts/#from-packages 54 | -------------------------------------------------------------------------------- /test/extended_sliver_test.dart: -------------------------------------------------------------------------------- 1 | // import 'package:flutter_test/flutter_test.dart'; 2 | 3 | // import 'package:extended_sliver/extended_sliver.dart'; 4 | 5 | // void main() { 6 | // test('adds one to input values', () { 7 | // final calculator = Calculator(); 8 | // expect(calculator.addOne(2), 3); 9 | // expect(calculator.addOne(-7), -6); 10 | // expect(calculator.addOne(0), 1); 11 | // expect(() => calculator.addOne(null), throwsNoSuchMethodError); 12 | // }); 13 | // } 14 | --------------------------------------------------------------------------------