├── example
├── README.md
├── ios
│ ├── Flutter
│ │ ├── Debug.xcconfig
│ │ ├── Release.xcconfig
│ │ └── AppFrameworkInfo.plist
│ ├── Runner
│ │ ├── Runner-Bridging-Header.h
│ │ ├── Assets.xcassets
│ │ │ ├── LaunchImage.imageset
│ │ │ │ ├── LaunchImage.png
│ │ │ │ ├── LaunchImage@2x.png
│ │ │ │ ├── LaunchImage@3x.png
│ │ │ │ ├── README.md
│ │ │ │ └── Contents.json
│ │ │ └── AppIcon.appiconset
│ │ │ │ ├── Icon-App-20x20@1x.png
│ │ │ │ ├── Icon-App-20x20@2x.png
│ │ │ │ ├── Icon-App-20x20@3x.png
│ │ │ │ ├── Icon-App-29x29@1x.png
│ │ │ │ ├── Icon-App-29x29@2x.png
│ │ │ │ ├── Icon-App-29x29@3x.png
│ │ │ │ ├── Icon-App-40x40@1x.png
│ │ │ │ ├── Icon-App-40x40@2x.png
│ │ │ │ ├── Icon-App-40x40@3x.png
│ │ │ │ ├── Icon-App-60x60@2x.png
│ │ │ │ ├── Icon-App-60x60@3x.png
│ │ │ │ ├── Icon-App-76x76@1x.png
│ │ │ │ ├── Icon-App-76x76@2x.png
│ │ │ │ ├── Icon-App-1024x1024@1x.png
│ │ │ │ ├── Icon-App-83.5x83.5@2x.png
│ │ │ │ └── Contents.json
│ │ ├── AppDelegate.swift
│ │ ├── Base.lproj
│ │ │ ├── Main.storyboard
│ │ │ └── LaunchScreen.storyboard
│ │ └── Info.plist
│ ├── Runner.xcodeproj
│ │ ├── project.xcworkspace
│ │ │ ├── contents.xcworkspacedata
│ │ │ └── xcshareddata
│ │ │ │ ├── WorkspaceSettings.xcsettings
│ │ │ │ └── IDEWorkspaceChecks.plist
│ │ ├── xcshareddata
│ │ │ └── xcschemes
│ │ │ │ └── Runner.xcscheme
│ │ └── project.pbxproj
│ ├── Runner.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ ├── WorkspaceSettings.xcsettings
│ │ │ └── IDEWorkspaceChecks.plist
│ └── .gitignore
├── web
│ ├── favicon.png
│ ├── icons
│ │ ├── Icon-192.png
│ │ ├── Icon-512.png
│ │ ├── Icon-maskable-192.png
│ │ └── Icon-maskable-512.png
│ ├── manifest.json
│ └── index.html
├── android
│ ├── gradle.properties
│ ├── app
│ │ ├── src
│ │ │ ├── main
│ │ │ │ ├── res
│ │ │ │ │ ├── mipmap-hdpi
│ │ │ │ │ │ └── ic_launcher.png
│ │ │ │ │ ├── mipmap-mdpi
│ │ │ │ │ │ └── ic_launcher.png
│ │ │ │ │ ├── mipmap-xhdpi
│ │ │ │ │ │ └── ic_launcher.png
│ │ │ │ │ ├── mipmap-xxhdpi
│ │ │ │ │ │ └── ic_launcher.png
│ │ │ │ │ ├── mipmap-xxxhdpi
│ │ │ │ │ │ └── ic_launcher.png
│ │ │ │ │ ├── drawable
│ │ │ │ │ │ └── launch_background.xml
│ │ │ │ │ ├── drawable-v21
│ │ │ │ │ │ └── launch_background.xml
│ │ │ │ │ ├── values
│ │ │ │ │ │ └── styles.xml
│ │ │ │ │ └── values-night
│ │ │ │ │ │ └── styles.xml
│ │ │ │ ├── kotlin
│ │ │ │ │ └── com
│ │ │ │ │ │ └── example
│ │ │ │ │ │ └── example
│ │ │ │ │ │ └── MainActivity.kt
│ │ │ │ └── AndroidManifest.xml
│ │ │ ├── debug
│ │ │ │ └── AndroidManifest.xml
│ │ │ └── profile
│ │ │ │ └── AndroidManifest.xml
│ │ └── build.gradle
│ ├── gradle
│ │ └── wrapper
│ │ │ └── gradle-wrapper.properties
│ ├── .gitignore
│ ├── settings.gradle
│ └── build.gradle
├── .metadata
├── lib
│ ├── widgets.dart
│ ├── route_path.dart
│ ├── mock_data.dart
│ ├── example_listview.dart
│ ├── example_sliver.dart
│ ├── example_side_header.dart
│ ├── example_animatable_header.dart
│ ├── example_custom_section.dart
│ ├── main.dart
│ ├── example_scroll_to_index.dart
│ ├── example_custom_section_animation.dart
│ ├── example_nested_listview.dart
│ ├── example_pull_to_refresh.dart
│ └── example_nested_scroll_view.dart
├── .gitignore
├── analysis_options.yaml
├── integration_test
│ └── example_tests.dart
└── pubspec.yaml
├── doc
└── images
│ └── sliverlist.gif
├── publish.txt
├── lib
├── sticky_and_expandable_list.dart
└── src
│ ├── expandable_list_view.dart
│ ├── sliver_expandable_list.dart
│ └── expandable_section_container.dart
├── .metadata
├── .gitignore
├── .github
└── FUNDING.yml
├── LICENSE
├── CHANGELOG.md
├── pubspec.yaml
├── README_zh_CN.md
└── README.md
/example/README.md:
--------------------------------------------------------------------------------
1 | ## example
--------------------------------------------------------------------------------
/example/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Generated.xcconfig"
2 |
--------------------------------------------------------------------------------
/example/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Generated.xcconfig"
2 |
--------------------------------------------------------------------------------
/example/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
2 |
--------------------------------------------------------------------------------
/example/web/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tp7309/flutter_sticky_and_expandable_list/HEAD/example/web/favicon.png
--------------------------------------------------------------------------------
/doc/images/sliverlist.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tp7309/flutter_sticky_and_expandable_list/HEAD/doc/images/sliverlist.gif
--------------------------------------------------------------------------------
/example/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 | android.useAndroidX=true
3 | android.enableJetifier=true
4 |
--------------------------------------------------------------------------------
/example/web/icons/Icon-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tp7309/flutter_sticky_and_expandable_list/HEAD/example/web/icons/Icon-192.png
--------------------------------------------------------------------------------
/example/web/icons/Icon-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tp7309/flutter_sticky_and_expandable_list/HEAD/example/web/icons/Icon-512.png
--------------------------------------------------------------------------------
/publish.txt:
--------------------------------------------------------------------------------
1 | # 正式发布,国内用户应该都有使用flutter提供的中国镜像,所以上传时要指明上传到https://pub.dartlang.org地址。
2 | flutter packages pub publish --server=https://pub.dartlang.org
3 |
--------------------------------------------------------------------------------
/example/web/icons/Icon-maskable-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tp7309/flutter_sticky_and_expandable_list/HEAD/example/web/icons/Icon-maskable-192.png
--------------------------------------------------------------------------------
/example/web/icons/Icon-maskable-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tp7309/flutter_sticky_and_expandable_list/HEAD/example/web/icons/Icon-maskable-512.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tp7309/flutter_sticky_and_expandable_list/HEAD/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tp7309/flutter_sticky_and_expandable_list/HEAD/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tp7309/flutter_sticky_and_expandable_list/HEAD/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tp7309/flutter_sticky_and_expandable_list/HEAD/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tp7309/flutter_sticky_and_expandable_list/HEAD/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/lib/sticky_and_expandable_list.dart:
--------------------------------------------------------------------------------
1 | library expandable_list;
2 |
3 | export 'src/expandable_section_container.dart';
4 | export 'src/expandable_list_view.dart';
5 | export 'src/sliver_expandable_list.dart';
6 |
--------------------------------------------------------------------------------
/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.example.example
2 |
3 | import io.flutter.embedding.android.FlutterActivity
4 |
5 | class MainActivity: FlutterActivity() {
6 | }
7 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tp7309/flutter_sticky_and_expandable_list/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tp7309/flutter_sticky_and_expandable_list/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tp7309/flutter_sticky_and_expandable_list/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tp7309/flutter_sticky_and_expandable_list/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tp7309/flutter_sticky_and_expandable_list/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tp7309/flutter_sticky_and_expandable_list/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tp7309/flutter_sticky_and_expandable_list/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tp7309/flutter_sticky_and_expandable_list/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tp7309/flutter_sticky_and_expandable_list/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tp7309/flutter_sticky_and_expandable_list/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tp7309/flutter_sticky_and_expandable_list/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tp7309/flutter_sticky_and_expandable_list/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tp7309/flutter_sticky_and_expandable_list/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tp7309/flutter_sticky_and_expandable_list/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tp7309/flutter_sticky_and_expandable_list/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tp7309/flutter_sticky_and_expandable_list/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tp7309/flutter_sticky_and_expandable_list/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tp7309/flutter_sticky_and_expandable_list/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/example/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Jun 23 08:50:38 CEST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip
7 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: 27321ebbad34b0a3fafe99fac037102196d655ff
8 | channel: stable
9 |
10 | project_type: package
11 |
--------------------------------------------------------------------------------
/example/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: 097d3313d8e2c7f901932d63e537c1acefb87800
8 | channel: stable
9 |
10 | project_type: app
11 |
--------------------------------------------------------------------------------
/example/android/.gitignore:
--------------------------------------------------------------------------------
1 | gradle-wrapper.jar
2 | /.gradle
3 | /captures/
4 | /gradlew
5 | /gradlew.bat
6 | /local.properties
7 | GeneratedPluginRegistrant.java
8 |
9 | # Remember to never publicly share your keystore.
10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
11 | key.properties
12 | **/*.keystore
13 | **/*.jks
14 |
--------------------------------------------------------------------------------
/example/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md:
--------------------------------------------------------------------------------
1 | # Launch Screen Assets
2 |
3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory.
4 |
5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
--------------------------------------------------------------------------------
/example/lib/widgets.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class CustomAppBar extends AppBar {
4 | CustomAppBar({
5 | Key? key,
6 | String title = "",
7 | }) : super(
8 | key: key,
9 | title: Text(
10 | title,
11 | style: TextStyle(color: Colors.white),
12 | ),
13 | iconTheme: IconThemeData(color: Colors.white),
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/example/ios/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import Flutter
3 |
4 | @UIApplicationMain
5 | @objc class AppDelegate: FlutterAppDelegate {
6 | override func application(
7 | _ application: UIApplication,
8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
9 | ) -> Bool {
10 | GeneratedPluginRegistrant.register(with: self)
11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/example/android/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
4 | def properties = new Properties()
5 |
6 | assert localPropertiesFile.exists()
7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
8 |
9 | def flutterSdkPath = properties.getProperty("flutter.sdk")
10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
12 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://www.dartlang.org/tools/private-files.html
2 |
3 | # Files and directories created by pub
4 | .dart_tool/
5 | .packages
6 | .pub/
7 | build/
8 | # If you're building an application, you may want to check-in your pubspec.lock
9 | pubspec.lock
10 |
11 | # Directory created by dartdoc
12 | # If you don't generate documentation locally you can remove this line.
13 | doc/api/
14 |
15 | *.iml
16 | *.ipr
17 | *.iws
18 | .idea/
19 | .DS_Store
20 | .vscode/
21 | .history/
22 | .last_build_id
23 | flutter_*.log
24 | Podfile.lock
--------------------------------------------------------------------------------
/example/android/app/src/main/res/drawable-v21/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "LaunchImage.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "LaunchImage@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "LaunchImage@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/example/lib/route_path.dart:
--------------------------------------------------------------------------------
1 | class RoutePath {
2 | static const home = "/";
3 | static const listView = "/listView";
4 | static const sliver = "/sliver";
5 | static const animatableHeader = "/animatableHeader";
6 | static const customSection = "/customSection";
7 | static const nestedListView = "/subListView";
8 | static const customSectionAnimation = "/customSectionAnimation";
9 | static const nestedScrollView = "/nestedScrollView";
10 | static const sideHeader = "/sideHeader";
11 | static const scrollToIndex = "/scrollToIndex";
12 | static const pullToRefresh = "/pullToRefresh";
13 | }
14 |
--------------------------------------------------------------------------------
/example/ios/.gitignore:
--------------------------------------------------------------------------------
1 | **/dgph
2 | *.mode1v3
3 | *.mode2v3
4 | *.moved-aside
5 | *.pbxuser
6 | *.perspectivev3
7 | **/*sync/
8 | .sconsign.dblite
9 | .tags*
10 | **/.vagrant/
11 | **/DerivedData/
12 | Icon?
13 | **/Pods/
14 | **/.symlinks/
15 | profile
16 | xcuserdata
17 | **/.generated/
18 | Flutter/App.framework
19 | Flutter/Flutter.framework
20 | Flutter/Flutter.podspec
21 | Flutter/Generated.xcconfig
22 | Flutter/ephemeral/
23 | Flutter/app.flx
24 | Flutter/app.zip
25 | Flutter/flutter_assets/
26 | Flutter/flutter_export_environment.sh
27 | ServiceDefinitions.json
28 | Runner/GeneratedPluginRegistrant.*
29 |
30 | # Exceptions to above rules.
31 | !default.mode1v3
32 | !default.mode2v3
33 | !default.pbxuser
34 | !default.perspectivev3
35 |
--------------------------------------------------------------------------------
/example/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext.kotlin_version = '1.6.10'
3 | repositories {
4 | google()
5 | mavenCentral()
6 | }
7 |
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:4.1.0'
10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
11 | }
12 | }
13 |
14 | allprojects {
15 | repositories {
16 | google()
17 | mavenCentral()
18 | }
19 | }
20 |
21 | rootProject.buildDir = '../build'
22 | subprojects {
23 | project.buildDir = "${rootProject.buildDir}/${project.name}"
24 | }
25 | subprojects {
26 | project.evaluationDependsOn(':app')
27 | }
28 |
29 | task clean(type: Delete) {
30 | delete rootProject.buildDir
31 | }
32 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: ['https://www.paypal.com/paypalme/tp7309','https://user-images.githubusercontent.com/5046191/118354036-b075ca80-b59b-11eb-862e-ffd1b8e1659f.png']
13 |
--------------------------------------------------------------------------------
/example/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | MinimumOSVersion
24 | 9.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 |
12 | # IntelliJ related
13 | *.iml
14 | *.ipr
15 | *.iws
16 | .idea/
17 |
18 | # The .vscode folder contains launch configuration and tasks you configure in
19 | # VS Code which you may wish to be included in version control, so this line
20 | # is commented out by default.
21 | #.vscode/
22 |
23 | # Flutter/Dart/Pub related
24 | **/doc/api/
25 | **/ios/Flutter/.last_build_id
26 | .dart_tool/
27 | .flutter-plugins
28 | .flutter-plugins-dependencies
29 | .packages
30 | .pub-cache/
31 | .pub/
32 | /build/
33 |
34 | # Web related
35 | lib/generated_plugin_registrant.dart
36 |
37 | # Symbolication related
38 | app.*.symbols
39 |
40 | # Obfuscation related
41 | app.*.map.json
42 |
43 | # Android Studio will place build artifacts here
44 | /android/app/debug
45 | /android/app/profile
46 | /android/app/release
47 |
--------------------------------------------------------------------------------
/example/web/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example",
3 | "short_name": "example",
4 | "start_url": ".",
5 | "display": "standalone",
6 | "background_color": "#0175C2",
7 | "theme_color": "#0175C2",
8 | "description": "A new Flutter project.",
9 | "orientation": "portrait-primary",
10 | "prefer_related_applications": false,
11 | "icons": [
12 | {
13 | "src": "icons/Icon-192.png",
14 | "sizes": "192x192",
15 | "type": "image/png"
16 | },
17 | {
18 | "src": "icons/Icon-512.png",
19 | "sizes": "512x512",
20 | "type": "image/png"
21 | },
22 | {
23 | "src": "icons/Icon-maskable-192.png",
24 | "sizes": "192x192",
25 | "type": "image/png",
26 | "purpose": "maskable"
27 | },
28 | {
29 | "src": "icons/Icon-maskable-512.png",
30 | "sizes": "512x512",
31 | "type": "image/png",
32 | "purpose": "maskable"
33 | }
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/values-night/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Romain Rastel
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 1.1.3
2 | - remove unused log.
3 | -
4 | ## 1.1.2
5 | - [BugFix]sticky header not work when section's item is less.#45
6 |
7 | ## 1.1.0
8 | - migrate to flutter 3.0.0.
9 |
10 | ## 1.0.3
11 | - [Bugfix]Adding a separatorBuilder to the package's ListView example causes overlapping items #37
12 |
13 | ## 1.0.1
14 | - fix example warning.
15 |
16 | ## 1.0.0
17 | - update example to nullsafety version.
18 |
19 | ## 1.0.0-nullsafety.1
20 | - [Bugfix]compatibility error: type 'List' is not a subtype of type 'List' in type cast #28.
21 |
22 | ## 1.0.0-nullsafety
23 | - migrate to nullsafety version.
24 |
25 | ## 0.3.1
26 | - support add nested list view, but has some limit.
27 | - add integration_test for example.
28 |
29 | ## 0.3.0
30 | - support overlapsContent property.
31 | - add more examples.
32 | - [Bugfix]show error when collapse sticky section or last section.
33 |
34 | ## 0.2.3
35 | - [Bugfix]the header will move from top to the position where I click last item/position #18
36 |
37 | ## 0.2.1
38 | - fix bugs on Flutter 1.17.0.
39 |
40 | ## 0.2.0
41 | - import widgets.dart instead of material.dart
42 | - fix some bugs.
43 |
44 | ## 0.2.0-beta
45 | - Use more friendly api.
46 | - Support customize expand/collapse animation.
47 | - fix some bugs.
48 |
49 | ## 0.1.0
50 |
51 | - Initial repository.
--------------------------------------------------------------------------------
/example/lib/mock_data.dart:
--------------------------------------------------------------------------------
1 | import 'package:sticky_and_expandable_list/sticky_and_expandable_list.dart';
2 |
3 | ///
4 | /// create some example data.
5 | ///
6 | class MockData {
7 | ///return a example list, by default, we have 4 sections,
8 | ///each section has 5 items.
9 | static List getExampleSections(
10 | [int sectionSize = 10, int itemSize = 5]) {
11 | var sections = List.empty(growable: true);
12 | for (int i = 0; i < sectionSize; i++) {
13 | var section = ExampleSection()
14 | ..header = "Header#$i"
15 | ..items = List.generate(itemSize, (index) => "ListTile #$index")
16 | ..expanded = true;
17 | sections.add(section);
18 | }
19 | return sections;
20 | }
21 | }
22 |
23 | ///Section model example
24 | ///
25 | ///Section model must implements ExpandableListSection, each section has
26 | ///expand state, sublist. "T" is the model of each item in the sublist.
27 | class ExampleSection implements ExpandableListSection {
28 | //store expand state.
29 | late bool expanded;
30 |
31 | //return item model list.
32 | late List items;
33 |
34 | //example header, optional
35 | late String header;
36 |
37 | @override
38 | List getItems() {
39 | return items;
40 | }
41 |
42 | @override
43 | bool isSectionExpanded() {
44 | return expanded;
45 | }
46 |
47 | @override
48 | void setSectionExpanded(bool expanded) {
49 | this.expanded = expanded;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/lib/src/expandable_list_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/gestures.dart';
2 | import 'package:flutter/widgets.dart';
3 |
4 | import '../sticky_and_expandable_list.dart';
5 |
6 | /// A scrollable list of widgets arranged linearly, support expand/collapse item and
7 | /// sticky header.
8 | /// all build options are set in [SliverExpandableChildDelegate], this is to avoid
9 | /// [SliverExpandableList] use generics.
10 | class ExpandableListView extends BoxScrollView {
11 | ///same as ListView
12 | final SliverExpandableChildDelegate builder;
13 |
14 | ExpandableListView({
15 | Key? key,
16 | required this.builder,
17 | bool reverse = false,
18 | ScrollController? controller,
19 | bool? primary,
20 | ScrollPhysics? physics,
21 | bool shrinkWrap = false,
22 | EdgeInsetsGeometry? padding,
23 | double? cacheExtent,
24 | DragStartBehavior dragStartBehavior = DragStartBehavior.start,
25 | }) : super(
26 | key: key,
27 | scrollDirection: Axis.vertical,
28 | reverse: reverse,
29 | controller: controller,
30 | primary: primary,
31 | physics: physics,
32 | shrinkWrap: shrinkWrap,
33 | padding: padding,
34 | cacheExtent: cacheExtent,
35 | semanticChildCount: builder.sectionList.length,
36 | dragStartBehavior: dragStartBehavior,
37 | );
38 |
39 | @override
40 | Widget buildChildLayout(BuildContext context) {
41 | return SliverExpandableList(
42 | builder: builder,
43 | );
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/example/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | # This file configures the analyzer, which statically analyzes Dart code to
2 | # check for errors, warnings, and lints.
3 | #
4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled
5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
6 | # invoked from the command line by running `flutter analyze`.
7 |
8 | # The following line activates a set of recommended lints for Flutter apps,
9 | # packages, and plugins designed to encourage good coding practices.
10 | #include: package:flutter_lints/flutter.yaml
11 |
12 | linter:
13 | # The lint rules applied to this project can be customized in the
14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml`
15 | # included above or to enable additional rules. A list of all available lints
16 | # and their documentation is published at
17 | # https://dart-lang.github.io/linter/lints/index.html.
18 | #
19 | # Instead of disabling a lint rule for the entire project in the
20 | # section below, it can also be suppressed for a single line of code
21 | # or a specific dart file by using the `// ignore: name_of_lint` and
22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file
23 | # producing the lint.
24 | rules:
25 | # avoid_print: false # Uncomment to disable the `avoid_print` rule
26 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
27 |
28 | # Additional information about this file can be found at
29 | # https://dart.dev/guides/language/analysis-options
30 |
--------------------------------------------------------------------------------
/example/ios/Runner/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/example/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | Example
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | example
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | $(FLUTTER_BUILD_NAME)
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | $(FLUTTER_BUILD_NUMBER)
25 | LSRequiresIPhoneOS
26 |
27 | UILaunchStoryboardName
28 | LaunchScreen
29 | UIMainStoryboardFile
30 | Main
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | UISupportedInterfaceOrientations~ipad
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationPortraitUpsideDown
41 | UIInterfaceOrientationLandscapeLeft
42 | UIInterfaceOrientationLandscapeRight
43 |
44 | UIViewControllerBasedStatusBarAppearance
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/example/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
7 |
15 |
19 |
23 |
24 |
25 |
26 |
27 |
28 |
30 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: sticky_and_expandable_list
2 | description: Build a grouped list, which support expand/collapse section and sticky header, support use it with sliver widget.
3 | version: 1.1.3
4 | homepage: https://github.com/tp7309/flutter_sticky_and_expandable_list
5 | issue_tracker: https://github.com/tp7309/flutter_sticky_and_expandable_list/issues
6 |
7 | environment:
8 | sdk: '>=2.12.0 <3.0.0'
9 |
10 | dependencies:
11 | flutter:
12 | sdk: flutter
13 |
14 | dev_dependencies:
15 | flutter_test:
16 | sdk: flutter
17 |
18 | # For information on the generic Dart part of this file, see the
19 | # following page: https://dart.dev/tools/pub/pubspec
20 |
21 | # The following section is specific to Flutter.
22 | flutter:
23 |
24 | # To add assets to your package, add an assets section, like this:
25 | # assets:
26 | # - images/a_dot_burr.jpeg
27 | # - images/a_dot_ham.jpeg
28 | #
29 | # For details regarding assets in packages, see
30 | # https://flutter.dev/assets-and-images/#from-packages
31 | #
32 | # An image asset can refer to one or more resolution-specific "variants", see
33 | # https://flutter.dev/assets-and-images/#resolution-aware.
34 |
35 | # To add custom fonts to your package, add a fonts section here,
36 | # in this "flutter" section. Each entry in this list should have a
37 | # "family" key with the font family name, and a "fonts" key with a
38 | # list giving the asset and other descriptors for the font. For
39 | # example:
40 | # fonts:
41 | # - family: Schyler
42 | # fonts:
43 | # - asset: fonts/Schyler-Regular.ttf
44 | # - asset: fonts/Schyler-Italic.ttf
45 | # style: italic
46 | # - family: Trajan Pro
47 | # fonts:
48 | # - asset: fonts/TrajanPro.ttf
49 | # - asset: fonts/TrajanPro_Bold.ttf
50 | # weight: 700
51 | #
52 | # For details regarding fonts in packages, see
53 | # https://flutter.dev/custom-fonts/#from-packages
54 |
--------------------------------------------------------------------------------
/example/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | def localProperties = new Properties()
2 | def localPropertiesFile = rootProject.file('local.properties')
3 | if (localPropertiesFile.exists()) {
4 | localPropertiesFile.withReader('UTF-8') { reader ->
5 | localProperties.load(reader)
6 | }
7 | }
8 |
9 | def flutterRoot = localProperties.getProperty('flutter.sdk')
10 | if (flutterRoot == null) {
11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
12 | }
13 |
14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
15 | if (flutterVersionCode == null) {
16 | flutterVersionCode = '1'
17 | }
18 |
19 | def flutterVersionName = localProperties.getProperty('flutter.versionName')
20 | if (flutterVersionName == null) {
21 | flutterVersionName = '1.0'
22 | }
23 |
24 | apply plugin: 'com.android.application'
25 | apply plugin: 'kotlin-android'
26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
27 |
28 | android {
29 | compileSdkVersion flutter.compileSdkVersion
30 |
31 | compileOptions {
32 | sourceCompatibility JavaVersion.VERSION_1_8
33 | targetCompatibility JavaVersion.VERSION_1_8
34 | }
35 |
36 | kotlinOptions {
37 | jvmTarget = '1.8'
38 | }
39 |
40 | sourceSets {
41 | main.java.srcDirs += 'src/main/kotlin'
42 | }
43 |
44 | defaultConfig {
45 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
46 | applicationId "com.example.example"
47 | minSdkVersion flutter.minSdkVersion
48 | targetSdkVersion flutter.targetSdkVersion
49 | versionCode flutterVersionCode.toInteger()
50 | versionName flutterVersionName
51 | }
52 |
53 | buildTypes {
54 | release {
55 | // TODO: Add your own signing config for the release build.
56 | // Signing with the debug keys for now, so `flutter run --release` works.
57 | signingConfig signingConfigs.debug
58 | }
59 | }
60 | }
61 |
62 | flutter {
63 | source '../..'
64 | }
65 |
66 | dependencies {
67 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
68 | }
69 |
--------------------------------------------------------------------------------
/example/lib/example_listview.dart:
--------------------------------------------------------------------------------
1 | import 'package:example/mock_data.dart';
2 | import 'package:example/widgets.dart';
3 | import 'package:flutter/material.dart';
4 | import 'package:sticky_and_expandable_list/sticky_and_expandable_list.dart';
5 |
6 | class ExampleListView extends StatefulWidget {
7 | @override
8 | _ExampleListViewState createState() => _ExampleListViewState();
9 | }
10 |
11 | class _ExampleListViewState extends State {
12 | var sectionList = MockData.getExampleSections();
13 |
14 | @override
15 | void initState() {
16 | super.initState();
17 | }
18 |
19 | @override
20 | Widget build(BuildContext context) {
21 | //In this example, we create a custom model class(ExampleSection).
22 | //class ExampleSection implements ExpandableListSection {}
23 | //so: SliverExpandableChildDelegate()
24 | return Scaffold(
25 | appBar: CustomAppBar(title: "ListView Example"),
26 | body: ExpandableListView(
27 | builder: SliverExpandableChildDelegate(
28 | sectionList: sectionList,
29 | headerBuilder: _buildHeader,
30 | itemBuilder: (context, sectionIndex, itemIndex, index) {
31 | String item = sectionList[sectionIndex].items[itemIndex];
32 | return ListTile(
33 | leading: CircleAvatar(
34 | child: Text("$index"),
35 | ),
36 | title: Text(item),
37 | );
38 | }),
39 | ));
40 | }
41 |
42 | Widget _buildHeader(BuildContext context, int sectionIndex, int index) {
43 | ExampleSection section = sectionList[sectionIndex];
44 | return InkWell(
45 | child: Container(
46 | color: Colors.lightBlue,
47 | height: 48,
48 | padding: EdgeInsets.only(left: 20),
49 | alignment: Alignment.centerLeft,
50 | child: Text(
51 | section.header,
52 | style: TextStyle(color: Colors.white),
53 | )),
54 | onTap: () {
55 | //toggle section expand state
56 | setState(() {
57 | section.setSectionExpanded(!section.isSectionExpanded());
58 | });
59 | });
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/example/integration_test/example_tests.dart:
--------------------------------------------------------------------------------
1 | // import 'package:example/route_path.dart';
2 | // import 'package:flutter/material.dart';
3 | // import 'package:flutter/widgets.dart';
4 | // import 'package:flutter_test/flutter_test.dart';
5 | // import 'package:integration_test/integration_test.dart';
6 | // import 'package:example/main.dart' as app;
7 | // import 'package:sticky_and_expandable_list/sticky_and_expandable_list.dart';
8 | //
9 | // void main() {
10 | // IntegrationTestWidgetsFlutterBinding.ensureInitialized();
11 | //
12 | // group("example_listivew_test", () {
13 | // testWidgets("sticky", (WidgetTester tester) async {
14 | // app.main();
15 | // await tester.pumpAndSettle();
16 | // await tester.tap(find.byKey(ValueKey(RoutePath.listView)));
17 | //
18 | // await tester.pumpAndSettle();
19 | // var listView = find.byType(ExpandableListView);
20 | // var header0 = find.text("Header#0");
21 | // var header1 = find.text("Header#1");
22 | // var header2 = find.text("Header#2");
23 | //
24 | // //initial position.
25 | // expect(header1, findsOneWidget);
26 | // expect(header2, findsOneWidget);
27 | //
28 | // //Header#1 sticky
29 | // await scrollTo(tester, listView, Offset(0, -200));
30 | // expect(header1, findsOneWidget);
31 | // expect(header2, findsOneWidget);
32 | // await tester.pumpAndSettle(Duration(seconds: 1));
33 | //
34 | // // //Header#2 sticky
35 | // await scrollTo(tester, listView, Offset(0, -300));
36 | // expect(header0, findsNothing);
37 | // expect(header1, findsNothing);
38 | // expect(header2, findsOneWidget);
39 | // await tester.pumpAndSettle(Duration(seconds: 30));
40 | // });
41 | // });
42 | // }
43 | //
44 | // Future scrollTo(WidgetTester tester, Finder finder, Offset offset) async {
45 | // await tester.timedDrag(finder, offset, Duration(milliseconds: 300));
46 | // }
47 | //
48 | // // Future scrollTo(WidgetTester tester, Finder finder, String text,
49 | // // {Type widgetType = CircleAvatar, Offset offset = Offset.zero}) async {
50 | // // var widget = find.widgetWithText(widgetType, text);
51 | // // var finderOffset = tester.getTopLeft(finder);
52 | // // var itemOffset = tester.getTopLeft(widget);
53 | // // var finalOffset = Offset(finderOffset.dx - itemOffset.dx + offset.dx,
54 | // // finderOffset.dy - itemOffset.dy + offset.dy);
55 | // // await tester.timedDrag(finder, finalOffset, Duration(milliseconds: 300));
56 | // // }
57 |
--------------------------------------------------------------------------------
/example/ios/Runner/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/example/lib/example_sliver.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:sticky_and_expandable_list/sticky_and_expandable_list.dart';
3 |
4 | import 'mock_data.dart';
5 |
6 | class ExampleSliver extends StatefulWidget {
7 | @override
8 | _ExampleSliverState createState() => _ExampleSliverState();
9 | }
10 |
11 | class _ExampleSliverState extends State {
12 | var sectionList = MockData.getExampleSections();
13 |
14 | @override
15 | Widget build(BuildContext context) {
16 | return SafeArea(
17 | child: Scaffold(
18 | body: CustomScrollView(
19 | slivers: [
20 | SliverAppBar(
21 | pinned: true,
22 | floating: true,
23 | expandedHeight: 200,
24 | flexibleSpace: FlexibleSpaceBar(
25 | title: Text(
26 | "Sliver Example",
27 | style: TextStyle(color: Colors.white),
28 | ),
29 | ),
30 | iconTheme: IconThemeData(color: Colors.white),
31 | ),
32 | SliverExpandableList(
33 | builder: SliverExpandableChildDelegate(
34 | sectionList: sectionList,
35 | headerBuilder: _buildHeader,
36 | itemBuilder: (context, sectionIndex, itemIndex, index) {
37 | String item = sectionList[sectionIndex].items[itemIndex];
38 | return ListTile(
39 | leading: CircleAvatar(
40 | child: Text("$index"),
41 | ),
42 | title: Text(item),
43 | );
44 | },
45 | ),
46 | ),
47 | ],
48 | ),
49 | ),
50 | );
51 | }
52 |
53 | Widget _buildHeader(BuildContext context, int sectionIndex, int index) {
54 | ExampleSection section = sectionList[sectionIndex];
55 | return InkWell(
56 | child: Container(
57 | color: Colors.lightBlue,
58 | height: 48,
59 | padding: EdgeInsets.only(left: 20),
60 | alignment: Alignment.centerLeft,
61 | child: Text(
62 | section.header,
63 | style: TextStyle(color: Colors.white),
64 | )),
65 | onTap: () {
66 | //toggle section expand state
67 | setState(() {
68 | section.setSectionExpanded(!section.isSectionExpanded());
69 | });
70 | });
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/example/lib/example_side_header.dart:
--------------------------------------------------------------------------------
1 | import 'package:example/mock_data.dart';
2 | import 'package:example/widgets.dart';
3 | import 'package:flutter/material.dart';
4 | import 'package:sticky_and_expandable_list/sticky_and_expandable_list.dart';
5 |
6 | class ExampleSideHeader extends StatefulWidget {
7 | @override
8 | _ExampleSideHeaderState createState() => _ExampleSideHeaderState();
9 | }
10 |
11 | class _ExampleSideHeaderState extends State {
12 | var sectionList = MockData.getExampleSections();
13 |
14 | var _controller = ExpandableListController();
15 |
16 | @override
17 | void initState() {
18 | super.initState();
19 | }
20 |
21 | @override
22 | void dispose() {
23 | _controller.dispose();
24 | super.dispose();
25 | }
26 |
27 | @override
28 | Widget build(BuildContext context) {
29 | return Scaffold(
30 | appBar: CustomAppBar(title: "Side Header Example"),
31 | body: ExpandableListView(
32 | builder: SliverExpandableChildDelegate(
33 | overlapsContent: true,
34 | controller: _controller,
35 | sectionList: sectionList,
36 | headerBuilder: _buildHeader,
37 | itemBuilder: (context, sectionIndex, itemIndex, index) {
38 | String item = sectionList[sectionIndex].items[itemIndex];
39 | return Padding(
40 | padding: const EdgeInsets.only(left: 50),
41 | child: Container(
42 | color: Colors.black26,
43 | child: ListTile(
44 | title: Text(
45 | item,
46 | style: TextStyle(color: Colors.white),
47 | ),
48 | ),
49 | ),
50 | );
51 | },
52 | separatorBuilder: (context, isSectionSeparator, index) {
53 | return isSectionSeparator
54 | ? SizedBox(
55 | height: 15,
56 | )
57 | : Container();
58 | }),
59 | ));
60 | }
61 |
62 | Widget _buildHeader(BuildContext context, int sectionIndex, int index) {
63 | return ExpandableAutoLayoutWidget(
64 | trigger: ExpandableDefaultAutoLayoutTrigger(_controller),
65 | builder: (context) {
66 | double opacity = _controller.switchingSectionIndex == sectionIndex
67 | ? (1 - _controller.percent)
68 | : 1;
69 | return Container(
70 | padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 4),
71 | child: CircleAvatar(
72 | backgroundColor: Colors.lightBlue.withOpacity(opacity),
73 | child: Text(
74 | "$sectionIndex",
75 | style: TextStyle(color: Colors.white),
76 | ),
77 | ));
78 | });
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/example/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: example
2 | description: flutter_sticky_and_expandable_list example project.
3 |
4 | # The following defines the version and build number for your application.
5 | # A version number is three numbers separated by dots, like 1.2.43
6 | # followed by an optional build number separated by a +.
7 | # Both the version and the builder number may be overridden in flutter
8 | # build by specifying --build-name and --build-number, respectively.
9 | # In Android, build-name is used as versionName while build-number used as versionCode.
10 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning
11 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
12 | # Read more about iOS versioning at
13 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
14 | version: 1.0.0+1
15 | publish_to: none
16 |
17 | environment:
18 | sdk: '>=2.12.0 <3.0.0'
19 |
20 | dependencies:
21 | flutter:
22 | sdk: flutter
23 |
24 | # The following adds the Cupertino Icons font to your application.
25 | # Use with the CupertinoIcons class for iOS style icons.
26 | # cupertino_icons: ^0.1.2
27 | sticky_and_expandable_list:
28 | path: ../
29 | scroll_to_index: ^3.0.0
30 | pull_to_refresh: ^2.0.0
31 |
32 | dev_dependencies:
33 | integration_test:
34 | sdk: flutter
35 | flutter_test:
36 | sdk: flutter
37 | flutter_driver:
38 | sdk: flutter
39 |
40 | # For information on the generic Dart part of this file, see the
41 | # following page: https://dart.dev/tools/pub/pubspec
42 |
43 | # The following section is specific to Flutter.
44 | flutter:
45 |
46 | # The following line ensures that the Material Icons font is
47 | # included with your application, so that you can use the icons in
48 | # the material Icons class.
49 | uses-material-design: true
50 |
51 | # To add assets to your application, add an assets section, like this:
52 | # assets:
53 | # - images/a_dot_burr.jpeg
54 | # - images/a_dot_ham.jpeg
55 |
56 | # An image asset can refer to one or more resolution-specific "variants", see
57 | # https://flutter.dev/assets-and-images/#resolution-aware.
58 |
59 | # For details regarding adding assets from package dependencies, see
60 | # https://flutter.dev/assets-and-images/#from-packages
61 |
62 | # To add custom fonts to your application, add a fonts section here,
63 | # in this "flutter" section. Each entry in this list should have a
64 | # "family" key with the font family name, and a "fonts" key with a
65 | # list giving the asset and other descriptors for the font. For
66 | # example:
67 | # fonts:
68 | # - family: Schyler
69 | # fonts:
70 | # - asset: fonts/Schyler-Regular.ttf
71 | # - asset: fonts/Schyler-Italic.ttf
72 | # style: italic
73 | # - family: Trajan Pro
74 | # fonts:
75 | # - asset: fonts/TrajanPro.ttf
76 | # - asset: fonts/TrajanPro_Bold.ttf
77 | # weight: 700
78 | #
79 | # For details regarding fonts from package dependencies,
80 | # see https://flutter.dev/custom-fonts/#from-packages
81 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "20x20",
5 | "idiom" : "iphone",
6 | "filename" : "Icon-App-20x20@2x.png",
7 | "scale" : "2x"
8 | },
9 | {
10 | "size" : "20x20",
11 | "idiom" : "iphone",
12 | "filename" : "Icon-App-20x20@3x.png",
13 | "scale" : "3x"
14 | },
15 | {
16 | "size" : "29x29",
17 | "idiom" : "iphone",
18 | "filename" : "Icon-App-29x29@1x.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "29x29",
23 | "idiom" : "iphone",
24 | "filename" : "Icon-App-29x29@2x.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "29x29",
29 | "idiom" : "iphone",
30 | "filename" : "Icon-App-29x29@3x.png",
31 | "scale" : "3x"
32 | },
33 | {
34 | "size" : "40x40",
35 | "idiom" : "iphone",
36 | "filename" : "Icon-App-40x40@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "40x40",
41 | "idiom" : "iphone",
42 | "filename" : "Icon-App-40x40@3x.png",
43 | "scale" : "3x"
44 | },
45 | {
46 | "size" : "60x60",
47 | "idiom" : "iphone",
48 | "filename" : "Icon-App-60x60@2x.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "60x60",
53 | "idiom" : "iphone",
54 | "filename" : "Icon-App-60x60@3x.png",
55 | "scale" : "3x"
56 | },
57 | {
58 | "size" : "20x20",
59 | "idiom" : "ipad",
60 | "filename" : "Icon-App-20x20@1x.png",
61 | "scale" : "1x"
62 | },
63 | {
64 | "size" : "20x20",
65 | "idiom" : "ipad",
66 | "filename" : "Icon-App-20x20@2x.png",
67 | "scale" : "2x"
68 | },
69 | {
70 | "size" : "29x29",
71 | "idiom" : "ipad",
72 | "filename" : "Icon-App-29x29@1x.png",
73 | "scale" : "1x"
74 | },
75 | {
76 | "size" : "29x29",
77 | "idiom" : "ipad",
78 | "filename" : "Icon-App-29x29@2x.png",
79 | "scale" : "2x"
80 | },
81 | {
82 | "size" : "40x40",
83 | "idiom" : "ipad",
84 | "filename" : "Icon-App-40x40@1x.png",
85 | "scale" : "1x"
86 | },
87 | {
88 | "size" : "40x40",
89 | "idiom" : "ipad",
90 | "filename" : "Icon-App-40x40@2x.png",
91 | "scale" : "2x"
92 | },
93 | {
94 | "size" : "76x76",
95 | "idiom" : "ipad",
96 | "filename" : "Icon-App-76x76@1x.png",
97 | "scale" : "1x"
98 | },
99 | {
100 | "size" : "76x76",
101 | "idiom" : "ipad",
102 | "filename" : "Icon-App-76x76@2x.png",
103 | "scale" : "2x"
104 | },
105 | {
106 | "size" : "83.5x83.5",
107 | "idiom" : "ipad",
108 | "filename" : "Icon-App-83.5x83.5@2x.png",
109 | "scale" : "2x"
110 | },
111 | {
112 | "size" : "1024x1024",
113 | "idiom" : "ios-marketing",
114 | "filename" : "Icon-App-1024x1024@1x.png",
115 | "scale" : "1x"
116 | }
117 | ],
118 | "info" : {
119 | "version" : 1,
120 | "author" : "xcode"
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/example/lib/example_animatable_header.dart:
--------------------------------------------------------------------------------
1 | import 'package:example/mock_data.dart';
2 | import 'package:example/widgets.dart';
3 | import 'package:flutter/material.dart';
4 | import 'package:sticky_and_expandable_list/sticky_and_expandable_list.dart';
5 |
6 | class ExampleAnimatableHeader extends StatefulWidget {
7 | @override
8 | _ExampleAnimatableHeaderState createState() =>
9 | _ExampleAnimatableHeaderState();
10 | }
11 |
12 | class _ExampleAnimatableHeaderState extends State {
13 | var sectionList = MockData.getExampleSections();
14 | var _controller = ExpandableListController();
15 |
16 | @override
17 | void initState() {
18 | super.initState();
19 | }
20 |
21 | @override
22 | void dispose() {
23 | _controller.dispose();
24 | super.dispose();
25 | }
26 |
27 | @override
28 | Widget build(BuildContext context) {
29 | return Scaffold(
30 | appBar: CustomAppBar(title: "Animatable Header Example"),
31 | body: CustomScrollView(
32 | slivers: [
33 | SliverToBoxAdapter(
34 | child: Container(
35 | height: 100,
36 | width: 100,
37 | color: Colors.white,
38 | alignment: Alignment.center,
39 | child: Text(
40 | "PlaceHolder",
41 | style: TextStyle(color: Colors.black),
42 | ),
43 | )),
44 | SliverExpandableList(
45 | builder: SliverExpandableChildDelegate(
46 | sectionList: sectionList,
47 | headerBuilder: _buildHeader,
48 | controller: _controller,
49 | itemBuilder: (context, sectionIndex, itemIndex, index) {
50 | String item = sectionList[sectionIndex].items[itemIndex];
51 | return ListTile(
52 | leading: CircleAvatar(
53 | child: Text("$index"),
54 | ),
55 | title: Text(item),
56 | );
57 | }),
58 | )
59 | ],
60 | ),
61 | );
62 | }
63 |
64 | Widget _buildHeader(BuildContext context, int sectionIndex, int index) {
65 | var section = sectionList[sectionIndex];
66 | return ExpandableAutoLayoutWidget(
67 | trigger: ExpandableDefaultAutoLayoutTrigger(_controller),
68 | builder: (context) {
69 | double opacity = _controller.switchingSectionIndex == sectionIndex
70 | ? (1 - _controller.percent)
71 | : 1;
72 | String headerText = section.header;
73 | if (_controller.switchingSectionIndex == sectionIndex) {
74 | headerText += " Switching";
75 | } else if (_controller.stickySectionIndex == sectionIndex) {
76 | headerText += " Pinned";
77 | }
78 | return Container(
79 | color: Colors.lightBlue.withOpacity(opacity),
80 | height: 48,
81 | padding: EdgeInsets.only(left: 20),
82 | alignment: Alignment.centerLeft,
83 | child: Text(
84 | headerText,
85 | style: TextStyle(color: Colors.white),
86 | ));
87 | },
88 | );
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/example/lib/example_custom_section.dart:
--------------------------------------------------------------------------------
1 | import 'package:example/mock_data.dart';
2 | import 'package:example/widgets.dart';
3 | import 'package:flutter/material.dart';
4 | import 'package:sticky_and_expandable_list/sticky_and_expandable_list.dart';
5 |
6 | class ExampleCustomSection extends StatefulWidget {
7 | @override
8 | _ExampleCustomSectionState createState() => _ExampleCustomSectionState();
9 | }
10 |
11 | class _ExampleCustomSectionState extends State {
12 | var sectionList = MockData.getExampleSections();
13 |
14 | @override
15 | Widget build(BuildContext context) {
16 | return Scaffold(
17 | appBar: CustomAppBar(title: "CustomSection Example"),
18 | body: ExpandableListView(
19 | builder: SliverExpandableChildDelegate(
20 | sectionList: sectionList,
21 | sectionBuilder: _buildSection,
22 | itemBuilder: (context, sectionIndex, itemIndex, index) {
23 | String item = sectionList[sectionIndex].items[itemIndex];
24 | return SizedBox(
25 | child: ListTile(
26 | leading: CircleAvatar(
27 | child: Text("$index"),
28 | ),
29 | title: Text(item),
30 | ),
31 | );
32 | }),
33 | ));
34 | }
35 |
36 | Widget _buildSection(
37 | BuildContext context, ExpandableSectionContainerInfo containerInfo) {
38 | containerInfo
39 | ..header = _buildHeader(context, containerInfo)
40 | ..content = _buildContent(context, containerInfo);
41 | return ExpandableSectionContainer(
42 | info: containerInfo,
43 | );
44 | }
45 |
46 | Widget _buildHeader(
47 | BuildContext context, ExpandableSectionContainerInfo containerInfo) {
48 | ExampleSection section = sectionList[containerInfo.sectionIndex];
49 | return InkWell(
50 | child: Container(
51 | color: Colors.lightBlue,
52 | height: 48,
53 | padding: EdgeInsets.only(left: 20),
54 | alignment: Alignment.centerLeft,
55 | child: Text(
56 | section.header,
57 | style: TextStyle(color: Colors.white),
58 | )),
59 | onTap: () {
60 | //toggle section expand state
61 | setState(() {
62 | section.setSectionExpanded(!section.isSectionExpanded());
63 | });
64 | });
65 | }
66 |
67 | Widget _buildContent(
68 | BuildContext context, ExpandableSectionContainerInfo containerInfo) {
69 | ExampleSection section = sectionList[containerInfo.sectionIndex];
70 | if (!section.isSectionExpanded()) {
71 | return Container();
72 | }
73 | return GridView.builder(
74 | gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
75 | crossAxisCount: 2,
76 | childAspectRatio: 3,
77 | ),
78 | shrinkWrap: true,
79 | physics: NeverScrollableScrollPhysics(),
80 | itemBuilder: containerInfo.childDelegate!.builder as Widget Function(
81 | BuildContext, int),
82 | itemCount: containerInfo.childDelegate!.childCount,
83 | );
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
41 |
42 |
52 |
54 |
60 |
61 |
62 |
63 |
69 |
71 |
77 |
78 |
79 |
80 |
82 |
83 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/example/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:example/example_custom_section_animation.dart';
4 | import 'package:example/example_nested_scroll_view.dart';
5 | import 'package:example/example_pull_to_refresh.dart';
6 | import 'package:example/example_scroll_to_index.dart';
7 | import 'package:example/example_side_header.dart';
8 | import 'package:example/route_path.dart';
9 | import 'package:example/widgets.dart';
10 | import 'package:flutter/material.dart';
11 | import 'package:flutter/services.dart';
12 |
13 | import 'example_animatable_header.dart';
14 | import 'example_custom_section.dart';
15 | import 'example_listview.dart';
16 | import 'example_sliver.dart';
17 | import 'example_nested_listview.dart';
18 |
19 | void main() {
20 | runApp(MyApp());
21 | if (Platform.isAndroid) {
22 | //set statusBar color
23 | var overlayStyle = SystemUiOverlayStyle(statusBarColor: Colors.blue);
24 | SystemChrome.setSystemUIOverlayStyle(overlayStyle);
25 | }
26 | }
27 |
28 | class MyApp extends StatelessWidget {
29 | // This widget is the root of your application.
30 | @override
31 | Widget build(BuildContext context) {
32 | return MaterialApp(
33 | title: 'Flutter sticky and expandable list',
34 | theme: ThemeData(
35 | primarySwatch: Colors.lightBlue,
36 | ),
37 | routes: _buildRoutes(),
38 | );
39 | }
40 |
41 | Map _buildRoutes() {
42 | return {
43 | RoutePath.home: (context) => _HomePage(),
44 | RoutePath.listView: (context) => ExampleListView(),
45 | RoutePath.sliver: (context) => ExampleSliver(),
46 | RoutePath.animatableHeader: (context) => ExampleAnimatableHeader(),
47 | RoutePath.customSection: (context) => ExampleCustomSection(),
48 | RoutePath.nestedListView: (context) => ExampleNestedListView(),
49 | RoutePath.customSectionAnimation: (context) =>
50 | ExampleCustomSectionAnimation(),
51 | RoutePath.nestedScrollView: (context) => ExampleNestedScrollView(),
52 | RoutePath.sideHeader: (context) => ExampleSideHeader(),
53 | RoutePath.scrollToIndex: (context) => ExampleScrollToIndex(),
54 | RoutePath.pullToRefresh: (context) => ExamplePullToRefresh(),
55 | };
56 | }
57 | }
58 |
59 | class _HomePage extends StatelessWidget {
60 | @override
61 | Widget build(BuildContext context) {
62 | return Scaffold(
63 | appBar: CustomAppBar(title: "Flutter sticky and expandable list"),
64 | body: ListView(
65 | children: [
66 | _Item("ListView Example", RoutePath.listView),
67 | _Item("Sliver Example", RoutePath.sliver),
68 | _Item("Animatable Header Example", RoutePath.animatableHeader),
69 | _Item("CustomSection Example", RoutePath.customSection),
70 | _Item("NestedListView Example", RoutePath.nestedListView),
71 | _Item("CustomSectionAnimation Example",
72 | RoutePath.customSectionAnimation),
73 | _Item("NestedScrollView Example", RoutePath.nestedScrollView),
74 | _Item("Side Header Example", RoutePath.sideHeader),
75 | _Item("ScrollToIndex Example", RoutePath.scrollToIndex),
76 | _Item("PullToRefresh Example", RoutePath.pullToRefresh),
77 | ],
78 | ),
79 | );
80 | }
81 | }
82 |
83 | class _Item extends StatelessWidget {
84 | final String jumpUrl;
85 | final String title;
86 |
87 | _Item(this.title, this.jumpUrl);
88 |
89 | @override
90 | Widget build(BuildContext context) {
91 | return Card(
92 | key: ValueKey(jumpUrl),
93 | color: Theme.of(context).primaryColor,
94 | child: TextButton(
95 | onPressed: () => Navigator.of(context).pushNamed(jumpUrl),
96 | child: Text(
97 | title,
98 | style: TextStyle(color: Colors.white, fontSize: 18),
99 | ),
100 | ),
101 | );
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/README_zh_CN.md:
--------------------------------------------------------------------------------
1 | # sticky_and_expandable_list
2 |
3 | 可拆叠列表的 Flutter 实现,支持粘性头部,可以与 Sliver 家族控件配合使用。
4 |
5 | [](https://pub.dartlang.org/packages/sticky_and_expandable_list)
6 |
7 | 
8 |
9 | ## 特性
10 |
11 | - 支持构建可切换拆叠/展开状态的 ListView,支持粘性头部。
12 | - 可以与 Sliver 家族控件配合使用,用在如 CustomScrollView、NestedScrollView 中。
13 | - 通过 controller 监听当前粘性头部的滚动偏移量,当前被固定 header 的索引和正在隐藏/显示的 header 的索引值。
14 | - 整个列表是一个类似 ListView 的控件,Builder 方式创建分组项,所以支持大量数据显示,不会把所有分组全部创建出来。
15 | - 可使用 sectionBuilder 进行更多 section 控件定制,如背景、自定义折叠/展开动画、不同 section 的布局方式定制等。
16 | - 支持添加分隔线。
17 | - 支持标题覆盖内容。
18 | - 支持使用[scroll-to-index](https://github.com/quire-io/scroll-to-index)滚动列表到指定位置。
19 | - 支持使用[pull_to_refresh](https://github.com/peng8350/flutter_pulltorefresh)之类库进行下拉刷新与加载更多。
20 |
21 |
22 | ## 开始
23 |
24 | 在 Flutter 项目中的 `pubspec.yaml` 文件中添加如下依赖。
25 |
26 | ```yaml
27 | dependencies:
28 | sticky_and_expandable_list: ^1.1.3
29 | ```
30 |
31 | ## 基础使用示例
32 | `sectionList`是`ExpandableListView`的数据源,需要使用者自己定义。
33 | 我们需要创建一个模型类列表去存储每个组的信息,这个模型类需要实现`ExpandableListSection`接口。
34 | ```dart
35 | //在这个示例中,创建了一个自定义的Section类(ExampleSection)。
36 | //class ExampleSection implements ExpandableListSection {}
37 | //so: SliverExpandableChildDelegate()
38 | List sectionList = List();
39 | return ExpandableListView(
40 | builder: SliverExpandableChildDelegate(
41 | sectionList: sectionList,
42 | headerBuilder: (context, sectionIndex, index) =>
43 | Text("Header #$sectionIndex"),
44 | itemBuilder: (context, sectionIndex, itemIndex, index) {
45 | String item = sectionList[sectionIndex].items[itemIndex];
46 | return ListTile(
47 | leading: CircleAvatar(
48 | child: Text("$index"),
49 | ),
50 | title: Text(item),
51 | );
52 | }),
53 | );
54 | ```
55 |
56 | [详细示例](https://github.com/tp7309/flutter_sticky_and_expandable_list/tree/master/example)
57 |
58 | 如果是与Sliver控件一起使用的话,使用**SliverExpandableList** 来代替ExpandableListView.
59 |
60 | ## 常见问题
61 |
62 | ### 如何切换列表的展开/拆叠状态?
63 |
64 | ```dart
65 | setState(() {
66 | sectionList[i].setSectionExpanded(true);
67 | });
68 | ```
69 |
70 | [Example](https://github.com/tp7309/flutter_sticky_and_expandable_list/blob/master/example/lib/example_listview.dart)
71 |
72 | ### 如何监听当前粘性头部的滚动偏移量?如何得知哪个 Header 是粘性头部?
73 |
74 | ```dart
75 | @override
76 | Widget build(BuildContext context) {
77 | ExpandableListView(
78 | builder: SliverExpandableChildDelegate(
79 | headerController: _getHeaderController(),
80 | ),
81 | )
82 | }
83 |
84 | _getHeaderController() {
85 | var controller = ExpandableListController();
86 | controller.addListener(() {
87 | print("switchingSectionIndex:${controller.switchingSectionIndex}, stickySectionIndex:" +
88 | "${controller.stickySectionIndex},scrollPercent:${controller.percent}");
89 | });
90 | return controller;
91 | }
92 | ```
93 |
94 | ### 如何定制每一组数据的背景、阴影等信息?
95 |
96 | 使用[sectionBuilder](https://github.com/tp7309/flutter_sticky_and_expandable_list/blob/master/example/lib/example_custom_section_animation.dart)
97 | 返回自定义的 Widget.
98 |
99 | ### 自定义折叠动画
100 |
101 | 使用 Flutter 自带动画进行定制:
102 | [Example](https://github.com/tp7309/flutter_sticky_and_expandable_list/blob/master/example/lib/example_custom_section_animation.dart)
103 |
104 | ## 更新日志
105 |
106 | [CHANGELOG](https://github.com/tp7309/flutter_sticky_and_expandable_list/blob/master/CHANGELOG.md)
107 |
108 | ## 支持
109 |
110 | 觉得对有帮助,请作者喝杯咖啡 (微信):
111 |
112 | 
113 |
--------------------------------------------------------------------------------
/example/lib/example_scroll_to_index.dart:
--------------------------------------------------------------------------------
1 | import 'package:example/mock_data.dart';
2 | import 'package:example/widgets.dart';
3 | import 'package:flutter/material.dart';
4 | import 'package:scroll_to_index/scroll_to_index.dart';
5 | import 'package:sticky_and_expandable_list/sticky_and_expandable_list.dart';
6 |
7 | class ExampleScrollToIndex extends StatefulWidget {
8 | @override
9 | _ExampleScrollToIndexState createState() => _ExampleScrollToIndexState();
10 | }
11 |
12 | class _ExampleScrollToIndexState extends State {
13 | var sectionList = MockData.getExampleSections(10, 5);
14 |
15 | late AutoScrollController scrollController;
16 | int counter = 0;
17 | int maxCount = 10 * (5 + 1) - 1;
18 |
19 | @override
20 | void initState() {
21 | super.initState();
22 | scrollController = AutoScrollController(
23 | viewportBoundaryGetter: () =>
24 | Rect.fromLTRB(0, 0, 0, MediaQuery.of(context).padding.bottom),
25 | axis: Axis.vertical);
26 | }
27 |
28 | @override
29 | Widget build(BuildContext context) {
30 | //In this example, we create a custom model class(ExampleSection).
31 | //class ExampleSection implements ExpandableListSection {}
32 | //so: SliverExpandableChildDelegate()
33 | return Scaffold(
34 | appBar: CustomAppBar(title: "ListView Example"),
35 | body: ExpandableListView(
36 | controller: scrollController,
37 | builder: SliverExpandableChildDelegate(
38 | sectionList: sectionList,
39 | headerBuilder: _buildHeader,
40 | itemBuilder: (context, sectionIndex, itemIndex, index) {
41 | String item = sectionList[sectionIndex].items[itemIndex];
42 | return _wrapScrollTag(
43 | index: index,
44 | child: ListTile(
45 | leading: CircleAvatar(
46 | child: Text("$index"),
47 | ),
48 | title: Text(item),
49 | ),
50 | );
51 | }),
52 | ),
53 | floatingActionButton: InkWell(
54 | onTap: _scrollToIndex,
55 | child: Container(
56 | height: 48,
57 | padding: EdgeInsets.all(10),
58 | alignment: Alignment.center,
59 | color: Colors.blueGrey,
60 | child: Text("Click Me: ${counter.toString()}", style: TextStyle(color: Colors.white),),
61 | ),
62 | ),
63 | );
64 | }
65 |
66 | Widget _buildHeader(BuildContext context, int sectionIndex, int index) {
67 | ExampleSection section = sectionList[sectionIndex];
68 | return _wrapScrollTag(
69 | index: index,
70 | child: InkWell(
71 | child: Container(
72 | color: Colors.lightBlue,
73 | height: 48,
74 | padding: EdgeInsets.only(left: 20),
75 | alignment: Alignment.centerLeft,
76 | child: Text(
77 | section.header,
78 | style: TextStyle(color: Colors.white),
79 | )),
80 | onTap: () {
81 | //toggle section expand state
82 | setState(() {
83 | section.setSectionExpanded(!section.isSectionExpanded());
84 | });
85 | }),
86 | );
87 | }
88 |
89 | Future _scrollToIndex() async {
90 | setState(() {
91 | counter++;
92 | if (counter >= maxCount) counter = 0;
93 | });
94 |
95 | await scrollController.scrollToIndex(counter,
96 | preferPosition: AutoScrollPosition.begin);
97 | scrollController.highlight(counter);
98 | }
99 |
100 | Widget _wrapScrollTag({required int index, required Widget child}) =>
101 | AutoScrollTag(
102 | key: ValueKey(index),
103 | controller: scrollController,
104 | index: index,
105 | child: child,
106 | highlightColor: Colors.black.withOpacity(0.1),
107 | );
108 | }
109 |
--------------------------------------------------------------------------------
/example/web/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | example
33 |
34 |
35 |
36 |
44 |
47 |
111 |
112 |
113 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # sticky_and_expandable_list
2 | Flutter implementation of sticky headers and expandable list.Support use it in a CustomScrollView.
3 |
4 | [](https://pub.dartlang.org/packages/sticky_and_expandable_list)
5 | README i18n:[中文说明](https://github.com/tp7309/flutter_sticky_and_expandable_list/blob/master/README_zh_CN.md)
6 |
7 | 
8 |
9 | ## Features
10 |
11 | - Build a grouped list, which support expand/collapse section and sticky header.
12 | - Use it with CustomScrollView、SliverAppBar.
13 | - Listen the scroll offset of current sticky header, current sticky header index and switching header index.
14 | - Only use one list widget, so it supports large data and a normal memory usage.
15 | - More section customization support, you can return a new section widget by sectionBuilder, to customize background,expand/collapse animation, section layout, and so on.
16 | - Support add divider.
17 | - Support overlap content.
18 | - Support scroll to index like ListView, by [scroll-to-index](https://github.com/quire-io/scroll-to-index).
19 | - Pull to refresh and load more, by [pull_to_refresh](https://github.com/peng8350/flutter_pulltorefresh).
20 |
21 | ## Getting Started
22 |
23 | In the `pubspec.yaml` of your flutter project, add the following dependency:
24 |
25 | ```yaml
26 | dependencies:
27 | sticky_and_expandable_list: ^1.1.3
28 | ```
29 |
30 | ## Basic Usage
31 | `sectionList` is a custom data source for `ExpandableListView`.
32 | We should create a model list to store the information of each section, the model must implement `ExpandableListSection`.
33 | ```dart
34 | //In this example, we create a custom model class(ExampleSection).
35 | //class ExampleSection implements ExpandableListSection {}
36 | //so: SliverExpandableChildDelegate()
37 | List sectionList = List();
38 | return ExpandableListView(
39 | builder: SliverExpandableChildDelegate(
40 | sectionList: sectionList,
41 | headerBuilder: (context, sectionIndex, index) =>
42 | Text("Header #$sectionIndex"),
43 | itemBuilder: (context, sectionIndex, itemIndex, index) {
44 | String item = sectionList[sectionIndex].items[itemIndex];
45 | return ListTile(
46 | leading: CircleAvatar(
47 | child: Text("$index"),
48 | ),
49 | title: Text(item),
50 | );
51 | }),
52 | );
53 | ```
54 |
55 | [Detail Examples](https://github.com/tp7309/flutter_sticky_and_expandable_list/tree/master/example/lib)
56 |
57 | If you want to use it with sliver widget, use **SliverExpandableList** instead of ExpandableListView.
58 |
59 | ## FAQ
60 |
61 | ### How to expand/collapse item?
62 |
63 | ```dart
64 | setState(() {
65 | sectionList[i].setSectionExpanded(true);
66 | });
67 | ```
68 |
69 | [Example](https://github.com/tp7309/flutter_sticky_and_expandable_list/blob/master/example/lib/example_listview.dart)
70 |
71 | ### How to listen current sticky header or the sticky header scroll offset?
72 |
73 | ```dart
74 | @override
75 | Widget build(BuildContext context) {
76 | ExpandableListView(
77 | builder: SliverExpandableChildDelegate(
78 | headerController: _getHeaderController(),
79 | ),
80 | )
81 | }
82 |
83 | _getHeaderController() {
84 | var controller = ExpandableListController();
85 | controller.addListener(() {
86 | print("switchingSectionIndex:${controller.switchingSectionIndex}, stickySectionIndex:" +
87 | "${controller.stickySectionIndex},scrollPercent:${controller.percent}");
88 | });
89 | return controller;
90 | }
91 | ```
92 |
93 | ### How to set background for each section?
94 |
95 | Use [sectionBuilder](https://github.com/tp7309/flutter_sticky_and_expandable_list/blob/master/example/lib/example_custom_section_animation.dart)
96 |
97 | ### Customize expand/collapse animation support?
98 |
99 | [Example](https://github.com/tp7309/flutter_sticky_and_expandable_list/blob/master/example/lib/example_custom_section_animation.dart)
100 |
101 | ## Change Log
102 |
103 | [CHANGELOG](https://github.com/tp7309/flutter_sticky_and_expandable_list/blob/master/CHANGELOG.md)
104 |
105 | ## Donate
106 |
107 | Buy a cup of coffee for me (Scan by wechat):
108 |
109 | 
110 |
111 |
112 |
--------------------------------------------------------------------------------
/example/lib/example_custom_section_animation.dart:
--------------------------------------------------------------------------------
1 | import 'package:example/widgets.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:sticky_and_expandable_list/sticky_and_expandable_list.dart';
4 |
5 | import 'mock_data.dart';
6 |
7 | class ExampleCustomSectionAnimation extends StatefulWidget {
8 | @override
9 | _ExampleCustomSectionAnimationState createState() =>
10 | _ExampleCustomSectionAnimationState();
11 | }
12 |
13 | class _ExampleCustomSectionAnimationState
14 | extends State {
15 | var sectionList = MockData.getExampleSections(3, 3);
16 |
17 | @override
18 | Widget build(BuildContext context) {
19 | return Scaffold(
20 | appBar: CustomAppBar(title: "CustomSectionAnimation Example"),
21 | body: ExpandableListView(
22 | builder: SliverExpandableChildDelegate(
23 | sectionList: sectionList,
24 | itemBuilder: (context, sectionIndex, itemIndex, index) {
25 | String item = sectionList[sectionIndex].items[itemIndex];
26 | return ListTile(
27 | leading: CircleAvatar(
28 | child: Text("$index"),
29 | ),
30 | title: Text(item),
31 | );
32 | },
33 | sectionBuilder: (context, containerInfo) => _SectionWidget(
34 | section: sectionList[containerInfo.sectionIndex],
35 | containerInfo: containerInfo,
36 | onStateChanged: () {
37 | //notify ExpandableListView that expand state has changed.
38 | WidgetsBinding.instance.addPostFrameCallback((_) {
39 | if (mounted) {
40 | setState(() {});
41 | }
42 | });
43 | },
44 | ),
45 | ),
46 | ));
47 | }
48 | }
49 |
50 | class _SectionWidget extends StatefulWidget {
51 | final ExampleSection section;
52 | final ExpandableSectionContainerInfo containerInfo;
53 | final VoidCallback onStateChanged;
54 |
55 | _SectionWidget(
56 | {required this.section,
57 | required this.containerInfo,
58 | required this.onStateChanged});
59 |
60 | @override
61 | __SectionWidgetState createState() => __SectionWidgetState();
62 | }
63 |
64 | class __SectionWidgetState extends State<_SectionWidget>
65 | with SingleTickerProviderStateMixin {
66 | static final Animatable _halfTween =
67 | Tween(begin: 0.0, end: 0.5);
68 | late AnimationController _controller;
69 |
70 | late Animation _iconTurns;
71 |
72 | late Animation _heightFactor;
73 |
74 | @override
75 | void initState() {
76 | super.initState();
77 | _controller =
78 | AnimationController(vsync: this, duration: Duration(milliseconds: 300));
79 | _iconTurns =
80 | _controller.drive(_halfTween.chain(CurveTween(curve: Curves.easeIn)));
81 | _heightFactor = _controller.drive(CurveTween(curve: Curves.easeIn));
82 |
83 | if (widget.section.isSectionExpanded()) {
84 | _controller.value = 1;
85 | }
86 | }
87 |
88 | @override
89 | void dispose() {
90 | _controller.dispose();
91 | super.dispose();
92 | }
93 |
94 | @override
95 | Widget build(BuildContext context) {
96 | widget.containerInfo
97 | ..header = _buildHeader(context)
98 | ..content = _buildContent(context);
99 | return ExpandableSectionContainer(
100 | info: widget.containerInfo,
101 | );
102 | }
103 |
104 | Widget _buildHeader(BuildContext context) {
105 | return Container(
106 | color: Colors.lightBlue,
107 | child: ListTile(
108 | title: Text(
109 | widget.section.header,
110 | style: TextStyle(color: Colors.white),
111 | ),
112 | trailing: RotationTransition(
113 | turns: _iconTurns as Animation,
114 | child: const Icon(
115 | Icons.expand_more,
116 | color: Colors.white70,
117 | ),
118 | ),
119 | onTap: _onTap,
120 | ),
121 | );
122 | }
123 |
124 | void _onTap() {
125 | widget.section.setSectionExpanded(!widget.section.isSectionExpanded());
126 | if (widget.section.isSectionExpanded()) {
127 | widget.onStateChanged();
128 | _controller.forward();
129 | } else {
130 | _controller.reverse().then((_) {
131 | widget.onStateChanged();
132 | });
133 | }
134 | }
135 |
136 | Widget _buildContent(BuildContext context) {
137 | return SizeTransition(
138 | sizeFactor: _heightFactor,
139 | child: SliverExpandableChildDelegate.buildDefaultContent(
140 | context, widget.containerInfo),
141 | );
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/example/lib/example_nested_listview.dart:
--------------------------------------------------------------------------------
1 | import 'package:example/mock_data.dart';
2 | import 'package:example/widgets.dart';
3 | import 'package:flutter/gestures.dart';
4 | import 'package:flutter/material.dart';
5 | import 'package:sticky_and_expandable_list/sticky_and_expandable_list.dart';
6 | import 'package:example/example_custom_section.dart';
7 |
8 | ///if you want user ListView inside ExpandableListView, you have two options:
9 | ///
10 | ///Option 1:
11 | ///use shrinkWrap:true, like [ExampleCustomSection]
12 | ///
13 | ///Option 2:
14 | ///wrap ListView with SizeBox/Container, like [ExampleNestedListView], fixed length.
15 | ///
16 | class ExampleNestedListView extends StatefulWidget {
17 | @override
18 | _ExampleNestedListViewState createState() => _ExampleNestedListViewState();
19 | }
20 |
21 | class _ExampleNestedListViewState extends State {
22 | var sectionList = MockData.getExampleSections(10, 20);
23 |
24 | late ScrollController _scrollListener;
25 |
26 | Drag? drag;
27 |
28 | DragStartDetails? dragStartDetails;
29 |
30 | @override
31 | void initState() {
32 | super.initState();
33 | _scrollListener = new ScrollController();
34 | }
35 |
36 | @override
37 | void dispose() {
38 | _scrollListener.dispose();
39 | super.dispose();
40 | }
41 |
42 | @override
43 | Widget build(BuildContext context) {
44 | return Scaffold(
45 | appBar: CustomAppBar(title: "NestedListView Example"),
46 | body: ExpandableListView(
47 | controller: _scrollListener,
48 | builder: SliverExpandableChildDelegate(
49 | sectionList: sectionList,
50 | sectionBuilder: _buildSection,
51 | itemBuilder: (context, sectionIndex, itemIndex, index) {
52 | String item = sectionList[sectionIndex].items[itemIndex];
53 | return Container(
54 | color: Colors.white12,
55 | child: ListTile(
56 | leading: CircleAvatar(
57 | child: Text("$index"),
58 | ),
59 | title: Text(item),
60 | ),
61 | );
62 | }),
63 | ));
64 | }
65 |
66 | Widget _buildSection(
67 | BuildContext context, ExpandableSectionContainerInfo containerInfo) {
68 | containerInfo
69 | ..header = _buildHeader(containerInfo)
70 | ..content = _buildContent(context, containerInfo);
71 | return ExpandableSectionContainer(
72 | info: containerInfo,
73 | );
74 | }
75 |
76 | Widget _buildHeader(ExpandableSectionContainerInfo containerInfo) {
77 | ExampleSection section = sectionList[containerInfo.sectionIndex];
78 | return InkWell(
79 | child: Container(
80 | color: Colors.lightBlue,
81 | height: 48,
82 | padding: EdgeInsets.only(left: 20),
83 | alignment: Alignment.centerLeft,
84 | child: Text(
85 | section.header,
86 | style: TextStyle(color: Colors.white),
87 | )),
88 | onTap: () {
89 | //toggle section expand state
90 | setState(() {
91 | section.setSectionExpanded(!section.isSectionExpanded());
92 | });
93 | });
94 | }
95 |
96 | Widget _buildContent(
97 | BuildContext context, ExpandableSectionContainerInfo containerInfo) {
98 | ExampleSection section = sectionList[containerInfo.sectionIndex];
99 | if (!section.isSectionExpanded()) {
100 | return Container();
101 | }
102 | return Container(
103 | height: 300,
104 | child: NotificationListener(
105 | onNotification: _onNotification,
106 | child: ListView.builder(
107 | physics: AlwaysScrollableScrollPhysics(),
108 | itemBuilder: containerInfo.childDelegate!.builder as Widget Function(
109 | BuildContext, int),
110 | itemCount: containerInfo.childDelegate!.childCount,
111 | ),
112 | ),
113 | );
114 | }
115 |
116 | bool _onNotification(ScrollNotification notification) {
117 | var metrics = notification.metrics;
118 | if (notification is ScrollEndNotification) {
119 | drag = null;
120 | }
121 | if (metrics.axis == Axis.horizontal) {
122 | return true;
123 | }
124 | if (notification is ScrollStartNotification) {
125 | drag = null;
126 | dragStartDetails = notification.dragDetails;
127 | }
128 | if (notification is UserScrollNotification) {
129 | if (metrics.pixels <= metrics.minScrollExtent) {
130 | if (drag == null && dragStartDetails != null) {
131 | drag = _scrollListener.position.drag(dragStartDetails!, () {
132 | drag = null;
133 | });
134 | }
135 | } else if (metrics.pixels >= metrics.maxScrollExtent) {
136 | if (drag == null && dragStartDetails != null) {
137 | drag = _scrollListener.position.drag(dragStartDetails!, () {
138 | drag = null;
139 | });
140 | }
141 | }
142 | }
143 | return true;
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/example/lib/example_pull_to_refresh.dart:
--------------------------------------------------------------------------------
1 | import 'package:example/mock_data.dart';
2 | import 'package:example/widgets.dart';
3 | import 'package:flutter/cupertino.dart';
4 | import 'package:flutter/material.dart';
5 | import 'package:flutter/physics.dart';
6 | import 'package:pull_to_refresh/pull_to_refresh.dart';
7 | import 'package:sticky_and_expandable_list/sticky_and_expandable_list.dart';
8 |
9 | //use library 'pull_to_refresh'
10 | //https://github.com/peng8350/flutter_pulltorefresh
11 | class ExamplePullToRefresh extends StatefulWidget {
12 | @override
13 | _ExamplePullToRefreshState createState() => _ExamplePullToRefreshState();
14 | }
15 |
16 | class _ExamplePullToRefreshState extends State {
17 | var sectionList = MockData.getExampleSections(5, 3);
18 |
19 | RefreshController _refreshController =
20 | RefreshController(initialRefresh: false);
21 |
22 | void _onRefresh() async {
23 | // monitor network fetch
24 | await Future.delayed(Duration(milliseconds: 1000));
25 | sectionList = MockData.getExampleSections(5, 3);
26 | if (mounted) setState(() {});
27 | // if failed,use refreshFailed()
28 | _refreshController.refreshCompleted();
29 | }
30 |
31 | void _onLoading() async {
32 | // monitor network fetch
33 | await Future.delayed(Duration(milliseconds: 1000));
34 | // if failed,use loadFailed(),if no data return,use LoadNodata()
35 | sectionList.addAll(MockData.getExampleSections(1, 1));
36 | if (mounted) setState(() {});
37 | _refreshController.loadComplete();
38 | }
39 |
40 | @override
41 | Widget build(BuildContext context) {
42 | return RefreshConfiguration(
43 | headerBuilder: () => WaterDropHeader(),
44 | // Configure the default header indicator. If you have the same header indicator for each page, you need to set this
45 | footerBuilder: () => ClassicFooter(),
46 | // Configure default bottom indicator
47 | headerTriggerDistance: 80.0,
48 | // header trigger refresh trigger distance
49 | springDescription:
50 | SpringDescription(stiffness: 170, damping: 16, mass: 1.9),
51 | // custom spring back animate,the props meaning see the flutter api
52 | maxOverScrollExtent: 100,
53 | //The maximum dragging range of the head. Set this property if a rush out of the view area occurs
54 | maxUnderScrollExtent: 0,
55 | // Maximum dragging range at the bottom
56 | enableScrollWhenRefreshCompleted: true,
57 | //This property is incompatible with PageView and TabBarView. If you need TabBarView to slide left and right, you need to set it to true.
58 | enableLoadingWhenFailed: true,
59 | //In the case of load failure, users can still trigger more loads by gesture pull-up.
60 | hideFooterWhenNotFull: false,
61 | // Disable pull-up to load more functionality when Viewport is less than one screen
62 | enableBallisticLoad: true,
63 | // trigger load more by BallisticScrollActivity
64 | child: Scaffold(
65 | appBar: CustomAppBar(title: "PullToRefresh Example"),
66 | body: SmartRefresher(
67 | enablePullDown: true,
68 | enablePullUp: true,
69 | header: ClassicHeader(),
70 | footer: CustomFooter(
71 | builder: (BuildContext context, LoadStatus? mode) {
72 | Widget body;
73 | if (mode == LoadStatus.idle) {
74 | body = Text("pull up load");
75 | } else if (mode == LoadStatus.loading) {
76 | body = CupertinoActivityIndicator();
77 | } else if (mode == LoadStatus.failed) {
78 | body = Text("Load Failed!Click retry!");
79 | } else if (mode == LoadStatus.canLoading) {
80 | body = Text("release to load more");
81 | } else {
82 | body = Text("No more Data");
83 | }
84 | return Container(
85 | height: 55.0,
86 | child: Center(child: body),
87 | );
88 | },
89 | ),
90 | controller: _refreshController,
91 | onRefresh: _onRefresh,
92 | onLoading: _onLoading,
93 | child: buildListView(context),
94 | ),
95 | ),
96 | );
97 | }
98 |
99 | Widget buildListView(BuildContext context) {
100 | //In this example, we create a custom model class(ExampleSection).
101 | //class ExampleSection implements ExpandableListSection {}
102 | //so: SliverExpandableChildDelegate()
103 | return ExpandableListView(
104 | builder: SliverExpandableChildDelegate(
105 | sectionList: sectionList,
106 | headerBuilder: _buildHeader,
107 | itemBuilder: (context, sectionIndex, itemIndex, index) {
108 | String item = sectionList[sectionIndex].items[itemIndex];
109 | return ListTile(
110 | leading: CircleAvatar(
111 | child: Text("$index"),
112 | ),
113 | title: Text(item),
114 | );
115 | }),
116 | );
117 | }
118 |
119 | Widget _buildHeader(BuildContext context, int sectionIndex, int index) {
120 | ExampleSection section = sectionList[sectionIndex];
121 | return InkWell(
122 | child: Container(
123 | color: Colors.lightBlue,
124 | height: 48,
125 | padding: EdgeInsets.only(left: 20),
126 | alignment: Alignment.centerLeft,
127 | child: Text(
128 | section.header,
129 | style: TextStyle(color: Colors.white),
130 | )),
131 | onTap: () {
132 | //toggle section expand state
133 | setState(() {
134 | section.setSectionExpanded(!section.isSectionExpanded());
135 | });
136 | });
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/example/lib/example_nested_scroll_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:sticky_and_expandable_list/sticky_and_expandable_list.dart';
3 |
4 | import 'mock_data.dart';
5 |
6 | class ExampleNestedScrollView extends StatefulWidget {
7 | @override
8 | _ExampleNestedScrollViewState createState() =>
9 | _ExampleNestedScrollViewState();
10 | }
11 |
12 | class _ExampleNestedScrollViewState extends State
13 | with TickerProviderStateMixin {
14 | var sectionList = MockData.getExampleSections();
15 | late TabController tabController, subTabController;
16 | final GlobalKey nestedScrollKey = GlobalKey();
17 | double _expandedHeight = 200;
18 |
19 | bool _isPinnedTitleShown = false;
20 |
21 | @override
22 | void initState() {
23 | super.initState();
24 | this.tabController = TabController(length: 2, vsync: this);
25 | this.subTabController = TabController(length: 2, vsync: this);
26 | var headerContentHeight = _expandedHeight - kToolbarHeight;
27 | WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
28 | outerController.addListener(() {
29 | var pinned = outerController.offset >= headerContentHeight;
30 | if (_isPinnedTitleShown != pinned) {
31 | setState(() {
32 | _isPinnedTitleShown = pinned;
33 | });
34 | }
35 | // print("outerController position: $outerController $kToolbarHeight");
36 | });
37 | });
38 | }
39 |
40 | @override
41 | void dispose() {
42 | tabController.dispose();
43 | subTabController.dispose();
44 | super.dispose();
45 | }
46 |
47 | ScrollController get outerController {
48 | return nestedScrollKey.currentState!.outerController;
49 | }
50 |
51 | @override
52 | Widget build(BuildContext context) {
53 | return Scaffold(
54 | body: NestedScrollView(
55 | key: nestedScrollKey,
56 | headerSliverBuilder: (context, innerBoxIsScrolled) {
57 | return [
58 | SliverAppBar(
59 | backgroundColor: Colors.white,
60 | pinned: false,
61 | expandedHeight: _expandedHeight,
62 | flexibleSpace: FlexibleSpaceBar(
63 | title: Text(
64 | "Appbar top area",
65 | style: TextStyle(color: Colors.black87),
66 | ),
67 | ),
68 | ),
69 | // ),
70 | ];
71 | },
72 | body: CustomScrollView(
73 | slivers: [
74 | SliverAppBar(
75 | backgroundColor: Colors.white,
76 | pinned: true,
77 | elevation: 0,
78 | title: Text(
79 | _isPinnedTitleShown ? "PinnedTitle" : "",
80 | style: TextStyle(color: Colors.black),
81 | ),
82 | bottom: TabBar(
83 | labelColor: Colors.black,
84 | controller: this.tabController,
85 | tabs: [
86 | Tab(text: 'Home'),
87 | Tab(text: 'Profile'),
88 | ],
89 | ),
90 | ),
91 | SliverPersistentHeader(
92 | // 可以吸顶的TabBar
93 | pinned: true,
94 | delegate: StickyTabBarDelegate(
95 | child: TabBar(
96 | labelColor: Colors.black,
97 | controller: this.subTabController,
98 | tabs: [
99 | Tab(text: 'SubTab1'),
100 | Tab(text: 'SubTab2'),
101 | ],
102 | ),
103 | ),
104 | ),
105 | SliverToBoxAdapter(
106 | child: Container(
107 | height: 300,
108 | child: TabBarView(
109 | controller: this.subTabController,
110 | children: [
111 | Center(child: Text('Content of SubTab1')),
112 | Center(child: Text('Content of SubTab2')),
113 | ],
114 | ),
115 | ),
116 | ),
117 | // SliverFillRemaining(
118 | // // 剩余补充内容TabBarView
119 | // child: TabBarView(
120 | // controller: this.tabController,
121 | // children: [
122 | // Center(child: Text('Content of Home')),
123 | // Center(child: Text('Content of Profile')),
124 | // ],
125 | // ),
126 | // ),
127 | SliverExpandableList(
128 | builder: SliverExpandableChildDelegate(
129 | sectionList: sectionList,
130 | headerBuilder: _buildHeader,
131 | itemBuilder: (context, sectionIndex, itemIndex, index) {
132 | String item = sectionList[sectionIndex].items[itemIndex];
133 | return ListTile(
134 | leading: CircleAvatar(
135 | child: Text("$index"),
136 | ),
137 | title: Text(item),
138 | );
139 | },
140 | ),
141 | ),
142 | ],
143 | ),
144 | ),
145 | );
146 | }
147 |
148 | Widget _buildHeader(BuildContext context, int sectionIndex, int index) {
149 | ExampleSection section = sectionList[sectionIndex];
150 | return InkWell(
151 | child: Container(
152 | color: Colors.lightBlue,
153 | height: 48,
154 | padding: EdgeInsets.only(left: 20),
155 | alignment: Alignment.centerLeft,
156 | child: Text(
157 | "Header #$sectionIndex",
158 | style: TextStyle(color: Colors.white),
159 | )),
160 | onTap: () {
161 | //toggle section expand state
162 | setState(() {
163 | section.setSectionExpanded(!section.isSectionExpanded());
164 | });
165 | });
166 | }
167 | }
168 |
169 | class StickyTabBarDelegate extends SliverPersistentHeaderDelegate {
170 | final TabBar child;
171 |
172 | StickyTabBarDelegate({required this.child});
173 |
174 | @override
175 | Widget build(
176 | BuildContext context, double shrinkOffset, bool overlapsContent) {
177 | // print("shrinkOffset:$shrinkOffset overlapsContent:$overlapsContent");
178 | return Container(color: Colors.yellow, child: this.child);
179 | }
180 |
181 | @override
182 | double get maxExtent => this.child.preferredSize.height;
183 |
184 | @override
185 | double get minExtent => this.child.preferredSize.height;
186 |
187 | @override
188 | bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) {
189 | return true;
190 | }
191 | }
192 |
--------------------------------------------------------------------------------
/lib/src/sliver_expandable_list.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math' as math;
2 |
3 | import 'package:flutter/foundation.dart';
4 | import 'package:flutter/rendering.dart';
5 | import 'package:flutter/widgets.dart';
6 |
7 | import '../sticky_and_expandable_list.dart';
8 |
9 | typedef ExpandableHeaderBuilder = Widget Function(
10 | BuildContext context, int sectionIndex, int index);
11 | typedef ExpandableItemBuilder = Widget Function(
12 | BuildContext context, int sectionIndex, int itemIndex, int index);
13 | typedef ExpandableSeparatorBuilder = Widget Function(
14 | BuildContext context, bool isSectionSeparator, int index);
15 | typedef ExpandableSectionBuilder = Widget Function(
16 | BuildContext context, ExpandableSectionContainerInfo containerInfo);
17 |
18 | /// A scrollable list of widgets arranged linearly, support expand/collapse item and
19 | /// sticky header.
20 | /// all build options are set in [SliverExpandableChildDelegate], this is to avoid
21 | /// [SliverExpandableList] use generics.
22 | class SliverExpandableList extends SliverList {
23 | final SliverExpandableChildDelegate builder;
24 |
25 | SliverExpandableList({
26 | Key? key,
27 | required this.builder,
28 | }) : super(key: key, delegate: builder.delegate);
29 |
30 | @override
31 | RenderSliverList createRenderObject(BuildContext context) {
32 | final SliverMultiBoxAdaptorElement element =
33 | context as SliverMultiBoxAdaptorElement;
34 | return RenderExpandableSliverList(childManager: element)
35 | ..expandStateList = _buildExpandStateList();
36 | }
37 |
38 | @override
39 | void updateRenderObject(
40 | BuildContext context, RenderExpandableSliverList renderObject) {
41 | var oldRenderList = renderObject.expandStateList;
42 | renderObject.expandStateList = _buildExpandStateList();
43 | if (!renderObject.sizeChanged &&
44 | listEquals(oldRenderList, renderObject.expandStateList)) {
45 | renderObject.sizeChanged = true;
46 | }
47 | super.updateRenderObject(context, renderObject);
48 | }
49 |
50 | List _buildExpandStateList() {
51 | List sectionList = builder.sectionList;
52 | return List.generate(
53 | sectionList.length, (index) => sectionList[index].isSectionExpanded());
54 | }
55 | }
56 |
57 | class RenderExpandableSliverList extends RenderSliverList {
58 | /// Creates a sliver that places multiple box children in a linear array along
59 | /// the main axis.
60 | ///
61 | /// The [childManager] argument must not be null.
62 |
63 | List expandStateList = [];
64 | bool sizeChanged = false;
65 |
66 | RenderExpandableSliverList({
67 | required RenderSliverBoxChildManager childManager,
68 | }) : super(childManager: childManager);
69 |
70 | @override
71 | void performLayout() {
72 | super.performLayout();
73 | sizeChanged = false;
74 | }
75 | }
76 |
77 | /// A delegate that supplies children for [SliverExpandableList] using
78 | /// a builder callback.
79 | class SliverExpandableChildDelegate> {
80 | ///data source
81 | final List sectionList;
82 |
83 | ///build section header
84 | final ExpandableHeaderBuilder? headerBuilder;
85 |
86 | ///build section item
87 | final ExpandableItemBuilder itemBuilder;
88 |
89 | ///build header and item separator, if pass null, SliverList has no separators.
90 | ///default null.
91 | final ExpandableSeparatorBuilder? separatorBuilder;
92 |
93 | ///whether to sticky the header.
94 | final bool sticky;
95 |
96 | /// Whether the header should be drawn on top of the content
97 | /// instead of before.
98 | final bool overlapsContent;
99 |
100 | ///store section real index in SliverList, format: [sectionList index, SliverList index].
101 | final List sectionRealIndexes;
102 |
103 | ///use this return a custom content widget, when use this builder, headerBuilder
104 | ///is invalid.
105 | /// See also:
106 | ///
107 | /// * ,
108 | /// a description of what ExpandableSectionBuilder are and how to use it.
109 | ///
110 | ExpandableSectionBuilder? sectionBuilder;
111 |
112 | ///expandable list controller, listen sticky header index scroll offset etc.
113 | ExpandableListController? controller;
114 |
115 | ///sliver list builder
116 | late SliverChildBuilderDelegate delegate;
117 |
118 | ///if value is true, when section is collapsed, all child widget in section widget will be removed.
119 | @Deprecated("unused property")
120 | final bool removeItemsOnCollapsed;
121 |
122 | SliverExpandableChildDelegate(
123 | {required this.sectionList,
124 | required this.itemBuilder,
125 | this.controller,
126 | this.separatorBuilder,
127 | this.headerBuilder,
128 | this.sectionBuilder,
129 | this.sticky = true,
130 | this.overlapsContent = false,
131 | this.removeItemsOnCollapsed = true,
132 | bool addAutomaticKeepAlives = true,
133 | bool addRepaintBoundaries = true,
134 | bool addSemanticIndexes = true})
135 | : assert(
136 | (headerBuilder != null && sectionBuilder == null) ||
137 | (headerBuilder == null && sectionBuilder != null),
138 | 'You must specify either headerBuilder or sectionBuilder.',
139 | ),
140 | sectionRealIndexes = _buildSectionRealIndexes(sectionList) {
141 | if (controller == null) {
142 | controller = ExpandableListController();
143 | }
144 | if (separatorBuilder == null) {
145 | delegate = SliverChildBuilderDelegate(
146 | (BuildContext context, int index) {
147 | int sectionIndex = index;
148 | S section = sectionList[sectionIndex];
149 | int sectionRealIndex = sectionRealIndexes[sectionIndex];
150 |
151 | int sectionChildCount = section.getItems()?.length ?? 0;
152 | if (!section.isSectionExpanded()) {
153 | sectionChildCount = 0;
154 | }
155 | var childBuilderDelegate = SliverChildBuilderDelegate(
156 | (context, i) => itemBuilder(
157 | context, sectionIndex, i, sectionRealIndex + i + 1),
158 | childCount: sectionChildCount);
159 | var containerInfo = ExpandableSectionContainerInfo(
160 | separated: false,
161 | listIndex: index,
162 | sectionIndex: sectionIndex,
163 | sectionRealIndexes: sectionRealIndexes,
164 | sticky: sticky,
165 | overlapsContent: overlapsContent,
166 | controller: controller!,
167 | header: Container(),
168 | content: Container(),
169 | childDelegate: childBuilderDelegate,
170 | );
171 | Widget? container = sectionBuilder != null
172 | ? sectionBuilder!(context, containerInfo)
173 | : null;
174 | if (container == null) {
175 | containerInfo
176 | ..header = headerBuilder!(context, sectionIndex, sectionRealIndex)
177 | ..content = buildDefaultContent(context, containerInfo);
178 | container = ExpandableSectionContainer(
179 | info: containerInfo,
180 | );
181 | }
182 | assert(containerInfo.header != null);
183 | assert(containerInfo.content != null);
184 | return container;
185 | },
186 | childCount: sectionList.length,
187 | addAutomaticKeepAlives: addAutomaticKeepAlives,
188 | addRepaintBoundaries: addRepaintBoundaries,
189 | addSemanticIndexes: addSemanticIndexes,
190 | );
191 | } else {
192 | delegate = SliverChildBuilderDelegate(
193 | (BuildContext context, int index) {
194 | final int sectionIndex = index ~/ 2;
195 | Widget itemView;
196 | S section = sectionList[sectionIndex];
197 | int sectionRealIndex = sectionRealIndexes[sectionIndex];
198 | if (index.isEven) {
199 | int sectionChildCount =
200 | _computeSemanticChildCount(section.getItems()?.length ?? 0);
201 | if (!section.isSectionExpanded()) {
202 | sectionChildCount = 0;
203 | }
204 | var childBuilderDelegate = SliverChildBuilderDelegate((context, i) {
205 | int itemRealIndex = sectionRealIndex + (i ~/ 2) + 1;
206 | if (i.isEven) {
207 | return itemBuilder(
208 | context, sectionIndex, i ~/ 2, itemRealIndex);
209 | } else {
210 | return separatorBuilder!(context, false, itemRealIndex);
211 | }
212 | }, childCount: sectionChildCount);
213 | var containerInfo = ExpandableSectionContainerInfo(
214 | separated: true,
215 | listIndex: index,
216 | sectionIndex: sectionIndex,
217 | sectionRealIndexes: sectionRealIndexes,
218 | sticky: sticky,
219 | overlapsContent: overlapsContent,
220 | controller: controller!,
221 | header: Container(),
222 | content: Container(),
223 | childDelegate: childBuilderDelegate,
224 | );
225 | Widget? container = sectionBuilder != null
226 | ? sectionBuilder!(context, containerInfo)
227 | : null;
228 | if (container == null) {
229 | containerInfo
230 | ..header =
231 | headerBuilder!(context, sectionIndex, sectionRealIndex)
232 | ..content = buildDefaultContent(context, containerInfo);
233 | container = ExpandableSectionContainer(
234 | info: containerInfo,
235 | );
236 | }
237 | assert(containerInfo.header != null);
238 | assert(containerInfo.content != null);
239 | return container;
240 | } else {
241 | itemView = separatorBuilder!(context, true,
242 | sectionIndex + (section.getItems()?.length ?? 0));
243 | }
244 | return itemView;
245 | },
246 | childCount: _computeSemanticChildCount(sectionList.length),
247 | addAutomaticKeepAlives: addAutomaticKeepAlives,
248 | addRepaintBoundaries: addRepaintBoundaries,
249 | addSemanticIndexes: addSemanticIndexes,
250 | semanticIndexCallback: (Widget _, int index) {
251 | return index.isEven ? index ~/ 2 : null;
252 | },
253 | );
254 | }
255 | }
256 |
257 | ///By default, build a Column widget for layout all children's size.
258 | static Widget buildDefaultContent(
259 | BuildContext context, ExpandableSectionContainerInfo containerInfo) {
260 | var childDelegate = containerInfo.childDelegate;
261 | if (childDelegate != null) {
262 | var children =
263 | List.generate(childDelegate.childCount ?? 0, (index) {
264 | return childDelegate.builder(context, index) ?? Container();
265 | });
266 | return Column(
267 | children: children,
268 | );
269 | }
270 | return Container();
271 | }
272 |
273 | static int _computeSemanticChildCount(int itemCount) {
274 | return math.max(0, itemCount * 2 - 1);
275 | }
276 |
277 | static List
278 | _buildSectionRealIndexes>(
279 | List sectionList) {
280 | int calcLength = sectionList.length - 1;
281 | List sectionRealIndexes = List.empty(growable: true);
282 | if (calcLength < 0) {
283 | return sectionRealIndexes;
284 | }
285 | sectionRealIndexes.add(0);
286 | int realIndex = 0;
287 | for (int i = 0; i < calcLength; i++) {
288 | S section = sectionList[i];
289 | //each section model should not null.
290 | realIndex += 1 + (section.getItems()?.length ?? 0);
291 | sectionRealIndexes.add(realIndex);
292 | }
293 | return sectionRealIndexes;
294 | }
295 | }
296 |
297 | ///Used to provide information for each section, each section model
298 | ///should implement [ExpandableListSection- ].
299 | abstract class ExpandableListSection {
300 | bool isSectionExpanded();
301 |
302 | void setSectionExpanded(bool expanded);
303 |
304 | List? getItems();
305 | }
306 |
307 | ///Controller for listen sticky header offset and current sticky header index.
308 | class ExpandableListController extends ChangeNotifier {
309 | ///switchingSection scroll percent, [0.1-1.0], 1.0 mean that the last sticky section
310 | ///is completely hidden.
311 | double _percent = 1.0;
312 | int _switchingSectionIndex = -1;
313 | int _stickySectionIndex = -1;
314 |
315 | ExpandableListController();
316 |
317 | ///store [ExpandableSectionContainer] information. [SliverList index, layoutOffset].
318 | ///don't modify it.
319 | List containerOffsets = [];
320 |
321 | double get percent => _percent;
322 |
323 | int get switchingSectionIndex => _switchingSectionIndex;
324 |
325 | ///get pinned header index
326 | int get stickySectionIndex => _stickySectionIndex;
327 |
328 | updatePercent(int sectionIndex, double percent) {
329 | if (_percent == percent && _switchingSectionIndex == sectionIndex) {
330 | return;
331 | }
332 | _switchingSectionIndex = sectionIndex;
333 | _percent = percent;
334 | notifyListeners();
335 | }
336 |
337 | set stickySectionIndex(int value) {
338 | if (_stickySectionIndex == value) {
339 | return;
340 | }
341 | _stickySectionIndex = value;
342 | notifyListeners();
343 | }
344 |
345 | void forceNotifyListeners() {
346 | notifyListeners();
347 | }
348 |
349 | @override
350 | String toString() {
351 | return 'ExpandableListController{_percent: $_percent, _switchingSectionIndex: $_switchingSectionIndex, _stickySectionIndex: $_stickySectionIndex} #$hashCode';
352 | }
353 | }
354 |
355 | ///Check if need rebuild [ExpandableAutoLayoutWidget]
356 | abstract class ExpandableAutoLayoutTrigger {
357 | ExpandableListController get controller;
358 |
359 | bool needBuild();
360 | }
361 |
362 | ///Default [ExpandableAutoLayoutTrigger] implementation, auto build when
363 | ///switch sticky header index.
364 | class ExpandableDefaultAutoLayoutTrigger
365 | implements ExpandableAutoLayoutTrigger {
366 | final ExpandableListController _controller;
367 |
368 | double _percent = 0;
369 | int _stickyIndex = 0;
370 |
371 | ExpandableDefaultAutoLayoutTrigger(this._controller) : super();
372 |
373 | @override
374 | bool needBuild() {
375 | if (_percent == _controller.percent &&
376 | _stickyIndex == _controller.stickySectionIndex) {
377 | return false;
378 | }
379 | _percent = _controller.percent;
380 | _stickyIndex = _controller.stickySectionIndex;
381 | return true;
382 | }
383 |
384 | @override
385 | ExpandableListController get controller => _controller;
386 | }
387 |
388 | ///Wrap header widget, when controller is set, the widget will rebuild
389 | ///when [trigger] condition matched.
390 | class ExpandableAutoLayoutWidget extends StatefulWidget {
391 | ///listen sticky header hide percent, [0.0-0.1].
392 | final ExpandableAutoLayoutTrigger trigger;
393 |
394 | ///build section header
395 | final WidgetBuilder builder;
396 |
397 | ExpandableAutoLayoutWidget({required this.builder, required this.trigger});
398 |
399 | @override
400 | _ExpandableAutoLayoutWidgetState createState() =>
401 | _ExpandableAutoLayoutWidgetState();
402 | }
403 |
404 | class _ExpandableAutoLayoutWidgetState
405 | extends State {
406 | @override
407 | void initState() {
408 | super.initState();
409 | widget.trigger.controller.addListener(_onChange);
410 | }
411 |
412 | void _onChange() {
413 | if (widget.trigger.needBuild()) {
414 | WidgetsBinding.instance.addPostFrameCallback((_) {
415 | if (mounted) {
416 | setState(() {});
417 | }
418 | });
419 | }
420 | }
421 |
422 | @override
423 | void dispose() {
424 | widget.trigger.controller.removeListener(_onChange);
425 | super.dispose();
426 | }
427 |
428 | @override
429 | Widget build(BuildContext context) {
430 | return RepaintBoundary(
431 | child: widget.builder(context),
432 | );
433 | }
434 | }
435 |
--------------------------------------------------------------------------------
/lib/src/expandable_section_container.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math';
2 |
3 | import 'package:flutter/widgets.dart';
4 | import 'package:flutter/rendering.dart';
5 | import '../sticky_and_expandable_list.dart';
6 |
7 | ///Section widget information.
8 | class ExpandableSectionContainerInfo {
9 | Widget? header;
10 | Widget? content;
11 | final SliverChildBuilderDelegate? childDelegate;
12 | final int listIndex;
13 | final List sectionRealIndexes;
14 | final bool separated;
15 |
16 | final ExpandableListController controller;
17 | final int sectionIndex;
18 | final bool sticky;
19 | final bool overlapsContent;
20 |
21 | ExpandableSectionContainerInfo(
22 | {this.header,
23 | this.content,
24 | this.childDelegate,
25 | required this.listIndex,
26 | required this.sectionRealIndexes,
27 | required this.separated,
28 | required this.controller,
29 | required this.sectionIndex,
30 | required this.sticky,
31 | required this.overlapsContent});
32 |
33 | @override
34 | bool operator ==(Object other) =>
35 | identical(this, other) ||
36 | other is ExpandableSectionContainerInfo &&
37 | runtimeType == other.runtimeType &&
38 | header == other.header &&
39 | content == other.content &&
40 | childDelegate == other.childDelegate &&
41 | listIndex == other.listIndex &&
42 | sectionRealIndexes == other.sectionRealIndexes &&
43 | separated == other.separated &&
44 | controller == other.controller &&
45 | sectionIndex == other.sectionIndex &&
46 | sticky == other.sticky &&
47 | overlapsContent == other.overlapsContent;
48 |
49 | @override
50 | int get hashCode =>
51 | (header?.hashCode ?? 0) ^
52 | (content?.hashCode ?? 0) ^
53 | (childDelegate?.hashCode ?? 0) ^
54 | listIndex.hashCode ^
55 | sectionRealIndexes.hashCode ^
56 | separated.hashCode ^
57 | controller.hashCode ^
58 | sectionIndex.hashCode ^
59 | sticky.hashCode ^
60 | overlapsContent.hashCode;
61 | }
62 |
63 | ///Section widget that contains header and content widget.
64 | ///You can return a custom [ExpandableSectionContainer]
65 | ///by [SliverExpandableChildDelegate.sectionBuilder], but only
66 | ///[header] and [content] field could be changed.
67 | ///
68 | class ExpandableSectionContainer extends MultiChildRenderObjectWidget {
69 | final ExpandableSectionContainerInfo info;
70 |
71 | ExpandableSectionContainer({
72 | Key? key,
73 | required this.info,
74 | }) : super(key: key, children: [info.content!, info.header!]);
75 |
76 | @override
77 | RenderExpandableSectionContainer createRenderObject(BuildContext context) {
78 | var renderSliver =
79 | context.findAncestorRenderObjectOfType()!;
80 | return RenderExpandableSectionContainer(
81 | renderSliver: renderSliver,
82 | scrollable: Scrollable.of(context)!,
83 | controller: this.info.controller,
84 | sticky: this.info.sticky,
85 | overlapsContent: this.info.overlapsContent,
86 | listIndex: this.info.listIndex,
87 | sectionRealIndexes: this.info.sectionRealIndexes,
88 | separated: this.info.separated,
89 | );
90 | }
91 |
92 | @override
93 | void updateRenderObject(
94 | BuildContext context, RenderExpandableSectionContainer renderObject) {
95 | renderObject
96 | ..scrollable = Scrollable.of(context)!
97 | ..controller = this.info.controller
98 | ..sticky = this.info.sticky
99 | ..overlapsContent = this.info.overlapsContent
100 | ..listIndex = this.info.listIndex
101 | ..sectionRealIndexes = this.info.sectionRealIndexes
102 | ..separated = this.info.separated;
103 | }
104 | }
105 |
106 | ///Render [ExpandableSectionContainer]
107 | class RenderExpandableSectionContainer extends RenderBox
108 | with
109 | ContainerRenderObjectMixin,
110 | RenderBoxContainerDefaultsMixin {
111 | static const String TAG = "ExpandableSectionContainer";
112 | bool _sticky;
113 | bool _overlapsContent;
114 | ScrollableState _scrollable;
115 | ExpandableListController _controller;
116 | RenderExpandableSliverList _renderSliver;
117 | int _listIndex;
118 | int _stickyIndex = -1;
119 |
120 | ///[sectionIndex, section in SliverList index].
121 | List _sectionRealIndexes;
122 |
123 | /// is SliverList has separator
124 | bool _separated;
125 |
126 | RenderExpandableSectionContainer({
127 | required ScrollableState scrollable,
128 | required ExpandableListController controller,
129 | sticky = true,
130 | overlapsContent = false,
131 | int listIndex = -1,
132 | List sectionRealIndexes = const [],
133 | bool separated = false,
134 | required RenderExpandableSliverList renderSliver,
135 | }) : _scrollable = scrollable,
136 | _controller = controller,
137 | _sticky = sticky,
138 | _overlapsContent = overlapsContent,
139 | _listIndex = listIndex,
140 | _sectionRealIndexes = sectionRealIndexes,
141 | _separated = separated,
142 | _renderSliver = renderSliver;
143 |
144 | List get sectionRealIndexes => _sectionRealIndexes;
145 |
146 | set sectionRealIndexes(List value) {
147 | if (_sectionRealIndexes == value) {
148 | return;
149 | }
150 | _sectionRealIndexes = value;
151 | markNeedsLayout();
152 | }
153 |
154 | bool get separated => _separated;
155 |
156 | set separated(bool value) {
157 | if (_separated == value) {
158 | return;
159 | }
160 | _separated = value;
161 | markNeedsLayout();
162 | }
163 |
164 | ScrollableState get scrollable => _scrollable;
165 |
166 | set scrollable(ScrollableState value) {
167 | // print("$TAG update scrollable: ${_renderSliver.sizeChanged}");
168 |
169 | //when collapse last section, Sliver list not callback correct offset, so layout again.
170 | if (_renderSliver.sizeChanged) {
171 | WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
172 | if (attached) {
173 | clearContainerLayoutOffsets();
174 | markNeedsLayout();
175 | }
176 | });
177 | }
178 | if (_scrollable == value) {
179 | return;
180 | }
181 | final ScrollableState oldValue = _scrollable;
182 | _scrollable = value;
183 | markNeedsLayout();
184 | if (attached) {
185 | oldValue.widget.controller?.removeListener(markNeedsLayout);
186 | if (_sticky) {
187 | _scrollable.widget.controller?.addListener(markNeedsLayout);
188 | }
189 | }
190 | }
191 |
192 | ExpandableListController get controller => _controller;
193 |
194 | set controller(ExpandableListController value) {
195 | if (_controller == value) {
196 | return;
197 | }
198 | _controller = value;
199 | markNeedsLayout();
200 | }
201 |
202 | bool get sticky => _sticky;
203 |
204 | set sticky(bool value) {
205 | if (_sticky == value) {
206 | return;
207 | }
208 | _sticky = value;
209 | markNeedsLayout();
210 | if (attached && !_sticky) {
211 | _scrollable.widget.controller?.removeListener(markNeedsLayout);
212 | }
213 | }
214 |
215 | bool get overlapsContent => _overlapsContent;
216 |
217 | set overlapsContent(bool value) {
218 | if (_overlapsContent == value) {
219 | return;
220 | }
221 | _overlapsContent = value;
222 | markNeedsLayout();
223 | }
224 |
225 | int get listIndex => _listIndex;
226 |
227 | set listIndex(int value) {
228 | if (_listIndex == value) {
229 | return;
230 | }
231 | _listIndex = value;
232 | markNeedsLayout();
233 | }
234 |
235 | @override
236 | void setupParentData(RenderBox child) {
237 | if (child.parentData is! MultiChildLayoutParentData)
238 | child.parentData = MultiChildLayoutParentData();
239 | }
240 |
241 | @override
242 | void attach(PipelineOwner owner) {
243 | super.attach(owner);
244 | if (sticky) {
245 | _scrollable.widget.controller?.addListener(markNeedsLayout);
246 | }
247 | }
248 |
249 | @override
250 | void detach() {
251 | _scrollable.widget.controller?.removeListener(markNeedsLayout);
252 | super.detach();
253 | }
254 |
255 | RenderBox get content => firstChild!;
256 |
257 | RenderBox get header => lastChild!;
258 |
259 | @override
260 | double computeMinIntrinsicWidth(double height) {
261 | return _overlapsContent
262 | ? max(header.getMinIntrinsicWidth(height),
263 | content.getMinIntrinsicWidth(height))
264 | : header.getMinIntrinsicWidth(height) +
265 | content.getMinIntrinsicWidth(height);
266 | }
267 |
268 | @override
269 | double computeMaxIntrinsicWidth(double height) {
270 | return _overlapsContent
271 | ? max(header.getMaxIntrinsicWidth(height),
272 | content.getMaxIntrinsicWidth(height))
273 | : header.getMaxIntrinsicWidth(height) +
274 | content.getMaxIntrinsicWidth(height);
275 | }
276 |
277 | @override
278 | double computeMinIntrinsicHeight(double width) {
279 | return _overlapsContent
280 | ? max(header.getMinIntrinsicHeight(width),
281 | content.getMinIntrinsicHeight(width))
282 | : header.getMinIntrinsicHeight(width) +
283 | content.getMinIntrinsicHeight(width);
284 | }
285 |
286 | @override
287 | double computeMaxIntrinsicHeight(double width) {
288 | return _overlapsContent
289 | ? max(header.getMaxIntrinsicHeight(width),
290 | content.getMaxIntrinsicHeight(width))
291 | : header.getMaxIntrinsicHeight(width) +
292 | content.getMaxIntrinsicHeight(width);
293 | }
294 |
295 | @override
296 | void performLayout() {
297 | assert(childCount == 2);
298 |
299 | //layout two child
300 | BoxConstraints exactlyConstraints = constraints.loosen();
301 | header.layout(exactlyConstraints, parentUsesSize: true);
302 | content.layout(exactlyConstraints, parentUsesSize: true);
303 |
304 | //header's size should not large than content's size.
305 | double headerLogicalExtent = _overlapsContent ? 0 : header.size.height;
306 |
307 | double width =
308 | max(constraints.minWidth, max(header.size.width, content.size.width));
309 | double height = max(constraints.minHeight,
310 | max(header.size.height, headerLogicalExtent + content.size.height));
311 | size = Size(width, height);
312 | assert(size.width == constraints.constrainWidth(width));
313 | assert(size.height == constraints.constrainHeight(height));
314 |
315 | //calc content offset
316 | positionChild(content, Offset(0, headerLogicalExtent));
317 |
318 | checkRefreshContainerOffset();
319 |
320 | double sliverListOffset = _getSliverListVisibleScrollOffset();
321 | double currContainerOffset = -1;
322 | if (_listIndex < _controller.containerOffsets.length) {
323 | currContainerOffset = _controller.containerOffsets[_listIndex]!;
324 | }
325 | bool containerPainted = (_listIndex == 0 && currContainerOffset == 0) ||
326 | currContainerOffset > 0;
327 | if (!containerPainted) {
328 | positionChild(header, Offset.zero);
329 | return;
330 | }
331 | double minScrollOffset = _listIndex >= _controller.containerOffsets.length
332 | ? 0
333 | : _controller.containerOffsets[_listIndex]!;
334 | double maxScrollOffset = minScrollOffset + size.height;
335 |
336 | //when [ExpandableSectionContainer] size changed, SliverList may give a wrong
337 | // layoutOffset at first time, so check offsets for store right layoutOffset
338 | // in [containerOffsets].
339 | if (_listIndex < _controller.containerOffsets.length) {
340 | currContainerOffset = _controller.containerOffsets[_listIndex]!;
341 | int nextListIndex = _listIndex + 1;
342 | if (nextListIndex < _controller.containerOffsets.length &&
343 | _controller.containerOffsets[nextListIndex]! < maxScrollOffset) {
344 | _controller.containerOffsets =
345 | _controller.containerOffsets.sublist(0, nextListIndex);
346 | }
347 | }
348 |
349 | if (sliverListOffset > minScrollOffset &&
350 | sliverListOffset <= maxScrollOffset) {
351 | if (_stickyIndex != _listIndex) {
352 | _stickyIndex = _listIndex;
353 | _controller.updatePercent(_controller.switchingSectionIndex, 1);
354 | //update sticky index
355 | _controller.stickySectionIndex = sectionIndex;
356 | }
357 | } else if (sliverListOffset <= 0) {
358 | _controller.stickySectionIndex = -1;
359 | _stickyIndex = -1;
360 | } else {
361 | _stickyIndex = -1;
362 | }
363 |
364 | //calc header offset
365 | double currHeaderOffset = 0;
366 | double headerMaxOffset = height - header.size.height;
367 | if (_sticky && isStickyChild && sliverListOffset > minScrollOffset) {
368 | currHeaderOffset = sliverListOffset - minScrollOffset;
369 | }
370 | // print(
371 | // "index:$listIndex currHeaderOffset:${currHeaderOffset.toStringAsFixed(2)}" +
372 | // " sliverListOffset:${sliverListOffset.toStringAsFixed(2)}" +
373 | // " [$minScrollOffset,$maxScrollOffset] size:${content.size.height}");
374 | positionChild(header, Offset(0, min(currHeaderOffset, headerMaxOffset)));
375 |
376 | //callback header hide percent
377 | if (currHeaderOffset >= headerMaxOffset && currHeaderOffset <= height) {
378 | double switchingPercent =
379 | (currHeaderOffset - headerMaxOffset) / header.size.height;
380 | _controller.updatePercent(sectionIndex, switchingPercent);
381 | } else if (sliverListOffset < minScrollOffset + headerMaxOffset &&
382 | _controller.switchingSectionIndex == sectionIndex) {
383 | //ensure callback 0% percent.
384 | _controller.updatePercent(sectionIndex, 0);
385 | //reset switchingSectionIndex
386 | _controller.updatePercent(-1, 1);
387 | }
388 | }
389 |
390 | bool get isStickyChild => _listIndex == _stickyIndex;
391 |
392 | int get sectionIndex => separated ? _listIndex ~/ 2 : _listIndex;
393 |
394 | double _getSliverListVisibleScrollOffset() {
395 | return _renderSliver.constraints.overlap +
396 | _renderSliver.constraints.scrollOffset;
397 | }
398 |
399 | void clearContainerLayoutOffsets() {
400 | // print("$TAG clearContainerLayoutOffsets");
401 | _controller.containerOffsets.clear();
402 | }
403 |
404 | void _refreshContainerLayoutOffsets(String reason) {
405 | // print("$TAG _refreshContainerLayoutOffsets reason:$reason");
406 | _renderSliver.visitChildren((renderObject) {
407 | var containerParentData =
408 | renderObject.parentData as SliverMultiBoxAdaptorParentData;
409 | // print("visitChildren $containerParentData");
410 |
411 | while (
412 | _controller.containerOffsets.length <= containerParentData.index!) {
413 | _controller.containerOffsets.add(0);
414 | }
415 | if (containerParentData.layoutOffset != null) {
416 | _controller.containerOffsets[containerParentData.index!] =
417 | containerParentData.layoutOffset;
418 | }
419 | });
420 | }
421 |
422 | void positionChild(RenderBox child, Offset offset) {
423 | final MultiChildLayoutParentData childParentData =
424 | child.parentData as MultiChildLayoutParentData;
425 | childParentData.offset = offset;
426 | }
427 |
428 | Offset childOffset(RenderBox child) {
429 | final MultiChildLayoutParentData childParentData =
430 | child.parentData as MultiChildLayoutParentData;
431 | return childParentData.offset;
432 | }
433 |
434 | @override
435 | bool get isRepaintBoundary => true;
436 |
437 | @override
438 | void paint(PaintingContext context, Offset offset) {
439 | defaultPaint(context, offset);
440 | }
441 |
442 | @override
443 | bool hitTestChildren(BoxHitTestResult result, {required Offset position}) {
444 | return defaultHitTestChildren(result, position: position);
445 | }
446 |
447 | void checkRefreshContainerOffset() {
448 | int length = _controller.containerOffsets.length;
449 | if (_listIndex >= length ||
450 | (_listIndex > 0 && _controller.containerOffsets[_listIndex]! <= 0)) {
451 | _refreshContainerLayoutOffsets("zero size");
452 | return;
453 | }
454 | for (int i = 0; i < _listIndex && _listIndex < length - 1; i++) {
455 | double currOffset = _controller.containerOffsets[i]?.toDouble() ?? 0;
456 | double nextOffset = _controller.containerOffsets[i + 1]?.toDouble() ?? 0;
457 | if (currOffset > nextOffset) {
458 | _refreshContainerLayoutOffsets("offset invalid: $currOffset->$nextOffset");
459 | break;
460 | }
461 | }
462 | }
463 | }
464 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
11 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
12 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
13 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
14 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
15 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
16 | /* End PBXBuildFile section */
17 |
18 | /* Begin PBXCopyFilesBuildPhase section */
19 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = {
20 | isa = PBXCopyFilesBuildPhase;
21 | buildActionMask = 2147483647;
22 | dstPath = "";
23 | dstSubfolderSpec = 10;
24 | files = (
25 | );
26 | name = "Embed Frameworks";
27 | runOnlyForDeploymentPostprocessing = 0;
28 | };
29 | /* End PBXCopyFilesBuildPhase section */
30 |
31 | /* Begin PBXFileReference section */
32 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; };
33 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
34 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
35 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; };
36 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
37 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
38 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
39 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
40 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
41 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
42 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
43 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
44 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
45 | /* End PBXFileReference section */
46 |
47 | /* Begin PBXFrameworksBuildPhase section */
48 | 97C146EB1CF9000F007C117D /* Frameworks */ = {
49 | isa = PBXFrameworksBuildPhase;
50 | buildActionMask = 2147483647;
51 | files = (
52 | );
53 | runOnlyForDeploymentPostprocessing = 0;
54 | };
55 | /* End PBXFrameworksBuildPhase section */
56 |
57 | /* Begin PBXGroup section */
58 | 9740EEB11CF90186004384FC /* Flutter */ = {
59 | isa = PBXGroup;
60 | children = (
61 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
62 | 9740EEB21CF90195004384FC /* Debug.xcconfig */,
63 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
64 | 9740EEB31CF90195004384FC /* Generated.xcconfig */,
65 | );
66 | name = Flutter;
67 | sourceTree = "";
68 | };
69 | 97C146E51CF9000F007C117D = {
70 | isa = PBXGroup;
71 | children = (
72 | 9740EEB11CF90186004384FC /* Flutter */,
73 | 97C146F01CF9000F007C117D /* Runner */,
74 | 97C146EF1CF9000F007C117D /* Products */,
75 | );
76 | sourceTree = "";
77 | };
78 | 97C146EF1CF9000F007C117D /* Products */ = {
79 | isa = PBXGroup;
80 | children = (
81 | 97C146EE1CF9000F007C117D /* Runner.app */,
82 | );
83 | name = Products;
84 | sourceTree = "";
85 | };
86 | 97C146F01CF9000F007C117D /* Runner */ = {
87 | isa = PBXGroup;
88 | children = (
89 | 97C146FA1CF9000F007C117D /* Main.storyboard */,
90 | 97C146FD1CF9000F007C117D /* Assets.xcassets */,
91 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
92 | 97C147021CF9000F007C117D /* Info.plist */,
93 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
94 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
95 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
96 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
97 | );
98 | path = Runner;
99 | sourceTree = "";
100 | };
101 | /* End PBXGroup section */
102 |
103 | /* Begin PBXNativeTarget section */
104 | 97C146ED1CF9000F007C117D /* Runner */ = {
105 | isa = PBXNativeTarget;
106 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
107 | buildPhases = (
108 | 9740EEB61CF901F6004384FC /* Run Script */,
109 | 97C146EA1CF9000F007C117D /* Sources */,
110 | 97C146EB1CF9000F007C117D /* Frameworks */,
111 | 97C146EC1CF9000F007C117D /* Resources */,
112 | 9705A1C41CF9048500538489 /* Embed Frameworks */,
113 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
114 | );
115 | buildRules = (
116 | );
117 | dependencies = (
118 | );
119 | name = Runner;
120 | productName = Runner;
121 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
122 | productType = "com.apple.product-type.application";
123 | };
124 | /* End PBXNativeTarget section */
125 |
126 | /* Begin PBXProject section */
127 | 97C146E61CF9000F007C117D /* Project object */ = {
128 | isa = PBXProject;
129 | attributes = {
130 | LastUpgradeCheck = 1300;
131 | ORGANIZATIONNAME = "";
132 | TargetAttributes = {
133 | 97C146ED1CF9000F007C117D = {
134 | CreatedOnToolsVersion = 7.3.1;
135 | LastSwiftMigration = 1100;
136 | };
137 | };
138 | };
139 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
140 | compatibilityVersion = "Xcode 9.3";
141 | developmentRegion = en;
142 | hasScannedForEncodings = 0;
143 | knownRegions = (
144 | en,
145 | Base,
146 | );
147 | mainGroup = 97C146E51CF9000F007C117D;
148 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
149 | projectDirPath = "";
150 | projectRoot = "";
151 | targets = (
152 | 97C146ED1CF9000F007C117D /* Runner */,
153 | );
154 | };
155 | /* End PBXProject section */
156 |
157 | /* Begin PBXResourcesBuildPhase section */
158 | 97C146EC1CF9000F007C117D /* Resources */ = {
159 | isa = PBXResourcesBuildPhase;
160 | buildActionMask = 2147483647;
161 | files = (
162 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
163 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
164 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
165 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
166 | );
167 | runOnlyForDeploymentPostprocessing = 0;
168 | };
169 | /* End PBXResourcesBuildPhase section */
170 |
171 | /* Begin PBXShellScriptBuildPhase section */
172 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
173 | isa = PBXShellScriptBuildPhase;
174 | buildActionMask = 2147483647;
175 | files = (
176 | );
177 | inputPaths = (
178 | );
179 | name = "Thin Binary";
180 | outputPaths = (
181 | );
182 | runOnlyForDeploymentPostprocessing = 0;
183 | shellPath = /bin/sh;
184 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
185 | };
186 | 9740EEB61CF901F6004384FC /* Run Script */ = {
187 | isa = PBXShellScriptBuildPhase;
188 | buildActionMask = 2147483647;
189 | files = (
190 | );
191 | inputPaths = (
192 | );
193 | name = "Run Script";
194 | outputPaths = (
195 | );
196 | runOnlyForDeploymentPostprocessing = 0;
197 | shellPath = /bin/sh;
198 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
199 | };
200 | /* End PBXShellScriptBuildPhase section */
201 |
202 | /* Begin PBXSourcesBuildPhase section */
203 | 97C146EA1CF9000F007C117D /* Sources */ = {
204 | isa = PBXSourcesBuildPhase;
205 | buildActionMask = 2147483647;
206 | files = (
207 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
208 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
209 | );
210 | runOnlyForDeploymentPostprocessing = 0;
211 | };
212 | /* End PBXSourcesBuildPhase section */
213 |
214 | /* Begin PBXVariantGroup section */
215 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = {
216 | isa = PBXVariantGroup;
217 | children = (
218 | 97C146FB1CF9000F007C117D /* Base */,
219 | );
220 | name = Main.storyboard;
221 | sourceTree = "";
222 | };
223 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
224 | isa = PBXVariantGroup;
225 | children = (
226 | 97C147001CF9000F007C117D /* Base */,
227 | );
228 | name = LaunchScreen.storyboard;
229 | sourceTree = "";
230 | };
231 | /* End PBXVariantGroup section */
232 |
233 | /* Begin XCBuildConfiguration section */
234 | 249021D3217E4FDB00AE95B9 /* Profile */ = {
235 | isa = XCBuildConfiguration;
236 | buildSettings = {
237 | ALWAYS_SEARCH_USER_PATHS = NO;
238 | CLANG_ANALYZER_NONNULL = YES;
239 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
240 | CLANG_CXX_LIBRARY = "libc++";
241 | CLANG_ENABLE_MODULES = YES;
242 | CLANG_ENABLE_OBJC_ARC = YES;
243 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
244 | CLANG_WARN_BOOL_CONVERSION = YES;
245 | CLANG_WARN_COMMA = YES;
246 | CLANG_WARN_CONSTANT_CONVERSION = YES;
247 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
248 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
249 | CLANG_WARN_EMPTY_BODY = YES;
250 | CLANG_WARN_ENUM_CONVERSION = YES;
251 | CLANG_WARN_INFINITE_RECURSION = YES;
252 | CLANG_WARN_INT_CONVERSION = YES;
253 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
254 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
255 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
256 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
257 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
258 | CLANG_WARN_STRICT_PROTOTYPES = YES;
259 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
260 | CLANG_WARN_UNREACHABLE_CODE = YES;
261 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
262 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
263 | COPY_PHASE_STRIP = NO;
264 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
265 | ENABLE_NS_ASSERTIONS = NO;
266 | ENABLE_STRICT_OBJC_MSGSEND = YES;
267 | GCC_C_LANGUAGE_STANDARD = gnu99;
268 | GCC_NO_COMMON_BLOCKS = YES;
269 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
270 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
271 | GCC_WARN_UNDECLARED_SELECTOR = YES;
272 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
273 | GCC_WARN_UNUSED_FUNCTION = YES;
274 | GCC_WARN_UNUSED_VARIABLE = YES;
275 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
276 | MTL_ENABLE_DEBUG_INFO = NO;
277 | SDKROOT = iphoneos;
278 | SUPPORTED_PLATFORMS = iphoneos;
279 | TARGETED_DEVICE_FAMILY = "1,2";
280 | VALIDATE_PRODUCT = YES;
281 | };
282 | name = Profile;
283 | };
284 | 249021D4217E4FDB00AE95B9 /* Profile */ = {
285 | isa = XCBuildConfiguration;
286 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
287 | buildSettings = {
288 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
289 | CLANG_ENABLE_MODULES = YES;
290 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
291 | ENABLE_BITCODE = NO;
292 | INFOPLIST_FILE = Runner/Info.plist;
293 | LD_RUNPATH_SEARCH_PATHS = (
294 | "$(inherited)",
295 | "@executable_path/Frameworks",
296 | );
297 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example;
298 | PRODUCT_NAME = "$(TARGET_NAME)";
299 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
300 | SWIFT_VERSION = 5.0;
301 | VERSIONING_SYSTEM = "apple-generic";
302 | };
303 | name = Profile;
304 | };
305 | 97C147031CF9000F007C117D /* Debug */ = {
306 | isa = XCBuildConfiguration;
307 | buildSettings = {
308 | ALWAYS_SEARCH_USER_PATHS = NO;
309 | CLANG_ANALYZER_NONNULL = YES;
310 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
311 | CLANG_CXX_LIBRARY = "libc++";
312 | CLANG_ENABLE_MODULES = YES;
313 | CLANG_ENABLE_OBJC_ARC = YES;
314 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
315 | CLANG_WARN_BOOL_CONVERSION = YES;
316 | CLANG_WARN_COMMA = YES;
317 | CLANG_WARN_CONSTANT_CONVERSION = YES;
318 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
319 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
320 | CLANG_WARN_EMPTY_BODY = YES;
321 | CLANG_WARN_ENUM_CONVERSION = YES;
322 | CLANG_WARN_INFINITE_RECURSION = YES;
323 | CLANG_WARN_INT_CONVERSION = YES;
324 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
325 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
326 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
327 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
328 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
329 | CLANG_WARN_STRICT_PROTOTYPES = YES;
330 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
331 | CLANG_WARN_UNREACHABLE_CODE = YES;
332 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
333 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
334 | COPY_PHASE_STRIP = NO;
335 | DEBUG_INFORMATION_FORMAT = dwarf;
336 | ENABLE_STRICT_OBJC_MSGSEND = YES;
337 | ENABLE_TESTABILITY = YES;
338 | GCC_C_LANGUAGE_STANDARD = gnu99;
339 | GCC_DYNAMIC_NO_PIC = NO;
340 | GCC_NO_COMMON_BLOCKS = YES;
341 | GCC_OPTIMIZATION_LEVEL = 0;
342 | GCC_PREPROCESSOR_DEFINITIONS = (
343 | "DEBUG=1",
344 | "$(inherited)",
345 | );
346 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
347 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
348 | GCC_WARN_UNDECLARED_SELECTOR = YES;
349 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
350 | GCC_WARN_UNUSED_FUNCTION = YES;
351 | GCC_WARN_UNUSED_VARIABLE = YES;
352 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
353 | MTL_ENABLE_DEBUG_INFO = YES;
354 | ONLY_ACTIVE_ARCH = YES;
355 | SDKROOT = iphoneos;
356 | TARGETED_DEVICE_FAMILY = "1,2";
357 | };
358 | name = Debug;
359 | };
360 | 97C147041CF9000F007C117D /* Release */ = {
361 | isa = XCBuildConfiguration;
362 | buildSettings = {
363 | ALWAYS_SEARCH_USER_PATHS = NO;
364 | CLANG_ANALYZER_NONNULL = YES;
365 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
366 | CLANG_CXX_LIBRARY = "libc++";
367 | CLANG_ENABLE_MODULES = YES;
368 | CLANG_ENABLE_OBJC_ARC = YES;
369 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
370 | CLANG_WARN_BOOL_CONVERSION = YES;
371 | CLANG_WARN_COMMA = YES;
372 | CLANG_WARN_CONSTANT_CONVERSION = YES;
373 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
374 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
375 | CLANG_WARN_EMPTY_BODY = YES;
376 | CLANG_WARN_ENUM_CONVERSION = YES;
377 | CLANG_WARN_INFINITE_RECURSION = YES;
378 | CLANG_WARN_INT_CONVERSION = YES;
379 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
380 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
381 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
382 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
383 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
384 | CLANG_WARN_STRICT_PROTOTYPES = YES;
385 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
386 | CLANG_WARN_UNREACHABLE_CODE = YES;
387 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
388 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
389 | COPY_PHASE_STRIP = NO;
390 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
391 | ENABLE_NS_ASSERTIONS = NO;
392 | ENABLE_STRICT_OBJC_MSGSEND = YES;
393 | GCC_C_LANGUAGE_STANDARD = gnu99;
394 | GCC_NO_COMMON_BLOCKS = YES;
395 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
396 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
397 | GCC_WARN_UNDECLARED_SELECTOR = YES;
398 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
399 | GCC_WARN_UNUSED_FUNCTION = YES;
400 | GCC_WARN_UNUSED_VARIABLE = YES;
401 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
402 | MTL_ENABLE_DEBUG_INFO = NO;
403 | SDKROOT = iphoneos;
404 | SUPPORTED_PLATFORMS = iphoneos;
405 | SWIFT_COMPILATION_MODE = wholemodule;
406 | SWIFT_OPTIMIZATION_LEVEL = "-O";
407 | TARGETED_DEVICE_FAMILY = "1,2";
408 | VALIDATE_PRODUCT = YES;
409 | };
410 | name = Release;
411 | };
412 | 97C147061CF9000F007C117D /* Debug */ = {
413 | isa = XCBuildConfiguration;
414 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
415 | buildSettings = {
416 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
417 | CLANG_ENABLE_MODULES = YES;
418 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
419 | ENABLE_BITCODE = NO;
420 | INFOPLIST_FILE = Runner/Info.plist;
421 | LD_RUNPATH_SEARCH_PATHS = (
422 | "$(inherited)",
423 | "@executable_path/Frameworks",
424 | );
425 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example;
426 | PRODUCT_NAME = "$(TARGET_NAME)";
427 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
428 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
429 | SWIFT_VERSION = 5.0;
430 | VERSIONING_SYSTEM = "apple-generic";
431 | };
432 | name = Debug;
433 | };
434 | 97C147071CF9000F007C117D /* Release */ = {
435 | isa = XCBuildConfiguration;
436 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
437 | buildSettings = {
438 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
439 | CLANG_ENABLE_MODULES = YES;
440 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
441 | ENABLE_BITCODE = NO;
442 | INFOPLIST_FILE = Runner/Info.plist;
443 | LD_RUNPATH_SEARCH_PATHS = (
444 | "$(inherited)",
445 | "@executable_path/Frameworks",
446 | );
447 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example;
448 | PRODUCT_NAME = "$(TARGET_NAME)";
449 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
450 | SWIFT_VERSION = 5.0;
451 | VERSIONING_SYSTEM = "apple-generic";
452 | };
453 | name = Release;
454 | };
455 | /* End XCBuildConfiguration section */
456 |
457 | /* Begin XCConfigurationList section */
458 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
459 | isa = XCConfigurationList;
460 | buildConfigurations = (
461 | 97C147031CF9000F007C117D /* Debug */,
462 | 97C147041CF9000F007C117D /* Release */,
463 | 249021D3217E4FDB00AE95B9 /* Profile */,
464 | );
465 | defaultConfigurationIsVisible = 0;
466 | defaultConfigurationName = Release;
467 | };
468 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
469 | isa = XCConfigurationList;
470 | buildConfigurations = (
471 | 97C147061CF9000F007C117D /* Debug */,
472 | 97C147071CF9000F007C117D /* Release */,
473 | 249021D4217E4FDB00AE95B9 /* Profile */,
474 | );
475 | defaultConfigurationIsVisible = 0;
476 | defaultConfigurationName = Release;
477 | };
478 | /* End XCConfigurationList section */
479 | };
480 | rootObject = 97C146E61CF9000F007C117D /* Project object */;
481 | }
482 |
--------------------------------------------------------------------------------