diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..c7f0c06
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,10 @@
+github: letsar
+patreon: romainrastel
+open_collective: # Replace with a single Open Collective username
+ko_fi: # Replace with a single Ko-fi username
+tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
+community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
+liberapay: # Replace with a single Liberapay username
+issuehunt: # Replace with a single IssueHunt username
+otechie: # Replace with a single Otechie username
+custom: ['https://www.buymeacoffee.com/romainrastel', 'paypal.me/RomainRastel']
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ad5ef16..2034efc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,62 @@
+## 0.6.4
+### Fixed
+* Not passing right overlap contraints to sliver child. Now we can create nested sticky headers!
+
+## 0.6.3
+### Fixed
+* Hit Test on not sticky header
+
+## 0.6.2
+### Fixed
+* Hit Test on not sticky header
+
+## 0.6.1
+### Fixed
+* Error due to null-safety migration.
+
+## 0.6.0
+### Changed
+* Migrated to sound null-safety.
+* Increase the minimum version of Flutter.
+* Increase the minimum version of Dart SDK.
+
+## 0.5.0
+### Changed
+* The minimum version of Flutter.
+
+## 0.4.6
+### Added
+* A new SliverStickyHeader.builder constructor instead of the deprecated SliverStickyHeaderBuilder.
+* A dependency to value_layout_builder in order to manage the SliverStickyHeader.builder.
+
+### Removed
+* Custom code to make SliverStickyHeader.builder work.
+
+## 0.4.5
+### Fixed
+* Null references issues in debug mode.
+
+## 0.4.4
+### Fixed
+* Static analysis issues.
+
+## 0.4.3
+### Fixed
+* Static analysis issues.
+
+## 0.4.2
+### Added
+* A StickyHeaderController to get the scroll offset of the current sticky header.
+
+## 0.4.1
+### Added
+* A sticky parameter to specify whether the header is sticky or not.
+
+## 0.4.0
+
+* Updated SDK constraint to support new error message formats.
+* Updated error message formats
+
## 0.3.4
### Removed
* Print call for headerPosition
diff --git a/README.md b/README.md
index 3be2638..240a9f2 100644
--- a/README.md
+++ b/README.md
@@ -14,6 +14,8 @@ A Flutter implementation of sticky headers with a sliver as a child.
* Notifies when the header scrolls outside the viewport.
* Can scroll in any direction.
* Supports overlapping (AppBars for example).
+* Supports not sticky headers (with `sticky: false` parameter).
+* Supports a controller which notifies the scroll offset of the current sticky header.
## Getting started
@@ -22,7 +24,7 @@ In the `pubspec.yaml` of your flutter project, add the following dependency:
```yaml
dependencies:
...
- flutter_sticky_header: "^0.3.4"
+ flutter_sticky_header:
```
In your library add the following import:
@@ -38,24 +40,24 @@ For help getting started with Flutter, view the online [documentation](https://f
You can place one or multiple `SliverStickyHeader`s inside a `CustomScrollView`.
```dart
-new SliverStickyHeader(
- header: new Container(
+SliverStickyHeader(
+ header: Container(
height: 60.0,
color: Colors.lightBlue,
padding: EdgeInsets.symmetric(horizontal: 16.0),
alignment: Alignment.centerLeft,
- child: new Text(
+ child: Text(
'Header #0',
style: const TextStyle(color: Colors.white),
),
),
- sliver: new SliverList(
- delegate: new SliverChildBuilderDelegate(
- (context, i) => new ListTile(
- leading: new CircleAvatar(
- child: new Text('0'),
+ sliver: SliverList(
+ delegate: SliverChildBuilderDelegate(
+ (context, i) => ListTile(
+ leading: CircleAvatar(
+ child: Text('0'),
),
- title: new Text('List tile #$i'),
+ title: Text('List tile #$i'),
),
childCount: 4,
),
@@ -63,32 +65,32 @@ new SliverStickyHeader(
);
```
-## SliverStickyHeaderBuilder
+## SliverStickyHeader.builder
-If you want to change the header layout during its scroll, you can use the `SliverStickyHeaderBuilder`.
+If you want to change the header layout during its scroll, you can use the `SliverStickyHeader.builder` constructor.
The example belows changes the opacity of the header as it scrolls off the viewport.
```dart
-new SliverStickyHeaderBuilder(
- builder: (context, state) => new Container(
+SliverStickyHeader.builder(
+ builder: (context, state) => Container(
height: 60.0,
color: (state.isPinned ? Colors.pink : Colors.lightBlue)
.withOpacity(1.0 - state.scrollPercentage),
padding: EdgeInsets.symmetric(horizontal: 16.0),
alignment: Alignment.centerLeft,
- child: new Text(
+ child: Text(
'Header #1',
style: const TextStyle(color: Colors.white),
),
),
- sliver: new SliverList(
- delegate: new SliverChildBuilderDelegate(
- (context, i) => new ListTile(
- leading: new CircleAvatar(
- child: new Text('0'),
+ sliver: SliverList(
+ delegate: SliverChildBuilderDelegate(
+ (context, i) => ListTile(
+ leading: CircleAvatar(
+ child: Text('0'),
),
- title: new Text('List tile #$i'),
+ title: Text('List tile #$i'),
),
childCount: 4,
),
@@ -98,6 +100,10 @@ new SliverStickyHeaderBuilder(
You can find more examples in the [Example](https://github.com/letsar/flutter_sticky_header/tree/master/example) project.
+## Sponsoring
+
+I'm working on my packages on my free-time, but I don't have as much time as I would. If this package or any other package I created is helping you, please consider to sponsor me. By doing so, I will prioritize your issues or your pull-requests before the others.
+
## Changelog
Please see the [Changelog](https://github.com/letsar/flutter_sticky_header/blob/master/CHANGELOG.md) page to know what's recently changed.
diff --git a/example/.gitignore b/example/.gitignore
index dee655c..f3c2053 100644
--- a/example/.gitignore
+++ b/example/.gitignore
@@ -1,9 +1,44 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
.DS_Store
-.dart_tool/
+.atom/
+.buildlog/
+.history
+.svn/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
+# Flutter/Dart/Pub related
+**/doc/api/
+**/ios/Flutter/.last_build_id
+.dart_tool/
+.flutter-plugins
+.flutter-plugins-dependencies
.packages
+.pub-cache/
.pub/
+/build/
-build/
+# Web related
+lib/generated_plugin_registrant.dart
-.flutter-plugins
+# Symbolication related
+app.*.symbols
+
+# Obfuscation related
+app.*.map.json
+
+# Exceptions to above rules.
+!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
diff --git a/example/.metadata b/example/.metadata
index 94ee986..bcef94e 100644
--- a/example/.metadata
+++ b/example/.metadata
@@ -4,5 +4,7 @@
# This file should be version controlled and should not be manually edited.
version:
- revision: c7ea3ca377e909469c68f2ab878a5bc53d3cf66b
- channel: dev
+ revision: bbfbf1770cca2da7c82e887e4e4af910034800b6
+ channel: stable
+
+project_type: app
diff --git a/example/README.md b/example/README.md
index 64a12f6..a135626 100644
--- a/example/README.md
+++ b/example/README.md
@@ -4,5 +4,13 @@ A new Flutter project.
## Getting Started
-For help getting started with Flutter, view our online
-[documentation](https://flutter.io/).
+This project is a starting point for a Flutter application.
+
+A few resources to get you started if this is your first Flutter project:
+
+- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)
+- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)
+
+For help getting started with Flutter, view our
+[online documentation](https://flutter.dev/docs), which offers tutorials,
+samples, guidance on mobile development, and a full API reference.
diff --git a/example/android/.gitignore b/example/android/.gitignore
index 65b7315..6f56801 100644
--- a/example/android/.gitignore
+++ b/example/android/.gitignore
@@ -1,10 +1,13 @@
-*.iml
-*.class
-.gradle
+gradle-wrapper.jar
+/.gradle
+/captures/
+/gradlew
+/gradlew.bat
/local.properties
-/.idea/workspace.xml
-/.idea/libraries
-.DS_Store
-/build
-/captures
GeneratedPluginRegistrant.java
+
+# Remember to never publicly share your keystore.
+# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
+key.properties
+**/*.keystore
+**/*.jks
diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle
index a23f7c0..ce52202 100644
--- a/example/android/app/build.gradle
+++ b/example/android/app/build.gradle
@@ -11,24 +11,43 @@ if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
+def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
+if (flutterVersionCode == null) {
+ flutterVersionCode = '1'
+}
+
+def flutterVersionName = localProperties.getProperty('flutter.versionName')
+if (flutterVersionName == null) {
+ flutterVersionName = '1.0'
+}
+
apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
- compileSdkVersion 27
+ compileSdkVersion flutter.compileSdkVersion
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
- lintOptions {
- disable 'InvalidPackage'
+ sourceSets {
+ main.java.srcDirs += 'src/main/kotlin'
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
- applicationId "com.letsar.fsh.example.example"
- minSdkVersion 16
- targetSdkVersion 27
- versionCode 1
- versionName "1.0"
- testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ applicationId "com.letsar.sh.example"
+ minSdkVersion flutter.minSdkVersion
+ targetSdkVersion flutter.targetSdkVersion
+ versionCode flutterVersionCode.toInteger()
+ versionName flutterVersionName
}
buildTypes {
@@ -45,7 +64,5 @@ flutter {
}
dependencies {
- testImplementation 'junit:junit:4.12'
- androidTestImplementation 'com.android.support.test:runner:1.0.1'
- androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}
diff --git a/example/android/app/src/debug/AndroidManifest.xml b/example/android/app/src/debug/AndroidManifest.xml
new file mode 100644
index 0000000..6ea83aa
--- /dev/null
+++ b/example/android/app/src/debug/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml
index 37fde23..ef91f0f 100644
--- a/example/android/app/src/main/AndroidManifest.xml
+++ b/example/android/app/src/main/AndroidManifest.xml
@@ -1,39 +1,34 @@
-
-
-
-
-
-
+
-
+
+ android:name="io.flutter.embedding.android.NormalTheme"
+ android:resource="@style/NormalTheme"
+ />
+
+
diff --git a/example/android/app/src/main/java/com/letsar/fsh/example/example/MainActivity.java b/example/android/app/src/main/java/com/letsar/fsh/example/example/MainActivity.java
deleted file mode 100644
index 8672db0..0000000
--- a/example/android/app/src/main/java/com/letsar/fsh/example/example/MainActivity.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.letsar.fsh.example.example;
-
-import android.os.Bundle;
-import io.flutter.app.FlutterActivity;
-import io.flutter.plugins.GeneratedPluginRegistrant;
-
-public class MainActivity extends FlutterActivity {
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- GeneratedPluginRegistrant.registerWith(this);
- }
-}
diff --git a/example/android/app/src/main/kotlin/com/letsar/sh/example/MainActivity.kt b/example/android/app/src/main/kotlin/com/letsar/sh/example/MainActivity.kt
new file mode 100644
index 0000000..e4b5f8e
--- /dev/null
+++ b/example/android/app/src/main/kotlin/com/letsar/sh/example/MainActivity.kt
@@ -0,0 +1,6 @@
+package com.letsar.sh.example
+
+import io.flutter.embedding.android.FlutterActivity
+
+class MainActivity: FlutterActivity() {
+}
diff --git a/example/android/app/src/main/res/drawable-v21/launch_background.xml b/example/android/app/src/main/res/drawable-v21/launch_background.xml
new file mode 100644
index 0000000..f74085f
--- /dev/null
+++ b/example/android/app/src/main/res/drawable-v21/launch_background.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/example/android/app/src/main/res/values-night/styles.xml b/example/android/app/src/main/res/values-night/styles.xml
new file mode 100644
index 0000000..3db14bb
--- /dev/null
+++ b/example/android/app/src/main/res/values-night/styles.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/example/android/app/src/main/res/values/styles.xml b/example/android/app/src/main/res/values/styles.xml
index 00fa441..d460d1e 100644
--- a/example/android/app/src/main/res/values/styles.xml
+++ b/example/android/app/src/main/res/values/styles.xml
@@ -1,8 +1,18 @@
-
+
+
diff --git a/example/android/app/src/profile/AndroidManifest.xml b/example/android/app/src/profile/AndroidManifest.xml
new file mode 100644
index 0000000..6ea83aa
--- /dev/null
+++ b/example/android/app/src/profile/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/example/android/build.gradle b/example/android/build.gradle
index 4476887..4256f91 100644
--- a/example/android/build.gradle
+++ b/example/android/build.gradle
@@ -1,18 +1,20 @@
buildscript {
+ ext.kotlin_version = '1.6.10'
repositories {
google()
- jcenter()
+ mavenCentral()
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.0.1'
+ classpath 'com.android.tools.build:gradle:4.1.0'
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
- jcenter()
+ mavenCentral()
}
}
diff --git a/example/android/gradle.properties b/example/android/gradle.properties
index 8bd86f6..94adc3a 100644
--- a/example/android/gradle.properties
+++ b/example/android/gradle.properties
@@ -1 +1,3 @@
org.gradle.jvmargs=-Xmx1536M
+android.useAndroidX=true
+android.enableJetifier=true
diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties
index aa901e1..bc6a58a 100644
--- a/example/android/gradle/wrapper/gradle-wrapper.properties
+++ b/example/android/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip
diff --git a/example/android/gradlew b/example/android/gradlew
old mode 100644
new mode 100755
diff --git a/example/android/gradlew.bat b/example/android/gradlew.bat
index 8a0b282..aec9973 100644
--- a/example/android/gradlew.bat
+++ b/example/android/gradlew.bat
@@ -1,90 +1,90 @@
-@if "%DEBUG%" == "" @echo off
-@rem ##########################################################################
-@rem
-@rem Gradle startup script for Windows
-@rem
-@rem ##########################################################################
-
-@rem Set local scope for the variables with windows NT shell
-if "%OS%"=="Windows_NT" setlocal
-
-@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS=
-
-set DIRNAME=%~dp0
-if "%DIRNAME%" == "" set DIRNAME=.
-set APP_BASE_NAME=%~n0
-set APP_HOME=%DIRNAME%
-
-@rem Find java.exe
-if defined JAVA_HOME goto findJavaFromJavaHome
-
-set JAVA_EXE=java.exe
-%JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto init
-
-echo.
-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:findJavaFromJavaHome
-set JAVA_HOME=%JAVA_HOME:"=%
-set JAVA_EXE=%JAVA_HOME%/bin/java.exe
-
-if exist "%JAVA_EXE%" goto init
-
-echo.
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:init
-@rem Get command-line arguments, handling Windowz variants
-
-if not "%OS%" == "Windows_NT" goto win9xME_args
-if "%@eval[2+2]" == "4" goto 4NT_args
-
-:win9xME_args
-@rem Slurp the command line arguments.
-set CMD_LINE_ARGS=
-set _SKIP=2
-
-:win9xME_args_slurp
-if "x%~1" == "x" goto execute
-
-set CMD_LINE_ARGS=%*
-goto execute
-
-:4NT_args
-@rem Get arguments from the 4NT Shell from JP Software
-set CMD_LINE_ARGS=%$
-
-:execute
-@rem Setup the command line
-
-set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
-
-@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
-
-:end
-@rem End local scope for the variables with windows NT shell
-if "%ERRORLEVEL%"=="0" goto mainEnd
-
-:fail
-rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
-rem the _cmd.exe /c_ return code!
-if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
-exit /b 1
-
-:mainEnd
-if "%OS%"=="Windows_NT" endlocal
-
-:omega
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/example/android/settings.gradle b/example/android/settings.gradle
index 5a2f14f..44e62bc 100644
--- a/example/android/settings.gradle
+++ b/example/android/settings.gradle
@@ -1,15 +1,11 @@
include ':app'
-def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
+def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
+def properties = new Properties()
-def plugins = new Properties()
-def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
-if (pluginsFile.exists()) {
- pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
-}
+assert localPropertiesFile.exists()
+localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
-plugins.each { name, path ->
- def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
- include ":$name"
- project(":$name").projectDir = pluginDirectory
-}
+def flutterSdkPath = properties.getProperty("flutter.sdk")
+assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
+apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
diff --git a/example/ios/.gitignore b/example/ios/.gitignore
index 79cc4da..e96ef60 100644
--- a/example/ios/.gitignore
+++ b/example/ios/.gitignore
@@ -1,45 +1,32 @@
-.idea/
-.vagrant/
-.sconsign.dblite
-.svn/
-
-.DS_Store
-*.swp
-profile
-
-DerivedData/
-build/
-GeneratedPluginRegistrant.h
-GeneratedPluginRegistrant.m
-
-.generated/
-
-*.pbxuser
*.mode1v3
*.mode2v3
+*.moved-aside
+*.pbxuser
*.perspectivev3
-
-!default.pbxuser
+**/*sync/
+.sconsign.dblite
+.tags*
+**/.vagrant/
+**/DerivedData/
+Icon?
+**/Pods/
+**/.symlinks/
+profile
+xcuserdata
+**/.generated/
+Flutter/App.framework
+Flutter/Flutter.framework
+Flutter/Flutter.podspec
+Flutter/Generated.xcconfig
+Flutter/app.flx
+Flutter/app.zip
+Flutter/flutter_assets/
+Flutter/flutter_export_environment.sh
+ServiceDefinitions.json
+Runner/GeneratedPluginRegistrant.*
+
+# Exceptions to above rules.
!default.mode1v3
!default.mode2v3
+!default.pbxuser
!default.perspectivev3
-
-xcuserdata
-
-*.moved-aside
-
-*.pyc
-*sync/
-Icon?
-.tags*
-
-/Flutter/app.flx
-/Flutter/app.zip
-/Flutter/flutter_assets/
-/Flutter/App.framework
-/Flutter/Flutter.framework
-/Flutter/Generated.xcconfig
-/ServiceDefinitions.json
-
-Pods/
-.symlinks/
diff --git a/example/ios/Flutter/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist
index 9367d48..f2872cf 100644
--- a/example/ios/Flutter/AppFrameworkInfo.plist
+++ b/example/ios/Flutter/AppFrameworkInfo.plist
@@ -3,7 +3,7 @@
CFBundleDevelopmentRegion
- en
+ $(DEVELOPMENT_LANGUAGE)
CFBundleExecutable
App
CFBundleIdentifier
@@ -21,6 +21,6 @@
CFBundleVersion
1.0
MinimumOSVersion
- 8.0
+ 9.0
diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj
index 623d510..2109967 100644
--- a/example/ios/Runner.xcodeproj/project.pbxproj
+++ b/example/ios/Runner.xcodeproj/project.pbxproj
@@ -3,21 +3,13 @@
archiveVersion = 1;
classes = {
};
- objectVersion = 46;
+ objectVersion = 50;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
- 2D5378261FAA1A9400D5DBA9 /* flutter_assets in Resources */ = {isa = PBXBuildFile; fileRef = 2D5378251FAA1A9400D5DBA9 /* flutter_assets */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
- 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; };
- 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
- 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; };
- 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
- 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; };
- 9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB31CF90195004384FC /* Generated.xcconfig */; };
- 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; };
- 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; };
+ 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
@@ -30,8 +22,6 @@
dstPath = "";
dstSubfolderSpec = 10;
files = (
- 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */,
- 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
@@ -41,17 +31,13 @@
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
- 2D5378251FAA1A9400D5DBA9 /* flutter_assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = flutter_assets; path = Flutter/flutter_assets; sourceTree = SOURCE_ROOT; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
- 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; };
+ 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; };
+ 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
- 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; };
- 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
- 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
- 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
@@ -63,8 +49,6 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */,
- 3B80C3941E831B6300D905FE /* App.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -74,10 +58,7 @@
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
- 2D5378251FAA1A9400D5DBA9 /* flutter_assets */,
- 3B80C3931E831B6300D905FE /* App.framework */,
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
- 9740EEBA1CF902C7004384FC /* Flutter.framework */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
@@ -91,7 +72,6 @@
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
- CF3B75C9A7D2FA2A4C99F110 /* Frameworks */,
);
sourceTree = "";
};
@@ -106,27 +86,18 @@
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
- 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */,
- 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */,
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
- 97C146F11CF9000F007C117D /* Supporting Files */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
+ 74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
+ 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
);
path = Runner;
sourceTree = "";
};
- 97C146F11CF9000F007C117D /* Supporting Files */ = {
- isa = PBXGroup;
- children = (
- 97C146F21CF9000F007C117D /* main.m */,
- );
- name = "Supporting Files";
- sourceTree = "";
- };
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@@ -156,17 +127,18 @@
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
- LastUpgradeCheck = 0910;
- ORGANIZATIONNAME = "The Chromium Authors";
+ LastUpgradeCheck = 1300;
+ ORGANIZATIONNAME = "";
TargetAttributes = {
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
+ LastSwiftMigration = 1100;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
- compatibilityVersion = "Xcode 3.2";
- developmentRegion = English;
+ compatibilityVersion = "Xcode 9.3";
+ developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
@@ -188,11 +160,8 @@
buildActionMask = 2147483647;
files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
- 9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
- 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
- 2D5378261FAA1A9400D5DBA9 /* flutter_assets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -212,7 +181,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin";
+ shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
@@ -235,8 +204,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
- 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */,
- 97C146F31CF9000F007C117D /* main.m in Sources */,
+ 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -263,9 +231,84 @@
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
+ 249021D3217E4FDB00AE95B9 /* Profile */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ SDKROOT = iphoneos;
+ SUPPORTED_PLATFORMS = iphoneos;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Profile;
+ };
+ 249021D4217E4FDB00AE95B9 /* Profile */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CLANG_ENABLE_MODULES = YES;
+ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+ ENABLE_BITCODE = NO;
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(PROJECT_DIR)/Flutter",
+ );
+ INFOPLIST_FILE = Runner/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+ LIBRARY_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(PROJECT_DIR)/Flutter",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = com.letsar.sh.example;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
+ SWIFT_VERSION = 5.0;
+ VERSIONING_SYSTEM = "apple-generic";
+ };
+ name = Profile;
+ };
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
@@ -277,12 +320,14 @@
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
@@ -309,7 +354,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 8.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@@ -319,7 +364,6 @@
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
@@ -331,12 +375,14 @@
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
@@ -357,9 +403,11 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 8.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
+ SUPPORTED_PLATFORMS = iphoneos;
+ SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
@@ -370,7 +418,8 @@
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
- CURRENT_PROJECT_VERSION = 1;
+ CLANG_ENABLE_MODULES = YES;
+ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
@@ -382,8 +431,11 @@
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
- PRODUCT_BUNDLE_IDENTIFIER = com.letsar.fsh.example.example;
+ PRODUCT_BUNDLE_IDENTIFIER = com.letsar.sh.example;
PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
@@ -393,7 +445,8 @@
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
- CURRENT_PROJECT_VERSION = 1;
+ CLANG_ENABLE_MODULES = YES;
+ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
@@ -405,8 +458,10 @@
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
- PRODUCT_BUNDLE_IDENTIFIER = com.letsar.fsh.example.example;
+ PRODUCT_BUNDLE_IDENTIFIER = com.letsar.sh.example;
PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
+ SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
@@ -419,6 +474,7 @@
buildConfigurations = (
97C147031CF9000F007C117D /* Debug */,
97C147041CF9000F007C117D /* Release */,
+ 249021D3217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
@@ -428,6 +484,7 @@
buildConfigurations = (
97C147061CF9000F007C117D /* Debug */,
97C147071CF9000F007C117D /* Release */,
+ 249021D4217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
index 1d526a1..919434a 100644
--- a/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
+++ b/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -2,6 +2,6 @@
+ location = "self:">
diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 0000000..f9b0d7c
--- /dev/null
+++ b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,8 @@
+
+
+
+
+ PreviewsEnabled
+
+
+
diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
index 1263ac8..3db53b6 100644
--- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
+++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -1,6 +1,6 @@
@@ -46,7 +45,6 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
- language = ""
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
@@ -67,7 +65,7 @@
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 0000000..f9b0d7c
--- /dev/null
+++ b/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,8 @@
+
+
+
+
+ PreviewsEnabled
+
+
+
diff --git a/example/ios/Runner/AppDelegate.h b/example/ios/Runner/AppDelegate.h
deleted file mode 100644
index 36e21bb..0000000
--- a/example/ios/Runner/AppDelegate.h
+++ /dev/null
@@ -1,6 +0,0 @@
-#import
-#import
-
-@interface AppDelegate : FlutterAppDelegate
-
-@end
diff --git a/example/ios/Runner/AppDelegate.m b/example/ios/Runner/AppDelegate.m
deleted file mode 100644
index 59a72e9..0000000
--- a/example/ios/Runner/AppDelegate.m
+++ /dev/null
@@ -1,13 +0,0 @@
-#include "AppDelegate.h"
-#include "GeneratedPluginRegistrant.h"
-
-@implementation AppDelegate
-
-- (BOOL)application:(UIApplication *)application
- didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
- [GeneratedPluginRegistrant registerWithRegistry:self];
- // Override point for customization after application launch.
- return [super application:application didFinishLaunchingWithOptions:launchOptions];
-}
-
-@end
diff --git a/example/ios/Runner/AppDelegate.swift b/example/ios/Runner/AppDelegate.swift
new file mode 100644
index 0000000..70693e4
--- /dev/null
+++ b/example/ios/Runner/AppDelegate.swift
@@ -0,0 +1,13 @@
+import UIKit
+import Flutter
+
+@UIApplicationMain
+@objc class AppDelegate: FlutterAppDelegate {
+ override func application(
+ _ application: UIApplication,
+ didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
+ ) -> Bool {
+ GeneratedPluginRegistrant.register(with: self)
+ return super.application(application, didFinishLaunchingWithOptions: launchOptions)
+ }
+}
diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
index 3d43d11..dc9ada4 100644
Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ
diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist
index 90181b7..1579fb3 100644
--- a/example/ios/Runner/Info.plist
+++ b/example/ios/Runner/Info.plist
@@ -3,7 +3,7 @@
CFBundleDevelopmentRegion
- en
+ $(DEVELOPMENT_LANGUAGE)
CFBundleExecutable
$(EXECUTABLE_NAME)
CFBundleIdentifier
@@ -15,11 +15,11 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 1.0
+ $(FLUTTER_BUILD_NAME)
CFBundleSignature
????
CFBundleVersion
- 1
+ $(FLUTTER_BUILD_NUMBER)
LSRequiresIPhoneOS
UILaunchStoryboardName
@@ -41,5 +41,7 @@
UIViewControllerBasedStatusBarAppearance
+ CADisableMinimumFrameDurationOnPhone
+
diff --git a/example/ios/Runner/Runner-Bridging-Header.h b/example/ios/Runner/Runner-Bridging-Header.h
new file mode 100644
index 0000000..308a2a5
--- /dev/null
+++ b/example/ios/Runner/Runner-Bridging-Header.h
@@ -0,0 +1 @@
+#import "GeneratedPluginRegistrant.h"
diff --git a/example/ios/Runner/main.m b/example/ios/Runner/main.m
deleted file mode 100644
index dff6597..0000000
--- a/example/ios/Runner/main.m
+++ /dev/null
@@ -1,9 +0,0 @@
-#import
-#import
-#import "AppDelegate.h"
-
-int main(int argc, char* argv[]) {
- @autoreleasepool {
- return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
- }
-}
diff --git a/example/lib/common.dart b/example/lib/common.dart
new file mode 100644
index 0000000..60c52e8
--- /dev/null
+++ b/example/lib/common.dart
@@ -0,0 +1,86 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_sticky_header/flutter_sticky_header.dart';
+
+class AppScaffold extends StatelessWidget {
+ const AppScaffold({
+ Key? key,
+ required this.title,
+ required this.slivers,
+ this.reverse = false,
+ }) : super(key: key);
+
+ final String title;
+ final List slivers;
+ final bool reverse;
+
+ @override
+ Widget build(BuildContext context) {
+ return DefaultStickyHeaderController(
+ child: Scaffold(
+ appBar: AppBar(
+ title: Text(title),
+ ),
+ body: CustomScrollView(
+ slivers: slivers,
+ reverse: reverse,
+ ),
+ floatingActionButton: const _FloatingActionButton(),
+ ),
+ );
+ }
+}
+
+class _FloatingActionButton extends StatelessWidget {
+ const _FloatingActionButton({
+ Key? key,
+ }) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ return FloatingActionButton(
+ child: Icon(Icons.adjust),
+ backgroundColor: Colors.green,
+ onPressed: () {
+ final double offset =
+ DefaultStickyHeaderController.of(context)!.stickyHeaderScrollOffset;
+ PrimaryScrollController.of(context)!.animateTo(
+ offset,
+ duration: Duration(milliseconds: 300),
+ curve: Curves.easeIn,
+ );
+ },
+ );
+ }
+}
+
+class Header extends StatelessWidget {
+ const Header({
+ Key? key,
+ this.index,
+ this.title,
+ this.color = Colors.lightBlue,
+ }) : super(key: key);
+
+ final String? title;
+ final int? index;
+ final Color color;
+
+ @override
+ Widget build(BuildContext context) {
+ return GestureDetector(
+ onTap: () {
+ print('hit $index');
+ },
+ child: Container(
+ height: 60,
+ color: color,
+ padding: EdgeInsets.symmetric(horizontal: 16.0),
+ alignment: Alignment.centerLeft,
+ child: Text(
+ title ?? 'Header #$index',
+ style: const TextStyle(color: Colors.white),
+ ),
+ ),
+ );
+ }
+}
diff --git a/example/lib/examples/animated_header.dart b/example/lib/examples/animated_header.dart
new file mode 100644
index 0000000..f624c57
--- /dev/null
+++ b/example/lib/examples/animated_header.dart
@@ -0,0 +1,72 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_sticky_header/flutter_sticky_header.dart';
+
+import '../common.dart';
+
+class AnimatedHeaderExample extends StatelessWidget {
+ const AnimatedHeaderExample({
+ Key? key,
+ }) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ return AppScaffold(
+ title: 'Animated header Example',
+ slivers: [
+ _StickyHeaderList(index: 0),
+ _StickyHeaderList(index: 1),
+ _StickyHeaderList(index: 2),
+ _StickyHeaderList(index: 3),
+ ],
+ );
+ }
+}
+
+class _StickyHeaderList extends StatelessWidget {
+ const _StickyHeaderList({
+ Key? key,
+ this.index,
+ }) : super(key: key);
+
+ final int? index;
+
+ @override
+ Widget build(BuildContext context) {
+ return SliverStickyHeader.builder(
+ builder: (context, state) => _AnimatedHeader(
+ state: state,
+ index: index,
+ ),
+ sliver: SliverList(
+ delegate: SliverChildBuilderDelegate(
+ (context, i) => ListTile(
+ leading: CircleAvatar(
+ child: Text('$index'),
+ ),
+ title: Text('List tile #$i'),
+ ),
+ childCount: 6,
+ ),
+ ),
+ );
+ }
+}
+
+class _AnimatedHeader extends StatelessWidget {
+ const _AnimatedHeader({
+ Key? key,
+ this.state,
+ this.index,
+ }) : super(key: key);
+
+ final int? index;
+ final SliverStickyHeaderState? state;
+
+ @override
+ Widget build(BuildContext context) {
+ return Header(
+ index: index,
+ color: Colors.lightBlue.withOpacity(1 - state!.scrollPercentage),
+ );
+ }
+}
diff --git a/example/lib/examples/grid.dart b/example/lib/examples/grid.dart
new file mode 100644
index 0000000..da1af75
--- /dev/null
+++ b/example/lib/examples/grid.dart
@@ -0,0 +1,63 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_sticky_header/flutter_sticky_header.dart';
+
+import '../common.dart';
+
+class GridExample extends StatelessWidget {
+ const GridExample({
+ Key? key,
+ }) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ return AppScaffold(
+ title: 'Grid Example',
+ slivers: [
+ _StickyHeaderGrid(index: 0),
+ _StickyHeaderGrid(index: 1),
+ _StickyHeaderGrid(index: 2),
+ _StickyHeaderGrid(index: 3),
+ ],
+ );
+ }
+}
+
+class _StickyHeaderGrid extends StatelessWidget {
+ const _StickyHeaderGrid({
+ Key? key,
+ this.index,
+ }) : super(key: key);
+
+ final int? index;
+
+ @override
+ Widget build(BuildContext context) {
+ return SliverStickyHeader(
+ header: Header(index: index),
+ sliver: SliverGrid(
+ gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
+ crossAxisCount: 3, crossAxisSpacing: 4.0, mainAxisSpacing: 4.0),
+ delegate: SliverChildBuilderDelegate(
+ (context, i) => GridTile(
+ child: Card(
+ child: Container(
+ color: Colors.green,
+ ),
+ ),
+ footer: Container(
+ color: Colors.white.withOpacity(0.5),
+ child: Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: Text(
+ 'Grid tile #$i',
+ style: const TextStyle(color: Colors.black),
+ ),
+ ),
+ ),
+ ),
+ childCount: 9,
+ ),
+ ),
+ );
+ }
+}
diff --git a/example/lib/examples/list.dart b/example/lib/examples/list.dart
new file mode 100644
index 0000000..f127bac
--- /dev/null
+++ b/example/lib/examples/list.dart
@@ -0,0 +1,53 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_sticky_header/flutter_sticky_header.dart';
+
+import '../common.dart';
+
+class ListExample extends StatelessWidget {
+ const ListExample({
+ Key? key,
+ }) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ return AppScaffold(
+ title: 'List Example',
+ slivers: [
+ _StickyHeaderList(index: 0),
+ _StickyHeaderList(index: 1),
+ _StickyHeaderList(index: 2),
+ _StickyHeaderList(index: 3),
+ ],
+ );
+ }
+}
+
+class _StickyHeaderList extends StatelessWidget {
+ const _StickyHeaderList({
+ Key? key,
+ this.index,
+ }) : super(key: key);
+
+ final int? index;
+
+ @override
+ Widget build(BuildContext context) {
+ return SliverStickyHeader(
+ header: Header(index: index),
+ sliver: SliverList(
+ delegate: SliverChildBuilderDelegate(
+ (context, i) => ListTile(
+ onTap: () {
+ print('tile $i');
+ },
+ leading: CircleAvatar(
+ child: Text('$index'),
+ ),
+ title: Text('List tile #$i'),
+ ),
+ childCount: 6,
+ ),
+ ),
+ );
+ }
+}
diff --git a/example/lib/examples/mix_slivers.dart b/example/lib/examples/mix_slivers.dart
new file mode 100644
index 0000000..ce4b8e3
--- /dev/null
+++ b/example/lib/examples/mix_slivers.dart
@@ -0,0 +1,62 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_sticky_header/flutter_sticky_header.dart';
+
+import '../common.dart';
+
+class MixSliversExample extends StatelessWidget {
+ const MixSliversExample({
+ Key? key,
+ }) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ return AppScaffold(
+ title: 'List Example',
+ slivers: [
+ SliverAppBar(
+ backgroundColor: Colors.orange,
+ title: Text('SliverAppBar'),
+ automaticallyImplyLeading: false,
+ pinned: true,
+ ),
+ SliverToBoxAdapter(
+ child: Container(
+ height: 50,
+ color: Colors.red,
+ ),
+ ),
+ _StickyHeaderList(index: 0),
+ _StickyHeaderList(index: 1),
+ _StickyHeaderList(index: 2),
+ _StickyHeaderList(index: 3),
+ ],
+ );
+ }
+}
+
+class _StickyHeaderList extends StatelessWidget {
+ const _StickyHeaderList({
+ Key? key,
+ this.index,
+ }) : super(key: key);
+
+ final int? index;
+
+ @override
+ Widget build(BuildContext context) {
+ return SliverStickyHeader(
+ header: Header(index: index),
+ sliver: SliverList(
+ delegate: SliverChildBuilderDelegate(
+ (context, i) => ListTile(
+ leading: CircleAvatar(
+ child: Text('$index'),
+ ),
+ title: Text('List tile #$i'),
+ ),
+ childCount: 6,
+ ),
+ ),
+ );
+ }
+}
diff --git a/example/lib/examples/nested.dart b/example/lib/examples/nested.dart
new file mode 100644
index 0000000..4affc58
--- /dev/null
+++ b/example/lib/examples/nested.dart
@@ -0,0 +1,124 @@
+import 'package:example/common.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_sticky_header/flutter_sticky_header.dart';
+import 'package:sliver_tools/sliver_tools.dart';
+
+class NestedExample extends StatelessWidget {
+ const NestedExample({
+ super.key,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ return AppScaffold(
+ title: 'Nested Sticky Headers',
+ slivers: [
+ SliverStickyHeader(
+ header: Header(title: '1'),
+ sliver: MultiSliver(
+ children: [
+ SliverStickyHeader(
+ header: Header(title: '1.1'),
+ sliver: _SliverLeaf(),
+ ),
+ SliverStickyHeader(
+ header: Header(title: '1.2'),
+ sliver: MultiSliver(
+ children: [
+ SliverStickyHeader(
+ header: Header(title: '1.2.1'),
+ sliver: _SliverLeaf(),
+ ),
+ SliverStickyHeader(
+ header: Header(title: '1.2.2'),
+ sliver: _SliverLeaf(),
+ ),
+ SliverStickyHeader(
+ header: Header(title: '1.2.3'),
+ sliver: _SliverLeaf(),
+ ),
+ ],
+ ),
+ ),
+ SliverStickyHeader(
+ header: Header(title: '1.3'),
+ sliver: _SliverLeaf(),
+ ),
+ ],
+ ),
+ ),
+ SliverStickyHeader(
+ header: Header(title: '2'),
+ sliver: MultiSliver(
+ children: [
+ SliverStickyHeader(
+ header: Header(title: '2.1'),
+ sliver: _SliverLeaf(),
+ ),
+ SliverStickyHeader(
+ header: Header(title: '2.2'),
+ sliver: MultiSliver(
+ children: [
+ SliverStickyHeader(
+ header: Header(title: '2.2.1'),
+ sliver: _SliverLeaf(),
+ ),
+ SliverStickyHeader(
+ header: Header(title: '2.2.2'),
+ sliver: _SliverLeaf(),
+ ),
+ SliverStickyHeader(
+ header: Header(title: '2.2.3'),
+ sliver: _SliverLeaf(),
+ ),
+ ],
+ ),
+ ),
+ SliverStickyHeader(
+ header: Header(title: '2.3'),
+ sliver: _SliverLeaf(),
+ ),
+ ],
+ ),
+ ),
+ SliverStickyHeader(
+ header: Header(title: '3'),
+ sliver: _SliverLeaf(),
+ ),
+ SliverStickyHeader(
+ header: Header(title: '4'),
+ sliver: MultiSliver(
+ children: [
+ SliverStickyHeader(
+ header: Header(title: '4.1'),
+ sliver: _SliverLeaf(),
+ ),
+ SliverStickyHeader(
+ header: Header(title: '4.2'),
+ sliver: _SliverLeaf(),
+ ),
+ SliverStickyHeader(
+ header: Header(title: '4.3'),
+ sliver: _SliverLeaf(),
+ ),
+ ],
+ ),
+ ),
+ ],
+ );
+ }
+}
+
+class _SliverLeaf extends StatelessWidget {
+ const _SliverLeaf();
+
+ @override
+ Widget build(BuildContext context) {
+ return SliverToBoxAdapter(
+ child: Container(
+ height: 200,
+ color: Colors.amber,
+ ),
+ );
+ }
+}
diff --git a/example/lib/examples/not_sticky.dart b/example/lib/examples/not_sticky.dart
new file mode 100644
index 0000000..3ad8405
--- /dev/null
+++ b/example/lib/examples/not_sticky.dart
@@ -0,0 +1,54 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_sticky_header/flutter_sticky_header.dart';
+
+import '../common.dart';
+
+class NotStickyExample extends StatelessWidget {
+ const NotStickyExample({
+ Key? key,
+ }) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ return AppScaffold(
+ title: 'Not Sticky Example',
+ slivers: [
+ _NotStickyList(index: 0),
+ _NotStickyList(index: 1),
+ _NotStickyList(index: 2),
+ _NotStickyList(index: 3),
+ ],
+ );
+ }
+}
+
+class _NotStickyList extends StatelessWidget {
+ const _NotStickyList({
+ Key? key,
+ this.index,
+ }) : super(key: key);
+
+ final int? index;
+
+ @override
+ Widget build(BuildContext context) {
+ return SliverStickyHeader(
+ header: Header(index: index),
+ sticky: false,
+ sliver: SliverList(
+ delegate: SliverChildBuilderDelegate(
+ (context, i) => ListTile(
+ onTap: () {
+ print('tile $i');
+ },
+ leading: CircleAvatar(
+ child: Text('$index'),
+ ),
+ title: Text('List tile #$i'),
+ ),
+ childCount: 6,
+ ),
+ ),
+ );
+ }
+}
diff --git a/example/lib/examples/reverse.dart b/example/lib/examples/reverse.dart
new file mode 100644
index 0000000..17fea56
--- /dev/null
+++ b/example/lib/examples/reverse.dart
@@ -0,0 +1,51 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_sticky_header/flutter_sticky_header.dart';
+
+import '../common.dart';
+
+class ReverseExample extends StatelessWidget {
+ const ReverseExample({
+ Key? key,
+ }) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ return AppScaffold(
+ reverse: true,
+ title: 'Reverse Example',
+ slivers: [
+ _StickyHeaderList(index: 0),
+ _StickyHeaderList(index: 1),
+ _StickyHeaderList(index: 2),
+ _StickyHeaderList(index: 3),
+ ],
+ );
+ }
+}
+
+class _StickyHeaderList extends StatelessWidget {
+ const _StickyHeaderList({
+ Key? key,
+ this.index,
+ }) : super(key: key);
+
+ final int? index;
+
+ @override
+ Widget build(BuildContext context) {
+ return SliverStickyHeader(
+ header: Header(index: index),
+ sliver: SliverList(
+ delegate: SliverChildBuilderDelegate(
+ (context, i) => ListTile(
+ leading: CircleAvatar(
+ child: Text('$index'),
+ ),
+ title: Text('List tile #$i'),
+ ),
+ childCount: 6,
+ ),
+ ),
+ );
+ }
+}
diff --git a/example/lib/examples/reverse2.dart b/example/lib/examples/reverse2.dart
new file mode 100644
index 0000000..ee0f73e
--- /dev/null
+++ b/example/lib/examples/reverse2.dart
@@ -0,0 +1,52 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_sticky_header/flutter_sticky_header.dart';
+
+import '../common.dart';
+
+class ReverseExample2 extends StatelessWidget {
+ const ReverseExample2({
+ Key? key,
+ }) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ return AppScaffold(
+ reverse: true,
+ title: 'Reverse Example',
+ slivers: [
+ _StickyHeaderList(index: 0),
+ _StickyHeaderList(index: 1),
+ _StickyHeaderList(index: 2),
+ _StickyHeaderList(index: 3),
+ ],
+ );
+ }
+}
+
+class _StickyHeaderList extends StatelessWidget {
+ const _StickyHeaderList({
+ Key? key,
+ this.index,
+ }) : super(key: key);
+
+ final int? index;
+
+ @override
+ Widget build(BuildContext context) {
+ return SliverStickyHeader(
+ header: Header(index: index),
+ reverse: true,
+ sliver: SliverList(
+ delegate: SliverChildBuilderDelegate(
+ (context, i) => ListTile(
+ leading: CircleAvatar(
+ child: Text('$index'),
+ ),
+ title: Text('List tile #$i'),
+ ),
+ childCount: 6,
+ ),
+ ),
+ );
+ }
+}
diff --git a/example/lib/examples/side_header.dart b/example/lib/examples/side_header.dart
new file mode 100644
index 0000000..a7602da
--- /dev/null
+++ b/example/lib/examples/side_header.dart
@@ -0,0 +1,95 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_sticky_header/flutter_sticky_header.dart';
+
+import '../common.dart';
+
+class SideHeaderExample extends StatelessWidget {
+ const SideHeaderExample({
+ Key? key,
+ }) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ return AppScaffold(
+ title: 'Side Header Example',
+ slivers: [
+ _StickyHeaderGrid(index: 0),
+ _StickyHeaderGrid(index: 1),
+ _StickyHeaderGrid(index: 2),
+ _StickyHeaderGrid(index: 3),
+ ],
+ );
+ }
+}
+
+class _StickyHeaderGrid extends StatelessWidget {
+ const _StickyHeaderGrid({
+ Key? key,
+ this.index,
+ }) : super(key: key);
+
+ final int? index;
+
+ @override
+ Widget build(BuildContext context) {
+ return SliverStickyHeader(
+ overlapsContent: true,
+ header: _SideHeader(index: index),
+ sliver: SliverPadding(
+ padding: const EdgeInsets.only(left: 60),
+ sliver: SliverGrid(
+ gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
+ crossAxisCount: 3, crossAxisSpacing: 4.0, mainAxisSpacing: 4.0),
+ delegate: SliverChildBuilderDelegate(
+ (context, i) => GridTile(
+ child: Card(
+ child: Container(
+ color: Colors.green,
+ ),
+ ),
+ footer: Container(
+ color: Colors.white.withOpacity(0.5),
+ child: Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: Text(
+ 'Grid tile #$i',
+ style: const TextStyle(color: Colors.black),
+ ),
+ ),
+ ),
+ ),
+ childCount: 9,
+ ),
+ ),
+ ),
+ );
+ }
+}
+
+class _SideHeader extends StatelessWidget {
+ const _SideHeader({
+ Key? key,
+ this.index,
+ }) : super(key: key);
+
+ final int? index;
+
+ @override
+ Widget build(BuildContext context) {
+ return Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0),
+ child: Align(
+ alignment: Alignment.centerLeft,
+ child: SizedBox(
+ height: 44.0,
+ width: 44.0,
+ child: CircleAvatar(
+ backgroundColor: Colors.orangeAccent,
+ foregroundColor: Colors.white,
+ child: Text('$index'),
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/example/lib/main.dart b/example/lib/main.dart
index dc06166..bc624db 100644
--- a/example/lib/main.dart
+++ b/example/lib/main.dart
@@ -1,310 +1,115 @@
+import 'package:example/examples/nested.dart';
import 'package:flutter/material.dart';
-import 'package:flutter_sticky_header/flutter_sticky_header.dart';
-void main() => runApp(new MyApp());
+import 'examples/animated_header.dart';
+import 'examples/grid.dart';
+import 'examples/list.dart';
+import 'examples/mix_slivers.dart';
+import 'examples/not_sticky.dart';
+import 'examples/reverse.dart';
+import 'examples/reverse2.dart';
+import 'examples/side_header.dart';
+
+void main() {
+ // debugPaintPointersEnabled = true;
+ runApp(const App());
+}
+
+class App extends StatelessWidget {
+ const App({
+ Key? key,
+ }) : super(key: key);
-class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
- return new MaterialApp(
- title: 'Sticky Header example',
- theme: new ThemeData(
- primarySwatch: Colors.blue,
- ),
- home: new MainScreen(),
+ return MaterialApp(
+ title: 'Flutter Sticky Headers',
+ home: const _Home(),
);
}
}
-class MainScreen extends StatelessWidget {
+class _Home extends StatelessWidget {
+ const _Home({
+ Key? key,
+ }) : super(key: key);
+
@override
Widget build(BuildContext context) {
- return new SimpleScaffold(
- title: 'Flutter Sticky Header example',
- child: new Builder(builder: (BuildContext context) {
- return new CustomScrollView(
- slivers: _buildSlivers(context),
- );
- }),
- );
- }
-
- List _buildSlivers(BuildContext context) {
- List slivers = new List();
-
- //slivers.add(_buildExample());
- //slivers.add(_buildBuilderExample());
- int i = 0;
- slivers.add(SliverAppBar(
- backgroundColor: Colors.blue.withOpacity(0.5),
- title: Text('text'),
- pinned: true,
- ));
- slivers.add(SliverAppBar(
- backgroundColor: Colors.yellow.withOpacity(0.5),
- title: Text('text'),
- pinned: true,
- ));
- slivers.addAll(_buildHeaderBuilderLists(context, i, i += 5));
- slivers.addAll(_buildLists(context, i, i += 3));
- slivers.addAll(_buildGrids(context, i, i += 3));
- slivers.addAll(_buildSideHeaderGrids(context, i, i += 3));
- slivers.addAll(_buildHeaderBuilderLists(context, i, i += 5));
- return slivers;
- }
-
- List _buildLists(BuildContext context, int firstIndex, int count) {
- return List.generate(count, (sliverIndex) {
- sliverIndex += firstIndex;
- return new SliverStickyHeader(
- header: _buildHeader(sliverIndex),
- sliver: new SliverList(
- delegate: new SliverChildBuilderDelegate(
- (context, i) => new ListTile(
- leading: new CircleAvatar(
- child: new Text('$sliverIndex'),
- ),
- title: new Text('List tile #$i'),
- ),
- childCount: 4,
+ return Scaffold(
+ appBar: AppBar(
+ title: Text('Flutter Sticky Headers'),
+ ),
+ body: ListView(
+ children: [
+ _Item(
+ text: 'List Example',
+ builder: (_) => const ListExample(),
),
- ),
- );
- });
- }
-
- List _buildHeaderBuilderLists(
- BuildContext context, int firstIndex, int count) {
- return List.generate(count, (sliverIndex) {
- sliverIndex += firstIndex;
- return new SliverStickyHeaderBuilder(
- builder: (context, state) =>
- _buildAnimatedHeader(context, sliverIndex, state),
- sliver: new SliverList(
- delegate: new SliverChildBuilderDelegate(
- (context, i) => new ListTile(
- leading: new CircleAvatar(
- child: new Text('$sliverIndex'),
- ),
- title: new Text('List tile #$i'),
- ),
- childCount: 4,
+ _Item(
+ text: 'Grid Example',
+ builder: (_) => const GridExample(),
),
- ),
- );
- });
- }
-
- List _buildGrids(BuildContext context, int firstIndex, int count) {
- return List.generate(count, (sliverIndex) {
- sliverIndex += firstIndex;
- return new SliverStickyHeader(
- header: _buildHeader(sliverIndex),
- sliver: new SliverGrid(
- gridDelegate: new SliverGridDelegateWithFixedCrossAxisCount(
- crossAxisCount: 3, crossAxisSpacing: 4.0, mainAxisSpacing: 4.0),
- delegate: new SliverChildBuilderDelegate(
- (context, i) => GestureDetector(
- onTap: () => Scaffold.of(context).showSnackBar(
- new SnackBar(content: Text('Grid tile #$i'))),
- child: new GridTile(
- child: Card(
- child: new Container(
- color: Colors.green,
- ),
- ),
- footer: new Container(
- color: Colors.white.withOpacity(0.5),
- child: Padding(
- padding: const EdgeInsets.all(8.0),
- child: new Text(
- 'Grid tile #$i',
- style: const TextStyle(color: Colors.black),
- ),
- ),
- ),
- ),
- ),
- childCount: 9,
+ _Item(
+ text: 'Not Sticky Example',
+ builder: (_) => const NotStickyExample(),
),
- ),
- );
- });
- }
-
- List _buildSideHeaderGrids(
- BuildContext context, int firstIndex, int count) {
- return List.generate(count, (sliverIndex) {
- sliverIndex += firstIndex;
- return new SliverStickyHeader(
- overlapsContent: true,
- header: _buildSideHeader(context, sliverIndex),
- sliver: new SliverPadding(
- padding: new EdgeInsets.only(left: 60.0),
- sliver: new SliverGrid(
- gridDelegate: new SliverGridDelegateWithFixedCrossAxisCount(
- crossAxisCount: 3,
- crossAxisSpacing: 4.0,
- mainAxisSpacing: 4.0,
- childAspectRatio: 1.0),
- delegate: new SliverChildBuilderDelegate(
- (context, i) => GestureDetector(
- onTap: () => Scaffold.of(context).showSnackBar(
- new SnackBar(content: Text('Grid tile #$i'))),
- child: new GridTile(
- child: Card(
- child: new Container(
- color: Colors.orange,
- ),
- ),
- footer: new Container(
- color: Colors.white.withOpacity(0.5),
- child: Padding(
- padding: const EdgeInsets.all(8.0),
- child: new Text(
- 'Grid tile #$i',
- style: const TextStyle(color: Colors.black),
- ),
- ),
- ),
- ),
- ),
- childCount: 12,
- ),
+ _Item(
+ text: 'Side Header Example',
+ builder: (_) => const SideHeaderExample(),
),
- ),
- );
- });
- }
-
- Widget _buildHeader(int index, {String text}) {
- return new Container(
- height: 60.0,
- color: Colors.lightBlue,
- padding: EdgeInsets.symmetric(horizontal: 16.0),
- alignment: Alignment.centerLeft,
- child: new Text(
- text ?? 'Header #$index',
- style: const TextStyle(color: Colors.white),
- ),
- );
- }
-
- Widget _buildSideHeader(BuildContext context, int index, {String text}) {
- return Padding(
- padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0),
- child: Align(
- alignment: Alignment.centerLeft,
- child: new SizedBox(
- height: 44.0,
- width: 44.0,
- child: GestureDetector(
- onTap: () => Scaffold
- .of(context)
- .showSnackBar(new SnackBar(content: Text('$index'))),
- child: new CircleAvatar(
- backgroundColor: Colors.orangeAccent,
- foregroundColor: Colors.white,
- child: new Text('$index'),
- ),
+ _Item(
+ text: 'Animated Header Example',
+ builder: (_) => const AnimatedHeaderExample(),
),
- ),
- ),
- );
- }
-
- Widget _buildAnimatedHeader(
- BuildContext context, int index, SliverStickyHeaderState state) {
- return GestureDetector(
- onTap: () => Scaffold
- .of(context)
- .showSnackBar(new SnackBar(content: Text('$index'))),
- child: new Container(
- height: 60.0,
- color: (state.isPinned ? Colors.pink : Colors.lightBlue)
- .withOpacity(1.0 - state.scrollPercentage),
- padding: EdgeInsets.symmetric(horizontal: 16.0),
- alignment: Alignment.centerLeft,
- child: new Text(
- 'Header #$index',
- style: const TextStyle(color: Colors.white),
- ),
- ),
- );
- }
-
- Widget _buildExample() {
- return new SliverStickyHeader(
- header: new Container(
- height: 60.0,
- color: Colors.lightBlue,
- padding: EdgeInsets.symmetric(horizontal: 16.0),
- alignment: Alignment.centerLeft,
- child: new Text(
- 'Header #0',
- style: const TextStyle(color: Colors.white),
- ),
- ),
- sliver: new SliverList(
- delegate: new SliverChildBuilderDelegate(
- (context, i) => new ListTile(
- leading: new CircleAvatar(
- child: new Text('0'),
- ),
- title: new Text('List tile #$i'),
- ),
- childCount: 4,
- ),
- ),
- );
- }
-
- Widget _buildBuilderExample() {
- return new SliverStickyHeaderBuilder(
- builder: (context, state) => new Container(
- height: 60.0,
- color: (state.isPinned ? Colors.pink : Colors.lightBlue)
- .withOpacity(1.0 - state.scrollPercentage),
- padding: EdgeInsets.symmetric(horizontal: 16.0),
- alignment: Alignment.centerLeft,
- child: new Text(
- 'Header #1',
- style: const TextStyle(color: Colors.white),
- ),
+ _Item(
+ text: 'Reverse List Example',
+ builder: (_) => const ReverseExample(),
),
- sliver: new SliverList(
- delegate: new SliverChildBuilderDelegate(
- (context, i) => new ListTile(
- leading: new CircleAvatar(
- child: new Text('0'),
- ),
- title: new Text('List tile #$i'),
- ),
- childCount: 4,
- ),
+ _Item(
+ text: 'Reverse List Example 2',
+ builder: (_) => const ReverseExample2(),
+ ),
+ _Item(
+ text: 'Mixing other slivers',
+ builder: (_) => const MixSliversExample(),
+ ),
+ _Item(
+ text: 'Nested sticky headers',
+ builder: (_) => const NestedExample(),
+ ),
+ ],
),
);
}
}
-class SimpleScaffold extends StatelessWidget {
- const SimpleScaffold({
- Key key,
- this.title,
- this.child,
+class _Item extends StatelessWidget {
+ const _Item({
+ Key? key,
+ required this.text,
+ required this.builder,
}) : super(key: key);
- final String title;
-
- final Widget child;
+ final String text;
+ final WidgetBuilder builder;
@override
Widget build(BuildContext context) {
- return new Scaffold(
- appBar: new AppBar(
- title: new Text(title),
+ return Card(
+ color: Colors.blue,
+ child: InkWell(
+ onTap: () =>
+ Navigator.push(context, MaterialPageRoute(builder: builder)),
+ child: Container(
+ padding: EdgeInsets.all(16),
+ child: Text(
+ text,
+ style: TextStyle(
+ color: Colors.white, fontWeight: FontWeight.bold, fontSize: 20),
+ ),
+ ),
),
- body: child,
);
}
}
diff --git a/example/lib/main_issue_75.dart b/example/lib/main_issue_75.dart
new file mode 100644
index 0000000..a30f1cd
--- /dev/null
+++ b/example/lib/main_issue_75.dart
@@ -0,0 +1,144 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_sticky_header/flutter_sticky_header.dart';
+
+void main() {
+ runApp(MaterialApp(home: TabHeaderExample()));
+}
+
+class TabHeaderExample extends StatefulWidget {
+ const TabHeaderExample({Key? key}) : super(key: key);
+
+ @override
+ State createState() => _TabHeaderExampleState();
+}
+
+class _TabHeaderExampleState extends State {
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ title: Text('Tab List Example'),
+ ),
+ body: CustomScrollView(
+ slivers: [
+ _StickyHeaderList(index: 0),
+ _StickyHeaderList(index: 1),
+ _StickyHeaderList(index: 2),
+ _StickyHeaderList(index: 3),
+ ],
+ ),
+ );
+ }
+}
+
+class _StickyHeaderList extends StatefulWidget {
+ final int index;
+ const _StickyHeaderList({Key? key, required this.index}) : super(key: key);
+
+ @override
+ State<_StickyHeaderList> createState() => _StickyHeaderListState();
+}
+
+class _StickyHeaderListState extends State<_StickyHeaderList>
+ with TickerProviderStateMixin {
+ late TabController _tabController;
+
+ @override
+ void initState() {
+ super.initState();
+ _tabController = TabController(length: 10, vsync: this);
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return SliverStickyHeader(
+ header: Container(
+ color: Colors.white,
+ height: 47,
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Expanded(
+ child: TabBar(
+ isScrollable: true,
+ controller: _tabController,
+ indicatorColor: const Color(0xFF216DDF),
+ indicatorSize: TabBarIndicatorSize.label,
+ indicatorWeight: 2,
+ indicatorPadding: EdgeInsets.only(bottom: 10),
+ unselectedLabelColor: const Color(0xFF888888),
+ unselectedLabelStyle: TextStyle(fontSize: 17),
+ labelColor: const Color(0xFF216DDF),
+ labelStyle:
+ TextStyle(fontSize: 17, fontWeight: FontWeight.bold),
+ tabs: _createTabs(),
+ onTap: (index) {
+ print(index);
+ },
+ ),
+ ),
+ InkWell(
+ child: Container(
+ decoration: BoxDecoration(
+ color: Colors.red,
+ boxShadow: [
+ BoxShadow(
+ color: Colors.white,
+ blurRadius: 8,
+ offset: Offset(-20, 0),
+ ),
+ ],
+ ),
+ padding: EdgeInsets.only(left: 5, right: 12),
+ child: Row(
+ children: [
+ Text(
+ "全部",
+ style: TextStyle(
+ color: const Color(0xFF101010), fontSize: 17),
+ ),
+ ],
+ ),
+ ),
+ onTap: () {
+ // Can not response
+ print("1234567");
+ },
+ ),
+ ],
+ ),
+ ),
+ sliver: SliverList(
+ delegate: SliverChildBuilderDelegate(
+ (context, i) => ListTile(
+ onTap: () {
+ print('tile $i');
+ },
+ leading: CircleAvatar(
+ child: Text('${widget.index}'),
+ ),
+ title: Text('List tile #$i'),
+ ),
+ childCount: 6,
+ ),
+ ),
+ );
+ }
+
+ /// 创建Tabs
+ List _createTabs() {
+ return [
+ "我是tab",
+ "我是tab",
+ "我是tab",
+ "我是tab",
+ "我是tab",
+ "我是tab",
+ "我是tab",
+ "我是tab",
+ "我是tab",
+ "我是tab"
+ ].map((e) => Tab(text: e)).toList();
+ }
+}
diff --git a/example/pubspec.yaml b/example/pubspec.yaml
index b2da0ad..903f267 100644
--- a/example/pubspec.yaml
+++ b/example/pubspec.yaml
@@ -1,24 +1,43 @@
name: example
description: A new Flutter project.
+# The following line prevents the package from being accidentally published to
+# pub.dev using `pub publish`. This is preferred for private packages.
+publish_to: 'none' # Remove this line if you wish to publish to pub.dev
+
+# The following defines the version and build number for your application.
+# A version number is three numbers separated by dots, like 1.2.43
+# followed by an optional build number separated by a +.
+# Both the version and the builder number may be overridden in flutter
+# build by specifying --build-name and --build-number, respectively.
+# In Android, build-name is used as versionName while build-number used as versionCode.
+# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
+# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
+# Read more about iOS versioning at
+# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
+version: 1.0.0+1
+
+environment:
+ sdk: '>=2.17.0 <3.0.0'
+
dependencies:
flutter:
sdk: flutter
+ flutter_sticky_header:
+ path: ../
+
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
- cupertino_icons: ^0.1.2
-
- flutter_sticky_header:
- path: ../
+ cupertino_icons: ^1.0.2
+ sliver_tools: ^0.2.7
dev_dependencies:
flutter_test:
sdk: flutter
-
# For information on the generic Dart part of this file, see the
-# following page: https://www.dartlang.org/tools/pub/pubspec
+# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter.
flutter:
@@ -30,14 +49,14 @@ flutter:
# To add assets to your application, add an assets section, like this:
# assets:
- # - images/a_dot_burr.jpeg
- # - images/a_dot_ham.jpeg
+ # - images/a_dot_burr.jpeg
+ # - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
- # https://flutter.io/assets-and-images/#resolution-aware.
+ # https://flutter.dev/assets-and-images/#resolution-aware.
# For details regarding adding assets from package dependencies, see
- # https://flutter.io/assets-and-images/#from-packages
+ # https://flutter.dev/assets-and-images/#from-packages
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
@@ -57,4 +76,4 @@ flutter:
# weight: 700
#
# For details regarding fonts from package dependencies,
- # see https://flutter.io/custom-fonts/#from-packages
+ # see https://flutter.dev/custom-fonts/#from-packages
diff --git a/lib/src/rendering/sliver_sticky_header.dart b/lib/src/rendering/sliver_sticky_header.dart
index e7d871c..3ff58e4 100644
--- a/lib/src/rendering/sliver_sticky_header.dart
+++ b/lib/src/rendering/sliver_sticky_header.dart
@@ -1,10 +1,10 @@
import 'dart:math' as math;
-import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
-import 'package:flutter_sticky_header/flutter_sticky_header.dart';
-import 'package:flutter_sticky_header/src/rendering/sticky_header_constraints.dart';
-import 'package:flutter_sticky_header/src/rendering/sticky_header_layout_builder.dart';
+// import 'package:flutter_sticky_header/flutter_sticky_header.dart';
+import '../../flutter_sticky_header.dart';
+import 'package:value_layout_builder/value_layout_builder.dart';
/// A sliver with a [RenderBox] as header and a [RenderSliver] as child.
///
@@ -12,104 +12,149 @@ import 'package:flutter_sticky_header/src/rendering/sticky_header_layout_builder
/// the [child] scrolls off the viewport.
class RenderSliverStickyHeader extends RenderSliver with RenderSliverHelpers {
RenderSliverStickyHeader({
- RenderObject header,
- RenderSliver child,
- overlapsContent: false,
- }) : assert(overlapsContent != null),
- _overlapsContent = overlapsContent {
- this.header = header;
+ RenderObject? header,
+ RenderSliver? child,
+ bool overlapsContent: false,
+ bool reverse: false,
+ bool sticky: true,
+ StickyHeaderController? controller,
+ }) : _overlapsContent = overlapsContent,
+ _sticky = sticky,
+ _controller = controller,
+ _reverse = reverse
+ {
+ this.header = header as RenderBox?;
this.child = child;
}
- SliverStickyHeaderState _oldState;
- double _headerExtent;
- bool _isPinned;
+ SliverStickyHeaderState? _oldState;
+ double? _headerExtent;
+ late bool _isPinned;
bool get overlapsContent => _overlapsContent;
bool _overlapsContent;
+
set overlapsContent(bool value) {
- assert(value != null);
if (_overlapsContent == value) return;
_overlapsContent = value;
markNeedsLayout();
}
+ bool get reverse => _reverse;
+ bool _reverse;
+
+ set reverse(bool value) {
+ assert(value != null);
+ if (_reverse == value) return;
+ _reverse = value;
+ markNeedsLayout();
+ }
+
+ bool get sticky => _sticky;
+ bool _sticky;
+
+ set sticky(bool value) {
+ if (_sticky == value) return;
+ _sticky = value;
+ markNeedsLayout();
+ }
+
+ StickyHeaderController? get controller => _controller;
+ StickyHeaderController? _controller;
+
+ set controller(StickyHeaderController? value) {
+ if (_controller == value) return;
+ if (_controller != null && value != null) {
+ // We copy the state of the old controller.
+ value.stickyHeaderScrollOffset = _controller!.stickyHeaderScrollOffset;
+ }
+ _controller = value;
+ }
+
/// The render object's header
- RenderBox get header => _header;
- RenderBox _header;
- set header(RenderBox value) {
- if (_header != null) dropChild(_header);
+ RenderBox? get header => _header;
+ RenderBox? _header;
+
+ set header(RenderBox? value) {
+ if (_header != null) dropChild(_header!);
_header = value;
- if (_header != null) adoptChild(_header);
+ if (_header != null) adoptChild(_header!);
}
/// The render object's unique child
- RenderSliver get child => _child;
- RenderSliver _child;
- set child(RenderSliver value) {
- if (_child != null) dropChild(_child);
+ RenderSliver? get child => _child;
+ RenderSliver? _child;
+
+ set child(RenderSliver? value) {
+ if (_child != null) dropChild(_child!);
_child = value;
- if (_child != null) adoptChild(_child);
+ if (_child != null) adoptChild(_child!);
}
@override
void setupParentData(RenderObject child) {
if (child.parentData is! SliverPhysicalParentData)
- child.parentData = new SliverPhysicalParentData();
+ child.parentData = SliverPhysicalParentData();
}
@override
void attach(PipelineOwner owner) {
super.attach(owner);
- if (_header != null) _header.attach(owner);
- if (_child != null) _child.attach(owner);
+ if (_header != null) _header!.attach(owner);
+ if (_child != null) _child!.attach(owner);
}
@override
void detach() {
super.detach();
- if (_header != null) _header.detach();
- if (_child != null) _child.detach();
+ if (_header != null) _header!.detach();
+ if (_child != null) _child!.detach();
}
@override
void redepthChildren() {
- if (_header != null) redepthChild(_header);
- if (_child != null) redepthChild(_child);
+ if (_header != null) redepthChild(_header!);
+ if (_child != null) redepthChild(_child!);
}
@override
void visitChildren(RenderObjectVisitor visitor) {
- if (_header != null) visitor(_header);
- if (_child != null) visitor(_child);
+ if (_header != null) visitor(_header!);
+ if (_child != null) visitor(_child!);
}
@override
List debugDescribeChildren() {
List result = [];
if (header != null) {
- result.add(header.toDiagnosticsNode(name: 'header'));
+ result.add(header!.toDiagnosticsNode(name: 'header'));
}
if (child != null) {
- result.add(child.toDiagnosticsNode(name: 'child'));
+ result.add(child!.toDiagnosticsNode(name: 'child'));
}
return result;
}
double computeHeaderExtent() {
if (header == null) return 0.0;
- assert(header.hasSize);
- assert(constraints.axis != null);
+ assert(header!.hasSize);
switch (constraints.axis) {
case Axis.vertical:
- return header.size.height;
+ return header!.size.height;
case Axis.horizontal:
- return header.size.width;
+ return header!.size.width;
}
- return null;
}
- double get headerLogicalExtent => overlapsContent ? 0.0 : _headerExtent;
+ double? get headerLogicalExtent => overlapsContent ? 0.0 : _headerExtent;
+
+ double get headerPosition => sticky
+ ? math.min(
+ constraints.overlap,
+ (child?.geometry?.scrollExtent ?? 0.0) -
+ constraints.scrollOffset -
+ (overlapsContent ? _headerExtent! : 0.0))
+ : -constraints.scrollOffset;
@override
void performLayout() {
@@ -118,15 +163,14 @@ class RenderSliverStickyHeader extends RenderSliver with RenderSliverHelpers {
return;
}
- // One of them is not null.
AxisDirection axisDirection = applyGrowthDirectionToAxisDirection(
constraints.axisDirection, constraints.growthDirection);
if (header != null) {
- header.layout(
- new StickyHeaderConstraints(
- state: _oldState ?? new SliverStickyHeaderState(0.0, false),
- boxConstraints: constraints.asBoxConstraints(),
+ header!.layout(
+ BoxValueConstraints(
+ value: _oldState ?? SliverStickyHeaderState(0.0, false),
+ constraints: constraints.asBoxConstraints(),
),
parentUsesSize: true,
);
@@ -134,14 +178,14 @@ class RenderSliverStickyHeader extends RenderSliver with RenderSliverHelpers {
}
// Compute the header extent only one time.
- double headerExtent = headerLogicalExtent;
+ double headerExtent = headerLogicalExtent!;
final double headerPaintExtent =
calculatePaintOffset(constraints, from: 0.0, to: headerExtent);
final double headerCacheExtent =
calculateCacheOffset(constraints, from: 0.0, to: headerExtent);
if (child == null) {
- geometry = new SliverGeometry(
+ geometry = SliverGeometry(
scrollExtent: headerExtent,
maxPaintExtent: headerExtent,
paintExtent: headerPaintExtent,
@@ -150,11 +194,11 @@ class RenderSliverStickyHeader extends RenderSliver with RenderSliverHelpers {
hasVisualOverflow: headerExtent > constraints.remainingPaintExtent ||
constraints.scrollOffset > 0.0);
} else {
- child.layout(
+ child!.layout(
constraints.copyWith(
scrollOffset: math.max(0.0, constraints.scrollOffset - headerExtent),
cacheOrigin: math.min(0.0, constraints.cacheOrigin + headerExtent),
- overlap: 0.0,
+ overlap: math.min(headerExtent, constraints.scrollOffset),
remainingPaintExtent:
constraints.remainingPaintExtent - headerPaintExtent,
remainingCacheExtent:
@@ -162,22 +206,24 @@ class RenderSliverStickyHeader extends RenderSliver with RenderSliverHelpers {
),
parentUsesSize: true,
);
- final SliverGeometry childLayoutGeometry = child.geometry;
+ final SliverGeometry childLayoutGeometry = child!.geometry!;
if (childLayoutGeometry.scrollOffsetCorrection != null) {
- geometry = new SliverGeometry(
+ geometry = SliverGeometry(
scrollOffsetCorrection: childLayoutGeometry.scrollOffsetCorrection,
);
return;
}
final double paintExtent = math.min(
- headerPaintExtent +
- math.max(childLayoutGeometry.paintExtent,
- childLayoutGeometry.layoutExtent),
- constraints.remainingPaintExtent);
+ headerPaintExtent +
+ math.max(childLayoutGeometry.paintExtent,
+ childLayoutGeometry.layoutExtent),
+ constraints.remainingPaintExtent,
+ );
- geometry = new SliverGeometry(
+ geometry = SliverGeometry(
scrollExtent: headerExtent + childLayoutGeometry.scrollExtent,
+ maxScrollObstructionExtent: sticky ? headerPaintExtent : 0,
paintExtent: paintExtent,
layoutExtent: math.min(
headerPaintExtent + childLayoutGeometry.layoutExtent, paintExtent),
@@ -191,54 +237,66 @@ class RenderSliverStickyHeader extends RenderSliver with RenderSliverHelpers {
hasVisualOverflow: childLayoutGeometry.hasVisualOverflow,
);
- final SliverPhysicalParentData childParentData = child.parentData;
- assert(constraints.axisDirection != null);
- assert(constraints.growthDirection != null);
+ final SliverPhysicalParentData? childParentData =
+ child!.parentData as SliverPhysicalParentData?;
+
switch (axisDirection) {
case AxisDirection.up:
- childParentData.paintOffset = Offset.zero;
+ // this was working ... but maybe this is getting in the way of what we should be re-positioning
+ if (_reverse) childParentData!.paintOffset = Offset(0.0,-headerExtent);
+ else childParentData!.paintOffset = Offset.zero; // reverse
break;
+ case AxisDirection.down:
+ if (_reverse) childParentData!.paintOffset = Offset(0.0,
+ -headerExtent);
+ else
+ childParentData!.paintOffset = Offset(0.0,
+ calculatePaintOffset(constraints, from: 0.0, to: headerExtent));
+ break;
+
case AxisDirection.right:
- childParentData.paintOffset = new Offset(
+ childParentData!.paintOffset = Offset(
calculatePaintOffset(constraints, from: 0.0, to: headerExtent),
0.0);
break;
- case AxisDirection.down:
- childParentData.paintOffset = new Offset(0.0,
- calculatePaintOffset(constraints, from: 0.0, to: headerExtent));
- break;
case AxisDirection.left:
- childParentData.paintOffset = Offset.zero;
+ childParentData!.paintOffset = Offset.zero;
break;
}
}
if (header != null) {
- final SliverPhysicalParentData headerParentData = header.parentData;
- final childScrollExtent = child?.geometry?.scrollExtent ?? 0.0;
- double headerPosition = math.min(
- constraints.overlap,
- childScrollExtent -
- constraints.scrollOffset -
- (overlapsContent ? _headerExtent : 0.0));
-
- _isPinned = (constraints.scrollOffset + constraints.overlap) > 0.0 ||
- constraints.remainingPaintExtent ==
- constraints.viewportMainAxisExtent;
- // second layout if scroll percentage changed and header is a RenderStickyHeaderLayoutBuilder.
- if (header is RenderStickyHeaderLayoutBuilder) {
- double scrollPercentage =
- ((headerPosition - constraints.overlap).abs() / _headerExtent)
- .clamp(0.0, 1.0);
+ final SliverPhysicalParentData? headerParentData =
+ header!.parentData as SliverPhysicalParentData?;
+
+ _isPinned = () {
+ if (!sticky) return false;
+ if (_reverse) return (constraints.remainingPaintExtent < (constraints.viewportMainAxisExtent - ((child!.parentData as SliverPhysicalParentData?)?.paintOffset.distance ?? 0)));
+ else return ((constraints.scrollOffset + constraints.overlap) > 0.0 ||
+ constraints.remainingPaintExtent ==
+ constraints.viewportMainAxisExtent);
+ }();
+
+ final double headerScrollRatio =
+ ((headerPosition - constraints.overlap).abs() / _headerExtent!);
+ if (_isPinned && headerScrollRatio <= 1) {
+ controller?.stickyHeaderScrollOffset =
+ constraints.precedingScrollExtent;
+ }
+ // second layout if scroll percentage changed and header is a
+ // RenderStickyHeaderLayoutBuilder.
+ if (header is RenderConstrainedLayoutBuilder<
+ BoxValueConstraints, RenderBox>) {
+ double headerScrollRatioClamped = headerScrollRatio.clamp(0.0, 1.0);
SliverStickyHeaderState state =
- new SliverStickyHeaderState(scrollPercentage, _isPinned);
+ SliverStickyHeaderState(headerScrollRatioClamped, _isPinned);
if (_oldState != state) {
_oldState = state;
- header.layout(
- new StickyHeaderConstraints(
- state: _oldState,
- boxConstraints: constraints.asBoxConstraints(),
+ header!.layout(
+ BoxValueConstraints(
+ value: _oldState!,
+ constraints: constraints.asBoxConstraints(),
),
parentUsesSize: true,
);
@@ -247,64 +305,80 @@ class RenderSliverStickyHeader extends RenderSliver with RenderSliverHelpers {
switch (axisDirection) {
case AxisDirection.up:
- headerParentData.paintOffset = new Offset(
- 0.0, geometry.paintExtent - headerPosition - _headerExtent);
+ double headerOffset = - headerPosition - _headerExtent!;
+ if (_reverse) headerParentData!.paintOffset = Offset(0.0, 0.0 +
+ (constraints.remainingPaintExtent < _headerExtent! ? (geometry!.paintExtent + headerOffset)
+ : 0));
+ else headerParentData!.paintOffset = Offset(0.0, geometry!.paintExtent +headerOffset);
break;
case AxisDirection.down:
- headerParentData.paintOffset = new Offset(0.0, headerPosition);
+ headerParentData?.paintOffset = Offset(0.0, headerPosition);
break;
case AxisDirection.left:
- headerParentData.paintOffset = new Offset(
- geometry.paintExtent - headerPosition - _headerExtent, 0.0);
+ headerParentData!.paintOffset = Offset(
+ geometry!.paintExtent - headerPosition - _headerExtent!, 0.0);
break;
case AxisDirection.right:
- headerParentData.paintOffset = new Offset(headerPosition, 0.0);
+ headerParentData!.paintOffset = Offset(headerPosition, 0.0);
break;
}
}
}
@override
- bool hitTestChildren(HitTestResult result,
- {@required double mainAxisPosition, @required double crossAxisPosition}) {
- assert(geometry.hitTestExtent > 0.0);
+ bool hitTestChildren(SliverHitTestResult result,
+ {required double mainAxisPosition, required double crossAxisPosition}) {
+ assert(geometry!.hitTestExtent > 0.0);
if (header != null &&
- mainAxisPosition - constraints.overlap <= _headerExtent) {
- return hitTestBoxChild(result, header,
- mainAxisPosition: mainAxisPosition - constraints.overlap,
- crossAxisPosition: crossAxisPosition) ||
+ (geometry!.paintExtent - mainAxisPosition < _headerExtent!)
+ ) {
+ final didHitHeader = hitTestBoxChild(
+ BoxHitTestResult.wrap(SliverHitTestResult.wrap(result)),
+ header!,
+ mainAxisPosition: geometry!.paintExtent - mainAxisPosition + childMainAxisPosition(header),
+ crossAxisPosition: crossAxisPosition,
+ );
+
+ return didHitHeader ||
(_overlapsContent &&
child != null &&
- child.geometry.hitTestExtent > 0.0 &&
- child.hitTest(result,
+ child!.geometry!.hitTestExtent > 0.0 &&
+ child!.hitTest(result,
mainAxisPosition:
- mainAxisPosition - childMainAxisPosition(child),
+ mainAxisPosition,
crossAxisPosition: crossAxisPosition));
- } else if (child != null && child.geometry.hitTestExtent > 0.0) {
- return child.hitTest(result,
- mainAxisPosition: mainAxisPosition - childMainAxisPosition(child),
+ } else if (child != null && child!.geometry!.hitTestExtent > 0.0) {
+ print('testing child');
+ return child!.hitTest(result,
+ mainAxisPosition: mainAxisPosition,
crossAxisPosition: crossAxisPosition);
}
return false;
}
@override
- double childMainAxisPosition(RenderObject child) {
- if (child == header)
- return _isPinned
+ double childMainAxisPosition(RenderObject? child) {
+ if (child == header) {
+ if (_isPinned) return 0;
+ if (!_reverse) return _isPinned
? 0.0
: -(constraints.scrollOffset + constraints.overlap);
- if (child == this.child)
- return calculatePaintOffset(constraints,
- from: 0.0, to: headerLogicalExtent);
- return null;
+ return (constraints.scrollOffset + constraints.overlap);
+ } else if (child == this.child) {
+ return calculatePaintOffset(constraints,from: 0.0, to: headerLogicalExtent!);
+ }
+ return 0;
}
@override
- double childScrollOffset(RenderObject child) {
+ double? childScrollOffset(RenderObject child) {
assert(child.parent == this);
if (child == this.child) {
- return _headerExtent;
+ // if (_reverse)
+ return 0;
+ // return _headerExtent;
+ // } else if (_reverse && child == this._header) {
+ // return _headerExtent;
} else {
return super.childScrollOffset(child);
}
@@ -312,23 +386,24 @@ class RenderSliverStickyHeader extends RenderSliver with RenderSliverHelpers {
@override
void applyPaintTransform(RenderObject child, Matrix4 transform) {
- assert(child != null);
- final SliverPhysicalParentData childParentData = child.parentData;
+ final SliverPhysicalParentData childParentData =
+ child.parentData as SliverPhysicalParentData;
childParentData.applyPaintTransform(transform);
}
@override
void paint(PaintingContext context, Offset offset) {
- if (geometry.visible) {
- if (child != null && child.geometry.visible) {
- final SliverPhysicalParentData childParentData = child.parentData;
- context.paintChild(child, offset + childParentData.paintOffset);
+ if (geometry!.visible) {
+ final Offset? childParentDataOffset = (child == null) ? null : (child!.parentData as SliverPhysicalParentData).paintOffset;
+ final Offset? headerParentDataOffset = (header == null) ? null : (header!.parentData as SliverPhysicalParentData).paintOffset;
+
+ if (child != null && child!.geometry!.visible) {
+ context.paintChild(child!, offset + (_reverse ? -childParentDataOffset! : childParentDataOffset!));
}
- // The header must be draw over the sliver.
+ // The header must be drawn over the sliver.
if (header != null) {
- final SliverPhysicalParentData headerParentData = header.parentData;
- context.paintChild(header, offset + headerParentData.paintOffset);
+ context.paintChild(header!, offset + headerParentDataOffset!);
}
}
}
diff --git a/lib/src/rendering/sticky_header_constraints.dart b/lib/src/rendering/sticky_header_constraints.dart
deleted file mode 100644
index 3119a73..0000000
--- a/lib/src/rendering/sticky_header_constraints.dart
+++ /dev/null
@@ -1,45 +0,0 @@
-import 'package:flutter/rendering.dart';
-import 'package:flutter_sticky_header/src/widgets/sliver_sticky_header.dart';
-
-/// Immutable layout constraints for sticky header
-class StickyHeaderConstraints extends BoxConstraints {
- StickyHeaderConstraints({
- this.state,
- BoxConstraints boxConstraints,
- }) : assert(state != null),
- assert(boxConstraints != null),
- super(
- minWidth: boxConstraints.minWidth,
- maxWidth: boxConstraints.maxWidth,
- minHeight: boxConstraints.minHeight,
- maxHeight: boxConstraints.maxHeight,
- );
-
- final SliverStickyHeaderState state;
-
- @override
- bool get isNormalized =>
- state.scrollPercentage >= 0.0 &&
- state.scrollPercentage <= 1.0 &&
- super.isNormalized;
-
- @override
- bool operator ==(dynamic other) {
- assert(debugAssertIsValid());
- if (identical(this, other)) return true;
- if (other is! StickyHeaderConstraints) return false;
- final StickyHeaderConstraints typedOther = other;
- assert(typedOther.debugAssertIsValid());
- return state == typedOther.state &&
- minWidth == typedOther.minWidth &&
- maxWidth == typedOther.maxWidth &&
- minHeight == typedOther.minHeight &&
- maxHeight == typedOther.maxHeight;
- }
-
- @override
- int get hashCode {
- assert(debugAssertIsValid());
- return hashValues(minWidth, maxWidth, minHeight, maxHeight, state);
- }
-}
diff --git a/lib/src/rendering/sticky_header_layout_builder.dart b/lib/src/rendering/sticky_header_layout_builder.dart
deleted file mode 100644
index d9a5d7c..0000000
--- a/lib/src/rendering/sticky_header_layout_builder.dart
+++ /dev/null
@@ -1,81 +0,0 @@
-import 'package:flutter/rendering.dart';
-import 'package:flutter/widgets.dart';
-import 'package:flutter_sticky_header/src/rendering/sticky_header_constraints.dart';
-
-class RenderStickyHeaderLayoutBuilder extends RenderBox
- with RenderObjectWithChildMixin {
- RenderStickyHeaderLayoutBuilder({
- LayoutCallback callback,
- }) : _callback = callback;
-
- LayoutCallback get callback => _callback;
- LayoutCallback _callback;
- set callback(LayoutCallback value) {
- if (value == _callback) return;
- _callback = value;
- markNeedsLayout();
- }
-
- // layout input
- @override
- StickyHeaderConstraints get constraints => super.constraints;
-
- bool _debugThrowIfNotCheckingIntrinsics() {
- assert(() {
- if (!RenderObject.debugCheckingIntrinsics) {
- throw new FlutterError(
- 'StickyHeaderLayoutBuilder does not support returning intrinsic dimensions.\n'
- 'Calculating the intrinsic dimensions would require running the layout '
- 'callback speculatively, which might mutate the live render object tree.');
- }
- return true;
- }());
- return true;
- }
-
- @override
- double computeMinIntrinsicWidth(double height) {
- assert(_debugThrowIfNotCheckingIntrinsics());
- return 0.0;
- }
-
- @override
- double computeMaxIntrinsicWidth(double height) {
- assert(_debugThrowIfNotCheckingIntrinsics());
- return 0.0;
- }
-
- @override
- double computeMinIntrinsicHeight(double width) {
- assert(_debugThrowIfNotCheckingIntrinsics());
- return 0.0;
- }
-
- @override
- double computeMaxIntrinsicHeight(double width) {
- assert(_debugThrowIfNotCheckingIntrinsics());
- return 0.0;
- }
-
- @override
- void performLayout() {
- assert(callback != null);
- invokeLayoutCallback(callback);
- if (child != null) {
- child.layout(constraints, parentUsesSize: true);
- size = constraints.constrain(child.size);
- } else {
- size = constraints.biggest;
- }
- }
-
- @override
- bool hitTestChildren(HitTestResult result, {Offset position}) {
- return child?.hitTest(result, position: position) ?? false;
- }
-
- @override
- void paint(PaintingContext context, Offset offset) {
- if (child != null) context.paintChild(child, offset);
- }
-}
diff --git a/lib/src/widgets/sliver_sticky_header.dart b/lib/src/widgets/sliver_sticky_header.dart
index 6937505..7372f80 100644
--- a/lib/src/widgets/sliver_sticky_header.dart
+++ b/lib/src/widgets/sliver_sticky_header.dart
@@ -1,11 +1,112 @@
-import 'package:flutter/widgets.dart';
-import 'package:flutter_sticky_header/src/rendering/sliver_sticky_header.dart';
-import 'package:flutter_sticky_header/src/widgets/sticky_header_layout_builder.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/rendering.dart';
+// import 'package:flutter_sticky_header/src/rendering/sliver_sticky_header.dart';
+import '../rendering/sliver_sticky_header.dart';
+import 'package:value_layout_builder/value_layout_builder.dart';
-/// Signature used by [SliverStickyHeaderBuilder] to build the header
+/// Signature used by [SliverStickyHeader.builder] to build the header
/// when the sticky header state has changed.
typedef Widget SliverStickyHeaderWidgetBuilder(
- BuildContext context, SliverStickyHeaderState state);
+ BuildContext context,
+ SliverStickyHeaderState state,
+);
+
+/// A
+class StickyHeaderController with ChangeNotifier {
+ /// The offset to use in order to jump to the first item
+ /// of current the sticky header.
+ ///
+ /// If there is no sticky headers, this is 0.
+ double get stickyHeaderScrollOffset => _stickyHeaderScrollOffset;
+ double _stickyHeaderScrollOffset = 0;
+
+ /// This setter should only be used by flutter_sticky_header package.
+ set stickyHeaderScrollOffset(double value) {
+ if (_stickyHeaderScrollOffset != value) {
+ _stickyHeaderScrollOffset = value;
+ notifyListeners();
+ }
+ }
+}
+
+/// The [StickyHeaderController] for descendant widgets that don't specify one
+/// explicitly.
+///
+/// [DefaultStickyHeaderController] is an inherited widget that is used to share a
+/// [StickyHeaderController] with [SliverStickyHeader]s. It's used when sharing an
+/// explicitly created [StickyHeaderController] isn't convenient because the sticky
+/// headers are created by a stateless parent widget or by different parent
+/// widgets.
+class DefaultStickyHeaderController extends StatefulWidget {
+ const DefaultStickyHeaderController({
+ Key? key,
+ required this.child,
+ }) : super(key: key);
+
+ /// The widget below this widget in the tree.
+ ///
+ /// Typically a [Scaffold] whose [AppBar] includes a [TabBar].
+ ///
+ /// {@macro flutter.widgets.child}
+ final Widget child;
+
+ /// The closest instance of this class that encloses the given context.
+ ///
+ /// Typical usage:
+ ///
+ /// ```dart
+ /// StickyHeaderController controller = DefaultStickyHeaderController.of(context);
+ /// ```
+ static StickyHeaderController? of(BuildContext context) {
+ final _StickyHeaderControllerScope? scope = context
+ .dependOnInheritedWidgetOfExactType<_StickyHeaderControllerScope>();
+ return scope?.controller;
+ }
+
+ @override
+ _DefaultStickyHeaderControllerState createState() =>
+ _DefaultStickyHeaderControllerState();
+}
+
+class _DefaultStickyHeaderControllerState
+ extends State {
+ StickyHeaderController? _controller;
+
+ @override
+ void initState() {
+ super.initState();
+ _controller = StickyHeaderController();
+ }
+
+ @override
+ void dispose() {
+ _controller!.dispose();
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return _StickyHeaderControllerScope(
+ controller: _controller,
+ child: widget.child,
+ );
+ }
+}
+
+class _StickyHeaderControllerScope extends InheritedWidget {
+ const _StickyHeaderControllerScope({
+ Key? key,
+ this.controller,
+ required Widget child,
+ }) : super(key: key, child: child);
+
+ final StickyHeaderController? controller;
+
+ @override
+ bool updateShouldNotify(_StickyHeaderControllerScope old) {
+ return controller != old.controller;
+ }
+}
/// State describing how a sticky header is rendered.
@immutable
@@ -13,8 +114,7 @@ class SliverStickyHeaderState {
const SliverStickyHeaderState(
this.scrollPercentage,
this.isPinned,
- ) : assert(scrollPercentage != null),
- assert(isPinned != null);
+ );
final double scrollPercentage;
@@ -40,44 +140,99 @@ class SliverStickyHeaderState {
///
/// Place this widget inside a [CustomScrollView] or similar.
class SliverStickyHeader extends RenderObjectWidget {
- /// Creates a sliver that displays the [header] before its [sliver], unless [overlapsContent] it's true.
+ /// Creates a sliver that displays the [header] before its [sliver], unless
+ /// [overlapsContent] it's true.
/// The [header] stays pinned when it hits the start of the viewport until
/// the [sliver] scrolls off the viewport.
///
- /// The [overlapsContent] argument must not be null.
+ /// The [overlapsContent] and [sticky] arguments must not be null.
+ ///
+ /// If a [StickyHeaderController] is not provided, then the value of
+ /// [DefaultStickyHeaderController.of] will be used.
SliverStickyHeader({
- Key key,
+ Key? key,
this.header,
this.sliver,
this.overlapsContent: false,
- }) : assert(overlapsContent != null),
- super(key: key);
+ this.sticky = true,
+ this.reverse = false,
+ this.controller,
+ }) : super(key: key);
+
+ /// Creates a widget that builds the header of a [SliverStickyHeader]
+ /// each time its scroll percentage changes.
+ ///
+ /// The [builder], [overlapsContent] and [sticky] arguments must not be null.
+ ///
+ /// If a [StickyHeaderController] is not provided, then the value of
+ /// [DefaultStickyHeaderController.of] will be used.
+ SliverStickyHeader.builder({
+ Key? key,
+ required SliverStickyHeaderWidgetBuilder builder,
+ Widget? sliver,
+ bool overlapsContent: false,
+ bool sticky = true,
+ bool reverse = false,
+ StickyHeaderController? controller,
+ }) : this(
+ key: key,
+ header: ValueLayoutBuilder(
+ builder: (context, constraints) =>
+ builder(context, constraints.value),
+ ),
+ sliver: sliver,
+ overlapsContent: overlapsContent,
+ sticky: sticky,
+ reverse: reverse,
+ controller: controller,
+ );
/// The header to display before the sliver.
- final Widget header;
+ final Widget? header;
/// The sliver to display after the header.
- final Widget sliver;
+ final Widget? sliver;
/// Whether the header should be drawn on top of the sliver
/// instead of before.
final bool overlapsContent;
+ /// Whether to stick the header.
+ /// Defaults to true.
+ final bool sticky;
+
+ final bool reverse;
+
+ /// The controller used to interact with this sliver.
+ ///
+ /// If a [StickyHeaderController] is not provided, then the value of [DefaultStickyHeaderController.of]
+ /// will be used.
+ final StickyHeaderController? controller;
+
@override
RenderSliverStickyHeader createRenderObject(BuildContext context) {
- return new RenderSliverStickyHeader(
+ return RenderSliverStickyHeader(
overlapsContent: overlapsContent,
+ sticky: sticky,
+ reverse: reverse,
+ controller: controller ?? DefaultStickyHeaderController.of(context),
);
}
@override
SliverStickyHeaderRenderObjectElement createElement() =>
- new SliverStickyHeaderRenderObjectElement(this);
+ SliverStickyHeaderRenderObjectElement(this);
@override
void updateRenderObject(
- BuildContext context, RenderSliverStickyHeader renderObject) {
- renderObject..overlapsContent = overlapsContent;
+ BuildContext context,
+ RenderSliverStickyHeader renderObject,
+ ) {
+ renderObject
+ ..overlapsContent = overlapsContent
+ ..sticky = sticky
+ ..reverse = reverse
+ ..controller = controller ?? DefaultStickyHeaderController.of(context);
}
}
@@ -87,19 +242,24 @@ class SliverStickyHeader extends RenderObjectWidget {
/// This is useful if you want to change the header layout when it starts to scroll off the viewport.
/// You cannot change the main axis extent of the header in this builder otherwise it could result
/// in strange behavior.
+@Deprecated('Use SliverStickyHeader.builder instead.')
class SliverStickyHeaderBuilder extends StatelessWidget {
/// Creates a widget that builds the header of a [SliverStickyHeader]
/// each time its scroll percentage changes.
///
- /// The [builder] and [overlapsContent] arguments must not be null.
+ /// The [builder], [overlapsContent] and [sticky] arguments must not be null.
+ ///
+ /// If a [StickyHeaderController] is not provided, then the value of [DefaultStickyHeaderController.of]
+ /// will be used.
const SliverStickyHeaderBuilder({
- Key key,
- @required this.builder,
+ Key? key,
+ required this.builder,
this.sliver,
this.overlapsContent: false,
- }) : assert(builder != null),
- assert(overlapsContent != null),
- super(key: key);
+ this.sticky = true,
+ this.reverse = false,
+ this.controller,
+ }) : super(key: key);
/// Called to build the [SliverStickyHeader]'s header.
///
@@ -108,19 +268,34 @@ class SliverStickyHeaderBuilder extends StatelessWidget {
final SliverStickyHeaderWidgetBuilder builder;
/// The sliver to display after the header.
- final Widget sliver;
+ final Widget? sliver;
/// Whether the header should be drawn on top of the sliver
/// instead of before.
final bool overlapsContent;
+ /// Whether to stick the header.
+ /// Defaults to true.
+ final bool sticky;
+
+ final bool reverse;
+
+ /// The controller used to interact with this sliver.
+ ///
+ /// If a [StickyHeaderController] is not provided, then the value of [DefaultStickyHeaderController.of]
+ /// will be used.
+ final StickyHeaderController? controller;
+
@override
Widget build(BuildContext context) {
- return new SliverStickyHeader(
+ return SliverStickyHeader(
overlapsContent: overlapsContent,
sliver: sliver,
- header: new StickyHeaderLayoutBuilder(
- builder: (context, constraints) => builder(context, constraints.state),
+ sticky: sticky,
+ reverse: reverse,
+ controller: controller,
+ header: ValueLayoutBuilder(
+ builder: (context, constraints) => builder(context, constraints.value),
),
);
}
@@ -132,26 +307,27 @@ class SliverStickyHeaderRenderObjectElement extends RenderObjectElement {
: super(widget);
@override
- SliverStickyHeader get widget => super.widget;
+ SliverStickyHeader get widget => super.widget as SliverStickyHeader;
- Element _header;
+ Element? _header;
- Element _sliver;
+ Element? _sliver;
@override
void visitChildren(ElementVisitor visitor) {
- if (_header != null) visitor(_header);
- if (_sliver != null) visitor(_sliver);
+ if (_header != null) visitor(_header!);
+ if (_sliver != null) visitor(_sliver!);
}
@override
void forgetChild(Element child) {
+ super.forgetChild(child);
if (child == _header) _header = null;
if (child == _sliver) _sliver = null;
}
@override
- void mount(Element parent, dynamic newSlot) {
+ void mount(Element? parent, dynamic newSlot) {
super.mount(parent, newSlot);
_header = updateChild(_header, widget.header, 0);
_sliver = updateChild(_sliver, widget.sliver, 1);
@@ -166,21 +342,23 @@ class SliverStickyHeaderRenderObjectElement extends RenderObjectElement {
}
@override
- void insertChildRenderObject(RenderObject child, int slot) {
- final RenderSliverStickyHeader renderObject = this.renderObject;
- if (slot == 0) renderObject.header = child;
- if (slot == 1) renderObject.child = child;
+ void insertRenderObjectChild(RenderObject child, int? slot) {
+ final RenderSliverStickyHeader renderObject =
+ this.renderObject as RenderSliverStickyHeader;
+ if (slot == 0) renderObject.header = child as RenderBox?;
+ if (slot == 1) renderObject.child = child as RenderSliver?;
assert(renderObject == this.renderObject);
}
@override
- void moveChildRenderObject(RenderObject child, slot) {
+ void moveRenderObjectChild(RenderObject child, slot, newSlot) {
assert(false);
}
@override
- void removeChildRenderObject(RenderObject child) {
- final RenderSliverStickyHeader renderObject = this.renderObject;
+ void removeRenderObjectChild(RenderObject child, slot) {
+ final RenderSliverStickyHeader renderObject =
+ this.renderObject as RenderSliverStickyHeader;
if (renderObject.header == child) renderObject.header = null;
if (renderObject.child == child) renderObject.child = null;
assert(renderObject == this.renderObject);
diff --git a/lib/src/widgets/sticky_header_layout_builder.dart b/lib/src/widgets/sticky_header_layout_builder.dart
deleted file mode 100644
index 984a328..0000000
--- a/lib/src/widgets/sticky_header_layout_builder.dart
+++ /dev/null
@@ -1,150 +0,0 @@
-import 'package:flutter/rendering.dart';
-import 'package:flutter/widgets.dart';
-import 'package:flutter_sticky_header/src/rendering/sticky_header_constraints.dart';
-import 'package:flutter_sticky_header/src/rendering/sticky_header_layout_builder.dart';
-
-typedef Widget StickyHeaderLayoutWidgetBuilder(
- BuildContext context, StickyHeaderConstraints constraints);
-
-/// Builds a widget tree that can depend on the [StickyHeaderConstraints].
-///
-/// This is used by [SliverStickyHeaderBuilder] to change the header layout
-/// while it starts to scroll off the viewport.
-class StickyHeaderLayoutBuilder extends RenderObjectWidget {
- /// Creates a widget that defers its building until layout.
- ///
- /// The [builder] argument must not be null.
- const StickyHeaderLayoutBuilder({
- Key key,
- @required this.builder,
- }) : assert(builder != null),
- super(key: key);
-
- /// Called at layout time to construct the widget tree. The builder must not
- /// return null.
- final StickyHeaderLayoutWidgetBuilder builder;
-
- @override
- RenderObjectElement createElement() =>
- new _StickyHeaderLayoutBuilderElement(this);
-
- @override
- RenderObject createRenderObject(BuildContext context) =>
- new RenderStickyHeaderLayoutBuilder();
-
- // updateRenderObject is redundant with the logic in the _StickyHeaderLayoutBuilderElement below.
-}
-
-class _StickyHeaderLayoutBuilderElement extends RenderObjectElement {
- _StickyHeaderLayoutBuilderElement(StickyHeaderLayoutBuilder widget)
- : super(widget);
-
- @override
- StickyHeaderLayoutBuilder get widget => super.widget;
-
- @override
- RenderStickyHeaderLayoutBuilder get renderObject => super.renderObject;
-
- Element _child;
-
- @override
- void visitChildren(ElementVisitor visitor) {
- if (_child != null) visitor(_child);
- }
-
- @override
- void forgetChild(Element child) {
- assert(child == _child);
- _child = null;
- }
-
- @override
- void mount(Element parent, dynamic newSlot) {
- super.mount(parent, newSlot); // Creates the renderObject.
- renderObject.callback = _layout;
- }
-
- @override
- void update(StickyHeaderLayoutBuilder newWidget) {
- assert(widget != newWidget);
- super.update(newWidget);
- assert(widget == newWidget);
- renderObject.callback = _layout;
- renderObject.markNeedsLayout();
- }
-
- @override
- void performRebuild() {
- // This gets called if markNeedsBuild() is called on us.
- // That might happen if, e.g., our builder uses Inherited widgets.
- renderObject.markNeedsLayout();
- super
- .performRebuild(); // Calls widget.updateRenderObject (a no-op in this case).
- }
-
- @override
- void unmount() {
- renderObject.callback = null;
- super.unmount();
- }
-
- void _layout(StickyHeaderConstraints constraints) {
- owner.buildScope(this, () {
- Widget built;
- if (widget.builder != null) {
- try {
- built = widget.builder(this, constraints);
- debugWidgetBuilderValue(widget, built);
- } catch (e, stack) {
- built = ErrorWidget
- .builder(_debugReportException('building $widget', e, stack));
- }
- }
- try {
- _child = updateChild(_child, built, null);
- assert(_child != null);
- } catch (e, stack) {
- built = ErrorWidget
- .builder(_debugReportException('building $widget', e, stack));
- _child = updateChild(null, built, slot);
- }
- });
- }
-
- @override
- void insertChildRenderObject(RenderObject child, slot) {
- final RenderObjectWithChildMixin renderObject =
- this.renderObject;
- assert(slot == null);
- assert(renderObject.debugValidateChild(child));
- renderObject.child = child;
- assert(renderObject == this.renderObject);
- }
-
- @override
- void moveChildRenderObject(RenderObject child, slot) {
- assert(false);
- }
-
- @override
- void removeChildRenderObject(RenderObject child) {
- final RenderStickyHeaderLayoutBuilder renderObject = this.renderObject;
- assert(renderObject.child == child);
- renderObject.child = null;
- assert(renderObject == this.renderObject);
- }
-}
-
-FlutterErrorDetails _debugReportException(
- String context,
- dynamic exception,
- StackTrace stack,
-) {
- final FlutterErrorDetails details = new FlutterErrorDetails(
- exception: exception,
- stack: stack,
- library: 'flutter_sticky_header widgets library',
- context: context);
- FlutterError.reportError(details);
- return details;
-}
diff --git a/pubspec.yaml b/pubspec.yaml
index 77e2f69..bfa2b5a 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,16 +1,18 @@
name: flutter_sticky_header
description: Flutter implementation of sticky headers as a sliver. Use it in a CustomScrollView.
-version: 0.3.4
-author: Romain Rastel
+version: 0.6.4
homepage: https://github.com/letsar/flutter_sticky_header
dependencies:
flutter:
sdk: flutter
+ value_layout_builder: ^0.3.0
dev_dependencies:
flutter_test:
sdk: flutter
+ sliver_tools: ^0.2.7
environment:
- sdk: ">=1.19.0 <3.0.0"
+ sdk: '>=2.12.0 <3.0.0'
+ flutter: '>=1.24.0-10.2.pre'
diff --git a/test/controller_test.dart b/test/controller_test.dart
new file mode 100644
index 0000000..d5ee534
--- /dev/null
+++ b/test/controller_test.dart
@@ -0,0 +1,238 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_sticky_header/flutter_sticky_header.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+void main() {
+ setUp(() {
+ WidgetsBinding.instance.renderView.configuration =
+ TestViewConfiguration(size: Size(400, 800));
+ });
+
+ testWidgets('StickyHeaderController.stickyHeaderScrollOffset',
+ (WidgetTester tester) async {
+ final StickyHeaderController stickyHeaderController =
+ StickyHeaderController();
+ final ScrollController scrollController = ScrollController();
+
+ await tester.pumpWidget(
+ MaterialApp(
+ home: Scaffold(
+ body: CustomScrollView(
+ cacheExtent: 0,
+ controller: scrollController,
+ slivers: [
+ SliverStickyHeader(
+ header: _Header(index: 0),
+ sliver: const _Sliver(),
+ controller: stickyHeaderController,
+ ),
+ SliverStickyHeader(
+ header: _Header(index: 1),
+ sliver: const _Sliver(),
+ controller: stickyHeaderController,
+ ),
+ SliverStickyHeader(
+ header: _Header(index: 2),
+ sliver: const _Sliver(),
+ controller: stickyHeaderController,
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+
+ final header00Finder = find.text('Header #0');
+ final header01Finder = find.text('Header #1');
+ final header02Finder = find.text('Header #2');
+
+ expect(header00Finder, findsOneWidget);
+ expect(header01Finder, findsNothing);
+ expect(header02Finder, findsNothing);
+ expect(stickyHeaderController.stickyHeaderScrollOffset, equals(0));
+
+ final gesture = await tester.startGesture(Offset(200, 100));
+
+ // We scroll just before the Header #1.
+ await gesture.moveBy(Offset(0, -80));
+ await tester.pump();
+
+ expect(header00Finder, findsOneWidget);
+ expect(header01Finder, findsNothing);
+ expect(header02Finder, findsNothing);
+ expect(stickyHeaderController.stickyHeaderScrollOffset, equals(0));
+
+ // We scroll just after the Header #1 so that it is visible.
+ await gesture.moveBy(Offset(0, -80));
+ await tester.pump();
+
+ expect(header00Finder, findsOneWidget);
+ expect(header01Finder, findsOneWidget);
+ expect(header02Finder, findsNothing);
+ expect(stickyHeaderController.stickyHeaderScrollOffset, equals(0));
+
+ // We scroll in a way that Headers 0 and 1 are side by side.
+ await gesture.moveBy(Offset(0, -640));
+ await tester.pump();
+
+ expect(header00Finder, findsOneWidget);
+ expect(header01Finder, findsOneWidget);
+ expect(header02Finder, findsNothing);
+ expect(stickyHeaderController.stickyHeaderScrollOffset, equals(0));
+
+ // We scroll in a way that Header #1 is at the top of the screen.
+ await gesture.moveBy(Offset(0, -80));
+ await tester.pump();
+
+ expect(header00Finder, findsNothing);
+ expect(header01Finder, findsOneWidget);
+ expect(header02Finder, findsNothing);
+ expect(stickyHeaderController.stickyHeaderScrollOffset, equals(880));
+
+ // We scroll in a way that Header #1 is not visible.
+ await gesture.moveBy(Offset(0, -80));
+ await tester.pump();
+
+ expect(header00Finder, findsNothing);
+ expect(header01Finder, findsOneWidget);
+ expect(header02Finder, findsNothing);
+ expect(stickyHeaderController.stickyHeaderScrollOffset, equals(880));
+ });
+
+ testWidgets('StickyHeaderController.stickyHeaderScrollOffset - reverse',
+ (WidgetTester tester) async {
+ final StickyHeaderController stickyHeaderController =
+ StickyHeaderController();
+ final ScrollController scrollController = ScrollController();
+
+ await tester.pumpWidget(
+ MaterialApp(
+ home: Scaffold(
+ body: CustomScrollView(
+ controller: scrollController,
+ cacheExtent: 0,
+ reverse: true,
+ slivers: [
+ SliverStickyHeader(
+ header: _Header(index: 0),
+ sliver: const _Sliver(),
+ controller: stickyHeaderController,
+ ),
+ SliverStickyHeader(
+ header: _Header(index: 1),
+ sliver: const _Sliver(),
+ controller: stickyHeaderController,
+ ),
+ SliverStickyHeader(
+ header: _Header(index: 2),
+ sliver: const _Sliver(),
+ controller: stickyHeaderController,
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+
+ final header00Finder = find.text('Header #0');
+ final header01Finder = find.text('Header #1');
+ final header02Finder = find.text('Header #2');
+
+ expect(header00Finder, findsOneWidget);
+ expect(header01Finder, findsNothing);
+ expect(header02Finder, findsNothing);
+ expect(stickyHeaderController.stickyHeaderScrollOffset, equals(0));
+
+ final gesture = await tester.startGesture(Offset(200, 100));
+
+ // We scroll just before the Header #1.
+ await gesture.moveBy(Offset(0, 80));
+ await tester.pump();
+
+ expect(header00Finder, findsOneWidget);
+ expect(header01Finder, findsNothing);
+ expect(header02Finder, findsNothing);
+ expect(stickyHeaderController.stickyHeaderScrollOffset, equals(0));
+
+ // We scroll just after the Header #1 so that it is visible.
+ await gesture.moveBy(Offset(0, 80));
+ await tester.pump();
+
+ expect(header00Finder, findsOneWidget);
+ expect(header01Finder, findsOneWidget);
+ expect(header02Finder, findsNothing);
+ expect(stickyHeaderController.stickyHeaderScrollOffset, equals(0));
+
+ // We scroll in a way that Headers 0 and 1 are side by side.
+ await gesture.moveBy(Offset(0, 640));
+ await tester.pump();
+
+ expect(header00Finder, findsOneWidget);
+ expect(header01Finder, findsOneWidget);
+ expect(header02Finder, findsNothing);
+ expect(stickyHeaderController.stickyHeaderScrollOffset, equals(0));
+
+ // We scroll in a way that Header #1 is at the top of the screen.
+ await gesture.moveBy(Offset(0, 80));
+ await tester.pump();
+
+ expect(header00Finder, findsNothing);
+ expect(header01Finder, findsOneWidget);
+ expect(header02Finder, findsNothing);
+ expect(stickyHeaderController.stickyHeaderScrollOffset, equals(880));
+
+ // We scroll in a way that Header #1 is no longer visible.
+ await gesture.moveBy(Offset(0, 80));
+ await tester.pump();
+
+ expect(header00Finder, findsNothing);
+ expect(header01Finder, findsOneWidget);
+ expect(header02Finder, findsNothing);
+ expect(stickyHeaderController.stickyHeaderScrollOffset, equals(880));
+ });
+}
+
+class _Header extends StatelessWidget {
+ const _Header({
+ Key? key,
+ required this.index,
+ }) : super(key: key);
+
+ final int index;
+
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ color: Colors.blue,
+ child: Text('Header #$index'),
+ height: 80,
+ );
+ }
+}
+
+class _Sliver extends StatelessWidget {
+ const _Sliver({
+ Key? key,
+ }) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ return SliverList(
+ delegate: SliverChildBuilderDelegate(
+ (context, i) => const _SliverItem(),
+ childCount: 20,
+ ),
+ );
+ }
+}
+
+class _SliverItem extends StatelessWidget {
+ const _SliverItem({
+ Key? key,
+ }) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ return SizedBox(height: 40);
+ }
+}
diff --git a/test/flutter_sticky_header_test.dart b/test/flutter_sticky_header_test.dart
deleted file mode 100644
index ab73b3a..0000000
--- a/test/flutter_sticky_header_test.dart
+++ /dev/null
@@ -1 +0,0 @@
-void main() {}
diff --git a/test/sticky_test.dart b/test/sticky_test.dart
new file mode 100644
index 0000000..402f34e
--- /dev/null
+++ b/test/sticky_test.dart
@@ -0,0 +1,341 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_sticky_header/flutter_sticky_header.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:sliver_tools/sliver_tools.dart';
+
+void main() {
+ setUp(() {
+ WidgetsBinding.instance.renderView.configuration =
+ TestViewConfiguration(size: Size(400, 800));
+ });
+
+ testWidgets('Mix sticky and not sticky headers', (WidgetTester tester) async {
+ await tester.pumpWidget(
+ MaterialApp(
+ home: Scaffold(
+ body: CustomScrollView(
+ cacheExtent: 0,
+ slivers: [
+ SliverStickyHeader(
+ header: _Header(index: 0),
+ sliver: const _Sliver(),
+ ),
+ SliverStickyHeader(
+ header: _Header(index: 1),
+ sticky: false,
+ sliver: const _Sliver(),
+ ),
+ SliverStickyHeader(
+ header: _Header(index: 2),
+ sliver: const _Sliver(),
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+
+ final header00Finder = find.text('Header #0');
+ final header01Finder = find.text('Header #1');
+ final header02Finder = find.text('Header #2');
+
+ expect(header00Finder, findsOneWidget);
+ expect(header01Finder, findsNothing);
+ expect(header02Finder, findsNothing);
+
+ final gesture = await tester.startGesture(Offset(200, 100));
+
+ // We scroll just before the Header #1.
+ await gesture.moveBy(Offset(0, -80));
+ await tester.pump();
+
+ expect(header00Finder, findsOneWidget);
+ expect(header01Finder, findsNothing);
+ expect(header02Finder, findsNothing);
+
+ // We scroll just after the Header #1 so that it is visible.
+ await gesture.moveBy(Offset(0, -80));
+ await tester.pump();
+
+ expect(header00Finder, findsOneWidget);
+ expect(header01Finder, findsOneWidget);
+ expect(header02Finder, findsNothing);
+
+ // We scroll in a way that Headers 0 and 1 are side by side.
+ await gesture.moveBy(Offset(0, -640));
+ await tester.pump();
+
+ expect(header00Finder, findsOneWidget);
+ expect(header01Finder, findsOneWidget);
+ expect(header02Finder, findsNothing);
+
+ // We scroll in a way that Header #1 is at the top of the screen.
+ await gesture.moveBy(Offset(0, -80));
+ await tester.pump();
+
+ expect(header00Finder, findsNothing);
+ expect(header01Finder, findsOneWidget);
+ expect(header02Finder, findsNothing);
+
+ // We scroll in a way that Header #1 is not visible.
+ await gesture.moveBy(Offset(0, -80));
+ await tester.pump();
+
+ expect(header00Finder, findsNothing);
+ // Header #1 is in the tree (because the sliver is onstage).
+ expect(tester.getRect(header01Finder), const Rect.fromLTRB(0, -80, 400, 0));
+ expect(header02Finder, findsNothing);
+ });
+
+ testWidgets('Mix sticky and not sticky headers - reverse',
+ (WidgetTester tester) async {
+ await tester.pumpWidget(
+ MaterialApp(
+ home: Scaffold(
+ body: CustomScrollView(
+ cacheExtent: 0,
+ reverse: true,
+ slivers: [
+ SliverStickyHeader(
+ header: _Header(index: 0),
+ sliver: const _Sliver(),
+ ),
+ SliverStickyHeader(
+ header: _Header(index: 1),
+ sticky: false,
+ sliver: const _Sliver(),
+ ),
+ SliverStickyHeader(
+ header: _Header(index: 2),
+ sliver: const _Sliver(),
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+
+ final header00Finder = find.text('Header #0');
+ final header01Finder = find.text('Header #1');
+ final header02Finder = find.text('Header #2');
+
+ expect(header00Finder, findsOneWidget);
+ expect(header01Finder, findsNothing);
+ expect(header02Finder, findsNothing);
+
+ final gesture = await tester.startGesture(Offset(200, 100));
+
+ // We scroll just before the Header #1.
+ await gesture.moveBy(Offset(0, 80));
+ await tester.pump();
+
+ expect(header00Finder, findsOneWidget);
+ expect(header01Finder, findsNothing);
+ expect(header02Finder, findsNothing);
+
+ // We scroll just after the Header #1 so that it is visible.
+ await gesture.moveBy(Offset(0, 80));
+ await tester.pump();
+
+ expect(header00Finder, findsOneWidget);
+ expect(header01Finder, findsOneWidget);
+ expect(header02Finder, findsNothing);
+
+ // We scroll in a way that Headers 0 and 1 are side by side.
+ await gesture.moveBy(Offset(0, 640));
+ await tester.pump();
+
+ expect(header00Finder, findsOneWidget);
+ expect(header01Finder, findsOneWidget);
+ expect(header02Finder, findsNothing);
+
+ // We scroll in a way that Header #1 is at the top of the screen.
+ await gesture.moveBy(Offset(0, 80));
+ await tester.pump();
+
+ expect(header00Finder, findsNothing);
+ expect(header01Finder, findsOneWidget);
+ expect(header02Finder, findsNothing);
+
+ // We scroll in a way that Header #1 is no longer visible.
+ await gesture.moveBy(Offset(0, 80));
+ await tester.pump();
+
+ expect(header00Finder, findsNothing);
+ // Header #1 is in the tree (because the sliver is onstage).
+ expect(
+ tester.getRect(header01Finder), const Rect.fromLTRB(0, 800, 400, 880));
+ expect(header02Finder, findsNothing);
+ });
+
+ testWidgets('Testing multi-depth sticky headers', (tester) async {
+ await tester.pumpWidget(
+ MaterialApp(
+ home: Scaffold(
+ body: CustomScrollView(
+ cacheExtent: 0,
+ slivers: [
+ SliverStickyHeader(
+ header: _HierarchyHeader(hierarchy: '1'),
+ sliver: MultiSliver(
+ children: [
+ SliverStickyHeader(
+ header: _HierarchyHeader(hierarchy: '1.1'),
+ sliver: const _Sliver100(),
+ ),
+ SliverStickyHeader(
+ header: _HierarchyHeader(hierarchy: '1.2'),
+ sliver: MultiSliver(
+ children: [
+ SliverStickyHeader(
+ header: _HierarchyHeader(hierarchy: '1.2.1'),
+ sliver: const _Sliver100(),
+ ),
+ SliverStickyHeader(
+ header: _HierarchyHeader(hierarchy: '1.2.2'),
+ sliver: const _Sliver100(),
+ ),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ),
+ SliverStickyHeader(
+ header: _HierarchyHeader(hierarchy: '2'),
+ sliver: const _Sliver100(),
+ ),
+ SliverStickyHeader(
+ header: _HierarchyHeader(hierarchy: '3'),
+ sliver: MultiSliver(
+ children: [
+ SliverStickyHeader(
+ header: _HierarchyHeader(hierarchy: '3.1'),
+ sliver: const _Sliver100(),
+ ),
+ SliverStickyHeader(
+ header: _HierarchyHeader(hierarchy: '3.2'),
+ sliver: const _Sliver100(),
+ ),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+
+ final header001Finder = find.text('Header 1');
+ final header011Finder = find.text('Header 1.1');
+ final header012Finder = find.text('Header 1.2');
+ final header121Finder = find.text('Header 1.2.1');
+
+ expect(header001Finder, findsOneWidget);
+ expect(header011Finder, findsOneWidget);
+ expect(header012Finder, findsOneWidget);
+ expect(header121Finder, findsOneWidget);
+
+ expect(tester.getTopLeft(header011Finder).dy, 50);
+ expect(tester.getTopLeft(header012Finder).dy, 200);
+ expect(tester.getTopLeft(header121Finder).dy, 250);
+
+ // We scroll a little and expect that header 1 is sticky.
+ final gesture = await tester.startGesture(Offset(200, 100));
+ await gesture.moveBy(Offset(0, -25));
+ await tester.pump();
+
+ expect(tester.getTopLeft(header011Finder).dy, 50);
+ expect(tester.getTopLeft(header012Finder).dy, 175);
+ expect(tester.getTopLeft(header121Finder).dy, 225);
+
+ await gesture.moveBy(Offset(0, -125));
+ await tester.pump();
+
+ expect(tester.getTopLeft(header011Finder).dy, 0);
+ expect(tester.getTopLeft(header012Finder).dy, 50);
+ expect(tester.getTopLeft(header121Finder).dy, 100);
+
+ await gesture.moveBy(Offset(0, -25));
+ await tester.pump();
+
+ expect(tester.getTopLeft(header012Finder).dy, 50);
+ expect(tester.getTopLeft(header121Finder).dy, 100);
+ });
+}
+
+class _HierarchyHeader extends StatelessWidget {
+ const _HierarchyHeader({
+ Key? key,
+ required this.hierarchy,
+ }) : super(key: key);
+
+ final String hierarchy;
+
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ color: Colors.blue,
+ child: Text('Header $hierarchy'),
+ height: 50,
+ );
+ }
+}
+
+class _Sliver100 extends StatelessWidget {
+ const _Sliver100({
+ Key? key,
+ }) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ return SliverToBoxAdapter(
+ child: SizedBox(height: 100),
+ );
+ }
+}
+
+class _Header extends StatelessWidget {
+ const _Header({
+ Key? key,
+ required this.index,
+ }) : super(key: key);
+
+ final int index;
+
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ color: Colors.blue,
+ child: Text('Header #$index'),
+ height: 80,
+ );
+ }
+}
+
+class _Sliver extends StatelessWidget {
+ const _Sliver({
+ Key? key,
+ }) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ return SliverList(
+ delegate: SliverChildBuilderDelegate(
+ (context, i) => const _SliverItem(),
+ childCount: 20,
+ ),
+ );
+ }
+}
+
+class _SliverItem extends StatelessWidget {
+ const _SliverItem({
+ Key? key,
+ }) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ return SizedBox(height: 40);
+ }
+}