├── test └── listtreeview_test.dart ├── example ├── 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 │ │ │ │ │ └── com │ │ │ │ │ │ └── example │ │ │ │ │ │ └── example │ │ │ │ │ │ └── MainActivity.kt │ │ │ │ └── AndroidManifest.xml │ │ │ ├── debug │ │ │ │ └── AndroidManifest.xml │ │ │ └── profile │ │ │ │ └── AndroidManifest.xml │ │ └── build.gradle │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ ├── .gitignore │ ├── settings.gradle │ └── build.gradle ├── lib │ ├── node_data.dart │ ├── tree_root.dart │ └── main.dart ├── README.md ├── .gitignore ├── test │ └── widget_test.dart ├── .metadata ├── analysis_options.yaml ├── pubspec.yaml └── pubspec.lock ├── images ├── tree.gif └── screen3.jpg ├── lib ├── list_treeview.dart └── tree │ ├── tree_define.dart │ ├── node │ └── tree_node.dart │ ├── tree_view.dart │ └── controller │ ├── node_controller.dart │ └── tree_controller.dart ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── .gitignore ├── pubspec.yaml ├── pubspec.lock └── README.md /test/listtreeview_test.dart: -------------------------------------------------------------------------------- 1 | void main() {} 2 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /images/tree.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sooxie/list_treeview/HEAD/images/tree.gif -------------------------------------------------------------------------------- /images/screen3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sooxie/list_treeview/HEAD/images/screen3.jpg -------------------------------------------------------------------------------- /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/sooxie/list_treeview/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/sooxie/list_treeview/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/sooxie/list_treeview/HEAD/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /lib/list_treeview.dart: -------------------------------------------------------------------------------- 1 | library list_treeview; 2 | 3 | export 'tree/tree_view.dart'; 4 | export 'tree/controller/tree_controller.dart'; 5 | export 'tree/node/tree_node.dart'; 6 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sooxie/list_treeview/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/sooxie/list_treeview/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/sooxie/list_treeview/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sooxie/list_treeview/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/sooxie/list_treeview/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/sooxie/list_treeview/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/sooxie/list_treeview/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/sooxie/list_treeview/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/sooxie/list_treeview/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/sooxie/list_treeview/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/sooxie/list_treeview/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/sooxie/list_treeview/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/sooxie/list_treeview/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/sooxie/list_treeview/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/sooxie/list_treeview/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/sooxie/list_treeview/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sooxie/list_treeview/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/sooxie/list_treeview/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sooxie/list_treeview/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/sooxie/list_treeview/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/com/example/example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.example 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /example/ios/Runner.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/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-7.4-all.zip 7 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 5f21edf8b66e31a39133177319414395cc5b5f48 8 | channel: stable 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /example/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 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/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 | -------------------------------------------------------------------------------- /example/lib/node_data.dart: -------------------------------------------------------------------------------- 1 | //import 'package:flutter/cupertino.dart'; 2 | //import 'package:list_treeview/list_treeview.dart'; 3 | 4 | /// The data class that is bound to the child node 5 | /// You must inherit from NodeData !!! 6 | /// You can customize any of your properties 7 | //class TreeNodeData extends NodeData { 8 | // TreeNodeData({this.label,this.color}) : super(); 9 | // 10 | // /// Other properties that you want to define 11 | // final String label; 12 | // final Color color; 13 | // String property1; 14 | // String property2; 15 | // String property3; 16 | // ///... 17 | //} 18 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # example 2 | 3 | A new Flutter project. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) 13 | 14 | For help getting started with Flutter development, view the 15 | [online documentation](https://docs.flutter.dev/), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.3.0 2 | 3 | - flutter 2.0 null safety support 4 | - Fix some bugs 5 | 6 | 7 | # 0.2.4 8 | 9 | - Fix some bug 10 | 11 | # 0.2.3 12 | 13 | - Add the selected method 14 | 15 | # 0.2.2 16 | 17 | - Added a way to disable implicit node toggling 18 | 19 | # 0.2.0 20 | 21 | - The controller must be initialized when the treeView create 22 | - Add asynchronous setup data 23 | - Fix bug 24 | 25 | # 0.1.4 26 | 27 | - Optimize code format 28 | 29 | # 0.1.2 30 | 31 | - Adjust the example 32 | 33 | # 0.1.1 34 | 35 | - Modify the document 36 | 37 | # 0.1.0 38 | 39 | - Add the project 40 | 41 | ## 0.0.1 42 | 43 | - Initialize the project 44 | -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.6.10' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:7.1.2' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | mavenCentral() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 9.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | **/doc/api/ 26 | **/ios/Flutter/.last_build_id 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .packages 31 | .pub-cache/ 32 | .pub/ 33 | /build/ 34 | 35 | # Web related 36 | lib/generated_plugin_registrant.dart 37 | 38 | # Symbolication related 39 | app.*.symbols 40 | 41 | # Obfuscation related 42 | app.*.map.json 43 | 44 | # Android Studio will place build artifacts here 45 | /android/app/debug 46 | /android/app/profile 47 | /android/app/release 48 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Sooxie 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /example/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility in the flutter_test package. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:example/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MyApp()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /example/.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. 5 | 6 | version: 7 | revision: f1875d570e39de09040c8f79aa13cc56baab8db1 8 | channel: stable 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 17 | base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 18 | - platform: android 19 | create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 20 | base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 21 | - platform: ios 22 | create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 23 | base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 24 | 25 | # User provided section 26 | 27 | # List of Local paths (relative to this file) that should be 28 | # ignored by the migrate tool. 29 | # 30 | # Files that are not part of the templates will be ignored by default. 31 | unmanaged_files: 32 | - 'lib/main.dart' 33 | - 'ios/Runner.xcodeproj/project.pbxproj' 34 | -------------------------------------------------------------------------------- /example/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at 17 | # https://dart-lang.github.io/linter/lints/index.html. 18 | # 19 | # Instead of disabling a lint rule for the entire project in the 20 | # section below, it can also be suppressed for a single line of code 21 | # or a specific dart file by using the `// ignore: name_of_lint` and 22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 23 | # producing the lint. 24 | rules: 25 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 26 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 27 | 28 | # Additional information about this file can be found at 29 | # https://dart.dev/guides/language/analysis-options 30 | -------------------------------------------------------------------------------- /lib/tree/tree_define.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 sooxie 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | import 'package:flutter/material.dart'; 22 | import 'package:list_treeview/list_treeview.dart'; 23 | 24 | typedef WidgetBuilder = Widget Function(BuildContext context); 25 | typedef ExpandCallback = bool Function(NodeData? item); 26 | typedef IndexedBuilder = Widget Function(BuildContext context, NodeData data); 27 | 28 | typedef PressCallback = Function(NodeData item); 29 | 30 | class Constant { 31 | final int min = -1; 32 | } 33 | -------------------------------------------------------------------------------- /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/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 15 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 30 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | Example 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | example 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(FLUTTER_BUILD_NUMBER) 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UIViewControllerBasedStatusBarAppearance 45 | 46 | CADisableMinimumFrameDurationOnPhone 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /.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 | 34 | 35 | # Android related 36 | **/android/**/gradle-wrapper.jar 37 | **/android/.gradle 38 | **/android/captures/ 39 | **/android/gradlew 40 | **/android/gradlew.bat 41 | **/android/local.properties 42 | **/android/**/GeneratedPluginRegistrant.java 43 | 44 | # iOS/XCode related 45 | **/ios/**/*.mode1v3 46 | **/ios/**/*.mode2v3 47 | **/ios/**/*.moved-aside 48 | **/ios/**/*.pbxuser 49 | **/ios/**/*.perspectivev3 50 | **/ios/**/*sync/ 51 | **/ios/**/.sconsign.dblite 52 | **/ios/**/.tags* 53 | **/ios/**/.vagrant/ 54 | **/ios/**/DerivedData/ 55 | **/ios/**/Icon? 56 | **/ios/**/Pods/ 57 | **/ios/**/.symlinks/ 58 | **/ios/**/profile 59 | **/ios/**/xcuserdata 60 | **/ios/.generated/ 61 | **/ios/Flutter/App.framework 62 | **/ios/Flutter/Flutter.framework 63 | **/ios/Flutter/Flutter.podspec 64 | **/ios/Flutter/Generated.xcconfig 65 | **/ios/Flutter/app.flx 66 | **/ios/Flutter/app.zip 67 | **/ios/Flutter/flutter_assets/ 68 | **/ios/Flutter/flutter_export_environment.sh 69 | **/ios/ServiceDefinitions.json 70 | **/ios/Runner/GeneratedPluginRegistrant.* 71 | 72 | # Exceptions to above rules. 73 | !**/ios/**/default.mode1v3 74 | !**/ios/**/default.mode2v3 75 | !**/ios/**/default.pbxuser 76 | !**/ios/**/default.perspectivev3 77 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 78 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: list_treeview 2 | description: A treeview for Flutter. Based on the listview. Infinitely increasing child levels and child nodes. 3 | version: 0.3.0 4 | homepage: https://github.com/sooxie/list_treeview 5 | 6 | environment: 7 | sdk: ">=2.12.0 <3.0.0" 8 | 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | 13 | dev_dependencies: 14 | flutter_test: 15 | sdk: flutter 16 | 17 | # For information on the generic Dart part of this file, see the 18 | # following page: https://dart.dev/tools/pub/pubspec 19 | 20 | # The following section is specific to Flutter. 21 | flutter: 22 | # publish : flutter packages pub publish --server https://pub.dartlang.org 23 | # format dartfmt -w . 24 | # To add assets to your package, add an assets section, like this: 25 | # assets: 26 | # - images/a_dot_burr.jpeg 27 | # - images/a_dot_ham.jpeg 28 | # 29 | # For details regarding assets in packages, see 30 | # https://flutter.dev/assets-and-images/#from-packages 31 | # 32 | # An image asset can refer to one or more resolution-specific "variants", see 33 | # https://flutter.dev/assets-and-images/#resolution-aware. 34 | 35 | # To add custom fonts to your package, add a fonts section here, 36 | # in this "flutter" section. Each entry in this list should have a 37 | # "family" key with the font family name, and a "fonts" key with a 38 | # list giving the asset and other descriptors for the font. For 39 | # example: 40 | # fonts: 41 | # - family: Schyler 42 | # fonts: 43 | # - asset: fonts/Schyler-Regular.ttf 44 | # - asset: fonts/Schyler-Italic.ttf 45 | # style: italic 46 | # - family: Trajan Pro 47 | # fonts: 48 | # - asset: fonts/TrajanPro.ttf 49 | # - asset: fonts/TrajanPro_Bold.ttf 50 | # weight: 700 51 | # 52 | # For details regarding fonts in packages, see 53 | # https://flutter.dev/custom-fonts/#from-packages 54 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion flutter.compileSdkVersion 30 | ndkVersion flutter.ndkVersion 31 | 32 | compileOptions { 33 | sourceCompatibility JavaVersion.VERSION_1_8 34 | targetCompatibility JavaVersion.VERSION_1_8 35 | } 36 | 37 | kotlinOptions { 38 | jvmTarget = '1.8' 39 | } 40 | 41 | sourceSets { 42 | main.java.srcDirs += 'src/main/kotlin' 43 | } 44 | 45 | defaultConfig { 46 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 47 | applicationId "com.example.example" 48 | // You can update the following values to match your application needs. 49 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. 50 | minSdkVersion flutter.minSdkVersion 51 | targetSdkVersion flutter.targetSdkVersion 52 | versionCode flutterVersionCode.toInteger() 53 | versionName flutterVersionName 54 | } 55 | 56 | buildTypes { 57 | release { 58 | // TODO: Add your own signing config for the release build. 59 | // Signing with the debug keys for now, so `flutter run --release` works. 60 | signingConfig signingConfigs.debug 61 | } 62 | } 63 | } 64 | 65 | flutter { 66 | source '../..' 67 | } 68 | 69 | dependencies { 70 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 71 | } 72 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/tree/node/tree_node.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 sooxie 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | import '../../list_treeview.dart'; 21 | import '../tree_define.dart'; 22 | 23 | class TreeNode { 24 | TreeNode({this.lazyItem, this.expandCallback}); 25 | 26 | bool _expanded = false; 27 | ExpandCallback? expandCallback; 28 | 29 | final TreeNodeItem? lazyItem; 30 | 31 | NodeData? get item { 32 | if (lazyItem != null) { 33 | return lazyItem!.item; 34 | } 35 | return null; 36 | } 37 | 38 | bool get expanded { 39 | if (expandCallback != null) { 40 | _expanded = expandCallback!(this.item); 41 | } 42 | return _expanded; 43 | } 44 | 45 | set setExpanded(bool expanded) { 46 | this.expandCallback = null; 47 | _expanded = expanded; 48 | } 49 | } 50 | 51 | class TreeNodeItem { 52 | TreeNodeItem({this.parent, this.index, this.controller}); 53 | 54 | final dynamic parent; 55 | final int? index; 56 | final TreeViewController? controller; 57 | NodeData? _item; 58 | 59 | NodeData get item { 60 | if (_item == null) { 61 | _item = controller!.dataForTreeNode(this); 62 | } 63 | return _item!; 64 | } 65 | } 66 | 67 | ///This class contains information about the nodes, such as Index and level, and whether to expand. It also contains other information 68 | class NodeData { 69 | NodeData() : children = []; 70 | List children; 71 | bool isSelected = false; 72 | 73 | /// Index in all nodes 74 | int index = -1; 75 | 76 | /// Index in parent node 77 | int indexInParent = -1; 78 | int level = -1; 79 | bool isExpand = false; 80 | 81 | addChild(NodeData child) { 82 | children.add(child); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /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 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: example 2 | description: A new Flutter project. 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `flutter pub publish`. This is preferred for private packages. 6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 7 | 8 | # The following defines the version and build number for your application. 9 | # A version number is three numbers separated by dots, like 1.2.43 10 | # followed by an optional build number separated by a +. 11 | # Both the version and the builder number may be overridden in flutter 12 | # build by specifying --build-name and --build-number, respectively. 13 | # In Android, build-name is used as versionName while build-number used as versionCode. 14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 16 | # Read more about iOS versioning at 17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 18 | version: 1.0.0+1 19 | 20 | environment: 21 | sdk: ">=2.17.6 <3.0.0" 22 | 23 | # Dependencies specify other packages that your package needs in order to work. 24 | # To automatically upgrade your package dependencies to the latest versions 25 | # consider running `flutter pub upgrade --major-versions`. Alternatively, 26 | # dependencies can be manually updated by changing the version numbers below to 27 | # the latest version available on pub.dev. To see which dependencies have newer 28 | # versions available, run `flutter pub outdated`. 29 | dependencies: 30 | flutter: 31 | sdk: flutter 32 | list_treeview: 33 | path: ../ 34 | 35 | 36 | # The following adds the Cupertino Icons font to your application. 37 | # Use with the CupertinoIcons class for iOS style icons. 38 | cupertino_icons: ^1.0.2 39 | 40 | dev_dependencies: 41 | flutter_test: 42 | sdk: flutter 43 | 44 | # The "flutter_lints" package below contains a set of recommended lints to 45 | # encourage good coding practices. The lint set provided by the package is 46 | # activated in the `analysis_options.yaml` file located at the root of your 47 | # package. See that file for information about deactivating specific lint 48 | # rules and activating additional ones. 49 | flutter_lints: ^2.0.0 50 | 51 | # For information on the generic Dart part of this file, see the 52 | # following page: https://dart.dev/tools/pub/pubspec 53 | 54 | # The following section is specific to Flutter packages. 55 | flutter: 56 | 57 | # The following line ensures that the Material Icons font is 58 | # included with your application, so that you can use the icons in 59 | # the material Icons class. 60 | uses-material-design: true 61 | 62 | # To add assets to your application, add an assets section, like this: 63 | # assets: 64 | # - images/a_dot_burr.jpeg 65 | # - images/a_dot_ham.jpeg 66 | 67 | # An image asset can refer to one or more resolution-specific "variants", see 68 | # https://flutter.dev/assets-and-images/#resolution-aware 69 | 70 | # For details regarding adding assets from package dependencies, see 71 | # https://flutter.dev/assets-and-images/#from-packages 72 | 73 | # To add custom fonts to your application, add a fonts section here, 74 | # in this "flutter" section. Each entry in this list should have a 75 | # "family" key with the font family name, and a "fonts" key with a 76 | # list giving the asset and other descriptors for the font. For 77 | # example: 78 | # fonts: 79 | # - family: Schyler 80 | # fonts: 81 | # - asset: fonts/Schyler-Regular.ttf 82 | # - asset: fonts/Schyler-Italic.ttf 83 | # style: italic 84 | # - family: Trajan Pro 85 | # fonts: 86 | # - asset: fonts/TrajanPro.ttf 87 | # - asset: fonts/TrajanPro_Bold.ttf 88 | # weight: 700 89 | # 90 | # For details regarding fonts from package dependencies, 91 | # see https://flutter.dev/custom-fonts/#from-packages 92 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | url: "https://pub.flutter-io.cn" 9 | source: hosted 10 | version: "2.8.2" 11 | boolean_selector: 12 | dependency: transitive 13 | description: 14 | name: boolean_selector 15 | url: "https://pub.flutter-io.cn" 16 | source: hosted 17 | version: "2.1.0" 18 | characters: 19 | dependency: transitive 20 | description: 21 | name: characters 22 | url: "https://pub.flutter-io.cn" 23 | source: hosted 24 | version: "1.2.0" 25 | charcode: 26 | dependency: transitive 27 | description: 28 | name: charcode 29 | url: "https://pub.flutter-io.cn" 30 | source: hosted 31 | version: "1.3.1" 32 | clock: 33 | dependency: transitive 34 | description: 35 | name: clock 36 | url: "https://pub.flutter-io.cn" 37 | source: hosted 38 | version: "1.1.0" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.flutter-io.cn" 44 | source: hosted 45 | version: "1.15.0" 46 | fake_async: 47 | dependency: transitive 48 | description: 49 | name: fake_async 50 | url: "https://pub.flutter-io.cn" 51 | source: hosted 52 | version: "1.2.0" 53 | flutter: 54 | dependency: "direct main" 55 | description: flutter 56 | source: sdk 57 | version: "0.0.0" 58 | flutter_test: 59 | dependency: "direct dev" 60 | description: flutter 61 | source: sdk 62 | version: "0.0.0" 63 | matcher: 64 | dependency: transitive 65 | description: 66 | name: matcher 67 | url: "https://pub.flutter-io.cn" 68 | source: hosted 69 | version: "0.12.11" 70 | material_color_utilities: 71 | dependency: transitive 72 | description: 73 | name: material_color_utilities 74 | url: "https://pub.flutter-io.cn" 75 | source: hosted 76 | version: "0.1.3" 77 | meta: 78 | dependency: transitive 79 | description: 80 | name: meta 81 | url: "https://pub.flutter-io.cn" 82 | source: hosted 83 | version: "1.7.0" 84 | path: 85 | dependency: transitive 86 | description: 87 | name: path 88 | url: "https://pub.flutter-io.cn" 89 | source: hosted 90 | version: "1.8.0" 91 | sky_engine: 92 | dependency: transitive 93 | description: flutter 94 | source: sdk 95 | version: "0.0.99" 96 | source_span: 97 | dependency: transitive 98 | description: 99 | name: source_span 100 | url: "https://pub.flutter-io.cn" 101 | source: hosted 102 | version: "1.8.1" 103 | stack_trace: 104 | dependency: transitive 105 | description: 106 | name: stack_trace 107 | url: "https://pub.flutter-io.cn" 108 | source: hosted 109 | version: "1.10.0" 110 | stream_channel: 111 | dependency: transitive 112 | description: 113 | name: stream_channel 114 | url: "https://pub.flutter-io.cn" 115 | source: hosted 116 | version: "2.1.0" 117 | string_scanner: 118 | dependency: transitive 119 | description: 120 | name: string_scanner 121 | url: "https://pub.flutter-io.cn" 122 | source: hosted 123 | version: "1.1.0" 124 | term_glyph: 125 | dependency: transitive 126 | description: 127 | name: term_glyph 128 | url: "https://pub.flutter-io.cn" 129 | source: hosted 130 | version: "1.2.0" 131 | test_api: 132 | dependency: transitive 133 | description: 134 | name: test_api 135 | url: "https://pub.flutter-io.cn" 136 | source: hosted 137 | version: "0.4.8" 138 | typed_data: 139 | dependency: transitive 140 | description: 141 | name: typed_data 142 | url: "https://pub.flutter-io.cn" 143 | source: hosted 144 | version: "1.3.0" 145 | vector_math: 146 | dependency: transitive 147 | description: 148 | name: vector_math 149 | url: "https://pub.flutter-io.cn" 150 | source: hosted 151 | version: "2.1.1" 152 | sdks: 153 | dart: ">=2.14.0 <3.0.0" 154 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## ListTreeView 2 | [A treeview for Flutter. Based on the listview](https://github.com/sooxie/list_treeview) 3 | 4 | - Highly customizable. It only manages the tree structure of the data, and the UI is designed by yourself. 5 | - Performance is efficient because of the Listview's reuse mechanism. 6 | - Infinitely increasing child levels and child nodes 7 | 8 | ## Flutter 2.0 support 9 | - flutter 2.0 null safety support in version **0.3.0** 10 | 11 | ![avatar](./images/tree.gif) 12 | ![avatar](./images/screen3.jpg) 13 | 14 | 15 | 16 | ## Getting Started 17 | 18 | ### Installation 19 | 20 | ##### 1. Depend on it 21 | ```yaml 22 | dependencies: 23 | list_treeview: [version] 24 | ``` 25 | 26 | ##### 2. Install it 27 | ```dart 28 | $ flutter pub get 29 | 30 | ``` 31 | 32 | ##### 3. Import it 33 | ```dart 34 | import 'package:list_treeview/list_treeview.dart'; 35 | 36 | ``` 37 | 38 | ## Usage 39 | 40 | 41 | #### 1、initialize controller. 42 | The controller must be initialized when the treeView create 43 | ```dart 44 | class _TreePageState extends State { 45 | TreeViewController _controller; 46 | @override 47 | void initState() { 48 | super.initState(); 49 | ///The controller must be initialized when the treeView create 50 | _controller = TreeViewController(); 51 | } 52 | } 53 | ``` 54 | 55 | 56 | #### 2、Set the data for each row. 57 | - Your data class must inherit from **Nodedata**, and you can customize other properties of the class. 58 | 59 | ```dart 60 | /// The data class that is bound to the child node 61 | /// You must inherit from NodeData !!! 62 | /// You can customize any of your properties 63 | class TreeNodeData extends NodeData { 64 | TreeNodeData({this.label,this.color}) : super(); 65 | 66 | /// Other properties that you want to define 67 | final String label; 68 | final Color color; 69 | String property1; 70 | String property2; 71 | String property3; 72 | ///... 73 | } 74 | ``` 75 | 76 | - Set the TreeView's data, use '_controller.treeData()' 77 | 78 | ```dart 79 | void getData() async { 80 | print('start get data'); 81 | _isSuccess = false; 82 | await Future.delayed(Duration(seconds: 2)); 83 | 84 | var colors1 = TreeNodeData(label: 'Colors1'); 85 | var color11 = TreeNodeData( 86 | label: 'rgb(0,139,69)', color: Color.fromARGB(255, 0, 139, 69)); 87 | var color12 = TreeNodeData( 88 | label: 'rgb(0,139,69)', color: Color.fromARGB(255, 0, 191, 255)); 89 | var color13 = TreeNodeData( 90 | label: 'rgb(0,139,69)', color: Color.fromARGB(255, 255, 106, 106)); 91 | var color14 = TreeNodeData( 92 | label: 'rgb(0,139,69)', color: Color.fromARGB(255, 160, 32, 240)); 93 | colors1.addChild(color11); 94 | colors1.addChild(color12); 95 | colors1.addChild(color13); 96 | colors1.addChild(color14); 97 | 98 | var colors2 = TreeNodeData(label: 'Colors2'); 99 | var color21 = TreeNodeData( 100 | label: 'rgb(0,139,69)', color: Color.fromARGB(255, 255, 64, 64)); 101 | var color22 = TreeNodeData( 102 | label: 'rgb(0,139,69)', color: Color.fromARGB(255, 28, 134, 238)); 103 | var color23 = TreeNodeData( 104 | label: 'rgb(0,139,69)', color: Color.fromARGB(255, 255, 106, 106)); 105 | var color24 = TreeNodeData( 106 | label: 'rgb(0,139,69)', color: Color.fromARGB(255, 205, 198, 115)); 107 | colors2.addChild(color21); 108 | colors2.addChild(color22); 109 | colors2.addChild(color23); 110 | colors2.addChild(color24); 111 | 112 | /// set data 113 | _controller.treeData([colors1, colors2]); 114 | print('set treeData suceess'); 115 | 116 | setState(() { 117 | _isSuccess = true; 118 | }); 119 | 120 | } 121 | 122 | ``` 123 | 124 | #### Insert 125 | 126 | ```dart 127 | _controller.insertAtFront(dataNode,newNode); 128 | //_controller.insertAtRear(dataNode, newNode); 129 | //_controller.insertAtIndex(1, dataNode, newNode); 130 | ``` 131 | 132 | #### Remove 133 | 134 | ```dart 135 | _controller.removeItem(item); 136 | ``` 137 | 138 | #### Expand or collapse children 139 | ```dart 140 | /// Control item to expand or collapse 141 | /// [index] The index of the selected item 142 | _controller.expandOrCollapse(index); 143 | ``` 144 | 145 | #### Selected 146 | ``` 147 | /// select only itself 148 | _controller.selectItem(item); 149 | 150 | /// Select itself and all child nodes 151 | _controller.selectAllChild(item); 152 | ``` 153 | 154 | 155 | 156 | -------------------------------------------------------------------------------- /example/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | url: "https://pub.flutter-io.cn" 9 | source: hosted 10 | version: "2.8.2" 11 | boolean_selector: 12 | dependency: transitive 13 | description: 14 | name: boolean_selector 15 | url: "https://pub.flutter-io.cn" 16 | source: hosted 17 | version: "2.1.0" 18 | characters: 19 | dependency: transitive 20 | description: 21 | name: characters 22 | url: "https://pub.flutter-io.cn" 23 | source: hosted 24 | version: "1.2.0" 25 | charcode: 26 | dependency: transitive 27 | description: 28 | name: charcode 29 | url: "https://pub.flutter-io.cn" 30 | source: hosted 31 | version: "1.3.1" 32 | clock: 33 | dependency: transitive 34 | description: 35 | name: clock 36 | url: "https://pub.flutter-io.cn" 37 | source: hosted 38 | version: "1.1.0" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.flutter-io.cn" 44 | source: hosted 45 | version: "1.16.0" 46 | cupertino_icons: 47 | dependency: "direct main" 48 | description: 49 | name: cupertino_icons 50 | url: "https://pub.flutter-io.cn" 51 | source: hosted 52 | version: "1.0.5" 53 | fake_async: 54 | dependency: transitive 55 | description: 56 | name: fake_async 57 | url: "https://pub.flutter-io.cn" 58 | source: hosted 59 | version: "1.3.0" 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 | url: "https://pub.flutter-io.cn" 70 | source: hosted 71 | version: "2.0.1" 72 | flutter_test: 73 | dependency: "direct dev" 74 | description: flutter 75 | source: sdk 76 | version: "0.0.0" 77 | lints: 78 | dependency: transitive 79 | description: 80 | name: lints 81 | url: "https://pub.flutter-io.cn" 82 | source: hosted 83 | version: "2.0.0" 84 | list_treeview: 85 | dependency: "direct main" 86 | description: 87 | path: ".." 88 | relative: true 89 | source: path 90 | version: "0.3.0" 91 | matcher: 92 | dependency: transitive 93 | description: 94 | name: matcher 95 | url: "https://pub.flutter-io.cn" 96 | source: hosted 97 | version: "0.12.11" 98 | material_color_utilities: 99 | dependency: transitive 100 | description: 101 | name: material_color_utilities 102 | url: "https://pub.flutter-io.cn" 103 | source: hosted 104 | version: "0.1.4" 105 | meta: 106 | dependency: transitive 107 | description: 108 | name: meta 109 | url: "https://pub.flutter-io.cn" 110 | source: hosted 111 | version: "1.7.0" 112 | path: 113 | dependency: transitive 114 | description: 115 | name: path 116 | url: "https://pub.flutter-io.cn" 117 | source: hosted 118 | version: "1.8.1" 119 | sky_engine: 120 | dependency: transitive 121 | description: flutter 122 | source: sdk 123 | version: "0.0.99" 124 | source_span: 125 | dependency: transitive 126 | description: 127 | name: source_span 128 | url: "https://pub.flutter-io.cn" 129 | source: hosted 130 | version: "1.8.2" 131 | stack_trace: 132 | dependency: transitive 133 | description: 134 | name: stack_trace 135 | url: "https://pub.flutter-io.cn" 136 | source: hosted 137 | version: "1.10.0" 138 | stream_channel: 139 | dependency: transitive 140 | description: 141 | name: stream_channel 142 | url: "https://pub.flutter-io.cn" 143 | source: hosted 144 | version: "2.1.0" 145 | string_scanner: 146 | dependency: transitive 147 | description: 148 | name: string_scanner 149 | url: "https://pub.flutter-io.cn" 150 | source: hosted 151 | version: "1.1.0" 152 | term_glyph: 153 | dependency: transitive 154 | description: 155 | name: term_glyph 156 | url: "https://pub.flutter-io.cn" 157 | source: hosted 158 | version: "1.2.0" 159 | test_api: 160 | dependency: transitive 161 | description: 162 | name: test_api 163 | url: "https://pub.flutter-io.cn" 164 | source: hosted 165 | version: "0.4.9" 166 | vector_math: 167 | dependency: transitive 168 | description: 169 | name: vector_math 170 | url: "https://pub.flutter-io.cn" 171 | source: hosted 172 | version: "2.1.2" 173 | sdks: 174 | dart: ">=2.17.6 <3.0.0" 175 | -------------------------------------------------------------------------------- /lib/tree/tree_view.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 sooxie 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | import 'dart:io'; 21 | 22 | import 'package:flutter/material.dart'; 23 | import 'controller/tree_controller.dart'; 24 | import 'node/tree_node.dart'; 25 | import 'tree_define.dart'; 26 | 27 | /// ListTreeView based on ListView. 28 | /// [ListView] is the most commonly used scrolling widget. It displays its 29 | /// children one after another in the scroll direction. In the cross axis, the 30 | /// children are required to fill the [ListView]. 31 | 32 | /// The default constructor takes takes an [IndexedBuilder] of children.which 33 | /// builds the children on demand. 34 | 35 | class ListTreeView extends StatefulWidget { 36 | ListTreeView({ 37 | required this.itemBuilder, 38 | this.onTap, 39 | this.onLongPress, 40 | this.controller, 41 | this.toggleNodeOnTap = true, 42 | this.shrinkWrap = false, 43 | this.removeTop = true, 44 | this.removeBottom = true, 45 | this.reverse = false, 46 | this.padding = const EdgeInsets.all(0), 47 | }) : assert(controller != null, "The TreeViewController can't be empty"); 48 | 49 | final IndexedBuilder itemBuilder; 50 | final PressCallback? onLongPress; 51 | final TreeViewController? controller; 52 | final PressCallback? onTap; 53 | final bool shrinkWrap; 54 | final bool removeBottom; 55 | final bool removeTop; 56 | final bool reverse; 57 | final EdgeInsetsGeometry padding; 58 | 59 | /// If `false` you have to explicitly expand or collapse nodes. 60 | /// 61 | /// This can be done using the [TreeViewControlle].`expandOrCollapse()` method. 62 | final bool toggleNodeOnTap; 63 | 64 | @override 65 | State createState() { 66 | return _ListTreeViewState(); 67 | } 68 | } 69 | 70 | class _ListTreeViewState extends State { 71 | @override 72 | void initState() { 73 | super.initState(); 74 | widget.controller?.addListener(updateView); 75 | } 76 | 77 | /// update view 78 | void updateView() { 79 | if (mounted) { 80 | setState(() => {}); 81 | } 82 | } 83 | 84 | /// expand or collapse children 85 | void itemClick(int index) { 86 | widget.controller?.expandOrCollapse(index); 87 | } 88 | 89 | @override 90 | Widget build(BuildContext context) { 91 | if (widget.controller == null || 92 | widget.controller!.data == null || 93 | widget.controller!.data!.length == 0) { 94 | return Center( 95 | child: Text(''), 96 | ); 97 | } 98 | 99 | return Container( 100 | child: MediaQuery.removePadding( 101 | removeBottom: widget.removeBottom, 102 | removeTop: widget.removeTop, 103 | context: context, 104 | child: ListView.builder( 105 | physics: BouncingScrollPhysics(), 106 | padding: widget.padding, 107 | reverse: widget.reverse, 108 | shrinkWrap: widget.shrinkWrap, 109 | itemBuilder: (BuildContext context, int index) { 110 | // int num = widget.controller.numberOfVisibleChild(); 111 | ///The [TreeNode] associated with the current item 112 | TreeNode treeNode = widget.controller!.treeNodeOfIndex(index); 113 | 114 | ///The level of the current item 115 | treeNode.item!.level = 116 | widget.controller!.levelOfNode(treeNode.item); 117 | treeNode.item!.isExpand = 118 | widget.controller!.isExpanded(treeNode.item); 119 | treeNode.item!.index = index; 120 | NodeData? parent = widget.controller!.parentOfItem(treeNode.item); 121 | if (parent != null && parent.children.length > 0) { 122 | treeNode.item!.indexInParent = 123 | parent.children.indexOf(treeNode.item!); 124 | } else { 125 | treeNode.item!.indexInParent = 0; 126 | } 127 | 128 | ///Your event is passed through the [Function] with the relevant data 129 | return InkWell( 130 | onLongPress: () { 131 | if (widget.onLongPress != null) { 132 | widget.onLongPress!(treeNode.item!); 133 | } 134 | }, 135 | onTap: () { 136 | if (widget.toggleNodeOnTap) { 137 | itemClick(index); 138 | } 139 | if (widget.onTap != null) { 140 | widget.onTap!(treeNode.item!); 141 | } 142 | }, 143 | child: Container( 144 | child: widget.itemBuilder(context, treeNode.item!), 145 | ), 146 | ); 147 | }, 148 | itemCount: widget.controller?.numberOfVisibleChild(), 149 | )), 150 | ); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /example/lib/tree_root.dart: -------------------------------------------------------------------------------- 1 | //import 'dart:math'; 2 | //import 'package:flutter/material.dart'; 3 | //import 'package:list_treeview/list_treeview.dart'; 4 | // 5 | ///// The data class that is bound to the child node 6 | ///// You must inherit from NodeData !!! 7 | ///// You can customize any of your properties 8 | //class TreeNodeData extends NodeData { 9 | // TreeNodeData({this.label,this.color}) : super(); 10 | // 11 | // /// Other properties that you want to define 12 | // final String label; 13 | // final Color color; 14 | // String property1; 15 | // String property2; 16 | // String property3; 17 | /////... 18 | //} 19 | // 20 | //class TreePage extends StatefulWidget { 21 | // @override 22 | // State createState() { 23 | // // TODO: implement createState 24 | // return _TreePageState(); 25 | // } 26 | //} 27 | // 28 | //class _TreePageState extends State 29 | // with SingleTickerProviderStateMixin { 30 | // 31 | // TreeViewController _controller; 32 | // @override 33 | // void initState() { 34 | // super.initState(); 35 | // 36 | // var colors1 = TreeNodeData(label: 'Colors1'); 37 | // var color11 = TreeNodeData(label: 'rgb(0,139,69)', color: Color.fromARGB(255, 0 ,139 , 69)); 38 | // var color12 = TreeNodeData(label: 'rgb(0,139,69)', color: Color.fromARGB(255,0,191 ,255)); 39 | // var color13 = TreeNodeData(label: 'rgb(0,139,69)', color: Color.fromARGB(255,255 ,106, 106)); 40 | // var color14 = TreeNodeData(label: 'rgb(0,139,69)', color: Color.fromARGB(255,160 ,32, 240)); 41 | // colors1.addChild(color11); 42 | // colors1.addChild(color12); 43 | // colors1.addChild(color13); 44 | // colors1.addChild(color14); 45 | // 46 | // var colors2 = TreeNodeData(label: 'Colors2'); 47 | // var color21 = TreeNodeData(label: 'rgb(0,139,69)', color: Color.fromARGB(255, 255 ,64, 64)); 48 | // var color22 = TreeNodeData(label: 'rgb(0,139,69)', color: Color.fromARGB(255,28, 134, 238)); 49 | // var color23 = TreeNodeData(label: 'rgb(0,139,69)', color: Color.fromARGB(255,255 ,106, 106)); 50 | // var color24 = TreeNodeData(label: 'rgb(0,139,69)', color: Color.fromARGB(255,205 ,198, 115)); 51 | // colors2.addChild(color21); 52 | // colors2.addChild(color22); 53 | // colors2.addChild(color23); 54 | // colors2.addChild(color24); 55 | // /// set data 56 | // _controller = TreeViewController(data: [colors1,colors2]); 57 | // } 58 | // 59 | // @override 60 | // void dispose() { 61 | // super.dispose(); 62 | // } 63 | // 64 | // _getRandomColor() { 65 | // return Color.fromARGB( 66 | // 255, 67 | // Random.secure().nextInt(255), 68 | // Random.secure().nextInt(255), 69 | // Random.secure().nextInt(255)); 70 | // } 71 | // /// Add 72 | // void add(TreeNodeData dataNode) { 73 | // /// create New node 74 | //// DateTime time = DateTime.now(); 75 | //// int milliseconds = time.millisecondsSinceEpoch ~/ 1000; 76 | // int r = Random.secure().nextInt(255); 77 | // int g = Random.secure().nextInt(255); 78 | // int b = Random.secure().nextInt(255); 79 | // 80 | // var newNode = TreeNodeData( 81 | // label: 'rgb($r,$g,$b)', color: Color.fromARGB(2555, r, g, b) 82 | // ); 83 | // 84 | // _controller.insertAtFront(dataNode,newNode); 85 | //// _controller.insertAtRear(dataNode, newNode); 86 | //// _controller.insertAtIndex(1, dataNode, newNode); 87 | // } 88 | // 89 | // void delete(dynamic item) { 90 | // _controller.removeItem(item); 91 | // } 92 | // 93 | // @override 94 | // Widget build(BuildContext context) { 95 | // return Scaffold( 96 | // appBar: AppBar( 97 | // title: Text('TreeView'), 98 | // ), 99 | // body: Column( 100 | // children: [ 101 | // 102 | // Expanded( 103 | // child: ListTreeView( 104 | // itemBuilder: (BuildContext context, int index, int level, 105 | // bool isExpand, dynamic data) { 106 | // TreeNodeData item = data; 107 | //// double width = MediaQuery.of(context).size.width; 108 | // double offsetX = level * 16.0; 109 | // return Container( 110 | // padding: EdgeInsets.all(16.0), 111 | // decoration: BoxDecoration( 112 | // border: Border( 113 | // bottom: BorderSide(width: 1, color: Colors.grey))), 114 | // child: Row( 115 | // mainAxisAlignment: MainAxisAlignment.spaceBetween, 116 | // children: [ 117 | // Expanded( 118 | // child: Padding( 119 | // padding: EdgeInsets.only(left: offsetX), 120 | // child: Row( 121 | // mainAxisAlignment: MainAxisAlignment.start, 122 | // crossAxisAlignment: CrossAxisAlignment.center, 123 | // children: [ 124 | // 125 | // Text('$index', 126 | // 127 | // ), 128 | // SizedBox( 129 | // width: 10, 130 | // ), 131 | // Text('${item.label}', 132 | // style: TextStyle( 133 | // color: item.color 134 | // ), 135 | // ), 136 | // ], 137 | // ), 138 | // ), 139 | // ), 140 | // Visibility( 141 | // visible: isExpand, 142 | // child: InkWell( 143 | // onTap: () { 144 | // add(item); 145 | // }, 146 | // child: Icon(Icons.add, 147 | // size: 30, 148 | // ), 149 | // ), 150 | // ) 151 | // 152 | // ], 153 | // ), 154 | // ); 155 | // }, 156 | // onTap: (index, level, isExpand, data) { 157 | // print('index = $index'); 158 | // }, 159 | // onLongPress: (index, level, isExpand, data) { 160 | // delete(data); 161 | // }, 162 | // controller: _controller, 163 | // )), 164 | // ], 165 | // ), 166 | // ); 167 | // } 168 | //} 169 | -------------------------------------------------------------------------------- /lib/tree/controller/node_controller.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 sooxie 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | import '../tree_define.dart'; 21 | import '../node/tree_node.dart'; 22 | 23 | /// 24 | class NodeController { 25 | static final min = -1; 26 | 27 | NodeController({this.parent, this.nodeItem, this.expandCallback}) 28 | : treeNode = TreeNode(lazyItem: nodeItem, expandCallback: expandCallback), 29 | _index = min, 30 | _level = min, 31 | _numberOfVisibleChildren = min, 32 | _mutableChildControllers = []; 33 | 34 | final NodeController? parent; 35 | final TreeNodeItem? nodeItem; 36 | final TreeNode treeNode; 37 | 38 | final ExpandCallback? expandCallback; 39 | 40 | int _index; 41 | List _mutableChildControllers; 42 | 43 | int _level; 44 | int _numberOfVisibleChildren; 45 | 46 | List get childControllers => _mutableChildControllers; 47 | 48 | void resetData() { 49 | _numberOfVisibleChildren = min; 50 | _index = min; 51 | // resetNumberOfVisibleChildren(); 52 | // resetIndex(); 53 | } 54 | 55 | /// Gets the index associated with the data 56 | int indexOfItem(dynamic item) { 57 | var controller = controllerOfItem(item); 58 | return (controller != null) ? controller.index : min; 59 | } 60 | 61 | /// Gets the controller associated with the data 62 | NodeController? controllerOfItem(dynamic item) { 63 | if (item == treeNode.item) { 64 | return this; 65 | } 66 | for (NodeController controller in childControllers) { 67 | var result = controller.controllerOfItem(item); 68 | if (result != null) { 69 | return result; 70 | } 71 | } 72 | return null; 73 | } 74 | 75 | /// 76 | void addChildController(List? controllers) { 77 | if (controllers == null || controllers.length == 0) { 78 | return; 79 | } 80 | _mutableChildControllers.addAll(controllers); 81 | resetNodesAfterChildAtIndex(min); 82 | } 83 | 84 | void insertChildControllers( 85 | List? controllers, List indexes) { 86 | if (controllers == null || controllers.length == 0) { 87 | return; 88 | } 89 | indexes.forEach((index) { 90 | _mutableChildControllers.insert(index, controllers[index]); 91 | }); 92 | 93 | resetNodesAfterChildAtIndex(min); 94 | } 95 | 96 | void insertNewChildControllers(NodeController? controller, int index) { 97 | if (controller == null) { 98 | return; 99 | } 100 | _mutableChildControllers.insert(index, controller); 101 | 102 | resetNodesAfterChildAtIndex(min); 103 | } 104 | 105 | ///Remove 106 | void removeChildControllers(List indexes) { 107 | if (indexes.length == 0) { 108 | return; 109 | } 110 | indexes.forEach((index) { 111 | _mutableChildControllers.removeAt(index); 112 | }); 113 | resetNodesAfterChildAtIndex(-1); 114 | } 115 | 116 | void removeChildControllersForParent(dynamic parent, int index) {} 117 | 118 | void resetNodesAfterChildAtIndex(int index) { 119 | int selfIndex; 120 | if (parent == null) { 121 | selfIndex = 0; 122 | } else { 123 | selfIndex = parent!.childControllers.indexOf(this); 124 | parent!.resetNodesAfterChildAtIndex(selfIndex); 125 | } 126 | 127 | resetData(); 128 | 129 | resetChildNodesAfterChildAtIndex(index); 130 | } 131 | 132 | void resetChildNodesAfterChildAtIndex(int index) { 133 | if (!treeNode.expanded) { 134 | return; 135 | } 136 | 137 | for (int i = index + 1; i < childControllers.length; i++) { 138 | NodeController controller = childControllers[i]; 139 | controller.resetData(); 140 | controller.resetChildNodesAfterChildAtIndex(-1); 141 | } 142 | } 143 | 144 | /// Collapsing and expanding 145 | 146 | void expandAndExpandChildren(bool expandChildren) { 147 | for (NodeController controller in childControllers) { 148 | controller.resetData(); 149 | } 150 | 151 | treeNode.setExpanded = true; 152 | resetData(); 153 | 154 | /// Recursively expand all child nodes 155 | for (NodeController controller in childControllers) { 156 | if (controller.treeNode.expanded || expandChildren) { 157 | controller.expandAndExpandChildren(expandChildren); 158 | } 159 | } 160 | 161 | parent!.resetNodesAfterChildAtIndex(parent!.childControllers.indexOf(this)); 162 | } 163 | 164 | ///collapse 165 | void collapseAndCollapseChildren(bool collapseChildren) { 166 | treeNode.setExpanded = false; 167 | resetData(); 168 | 169 | ///collapse children 170 | if (collapseChildren) { 171 | for (NodeController controller in childControllers) { 172 | controller.collapseAndCollapseChildren(collapseChildren); 173 | } 174 | } 175 | parent!.resetNodesAfterChildAtIndex(parent!.childControllers.indexOf(this)); 176 | } 177 | 178 | /// Collapsing and expanding - end 179 | int numberOfVisibleDescendants() { 180 | if (this.treeNode.expanded) { 181 | int sum = this.childControllers.length; 182 | this.childControllers.forEach((item) { 183 | sum += item.numberOfVisibleDescendants(); 184 | }); 185 | _numberOfVisibleChildren = sum; 186 | } else { 187 | return 0; 188 | } 189 | return _numberOfVisibleChildren; 190 | } 191 | 192 | NodeController? controllerForIndex(int index) { 193 | if (this.index == index) { 194 | return this; 195 | } 196 | 197 | if (!treeNode.expanded) { 198 | return null; 199 | } 200 | for (NodeController controller in childControllers) { 201 | var result = controller.controllerForIndex(index); 202 | if (result != null) { 203 | return result; 204 | } 205 | } 206 | return null; 207 | } 208 | 209 | int get index { 210 | if (_index != min) { 211 | return _index; 212 | } 213 | if (parent == null) { 214 | _index = -1; 215 | } else if (!parent!.treeNode.expanded) { 216 | _index = -1; 217 | } else { 218 | var indexOf = parent!.childControllers.indexOf(this); 219 | if (indexOf != 0) { 220 | var controller = parent!.childControllers[indexOf - 1]; 221 | _index = controller.lastVisibleDescendatIndex + 1; 222 | } else { 223 | _index = parent!.index + 1; 224 | } 225 | } 226 | return _index; 227 | } 228 | 229 | int get lastVisibleDescendatIndex { 230 | return _index + numberOfVisibleDescendants(); 231 | } 232 | 233 | int lastVisibleDescendantIndexForItem(dynamic item) { 234 | if (this.treeNode.item == item) { 235 | return this.lastVisibleDescendatIndex; 236 | } 237 | 238 | for (NodeController nodeController in childControllers) { 239 | int lastIndex = nodeController.lastVisibleDescendantIndexForItem(item); 240 | if (lastIndex != -1) { 241 | return lastIndex; 242 | } 243 | } 244 | return -1; 245 | } 246 | 247 | /// NodeController's level 248 | int get level { 249 | if (treeNode.item == null) { 250 | return -1; 251 | } 252 | if (_level == min) { 253 | _level = parent!.level + 1; 254 | } 255 | return _level; 256 | } 257 | 258 | List get descendantsIndexes { 259 | int numberOfVisible = numberOfVisibleDescendants(); 260 | int startIndex = _index + 1; 261 | List indexes = []; 262 | for (int i = startIndex; i < startIndex + numberOfVisible; i++) { 263 | indexes.add(i); 264 | } 265 | return indexes; 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:list_treeview/list_treeview.dart'; 5 | import 'package:list_treeview/tree/tree_view.dart'; 6 | 7 | void main() { 8 | runApp(MyApp()); 9 | } 10 | 11 | class MyApp extends StatelessWidget { 12 | // This widget is the root of your application. 13 | @override 14 | Widget build(BuildContext context) { 15 | return MaterialApp( 16 | title: 'Flutter Demo', 17 | theme: ThemeData( 18 | primarySwatch: Colors.blue, 19 | visualDensity: VisualDensity.adaptivePlatformDensity, 20 | ), 21 | home: HomePage(), 22 | ); 23 | } 24 | } 25 | 26 | class HomePage extends StatefulWidget { 27 | @override 28 | State createState() { 29 | return _HomePageState(); 30 | } 31 | } 32 | 33 | class _HomePageState extends State { 34 | @override 35 | Widget build(BuildContext context) { 36 | // TODO: implement build 37 | return Scaffold( 38 | appBar: AppBar( 39 | title: Text('Home'), 40 | ), 41 | body: Center( 42 | child: RaisedButton( 43 | child: Text('TreeView'), 44 | onPressed: () { 45 | Navigator.push( 46 | context, CupertinoPageRoute(builder: (_) => TreePage())); 47 | }, 48 | ), 49 | ), 50 | ); 51 | } 52 | } 53 | 54 | /// The data class that is bound to the child node 55 | /// You must inherit from NodeData !!! 56 | /// You can customize any of your properties 57 | class TreeNodeData extends NodeData { 58 | TreeNodeData({this.label, this.color}) : super(); 59 | 60 | /// Other properties that you want to define 61 | final String? label; 62 | final Color? color; 63 | 64 | String? property1; 65 | String? property2; 66 | String? property3; 67 | 68 | ///... 69 | } 70 | 71 | class TreePage extends StatefulWidget { 72 | @override 73 | State createState() { 74 | // TODO: implement createState 75 | return _TreePageState(); 76 | } 77 | } 78 | 79 | class _TreePageState extends State 80 | with SingleTickerProviderStateMixin { 81 | TreeViewController? _controller; 82 | bool _isSuccess = false; 83 | List _colors = []; 84 | @override 85 | void initState() { 86 | super.initState(); 87 | 88 | ///The controller must be initialized when the treeView create 89 | _controller = TreeViewController(); 90 | 91 | for (int i = 0; i < 100; i++) { 92 | if (randomColor() != null) { 93 | _colors.add(randomColor()); 94 | } 95 | } 96 | 97 | 98 | ///Data may be requested asynchronously 99 | getData(); 100 | 101 | 102 | } 103 | 104 | void getData() async { 105 | print('start get data'); 106 | _isSuccess = false; 107 | await Future.delayed(Duration(seconds: 2)); 108 | 109 | var colors1 = TreeNodeData(label: 'Colors1'); 110 | var color11 = TreeNodeData( 111 | label: 'rgb(0,139,69)', color: Color.fromARGB(255, 0, 139, 69)); 112 | var color12 = TreeNodeData( 113 | label: 'rgb(0,139,69)', color: Color.fromARGB(255, 0, 191, 255)); 114 | var color13 = TreeNodeData( 115 | label: 'rgb(0,139,69)', color: Color.fromARGB(255, 255, 106, 106)); 116 | var color14 = TreeNodeData( 117 | label: 'rgb(0,139,69)', color: Color.fromARGB(255, 160, 32, 240)); 118 | colors1.addChild(color11); 119 | colors1.addChild(color12); 120 | colors1.addChild(color13); 121 | colors1.addChild(color14); 122 | 123 | var colors2 = TreeNodeData(label: 'Colors2'); 124 | var color21 = TreeNodeData( 125 | label: 'rgb(0,139,69)', color: Color.fromARGB(255, 255, 64, 64)); 126 | var color22 = TreeNodeData( 127 | label: 'rgb(0,139,69)', color: Color.fromARGB(255, 28, 134, 238)); 128 | var color23 = TreeNodeData( 129 | label: 'rgb(0,139,69)', color: Color.fromARGB(255, 255, 106, 106)); 130 | var color24 = TreeNodeData( 131 | label: 'rgb(0,139,69)', color: Color.fromARGB(255, 205, 198, 115)); 132 | colors2.addChild(color21); 133 | colors2.addChild(color22); 134 | colors2.addChild(color23); 135 | colors2.addChild(color24); 136 | 137 | /// set data 138 | _controller!.treeData([colors1]); 139 | print('set treeData suceess'); 140 | 141 | setState(() { 142 | _isSuccess = true; 143 | }); 144 | } 145 | 146 | @override 147 | void dispose() { 148 | super.dispose(); 149 | } 150 | 151 | Color getColor(int level) { 152 | return _colors[level % _colors.length]; 153 | } 154 | 155 | Color randomColor() { 156 | int r = Random.secure().nextInt(200); 157 | int g = Random.secure().nextInt(200); 158 | int b = Random.secure().nextInt(200); 159 | return Color.fromARGB(255, r, g, b); 160 | } 161 | 162 | /// Add 163 | void add(TreeNodeData dataNode) { 164 | /// create New node 165 | // DateTime time = DateTime.now(); 166 | // int milliseconds = time.millisecondsSinceEpoch ~/ 1000; 167 | int r = Random.secure().nextInt(255); 168 | int g = Random.secure().nextInt(255); 169 | int b = Random.secure().nextInt(255); 170 | 171 | var newNode = TreeNodeData( 172 | label: 'rgb($r,$g,$b)', color: Color.fromARGB(255, r, g, b)); 173 | 174 | _controller!.insertAtFront(dataNode, newNode); 175 | // _controller.insertAtRear(dataNode, newNode); 176 | // _controller.insertAtIndex(1, dataNode, newNode); 177 | } 178 | 179 | void delete(dynamic item) { 180 | _controller!.removeItem(item); 181 | } 182 | 183 | void select(dynamic item) { 184 | _controller!.selectItem(item); 185 | } 186 | 187 | void selectAllChild(dynamic item) { 188 | _controller!.selectAllChild(item); 189 | } 190 | 191 | @override 192 | Widget build(BuildContext context) { 193 | return Scaffold( 194 | appBar: AppBar( 195 | title: Text('TreeView'), 196 | ), 197 | body: _isSuccess ? getBody() : getProgressView(), 198 | ); 199 | } 200 | 201 | Widget getProgressView() { 202 | return Center( 203 | child: CircularProgressIndicator(), 204 | ); 205 | } 206 | 207 | Widget getBody() { 208 | return ListTreeView( 209 | shrinkWrap: false, 210 | padding: EdgeInsets.all(0), 211 | itemBuilder: (BuildContext context, NodeData data) { 212 | TreeNodeData item = data as TreeNodeData; 213 | // double width = MediaQuery.of(context).size.width; 214 | double offsetX = item.level * 16.0; 215 | return Container( 216 | height: 54, 217 | padding: EdgeInsets.symmetric(horizontal: 16), 218 | decoration: BoxDecoration( 219 | border: Border(bottom: BorderSide(width: 1, color: Colors.grey))), 220 | child: Row( 221 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 222 | children: [ 223 | Expanded( 224 | child: Padding( 225 | padding: EdgeInsets.only(left: offsetX), 226 | child: Row( 227 | mainAxisAlignment: MainAxisAlignment.start, 228 | crossAxisAlignment: CrossAxisAlignment.center, 229 | children: [ 230 | Padding( 231 | padding: EdgeInsets.only(right: 5), 232 | child: InkWell( 233 | splashColor: Colors.amberAccent.withOpacity(1), 234 | highlightColor: Colors.red, 235 | onTap: () { 236 | selectAllChild(item); 237 | }, 238 | child: data.isSelected 239 | ? Icon( 240 | Icons.star, 241 | size: 30, 242 | color: Color(0xFFFF7F50), 243 | ) 244 | : Icon( 245 | Icons.star_border, 246 | size: 30, 247 | color: Color(0xFFFFDAB9), 248 | ), 249 | ), 250 | ), 251 | Text( 252 | 'level-${item.level}-${item.indexInParent}', 253 | style: TextStyle( 254 | fontSize: 15, color: getColor(item.level)), 255 | ), 256 | SizedBox( 257 | width: 10, 258 | ), 259 | // Text( 260 | // '${item.label}', 261 | // style: TextStyle(color: item.color), 262 | // ), 263 | ], 264 | ), 265 | ), 266 | ), 267 | Visibility( 268 | visible: item.isExpand, 269 | child: InkWell( 270 | onTap: () { 271 | add(item); 272 | }, 273 | child: Icon( 274 | Icons.add, 275 | size: 30, 276 | ), 277 | ), 278 | ) 279 | ], 280 | ), 281 | ); 282 | }, 283 | onTap: (NodeData data) { 284 | print('index = ${data.index}'); 285 | }, 286 | onLongPress: (data) { 287 | delete(data); 288 | }, 289 | controller: _controller, 290 | ); 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /lib/tree/controller/tree_controller.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 sooxie 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | import 'package:flutter/cupertino.dart'; 21 | import 'node_controller.dart'; 22 | import '../node/tree_node.dart'; 23 | 24 | /// Controls the ListTreeView. 25 | class TreeViewController extends ChangeNotifier { 26 | TreeViewController(); 27 | 28 | NodeController? _rootController; 29 | 30 | dynamic rootDataNode; 31 | List? data; 32 | 33 | void treeData(List? data) { 34 | assert(data != null, 'The data should not be empty'); 35 | this.data = data; 36 | // notifyListeners(); 37 | } 38 | 39 | /// Gets the data associated with each item 40 | dynamic dataForTreeNode(TreeNodeItem nodeItem) { 41 | NodeData? nodeData = nodeItem.parent; 42 | if (nodeData == null) { 43 | return data![nodeItem.index!]; 44 | } 45 | return nodeData.children[nodeItem.index!]; 46 | } 47 | 48 | void rebuild() { 49 | notifyListeners(); 50 | } 51 | 52 | /// TreeNode by index 53 | TreeNode treeNodeOfIndex(int index) { 54 | return _rootController!.controllerForIndex(index)!.treeNode; 55 | } 56 | 57 | /// The level of the specified item 58 | int levelOfNode(dynamic item) { 59 | var controller = _rootController!.controllerOfItem(item); 60 | return controller!.level; 61 | } 62 | 63 | /// The index of the specified item 64 | int indexOfItem(dynamic item) { 65 | return _rootController!.indexOfItem(item); 66 | } 67 | 68 | /// Insert a node in the head 69 | /// [parent] The parent node 70 | /// [newNode] The node will be insert 71 | /// [closeCanInsert] Can insert when parent closed 72 | void insertAtFront(NodeData? parent, NodeData newNode, 73 | {bool closeCanInsert = false}) { 74 | if (!closeCanInsert) { 75 | if (parent != null && !isExpanded(parent)) { 76 | return; 77 | } 78 | } 79 | parent!.children.insert(0, newNode); 80 | _insertItemAtIndex(0, parent); 81 | notifyListeners(); 82 | } 83 | 84 | /// Appends all nodes to the head of parent. 85 | /// [parent] The parent node 86 | /// [newNode] The node will be insert 87 | /// [closeCanInsert] Can insert when parent closed 88 | void insertAllAtFront(NodeData? parent, List newNodes, 89 | {bool closeCanInsert = false}) { 90 | if (!closeCanInsert) { 91 | if (parent != null && !isExpanded(parent)) { 92 | return; 93 | } 94 | } 95 | parent!.children.insertAll(0, newNodes); 96 | _insertAllItemAtIndex(0, parent, newNodes); 97 | notifyListeners(); 98 | } 99 | 100 | /// Insert a node in the end 101 | /// [parent] The parent node 102 | /// [newNode] The node will be insert 103 | /// [closeCanInsert] Can insert when parent closed 104 | void insertAtRear(NodeData? parent, NodeData newNode, 105 | {bool closeCanInsert = false}) { 106 | if (!closeCanInsert) { 107 | if (parent != null && !isExpanded(parent)) { 108 | return; 109 | } 110 | } 111 | parent!.children.add(newNode); 112 | _insertItemAtIndex(0, parent, isFront: false); 113 | notifyListeners(); 114 | } 115 | 116 | ///Inserts a node at position [index] in parent. 117 | /// The [index] value must be non-negative and no greater than [length]. 118 | void insertAtIndex(int index, dynamic parent, NodeData newNode, 119 | {bool closeCanInsert = false}) { 120 | assert(index <= parent.children.length); 121 | if (!closeCanInsert) { 122 | if (parent != null && !isExpanded(parent)) { 123 | return; 124 | } 125 | } 126 | parent.children.insert(index, newNode); 127 | _insertItemAtIndex(index, parent, isIndex: true); 128 | 129 | notifyListeners(); 130 | } 131 | 132 | /// Click item to expand or contract or collapse 133 | /// [index] The index of the clicked item 134 | TreeNode expandOrCollapse(int index) { 135 | var treeNode = treeNodeOfIndex(index); 136 | if (treeNode.expanded) { 137 | collapseItem(treeNode); 138 | } else { 139 | expandItem(treeNode); 140 | } 141 | 142 | ///notify refresh ListTreeView 143 | notifyListeners(); 144 | return treeNode; 145 | } 146 | 147 | /// Begin collapse 148 | void collapseItem(TreeNode treeNode) { 149 | /// - warning 150 | NodeController controller = 151 | _rootController!.controllerOfItem(treeNode.item)!; 152 | controller.collapseAndCollapseChildren(true); 153 | } 154 | 155 | ///remove 156 | void removeItem(dynamic item) { 157 | dynamic temp = parentOfItem(item); 158 | NodeData? parent = temp; 159 | int index = 0; 160 | if (parent == null) { 161 | index = data!.indexOf(item); 162 | data!.remove(item); 163 | } else { 164 | index = parent.children.indexOf(item); 165 | parent.children.remove(item); 166 | } 167 | 168 | removeItemAtIndexes(index, parent); 169 | 170 | notifyListeners(); 171 | } 172 | 173 | int itemChildrenLength(dynamic item) { 174 | if (item == null) { 175 | return data!.length; 176 | } 177 | NodeData nodeData = item; 178 | return nodeData.children.length; 179 | } 180 | 181 | ///select 182 | void selectItem(dynamic item) { 183 | assert(item != null, 'Item should not be null'); 184 | NodeData sItem = item; 185 | sItem.isSelected = !sItem.isSelected; 186 | notifyListeners(); 187 | } 188 | 189 | void selectAllChild(dynamic item) { 190 | assert(item != null, 'Item should not be null'); 191 | NodeData sItem = item; 192 | sItem.isSelected = !sItem.isSelected; 193 | if (sItem.children.length > 0) { 194 | _selectAllChild(sItem); 195 | } 196 | notifyListeners(); 197 | } 198 | 199 | ///Gets the number of visible children of the ListTreeView 200 | int numberOfVisibleChild() { 201 | final num = this.rootController.numberOfVisibleDescendants(); 202 | return this.rootController.numberOfVisibleDescendants(); 203 | } 204 | 205 | ///Get the controller for the root node. If null will be initialized according to the data 206 | NodeController get rootController { 207 | if (_rootController == null) { 208 | _rootController = NodeController( 209 | parent: _rootController, 210 | expandCallback: (dynamic item) { 211 | return true; 212 | }); 213 | int num = data!.length; 214 | 215 | List indexes = []; 216 | for (int i = 0; i < num; i++) { 217 | indexes.add(i); 218 | } 219 | var controllers = createNodeController(_rootController!, indexes); 220 | _rootController!.insertChildControllers(controllers, indexes); 221 | } 222 | return _rootController!; 223 | } 224 | 225 | bool isExpanded(dynamic item) { 226 | int index = indexOfItem(item); 227 | return treeNodeOfIndex(index).expanded; 228 | } 229 | 230 | /// Begin expand 231 | void expandItem(TreeNode treeNode) { 232 | List items = [treeNode.item]; 233 | while (items.length > 0) { 234 | var currentItem = items.first; 235 | items.remove(currentItem); 236 | NodeController controller = 237 | _rootController!.controllerOfItem(currentItem)!; 238 | List oldChildItems = []; 239 | for (NodeController controller in controller.childControllers) { 240 | oldChildItems.add(controller); 241 | } 242 | int numberOfChildren = itemChildrenLength(currentItem); 243 | List indexes = []; 244 | for (int i = 0; i < numberOfChildren; i++) { 245 | indexes.add(i); 246 | } 247 | var currentChildControllers = createNodeController(controller, indexes); 248 | List childControllersToInsert = []; 249 | List indexesForInsertions = []; 250 | List childControllersToRemove = []; 251 | List indexesForDeletions = []; 252 | for (NodeController loopNodeController in currentChildControllers) { 253 | if (!controller.childControllers.contains(loopNodeController) && 254 | !oldChildItems.contains(controller.treeNode.item)) { 255 | childControllersToInsert.add(loopNodeController); 256 | int index = currentChildControllers.indexOf(loopNodeController); 257 | assert(index != -1); 258 | indexesForInsertions.add(index); 259 | } 260 | } 261 | 262 | for (NodeController loopNodeController in controller.childControllers) { 263 | if (!currentChildControllers.contains(loopNodeController) && 264 | !childControllersToInsert.contains(loopNodeController)) { 265 | childControllersToRemove.add(loopNodeController); 266 | int index = controller.childControllers.indexOf(loopNodeController); 267 | assert(index != -1); 268 | indexesForDeletions.add(index); 269 | } 270 | } 271 | 272 | controller.removeChildControllers(indexesForDeletions); 273 | controller.insertChildControllers( 274 | childControllersToInsert, indexesForInsertions); 275 | bool expandChildren = false; 276 | if (expandChildren) { 277 | for (NodeController nodeController in controller.childControllers) { 278 | items.add(nodeController.treeNode.item); 279 | } 280 | } 281 | controller.expandAndExpandChildren(false); 282 | notifyListeners(); 283 | } 284 | } 285 | 286 | void _insertItemAtIndex(int index, dynamic parent, 287 | {bool isIndex = false, bool isFront = true}) { 288 | int idx = indexOfItem(parent); 289 | if (idx == -1) { 290 | return; 291 | } 292 | NodeController parentController = 293 | _rootController!.controllerOfItem(parent)!; 294 | if (isIndex) { 295 | var newControllers = createNodeController(parentController, [index]); 296 | parentController.insertNewChildControllers(newControllers[0], index); 297 | } else { 298 | if (isFront) { 299 | var newControllers = createNodeController(parentController, [0]); 300 | parentController.insertChildControllers(newControllers, [0]); 301 | } else { 302 | var newControllers = createNodeController( 303 | parentController, [parentController.childControllers.length]); 304 | parentController.addChildController(newControllers); 305 | } 306 | } 307 | } 308 | 309 | void _insertAllItemAtIndex(int index, dynamic parent, List newNodes, 310 | {bool isIndex = false, bool isFront = true}) { 311 | int idx = indexOfItem(parent); 312 | if (idx == -1) { 313 | return; 314 | } 315 | NodeController parentController = 316 | _rootController!.controllerOfItem(parent)!; 317 | if (isIndex) { 318 | var newControllers = createNodeController(parentController, [index]); 319 | parentController.insertNewChildControllers(newControllers[0], index); 320 | } else { 321 | if (isFront) { 322 | List nodes = []; 323 | for (int i = 0; i < newNodes.length; i++) { 324 | nodes.add(i); 325 | } 326 | var newControllers = createNodeController(parentController, nodes); 327 | parentController.insertChildControllers(newControllers, nodes); 328 | } else { 329 | var newControllers = createNodeController( 330 | parentController, [parentController.childControllers.length]); 331 | parentController.addChildController(newControllers); 332 | } 333 | } 334 | } 335 | 336 | /// 337 | void removeItemAtIndexes(int index, dynamic parent) { 338 | if (parent != null && !isExpanded(parent)) { 339 | return; 340 | } 341 | NodeController nodeController = 342 | _rootController!.controllerOfItem(parent)!.childControllers[index]; 343 | dynamic child = nodeController.treeNode.item; 344 | int idx = _rootController!.lastVisibleDescendantIndexForItem(child); 345 | if (idx == -1) { 346 | return; 347 | } 348 | NodeController parentController = 349 | _rootController!.controllerOfItem(parent)!; 350 | parentController.removeChildControllers([index]); 351 | } 352 | 353 | void _selectAllChild(NodeData sItem) { 354 | if (sItem.children.length == 0) return; 355 | for (NodeData child in sItem.children) { 356 | child.isSelected = sItem.isSelected; 357 | _selectAllChild(child); 358 | } 359 | } 360 | 361 | /// Create controllers for each child node 362 | List createNodeController( 363 | NodeController parentController, List indexes) { 364 | List children = 365 | parentController.childControllers.map((e) => e).toList(); 366 | List newChildren = []; 367 | 368 | indexes.forEach((element) {}); 369 | 370 | for (int i in indexes) { 371 | NodeController? controller; 372 | NodeController? oldController; 373 | var lazyItem = TreeNodeItem( 374 | parent: parentController.treeNode.item, controller: this, index: i); 375 | parentController.childControllers.forEach((controller) { 376 | if (controller.treeNode.item == lazyItem.item) { 377 | oldController = controller; 378 | } 379 | }); 380 | if (oldController != null) { 381 | controller = oldController; 382 | } else { 383 | controller = NodeController( 384 | parent: parentController, 385 | nodeItem: lazyItem, 386 | expandCallback: (NodeData? item) { 387 | bool result = false; 388 | children.forEach((controller) { 389 | if (controller.treeNode.item == item) { 390 | result = true; 391 | } 392 | }); 393 | return result; 394 | }); 395 | } 396 | newChildren.add(controller!); 397 | } 398 | return newChildren; 399 | } 400 | 401 | NodeController createNewNodeController( 402 | NodeController parentController, int index) { 403 | var lazyItem = TreeNodeItem( 404 | parent: parentController.treeNode.item, controller: this, index: index); 405 | NodeController controller = NodeController( 406 | parent: parentController, 407 | nodeItem: lazyItem, 408 | expandCallback: (dynamic item) { 409 | bool result = false; 410 | return result; 411 | }); 412 | return controller; 413 | } 414 | 415 | ///Gets the data information for the parent node 416 | NodeData? parentOfItem(dynamic item) { 417 | NodeController controller = _rootController!.controllerOfItem(item)!; 418 | return controller.parent?.treeNode.item; 419 | } 420 | } 421 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 11 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 12 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 13 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 14 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 15 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXCopyFilesBuildPhase section */ 19 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 20 | isa = PBXCopyFilesBuildPhase; 21 | buildActionMask = 2147483647; 22 | dstPath = ""; 23 | dstSubfolderSpec = 10; 24 | files = ( 25 | ); 26 | name = "Embed Frameworks"; 27 | runOnlyForDeploymentPostprocessing = 0; 28 | }; 29 | /* End PBXCopyFilesBuildPhase section */ 30 | 31 | /* Begin PBXFileReference section */ 32 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 33 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 34 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 35 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 36 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 37 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 38 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 39 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 40 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 41 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 42 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 43 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 44 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 45 | /* End PBXFileReference section */ 46 | 47 | /* Begin PBXFrameworksBuildPhase section */ 48 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 49 | isa = PBXFrameworksBuildPhase; 50 | buildActionMask = 2147483647; 51 | files = ( 52 | ); 53 | runOnlyForDeploymentPostprocessing = 0; 54 | }; 55 | /* End PBXFrameworksBuildPhase section */ 56 | 57 | /* Begin PBXGroup section */ 58 | 9740EEB11CF90186004384FC /* Flutter */ = { 59 | isa = PBXGroup; 60 | children = ( 61 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 62 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 63 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 64 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 65 | ); 66 | name = Flutter; 67 | sourceTree = ""; 68 | }; 69 | 97C146E51CF9000F007C117D = { 70 | isa = PBXGroup; 71 | children = ( 72 | 9740EEB11CF90186004384FC /* Flutter */, 73 | 97C146F01CF9000F007C117D /* Runner */, 74 | 97C146EF1CF9000F007C117D /* Products */, 75 | ); 76 | sourceTree = ""; 77 | }; 78 | 97C146EF1CF9000F007C117D /* Products */ = { 79 | isa = PBXGroup; 80 | children = ( 81 | 97C146EE1CF9000F007C117D /* Runner.app */, 82 | ); 83 | name = Products; 84 | sourceTree = ""; 85 | }; 86 | 97C146F01CF9000F007C117D /* Runner */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 90 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 91 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 92 | 97C147021CF9000F007C117D /* Info.plist */, 93 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 94 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 95 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 96 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 97 | ); 98 | path = Runner; 99 | sourceTree = ""; 100 | }; 101 | /* End PBXGroup section */ 102 | 103 | /* Begin PBXNativeTarget section */ 104 | 97C146ED1CF9000F007C117D /* Runner */ = { 105 | isa = PBXNativeTarget; 106 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 107 | buildPhases = ( 108 | 9740EEB61CF901F6004384FC /* Run Script */, 109 | 97C146EA1CF9000F007C117D /* Sources */, 110 | 97C146EB1CF9000F007C117D /* Frameworks */, 111 | 97C146EC1CF9000F007C117D /* Resources */, 112 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 113 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 114 | ); 115 | buildRules = ( 116 | ); 117 | dependencies = ( 118 | ); 119 | name = Runner; 120 | productName = Runner; 121 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 122 | productType = "com.apple.product-type.application"; 123 | }; 124 | /* End PBXNativeTarget section */ 125 | 126 | /* Begin PBXProject section */ 127 | 97C146E61CF9000F007C117D /* Project object */ = { 128 | isa = PBXProject; 129 | attributes = { 130 | LastUpgradeCheck = 1300; 131 | ORGANIZATIONNAME = ""; 132 | TargetAttributes = { 133 | 97C146ED1CF9000F007C117D = { 134 | CreatedOnToolsVersion = 7.3.1; 135 | LastSwiftMigration = 1100; 136 | }; 137 | }; 138 | }; 139 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 140 | compatibilityVersion = "Xcode 9.3"; 141 | developmentRegion = en; 142 | hasScannedForEncodings = 0; 143 | knownRegions = ( 144 | en, 145 | Base, 146 | ); 147 | mainGroup = 97C146E51CF9000F007C117D; 148 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 149 | projectDirPath = ""; 150 | projectRoot = ""; 151 | targets = ( 152 | 97C146ED1CF9000F007C117D /* Runner */, 153 | ); 154 | }; 155 | /* End PBXProject section */ 156 | 157 | /* Begin PBXResourcesBuildPhase section */ 158 | 97C146EC1CF9000F007C117D /* Resources */ = { 159 | isa = PBXResourcesBuildPhase; 160 | buildActionMask = 2147483647; 161 | files = ( 162 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 163 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 164 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 165 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 166 | ); 167 | runOnlyForDeploymentPostprocessing = 0; 168 | }; 169 | /* End PBXResourcesBuildPhase section */ 170 | 171 | /* Begin PBXShellScriptBuildPhase section */ 172 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 173 | isa = PBXShellScriptBuildPhase; 174 | buildActionMask = 2147483647; 175 | files = ( 176 | ); 177 | inputPaths = ( 178 | ); 179 | name = "Thin Binary"; 180 | outputPaths = ( 181 | ); 182 | runOnlyForDeploymentPostprocessing = 0; 183 | shellPath = /bin/sh; 184 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; 185 | }; 186 | 9740EEB61CF901F6004384FC /* Run Script */ = { 187 | isa = PBXShellScriptBuildPhase; 188 | buildActionMask = 2147483647; 189 | files = ( 190 | ); 191 | inputPaths = ( 192 | ); 193 | name = "Run Script"; 194 | outputPaths = ( 195 | ); 196 | runOnlyForDeploymentPostprocessing = 0; 197 | shellPath = /bin/sh; 198 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 199 | }; 200 | /* End PBXShellScriptBuildPhase section */ 201 | 202 | /* Begin PBXSourcesBuildPhase section */ 203 | 97C146EA1CF9000F007C117D /* Sources */ = { 204 | isa = PBXSourcesBuildPhase; 205 | buildActionMask = 2147483647; 206 | files = ( 207 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 208 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 209 | ); 210 | runOnlyForDeploymentPostprocessing = 0; 211 | }; 212 | /* End PBXSourcesBuildPhase section */ 213 | 214 | /* Begin PBXVariantGroup section */ 215 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 216 | isa = PBXVariantGroup; 217 | children = ( 218 | 97C146FB1CF9000F007C117D /* Base */, 219 | ); 220 | name = Main.storyboard; 221 | sourceTree = ""; 222 | }; 223 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 224 | isa = PBXVariantGroup; 225 | children = ( 226 | 97C147001CF9000F007C117D /* Base */, 227 | ); 228 | name = LaunchScreen.storyboard; 229 | sourceTree = ""; 230 | }; 231 | /* End PBXVariantGroup section */ 232 | 233 | /* Begin XCBuildConfiguration section */ 234 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 235 | isa = XCBuildConfiguration; 236 | buildSettings = { 237 | ALWAYS_SEARCH_USER_PATHS = NO; 238 | CLANG_ANALYZER_NONNULL = YES; 239 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 240 | CLANG_CXX_LIBRARY = "libc++"; 241 | CLANG_ENABLE_MODULES = YES; 242 | CLANG_ENABLE_OBJC_ARC = YES; 243 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 244 | CLANG_WARN_BOOL_CONVERSION = YES; 245 | CLANG_WARN_COMMA = YES; 246 | CLANG_WARN_CONSTANT_CONVERSION = YES; 247 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 248 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 249 | CLANG_WARN_EMPTY_BODY = YES; 250 | CLANG_WARN_ENUM_CONVERSION = YES; 251 | CLANG_WARN_INFINITE_RECURSION = YES; 252 | CLANG_WARN_INT_CONVERSION = YES; 253 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 254 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 255 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 256 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 257 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 258 | CLANG_WARN_STRICT_PROTOTYPES = YES; 259 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 260 | CLANG_WARN_UNREACHABLE_CODE = YES; 261 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 262 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 263 | COPY_PHASE_STRIP = NO; 264 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 265 | ENABLE_NS_ASSERTIONS = NO; 266 | ENABLE_STRICT_OBJC_MSGSEND = YES; 267 | GCC_C_LANGUAGE_STANDARD = gnu99; 268 | GCC_NO_COMMON_BLOCKS = YES; 269 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 270 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 271 | GCC_WARN_UNDECLARED_SELECTOR = YES; 272 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 273 | GCC_WARN_UNUSED_FUNCTION = YES; 274 | GCC_WARN_UNUSED_VARIABLE = YES; 275 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 276 | MTL_ENABLE_DEBUG_INFO = NO; 277 | SDKROOT = iphoneos; 278 | SUPPORTED_PLATFORMS = iphoneos; 279 | TARGETED_DEVICE_FAMILY = "1,2"; 280 | VALIDATE_PRODUCT = YES; 281 | }; 282 | name = Profile; 283 | }; 284 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 285 | isa = XCBuildConfiguration; 286 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 287 | buildSettings = { 288 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 289 | CLANG_ENABLE_MODULES = YES; 290 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 291 | DEVELOPMENT_TEAM = 5KJVG49286; 292 | ENABLE_BITCODE = NO; 293 | INFOPLIST_FILE = Runner/Info.plist; 294 | LD_RUNPATH_SEARCH_PATHS = ( 295 | "$(inherited)", 296 | "@executable_path/Frameworks", 297 | ); 298 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example; 299 | PRODUCT_NAME = "$(TARGET_NAME)"; 300 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 301 | SWIFT_VERSION = 5.0; 302 | VERSIONING_SYSTEM = "apple-generic"; 303 | }; 304 | name = Profile; 305 | }; 306 | 97C147031CF9000F007C117D /* Debug */ = { 307 | isa = XCBuildConfiguration; 308 | buildSettings = { 309 | ALWAYS_SEARCH_USER_PATHS = NO; 310 | CLANG_ANALYZER_NONNULL = YES; 311 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 312 | CLANG_CXX_LIBRARY = "libc++"; 313 | CLANG_ENABLE_MODULES = YES; 314 | CLANG_ENABLE_OBJC_ARC = YES; 315 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 316 | CLANG_WARN_BOOL_CONVERSION = YES; 317 | CLANG_WARN_COMMA = YES; 318 | CLANG_WARN_CONSTANT_CONVERSION = YES; 319 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 320 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 321 | CLANG_WARN_EMPTY_BODY = YES; 322 | CLANG_WARN_ENUM_CONVERSION = YES; 323 | CLANG_WARN_INFINITE_RECURSION = YES; 324 | CLANG_WARN_INT_CONVERSION = YES; 325 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 326 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 327 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 328 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 329 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 330 | CLANG_WARN_STRICT_PROTOTYPES = YES; 331 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 332 | CLANG_WARN_UNREACHABLE_CODE = YES; 333 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 334 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 335 | COPY_PHASE_STRIP = NO; 336 | DEBUG_INFORMATION_FORMAT = dwarf; 337 | ENABLE_STRICT_OBJC_MSGSEND = YES; 338 | ENABLE_TESTABILITY = YES; 339 | GCC_C_LANGUAGE_STANDARD = gnu99; 340 | GCC_DYNAMIC_NO_PIC = NO; 341 | GCC_NO_COMMON_BLOCKS = YES; 342 | GCC_OPTIMIZATION_LEVEL = 0; 343 | GCC_PREPROCESSOR_DEFINITIONS = ( 344 | "DEBUG=1", 345 | "$(inherited)", 346 | ); 347 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 348 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 349 | GCC_WARN_UNDECLARED_SELECTOR = YES; 350 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 351 | GCC_WARN_UNUSED_FUNCTION = YES; 352 | GCC_WARN_UNUSED_VARIABLE = YES; 353 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 354 | MTL_ENABLE_DEBUG_INFO = YES; 355 | ONLY_ACTIVE_ARCH = YES; 356 | SDKROOT = iphoneos; 357 | TARGETED_DEVICE_FAMILY = "1,2"; 358 | }; 359 | name = Debug; 360 | }; 361 | 97C147041CF9000F007C117D /* Release */ = { 362 | isa = XCBuildConfiguration; 363 | buildSettings = { 364 | ALWAYS_SEARCH_USER_PATHS = NO; 365 | CLANG_ANALYZER_NONNULL = YES; 366 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 367 | CLANG_CXX_LIBRARY = "libc++"; 368 | CLANG_ENABLE_MODULES = YES; 369 | CLANG_ENABLE_OBJC_ARC = YES; 370 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 371 | CLANG_WARN_BOOL_CONVERSION = YES; 372 | CLANG_WARN_COMMA = YES; 373 | CLANG_WARN_CONSTANT_CONVERSION = YES; 374 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 375 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 376 | CLANG_WARN_EMPTY_BODY = YES; 377 | CLANG_WARN_ENUM_CONVERSION = YES; 378 | CLANG_WARN_INFINITE_RECURSION = YES; 379 | CLANG_WARN_INT_CONVERSION = YES; 380 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 381 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 382 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 383 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 384 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 385 | CLANG_WARN_STRICT_PROTOTYPES = YES; 386 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 387 | CLANG_WARN_UNREACHABLE_CODE = YES; 388 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 389 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 390 | COPY_PHASE_STRIP = NO; 391 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 392 | ENABLE_NS_ASSERTIONS = NO; 393 | ENABLE_STRICT_OBJC_MSGSEND = YES; 394 | GCC_C_LANGUAGE_STANDARD = gnu99; 395 | GCC_NO_COMMON_BLOCKS = YES; 396 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 397 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 398 | GCC_WARN_UNDECLARED_SELECTOR = YES; 399 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 400 | GCC_WARN_UNUSED_FUNCTION = YES; 401 | GCC_WARN_UNUSED_VARIABLE = YES; 402 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 403 | MTL_ENABLE_DEBUG_INFO = NO; 404 | SDKROOT = iphoneos; 405 | SUPPORTED_PLATFORMS = iphoneos; 406 | SWIFT_COMPILATION_MODE = wholemodule; 407 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 408 | TARGETED_DEVICE_FAMILY = "1,2"; 409 | VALIDATE_PRODUCT = YES; 410 | }; 411 | name = Release; 412 | }; 413 | 97C147061CF9000F007C117D /* Debug */ = { 414 | isa = XCBuildConfiguration; 415 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 416 | buildSettings = { 417 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 418 | CLANG_ENABLE_MODULES = YES; 419 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 420 | DEVELOPMENT_TEAM = 5KJVG49286; 421 | ENABLE_BITCODE = NO; 422 | INFOPLIST_FILE = Runner/Info.plist; 423 | LD_RUNPATH_SEARCH_PATHS = ( 424 | "$(inherited)", 425 | "@executable_path/Frameworks", 426 | ); 427 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example; 428 | PRODUCT_NAME = "$(TARGET_NAME)"; 429 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 430 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 431 | SWIFT_VERSION = 5.0; 432 | VERSIONING_SYSTEM = "apple-generic"; 433 | }; 434 | name = Debug; 435 | }; 436 | 97C147071CF9000F007C117D /* Release */ = { 437 | isa = XCBuildConfiguration; 438 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 439 | buildSettings = { 440 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 441 | CLANG_ENABLE_MODULES = YES; 442 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 443 | DEVELOPMENT_TEAM = 5KJVG49286; 444 | ENABLE_BITCODE = NO; 445 | INFOPLIST_FILE = Runner/Info.plist; 446 | LD_RUNPATH_SEARCH_PATHS = ( 447 | "$(inherited)", 448 | "@executable_path/Frameworks", 449 | ); 450 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example; 451 | PRODUCT_NAME = "$(TARGET_NAME)"; 452 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 453 | SWIFT_VERSION = 5.0; 454 | VERSIONING_SYSTEM = "apple-generic"; 455 | }; 456 | name = Release; 457 | }; 458 | /* End XCBuildConfiguration section */ 459 | 460 | /* Begin XCConfigurationList section */ 461 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 462 | isa = XCConfigurationList; 463 | buildConfigurations = ( 464 | 97C147031CF9000F007C117D /* Debug */, 465 | 97C147041CF9000F007C117D /* Release */, 466 | 249021D3217E4FDB00AE95B9 /* Profile */, 467 | ); 468 | defaultConfigurationIsVisible = 0; 469 | defaultConfigurationName = Release; 470 | }; 471 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 472 | isa = XCConfigurationList; 473 | buildConfigurations = ( 474 | 97C147061CF9000F007C117D /* Debug */, 475 | 97C147071CF9000F007C117D /* Release */, 476 | 249021D4217E4FDB00AE95B9 /* Profile */, 477 | ); 478 | defaultConfigurationIsVisible = 0; 479 | defaultConfigurationName = Release; 480 | }; 481 | /* End XCConfigurationList section */ 482 | }; 483 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 484 | } 485 | --------------------------------------------------------------------------------