├── _config.yml ├── example ├── devtools_options.yaml ├── 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 ├── 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 │ │ │ │ │ └── net │ │ │ │ │ │ └── rcprogrammer │ │ │ │ │ │ └── flutter_bargraph_example │ │ │ │ │ │ └── MainActivity.kt │ │ │ │ └── AndroidManifest.xml │ │ │ ├── debug │ │ │ │ └── AndroidManifest.xml │ │ │ └── profile │ │ │ │ └── AndroidManifest.xml │ │ └── build.gradle │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ ├── .gitignore │ ├── settings.gradle │ └── build.gradle ├── .metadata ├── pubspec.yaml ├── analysis_options.yaml ├── .gitignore ├── lib │ └── main.dart ├── pubspec.lock └── README.md ├── .github ├── FUNDING.yml └── workflows │ └── flutter.yml ├── doc └── media │ ├── example.gif │ ├── example.mp4 │ └── screenshot.png ├── .metadata ├── lib ├── flutter_expandable_table.dart └── src │ ├── class │ ├── cell_details.dart │ ├── cell.dart │ ├── header.dart │ ├── row.dart │ └── controller.dart │ ├── widget_internal │ ├── cell.dart │ └── table.dart │ └── widget │ └── table.dart ├── analysis_options.yaml ├── pubspec.yaml ├── LICENSE ├── CHANGELOG.md ├── .gitignore ├── test ├── widget_test.dart └── my_app_test.dart ├── pubspec.lock └── README.md /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-hacker -------------------------------------------------------------------------------- /example/devtools_options.yaml: -------------------------------------------------------------------------------- 1 | extensions: 2 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: rickypid 2 | custom: https://buymeacoffee.com/veweju 3 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /doc/media/example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickypid/flutter_expandable_table/HEAD/doc/media/example.gif -------------------------------------------------------------------------------- /doc/media/example.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickypid/flutter_expandable_table/HEAD/doc/media/example.mp4 -------------------------------------------------------------------------------- /doc/media/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickypid/flutter_expandable_table/HEAD/doc/media/screenshot.png -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickypid/flutter_expandable_table/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/rickypid/flutter_expandable_table/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/rickypid/flutter_expandable_table/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/rickypid/flutter_expandable_table/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/rickypid/flutter_expandable_table/HEAD/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickypid/flutter_expandable_table/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickypid/flutter_expandable_table/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/rickypid/flutter_expandable_table/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickypid/flutter_expandable_table/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/rickypid/flutter_expandable_table/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/rickypid/flutter_expandable_table/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/rickypid/flutter_expandable_table/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/rickypid/flutter_expandable_table/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/rickypid/flutter_expandable_table/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/rickypid/flutter_expandable_table/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/rickypid/flutter_expandable_table/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/rickypid/flutter_expandable_table/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/rickypid/flutter_expandable_table/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/rickypid/flutter_expandable_table/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/rickypid/flutter_expandable_table/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/rickypid/flutter_expandable_table/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickypid/flutter_expandable_table/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/rickypid/flutter_expandable_table/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.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/android/app/src/main/kotlin/net/rcprogrammer/flutter_bargraph_example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package net.rcprogrammer.flutter_expandable_table_example 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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: b2e05062b6d1d73024bbd42bf974509f9ddfb383 8 | channel: master 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: b2e05062b6d1d73024bbd42bf974509f9ddfb383 8 | channel: master 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /lib/flutter_expandable_table.dart: -------------------------------------------------------------------------------- 1 | /// A Flutter widget for create an expandable table with header and first column fixed. 2 | library flutter_expandable_table; 3 | 4 | export 'src/class/row.dart'; 5 | export 'src/class/header.dart'; 6 | export 'src/class/cell.dart'; 7 | export 'src/class/controller.dart'; 8 | export 'src/class/cell_details.dart'; 9 | export 'src/widget/table.dart'; 10 | -------------------------------------------------------------------------------- /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/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/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/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_expandable_table_example 2 | description: A Flutter widget for create an expandable table with header and first column fixed. 3 | version: 2.1.0 4 | 5 | environment: 6 | sdk: ">=2.18.0 <4.0.0" 7 | 8 | dependencies: 9 | flutter: 10 | sdk: flutter 11 | 12 | dev_dependencies: 13 | flutter_expandable_table: 14 | path: ../ 15 | flutter_lints: ^4.0.0 16 | flutter_test: 17 | sdk: flutter 18 | import_sorter: ^4.6.0 19 | 20 | flutter: 21 | uses-material-design: true -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml 2 | 3 | linter: 4 | rules: 5 | - public_member_api_docs 6 | - recursive_getters 7 | - always_use_package_imports 8 | - prefer_expression_function_bodies 9 | - prefer_single_quotes 10 | - always_declare_return_types 11 | - avoid_unused_constructor_parameters 12 | - prefer_final_fields 13 | - prefer_final_locals 14 | - sort_pub_dependencies 15 | - type_annotate_public_apis 16 | - unawaited_futures 17 | - use_named_constants 18 | - use_super_parameters 19 | -------------------------------------------------------------------------------- /example/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml 2 | 3 | linter: 4 | rules: 5 | - public_member_api_docs 6 | - recursive_getters 7 | - always_use_package_imports 8 | - prefer_expression_function_bodies 9 | - prefer_single_quotes 10 | - always_declare_return_types 11 | - avoid_unused_constructor_parameters 12 | - prefer_final_fields 13 | - prefer_final_locals 14 | - sort_pub_dependencies 15 | - type_annotate_public_apis 16 | - unawaited_futures 17 | - use_named_constants 18 | - use_super_parameters 19 | -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:4.1.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /.github/workflows/flutter.yml: -------------------------------------------------------------------------------- 1 | name: Flutter 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v3 15 | - uses: subosito/flutter-action@v2 16 | with: 17 | channel: 'stable' 18 | 19 | - name: Install dependencies 20 | run: flutter pub get 21 | 22 | - name: Analyze project source 23 | run: dart analyze 24 | 25 | - name: Flutter test 26 | run: flutter test 27 | 28 | - name: Install pana 29 | run: dart pub global activate pana 30 | 31 | - name: Analyze project source with pana 32 | run: pana -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_expandable_table 2 | description: A Flutter widget for create an expandable table with header and first column fixed. 3 | version: 2.1.0 4 | repository: https://github.com/rickypid/flutter_expandable_table 5 | issue_tracker: https://github.com/rickypid/flutter_expandable_table/issues 6 | 7 | environment: 8 | sdk: ">=2.18.0 <4.0.0" 9 | flutter: ">=1.17.0" 10 | 11 | dependencies: 12 | flutter: 13 | sdk: flutter 14 | flutter_scroll_shadow: ^1.2.4 15 | linked_scroll_controller: ^0.2.0 16 | provider: ^6.1.2 17 | 18 | dev_dependencies: 19 | flutter_lints: ^4.0.0 20 | flutter_test: 21 | sdk: flutter 22 | import_sorter: ^4.6.0 23 | 24 | flutter: 25 | screenshots: 26 | - description: 'Expandable Table - screenshot 1' 27 | path: doc/media/screenshot.png -------------------------------------------------------------------------------- /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 | 8.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/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 | -------------------------------------------------------------------------------- /lib/src/class/cell_details.dart: -------------------------------------------------------------------------------- 1 | // Project imports: 2 | import 'package:flutter_expandable_table/flutter_expandable_table.dart'; 3 | 4 | /// [CellDetails] class. 5 | /// This class contains the details of a cell, such as the 6 | /// instance of the row and column it belongs to, furthermore 7 | /// if the cell is inside a nested row or column it is possible 8 | /// to access the instance of the parent row or column 9 | class CellDetails { 10 | /// [header] is the instance of the column it belongs to. 11 | final ExpandableTableHeader? header; 12 | 13 | /// [row] is the instance of the row it belongs to. 14 | final ExpandableTableRow? row; 15 | 16 | /// [headerParent] is the instance of the parent column it 17 | /// belongs to (Only if it is inside a nested column). 18 | final ExpandableTableHeader? headerParent; 19 | 20 | /// [rowParent] is the instance of the parent row it 21 | /// belongs to (Only if it is inside a nested row). 22 | final ExpandableTableRow? rowParent; 23 | 24 | /// [CellDetails] class constructor. 25 | /// This class contains the details of a cell, such as the 26 | /// instance of the row and column it belongs to, furthermore 27 | /// if the cell is inside a nested row or column it is possible 28 | /// to access the instance of the parent row or column 29 | CellDetails({ 30 | required this.headerParent, 31 | required this.rowParent, 32 | required this.header, 33 | required this.row, 34 | }); 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright © 2021 Riccardo Cucia (https://www.rcprogrammer.net/) All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without modification, 4 | are permitted provided that the following conditions are met: 5 | * Redistributions of source code must retain the above copyright notice, this 6 | list of conditions and the following disclaimer. 7 | * Redistributions in binary form must reproduce the above copyright notice, this 8 | list of conditions and the following disclaimer in the documentation and/or 9 | other materials provided with the distribution. 10 | * Neither the name of Riccardo Cucia nor the names of its contributors may be used to 11 | endorse or promote products derived from this software without specific prior 12 | written permission. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 21 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /lib/src/class/cell.dart: -------------------------------------------------------------------------------- 1 | // Flutter imports: 2 | import 'package:flutter/material.dart'; 3 | 4 | // Project imports: 5 | import 'package:flutter_expandable_table/src/class/cell_details.dart'; 6 | 7 | /// [ExpandableTableCell] class. 8 | /// This class defines a single table cell. 9 | /// You can define a child Widget or pass a builder function to build 10 | /// your widget in the cell. Using the builder it is possible to access 11 | /// the details of the cell itself. 12 | class ExpandableTableCell extends ChangeNotifier { 13 | /// [builder] By defining the builder function it is possible to return 14 | /// the Widget to be inserted inside the cell and access the details 15 | /// of the cell itself during the build.. 16 | /// `optional` 17 | final CellBuilder? builder; 18 | 19 | /// [child] Widget to insert inside the cell 20 | /// `optional` 21 | final Widget? child; 22 | 23 | /// [ExpandableTableCell] class constructor. 24 | /// This class defines a single table cell. 25 | /// You can define a child Widget or pass a builder function to build 26 | /// your widget in the cell. Using the builder it is possible to access 27 | /// the details of the cell itself. 28 | ExpandableTableCell({ 29 | this.builder, 30 | this.child, 31 | }) : assert((builder != null || child != null) && 32 | (builder == null || child == null)); 33 | 34 | /// [build] method for building the cell contents 35 | Widget build(BuildContext context, CellDetails details) => 36 | child ?? builder!(context, details); 37 | } 38 | 39 | /// [CellBuilder] type definition. 40 | typedef CellBuilder = Widget Function( 41 | BuildContext context, CellDetails details); 42 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Flutter Expandable Table 2 | 3 | ## [2.1.0] - 2024-07-03 4 | #### [@RichiB20](https://github.com/RichiB20) 5 | - Fixed vertical Scrollbar and added horizontal Scrollbar. 6 | - Added `trackVisibilityScrollbar`,`thumbVisibilityScrollbar` and `expanded` parameters. 7 | - Fixed table size. 8 | 9 | ## [2.0.1] - 2023-09-04 10 | #### [@rickypid](https://github.com/rickypid) 11 | - Update flutter_scroll_shadow dependency, add GitHub Actions. 12 | 13 | ## [2.0.0] - 2023-06-01 14 | #### [@rickypid](https://github.com/rickypid) 15 | - Release 2.0.0, improved documentation and tests. 16 | 17 | ## [2.0.0-beta.2] - 2023-05-19 18 | #### [@rickypid](https://github.com/rickypid) 19 | - Release 2.0.0, improved functionality and structure. 20 | 21 | ## [2.0.0-beta.1] - 2023-05-13 22 | #### [@rickypid](https://github.com/rickypid) 23 | - Release 2.0.0, improved functionality and structure. 24 | 25 | ## [1.1.1] - 2022-06-07 26 | #### [@allato](https://github.com/allato) 27 | - Format code. 28 | 29 | ## [1.1.0] - 2022-06-07 30 | #### [@RichiB20](https://github.com/RichiB20) 31 | - implemented property `visibleScrollbar`. 32 | 33 | ## [1.0.2] - 2022-02-03 34 | #### [@rickypid](https://github.com/rickypid) 35 | - Remove unnecessary imports and unnecessary null check, upgraded dependency. 36 | 37 | ## [1.0.1] - 2022-01-25 38 | #### [@rickypid](https://github.com/rickypid) 39 | - Improved documentations. 40 | 41 | ## [1.0.0] - 2021-07-23 42 | #### [@rickypid](https://github.com/rickypid) 43 | - Stable release. 44 | 45 | ## [0.0.2-alpha] - 2021-07-20 46 | #### [@rickypid](https://github.com/rickypid) 47 | - Change README.md. 48 | 49 | ## [0.0.1-alpha] - 2021-07-20 50 | #### [@rickypid](https://github.com/rickypid) 51 | - Initial release. -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | flutter_expandable_table_example 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | build/ 32 | 33 | # Android related 34 | **/android/**/gradle-wrapper.jar 35 | **/android/.gradle 36 | **/android/captures/ 37 | **/android/gradlew 38 | **/android/gradlew.bat 39 | **/android/local.properties 40 | **/android/**/GeneratedPluginRegistrant.java 41 | 42 | # iOS/XCode related 43 | **/ios/**/*.mode1v3 44 | **/ios/**/*.mode2v3 45 | **/ios/**/*.moved-aside 46 | **/ios/**/*.pbxuser 47 | **/ios/**/*.perspectivev3 48 | **/ios/**/*sync/ 49 | **/ios/**/.sconsign.dblite 50 | **/ios/**/.tags* 51 | **/ios/**/.vagrant/ 52 | **/ios/**/DerivedData/ 53 | **/ios/**/Icon? 54 | **/ios/**/Pods/ 55 | **/ios/**/.symlinks/ 56 | **/ios/**/profile 57 | **/ios/**/xcuserdata 58 | **/ios/.generated/ 59 | **/ios/Flutter/App.framework 60 | **/ios/Flutter/Flutter.framework 61 | **/ios/Flutter/Flutter.podspec 62 | **/ios/Flutter/Generated.xcconfig 63 | **/ios/Flutter/app.flx 64 | **/ios/Flutter/app.zip 65 | **/ios/Flutter/flutter_assets/ 66 | **/ios/Flutter/flutter_export_environment.sh 67 | **/ios/ServiceDefinitions.json 68 | **/ios/Runner/GeneratedPluginRegistrant.* 69 | 70 | # Exceptions to above rules. 71 | !**/ios/**/default.mode1v3 72 | !**/ios/**/default.mode2v3 73 | !**/ios/**/default.pbxuser 74 | !**/ios/**/default.perspectivev3 75 | -------------------------------------------------------------------------------- /lib/src/widget_internal/cell.dart: -------------------------------------------------------------------------------- 1 | // Flutter imports: 2 | import 'package:flutter/material.dart'; 3 | 4 | // Package imports: 5 | import 'package:provider/provider.dart'; 6 | 7 | // Project imports: 8 | import 'package:flutter_expandable_table/flutter_expandable_table.dart'; 9 | 10 | /// [ExpandableTableCellWidget] it is the widget that builds the table cell. 11 | class ExpandableTableCellWidget extends StatelessWidget { 12 | /// [builder] method for building cell content. 13 | final Function(BuildContext context, CellDetails details) builder; 14 | 15 | /// [height] cell height. 16 | final double height; 17 | 18 | /// [width] cell width. 19 | final double width; 20 | 21 | /// [onTap] tap event. 22 | final VoidCallback? onTap; 23 | 24 | /// [header] header of the table this cell belongs to. 25 | final ExpandableTableHeader? header; 26 | 27 | /// [row] row of the table this cell belongs to. 28 | final ExpandableTableRow? row; 29 | 30 | /// [ExpandableTableCellWidget] widget constructor. 31 | const ExpandableTableCellWidget({ 32 | super.key, 33 | required this.builder, 34 | required this.height, 35 | required this.width, 36 | this.onTap, 37 | this.header, 38 | this.row, 39 | }); 40 | 41 | @override 42 | Widget build(BuildContext context) => GestureDetector( 43 | onTap: onTap, 44 | child: AnimatedContainer( 45 | duration: context.watch().duration, 46 | curve: context.watch().curve, 47 | width: header?.visible == false ? 0 : width, 48 | height: row?.visible == false ? 0 : height, 49 | child: builder( 50 | context, 51 | CellDetails( 52 | headerParent: header?.parent, 53 | rowParent: row?.parent, 54 | header: header, 55 | row: row, 56 | ), 57 | ), 58 | ), 59 | ); 60 | } 61 | -------------------------------------------------------------------------------- /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 30 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | defaultConfig { 36 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 37 | applicationId "net.rcprogrammer.flutter_expandable_table_example" 38 | minSdkVersion 16 39 | targetSdkVersion 30 40 | versionCode flutterVersionCode.toInteger() 41 | versionName flutterVersionName 42 | } 43 | 44 | buildTypes { 45 | release { 46 | // TODO: Add your own signing config for the release build. 47 | // Signing with the debug keys for now, so `flutter run --release` works. 48 | signingConfig signingConfigs.debug 49 | } 50 | } 51 | } 52 | 53 | flutter { 54 | source '../..' 55 | } 56 | 57 | dependencies { 58 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 59 | } 60 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 13 | 17 | 21 | 26 | 30 | 31 | 32 | 33 | 34 | 35 | 37 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /lib/src/class/header.dart: -------------------------------------------------------------------------------- 1 | // Flutter imports: 2 | import 'package:flutter/material.dart'; 3 | 4 | // Project imports: 5 | import 'package:flutter_expandable_table/flutter_expandable_table.dart'; 6 | 7 | /// [ExpandableTableHeader] class. 8 | /// This class defines a single table header. 9 | class ExpandableTableHeader extends ChangeNotifier { 10 | /// [cell] Defines the contents of the column header cell. 11 | /// `required` 12 | final ExpandableTableCell cell; 13 | late List? _children; 14 | 15 | /// [children] returns nested columns to this one. 16 | List? get children => _children; 17 | 18 | /// [children] defines columns nested to this, populating 19 | /// this list will create an expandable column. 20 | set children(List? value) { 21 | _removeChildrenListener(); 22 | _children = value; 23 | _addChildrenListener(); 24 | notifyListeners(); 25 | } 26 | 27 | /// [width] defines the width of the column, if not specified 28 | /// the default width defined in the table will be used. 29 | /// `optional` 30 | final double? width; 31 | 32 | /// [hideWhenExpanded] Defines whether this column should be 33 | /// hidden when nested columns are expanded. Attention, by setting 34 | /// this property to true it will be necessary to implement manual 35 | /// management of column expansion. 36 | /// `Default: false` 37 | final bool hideWhenExpanded; 38 | 39 | /// [disableDefaultOnTapExpansion] Defines whether to disable the 40 | /// standard expand interaction, setting to true will require 41 | /// manually implementing an expand logic. 42 | /// `Default: false` 43 | final bool disableDefaultOnTapExpansion; 44 | 45 | late bool _childrenExpanded; 46 | 47 | /// [childrenExpanded] returns true if the children nested to this column have been expanded. 48 | bool get childrenExpanded => 49 | children?.isNotEmpty == true && _childrenExpanded; 50 | 51 | /// [childrenExpanded] allows you to expand or not the columns nested within this one. 52 | set childrenExpanded(bool value) { 53 | if (children != null) { 54 | _childrenExpanded = value; 55 | if (!_childrenExpanded) { 56 | for (var child in children!) { 57 | child.childrenExpanded = false; 58 | } 59 | } 60 | notifyListeners(); 61 | } 62 | } 63 | 64 | ExpandableTableHeader? _parent; 65 | 66 | /// [parent] if this column is nested within another, 67 | /// the instance of the parent column is returned 68 | ExpandableTableHeader? get parent => _parent; 69 | 70 | /// [index] indicates the current index of this column, referring to the parent. 71 | int? index; 72 | 73 | /// [ExpandableTableHeader] class constructor. 74 | /// This class defines a single table header. 75 | ExpandableTableHeader({ 76 | required this.cell, 77 | List? children, 78 | this.width, 79 | this.hideWhenExpanded = false, 80 | bool childrenExpanded = false, 81 | this.disableDefaultOnTapExpansion = false, 82 | }) { 83 | _childrenExpanded = childrenExpanded; 84 | _children = children; 85 | _addChildrenListener(); 86 | } 87 | 88 | void _addChildrenListener() { 89 | if (_children != null) { 90 | for (var i = 0; i < _children!.length; i++) { 91 | children![i]._parent = this; 92 | _children![i].addListener(_listener); 93 | _children![i].index = i; 94 | } 95 | } 96 | } 97 | 98 | void _removeChildrenListener() { 99 | if (_children != null) { 100 | for (var child in _children!) { 101 | child.removeListener(_listener); 102 | } 103 | } 104 | } 105 | 106 | @override 107 | void dispose() { 108 | _removeChildrenListener(); 109 | super.dispose(); 110 | } 111 | 112 | void _listener() => notifyListeners(); 113 | 114 | /// [columnsCount] returns the number of columns, this one and 115 | /// all those nested within it. 116 | int get columnsCount { 117 | int count = 1; 118 | if (children != null) { 119 | for (var e in children!) { 120 | count += e.columnsCount; 121 | } 122 | } 123 | return count; 124 | } 125 | 126 | /// [visibleColumnsCount] returns the number of columns currently visible, 127 | /// this one and all those nested within it. 128 | int get visibleColumnsCount { 129 | int count = childrenExpanded && hideWhenExpanded ? 0 : 1; 130 | if (children != null) { 131 | for (var e in children!) { 132 | count += e.visibleColumnsCount; 133 | } 134 | } 135 | return count; 136 | } 137 | 138 | /// [visible] returns true if this column is currently visible. 139 | bool get visible => 140 | (!childrenExpanded || !hideWhenExpanded) && 141 | (parent == null || parent?.childrenExpanded == true); 142 | 143 | /// [address] returns a list of integers, each of which the position 144 | /// referred to the parent column, each nesting adds an element to the 145 | /// list, this element will be the address of the column with respect to the parent. 146 | List get address => (parent?.address ?? [])..add(index ?? -1); 147 | 148 | /// [toggleExpand] this method allows you to reverse the 149 | /// expansion or not of the child columns. 150 | void toggleExpand() => childrenExpanded = !childrenExpanded; 151 | } 152 | -------------------------------------------------------------------------------- /lib/src/class/row.dart: -------------------------------------------------------------------------------- 1 | // Flutter imports: 2 | import 'package:flutter/material.dart'; 3 | 4 | // Project imports: 5 | import 'package:flutter_expandable_table/flutter_expandable_table.dart'; 6 | 7 | /// [ExpandableTableRow] class. 8 | /// This class defines a single table row. 9 | class ExpandableTableRow extends ChangeNotifier { 10 | /// [firstCell] defines the contents of the first cell, this cell 11 | /// is the cell that remains fixed during horizontal scrolling.. 12 | /// `required` 13 | final ExpandableTableCell firstCell; 14 | 15 | /// [cells] defines the cells in the row, excluding the first one on the left. 16 | /// The length of this list must be identical to the total headers, including nested ones. 17 | /// `optional, if it is not defined, the legend must be defined` 18 | final List? cells; 19 | 20 | late List? _children; 21 | 22 | /// [children] returns nested rows to this one. 23 | List? get children => _children; 24 | 25 | /// [children] defines rows nested to this, populating 26 | /// this list will create an expandable row. 27 | set children(List? value) { 28 | _removeChildrenListener(); 29 | _children = value; 30 | _addChildrenListener(); 31 | notifyListeners(); 32 | } 33 | 34 | /// [legend] defines the object to insert in place of the cells of the row, used to 35 | /// create separation or display of totals for example. 36 | /// `optional, if it is not defined, the [cells] must be defined` 37 | final Widget? legend; 38 | 39 | /// [height] defines the height of the row, if not specified 40 | /// the default height defined in the table will be used. 41 | /// `optional` 42 | final double? height; 43 | 44 | /// [hideWhenExpanded] Defines whether this row should be 45 | /// hidden when nested rows are expanded. Attention, by setting 46 | /// this property to true it will be necessary to implement manual 47 | /// management of column expansion. 48 | /// `Default: false` 49 | final bool hideWhenExpanded; 50 | 51 | /// [disableDefaultOnTapExpansion] Defines whether to disable the 52 | /// standard expand interaction, setting to true will require 53 | /// manually implementing an expand logic. 54 | /// `Default: false` 55 | final bool disableDefaultOnTapExpansion; 56 | 57 | late bool _childrenExpanded; 58 | 59 | /// [childrenExpanded] returns true if the children nested to this row have been expanded. 60 | bool get childrenExpanded => 61 | children?.isNotEmpty == true && _childrenExpanded; 62 | 63 | /// [childrenExpanded] allows you to expand or not the rows nested within this one. 64 | set childrenExpanded(bool value) { 65 | if (children != null) { 66 | _childrenExpanded = value; 67 | if (!_childrenExpanded) { 68 | for (var child in children!) { 69 | child.childrenExpanded = false; 70 | } 71 | } 72 | notifyListeners(); 73 | } 74 | } 75 | 76 | ExpandableTableRow? _parent; 77 | 78 | /// [parent] if this row is nested within another, 79 | /// the instance of the parent row is returned 80 | ExpandableTableRow? get parent => _parent; 81 | 82 | /// [index] indicates the current index of this row, referring to the parent. 83 | int? index; 84 | 85 | /// [ExpandableTableRow] class constructor. 86 | /// This class defines a single table row. 87 | ExpandableTableRow({ 88 | required this.firstCell, 89 | this.cells, 90 | this.legend, 91 | List? children, 92 | this.height, 93 | this.hideWhenExpanded = false, 94 | bool childrenExpanded = false, 95 | this.disableDefaultOnTapExpansion = false, 96 | }) : assert((cells != null || legend != null) && 97 | (cells == null || legend == null)) { 98 | _childrenExpanded = childrenExpanded; 99 | _children = children; 100 | _addChildrenListener(); 101 | } 102 | 103 | void _addChildrenListener() { 104 | if (_children != null) { 105 | for (var i = 0; i < _children!.length; i++) { 106 | children![i]._parent = this; 107 | _children![i].addListener(_listener); 108 | _children![i].index = i; 109 | } 110 | } 111 | } 112 | 113 | void _removeChildrenListener() { 114 | if (_children != null) { 115 | for (var child in _children!) { 116 | child.removeListener(_listener); 117 | } 118 | } 119 | } 120 | 121 | @override 122 | void dispose() { 123 | _removeChildrenListener(); 124 | super.dispose(); 125 | } 126 | 127 | void _listener() => notifyListeners(); 128 | 129 | /// [rowsCount] returns the number of rows, this one and 130 | /// all those nested within it. 131 | int get rowsCount { 132 | int count = 1; 133 | if (children != null) { 134 | for (var e in children!) { 135 | count += e.visibleRowsCount; 136 | } 137 | } 138 | return count; 139 | } 140 | 141 | /// [visibleRowsCount] returns the number of rows currently visible, 142 | /// this one and all those nested within it. 143 | int get visibleRowsCount { 144 | int count = childrenExpanded && hideWhenExpanded ? 0 : 1; 145 | if (children != null) { 146 | for (var e in children!) { 147 | count += e.visibleRowsCount; 148 | } 149 | } 150 | return count; 151 | } 152 | 153 | /// [cellsCount] returns the number of cells in the row, excluding the first. 154 | int? get cellsCount => cells?.length; 155 | 156 | /// [visible] returns true if this row is currently visible. 157 | bool get visible => 158 | (!childrenExpanded || !hideWhenExpanded) && 159 | (parent == null || parent?.childrenExpanded == true); 160 | 161 | /// [address] returns a list of integers, each of which the position 162 | /// referred to the parent row, each nesting adds an element to the 163 | /// list, this element will be the address of the row with respect to the parent. 164 | List get address => (parent?.address ?? [])..add(index ?? 0); 165 | 166 | /// [toggleExpand] this method allows you to reverse the 167 | /// expansion or not of the child rows. 168 | void toggleExpand() => childrenExpanded = !childrenExpanded; 169 | } 170 | -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // Flutter imports: 2 | import 'package:flutter/material.dart'; 3 | 4 | // Package imports: 5 | import 'package:flutter_test/flutter_test.dart'; 6 | 7 | // Project imports: 8 | import 'my_app_test.dart'; 9 | 10 | void main() { 11 | const widget = MyApp(); 12 | group('Simple table', () { 13 | testWidgets('First header table cell', (tester) async { 14 | await tester.binding.setSurfaceSize(const Size(1920, 1080)); 15 | await tester.pumpWidget(widget); 16 | expect(find.text('Simple\nTable'), findsOneWidget); 17 | }); 18 | testWidgets('Visible columns and rows limits', (tester) async { 19 | await tester.binding.setSurfaceSize(const Size(1920, 1080)); 20 | await tester.pumpWidget(widget); 21 | expect(find.text('_Column 0').hitTestable(), findsOneWidget); 22 | expect(find.text('_Column 3').hitTestable(), findsOneWidget); 23 | expect(find.text('_Column 4').hitTestable(), findsNothing); 24 | expect(find.text('_Row 0').hitTestable(), findsOneWidget); 25 | expect(find.text('_Row 12').hitTestable(), findsOneWidget); 26 | expect(find.text('_Row 13').hitTestable(), findsNothing); 27 | expect(find.text('_Cell 0:0').hitTestable(), findsOneWidget); 28 | expect(find.text('_Cell 0:3').hitTestable(), findsOneWidget); 29 | expect(find.text('_Cell 0:4').hitTestable(), findsNothing); 30 | expect(find.text('_Cell 12:0').hitTestable(), findsOneWidget); 31 | expect(find.text('_Cell 12:3').hitTestable(), findsOneWidget); 32 | expect(find.text('_Cell 12:4').hitTestable(), findsNothing); 33 | }); 34 | testWidgets('First column vertical scroll', (tester) async { 35 | await tester.binding.setSurfaceSize(const Size(1920, 1080)); 36 | await tester.pumpWidget(widget); 37 | expect(find.text('_Row 0').hitTestable(), findsOneWidget); 38 | expect(find.text('_Cell 0:0').hitTestable(), findsOneWidget); 39 | expect(find.text('_Row 18').hitTestable(), findsNothing); 40 | expect(find.text('_Cell 18:0').hitTestable(), findsNothing); 41 | await tester.dragFrom( 42 | tester.getCenter(find.text('_Row 12')), const Offset(0, -500)); 43 | await tester.pump(); 44 | expect(find.text('_Row 18').hitTestable(), findsOneWidget); 45 | expect(find.text('_Cell 18:0').hitTestable(), findsOneWidget); 46 | expect(find.text('_Row 0').hitTestable(), findsNothing); 47 | expect(find.text('_Cell 0:0').hitTestable(), findsNothing); 48 | }); 49 | testWidgets('Header horizontal scroll', (tester) async { 50 | await tester.binding.setSurfaceSize(const Size(1920, 1080)); 51 | await tester.pumpWidget(widget); 52 | expect(find.text('_Column 0').hitTestable(), findsOneWidget); 53 | expect(find.text('_Cell 0:0').hitTestable(), findsOneWidget); 54 | expect(find.text('_Column 7').hitTestable(), findsNothing); 55 | expect(find.text('_Cell 0:7').hitTestable(), findsNothing); 56 | await tester.dragFrom( 57 | tester.getCenter(find.text('_Column 3')), const Offset(-730, 0)); 58 | await tester.pump(); 59 | expect(find.text('_Column 0').hitTestable(), findsNothing); 60 | expect(find.text('_Cell 0:0').hitTestable(), findsNothing); 61 | expect(find.text('_Column 7').hitTestable(), findsOneWidget); 62 | expect(find.text('_Cell 0:7').hitTestable(), findsOneWidget); 63 | }); 64 | testWidgets('Body vertical scroll', (tester) async { 65 | await tester.binding.setSurfaceSize(const Size(1920, 1080)); 66 | await tester.pumpWidget(widget); 67 | expect(find.text('_Row 0').hitTestable(), findsOneWidget); 68 | expect(find.text('_Cell 0:0').hitTestable(), findsOneWidget); 69 | expect(find.text('_Row 18').hitTestable(), findsNothing); 70 | expect(find.text('_Cell 18:0').hitTestable(), findsNothing); 71 | await tester.dragFrom( 72 | tester.getCenter(find.text('_Cell 12:0')), const Offset(0, -500)); 73 | await tester.pump(); 74 | expect(find.text('_Row 18').hitTestable(), findsOneWidget); 75 | expect(find.text('_Cell 18:0').hitTestable(), findsOneWidget); 76 | expect(find.text('_Row 0').hitTestable(), findsNothing); 77 | expect(find.text('_Cell 0:0').hitTestable(), findsNothing); 78 | }); 79 | testWidgets('Body horizontal scroll', (tester) async { 80 | await tester.binding.setSurfaceSize(const Size(1920, 1080)); 81 | await tester.pumpWidget(widget); 82 | expect(find.text('_Column 0').hitTestable(), findsOneWidget); 83 | expect(find.text('_Cell 0:0').hitTestable(), findsOneWidget); 84 | expect(find.text('_Column 7').hitTestable(), findsNothing); 85 | expect(find.text('_Cell 0:7').hitTestable(), findsNothing); 86 | await tester.dragFrom( 87 | tester.getCenter(find.text('_Cell 0:3')), const Offset(-730, 0)); 88 | await tester.pump(); 89 | expect(find.text('_Column 0').hitTestable(), findsNothing); 90 | expect(find.text('_Cell 0:0').hitTestable(), findsNothing); 91 | expect(find.text('_Column 7').hitTestable(), findsOneWidget); 92 | expect(find.text('_Cell 0:7').hitTestable(), findsOneWidget); 93 | }); 94 | }); 95 | group('Expandable table', () { 96 | testWidgets('First header table cell', (tester) async { 97 | await tester.binding.setSurfaceSize(const Size(1920, 1080)); 98 | await tester.pumpWidget(widget); 99 | expect(find.text('Expandable\nTable'), findsOneWidget); 100 | }); 101 | testWidgets('Visible columns and rows limits', (tester) async { 102 | await tester.binding.setSurfaceSize(const Size(1920, 1080)); 103 | await tester.pumpWidget(widget); 104 | expect(find.text('&Column 0').hitTestable(), findsOneWidget); 105 | expect(find.text('&Column 3').hitTestable(), findsOneWidget); 106 | expect(find.text('&Column 4').hitTestable(), findsNothing); 107 | expect(find.text('&Row 0').hitTestable(), findsOneWidget); 108 | expect(find.text('&Row 5').hitTestable(), findsOneWidget); 109 | expect(find.text('&Cell 0:0').hitTestable(), findsOneWidget); 110 | expect(find.text('&Cell 0:5').hitTestable(), findsOneWidget); 111 | expect(find.text('&Cell 0:6').hitTestable(), findsNothing); 112 | expect(find.text('&Cell 5:0').hitTestable(), findsOneWidget); 113 | expect(find.text('&Cell 5:5').hitTestable(), findsOneWidget); 114 | expect(find.text('&Cell 5:6').hitTestable(), findsNothing); 115 | }); /* 116 | testWidgets('Rows expansion', (tester) async { 117 | await tester.binding.setSurfaceSize(const Size(1920, 1080)); 118 | await tester.pumpWidget(widget); 119 | expect(find.text('&Row 2').hitTestable(), findsOneWidget); 120 | expect(find.text('&Sub &Row 0').hitTestable(), findsNothing); 121 | expect(find.text('&Cell 0:0').hitTestable(), findsOneWidget); 122 | await tester.tap(find.text('&Row 2')); 123 | await tester.pump(const Duration(seconds: 1)); 124 | expect(find.text('&Row 2').hitTestable(), findsOneWidget); 125 | expect(find.text('&Cell 0:0').hitTestable(), findsNWidgets(2)); 126 | expect(find.textContaining('&Sub &Row 0').hitTestable(), findsOneWidget); 127 | });*/ 128 | }); 129 | } 130 | -------------------------------------------------------------------------------- /lib/src/widget/table.dart: -------------------------------------------------------------------------------- 1 | // Flutter imports: 2 | import 'package:flutter/material.dart'; 3 | 4 | // Package imports: 5 | import 'package:provider/provider.dart'; 6 | 7 | // Project imports: 8 | import 'package:flutter_expandable_table/src/class/cell.dart'; 9 | import 'package:flutter_expandable_table/src/class/controller.dart'; 10 | import 'package:flutter_expandable_table/src/class/header.dart'; 11 | import 'package:flutter_expandable_table/src/class/row.dart'; 12 | import 'package:flutter_expandable_table/src/widget_internal/table.dart'; 13 | 14 | /// [ExpandableTable] class. 15 | class ExpandableTable extends StatefulWidget { 16 | /// [firstHeaderCell] is the top left cell, i.e. the first header cell. 17 | /// Not to be used if the [controller] is used. 18 | /// `optional` 19 | final ExpandableTableCell? firstHeaderCell; 20 | 21 | /// [headers] contains the list of all column headers, 22 | /// each one of these can contain a list of further headers, 23 | /// this allows you to create nested and expandable columns. 24 | /// Not to be used if the [controller] is used. 25 | /// `optional` 26 | final List? headers; 27 | 28 | /// [rows] contains the list of all the rows of the table, 29 | /// each of these can contain a list of further rows, 30 | /// this allows you to create nested and expandable rows. 31 | /// Not to be used if the [controller] is used. 32 | /// `optional` 33 | final List? rows; 34 | 35 | /// [headerHeight] is the height of each column header, i.e. the first row. 36 | /// `Default: 188` 37 | final double headerHeight; 38 | 39 | /// [firstColumnWidth] determines first Column width size. 40 | /// 41 | /// Default: [200] 42 | final double firstColumnWidth; 43 | 44 | /// [defaultsColumnWidth] defines the default width of all columns, 45 | /// it is possible to redefine it for each individual column. 46 | /// Default: [120] 47 | final double defaultsColumnWidth; 48 | 49 | /// [defaultsRowHeight] defines the default height of all rows, 50 | /// it is possible to redefine it for every single row. 51 | /// Default: [50] 52 | final double defaultsRowHeight; 53 | 54 | /// [duration] determines duration rendered animation of Rows/Columns expansion. 55 | /// 56 | /// Default: [500ms] 57 | final Duration duration; 58 | 59 | /// [curve] determines rendered curve animation of Rows/Columns expansion. 60 | /// 61 | /// Default: [Curves.fastOutSlowIn] 62 | final Curve curve; 63 | 64 | /// [scrollShadowDuration] determines duration rendered animation of shadows. 65 | /// 66 | /// Default: [500ms] 67 | final Duration scrollShadowDuration; 68 | 69 | /// [scrollShadowCurve] determines rendered curve animation of shadows. 70 | /// 71 | /// Default: [Curves.fastOutSlowIn] 72 | final Curve scrollShadowCurve; 73 | 74 | /// [scrollShadowColor] determines rendered color of shadows. 75 | /// 76 | /// Default: [Colors.transparent] 77 | final Color scrollShadowColor; 78 | 79 | /// [scrollShadowSize] determines size of shadows. 80 | /// 81 | /// Default: [10] 82 | final double scrollShadowSize; 83 | 84 | /// [visibleScrollbar] determines visibility of horizontal and vertical scrollbars. 85 | /// 86 | /// Default: [false] 87 | final bool visibleScrollbar; 88 | 89 | /// [trackVisibilityScrollbar] indicates that the scrollbar track should be visible. 90 | /// 91 | /// 'optional' 92 | final bool? trackVisibilityScrollbar; 93 | 94 | /// [thumbVisibilityScrollbar] indicates that the scrollbar thumb should be visible, even when a scroll is not underway. 95 | /// 96 | /// 'optional' 97 | final bool? thumbVisibilityScrollbar; 98 | 99 | /// [expanded] indicates that the table expands, so it fills the available space along the horizontal and vertical axes. 100 | /// 101 | /// Default: [true] 102 | final bool expanded; 103 | 104 | /// [controller] specifies the external controller of the table, allows 105 | /// you to dynamically manage the data in the table externally. 106 | /// Do not use if [firstHeaderCell], [headers] and [rows] are passed 107 | /// 'optional' 108 | final ExpandableTableController? controller; 109 | 110 | /// [ExpandableTable] class constructor. 111 | /// Required: 112 | /// - [firstHeaderCell] 113 | /// - [rows] 114 | /// - [headers] 115 | /// ```dart 116 | /// return ExpandableTable( 117 | /// firstHeaderCell: ExpandableTableCell( 118 | /// child: Text('Simple\nTable'), 119 | /// ), 120 | /// headers: headers, 121 | /// rows: rows, 122 | /// ); 123 | /// ``` 124 | const ExpandableTable({ 125 | super.key, 126 | this.firstHeaderCell, 127 | this.headers, 128 | this.rows, 129 | this.controller, 130 | this.headerHeight = 188, 131 | this.firstColumnWidth = 200, 132 | this.defaultsColumnWidth = 120, 133 | this.defaultsRowHeight = 50, 134 | this.duration = const Duration(milliseconds: 500), 135 | this.curve = Curves.fastOutSlowIn, 136 | this.scrollShadowDuration = const Duration(milliseconds: 500), 137 | this.scrollShadowCurve = Curves.fastOutSlowIn, 138 | this.scrollShadowColor = Colors.transparent, 139 | this.scrollShadowSize = 10, 140 | this.visibleScrollbar = false, 141 | this.trackVisibilityScrollbar, 142 | this.thumbVisibilityScrollbar, 143 | this.expanded = true, 144 | }) : assert(((firstHeaderCell != null && rows != null && headers != null) || 145 | controller != null) && 146 | !(thumbVisibilityScrollbar == false && 147 | (trackVisibilityScrollbar ?? false))); 148 | 149 | @override 150 | State createState() => _ExpandableTableState(); 151 | } 152 | 153 | class _ExpandableTableState extends State { 154 | @override 155 | void initState() { 156 | if (widget.controller == null) { 157 | final int totalColumns = 158 | widget.headers!.map((e) => e.columnsCount).fold(0, (a, b) => a + b); 159 | for (int i = 0; i < widget.rows!.length; i++) { 160 | if (widget.rows![i].cellsCount != null && 161 | widget.rows![i].cellsCount != totalColumns) { 162 | throw FormatException( 163 | 'Row $i cells count ${widget.rows![i].cellsCount} <> $totalColumns header cell count.'); 164 | } 165 | } 166 | } 167 | super.initState(); 168 | } 169 | 170 | @override 171 | Widget build(BuildContext context) => widget.controller != null 172 | ? ChangeNotifierProvider.value( 173 | value: widget.controller!, 174 | builder: (context, child) => const InternalTable(), 175 | ) 176 | : ChangeNotifierProvider( 177 | create: (context) => ExpandableTableController( 178 | firstHeaderCell: widget.firstHeaderCell!, 179 | headers: widget.headers!, 180 | rows: widget.rows!, 181 | duration: widget.duration, 182 | curve: widget.curve, 183 | scrollShadowDuration: widget.scrollShadowDuration, 184 | scrollShadowFadeInCurve: widget.scrollShadowCurve, 185 | scrollShadowFadeOutCurve: widget.scrollShadowCurve, 186 | scrollShadowColor: widget.scrollShadowColor, 187 | scrollShadowSize: widget.scrollShadowSize, 188 | firstColumnWidth: widget.firstColumnWidth, 189 | defaultsColumnWidth: widget.defaultsColumnWidth, 190 | defaultsRowHeight: widget.defaultsRowHeight, 191 | headerHeight: widget.headerHeight, 192 | visibleScrollbar: widget.visibleScrollbar, 193 | trackVisibilityScrollbar: widget.trackVisibilityScrollbar, 194 | thumbVisibilityScrollbar: widget.thumbVisibilityScrollbar, 195 | expanded: widget.expanded, 196 | ), 197 | builder: (context, child) => const InternalTable(), 198 | ); 199 | } 200 | -------------------------------------------------------------------------------- /test/my_app_test.dart: -------------------------------------------------------------------------------- 1 | // Flutter imports: 2 | import 'package:flutter/gestures.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | // Project imports: 6 | import 'package:flutter_expandable_table/flutter_expandable_table.dart'; 7 | 8 | // ignore: depend_on_referenced_packages 9 | 10 | const Color primaryColor = Color(0xFF1e2f36); //corner 11 | const Color accentColor = Color(0xFF0d2026); //background 12 | const TextStyle textStyle = TextStyle(color: Colors.white); 13 | const TextStyle textStyleSubItems = TextStyle(color: Colors.grey); 14 | 15 | void main() => runApp(const MyApp()); 16 | 17 | class MyApp extends StatelessWidget { 18 | const MyApp({super.key}); 19 | 20 | @override 21 | Widget build(BuildContext context) => MaterialApp( 22 | title: 'ExpandableTable Example', 23 | theme: ThemeData(primarySwatch: Colors.grey), 24 | home: const MyHomePage(), 25 | scrollBehavior: AppCustomScrollBehavior(), 26 | ); 27 | } 28 | 29 | class MyHomePage extends StatefulWidget { 30 | const MyHomePage({super.key}); 31 | 32 | @override 33 | State createState() => _MyHomePageState(); 34 | } 35 | 36 | class DefaultCellCard extends StatelessWidget { 37 | final Widget child; 38 | 39 | const DefaultCellCard({ 40 | super.key, 41 | required this.child, 42 | }); 43 | 44 | @override 45 | Widget build(BuildContext context) => Container( 46 | color: primaryColor, 47 | margin: const EdgeInsets.all(1), 48 | child: child, 49 | ); 50 | } 51 | 52 | class _MyHomePageState extends State { 53 | ExpandableTableCell _buildCell(String content, {CellBuilder? builder}) => 54 | ExpandableTableCell( 55 | child: builder != null 56 | ? null 57 | : DefaultCellCard( 58 | child: Center( 59 | child: Text( 60 | content, 61 | style: textStyle, 62 | ), 63 | ), 64 | ), 65 | builder: builder, 66 | ); 67 | 68 | ExpandableTableCell _buildFirstRowCell() => ExpandableTableCell( 69 | builder: (context, details) => DefaultCellCard( 70 | child: Padding( 71 | padding: const EdgeInsets.only(left: 16.0), 72 | child: Row( 73 | children: [ 74 | SizedBox( 75 | width: 24 * details.row!.address.length.toDouble(), 76 | child: details.row?.children != null 77 | ? Align( 78 | alignment: Alignment.centerRight, 79 | child: AnimatedRotation( 80 | duration: const Duration(milliseconds: 500), 81 | turns: details.row?.childrenExpanded == true 82 | ? 0.25 83 | : 0, 84 | child: const Icon( 85 | Icons.keyboard_arrow_right, 86 | color: Colors.white, 87 | ), 88 | ), 89 | ) 90 | : null, 91 | ), 92 | Text( 93 | '${details.row!.address.length > 1 ? details.row!.address.skip(1).map((e) => '&Sub ').join() : ''}&Row ${details.row!.address.last}', 94 | style: textStyle, 95 | ), 96 | ], 97 | ), 98 | ), 99 | ), 100 | ); 101 | 102 | ExpandableTable _buildSimpleTable() { 103 | const int columnsCount = 20; 104 | const int rowsCount = 20; 105 | //Creation header 106 | final List headers = List.generate( 107 | columnsCount - 1, 108 | (index) => ExpandableTableHeader( 109 | width: index % 2 == 0 ? 200 : 150, 110 | cell: _buildCell('_Column $index'), 111 | ), 112 | ); 113 | //Creation rows 114 | final List rows = List.generate( 115 | rowsCount, 116 | (rowIndex) => ExpandableTableRow( 117 | height: rowIndex % 2 == 0 ? 50 : 70, 118 | firstCell: _buildCell('_Row $rowIndex'), 119 | cells: List.generate( 120 | columnsCount - 1, 121 | (columnIndex) => _buildCell('_Cell $rowIndex:$columnIndex'), 122 | ), 123 | ), 124 | ); 125 | 126 | return ExpandableTable( 127 | firstHeaderCell: _buildCell('Simple\nTable'), 128 | headers: headers, 129 | scrollShadowColor: accentColor, 130 | rows: rows, 131 | ); 132 | } 133 | 134 | static const int columnsCount = 20; 135 | static const int subColumnsCount = 2; 136 | static const int rowsCount = 6; 137 | static const int subRowsCount = 3; 138 | static const int totalColumns = columnsCount + subColumnsCount; 139 | 140 | List _generateRows(int quantity, {int depth = 0}) { 141 | final bool generateLegendRow = (depth == 0 || depth == 2); 142 | return List.generate( 143 | quantity, 144 | (rowIndex) => ExpandableTableRow( 145 | firstCell: _buildFirstRowCell(), 146 | children: ((rowIndex == 3 || rowIndex == 2) && depth < 3) 147 | ? _generateRows(subRowsCount, depth: depth + 1) 148 | : null, 149 | cells: !(generateLegendRow && (rowIndex == 3 || rowIndex == 2)) 150 | ? List.generate( 151 | totalColumns, 152 | (columnIndex) => _buildCell('&Cell $rowIndex:$columnIndex'), 153 | ) 154 | : null, 155 | legend: generateLegendRow && (rowIndex == 3 || rowIndex == 2) 156 | ? const DefaultCellCard( 157 | child: Align( 158 | alignment: FractionalOffset.centerLeft, 159 | child: Padding( 160 | padding: EdgeInsets.only(left: 24.0), 161 | child: Text( 162 | 'This is row legend', 163 | style: textStyle, 164 | ), 165 | ), 166 | ), 167 | ) 168 | : null, 169 | ), 170 | ); 171 | } 172 | 173 | ExpandableTable _buildExpandableTable() { 174 | //Creation header 175 | final List subHeader = List.generate( 176 | subColumnsCount, 177 | (index) => ExpandableTableHeader( 178 | cell: _buildCell('&Sub Column $index'), 179 | ), 180 | ); 181 | 182 | //Creation header 183 | final List headers = List.generate( 184 | columnsCount, 185 | (index) => ExpandableTableHeader( 186 | cell: _buildCell( 187 | '${index == 1 ? '&Expandable\nColumn' : '&Column'} $index'), 188 | children: index == 1 ? subHeader : null), 189 | ); 190 | 191 | return ExpandableTable( 192 | firstHeaderCell: _buildCell('Expandable\nTable'), 193 | rows: _generateRows(rowsCount), 194 | headers: headers, 195 | defaultsRowHeight: 60, 196 | defaultsColumnWidth: 150, 197 | firstColumnWidth: 250, 198 | scrollShadowColor: accentColor, 199 | ); 200 | } 201 | 202 | @override 203 | Widget build(BuildContext context) => Scaffold( 204 | appBar: AppBar( 205 | title: const Text( 206 | ' Simple Table | Expandable Table'), 207 | centerTitle: true, 208 | ), 209 | body: Container( 210 | color: accentColor, 211 | child: Row( 212 | children: [ 213 | Expanded( 214 | child: Padding( 215 | padding: const EdgeInsets.all(20.0), 216 | child: _buildSimpleTable(), 217 | ), 218 | ), 219 | Expanded( 220 | child: Padding( 221 | padding: const EdgeInsets.all(20.0), 222 | child: _buildExpandableTable(), 223 | ), 224 | ), 225 | ], 226 | ), 227 | ), 228 | ); 229 | } 230 | 231 | class AppCustomScrollBehavior extends MaterialScrollBehavior { 232 | @override 233 | Set get dragDevices => { 234 | PointerDeviceKind.touch, 235 | PointerDeviceKind.mouse, 236 | PointerDeviceKind.trackpad, 237 | }; 238 | } 239 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/gestures.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | // ignore: depend_on_referenced_packages 5 | import 'package:flutter_expandable_table/flutter_expandable_table.dart'; 6 | 7 | const Color _primaryColor = Color(0xFF1e2f36); //corner 8 | const Color _accentColor = Color(0xFF0d2026); //background 9 | const TextStyle _textStyle = TextStyle(color: Colors.white); 10 | // const TextStyle _textStyleSubItems = TextStyle(color: Colors.grey); 11 | 12 | void main() => runApp(const _MyApp()); 13 | 14 | class _MyApp extends StatelessWidget { 15 | const _MyApp(); 16 | 17 | @override 18 | Widget build(BuildContext context) => MaterialApp( 19 | title: 'ExpandableTable Example', 20 | theme: ThemeData(primarySwatch: Colors.grey), 21 | home: const _MyHomePage(), 22 | scrollBehavior: _AppCustomScrollBehavior(), 23 | ); 24 | } 25 | 26 | class _MyHomePage extends StatefulWidget { 27 | const _MyHomePage(); 28 | 29 | @override 30 | State<_MyHomePage> createState() => _MyHomePageState(); 31 | } 32 | 33 | class _DefaultCellCard extends StatelessWidget { 34 | final Widget child; 35 | 36 | const _DefaultCellCard({ 37 | required this.child, 38 | }); 39 | 40 | @override 41 | Widget build(BuildContext context) => Container( 42 | color: _primaryColor, 43 | margin: const EdgeInsets.all(1), 44 | child: child, 45 | ); 46 | } 47 | 48 | class _MyHomePageState extends State<_MyHomePage> { 49 | ExpandableTableCell _buildCell(String content, {CellBuilder? builder}) => 50 | ExpandableTableCell( 51 | child: builder != null 52 | ? null 53 | : _DefaultCellCard( 54 | child: Center( 55 | child: Text( 56 | content, 57 | style: _textStyle, 58 | ), 59 | ), 60 | ), 61 | builder: builder, 62 | ); 63 | 64 | ExpandableTableCell _buildFirstRowCell() => ExpandableTableCell( 65 | builder: (context, details) => _DefaultCellCard( 66 | child: Padding( 67 | padding: const EdgeInsets.only(left: 16.0), 68 | child: Row( 69 | children: [ 70 | SizedBox( 71 | width: 24 * details.row!.address.length.toDouble(), 72 | child: details.row?.children != null 73 | ? Align( 74 | alignment: Alignment.centerRight, 75 | child: AnimatedRotation( 76 | duration: const Duration(milliseconds: 500), 77 | turns: details.row?.childrenExpanded == true 78 | ? 0.25 79 | : 0, 80 | child: const Icon( 81 | Icons.keyboard_arrow_right, 82 | color: Colors.white, 83 | ), 84 | ), 85 | ) 86 | : null, 87 | ), 88 | Text( 89 | '${details.row!.address.length > 1 ? details.row!.address.skip(1).map((e) => 'Sub ').join() : ''}Row ${details.row!.address.last}', 90 | style: _textStyle, 91 | ), 92 | ], 93 | ), 94 | ), 95 | ), 96 | ); 97 | 98 | ExpandableTable _buildSimpleTable() { 99 | const int columnsCount = 20; 100 | const int rowsCount = 20; 101 | //Creation header 102 | final List headers = List.generate( 103 | columnsCount - 1, 104 | (index) => ExpandableTableHeader( 105 | width: index % 2 == 0 ? 200 : 150, 106 | cell: _buildCell('Column $index'), 107 | ), 108 | ); 109 | //Creation rows 110 | final List rows = List.generate( 111 | rowsCount, 112 | (rowIndex) => ExpandableTableRow( 113 | height: rowIndex % 2 == 0 ? 50 : 70, 114 | firstCell: _buildCell('Row $rowIndex'), 115 | cells: List.generate( 116 | columnsCount - 1, 117 | (columnIndex) => _buildCell('Cell $rowIndex:$columnIndex'), 118 | ), 119 | ), 120 | ); 121 | 122 | return ExpandableTable( 123 | firstHeaderCell: _buildCell('Simple\nTable'), 124 | headers: headers, 125 | scrollShadowColor: _accentColor, 126 | rows: rows, 127 | visibleScrollbar: true, 128 | trackVisibilityScrollbar: true, 129 | thumbVisibilityScrollbar: true, 130 | ); 131 | } 132 | 133 | static const int columnsCount = 20; 134 | static const int subColumnsCount = 2; 135 | static const int rowsCount = 6; 136 | static const int subRowsCount = 3; 137 | static const int totalColumns = columnsCount + subColumnsCount; 138 | 139 | List _generateRows(int quantity, {int depth = 0}) { 140 | final bool generateLegendRow = (depth == 0 || depth == 2); 141 | return List.generate( 142 | quantity, 143 | (rowIndex) => ExpandableTableRow( 144 | firstCell: _buildFirstRowCell(), 145 | children: ((rowIndex == 3 || rowIndex == 2) && depth < 3) 146 | ? _generateRows(subRowsCount, depth: depth + 1) 147 | : null, 148 | cells: !(generateLegendRow && (rowIndex == 3 || rowIndex == 2)) 149 | ? List.generate( 150 | totalColumns, 151 | (columnIndex) => _buildCell('Cell $rowIndex:$columnIndex'), 152 | ) 153 | : null, 154 | legend: generateLegendRow && (rowIndex == 3 || rowIndex == 2) 155 | ? const _DefaultCellCard( 156 | child: Align( 157 | alignment: FractionalOffset.centerLeft, 158 | child: Padding( 159 | padding: EdgeInsets.only(left: 24.0), 160 | child: Text( 161 | 'This is row legend', 162 | style: _textStyle, 163 | ), 164 | ), 165 | ), 166 | ) 167 | : null, 168 | ), 169 | ); 170 | } 171 | 172 | ExpandableTable _buildExpandableTable() { 173 | //Creation header 174 | final List subHeader = List.generate( 175 | subColumnsCount, 176 | (index) => ExpandableTableHeader( 177 | cell: _buildCell('Sub Column $index'), 178 | ), 179 | ); 180 | 181 | //Creation header 182 | final List headers = List.generate( 183 | columnsCount, 184 | (index) => ExpandableTableHeader( 185 | cell: _buildCell( 186 | '${index == 1 ? 'Expandable\nColumn' : 'Column'} $index'), 187 | children: index == 1 ? subHeader : null), 188 | ); 189 | 190 | return ExpandableTable( 191 | firstHeaderCell: _buildCell('Expandable\nTable'), 192 | rows: _generateRows(rowsCount), 193 | headers: headers, 194 | defaultsRowHeight: 60, 195 | defaultsColumnWidth: 150, 196 | firstColumnWidth: 250, 197 | scrollShadowColor: _accentColor, 198 | visibleScrollbar: true, 199 | expanded: false, 200 | ); 201 | } 202 | 203 | @override 204 | Widget build(BuildContext context) => Scaffold( 205 | appBar: AppBar( 206 | title: const Text( 207 | ' Simple Table | Expandable Table'), 208 | centerTitle: true, 209 | ), 210 | body: Container( 211 | color: _accentColor, 212 | child: Row( 213 | crossAxisAlignment: CrossAxisAlignment.start, 214 | children: [ 215 | Expanded( 216 | child: Padding( 217 | padding: const EdgeInsets.all(20.0), 218 | child: _buildSimpleTable(), 219 | ), 220 | ), 221 | Expanded( 222 | child: Padding( 223 | padding: const EdgeInsets.all(20.0), 224 | child: _buildExpandableTable(), 225 | ), 226 | ), 227 | ], 228 | ), 229 | ), 230 | ); 231 | } 232 | 233 | class _AppCustomScrollBehavior extends MaterialScrollBehavior { 234 | @override 235 | Set get dragDevices => { 236 | PointerDeviceKind.touch, 237 | PointerDeviceKind.mouse, 238 | PointerDeviceKind.trackpad, 239 | }; 240 | } 241 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | args: 5 | dependency: transitive 6 | description: 7 | name: args 8 | sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" 9 | url: "https://pub.dev" 10 | source: hosted 11 | version: "2.5.0" 12 | async: 13 | dependency: transitive 14 | description: 15 | name: async 16 | sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" 17 | url: "https://pub.dev" 18 | source: hosted 19 | version: "2.11.0" 20 | boolean_selector: 21 | dependency: transitive 22 | description: 23 | name: boolean_selector 24 | sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" 25 | url: "https://pub.dev" 26 | source: hosted 27 | version: "2.1.1" 28 | characters: 29 | dependency: transitive 30 | description: 31 | name: characters 32 | sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" 33 | url: "https://pub.dev" 34 | source: hosted 35 | version: "1.3.0" 36 | clock: 37 | dependency: transitive 38 | description: 39 | name: clock 40 | sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf 41 | url: "https://pub.dev" 42 | source: hosted 43 | version: "1.1.1" 44 | collection: 45 | dependency: transitive 46 | description: 47 | name: collection 48 | sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a 49 | url: "https://pub.dev" 50 | source: hosted 51 | version: "1.18.0" 52 | fake_async: 53 | dependency: transitive 54 | description: 55 | name: fake_async 56 | sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" 57 | url: "https://pub.dev" 58 | source: hosted 59 | version: "1.3.1" 60 | flutter: 61 | dependency: "direct main" 62 | description: flutter 63 | source: sdk 64 | version: "0.0.0" 65 | flutter_lints: 66 | dependency: "direct dev" 67 | description: 68 | name: flutter_lints 69 | sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c" 70 | url: "https://pub.dev" 71 | source: hosted 72 | version: "4.0.0" 73 | flutter_scroll_shadow: 74 | dependency: "direct main" 75 | description: 76 | name: flutter_scroll_shadow 77 | sha256: c0509c642c5077654301fab1fb2260adc94c82a407c60e64162974b4366e7874 78 | url: "https://pub.dev" 79 | source: hosted 80 | version: "1.2.4" 81 | flutter_test: 82 | dependency: "direct dev" 83 | description: flutter 84 | source: sdk 85 | version: "0.0.0" 86 | import_sorter: 87 | dependency: "direct dev" 88 | description: 89 | name: import_sorter 90 | sha256: eb15738ccead84e62c31e0208ea4e3104415efcd4972b86906ca64a1187d0836 91 | url: "https://pub.dev" 92 | source: hosted 93 | version: "4.6.0" 94 | leak_tracker: 95 | dependency: transitive 96 | description: 97 | name: leak_tracker 98 | sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" 99 | url: "https://pub.dev" 100 | source: hosted 101 | version: "10.0.4" 102 | leak_tracker_flutter_testing: 103 | dependency: transitive 104 | description: 105 | name: leak_tracker_flutter_testing 106 | sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" 107 | url: "https://pub.dev" 108 | source: hosted 109 | version: "3.0.3" 110 | leak_tracker_testing: 111 | dependency: transitive 112 | description: 113 | name: leak_tracker_testing 114 | sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" 115 | url: "https://pub.dev" 116 | source: hosted 117 | version: "3.0.1" 118 | linked_scroll_controller: 119 | dependency: "direct main" 120 | description: 121 | name: linked_scroll_controller 122 | sha256: e6020062bcf4ffc907ee7fd090fa971e65d8dfaac3c62baf601a3ced0b37986a 123 | url: "https://pub.dev" 124 | source: hosted 125 | version: "0.2.0" 126 | lints: 127 | dependency: transitive 128 | description: 129 | name: lints 130 | sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" 131 | url: "https://pub.dev" 132 | source: hosted 133 | version: "4.0.0" 134 | matcher: 135 | dependency: transitive 136 | description: 137 | name: matcher 138 | sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb 139 | url: "https://pub.dev" 140 | source: hosted 141 | version: "0.12.16+1" 142 | material_color_utilities: 143 | dependency: transitive 144 | description: 145 | name: material_color_utilities 146 | sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" 147 | url: "https://pub.dev" 148 | source: hosted 149 | version: "0.8.0" 150 | meta: 151 | dependency: transitive 152 | description: 153 | name: meta 154 | sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" 155 | url: "https://pub.dev" 156 | source: hosted 157 | version: "1.12.0" 158 | nested: 159 | dependency: transitive 160 | description: 161 | name: nested 162 | sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" 163 | url: "https://pub.dev" 164 | source: hosted 165 | version: "1.0.0" 166 | path: 167 | dependency: transitive 168 | description: 169 | name: path 170 | sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" 171 | url: "https://pub.dev" 172 | source: hosted 173 | version: "1.9.0" 174 | provider: 175 | dependency: "direct main" 176 | description: 177 | name: provider 178 | sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c 179 | url: "https://pub.dev" 180 | source: hosted 181 | version: "6.1.2" 182 | sky_engine: 183 | dependency: transitive 184 | description: flutter 185 | source: sdk 186 | version: "0.0.99" 187 | source_span: 188 | dependency: transitive 189 | description: 190 | name: source_span 191 | sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" 192 | url: "https://pub.dev" 193 | source: hosted 194 | version: "1.10.0" 195 | stack_trace: 196 | dependency: transitive 197 | description: 198 | name: stack_trace 199 | sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" 200 | url: "https://pub.dev" 201 | source: hosted 202 | version: "1.11.1" 203 | stream_channel: 204 | dependency: transitive 205 | description: 206 | name: stream_channel 207 | sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 208 | url: "https://pub.dev" 209 | source: hosted 210 | version: "2.1.2" 211 | string_scanner: 212 | dependency: transitive 213 | description: 214 | name: string_scanner 215 | sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" 216 | url: "https://pub.dev" 217 | source: hosted 218 | version: "1.2.0" 219 | term_glyph: 220 | dependency: transitive 221 | description: 222 | name: term_glyph 223 | sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 224 | url: "https://pub.dev" 225 | source: hosted 226 | version: "1.2.1" 227 | test_api: 228 | dependency: transitive 229 | description: 230 | name: test_api 231 | sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" 232 | url: "https://pub.dev" 233 | source: hosted 234 | version: "0.7.0" 235 | tint: 236 | dependency: transitive 237 | description: 238 | name: tint 239 | sha256: "9652d9a589f4536d5e392cf790263d120474f15da3cf1bee7f1fdb31b4de5f46" 240 | url: "https://pub.dev" 241 | source: hosted 242 | version: "2.0.1" 243 | vector_math: 244 | dependency: transitive 245 | description: 246 | name: vector_math 247 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" 248 | url: "https://pub.dev" 249 | source: hosted 250 | version: "2.1.4" 251 | vm_service: 252 | dependency: transitive 253 | description: 254 | name: vm_service 255 | sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" 256 | url: "https://pub.dev" 257 | source: hosted 258 | version: "14.2.1" 259 | yaml: 260 | dependency: transitive 261 | description: 262 | name: yaml 263 | sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" 264 | url: "https://pub.dev" 265 | source: hosted 266 | version: "3.1.2" 267 | sdks: 268 | dart: ">=3.3.0 <4.0.0" 269 | flutter: ">=3.18.0-18.0.pre.54" 270 | -------------------------------------------------------------------------------- /example/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | args: 5 | dependency: transitive 6 | description: 7 | name: args 8 | sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" 9 | url: "https://pub.dev" 10 | source: hosted 11 | version: "2.5.0" 12 | async: 13 | dependency: transitive 14 | description: 15 | name: async 16 | sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" 17 | url: "https://pub.dev" 18 | source: hosted 19 | version: "2.11.0" 20 | boolean_selector: 21 | dependency: transitive 22 | description: 23 | name: boolean_selector 24 | sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" 25 | url: "https://pub.dev" 26 | source: hosted 27 | version: "2.1.1" 28 | characters: 29 | dependency: transitive 30 | description: 31 | name: characters 32 | sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" 33 | url: "https://pub.dev" 34 | source: hosted 35 | version: "1.3.0" 36 | clock: 37 | dependency: transitive 38 | description: 39 | name: clock 40 | sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf 41 | url: "https://pub.dev" 42 | source: hosted 43 | version: "1.1.1" 44 | collection: 45 | dependency: transitive 46 | description: 47 | name: collection 48 | sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a 49 | url: "https://pub.dev" 50 | source: hosted 51 | version: "1.18.0" 52 | fake_async: 53 | dependency: transitive 54 | description: 55 | name: fake_async 56 | sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" 57 | url: "https://pub.dev" 58 | source: hosted 59 | version: "1.3.1" 60 | flutter: 61 | dependency: "direct main" 62 | description: flutter 63 | source: sdk 64 | version: "0.0.0" 65 | flutter_expandable_table: 66 | dependency: "direct dev" 67 | description: 68 | path: ".." 69 | relative: true 70 | source: path 71 | version: "2.1.0" 72 | flutter_lints: 73 | dependency: "direct dev" 74 | description: 75 | name: flutter_lints 76 | sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c" 77 | url: "https://pub.dev" 78 | source: hosted 79 | version: "4.0.0" 80 | flutter_scroll_shadow: 81 | dependency: transitive 82 | description: 83 | name: flutter_scroll_shadow 84 | sha256: c0509c642c5077654301fab1fb2260adc94c82a407c60e64162974b4366e7874 85 | url: "https://pub.dev" 86 | source: hosted 87 | version: "1.2.4" 88 | flutter_test: 89 | dependency: "direct dev" 90 | description: flutter 91 | source: sdk 92 | version: "0.0.0" 93 | import_sorter: 94 | dependency: "direct dev" 95 | description: 96 | name: import_sorter 97 | sha256: eb15738ccead84e62c31e0208ea4e3104415efcd4972b86906ca64a1187d0836 98 | url: "https://pub.dev" 99 | source: hosted 100 | version: "4.6.0" 101 | leak_tracker: 102 | dependency: transitive 103 | description: 104 | name: leak_tracker 105 | sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" 106 | url: "https://pub.dev" 107 | source: hosted 108 | version: "10.0.4" 109 | leak_tracker_flutter_testing: 110 | dependency: transitive 111 | description: 112 | name: leak_tracker_flutter_testing 113 | sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" 114 | url: "https://pub.dev" 115 | source: hosted 116 | version: "3.0.3" 117 | leak_tracker_testing: 118 | dependency: transitive 119 | description: 120 | name: leak_tracker_testing 121 | sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" 122 | url: "https://pub.dev" 123 | source: hosted 124 | version: "3.0.1" 125 | linked_scroll_controller: 126 | dependency: transitive 127 | description: 128 | name: linked_scroll_controller 129 | sha256: e6020062bcf4ffc907ee7fd090fa971e65d8dfaac3c62baf601a3ced0b37986a 130 | url: "https://pub.dev" 131 | source: hosted 132 | version: "0.2.0" 133 | lints: 134 | dependency: transitive 135 | description: 136 | name: lints 137 | sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" 138 | url: "https://pub.dev" 139 | source: hosted 140 | version: "4.0.0" 141 | matcher: 142 | dependency: transitive 143 | description: 144 | name: matcher 145 | sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb 146 | url: "https://pub.dev" 147 | source: hosted 148 | version: "0.12.16+1" 149 | material_color_utilities: 150 | dependency: transitive 151 | description: 152 | name: material_color_utilities 153 | sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" 154 | url: "https://pub.dev" 155 | source: hosted 156 | version: "0.8.0" 157 | meta: 158 | dependency: transitive 159 | description: 160 | name: meta 161 | sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" 162 | url: "https://pub.dev" 163 | source: hosted 164 | version: "1.12.0" 165 | nested: 166 | dependency: transitive 167 | description: 168 | name: nested 169 | sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" 170 | url: "https://pub.dev" 171 | source: hosted 172 | version: "1.0.0" 173 | path: 174 | dependency: transitive 175 | description: 176 | name: path 177 | sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" 178 | url: "https://pub.dev" 179 | source: hosted 180 | version: "1.9.0" 181 | provider: 182 | dependency: transitive 183 | description: 184 | name: provider 185 | sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c 186 | url: "https://pub.dev" 187 | source: hosted 188 | version: "6.1.2" 189 | sky_engine: 190 | dependency: transitive 191 | description: flutter 192 | source: sdk 193 | version: "0.0.99" 194 | source_span: 195 | dependency: transitive 196 | description: 197 | name: source_span 198 | sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" 199 | url: "https://pub.dev" 200 | source: hosted 201 | version: "1.10.0" 202 | stack_trace: 203 | dependency: transitive 204 | description: 205 | name: stack_trace 206 | sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" 207 | url: "https://pub.dev" 208 | source: hosted 209 | version: "1.11.1" 210 | stream_channel: 211 | dependency: transitive 212 | description: 213 | name: stream_channel 214 | sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 215 | url: "https://pub.dev" 216 | source: hosted 217 | version: "2.1.2" 218 | string_scanner: 219 | dependency: transitive 220 | description: 221 | name: string_scanner 222 | sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" 223 | url: "https://pub.dev" 224 | source: hosted 225 | version: "1.2.0" 226 | term_glyph: 227 | dependency: transitive 228 | description: 229 | name: term_glyph 230 | sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 231 | url: "https://pub.dev" 232 | source: hosted 233 | version: "1.2.1" 234 | test_api: 235 | dependency: transitive 236 | description: 237 | name: test_api 238 | sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" 239 | url: "https://pub.dev" 240 | source: hosted 241 | version: "0.7.0" 242 | tint: 243 | dependency: transitive 244 | description: 245 | name: tint 246 | sha256: "9652d9a589f4536d5e392cf790263d120474f15da3cf1bee7f1fdb31b4de5f46" 247 | url: "https://pub.dev" 248 | source: hosted 249 | version: "2.0.1" 250 | vector_math: 251 | dependency: transitive 252 | description: 253 | name: vector_math 254 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" 255 | url: "https://pub.dev" 256 | source: hosted 257 | version: "2.1.4" 258 | vm_service: 259 | dependency: transitive 260 | description: 261 | name: vm_service 262 | sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" 263 | url: "https://pub.dev" 264 | source: hosted 265 | version: "14.2.1" 266 | yaml: 267 | dependency: transitive 268 | description: 269 | name: yaml 270 | sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" 271 | url: "https://pub.dev" 272 | source: hosted 273 | version: "3.1.2" 274 | sdks: 275 | dart: ">=3.3.0 <4.0.0" 276 | flutter: ">=3.18.0-18.0.pre.54" 277 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | #### flutter_expandable_table 2 | # Expandable Table 3 | 4 | [![](https://img.shields.io/static/v1?label=flutter&message=flutter_expandable_table&color=red??style=for-the-badge&logo=GitHub)](https://github.com/rickypid/flutter_expandable_table) 5 | [![Pub Package](https://img.shields.io/pub/v/flutter_expandable_table.svg?style=flat-square)](https://pub.dartlang.org/packages/flutter_expandable_table) 6 | [![Pub Points](https://img.shields.io/pub/points/flutter_expandable_table)](https://pub.dev/packages/flutter_expandable_table/score) 7 | [![Pub Likes](https://img.shields.io/pub/likes/flutter_expandable_table)](https://pub.dev/packages/flutter_expandable_table/score) 8 | 9 | [![Package Issue](https://img.shields.io/github/issues/rickypid/flutter_expandable_table)](https://github.com/rickypid/flutter_expandable_table/issues) 10 | ![Package License](https://img.shields.io/github/license/rickypid/flutter_expandable_table) 11 | 12 | `ExpandableTable` is a widget for Flutter that create a Table with header and first column fixed. You can create a nested Rows/Columns grouped in expandable Row/Column 13 | 14 | | ![Image](https://github.com/rickypid/flutter_expandable_table/blob/master/doc/media/example.gif?raw=true) | 15 | | :------------: | 16 | | **ExpandableTable** | 17 | 18 | ## Features 19 | 20 | * Header and first column fixed 21 | * Supports vertical and horizontal scroll 22 | * Customizable animation Duration and Curve 23 | * Specific height definition for each single row 24 | * Specific width definition for each single column 25 | * Access to cell address when building cell content 26 | * Access to the parent rows and columns of the cell while building the contents of a cell 27 | 28 |   29 | 30 | ## Usage 31 | Make sure to check out the [examples on GitHub](https://github.com/rickypid/flutter_expandable_table/tree/master/example). 32 | 33 | ### Installation 34 | 35 | Add the following line to `pubspec.yaml`: 36 | 37 | ```yaml 38 | dependencies: 39 | flutter_expandable_table: 40 | ``` 41 | 42 | ### Basic setup 43 | 44 | *Complete example [available here](https://github.com/rickypid/flutter_expandable_table/blob/master/example/lib/main.dart).* 45 | 46 | ```dart 47 | return ExpandableTable( 48 | firstHeaderCell: ExpandableTableCell( 49 | child: Text('Simple\nTable'), 50 | ), 51 | headers: headers, 52 | rows: rows, 53 | ); 54 | ``` 55 | 56 | ### Use with the controller 57 | 58 | You can create an external controller to be able to dynamically manage the table, for example to add or remove rows within it. 59 | 60 | Here is an example: 61 | 62 | ```dart 63 | //... Inside Widget State 64 | late ExpandableTableController controller; 65 | //.... 66 | @override 67 | void initState() { 68 | controller = ExpandableTableController( 69 | firstHeaderCell: ExpandableTableCell(child: Container()), 70 | headers: [], 71 | rows: [], 72 | headerHeight: 263, 73 | defaultsColumnWidth: 200, 74 | firstColumnWidth: 300, 75 | scrollShadowColor: AppColors.black, 76 | ); 77 | super.initState(); 78 | } 79 | void _onEvent(){ 80 | controller.rows.add(...your code...); 81 | } 82 | @override 83 | Widget build(BuildContext context) { 84 | return ExpandableTable( 85 | controller: controller, 86 | ); 87 | } 88 | //.... 89 | ``` 90 | 91 | ### ExpandableTable Properties 92 | * `firstHeaderCell`: Is the top left cell, i.e. the first header cell. 93 | * `headers`: contains the list of all column headers, each one of these can contain a list of further headers, this allows you to create nested and expandable columns. 94 | * `rows`: contains the list of all the rows of the table, each of these can contain a list of further rows, this allows you to create nested and expandable rows. 95 | * `headerHeight`: is the height of each column header, i.e. the first row. 96 | * `firstColumnWidth`: determines first Column width size. 97 | * `defaultsColumnWidth`: defines the default width of all columns, it is possible to redefine it for each individual column. 98 | * `defaultsRowHeight`: defines the default height of all rows, it is possible to redefine it for every single row. 99 | * `duration`: determines duration rendered animation of Rows/Columns expansion. 100 | * `curve`: determines rendered curve animation of Rows/Columns expansion. 101 | * `scrollShadowDuration`: determines duration rendered animation of shadows. 102 | * `scrollShadowFadeInCurve`: determines rendered curve animation of shadows appearance. 103 | * `scrollShadowFadeOutCurve`: determines rendered curve animation of shadows disappearance. 104 | * `scrollShadowColor`: determines rendered color of shadows. 105 | * `scrollShadowSize`: determines size of shadows. 106 | * `visibleScrollbar`: determines visibility of horizontal and vertical scrollbars. 107 | * `trackVisibilityScrollbar`: indicates that the scrollbar track should be visible. 108 | * `thumbVisibilityScrollbar`: indicates that the scrollbar thumb should be visible, even when a scroll is not underway. 109 | * `expanded`: indicates that the table expands, so it fills the available space along the horizontal and vertical axes. 110 | 111 |   112 | 113 | ## 📚 My open source projects 114 | 115 | ### Flutter 116 | 117 | | Package | Verison | Score | Likes | Test | Coverage | 118 | |--|--|--|--|--|--| 119 | | [![](https://img.shields.io/static/v1?label=flutter&message=flutter_expandable_table&color=red??style=for-the-badge&logo=GitHub)](https://github.com/rickypid/flutter_expandable_table) | [![Pub Package](https://img.shields.io/pub/v/flutter_expandable_table.svg?style=flat-square)](https://pub.dartlang.org/packages/flutter_expandable_table) | [![Pub Points](https://img.shields.io/pub/points/flutter_expandable_table)](https://pub.dev/packages/flutter_expandable_table/score) | [![Pub Likes](https://img.shields.io/pub/likes/flutter_expandable_table)](https://pub.dev/packages/flutter_expandable_table/score) | | | 120 | | [![](https://img.shields.io/static/v1?label=flutter&message=widget_tree_depth_counter&color=red??style=for-the-badge&logo=GitHub)](https://github.com/rickypid/widget_tree_depth_counter) | [![Pub Package](https://img.shields.io/pub/v/widget_tree_depth_counter.svg?style=flat-square)](https://pub.dartlang.org/packages/widget_tree_depth_counter) | [![Pub Points](https://img.shields.io/pub/points/widget_tree_depth_counter)](https://pub.dev/packages/widget_tree_depth_counter/score) | [![Pub Likes](https://img.shields.io/pub/likes/widget_tree_depth_counter)](https://pub.dev/packages/widget_tree_depth_counter/score) | | | 121 | | [![](https://img.shields.io/static/v1?label=flutter&message=flutter_scroll_shadow&color=red??style=for-the-badge&logo=GitHub)](https://github.com/rickypid/flutter_scroll_shadow) | [![Pub Package](https://img.shields.io/pub/v/flutter_scroll_shadow.svg?style=flat-square)](https://pub.dartlang.org/packages/flutter_scroll_shadow) | [![Pub Points](https://img.shields.io/pub/points/flutter_scroll_shadow)](https://pub.dev/packages/flutter_scroll_shadow/score) | [![Pub Likes](https://img.shields.io/pub/likes/flutter_scroll_shadow)](https://pub.dev/packages/flutter_scroll_shadow/score) | | | 122 | | [![](https://img.shields.io/static/v1?label=flutter&message=flutter_bargraph&color=red??style=for-the-badge&logo=GitHub)](https://github.com/rickypid/flutter_bargraph) | [![Pub Package](https://img.shields.io/pub/v/flutter_bargraph.svg?style=flat-square)](https://pub.dartlang.org/packages/flutter_bargraph) | [![Pub Points](https://img.shields.io/pub/points/flutter_bargraph)](https://pub.dev/packages/flutter_bargraph/score) | [![Pub Likes](https://img.shields.io/pub/likes/flutter_bargraph)](https://pub.dev/packages/flutter_bargraph/score) | | | 123 | 124 | 125 | ### Dart 126 | 127 | | Package | Verison | Score | Likes | Test | Coverage | 128 | |--|--|--|--|--|--| 129 | | [![](https://img.shields.io/static/v1?label=dart&message=cowsay&color=red??style=for-the-badge&logo=GitHub)](https://github.com/rickypid/cowsay) | [![Pub Package](https://img.shields.io/pub/v/cowsay.svg?style=flat-square)](https://pub.dartlang.org/packages/cowsay) | [![Pub Points](https://img.shields.io/pub/points/cowsay)](https://pub.dev/packages/cowsay/score) | [![Pub Likes](https://img.shields.io/pub/likes/cowsay)](https://pub.dev/packages/cowsay/score) | [![Test CI](https://github.com/rickypid/cowsay/actions/workflows/test.yml/badge.svg)](https://github.com/rickypid/cowsay/actions/workflows/test.yml) | [![codecov](https://codecov.io/gh/rickypid/cowsay/branch/master/graph/badge.svg?token=Z65KEB9SAX)](https://codecov.io/gh/rickypid/cowsay) | 130 | | [![](https://img.shields.io/static/v1?label=dart&message=telegram_link&color=red??style=for-the-badge&logo=GitHub)](https://github.com/rickypid/telegram_link) | [![Pub Package](https://img.shields.io/pub/v/telegram_link.svg?style=flat-square)](https://pub.dartlang.org/packages/telegram_link) | [![Pub Points](https://img.shields.io/pub/points/telegram_link)](https://pub.dev/packages/telegram_link/score) | [![Pub Likes](https://img.shields.io/pub/likes/telegram_link)](https://pub.dev/packages/telegram_link/score) | [![Test CI](https://github.com/rickypid/telegram_link/actions/workflows/test.yml/badge.svg)](https://github.com/rickypid/telegram_link/actions/workflows/test.yml) | [![codecov](https://codecov.io/gh/rickypid/telegram_link/branch/main/graph/badge.svg?token=Z65KEB9SAX)](https://codecov.io/gh/rickypid/telegram_link) | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #### flutter_expandable_table 2 | # Expandable Table 3 | 4 | [![](https://img.shields.io/static/v1?label=flutter&message=flutter_expandable_table&color=red??style=for-the-badge&logo=GitHub)](https://github.com/rickypid/flutter_expandable_table) 5 | [![Pub Package](https://img.shields.io/pub/v/flutter_expandable_table.svg?style=flat-square)](https://pub.dartlang.org/packages/flutter_expandable_table) 6 | [![Pub Points](https://img.shields.io/pub/points/flutter_expandable_table)](https://pub.dev/packages/flutter_expandable_table/score) 7 | [![Pub Likes](https://img.shields.io/pub/likes/flutter_expandable_table)](https://pub.dev/packages/flutter_expandable_table/score) 8 | 9 | [![Package Issue](https://img.shields.io/github/issues/rickypid/flutter_expandable_table)](https://github.com/rickypid/flutter_expandable_table/issues) 10 | ![Package License](https://img.shields.io/github/license/rickypid/flutter_expandable_table) 11 | 12 | `ExpandableTable` is a widget for Flutter that create a Table with header and first column fixed. You can create a nested Rows/Columns grouped in expandable Row/Column 13 | 14 | | ![Image](https://github.com/rickypid/flutter_expandable_table/blob/master/doc/media/example.gif?raw=true) | 15 | | :------------: | 16 | | **ExpandableTable** | 17 | 18 | ## Features 19 | 20 | * Header and first column fixed 21 | * Supports vertical and horizontal scroll 22 | * Customizable animation Duration and Curve 23 | * Specific height definition for each single row 24 | * Specific width definition for each single column 25 | * Access to cell address when building cell content 26 | * Access to the parent rows and columns of the cell while building the contents of a cell 27 | 28 |   29 | 30 | ## Usage 31 | Make sure to check out the [examples on GitHub](https://github.com/rickypid/flutter_expandable_table/tree/master/example). 32 | 33 | ### Installation 34 | 35 | Add the following line to `pubspec.yaml`: 36 | 37 | ```yaml 38 | dependencies: 39 | flutter_expandable_table: 40 | ``` 41 | 42 | ### Basic setup 43 | 44 | *Complete example [available here](https://github.com/rickypid/flutter_expandable_table/blob/master/example/lib/main.dart).* 45 | 46 | ```dart 47 | return ExpandableTable( 48 | firstHeaderCell: ExpandableTableCell( 49 | child: Text('Simple\nTable'), 50 | ), 51 | headers: headers, 52 | rows: rows, 53 | ); 54 | ``` 55 | 56 | ### Use with the controller 57 | 58 | You can create an external controller to be able to dynamically manage the table, for example to add or remove rows within it. 59 | 60 | Here is an example: 61 | 62 | ```dart 63 | //... Inside Widget State 64 | late ExpandableTableController controller; 65 | //.... 66 | @override 67 | void initState() { 68 | controller = ExpandableTableController( 69 | firstHeaderCell: ExpandableTableCell(child: Container()), 70 | headers: [], 71 | rows: [], 72 | headerHeight: 263, 73 | defaultsColumnWidth: 200, 74 | firstColumnWidth: 300, 75 | scrollShadowColor: AppColors.black, 76 | ); 77 | super.initState(); 78 | } 79 | void _onEvent(){ 80 | controller.rows.add(...your code...); 81 | } 82 | @override 83 | Widget build(BuildContext context) { 84 | return ExpandableTable( 85 | controller: controller, 86 | ); 87 | } 88 | //.... 89 | ``` 90 | 91 | ### ExpandableTable Properties 92 | * `firstHeaderCell`: Is the top left cell, i.e. the first header cell. 93 | * `headers`: contains the list of all column headers, each one of these can contain a list of further headers, this allows you to create nested and expandable columns. 94 | * `rows`: contains the list of all the rows of the table, each of these can contain a list of further rows, this allows you to create nested and expandable rows. 95 | * `headerHeight`: is the height of each column header, i.e. the first row. 96 | * `firstColumnWidth`: determines first Column width size. 97 | * `defaultsColumnWidth`: defines the default width of all columns, it is possible to redefine it for each individual column. 98 | * `defaultsRowHeight`: defines the default height of all rows, it is possible to redefine it for every single row. 99 | * `duration`: determines duration rendered animation of Rows/Columns expansion. 100 | * `curve`: determines rendered curve animation of Rows/Columns expansion. 101 | * `scrollShadowDuration`: determines duration rendered animation of shadows. 102 | * `scrollShadowFadeInCurve`: determines rendered curve animation of shadows appearance. 103 | * `scrollShadowFadeOutCurve`: determines rendered curve animation of shadows disappearance. 104 | * `scrollShadowColor`: determines rendered color of shadows. 105 | * `scrollShadowSize`: determines size of shadows. 106 | * `visibleScrollbar`: determines visibility of horizontal and vertical scrollbars. 107 | * `trackVisibilityScrollbar`: indicates that the scrollbar track should be visible. 108 | * `thumbVisibilityScrollbar`: indicates that the scrollbar thumb should be visible, even when a scroll is not underway. 109 | * `expanded`: indicates that the table expands, so it fills the available space along the horizontal and vertical axes. 110 | 111 |   112 | 113 | ## 📚 My open source projects 114 | 115 | ### Flutter 116 | 117 | | Package | Verison | Score | Likes | Test | Coverage | 118 | |--|--|--|--|--|--| 119 | | [![](https://img.shields.io/static/v1?label=flutter&message=flutter_expandable_table&color=red??style=for-the-badge&logo=GitHub)](https://github.com/rickypid/flutter_expandable_table) | [![Pub Package](https://img.shields.io/pub/v/flutter_expandable_table.svg?style=flat-square)](https://pub.dartlang.org/packages/flutter_expandable_table) | [![Pub Points](https://img.shields.io/pub/points/flutter_expandable_table)](https://pub.dev/packages/flutter_expandable_table/score) | [![Pub Likes](https://img.shields.io/pub/likes/flutter_expandable_table)](https://pub.dev/packages/flutter_expandable_table/score) | | | 120 | | [![](https://img.shields.io/static/v1?label=flutter&message=widget_tree_depth_counter&color=red??style=for-the-badge&logo=GitHub)](https://github.com/rickypid/widget_tree_depth_counter) | [![Pub Package](https://img.shields.io/pub/v/widget_tree_depth_counter.svg?style=flat-square)](https://pub.dartlang.org/packages/widget_tree_depth_counter) | [![Pub Points](https://img.shields.io/pub/points/widget_tree_depth_counter)](https://pub.dev/packages/widget_tree_depth_counter/score) | [![Pub Likes](https://img.shields.io/pub/likes/widget_tree_depth_counter)](https://pub.dev/packages/widget_tree_depth_counter/score) | | | 121 | | [![](https://img.shields.io/static/v1?label=flutter&message=flutter_scroll_shadow&color=red??style=for-the-badge&logo=GitHub)](https://github.com/rickypid/flutter_scroll_shadow) | [![Pub Package](https://img.shields.io/pub/v/flutter_scroll_shadow.svg?style=flat-square)](https://pub.dartlang.org/packages/flutter_scroll_shadow) | [![Pub Points](https://img.shields.io/pub/points/flutter_scroll_shadow)](https://pub.dev/packages/flutter_scroll_shadow/score) | [![Pub Likes](https://img.shields.io/pub/likes/flutter_scroll_shadow)](https://pub.dev/packages/flutter_scroll_shadow/score) | | | 122 | | [![](https://img.shields.io/static/v1?label=flutter&message=flutter_bargraph&color=red??style=for-the-badge&logo=GitHub)](https://github.com/rickypid/flutter_bargraph) | [![Pub Package](https://img.shields.io/pub/v/flutter_bargraph.svg?style=flat-square)](https://pub.dartlang.org/packages/flutter_bargraph) | [![Pub Points](https://img.shields.io/pub/points/flutter_bargraph)](https://pub.dev/packages/flutter_bargraph/score) | [![Pub Likes](https://img.shields.io/pub/likes/flutter_bargraph)](https://pub.dev/packages/flutter_bargraph/score) | | | 123 | 124 | 125 | ### Dart 126 | 127 | | Package | Verison | Score | Likes | Test | Coverage | 128 | |--|--|--|--|--|--| 129 | | [![](https://img.shields.io/static/v1?label=dart&message=cowsay&color=red??style=for-the-badge&logo=GitHub)](https://github.com/rickypid/cowsay) | [![Pub Package](https://img.shields.io/pub/v/cowsay.svg?style=flat-square)](https://pub.dartlang.org/packages/cowsay) | [![Pub Points](https://img.shields.io/pub/points/cowsay)](https://pub.dev/packages/cowsay/score) | [![Pub Likes](https://img.shields.io/pub/likes/cowsay)](https://pub.dev/packages/cowsay/score) | [![Test CI](https://github.com/rickypid/cowsay/actions/workflows/test.yml/badge.svg)](https://github.com/rickypid/cowsay/actions/workflows/test.yml) | [![codecov](https://codecov.io/gh/rickypid/cowsay/branch/master/graph/badge.svg?token=Z65KEB9SAX)](https://codecov.io/gh/rickypid/cowsay) | 130 | | [![](https://img.shields.io/static/v1?label=dart&message=telegram_link&color=red??style=for-the-badge&logo=GitHub)](https://github.com/rickypid/telegram_link) | [![Pub Package](https://img.shields.io/pub/v/telegram_link.svg?style=flat-square)](https://pub.dartlang.org/packages/telegram_link) | [![Pub Points](https://img.shields.io/pub/points/telegram_link)](https://pub.dev/packages/telegram_link/score) | [![Pub Likes](https://img.shields.io/pub/likes/telegram_link)](https://pub.dev/packages/telegram_link/score) | [![Test CI](https://github.com/rickypid/telegram_link/actions/workflows/test.yml/badge.svg)](https://github.com/rickypid/telegram_link/actions/workflows/test.yml) | [![codecov](https://codecov.io/gh/rickypid/telegram_link/branch/main/graph/badge.svg?token=Z65KEB9SAX)](https://codecov.io/gh/rickypid/telegram_link) | -------------------------------------------------------------------------------- /lib/src/class/controller.dart: -------------------------------------------------------------------------------- 1 | // Flutter imports: 2 | import 'package:flutter/material.dart'; 3 | 4 | // Project imports: 5 | import 'package:flutter_expandable_table/flutter_expandable_table.dart'; 6 | 7 | /// [ExpandableTableController] class. 8 | class ExpandableTableController extends ChangeNotifier { 9 | late ExpandableTableCell _firstHeaderCell; 10 | 11 | /// [firstHeaderCell] is the top left cell, i.e. the first header cell. 12 | /// `required` 13 | ExpandableTableCell get firstHeaderCell => _firstHeaderCell; 14 | 15 | set firstHeaderCell(ExpandableTableCell value) { 16 | _firstHeaderCell = value; 17 | notifyListeners(); 18 | } 19 | 20 | late List _headers; 21 | 22 | /// [headers] contains the list of all column headers, 23 | /// each one of these can contain a list of further headers, 24 | /// this allows you to create nested and expandable columns. 25 | /// Not to be used if the [controller] is used. 26 | /// `required` 27 | List get headers => _headers; 28 | 29 | set headers(List value) { 30 | _removeHeadersListener(); 31 | _headers = value; 32 | _addHeadersListener(); 33 | notifyListeners(); 34 | } 35 | 36 | late List _rows; 37 | 38 | /// [rows] contains the list of all the rows of the table, 39 | /// each of these can contain a list of further rows, 40 | /// this allows you to create nested and expandable rows. 41 | /// Not to be used if the [controller] is used. 42 | /// `required` 43 | List get rows => _rows; 44 | 45 | late double _headerHeight; 46 | 47 | /// [headerHeight] is the height of each column header, i.e. the first row. 48 | /// `Default: 188` 49 | double get headerHeight => _headerHeight; 50 | 51 | set headerHeight(double value) { 52 | _headerHeight = value; 53 | notifyListeners(); 54 | } 55 | 56 | late double _firstColumnWidth; 57 | 58 | /// [firstColumnWidth] determines first Column width size. 59 | /// 60 | /// Default: [200] 61 | double get firstColumnWidth => _firstColumnWidth; 62 | 63 | set firstColumnWidth(double value) { 64 | _firstColumnWidth = value; 65 | notifyListeners(); 66 | } 67 | 68 | late double _defaultsColumnWidth; 69 | 70 | /// [defaultsColumnWidth] defines the default width of all columns, 71 | /// it is possible to redefine it for each individual column. 72 | /// Default: [120] 73 | double get defaultsColumnWidth => _defaultsColumnWidth; 74 | 75 | set defaultsColumnWidth(double value) { 76 | _defaultsColumnWidth = value; 77 | notifyListeners(); 78 | } 79 | 80 | late double _defaultsRowHeight; 81 | 82 | /// [defaultsRowHeight] defines the default height of all rows, 83 | /// it is possible to redefine it for every single row. 84 | /// Default: [50] 85 | double get defaultsRowHeight => _defaultsRowHeight; 86 | 87 | set defaultsRowHeight(double value) { 88 | _defaultsRowHeight = value; 89 | notifyListeners(); 90 | } 91 | 92 | bool _visibleScrollbar = false; 93 | 94 | /// [visibleScrollbar] determines visibility of horizontal and vertical scrollbars. 95 | /// 96 | /// Default: [false] 97 | bool get visibleScrollbar => _visibleScrollbar; 98 | 99 | set visibleScrollbar(bool value) { 100 | _visibleScrollbar = value; 101 | notifyListeners(); 102 | } 103 | 104 | bool? _trackVisibilityScrollbar; 105 | 106 | /// [trackVisibilityScrollbar] indicates that the scrollbar track should be visible. 107 | /// 108 | /// 'optional' 109 | bool? get trackVisibilityScrollbar => _trackVisibilityScrollbar; 110 | 111 | set trackVisibilityScrollbar(bool? value) { 112 | _trackVisibilityScrollbar = value; 113 | notifyListeners(); 114 | } 115 | 116 | bool? _thumbVisibilityScrollbar; 117 | 118 | /// [thumbVisibilityScrollbar] indicates that the scrollbar thumb should be visible, even when a scroll is not underway. 119 | /// 120 | /// 'optional' 121 | bool? get thumbVisibilityScrollbar => _thumbVisibilityScrollbar; 122 | 123 | set thumbVisibilityScrollbar(bool? value) { 124 | _thumbVisibilityScrollbar = value; 125 | notifyListeners(); 126 | } 127 | 128 | bool _expanded = true; 129 | 130 | /// [expanded] indicates that the table expands, so it fills the available space along the horizontal and vertical axes. 131 | /// 132 | /// Default: [true] 133 | bool get expanded => _expanded; 134 | 135 | set expanded(bool value) { 136 | _expanded = value; 137 | notifyListeners(); 138 | } 139 | 140 | /// [duration] determines duration rendered animation of Rows/Columns expansion. 141 | /// 142 | /// Default: [500ms] 143 | final Duration duration; 144 | 145 | /// [curve] determines rendered curve animation of Rows/Columns expansion. 146 | /// 147 | /// Default: [Curves.fastOutSlowIn] 148 | final Curve curve; 149 | 150 | /// [scrollShadowDuration] determines duration rendered animation of shadows. 151 | /// 152 | /// Default: [500ms] 153 | final Duration scrollShadowDuration; 154 | 155 | /// [scrollShadowFadeInCurve] determines rendered curve animation of shadows appearance. 156 | /// 157 | /// Default: [Curves.easeIn] 158 | final Curve scrollShadowFadeInCurve; 159 | 160 | /// [scrollShadowFadeOutCurve] determines rendered curve animation of shadows disappearance. 161 | /// 162 | /// Default: [Curves.easeOut] 163 | final Curve scrollShadowFadeOutCurve; 164 | 165 | /// [scrollShadowColor] determines rendered color of shadows. 166 | /// 167 | /// Default: [Colors.transparent] 168 | final Color scrollShadowColor; 169 | 170 | /// [scrollShadowSize] determines size of shadows. 171 | /// 172 | /// Default: [10] 173 | final double scrollShadowSize; 174 | 175 | set rows(List value) { 176 | _removeRowsListener(); 177 | _rows = value; 178 | _addRowsListener(); 179 | notifyListeners(); 180 | } 181 | 182 | /// [ExpandableTableController] class constructor. 183 | ExpandableTableController({ 184 | required ExpandableTableCell firstHeaderCell, 185 | required List headers, 186 | required List rows, 187 | bool visibleScrollbar = false, 188 | bool? trackVisibilityScrollbar, 189 | bool? thumbVisibilityScrollbar, 190 | bool expanded = true, 191 | this.duration = const Duration(milliseconds: 500), 192 | this.curve = Curves.fastOutSlowIn, 193 | this.scrollShadowDuration = const Duration(milliseconds: 500), 194 | this.scrollShadowFadeInCurve = Curves.easeIn, 195 | this.scrollShadowFadeOutCurve = Curves.easeOut, 196 | this.scrollShadowColor = Colors.transparent, 197 | this.scrollShadowSize = 10, 198 | double headerHeight = 188, 199 | double firstColumnWidth = 200, 200 | double defaultsColumnWidth = 120, 201 | double defaultsRowHeight = 50, 202 | }) { 203 | _firstHeaderCell = firstHeaderCell; 204 | _headerHeight = headerHeight; 205 | _firstColumnWidth = firstColumnWidth; 206 | _defaultsColumnWidth = defaultsColumnWidth; 207 | _defaultsRowHeight = defaultsRowHeight; 208 | _headers = headers; 209 | _rows = rows; 210 | _visibleScrollbar = visibleScrollbar; 211 | _trackVisibilityScrollbar = trackVisibilityScrollbar; 212 | _thumbVisibilityScrollbar = thumbVisibilityScrollbar; 213 | _expanded = expanded; 214 | _addHeadersListener(); 215 | _addRowsListener(); 216 | } 217 | 218 | void _addHeadersListener() { 219 | for (var i = 0; i < _headers.length; i++) { 220 | _headers[i].addListener(_listener); 221 | _headers[i].index = i; 222 | } 223 | } 224 | 225 | void _removeHeadersListener() { 226 | for (var header in _headers) { 227 | header.removeListener(_listener); 228 | } 229 | } 230 | 231 | void _addRowsListener() { 232 | for (var i = 0; i < _rows.length; i++) { 233 | _rows[i].addListener(_listener); 234 | _rows[i].index = i; 235 | } 236 | } 237 | 238 | void _removeRowsListener() { 239 | for (var row in _rows) { 240 | row.removeListener(_listener); 241 | } 242 | } 243 | 244 | @override 245 | void dispose() { 246 | _removeHeadersListener(); 247 | _removeRowsListener(); 248 | super.dispose(); 249 | } 250 | 251 | void _listener() => notifyListeners(); 252 | 253 | /// [allHeaders] returns all table headers, visible and not, including nested ones. 254 | List get allHeaders => _getAllHeaders(headers); 255 | 256 | /// [visibleHeaders] returns all visible table headers, including nested ones. 257 | List get visibleHeaders => 258 | allHeaders.where((element) => element.visible).toList(); 259 | 260 | /// [visibleHeadersWidth] returns the overall width of all visible table headers, including nested ones 261 | double get visibleHeadersWidth => visibleHeaders 262 | .map((e) => e.width ?? defaultsColumnWidth) 263 | .fold(0, (a, b) => a + b); 264 | 265 | List _getAllHeaders( 266 | List headers) { 267 | final List cells = []; 268 | for (var header in headers) { 269 | cells.add(header); 270 | if (header.children != null) { 271 | cells.addAll(_getAllHeaders(header.children!)); 272 | } 273 | } 274 | return cells; 275 | } 276 | 277 | /// [allRows] returns all table rows, visible and not, including nested ones. 278 | List get allRows => _getAllRows(rows); 279 | 280 | /// [visibleRows] returns all visible table rows, including nested ones. 281 | List get visibleRows => 282 | allRows.where((element) => element.visible).toList(); 283 | 284 | /// [visibleHeadersWidth] returns the overall width of all visible table rows, including nested ones 285 | double get visibleRowsHeight => visibleRows 286 | .map((e) => e.height ?? defaultsRowHeight) 287 | .fold(0, (a, b) => a + b); 288 | 289 | List _getAllRows(List rows) { 290 | final List rowsTmp = []; 291 | for (var row in rows) { 292 | rowsTmp.add(row); 293 | if (row.children != null) { 294 | rowsTmp.addAll(_getAllRows(row.children!)); 295 | } 296 | } 297 | return rowsTmp; 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /lib/src/widget_internal/table.dart: -------------------------------------------------------------------------------- 1 | // Flutter imports: 2 | import 'package:flutter/material.dart'; 3 | 4 | // Package imports: 5 | import 'package:flutter_scroll_shadow/flutter_scroll_shadow.dart'; 6 | import 'package:linked_scroll_controller/linked_scroll_controller.dart'; 7 | import 'package:provider/provider.dart'; 8 | 9 | // Project imports: 10 | import 'package:flutter_expandable_table/flutter_expandable_table.dart'; 11 | import 'package:flutter_expandable_table/src/widget_internal/cell.dart'; 12 | 13 | /// [InternalTable] it is the widget that builds the table. 14 | class InternalTable extends StatefulWidget { 15 | /// [InternalTable] constructor. 16 | const InternalTable({ 17 | super.key, 18 | }); 19 | 20 | @override 21 | InternalTableState createState() => InternalTableState(); 22 | } 23 | 24 | /// [InternalTable] state. 25 | class InternalTableState extends State { 26 | late LinkedScrollControllerGroup _horizontalLinkedControllers; 27 | late ScrollController _headController; 28 | late ScrollController _horizontalBodyController; 29 | late LinkedScrollControllerGroup _verticalLinkedControllers; 30 | late ScrollController _firstColumnController; 31 | late ScrollController _restColumnsController; 32 | 33 | @override 34 | void initState() { 35 | super.initState(); 36 | _horizontalLinkedControllers = LinkedScrollControllerGroup(); 37 | _headController = _horizontalLinkedControllers.addAndGet(); 38 | _horizontalBodyController = _horizontalLinkedControllers.addAndGet(); 39 | _verticalLinkedControllers = LinkedScrollControllerGroup(); 40 | _firstColumnController = _verticalLinkedControllers.addAndGet(); 41 | _restColumnsController = _verticalLinkedControllers.addAndGet(); 42 | } 43 | 44 | @override 45 | void dispose() { 46 | _headController.dispose(); 47 | _horizontalBodyController.dispose(); 48 | _restColumnsController.dispose(); 49 | _firstColumnController.dispose(); 50 | super.dispose(); 51 | } 52 | 53 | List _buildHeaderCells(ExpandableTableController data) => 54 | data.allHeaders 55 | .map( 56 | (e) => ExpandableTableCellWidget( 57 | height: data.headerHeight, 58 | width: e.width ?? data.defaultsColumnWidth, 59 | header: e, 60 | onTap: () { 61 | if (!e.disableDefaultOnTapExpansion) { 62 | e.toggleExpand(); 63 | } 64 | }, 65 | builder: e.cell.build, 66 | ), 67 | ) 68 | .toList(); 69 | 70 | Widget _buildRowCells( 71 | ExpandableTableController data, ExpandableTableRow row) { 72 | if (row.cells != null) { 73 | return Row( 74 | children: row.cells! 75 | .map( 76 | (cell) => ExpandableTableCellWidget( 77 | header: data.allHeaders[row.cells!.indexOf(cell)], 78 | row: row, 79 | height: row.height ?? data.defaultsRowHeight, 80 | width: data.allHeaders[row.cells!.indexOf(cell)].width ?? 81 | data.defaultsColumnWidth, 82 | builder: cell.build, 83 | ), 84 | ) 85 | .toList(), 86 | ); 87 | } else { 88 | return ExpandableTableCellWidget( 89 | height: row.height ?? data.defaultsRowHeight, 90 | width: double.infinity, 91 | row: row, 92 | builder: (context, details) => row.legend!, 93 | ); 94 | } 95 | } 96 | 97 | Widget _buildBody(ExpandableTableController data) => Row( 98 | children: [ 99 | Builder( 100 | builder: (context) { 101 | final Widget child = ListView( 102 | controller: _firstColumnController, 103 | physics: const ClampingScrollPhysics(), 104 | children: data.allRows 105 | .map( 106 | (e) => ChangeNotifierProvider.value( 107 | value: e, 108 | builder: (context, child) => ExpandableTableCellWidget( 109 | row: context.watch(), 110 | height: context.watch().height ?? 111 | data.defaultsRowHeight, 112 | width: data.firstColumnWidth, 113 | builder: context 114 | .watch() 115 | .firstCell 116 | .build, 117 | onTap: () { 118 | if (!e.disableDefaultOnTapExpansion) { 119 | e.toggleExpand(); 120 | } 121 | }, 122 | ), 123 | ), 124 | ) 125 | .toList(), 126 | ); 127 | return SizedBox( 128 | width: data.firstColumnWidth, 129 | child: ScrollConfiguration( 130 | behavior: ScrollConfiguration.of(context) 131 | .copyWith(scrollbars: false), 132 | child: ScrollShadow( 133 | size: data.scrollShadowSize, 134 | color: data.scrollShadowColor, 135 | fadeInCurve: data.scrollShadowFadeInCurve, 136 | fadeOutCurve: data.scrollShadowFadeOutCurve, 137 | duration: data.scrollShadowDuration, 138 | child: data.visibleScrollbar 139 | ? Scrollbar( 140 | controller: _firstColumnController, 141 | thumbVisibility: data.thumbVisibilityScrollbar, 142 | trackVisibility: data.trackVisibilityScrollbar, 143 | scrollbarOrientation: ScrollbarOrientation.left, 144 | child: child, 145 | ) 146 | : child, 147 | ), 148 | ), 149 | ); 150 | }, 151 | ), 152 | Builder( 153 | builder: (context) { 154 | final Widget child = SingleChildScrollView( 155 | controller: _horizontalBodyController, 156 | scrollDirection: Axis.horizontal, 157 | physics: const ClampingScrollPhysics(), 158 | child: AnimatedContainer( 159 | width: data.visibleHeadersWidth, 160 | duration: data.duration, 161 | curve: data.curve, 162 | child: ScrollShadow( 163 | size: data.scrollShadowSize, 164 | color: data.scrollShadowColor, 165 | fadeInCurve: data.scrollShadowFadeInCurve, 166 | fadeOutCurve: data.scrollShadowFadeOutCurve, 167 | duration: data.scrollShadowDuration, 168 | child: ListView( 169 | controller: _restColumnsController, 170 | physics: const ClampingScrollPhysics(), 171 | children: data.allRows 172 | .map( 173 | (e) => _buildRowCells(data, e), 174 | ) 175 | .toList(), 176 | ), 177 | ), 178 | ), 179 | ); 180 | 181 | return Expanded( 182 | child: ScrollConfiguration( 183 | behavior: ScrollConfiguration.of(context) 184 | .copyWith(scrollbars: false), 185 | child: ScrollShadow( 186 | size: data.scrollShadowSize, 187 | color: data.scrollShadowColor, 188 | fadeInCurve: data.scrollShadowFadeInCurve, 189 | fadeOutCurve: data.scrollShadowFadeOutCurve, 190 | duration: data.scrollShadowDuration, 191 | child: data.visibleScrollbar 192 | ? Scrollbar( 193 | controller: _horizontalBodyController, 194 | thumbVisibility: data.thumbVisibilityScrollbar, 195 | trackVisibility: data.trackVisibilityScrollbar, 196 | child: child, 197 | ) 198 | : child, 199 | ), 200 | ), 201 | ); 202 | }, 203 | ), 204 | ], 205 | ); 206 | 207 | double _computeTableWidth({required ExpandableTableController data}) => 208 | data.firstColumnWidth + 209 | (data.headers 210 | .map((e) => 211 | (e.width ?? data.defaultsColumnWidth) + 212 | _computeChildrenWidth( 213 | expandableTableHeader: e, 214 | defaultsColumnWidth: data.defaultsColumnWidth)) 215 | .reduce((value, element) => value + element)); 216 | 217 | double _computeTableHeight({required ExpandableTableController data}) => 218 | data.headerHeight + 219 | (data.rows 220 | .map((e) => 221 | (e.height ?? data.defaultsRowHeight) + 222 | _computeChildrenHeight( 223 | expandableTableRow: e, 224 | defaultsRowHeight: data.defaultsRowHeight)) 225 | .reduce((value, element) => value + element)); 226 | 227 | double _computeChildrenHeight({ 228 | required ExpandableTableRow expandableTableRow, 229 | required double defaultsRowHeight, 230 | }) => 231 | expandableTableRow.childrenExpanded 232 | ? expandableTableRow.children! 233 | .map((e) => 234 | (e.height ?? defaultsRowHeight) + 235 | _computeChildrenHeight( 236 | expandableTableRow: e, 237 | defaultsRowHeight: defaultsRowHeight)) 238 | .reduce((value, element) => value + element) 239 | : 0; 240 | 241 | double _computeChildrenWidth({ 242 | required ExpandableTableHeader expandableTableHeader, 243 | required double defaultsColumnWidth, 244 | }) => 245 | expandableTableHeader.childrenExpanded 246 | ? expandableTableHeader.children! 247 | .map((e) => 248 | (e.width ?? defaultsColumnWidth) + 249 | _computeChildrenWidth( 250 | expandableTableHeader: e, 251 | defaultsColumnWidth: defaultsColumnWidth)) 252 | .reduce((value, element) => value + element) 253 | : 0; 254 | 255 | @override 256 | Widget build(BuildContext context) { 257 | final ExpandableTableController data = 258 | context.watch(); 259 | return SizedBox( 260 | width: data.expanded ? null : _computeTableWidth(data: data), 261 | height: data.expanded ? null : _computeTableHeight(data: data), 262 | child: Column( 263 | children: [ 264 | SizedBox( 265 | height: data.headerHeight, 266 | child: Row( 267 | children: [ 268 | ExpandableTableCellWidget( 269 | height: data.headerHeight, 270 | width: data.firstColumnWidth, 271 | builder: data.firstHeaderCell.build, 272 | ), 273 | Expanded( 274 | child: ScrollShadow( 275 | size: data.scrollShadowSize, 276 | color: data.scrollShadowColor, 277 | fadeInCurve: data.scrollShadowFadeInCurve, 278 | fadeOutCurve: data.scrollShadowFadeOutCurve, 279 | duration: data.scrollShadowDuration, 280 | child: ListView( 281 | controller: _headController, 282 | physics: const ClampingScrollPhysics(), 283 | scrollDirection: Axis.horizontal, 284 | children: _buildHeaderCells(data), 285 | ), 286 | ), 287 | ), 288 | ], 289 | ), 290 | ), 291 | Expanded( 292 | child: _buildBody(data), 293 | ), 294 | ], 295 | ), 296 | ); 297 | } 298 | } 299 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 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 = 1020; 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 = "$(inherited) @executable_path/Frameworks"; 294 | PRODUCT_BUNDLE_IDENTIFIER = net.rcprogrammer.flutterBargraphExample; 295 | PRODUCT_NAME = "$(TARGET_NAME)"; 296 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 297 | SWIFT_VERSION = 5.0; 298 | VERSIONING_SYSTEM = "apple-generic"; 299 | }; 300 | name = Profile; 301 | }; 302 | 97C147031CF9000F007C117D /* Debug */ = { 303 | isa = XCBuildConfiguration; 304 | buildSettings = { 305 | ALWAYS_SEARCH_USER_PATHS = NO; 306 | CLANG_ANALYZER_NONNULL = YES; 307 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 308 | CLANG_CXX_LIBRARY = "libc++"; 309 | CLANG_ENABLE_MODULES = YES; 310 | CLANG_ENABLE_OBJC_ARC = YES; 311 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 312 | CLANG_WARN_BOOL_CONVERSION = YES; 313 | CLANG_WARN_COMMA = YES; 314 | CLANG_WARN_CONSTANT_CONVERSION = YES; 315 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 316 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 317 | CLANG_WARN_EMPTY_BODY = YES; 318 | CLANG_WARN_ENUM_CONVERSION = YES; 319 | CLANG_WARN_INFINITE_RECURSION = YES; 320 | CLANG_WARN_INT_CONVERSION = YES; 321 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 322 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 323 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 324 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 325 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 326 | CLANG_WARN_STRICT_PROTOTYPES = YES; 327 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 328 | CLANG_WARN_UNREACHABLE_CODE = YES; 329 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 330 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 331 | COPY_PHASE_STRIP = NO; 332 | DEBUG_INFORMATION_FORMAT = dwarf; 333 | ENABLE_STRICT_OBJC_MSGSEND = YES; 334 | ENABLE_TESTABILITY = YES; 335 | GCC_C_LANGUAGE_STANDARD = gnu99; 336 | GCC_DYNAMIC_NO_PIC = NO; 337 | GCC_NO_COMMON_BLOCKS = YES; 338 | GCC_OPTIMIZATION_LEVEL = 0; 339 | GCC_PREPROCESSOR_DEFINITIONS = ( 340 | "DEBUG=1", 341 | "$(inherited)", 342 | ); 343 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 344 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 345 | GCC_WARN_UNDECLARED_SELECTOR = YES; 346 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 347 | GCC_WARN_UNUSED_FUNCTION = YES; 348 | GCC_WARN_UNUSED_VARIABLE = YES; 349 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 350 | MTL_ENABLE_DEBUG_INFO = YES; 351 | ONLY_ACTIVE_ARCH = YES; 352 | SDKROOT = iphoneos; 353 | TARGETED_DEVICE_FAMILY = "1,2"; 354 | }; 355 | name = Debug; 356 | }; 357 | 97C147041CF9000F007C117D /* Release */ = { 358 | isa = XCBuildConfiguration; 359 | buildSettings = { 360 | ALWAYS_SEARCH_USER_PATHS = NO; 361 | CLANG_ANALYZER_NONNULL = YES; 362 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 363 | CLANG_CXX_LIBRARY = "libc++"; 364 | CLANG_ENABLE_MODULES = YES; 365 | CLANG_ENABLE_OBJC_ARC = YES; 366 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 367 | CLANG_WARN_BOOL_CONVERSION = YES; 368 | CLANG_WARN_COMMA = YES; 369 | CLANG_WARN_CONSTANT_CONVERSION = YES; 370 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 371 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 372 | CLANG_WARN_EMPTY_BODY = YES; 373 | CLANG_WARN_ENUM_CONVERSION = YES; 374 | CLANG_WARN_INFINITE_RECURSION = YES; 375 | CLANG_WARN_INT_CONVERSION = YES; 376 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 377 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 378 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 379 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 380 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 381 | CLANG_WARN_STRICT_PROTOTYPES = YES; 382 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 383 | CLANG_WARN_UNREACHABLE_CODE = YES; 384 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 385 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 386 | COPY_PHASE_STRIP = NO; 387 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 388 | ENABLE_NS_ASSERTIONS = NO; 389 | ENABLE_STRICT_OBJC_MSGSEND = YES; 390 | GCC_C_LANGUAGE_STANDARD = gnu99; 391 | GCC_NO_COMMON_BLOCKS = YES; 392 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 393 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 394 | GCC_WARN_UNDECLARED_SELECTOR = YES; 395 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 396 | GCC_WARN_UNUSED_FUNCTION = YES; 397 | GCC_WARN_UNUSED_VARIABLE = YES; 398 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 399 | MTL_ENABLE_DEBUG_INFO = NO; 400 | SDKROOT = iphoneos; 401 | SUPPORTED_PLATFORMS = iphoneos; 402 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 403 | TARGETED_DEVICE_FAMILY = "1,2"; 404 | VALIDATE_PRODUCT = YES; 405 | }; 406 | name = Release; 407 | }; 408 | 97C147061CF9000F007C117D /* Debug */ = { 409 | isa = XCBuildConfiguration; 410 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 411 | buildSettings = { 412 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 413 | CLANG_ENABLE_MODULES = YES; 414 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 415 | ENABLE_BITCODE = NO; 416 | INFOPLIST_FILE = Runner/Info.plist; 417 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 418 | PRODUCT_BUNDLE_IDENTIFIER = net.rcprogrammer.flutterBargraphExample; 419 | PRODUCT_NAME = "$(TARGET_NAME)"; 420 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 421 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 422 | SWIFT_VERSION = 5.0; 423 | VERSIONING_SYSTEM = "apple-generic"; 424 | }; 425 | name = Debug; 426 | }; 427 | 97C147071CF9000F007C117D /* Release */ = { 428 | isa = XCBuildConfiguration; 429 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 430 | buildSettings = { 431 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 432 | CLANG_ENABLE_MODULES = YES; 433 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 434 | ENABLE_BITCODE = NO; 435 | INFOPLIST_FILE = Runner/Info.plist; 436 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 437 | PRODUCT_BUNDLE_IDENTIFIER = net.rcprogrammer.flutterBargraphExample; 438 | PRODUCT_NAME = "$(TARGET_NAME)"; 439 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 440 | SWIFT_VERSION = 5.0; 441 | VERSIONING_SYSTEM = "apple-generic"; 442 | }; 443 | name = Release; 444 | }; 445 | /* End XCBuildConfiguration section */ 446 | 447 | /* Begin XCConfigurationList section */ 448 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 449 | isa = XCConfigurationList; 450 | buildConfigurations = ( 451 | 97C147031CF9000F007C117D /* Debug */, 452 | 97C147041CF9000F007C117D /* Release */, 453 | 249021D3217E4FDB00AE95B9 /* Profile */, 454 | ); 455 | defaultConfigurationIsVisible = 0; 456 | defaultConfigurationName = Release; 457 | }; 458 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 459 | isa = XCConfigurationList; 460 | buildConfigurations = ( 461 | 97C147061CF9000F007C117D /* Debug */, 462 | 97C147071CF9000F007C117D /* Release */, 463 | 249021D4217E4FDB00AE95B9 /* Profile */, 464 | ); 465 | defaultConfigurationIsVisible = 0; 466 | defaultConfigurationName = Release; 467 | }; 468 | /* End XCConfigurationList section */ 469 | }; 470 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 471 | } 472 | --------------------------------------------------------------------------------