├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── doc
└── images
│ ├── align.png
│ ├── circle.png
│ ├── ellipse.png
│ ├── home.png
│ ├── spiral.png
│ ├── word_cloud.png
│ └── word_cloud_not_filled.png
├── example
├── .gitignore
├── .metadata
├── README.md
├── android
│ ├── app
│ │ ├── build.gradle
│ │ └── src
│ │ │ └── main
│ │ │ ├── AndroidManifest.xml
│ │ │ ├── java
│ │ │ └── com
│ │ │ │ └── letsar
│ │ │ │ └── scatter
│ │ │ │ └── example
│ │ │ │ └── MainActivity.java
│ │ │ └── res
│ │ │ ├── drawable
│ │ │ └── launch_background.xml
│ │ │ ├── mipmap-hdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-mdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xhdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ └── ic_launcher.png
│ │ │ └── values
│ │ │ └── styles.xml
│ ├── build.gradle
│ ├── gradle.properties
│ ├── gradle
│ │ └── wrapper
│ │ │ └── gradle-wrapper.properties
│ └── settings.gradle
├── ios
│ ├── Flutter
│ │ ├── AppFrameworkInfo.plist
│ │ ├── Debug.xcconfig
│ │ └── Release.xcconfig
│ ├── Runner.xcodeproj
│ │ ├── project.pbxproj
│ │ ├── project.xcworkspace
│ │ │ └── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ └── xcschemes
│ │ │ └── Runner.xcscheme
│ ├── Runner.xcworkspace
│ │ └── contents.xcworkspacedata
│ └── Runner
│ │ ├── AppDelegate.h
│ │ ├── AppDelegate.m
│ │ ├── Assets.xcassets
│ │ ├── AppIcon.appiconset
│ │ │ ├── Contents.json
│ │ │ ├── Icon-App-1024x1024@1x.png
│ │ │ ├── Icon-App-20x20@1x.png
│ │ │ ├── Icon-App-20x20@2x.png
│ │ │ ├── Icon-App-20x20@3x.png
│ │ │ ├── Icon-App-29x29@1x.png
│ │ │ ├── Icon-App-29x29@2x.png
│ │ │ ├── Icon-App-29x29@3x.png
│ │ │ ├── Icon-App-40x40@1x.png
│ │ │ ├── Icon-App-40x40@2x.png
│ │ │ ├── Icon-App-40x40@3x.png
│ │ │ ├── Icon-App-60x60@2x.png
│ │ │ ├── Icon-App-60x60@3x.png
│ │ │ ├── Icon-App-76x76@1x.png
│ │ │ ├── Icon-App-76x76@2x.png
│ │ │ └── Icon-App-83.5x83.5@2x.png
│ │ └── LaunchImage.imageset
│ │ │ ├── Contents.json
│ │ │ ├── LaunchImage.png
│ │ │ ├── LaunchImage@2x.png
│ │ │ ├── LaunchImage@3x.png
│ │ │ └── README.md
│ │ ├── Base.lproj
│ │ ├── LaunchScreen.storyboard
│ │ └── Main.storyboard
│ │ ├── Info.plist
│ │ └── main.m
├── lib
│ ├── flutter_hashtags.dart
│ ├── main.dart
│ └── screens
│ │ ├── align.dart
│ │ ├── archi_spiral.dart
│ │ ├── circle.dart
│ │ └── word_cloud.dart
└── pubspec.yaml
├── lib
├── flutter_scatter.dart
└── src
│ ├── rendering
│ └── scatter.dart
│ └── widgets
│ └── scatter.dart
├── pubspec.yaml
└── test
└── flutter_scatter_test.dart
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://www.dartlang.org/tools/private-files.html
2 |
3 | # Files and directories created by pub
4 | .dart_tool/
5 | .packages
6 | .pub/
7 | build/
8 | # If you're building an application, you may want to check-in your pubspec.lock
9 | pubspec.lock
10 |
11 | # Directory created by dartdoc
12 | # If you don't generate documentation locally you can remove this line.
13 | doc/api/
14 |
15 | *.iml
16 | *.ipr
17 | *.iws
18 | .idea/
19 | .DS_Store
20 | .vscode/
21 |
22 | example/android/\.project
23 |
24 | example/android/\.settings/org\.eclipse\.buildship\.core\.prefs
25 |
26 | example/android/app/\.classpath
27 |
28 | example/android/app/\.project
29 |
30 | example/android/app/\.settings/org\.eclipse\.buildship\.core\.prefs
31 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 0.1.0
2 | * Initial Open Source release.
3 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Romain Rastel
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # flutter_scatter
2 |
3 | A widget that displays a collection of dispersed and non-overlapping children.
4 |
5 | [](https://pub.dartlang.org/packages/flutter_scatter)
6 | [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=QTT34M25RDNL6)
7 |
8 | Can be used to create word clouds:
9 | 
10 |
11 | ## Features
12 |
13 | * Built-in delegates (Spirals, Align, Ellipse).
14 | * Allow you to specify how to align chlidren.
15 |
16 | ## Getting started
17 |
18 | In the `pubspec.yaml` of your flutter project, add the following dependency:
19 | The latest version is [](https://pub.dartlang.org/packages/flutter_scatter)
20 |
21 | ```yaml
22 | dependencies:
23 | ...
24 | flutter_scatter: ^latest_version
25 | ```
26 |
27 | In your library add the following import:
28 |
29 | ```dart
30 | import 'package:flutter_scatter/flutter_scatter.dart';
31 | ```
32 |
33 | For help getting started with Flutter, view the online [documentation](https://flutter.io/).
34 |
35 | ## Widgets
36 |
37 | You can simply create a `Scatter` by providing a delegate and a list of widgets.
38 |
39 | The delegate is responsible for computing positions.
40 | For example if you want to position your widgets on a circle, you will use the `EllipseScatterDelegate`:
41 |
42 | ```dart
43 | return Center(
44 | child: Scatter(
45 | delegate: EllipseScatterDelegate(
46 | a: 185.0,
47 | b: 185.0,
48 | step: 1.0 / count,
49 | ),
50 | children: widgets,
51 | ),
52 | );
53 | ```
54 |
55 | 
56 |
57 | It may be useful to choose how children are aligned with the computed positions. By default, the center of the widgets will be placed on the positions generated by the delegate.
58 | If you want to be left aligned, you will change the `alignment` argument of the `Scatter` to be `Alignment.topLeft`.
59 |
60 | By default, the Scatter will not try to fill gaps (for performance reasons). You can override this behavior by setting the `fillGaps` argument to `true`.
61 |
62 | For example this is what the above word cloud would look if the `fillGaps` argument would be set to `false`:
63 | 
64 |
65 | ## Delegates
66 |
67 | `Scatter` has built-in delegates which can be highly parameterized:
68 |
69 | ### Spirals
70 |
71 | * ArchimedeanSpiralScatterDelegate
72 | * FermatSpiralScatterDelegate
73 | * LogarithmicSpiralScatterDelegate
74 |
75 | 
76 |
77 | ### Alignments
78 |
79 | * AlignScatterDelegate
80 |
81 | 
82 |
83 | ### Ellipses
84 |
85 | * EllipseScatterDelegate
86 |
87 | 
88 |
89 | ## Examples
90 |
91 | You can find more examples in this [app](https://github.com/letsar/flutter_scatter/tree/master/example).
92 |
93 | ## Changelog
94 |
95 | Please see the [Changelog](https://github.com/letsar/flutter_scatter/blob/master/CHANGELOG.md) page to know what's recently changed.
96 |
97 | ## Contributions
98 |
99 | Feel free to contribute to this project.
100 |
101 | If you find a bug or want a feature, but don't know how to fix/implement it, please fill an [issue](https://github.com/letsar/flutter_scatter/issues).
102 | If you fixed a bug or implemented a new feature, please send a [pull request](https://github.com/letsar/flutter_scatter/pulls).
103 |
--------------------------------------------------------------------------------
/doc/images/align.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/letsar/flutter_scatter/79dac180b23931452dc77c4681bce9278405311d/doc/images/align.png
--------------------------------------------------------------------------------
/doc/images/circle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/letsar/flutter_scatter/79dac180b23931452dc77c4681bce9278405311d/doc/images/circle.png
--------------------------------------------------------------------------------
/doc/images/ellipse.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/letsar/flutter_scatter/79dac180b23931452dc77c4681bce9278405311d/doc/images/ellipse.png
--------------------------------------------------------------------------------
/doc/images/home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/letsar/flutter_scatter/79dac180b23931452dc77c4681bce9278405311d/doc/images/home.png
--------------------------------------------------------------------------------
/doc/images/spiral.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/letsar/flutter_scatter/79dac180b23931452dc77c4681bce9278405311d/doc/images/spiral.png
--------------------------------------------------------------------------------
/doc/images/word_cloud.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/letsar/flutter_scatter/79dac180b23931452dc77c4681bce9278405311d/doc/images/word_cloud.png
--------------------------------------------------------------------------------
/doc/images/word_cloud_not_filled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/letsar/flutter_scatter/79dac180b23931452dc77c4681bce9278405311d/doc/images/word_cloud_not_filled.png
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.lock
4 | *.log
5 | *.pyc
6 | *.swp
7 | .DS_Store
8 | .atom/
9 | .buildlog/
10 | .history
11 | .svn/
12 |
13 | # IntelliJ related
14 | *.iml
15 | *.ipr
16 | *.iws
17 | .idea/
18 |
19 | # Visual Studio Code related
20 | .vscode/
21 |
22 | # Flutter/Dart/Pub related
23 | **/doc/api/
24 | .dart_tool/
25 | .flutter-plugins
26 | .packages
27 | .pub-cache/
28 | .pub/
29 | build/
30 |
31 | # Android related
32 | **/android/**/gradle-wrapper.jar
33 | **/android/.gradle
34 | **/android/captures/
35 | **/android/gradlew
36 | **/android/gradlew.bat
37 | **/android/local.properties
38 | **/android/**/GeneratedPluginRegistrant.java
39 |
40 | # iOS/XCode related
41 | **/ios/**/*.mode1v3
42 | **/ios/**/*.mode2v3
43 | **/ios/**/*.moved-aside
44 | **/ios/**/*.pbxuser
45 | **/ios/**/*.perspectivev3
46 | **/ios/**/*sync/
47 | **/ios/**/.sconsign.dblite
48 | **/ios/**/.tags*
49 | **/ios/**/.vagrant/
50 | **/ios/**/DerivedData/
51 | **/ios/**/Icon?
52 | **/ios/**/Pods/
53 | **/ios/**/.symlinks/
54 | **/ios/**/profile
55 | **/ios/**/xcuserdata
56 | **/ios/.generated/
57 | **/ios/Flutter/App.framework
58 | **/ios/Flutter/Flutter.framework
59 | **/ios/Flutter/Generated.xcconfig
60 | **/ios/Flutter/app.flx
61 | **/ios/Flutter/app.zip
62 | **/ios/Flutter/flutter_assets/
63 | **/ios/ServiceDefinitions.json
64 | **/ios/Runner/GeneratedPluginRegistrant.*
65 |
66 | # Exceptions to above rules.
67 | !**/ios/**/default.mode1v3
68 | !**/ios/**/default.mode2v3
69 | !**/ios/**/default.pbxuser
70 | !**/ios/**/default.perspectivev3
71 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
72 |
--------------------------------------------------------------------------------
/example/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: f37c235c32fc15babe6dc7b7bc2ee4387e5ecf92
8 | channel: beta
9 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | # example
2 |
3 | Example app for the scatter widget
4 |
5 | ## Getting Started
6 |
7 | For help getting started with Flutter, view our online
8 | [documentation](https://flutter.io/).
9 |
--------------------------------------------------------------------------------
/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 from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
26 |
27 | android {
28 | compileSdkVersion 27
29 |
30 | lintOptions {
31 | disable 'InvalidPackage'
32 | }
33 |
34 | defaultConfig {
35 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
36 | applicationId "com.letsar.scatter.example"
37 | minSdkVersion 16
38 | targetSdkVersion 27
39 | versionCode flutterVersionCode.toInteger()
40 | versionName flutterVersionName
41 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
42 | }
43 |
44 | buildTypes {
45 | release {
46 | // TODO: Add your own signing config for the release build.
47 | // Signing with the debug keys for now, so `flutter run --release` works.
48 | signingConfig signingConfigs.debug
49 | }
50 | }
51 | }
52 |
53 | flutter {
54 | source '../..'
55 | }
56 |
57 | dependencies {
58 | testImplementation 'junit:junit:4.12'
59 | androidTestImplementation 'com.android.support.test:runner:1.0.2'
60 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
61 | }
62 |
--------------------------------------------------------------------------------
/example/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
8 |
9 |
10 |
15 |
19 |
26 |
30 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/example/android/app/src/main/java/com/letsar/scatter/example/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.letsar.scatter.example;
2 |
3 | import android.os.Bundle;
4 | import io.flutter.app.FlutterActivity;
5 | import io.flutter.plugins.GeneratedPluginRegistrant;
6 |
7 | public class MainActivity extends FlutterActivity {
8 | @Override
9 | protected void onCreate(Bundle savedInstanceState) {
10 | super.onCreate(savedInstanceState);
11 | GeneratedPluginRegistrant.registerWith(this);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/letsar/flutter_scatter/79dac180b23931452dc77c4681bce9278405311d/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/letsar/flutter_scatter/79dac180b23931452dc77c4681bce9278405311d/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/letsar/flutter_scatter/79dac180b23931452dc77c4681bce9278405311d/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/letsar/flutter_scatter/79dac180b23931452dc77c4681bce9278405311d/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/letsar/flutter_scatter/79dac180b23931452dc77c4681bce9278405311d/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
--------------------------------------------------------------------------------
/example/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | google()
4 | jcenter()
5 | }
6 |
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:3.1.2'
9 | }
10 | }
11 |
12 | allprojects {
13 | repositories {
14 | google()
15 | jcenter()
16 | }
17 | }
18 |
19 | rootProject.buildDir = '../build'
20 | subprojects {
21 | project.buildDir = "${rootProject.buildDir}/${project.name}"
22 | }
23 | subprojects {
24 | project.evaluationDependsOn(':app')
25 | }
26 |
27 | task clean(type: Delete) {
28 | delete rootProject.buildDir
29 | }
30 |
--------------------------------------------------------------------------------
/example/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 |
--------------------------------------------------------------------------------
/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-4.4-all.zip
7 |
--------------------------------------------------------------------------------
/example/android/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
4 |
5 | def plugins = new Properties()
6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
7 | if (pluginsFile.exists()) {
8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
9 | }
10 |
11 | plugins.each { name, path ->
12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
13 | include ":$name"
14 | project(":$name").projectDir = pluginDirectory
15 | }
16 |
--------------------------------------------------------------------------------
/example/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | MinimumOSVersion
24 | 8.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/example/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Generated.xcconfig"
2 |
--------------------------------------------------------------------------------
/example/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Generated.xcconfig"
2 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
11 | 2D5378261FAA1A9400D5DBA9 /* flutter_assets in Resources */ = {isa = PBXBuildFile; fileRef = 2D5378251FAA1A9400D5DBA9 /* flutter_assets */; };
12 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
13 | 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; };
14 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
15 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; };
16 | 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
17 | 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; };
18 | 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; };
19 | 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; };
20 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
21 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
22 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
23 | /* End PBXBuildFile section */
24 |
25 | /* Begin PBXCopyFilesBuildPhase section */
26 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = {
27 | isa = PBXCopyFilesBuildPhase;
28 | buildActionMask = 2147483647;
29 | dstPath = "";
30 | dstSubfolderSpec = 10;
31 | files = (
32 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */,
33 | 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */,
34 | );
35 | name = "Embed Frameworks";
36 | runOnlyForDeploymentPostprocessing = 0;
37 | };
38 | /* End PBXCopyFilesBuildPhase section */
39 |
40 | /* Begin PBXFileReference section */
41 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; };
42 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
43 | 2D5378251FAA1A9400D5DBA9 /* flutter_assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = flutter_assets; path = Flutter/flutter_assets; sourceTree = SOURCE_ROOT; };
44 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
45 | 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; };
46 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
47 | 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; };
48 | 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; };
49 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
50 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
51 | 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; };
52 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
53 | 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; };
54 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
55 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
56 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
57 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
58 | /* End PBXFileReference section */
59 |
60 | /* Begin PBXFrameworksBuildPhase section */
61 | 97C146EB1CF9000F007C117D /* Frameworks */ = {
62 | isa = PBXFrameworksBuildPhase;
63 | buildActionMask = 2147483647;
64 | files = (
65 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */,
66 | 3B80C3941E831B6300D905FE /* App.framework in Frameworks */,
67 | );
68 | runOnlyForDeploymentPostprocessing = 0;
69 | };
70 | /* End PBXFrameworksBuildPhase section */
71 |
72 | /* Begin PBXGroup section */
73 | 9740EEB11CF90186004384FC /* Flutter */ = {
74 | isa = PBXGroup;
75 | children = (
76 | 2D5378251FAA1A9400D5DBA9 /* flutter_assets */,
77 | 3B80C3931E831B6300D905FE /* App.framework */,
78 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
79 | 9740EEBA1CF902C7004384FC /* Flutter.framework */,
80 | 9740EEB21CF90195004384FC /* Debug.xcconfig */,
81 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
82 | 9740EEB31CF90195004384FC /* Generated.xcconfig */,
83 | );
84 | name = Flutter;
85 | sourceTree = "";
86 | };
87 | 97C146E51CF9000F007C117D = {
88 | isa = PBXGroup;
89 | children = (
90 | 9740EEB11CF90186004384FC /* Flutter */,
91 | 97C146F01CF9000F007C117D /* Runner */,
92 | 97C146EF1CF9000F007C117D /* Products */,
93 | CF3B75C9A7D2FA2A4C99F110 /* Frameworks */,
94 | );
95 | sourceTree = "";
96 | };
97 | 97C146EF1CF9000F007C117D /* Products */ = {
98 | isa = PBXGroup;
99 | children = (
100 | 97C146EE1CF9000F007C117D /* Runner.app */,
101 | );
102 | name = Products;
103 | sourceTree = "";
104 | };
105 | 97C146F01CF9000F007C117D /* Runner */ = {
106 | isa = PBXGroup;
107 | children = (
108 | 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */,
109 | 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */,
110 | 97C146FA1CF9000F007C117D /* Main.storyboard */,
111 | 97C146FD1CF9000F007C117D /* Assets.xcassets */,
112 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
113 | 97C147021CF9000F007C117D /* Info.plist */,
114 | 97C146F11CF9000F007C117D /* Supporting Files */,
115 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
116 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
117 | );
118 | path = Runner;
119 | sourceTree = "";
120 | };
121 | 97C146F11CF9000F007C117D /* Supporting Files */ = {
122 | isa = PBXGroup;
123 | children = (
124 | 97C146F21CF9000F007C117D /* main.m */,
125 | );
126 | name = "Supporting Files";
127 | sourceTree = "";
128 | };
129 | /* End PBXGroup section */
130 |
131 | /* Begin PBXNativeTarget section */
132 | 97C146ED1CF9000F007C117D /* Runner */ = {
133 | isa = PBXNativeTarget;
134 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
135 | buildPhases = (
136 | 9740EEB61CF901F6004384FC /* Run Script */,
137 | 97C146EA1CF9000F007C117D /* Sources */,
138 | 97C146EB1CF9000F007C117D /* Frameworks */,
139 | 97C146EC1CF9000F007C117D /* Resources */,
140 | 9705A1C41CF9048500538489 /* Embed Frameworks */,
141 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
142 | );
143 | buildRules = (
144 | );
145 | dependencies = (
146 | );
147 | name = Runner;
148 | productName = Runner;
149 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
150 | productType = "com.apple.product-type.application";
151 | };
152 | /* End PBXNativeTarget section */
153 |
154 | /* Begin PBXProject section */
155 | 97C146E61CF9000F007C117D /* Project object */ = {
156 | isa = PBXProject;
157 | attributes = {
158 | LastUpgradeCheck = 0910;
159 | ORGANIZATIONNAME = "The Chromium Authors";
160 | TargetAttributes = {
161 | 97C146ED1CF9000F007C117D = {
162 | CreatedOnToolsVersion = 7.3.1;
163 | };
164 | };
165 | };
166 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
167 | compatibilityVersion = "Xcode 3.2";
168 | developmentRegion = English;
169 | hasScannedForEncodings = 0;
170 | knownRegions = (
171 | en,
172 | Base,
173 | );
174 | mainGroup = 97C146E51CF9000F007C117D;
175 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
176 | projectDirPath = "";
177 | projectRoot = "";
178 | targets = (
179 | 97C146ED1CF9000F007C117D /* Runner */,
180 | );
181 | };
182 | /* End PBXProject section */
183 |
184 | /* Begin PBXResourcesBuildPhase section */
185 | 97C146EC1CF9000F007C117D /* Resources */ = {
186 | isa = PBXResourcesBuildPhase;
187 | buildActionMask = 2147483647;
188 | files = (
189 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
190 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
191 | 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */,
192 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
193 | 2D5378261FAA1A9400D5DBA9 /* flutter_assets in Resources */,
194 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
195 | );
196 | runOnlyForDeploymentPostprocessing = 0;
197 | };
198 | /* End PBXResourcesBuildPhase section */
199 |
200 | /* Begin PBXShellScriptBuildPhase section */
201 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
202 | isa = PBXShellScriptBuildPhase;
203 | buildActionMask = 2147483647;
204 | files = (
205 | );
206 | inputPaths = (
207 | );
208 | name = "Thin Binary";
209 | outputPaths = (
210 | );
211 | runOnlyForDeploymentPostprocessing = 0;
212 | shellPath = /bin/sh;
213 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin";
214 | };
215 | 9740EEB61CF901F6004384FC /* Run Script */ = {
216 | isa = PBXShellScriptBuildPhase;
217 | buildActionMask = 2147483647;
218 | files = (
219 | );
220 | inputPaths = (
221 | );
222 | name = "Run Script";
223 | outputPaths = (
224 | );
225 | runOnlyForDeploymentPostprocessing = 0;
226 | shellPath = /bin/sh;
227 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
228 | };
229 | /* End PBXShellScriptBuildPhase section */
230 |
231 | /* Begin PBXSourcesBuildPhase section */
232 | 97C146EA1CF9000F007C117D /* Sources */ = {
233 | isa = PBXSourcesBuildPhase;
234 | buildActionMask = 2147483647;
235 | files = (
236 | 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */,
237 | 97C146F31CF9000F007C117D /* main.m in Sources */,
238 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
239 | );
240 | runOnlyForDeploymentPostprocessing = 0;
241 | };
242 | /* End PBXSourcesBuildPhase section */
243 |
244 | /* Begin PBXVariantGroup section */
245 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = {
246 | isa = PBXVariantGroup;
247 | children = (
248 | 97C146FB1CF9000F007C117D /* Base */,
249 | );
250 | name = Main.storyboard;
251 | sourceTree = "";
252 | };
253 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
254 | isa = PBXVariantGroup;
255 | children = (
256 | 97C147001CF9000F007C117D /* Base */,
257 | );
258 | name = LaunchScreen.storyboard;
259 | sourceTree = "";
260 | };
261 | /* End PBXVariantGroup section */
262 |
263 | /* Begin XCBuildConfiguration section */
264 | 97C147031CF9000F007C117D /* Debug */ = {
265 | isa = XCBuildConfiguration;
266 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
267 | buildSettings = {
268 | ALWAYS_SEARCH_USER_PATHS = NO;
269 | CLANG_ANALYZER_NONNULL = YES;
270 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
271 | CLANG_CXX_LIBRARY = "libc++";
272 | CLANG_ENABLE_MODULES = YES;
273 | CLANG_ENABLE_OBJC_ARC = YES;
274 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
275 | CLANG_WARN_BOOL_CONVERSION = YES;
276 | CLANG_WARN_COMMA = YES;
277 | CLANG_WARN_CONSTANT_CONVERSION = YES;
278 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
279 | CLANG_WARN_EMPTY_BODY = YES;
280 | CLANG_WARN_ENUM_CONVERSION = YES;
281 | CLANG_WARN_INFINITE_RECURSION = YES;
282 | CLANG_WARN_INT_CONVERSION = YES;
283 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
284 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
285 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
286 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
287 | CLANG_WARN_STRICT_PROTOTYPES = YES;
288 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
289 | CLANG_WARN_UNREACHABLE_CODE = YES;
290 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
291 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
292 | COPY_PHASE_STRIP = NO;
293 | DEBUG_INFORMATION_FORMAT = dwarf;
294 | ENABLE_STRICT_OBJC_MSGSEND = YES;
295 | ENABLE_TESTABILITY = YES;
296 | GCC_C_LANGUAGE_STANDARD = gnu99;
297 | GCC_DYNAMIC_NO_PIC = NO;
298 | GCC_NO_COMMON_BLOCKS = YES;
299 | GCC_OPTIMIZATION_LEVEL = 0;
300 | GCC_PREPROCESSOR_DEFINITIONS = (
301 | "DEBUG=1",
302 | "$(inherited)",
303 | );
304 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
305 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
306 | GCC_WARN_UNDECLARED_SELECTOR = YES;
307 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
308 | GCC_WARN_UNUSED_FUNCTION = YES;
309 | GCC_WARN_UNUSED_VARIABLE = YES;
310 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
311 | MTL_ENABLE_DEBUG_INFO = YES;
312 | ONLY_ACTIVE_ARCH = YES;
313 | SDKROOT = iphoneos;
314 | TARGETED_DEVICE_FAMILY = "1,2";
315 | };
316 | name = Debug;
317 | };
318 | 97C147041CF9000F007C117D /* Release */ = {
319 | isa = XCBuildConfiguration;
320 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
321 | buildSettings = {
322 | ALWAYS_SEARCH_USER_PATHS = NO;
323 | CLANG_ANALYZER_NONNULL = YES;
324 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
325 | CLANG_CXX_LIBRARY = "libc++";
326 | CLANG_ENABLE_MODULES = YES;
327 | CLANG_ENABLE_OBJC_ARC = YES;
328 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
329 | CLANG_WARN_BOOL_CONVERSION = YES;
330 | CLANG_WARN_COMMA = YES;
331 | CLANG_WARN_CONSTANT_CONVERSION = YES;
332 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
333 | CLANG_WARN_EMPTY_BODY = YES;
334 | CLANG_WARN_ENUM_CONVERSION = YES;
335 | CLANG_WARN_INFINITE_RECURSION = YES;
336 | CLANG_WARN_INT_CONVERSION = YES;
337 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
338 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
339 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
340 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
341 | CLANG_WARN_STRICT_PROTOTYPES = YES;
342 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
343 | CLANG_WARN_UNREACHABLE_CODE = YES;
344 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
345 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
346 | COPY_PHASE_STRIP = NO;
347 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
348 | ENABLE_NS_ASSERTIONS = NO;
349 | ENABLE_STRICT_OBJC_MSGSEND = YES;
350 | GCC_C_LANGUAGE_STANDARD = gnu99;
351 | GCC_NO_COMMON_BLOCKS = YES;
352 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
353 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
354 | GCC_WARN_UNDECLARED_SELECTOR = YES;
355 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
356 | GCC_WARN_UNUSED_FUNCTION = YES;
357 | GCC_WARN_UNUSED_VARIABLE = YES;
358 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
359 | MTL_ENABLE_DEBUG_INFO = NO;
360 | SDKROOT = iphoneos;
361 | TARGETED_DEVICE_FAMILY = "1,2";
362 | VALIDATE_PRODUCT = YES;
363 | };
364 | name = Release;
365 | };
366 | 97C147061CF9000F007C117D /* Debug */ = {
367 | isa = XCBuildConfiguration;
368 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
369 | buildSettings = {
370 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
371 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
372 | ENABLE_BITCODE = NO;
373 | FRAMEWORK_SEARCH_PATHS = (
374 | "$(inherited)",
375 | "$(PROJECT_DIR)/Flutter",
376 | );
377 | INFOPLIST_FILE = Runner/Info.plist;
378 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
379 | LIBRARY_SEARCH_PATHS = (
380 | "$(inherited)",
381 | "$(PROJECT_DIR)/Flutter",
382 | );
383 | PRODUCT_BUNDLE_IDENTIFIER = com.letsar.scatter.example;
384 | PRODUCT_NAME = "$(TARGET_NAME)";
385 | VERSIONING_SYSTEM = "apple-generic";
386 | };
387 | name = Debug;
388 | };
389 | 97C147071CF9000F007C117D /* Release */ = {
390 | isa = XCBuildConfiguration;
391 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
392 | buildSettings = {
393 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
394 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
395 | ENABLE_BITCODE = NO;
396 | FRAMEWORK_SEARCH_PATHS = (
397 | "$(inherited)",
398 | "$(PROJECT_DIR)/Flutter",
399 | );
400 | INFOPLIST_FILE = Runner/Info.plist;
401 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
402 | LIBRARY_SEARCH_PATHS = (
403 | "$(inherited)",
404 | "$(PROJECT_DIR)/Flutter",
405 | );
406 | PRODUCT_BUNDLE_IDENTIFIER = com.letsar.scatter.example;
407 | PRODUCT_NAME = "$(TARGET_NAME)";
408 | VERSIONING_SYSTEM = "apple-generic";
409 | };
410 | name = Release;
411 | };
412 | /* End XCBuildConfiguration section */
413 |
414 | /* Begin XCConfigurationList section */
415 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
416 | isa = XCConfigurationList;
417 | buildConfigurations = (
418 | 97C147031CF9000F007C117D /* Debug */,
419 | 97C147041CF9000F007C117D /* Release */,
420 | );
421 | defaultConfigurationIsVisible = 0;
422 | defaultConfigurationName = Release;
423 | };
424 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
425 | isa = XCConfigurationList;
426 | buildConfigurations = (
427 | 97C147061CF9000F007C117D /* Debug */,
428 | 97C147071CF9000F007C117D /* Release */,
429 | );
430 | defaultConfigurationIsVisible = 0;
431 | defaultConfigurationName = Release;
432 | };
433 | /* End XCConfigurationList section */
434 | };
435 | rootObject = 97C146E61CF9000F007C117D /* Project object */;
436 | }
437 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
33 |
34 |
40 |
41 |
42 |
43 |
44 |
45 |
56 |
58 |
64 |
65 |
66 |
67 |
68 |
69 |
75 |
77 |
83 |
84 |
85 |
86 |
88 |
89 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/ios/Runner/AppDelegate.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | @interface AppDelegate : FlutterAppDelegate
5 |
6 | @end
7 |
--------------------------------------------------------------------------------
/example/ios/Runner/AppDelegate.m:
--------------------------------------------------------------------------------
1 | #include "AppDelegate.h"
2 | #include "GeneratedPluginRegistrant.h"
3 |
4 | @implementation AppDelegate
5 |
6 | - (BOOL)application:(UIApplication *)application
7 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
8 | [GeneratedPluginRegistrant registerWithRegistry:self];
9 | // Override point for customization after application launch.
10 | return [super application:application didFinishLaunchingWithOptions:launchOptions];
11 | }
12 |
13 | @end
14 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "20x20",
5 | "idiom" : "iphone",
6 | "filename" : "Icon-App-20x20@2x.png",
7 | "scale" : "2x"
8 | },
9 | {
10 | "size" : "20x20",
11 | "idiom" : "iphone",
12 | "filename" : "Icon-App-20x20@3x.png",
13 | "scale" : "3x"
14 | },
15 | {
16 | "size" : "29x29",
17 | "idiom" : "iphone",
18 | "filename" : "Icon-App-29x29@1x.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "29x29",
23 | "idiom" : "iphone",
24 | "filename" : "Icon-App-29x29@2x.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "29x29",
29 | "idiom" : "iphone",
30 | "filename" : "Icon-App-29x29@3x.png",
31 | "scale" : "3x"
32 | },
33 | {
34 | "size" : "40x40",
35 | "idiom" : "iphone",
36 | "filename" : "Icon-App-40x40@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "40x40",
41 | "idiom" : "iphone",
42 | "filename" : "Icon-App-40x40@3x.png",
43 | "scale" : "3x"
44 | },
45 | {
46 | "size" : "60x60",
47 | "idiom" : "iphone",
48 | "filename" : "Icon-App-60x60@2x.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "60x60",
53 | "idiom" : "iphone",
54 | "filename" : "Icon-App-60x60@3x.png",
55 | "scale" : "3x"
56 | },
57 | {
58 | "size" : "20x20",
59 | "idiom" : "ipad",
60 | "filename" : "Icon-App-20x20@1x.png",
61 | "scale" : "1x"
62 | },
63 | {
64 | "size" : "20x20",
65 | "idiom" : "ipad",
66 | "filename" : "Icon-App-20x20@2x.png",
67 | "scale" : "2x"
68 | },
69 | {
70 | "size" : "29x29",
71 | "idiom" : "ipad",
72 | "filename" : "Icon-App-29x29@1x.png",
73 | "scale" : "1x"
74 | },
75 | {
76 | "size" : "29x29",
77 | "idiom" : "ipad",
78 | "filename" : "Icon-App-29x29@2x.png",
79 | "scale" : "2x"
80 | },
81 | {
82 | "size" : "40x40",
83 | "idiom" : "ipad",
84 | "filename" : "Icon-App-40x40@1x.png",
85 | "scale" : "1x"
86 | },
87 | {
88 | "size" : "40x40",
89 | "idiom" : "ipad",
90 | "filename" : "Icon-App-40x40@2x.png",
91 | "scale" : "2x"
92 | },
93 | {
94 | "size" : "76x76",
95 | "idiom" : "ipad",
96 | "filename" : "Icon-App-76x76@1x.png",
97 | "scale" : "1x"
98 | },
99 | {
100 | "size" : "76x76",
101 | "idiom" : "ipad",
102 | "filename" : "Icon-App-76x76@2x.png",
103 | "scale" : "2x"
104 | },
105 | {
106 | "size" : "83.5x83.5",
107 | "idiom" : "ipad",
108 | "filename" : "Icon-App-83.5x83.5@2x.png",
109 | "scale" : "2x"
110 | },
111 | {
112 | "size" : "1024x1024",
113 | "idiom" : "ios-marketing",
114 | "filename" : "Icon-App-1024x1024@1x.png",
115 | "scale" : "1x"
116 | }
117 | ],
118 | "info" : {
119 | "version" : 1,
120 | "author" : "xcode"
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/letsar/flutter_scatter/79dac180b23931452dc77c4681bce9278405311d/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/letsar/flutter_scatter/79dac180b23931452dc77c4681bce9278405311d/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/letsar/flutter_scatter/79dac180b23931452dc77c4681bce9278405311d/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/letsar/flutter_scatter/79dac180b23931452dc77c4681bce9278405311d/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/letsar/flutter_scatter/79dac180b23931452dc77c4681bce9278405311d/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/letsar/flutter_scatter/79dac180b23931452dc77c4681bce9278405311d/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/letsar/flutter_scatter/79dac180b23931452dc77c4681bce9278405311d/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/letsar/flutter_scatter/79dac180b23931452dc77c4681bce9278405311d/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/letsar/flutter_scatter/79dac180b23931452dc77c4681bce9278405311d/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/letsar/flutter_scatter/79dac180b23931452dc77c4681bce9278405311d/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/letsar/flutter_scatter/79dac180b23931452dc77c4681bce9278405311d/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/letsar/flutter_scatter/79dac180b23931452dc77c4681bce9278405311d/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/letsar/flutter_scatter/79dac180b23931452dc77c4681bce9278405311d/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/letsar/flutter_scatter/79dac180b23931452dc77c4681bce9278405311d/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/letsar/flutter_scatter/79dac180b23931452dc77c4681bce9278405311d/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "LaunchImage.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "LaunchImage@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "LaunchImage@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/letsar/flutter_scatter/79dac180b23931452dc77c4681bce9278405311d/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/letsar/flutter_scatter/79dac180b23931452dc77c4681bce9278405311d/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/letsar/flutter_scatter/79dac180b23931452dc77c4681bce9278405311d/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md:
--------------------------------------------------------------------------------
1 | # Launch Screen Assets
2 |
3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory.
4 |
5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
--------------------------------------------------------------------------------
/example/ios/Runner/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/example/ios/Runner/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/example/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | example
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | $(FLUTTER_BUILD_NAME)
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(FLUTTER_BUILD_NUMBER)
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIMainStoryboardFile
28 | Main
29 | UISupportedInterfaceOrientations
30 |
31 | UIInterfaceOrientationPortrait
32 | UIInterfaceOrientationLandscapeLeft
33 | UIInterfaceOrientationLandscapeRight
34 |
35 | UISupportedInterfaceOrientations~ipad
36 |
37 | UIInterfaceOrientationPortrait
38 | UIInterfaceOrientationPortraitUpsideDown
39 | UIInterfaceOrientationLandscapeLeft
40 | UIInterfaceOrientationLandscapeRight
41 |
42 | UIViewControllerBasedStatusBarAppearance
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/example/ios/Runner/main.m:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 | #import "AppDelegate.h"
4 |
5 | int main(int argc, char* argv[]) {
6 | @autoreleasepool {
7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/example/lib/flutter_hashtags.dart:
--------------------------------------------------------------------------------
1 | import 'dart:ui';
2 |
3 | import 'package:flutter/painting.dart';
4 |
5 | class FlutterHashtag {
6 | const FlutterHashtag(
7 | this.hashtag,
8 | this.color,
9 | this.size,
10 | this.rotated,
11 | );
12 | final String hashtag;
13 | final Color color;
14 | final int size;
15 | final bool rotated;
16 | }
17 |
18 | class FlutterColors {
19 | const FlutterColors._();
20 |
21 | static const Color yellow = Color(0xFFFFC108);
22 |
23 | static const Color white = Color(0xFFFFFFFF);
24 |
25 | static const Color blue400 = Color(0xFF13B9FD);
26 | static const Color blue600 = Color(0xFF0175C2);
27 | static const Color blue = Color(0xFF02569B);
28 |
29 | static const Color gray100 = Color(0xFFD5D7DA);
30 | static const Color gray600 = Color(0xFF60646B);
31 | static const Color gray = Color(0xFF202124);
32 | }
33 |
34 | const List kFlutterHashtags = const [
35 | FlutterHashtag('#FlutterLive', FlutterColors.yellow, 100, false),
36 | FlutterHashtag('#FlutterSpiration', FlutterColors.gray600, 24, false),
37 | FlutterHashtag('#FlutterSpirit', FlutterColors.blue600, 12, true),
38 | FlutterHashtag('#FlutterHashtagging', FlutterColors.gray, 14, false),
39 | FlutterHashtag('#FlutterHashtagChallenge', FlutterColors.blue400, 16, false),
40 | FlutterHashtag('#FlutterHashtagOfTheDay', FlutterColors.blue600, 12, true),
41 | FlutterHashtag('#FlutterCover', FlutterColors.gray600, 20, true),
42 | FlutterHashtag('#FlutterDream', FlutterColors.blue, 36, false),
43 | FlutterHashtag('#FlutterAddict', FlutterColors.blue400, 40, false),
44 | FlutterHashtag('#FlutterDevOps', FlutterColors.gray, 32, true),
45 | FlutterHashtag('#Fluttermidable', FlutterColors.gray, 12, false),
46 | FlutterHashtag('#FlutterPackage', FlutterColors.gray600, 14, false),
47 | FlutterHashtag('#FlutterUpgradeDay', FlutterColors.blue600, 16, false),
48 | FlutterHashtag('#FlutterFunCoding', FlutterColors.blue600, 20, true),
49 | FlutterHashtag('#FlutterGuestStars', FlutterColors.blue, 22, false),
50 | FlutterHashtag('#FlutterMagician', FlutterColors.gray, 30, false),
51 | FlutterHashtag('#FlutterHotReload', FlutterColors.yellow, 44, false),
52 | FlutterHashtag('#FlutterMagicTrick', FlutterColors.blue400, 30, true),
53 | FlutterHashtag('#FlutterWeekEnd', FlutterColors.gray, 12, true),
54 | FlutterHashtag('#FlutterArtist', FlutterColors.blue600, 20, false),
55 | FlutterHashtag('#FlutterDevelopers', FlutterColors.gray600, 32, false),
56 | FlutterHashtag('#FlutterGuestStar', FlutterColors.blue600, 34, false),
57 | FlutterHashtag('#FlutterBestDayOfTheWeek', FlutterColors.gray, 12, true),
58 | FlutterHashtag('#FlutterIsMyBFF', FlutterColors.gray, 20, false),
59 | FlutterHashtag('#FlutterIsComing', FlutterColors.yellow, 44, false),
60 | FlutterHashtag('#FlutterMakers', FlutterColors.blue, 32, true),
61 | FlutterHashtag('#FlutterLiveInvite', FlutterColors.blue, 40, false),
62 | FlutterHashtag('#FlutterPower', FlutterColors.blue400, 32, false),
63 | FlutterHashtag('#FlutterCat', FlutterColors.blue, 20, true),
64 | FlutterHashtag('#FlutterExcellent', FlutterColors.gray, 24, true),
65 | FlutterHashtag('#FlutterIsAwesome', FlutterColors.blue, 26, false),
66 | FlutterHashtag('#FlutterExcited', FlutterColors.blue600, 28, false),
67 | FlutterHashtag('#FlutterReady', FlutterColors.gray, 36, true),
68 | FlutterHashtag('#FlutterRennes', FlutterColors.blue, 36, false),
69 | FlutterHashtag('#FlutterLiveRegistration', FlutterColors.blue400, 40, false),
70 | FlutterHashtag('#FlutterLiveTicket', FlutterColors.blue, 36, false),
71 | FlutterHashtag('#FlutterDreamComeTrue', FlutterColors.blue400, 20, false),
72 | FlutterHashtag('#SeeYouLiveAtFlutterLive', FlutterColors.blue, 12, false),
73 | FlutterHashtag('#GoodFlutterNews', FlutterColors.blue, 14, false),
74 | FlutterHashtag('#FlutterIsSoGreat', FlutterColors.blue, 20, false),
75 | FlutterHashtag('#FlutterUsers', FlutterColors.blue, 30, false),
76 | FlutterHashtag('#FlutterSpeakers', FlutterColors.blue, 22, true),
77 | FlutterHashtag('#FlutterSwag', FlutterColors.blue, 34, false),
78 | FlutterHashtag('#Flutter40K', FlutterColors.yellow, 50, false),
79 | ];
80 |
--------------------------------------------------------------------------------
/example/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_scatter/flutter_scatter.dart';
3 | import 'screens/align.dart';
4 | import 'screens/archi_spiral.dart';
5 | import 'screens/circle.dart';
6 | import 'screens/word_cloud.dart';
7 |
8 | void main() => runApp(ScatterApp());
9 |
10 | class ScatterHomeTileData {
11 | ScatterHomeTileData({
12 | @required this.route,
13 | @required this.builder,
14 | @required this.text,
15 | @required this.diameter,
16 | @required this.color,
17 | });
18 | final double diameter;
19 | final Color color;
20 | final String text;
21 | final String route;
22 | final WidgetBuilder builder;
23 | }
24 |
25 | final List _tiles = [
26 | ScatterHomeTileData(
27 | route: 'word_cloud',
28 | text: 'Word Cloud',
29 | builder: (_) => WordCloudExample(),
30 | diameter: 100.0,
31 | color: Colors.green,
32 | ),
33 | ScatterHomeTileData(
34 | route: 'circle',
35 | text: 'Circle',
36 | builder: (_) => CircleExample(),
37 | diameter: 50.0,
38 | color: Colors.blue,
39 | ),
40 | ScatterHomeTileData(
41 | route: 'align',
42 | text: 'Align',
43 | builder: (_) => AlignExample(),
44 | diameter: 70.0,
45 | color: Colors.red,
46 | ),
47 | ScatterHomeTileData(
48 | route: 'spiral',
49 | text: 'Spiral',
50 | builder: (_) => ArchiSpiralExample(),
51 | diameter: 60.0,
52 | color: Colors.orange,
53 | ),
54 | ];
55 |
56 | class ScatterApp extends StatelessWidget {
57 | @override
58 | Widget build(BuildContext context) {
59 | return MaterialApp(
60 | title: 'Scatter Demo',
61 | theme: ThemeData(
62 | primarySwatch: Colors.blue,
63 | ),
64 | home: ScatterHomePage(),
65 | routes: Map.fromEntries(
66 | _tiles.map((t) => MapEntry(
67 | t.route,
68 | (_) => SimpleScaffold(
69 | title: t.text,
70 | child: Builder(builder: t.builder),
71 | ))),
72 | ),
73 | );
74 | }
75 | }
76 |
77 | class ScatterHomePage extends StatelessWidget {
78 | @override
79 | Widget build(BuildContext context) {
80 | return Scaffold(
81 | appBar: AppBar(title: Text('Scatter Demo')),
82 | body: Center(
83 | child: Scatter(
84 | delegate: FermatSpiralScatterDelegate(),
85 | children: _tiles.map((t) => ScatterHomeTile(t)).toList(),
86 | ),
87 | ),
88 | );
89 | }
90 | }
91 |
92 | class ScatterHomeTile extends StatelessWidget {
93 | ScatterHomeTile(
94 | this.data,
95 | );
96 | final ScatterHomeTileData data;
97 |
98 | @override
99 | Widget build(BuildContext context) {
100 | return Material(
101 | color: data.color,
102 | borderRadius: BorderRadius.circular(data.diameter / 2.0),
103 | child: InkWell(
104 | radius: data.diameter / 2.0,
105 | customBorder: CircleBorder(),
106 | onTap: () => Navigator.of(context).pushNamed(data.route),
107 | child: Container(
108 | width: data.diameter,
109 | height: data.diameter,
110 | child: Center(
111 | child: Text(
112 | data.text,
113 | style: Theme.of(context)
114 | .textTheme
115 | .subhead
116 | .copyWith(color: Colors.white),
117 | ),
118 | ),
119 | ),
120 | ),
121 | );
122 | }
123 | }
124 |
125 | class TextApp extends StatelessWidget {
126 | @override
127 | Widget build(BuildContext context) {
128 | return Directionality(
129 | textDirection: TextDirection.ltr,
130 | child: Align(
131 | alignment: Alignment.center,
132 | child: Scatter(
133 | alignment: Alignment.center,
134 | delegate: AlignScatterDelegate(alignment: Alignment.topCenter),
135 | children: List.generate(
136 | 4,
137 | (i) => Container(
138 | width: (i + 1) * 20.0,
139 | height: (i + 1) * 20.0,
140 | key: ValueKey(i),
141 | color: i.isEven ? Colors.blue : Colors.orange,
142 | child: Text('$i'),
143 | ),
144 | ),
145 | ),
146 | ),
147 | );
148 | }
149 | }
150 |
151 | class SimpleScaffold extends StatelessWidget {
152 | const SimpleScaffold({
153 | Key key,
154 | this.title,
155 | this.child,
156 | }) : super(key: key);
157 |
158 | final String title;
159 |
160 | final Widget child;
161 |
162 | @override
163 | Widget build(BuildContext context) {
164 | return Scaffold(
165 | appBar: AppBar(
166 | title: Text(title),
167 | ),
168 | body: child,
169 | );
170 | }
171 | }
172 |
--------------------------------------------------------------------------------
/example/lib/screens/align.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_scatter/flutter_scatter.dart';
3 |
4 | class AlignExample extends StatelessWidget {
5 | @override
6 | Widget build(BuildContext context) {
7 | final int count = 11;
8 | List widgets = [];
9 | for (var i = 0; i < count; i++) {
10 | widgets.add(ScatterItem(i));
11 | }
12 |
13 | return Center(
14 | child: Scatter(
15 | delegate: AlignScatterDelegate(
16 | alignment: Alignment(0.2, 1.0),
17 | ),
18 | children: widgets,
19 | ),
20 | );
21 | }
22 | }
23 |
24 | class ScatterItem extends StatelessWidget {
25 | ScatterItem(this.index);
26 | final int index;
27 |
28 | @override
29 | Widget build(BuildContext context) {
30 | final TextStyle style = Theme.of(context).textTheme.body1.copyWith(
31 | color: Colors.white,
32 | );
33 | return Container(
34 | height: 40.0,
35 | width: 40.0,
36 | decoration: BoxDecoration(
37 | shape: BoxShape.circle,
38 | color: index.isEven ? Colors.red : Colors.pink),
39 | child: Center(
40 | child: Text(
41 | '$index',
42 | style: style,
43 | )),
44 | );
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/example/lib/screens/archi_spiral.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_scatter/flutter_scatter.dart';
3 |
4 | class ArchiSpiralExample extends StatelessWidget {
5 | @override
6 | Widget build(BuildContext context) {
7 | final int count = 50;
8 | List widgets = [];
9 | for (var i = 0; i < count; i++) {
10 | widgets.add(ScatterItem(i));
11 | }
12 |
13 | return Center(
14 | child: FittedBox(
15 | child: Scatter(
16 | delegate: ArchimedeanSpiralScatterDelegate(
17 | a: 35.0,
18 | step: 0.01,
19 | ),
20 | children: widgets,
21 | ),
22 | ),
23 | );
24 | }
25 | }
26 |
27 | class ScatterItem extends StatelessWidget {
28 | ScatterItem(this.index);
29 | final int index;
30 |
31 | @override
32 | Widget build(BuildContext context) {
33 | final TextStyle style = Theme.of(context).textTheme.body1.copyWith(
34 | color: Colors.white,
35 | );
36 | return GestureDetector(
37 | onTap: () => Scaffold.of(context).showSnackBar(SnackBar(
38 | content: Text('$index'),
39 | )),
40 | child: Container(
41 | height: 35.0,
42 | width: 35.0,
43 | decoration: BoxDecoration(
44 | shape: BoxShape.circle,
45 | color: index.isEven ? Colors.red : Colors.pink),
46 | child: Center(
47 | child: Text(
48 | '$index',
49 | style: style,
50 | )),
51 | ),
52 | );
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/example/lib/screens/circle.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_scatter/flutter_scatter.dart';
3 |
4 | class CircleExample extends StatelessWidget {
5 | @override
6 | Widget build(BuildContext context) {
7 | final int count = 20;
8 | List widgets = [];
9 | for (var i = 0; i < count; i++) {
10 | widgets.add(ScatterItem(i));
11 | }
12 |
13 | return Center(
14 | child: Scatter(
15 | delegate: EllipseScatterDelegate(
16 | a: 185.0,
17 | b: 185.0,
18 | step: 1.0 / count,
19 | ),
20 | children: widgets,
21 | ),
22 | );
23 | }
24 | }
25 |
26 | class ScatterItem extends StatelessWidget {
27 | ScatterItem(this.index);
28 | final int index;
29 |
30 | @override
31 | Widget build(BuildContext context) {
32 | final TextStyle style = Theme.of(context).textTheme.body1.copyWith(
33 | color: Colors.white,
34 | );
35 | return Container(
36 | height: 40.0,
37 | width: 40.0,
38 | decoration: BoxDecoration(
39 | shape: BoxShape.circle,
40 | color: index.isEven ? Colors.red : Colors.pink),
41 | child: Center(
42 | child: Text(
43 | '$index',
44 | style: style,
45 | )),
46 | );
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/example/lib/screens/word_cloud.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_scatter/flutter_scatter.dart';
3 | import '../flutter_hashtags.dart';
4 |
5 | class WordCloudExample extends StatelessWidget {
6 | @override
7 | Widget build(BuildContext context) {
8 | List widgets = [];
9 | for (var i = 0; i < kFlutterHashtags.length; i++) {
10 | widgets.add(ScatterItem(kFlutterHashtags[i], i));
11 | }
12 |
13 | final screenSize = MediaQuery.of(context).size;
14 | final ratio = screenSize.width / screenSize.height;
15 |
16 | return Center(
17 | child: FittedBox(
18 | child: Scatter(
19 | fillGaps: true,
20 | delegate: ArchimedeanSpiralScatterDelegate(ratio: ratio),
21 | children: widgets,
22 | ),
23 | ),
24 | );
25 | }
26 | }
27 |
28 | class ScatterItem extends StatelessWidget {
29 | ScatterItem(this.hashtag, this.index);
30 | final FlutterHashtag hashtag;
31 | final int index;
32 |
33 | @override
34 | Widget build(BuildContext context) {
35 | final TextStyle style = Theme.of(context).textTheme.body1.copyWith(
36 | fontSize: hashtag.size.toDouble(),
37 | color: hashtag.color,
38 | );
39 | return RotatedBox(
40 | quarterTurns: hashtag.rotated ? 1 : 0,
41 | child: Text(
42 | hashtag.hashtag,
43 | style: style,
44 | ),
45 | );
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/example/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: example
2 | description: Example app for the scatter widget
3 |
4 | # The following defines the version and build number for your application.
5 | # A version number is three numbers separated by dots, like 1.2.43
6 | # followed by an optional build number separated by a +.
7 | # Both the version and the builder number may be overridden in flutter
8 | # build by specifying --build-name and --build-number, respectively.
9 | # Read more about versioning at semver.org.
10 | version: 1.0.0+1
11 |
12 | environment:
13 | sdk: ">=2.0.0-dev.68.0 <3.0.0"
14 |
15 | dependencies:
16 | flutter_scatter:
17 | path: ../
18 | flutter:
19 | sdk: flutter
20 |
21 | # The following adds the Cupertino Icons font to your application.
22 | # Use with the CupertinoIcons class for iOS style icons.
23 | cupertino_icons: ^0.1.2
24 |
25 | dev_dependencies:
26 | flutter_test:
27 | sdk: flutter
28 |
29 |
30 | # For information on the generic Dart part of this file, see the
31 | # following page: https://www.dartlang.org/tools/pub/pubspec
32 |
33 | # The following section is specific to Flutter.
34 | flutter:
35 |
36 | # The following line ensures that the Material Icons font is
37 | # included with your application, so that you can use the icons in
38 | # the material Icons class.
39 | uses-material-design: true
40 |
41 | # To add assets to your application, add an assets section, like this:
42 | # assets:
43 | # - images/a_dot_burr.jpeg
44 | # - images/a_dot_ham.jpeg
45 |
46 | # An image asset can refer to one or more resolution-specific "variants", see
47 | # https://flutter.io/assets-and-images/#resolution-aware.
48 |
49 | # For details regarding adding assets from package dependencies, see
50 | # https://flutter.io/assets-and-images/#from-packages
51 |
52 | # To add custom fonts to your application, add a fonts section here,
53 | # in this "flutter" section. Each entry in this list should have a
54 | # "family" key with the font family name, and a "fonts" key with a
55 | # list giving the asset and other descriptors for the font. For
56 | # example:
57 | # fonts:
58 | # - family: Schyler
59 | # fonts:
60 | # - asset: fonts/Schyler-Regular.ttf
61 | # - asset: fonts/Schyler-Italic.ttf
62 | # style: italic
63 | # - family: Trajan Pro
64 | # fonts:
65 | # - asset: fonts/TrajanPro.ttf
66 | # - asset: fonts/TrajanPro_Bold.ttf
67 | # weight: 700
68 | #
69 | # For details regarding fonts from package dependencies,
70 | # see https://flutter.io/custom-fonts/#from-packages
71 |
--------------------------------------------------------------------------------
/lib/flutter_scatter.dart:
--------------------------------------------------------------------------------
1 | /// Contain a widget that displays a collection of dispersed and non-overlapping children
2 | library flutter_scatter;
3 |
4 | export 'package:flutter_scatter/src/rendering/scatter.dart';
5 | export 'package:flutter_scatter/src/widgets/scatter.dart';
6 |
--------------------------------------------------------------------------------
/lib/src/rendering/scatter.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/rendering.dart';
2 | import 'package:flutter/widgets.dart';
3 | import 'dart:math' as math;
4 |
5 | const double _2pi = math.pi * 2;
6 |
7 | /// A context in which a [ScatterDelegate] positions
8 | /// the [RenderScatter] children.
9 | ///
10 | /// See also:
11 | ///
12 | /// * [ScatterDelegate]
13 | /// * [RenderScatter]
14 | class ScatterContext {
15 | ScatterContext._(
16 | this.childSize,
17 | this.previousChldRect,
18 | this.bounds,
19 | this.alignment,
20 | );
21 | final Size childSize;
22 | final Rect previousChldRect;
23 | final Rect bounds;
24 | final Alignment alignment;
25 | }
26 |
27 | /// A delegate that controls the layout of a [Scatter].
28 | abstract class ScatterDelegate {
29 | /// Creates the delegate used by [RenderScatter] to
30 | /// place its children.
31 | ScatterDelegate({
32 | this.ratio,
33 | });
34 |
35 | /// The ratio used to place the children.
36 | ///
37 | /// For example if ratio is 16.0/9.0,
38 | /// children must be placed in a rectangle box
39 | /// with dimensions width/height respecting 16.0/9.0.
40 | final double ratio;
41 |
42 | double _ratioX;
43 |
44 | /// The ratio used for the x-axis points.
45 | double get ratioX => _ratioX;
46 |
47 | double _ratioY;
48 |
49 | /// The ratio used for the y-axis points.
50 | double get ratioY => _ratioY;
51 |
52 | Size _availableSize;
53 |
54 | /// The available size to place children.
55 | Size get availableSize => _availableSize;
56 | void _setAvailableSize(Size value) {
57 | _availableSize = value;
58 | final double ratio =
59 | this.ratio ?? (value.isFinite ? value.width / value.height : 1.0);
60 | _ratioX = ratio >= 1 ? ratio : 1.0;
61 | _ratioY = ratio <= 1 ? ratio : 1.0;
62 | }
63 |
64 | /// Returns the child offset for the given iteration.
65 | ///
66 | /// For a given iteration, the position should be unique.
67 | Offset getChildOffsetForIteration(int iteration, ScatterContext context) {
68 | final Offset position = getPositionForIteration(iteration, context);
69 | return getChildOffset(position, context);
70 | }
71 |
72 | /// Returns an offset for the specified iteration.
73 | ///
74 | /// For a given iteration, the offset should be unique.
75 | @protected
76 | Offset getPositionForIteration(int iteration, ScatterContext context);
77 |
78 | /// Returns the child offset for the given position.
79 | @protected
80 | Offset getChildOffset(Offset position, ScatterContext context) {
81 | return position - context.alignment.alongSize(context.childSize);
82 | }
83 |
84 | /// Override this method to return true when the children need to be laid out.
85 | /// This should compare the fields of the current delegate and the given
86 | /// oldDelegate and return true if the fields are such that the layout would
87 | /// be different.
88 | @mustCallSuper
89 | bool shouldRelayout(covariant ScatterDelegate oldDelegate) {
90 | return oldDelegate.ratio != ratio;
91 | }
92 | }
93 |
94 | /// A [ScatterDelegate] that places children in a spiral.
95 | ///
96 | /// See:
97 | /// * [ArchimedeanSpiralScatterDelegate] which represents an archimedean spiral.
98 | /// * [FermatSpiralScatterDelegate] which represents a special case of archimedean spiral.
99 | /// * [LogarithmicSpiralScatterDelegate] which represents a logarithmic spiral.
100 | abstract class SpiralScatterDelegate extends ScatterDelegate {
101 | /// Create a spiral where for each increment the angle is increased
102 | /// by [step] * 2π radians and with the given [rotation].
103 | ///
104 | /// For example if [step] is 0.25, the angle will be increased
105 | /// by π/2 radians.
106 | /// The [step] is a value between 0.0 and 1.0.
107 | ///
108 | /// The [rotation] is a value between 0.0 and 1.0.
109 | /// A [rotation] of 0.5 represents a rotation of π/2 radians.
110 | SpiralScatterDelegate({
111 | double ratio,
112 | double step = 0.01,
113 | double rotation = 0.0,
114 | }) : assert(step != null && step >= 0.0 && step <= 1.0),
115 | assert(rotation != null && rotation >= 0.0 && rotation <= 1.0),
116 | _stepRadians = step * _2pi,
117 | _rotationRadians = rotation * _2pi,
118 | super(ratio: ratio);
119 |
120 | final double _stepRadians;
121 |
122 | final double _rotationRadians;
123 |
124 | Offset getPositionForIteration(
125 | int iteration,
126 | ScatterContext context,
127 | ) {
128 | final double angle = iteration * _stepRadians;
129 | final double radius = computeRadius(angle);
130 | final double x = ratioX * radius * math.cos(angle + _rotationRadians);
131 | final double y = ratioY * radius * math.sin(angle + _rotationRadians);
132 | return Offset(x, y);
133 | }
134 |
135 | /// All spirals are a function of the angle: r=f(θ).
136 | double computeRadius(double angle);
137 |
138 | bool shouldRelayout(covariant SpiralScatterDelegate oldDelegate) {
139 | return super.shouldRelayout(oldDelegate) ||
140 | oldDelegate._stepRadians != _stepRadians ||
141 | oldDelegate._rotationRadians != _rotationRadians;
142 | }
143 | }
144 |
145 | /// A [ScatterDelegate] that places children in an archimedean spiral.
146 | /// See: https://en.wikipedia.org/wiki/Archimedean_spiral.
147 | class ArchimedeanSpiralScatterDelegate extends SpiralScatterDelegate {
148 | /// Creates an archimedean spiral [ScatterDelegate].
149 | ///
150 | /// An archimedean spiral has this polar equation: **r=a+bθ**
151 | ArchimedeanSpiralScatterDelegate({
152 | double ratio,
153 | this.a = 10.0,
154 | this.b = 10.0,
155 | double step = 0.01,
156 | double rotation = 0.0,
157 | }) : super(
158 | ratio: ratio,
159 | step: step,
160 | rotation: rotation,
161 | );
162 |
163 | /// The initial radius of the spiral.
164 | ///
165 | /// This is the **a** parameter in the equation **r=a+bθ**.
166 | final double a;
167 |
168 | /// The distance between successive turns of the spiral.
169 | ///
170 | /// This is the **b** parameter in the equation **r=a+bθ**.
171 | final double b;
172 |
173 | double computeRadius(double angle) => a + b * angle;
174 |
175 | bool shouldRelayout(covariant ArchimedeanSpiralScatterDelegate oldDelegate) {
176 | return super.shouldRelayout(oldDelegate) ||
177 | oldDelegate.a != a ||
178 | oldDelegate.b != b;
179 | }
180 | }
181 |
182 | /// A [ScatterDelegate] that places children in a Fermat spiral.
183 | /// See: https://en.wikipedia.org/wiki/Fermat%27s_spiral.
184 | class FermatSpiralScatterDelegate extends ArchimedeanSpiralScatterDelegate {
185 | static const double goldenAngle = 0.381966;
186 |
187 | /// Creates a Fermat spiral [ScatterDelegate].
188 | ///
189 | /// A Fermat spiral has this polar equation: **r=a+b√θ**
190 | FermatSpiralScatterDelegate({
191 | double ratio,
192 | double a = 1.0,
193 | double b = 15.0,
194 | double step = 0.47,
195 | double rotation = 0.0,
196 | }) : super(
197 | ratio: ratio,
198 | step: step,
199 | rotation: rotation,
200 | a: a,
201 | b: b,
202 | );
203 |
204 | double computeRadius(double angle) => a + b * math.sqrt(angle);
205 | }
206 |
207 | /// A [ScatterDelegate] that places children in a Logarithmic spiral.
208 | /// See: https://en.wikipedia.org/wiki/Logarithmic_spiral.
209 | class LogarithmicSpiralScatterDelegate extends SpiralScatterDelegate {
210 | /// Creates a logarithmic spiral [ScatterDelegate].
211 | ///
212 | /// A logarithmic spiral has this polar equation: **r=ae^bθ**
213 | LogarithmicSpiralScatterDelegate({
214 | double ratio,
215 | this.a = 1.0,
216 | this.b = 0.3063489,
217 | double step = 0.01,
218 | double rotation = 0.0,
219 | }) : super(
220 | ratio: ratio,
221 | step: step,
222 | rotation: rotation,
223 | );
224 |
225 | /// The initial radius of the spiral.
226 | ///
227 | /// This is the **a** parameter in the equation **r=ae^bθ**.
228 | final double a;
229 |
230 | /// The distance between successive turns of the spiral.
231 | ///
232 | /// This is the **b** parameter in the equation **r=ae^bθ**.
233 | final double b;
234 |
235 | double computeRadius(double angle) => a * math.exp(b * angle);
236 | }
237 |
238 | /// A [ScatterDelegate] that aligns a child with its predecessor.
239 | class AlignScatterDelegate extends ScatterDelegate {
240 | /// Creates a delegate where a child is aligned with its predecessor.
241 | ///
242 | /// The [alignment] must not be null, and must be on the side.
243 | AlignScatterDelegate({
244 | this.alignment = Alignment.bottomRight,
245 | }) : assert(alignment != null &&
246 | (alignment.x.abs() == 1 || alignment.y.abs() == 1)),
247 | _oppositeAlignment = -alignment;
248 |
249 | /// How to align a child with the previous one.
250 | final Alignment alignment;
251 |
252 | final Alignment _oppositeAlignment;
253 |
254 | @override
255 | Offset getPositionForIteration(int iteration, ScatterContext context) {
256 | return alignment.withinRect(context.previousChldRect);
257 | }
258 |
259 | /// Returns the child offset for the given position.
260 | @protected
261 | Offset getChildOffset(Offset position, ScatterContext context) {
262 | return position - _oppositeAlignment.alongSize(context.childSize);
263 | }
264 | }
265 |
266 | /// A [ScatterDelegate] that places children on an ellipse.
267 | class EllipseScatterDelegate extends ScatterDelegate {
268 | /// Creates a delegate where children of a [RenderScatter]
269 | /// are placed on an ellipse.
270 | ///
271 | /// The parametric representation of an ellipse is
272 | /// **(x,y)=(a cos θ, b sin θ)**
273 | ///
274 | /// The arguments cannot be null.
275 | /// The arguments [a] and [b] must be positive and [step] cannot be zero.
276 | EllipseScatterDelegate({
277 | @required this.a,
278 | @required this.b,
279 | double step = 0.01,
280 | double start = 0.0,
281 | }) : assert(a != null && a > 0),
282 | assert(b != null && b > 0),
283 | assert(step != null && step != 0),
284 | assert(start != null),
285 | _stepRadians = step * _2pi,
286 | _startRadians = start * _2pi;
287 |
288 | /// Semi x-axis.
289 | final double a;
290 |
291 | /// Semi y-axis.
292 | final double b;
293 |
294 | final double _stepRadians;
295 | final double _startRadians;
296 |
297 | @override
298 | Offset getPositionForIteration(int iteration, ScatterContext context) {
299 | final double angle = iteration * _stepRadians;
300 | final double x = a * math.cos(angle + _startRadians);
301 | final double y = b * math.sin(angle + _startRadians);
302 | return Offset(x, y);
303 | }
304 | }
305 |
306 | /// Parent data used by [RenderScatter] and its subclasses.
307 | class ScatterParentData extends ContainerBoxParentData {
308 | // The index of the child in the children list.
309 | int index;
310 |
311 | /// The child's width.
312 | double width;
313 |
314 | /// The child's height.
315 | double height;
316 |
317 | Rect get rect => Rect.fromLTWH(
318 | offset.dx,
319 | offset.dy,
320 | width,
321 | height,
322 | );
323 | }
324 |
325 | /// Implements the scatter layout algorithm.
326 | ///
327 | /// In a scatter layout, the children cannot overlap.
328 | /// They are placed with a delegate that is called until
329 | /// we find a position where the current child does not overlap
330 | /// previous children.
331 | ///
332 | /// To check if a child overlap another, we simply check
333 | /// if the rectangles englobing these objects are overlapping.
334 | class RenderScatter extends RenderBox
335 | with
336 | ContainerRenderObjectMixin,
337 | RenderBoxContainerDefaultsMixin {
338 | /// Creates a scatter render object.
339 | RenderScatter({
340 | @required ScatterDelegate delegate,
341 | Alignment alignment = Alignment.topLeft,
342 | List children,
343 | Overflow overflow = Overflow.clip,
344 | int maxChildIteration = 10000,
345 | bool fillGaps = false,
346 | }) : assert(delegate != null),
347 | assert(alignment != null),
348 | assert(maxChildIteration != null && maxChildIteration > 0),
349 | _delegate = delegate,
350 | _alignment = alignment,
351 | _overflow = overflow,
352 | _maxChildIteration = maxChildIteration,
353 | _fillGaps = fillGaps {
354 | addAll(children);
355 | }
356 |
357 | bool _hasVisualOverflow = false;
358 |
359 | /// The delegate that controls the placement of the children.
360 | ScatterDelegate get delegate => _delegate;
361 | ScatterDelegate _delegate;
362 | set delegate(ScatterDelegate value) {
363 | assert(value != null);
364 | if (_delegate == value) {
365 | return;
366 | }
367 | if (value.runtimeType != _delegate.runtimeType ||
368 | value.shouldRelayout(_delegate)) {
369 | markNeedsLayout();
370 | }
371 | _delegate = value;
372 | }
373 |
374 | // Determine how the children will be placed.
375 | Alignment get alignment => _alignment;
376 | Alignment _alignment;
377 | set alignment(Alignment value) {
378 | assert(value != null);
379 | if (_alignment == value) {
380 | return;
381 | }
382 | _alignment = value;
383 | markNeedsLayout();
384 | }
385 |
386 | /// Whether overflowing children should be clipped. See [Overflow].
387 | ///
388 | /// Some children in a stack might overflow its box. When this flag is set to
389 | /// [Overflow.clip], children cannot paint outside of the stack's box.
390 | Overflow get overflow => _overflow;
391 | Overflow _overflow;
392 | set overflow(Overflow value) {
393 | assert(value != null);
394 | if (_overflow != value) {
395 | _overflow = value;
396 | markNeedsPaint();
397 | }
398 | }
399 |
400 | /// The maximum of iterations we can do for one child.
401 | ///
402 | /// When it's impossible to place another child, a FlutterError
403 | /// is thrown when this number of iterations for one child is reached.
404 | int get maxChildIteration => _maxChildIteration;
405 | int _maxChildIteration;
406 | set maxChildIteration(int value) {
407 | assert(value != null);
408 | if (_maxChildIteration != value) {
409 | _maxChildIteration = value;
410 | markNeedsPaint();
411 | }
412 | }
413 |
414 | /// Indicates whether gaps should be filled if possible.
415 | /// Setting this value to `true` is more expansive.
416 | ///
417 | /// If `true` the [maxChildIteration] will be multiplied by
418 | /// the index of the current child.
419 | ///
420 | /// Defaults to false.
421 | bool get fillGaps => _fillGaps;
422 | bool _fillGaps;
423 | set fillGaps(bool value) {
424 | assert(value != null);
425 | if (_fillGaps != value) {
426 | _fillGaps = value;
427 | markNeedsPaint();
428 | }
429 | }
430 |
431 | @override
432 | void setupParentData(RenderBox child) {
433 | if (child.parentData is! ScatterParentData)
434 | child.parentData = ScatterParentData();
435 | }
436 |
437 | @override
438 | void performLayout() {
439 | _hasVisualOverflow = false;
440 | if (childCount == 0) {
441 | size = constraints.smallest;
442 | assert(size.isFinite);
443 | return;
444 | }
445 |
446 | Rect bounds = Rect.zero;
447 |
448 | final Size maxSize = constraints.biggest;
449 | size = Size.zero;
450 | delegate._setAvailableSize(maxSize);
451 |
452 | RenderBox child = firstChild;
453 | int index = 0;
454 | Rect previousChildRect = Rect.zero;
455 | int iteration = -1;
456 | while (child != null) {
457 | if (_fillGaps) {
458 | iteration = -1;
459 | }
460 |
461 | final ScatterParentData childParentData = child.parentData;
462 | childParentData.index = index;
463 |
464 | child.layout(constraints, parentUsesSize: true);
465 |
466 | final Size childSize = child.size;
467 | childParentData.width = childSize.width;
468 | childParentData.height = childSize.height;
469 |
470 | // Place the child following the placement strategy
471 | // until it does not overlap any previous child.
472 | final int max =
473 | _fillGaps ? _maxChildIteration * (index + 1) : _maxChildIteration;
474 | final int startIteration = iteration;
475 | do {
476 | assert(() {
477 | if (iteration - startIteration >= max) {
478 | throw FlutterError('Too much iterations for one child.\n'
479 | 'It may be impossible to place another child with this delegate '
480 | 'or consider to increase to maxChildIteration');
481 | }
482 | return true;
483 | }());
484 | final childOffset = delegate.getChildOffsetForIteration(
485 | ++iteration,
486 | ScatterContext._(
487 | childSize,
488 | previousChildRect,
489 | bounds,
490 | alignment,
491 | ),
492 | );
493 | childParentData.offset = childOffset;
494 | } while (_overlaps(childParentData));
495 |
496 | previousChildRect = childParentData.rect;
497 | bounds = bounds.expandToInclude(previousChildRect);
498 |
499 | child = childParentData.nextSibling;
500 | index++;
501 | }
502 |
503 | size = constraints
504 | .tighten(width: bounds.width, height: bounds.height)
505 | .smallest;
506 |
507 | _hasVisualOverflow =
508 | size.width < bounds.width || size.height < bounds.height;
509 |
510 | // Center the scatter.
511 | Offset boundsCenter = bounds.center;
512 | Offset scatterCenter = size.center(Offset.zero);
513 | Offset translation = scatterCenter - boundsCenter;
514 |
515 | // Move the whole scatter to the center.
516 | child = firstChild;
517 | while (child != null) {
518 | final ScatterParentData childParentData = child.parentData;
519 | childParentData.offset += translation;
520 | child = childParentData.nextSibling;
521 | }
522 | }
523 |
524 | bool _overlaps(ScatterParentData data) {
525 | final Rect rect = data.rect;
526 |
527 | RenderBox child = data.previousSibling;
528 |
529 | while (child != null) {
530 | ScatterParentData childParentData = child.parentData;
531 | if (rect.overlaps(childParentData.rect)) {
532 | return true;
533 | }
534 | child = childParentData.previousSibling;
535 | }
536 | return false;
537 | }
538 |
539 | @override
540 | double computeDistanceToActualBaseline(TextBaseline baseline) {
541 | return defaultComputeDistanceToHighestActualBaseline(baseline);
542 | }
543 |
544 | @override
545 | bool hitTestChildren(HitTestResult result, {Offset position}) {
546 | return defaultHitTestChildren(result, position: position);
547 | }
548 |
549 | @override
550 | void paint(PaintingContext context, Offset offset) {
551 | if (_hasVisualOverflow && _overflow == Overflow.clip)
552 | context.pushClipRect(
553 | needsCompositing,
554 | offset,
555 | Offset.zero & size,
556 | defaultPaint,
557 | );
558 | else
559 | defaultPaint(context, offset);
560 | }
561 | }
562 |
--------------------------------------------------------------------------------
/lib/src/widgets/scatter.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/widgets.dart';
2 | import 'package:flutter_scatter/src/rendering/scatter.dart';
3 |
4 | /// A widget that positions its children with a [ScatterDelegate]
5 | /// without overlapping them.
6 | ///
7 | /// This class is useful when you want to create a cloud of words.
8 | class Scatter extends MultiChildRenderObjectWidget {
9 | Scatter({
10 | Key key,
11 | ScatterDelegate delegate,
12 | this.alignment = Alignment.center,
13 | this.overflow = Overflow.clip,
14 | this.maxChildIteration = 10000,
15 | this.fillGaps = false,
16 | List children = const [],
17 | }) : delegate = delegate ?? ArchimedeanSpiralScatterDelegate(),
18 | super(key: key, children: children);
19 |
20 | /// The delegate that controls the layout of the [Scatter].
21 | final ScatterDelegate delegate;
22 |
23 | /// Determine how the children will be placed on
24 | /// the [ScatterDelegate.getPositionForIteration] offset.
25 | ///
26 | /// For example if [alignement] is [Alignment.center], all
27 | /// offsets given by [ScatterDelegate.getPositionForIteration]
28 | /// will be at center of a child.
29 | final Alignment alignment;
30 |
31 | /// Whether overflowing children should be clipped. See [Overflow].
32 | ///
33 | /// Some children in a scatter might overflow its box. When this flag is set to
34 | /// [Overflow.clip], children cannot paint outside of the stack's box.
35 | final Overflow overflow;
36 |
37 | /// The maximum of iterations we can do for one child.
38 | ///
39 | /// When it's impossible to place another child, a FlutterError
40 | /// is thrown when this number of iterations for one child is reached.
41 | final int maxChildIteration;
42 |
43 | /// Indicates whether gaps should be filled if possible.
44 | /// Setting this value to `true` is more expansive.
45 | ///
46 | /// If `true` the [maxChildIteration] will be multiplied by
47 | /// the index of the current child.
48 | ///
49 | /// Defaults to false.
50 | final bool fillGaps;
51 |
52 | @override
53 | RenderObject createRenderObject(BuildContext context) {
54 | return RenderScatter(
55 | delegate: delegate,
56 | alignment: alignment,
57 | overflow: overflow,
58 | maxChildIteration: maxChildIteration,
59 | fillGaps: fillGaps,
60 | );
61 | }
62 |
63 | @override
64 | void updateRenderObject(BuildContext context, RenderScatter renderObject) {
65 | renderObject
66 | ..delegate = delegate
67 | ..alignment = alignment
68 | ..overflow = overflow
69 | ..maxChildIteration = maxChildIteration
70 | ..fillGaps = fillGaps;
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: flutter_scatter
2 | description: A widget that displays a collection of dispersed and non-overlapping children
3 | version: 0.1.0
4 | author: Romain Rastel
5 | homepage: https://github.com/letsar/flutter_scatter
6 |
7 | environment:
8 | sdk: ">=2.0.0-dev.68.0 <3.0.0"
9 |
10 | dependencies:
11 | flutter:
12 | sdk: flutter
13 |
14 | dev_dependencies:
15 | flutter_test:
16 | sdk: flutter
17 |
--------------------------------------------------------------------------------
/test/flutter_scatter_test.dart:
--------------------------------------------------------------------------------
1 | import 'dart:ui';
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter_test/flutter_test.dart';
5 | import 'package:flutter_scatter/flutter_scatter.dart';
6 |
7 | class SamePositionScatterDelegate extends ScatterDelegate {
8 | @override
9 | Offset getPositionForIteration(int iteration, ScatterContext context) {
10 | return Offset.zero;
11 | }
12 | }
13 |
14 | void main() {
15 | group('AlignScatterDelegate', () {
16 | testWidgets('bottomRight', (WidgetTester tester) async {
17 | final int count = 4;
18 | final double size = 20.0;
19 | await tester.pumpWidget(
20 | Align(
21 | alignment: Alignment.topLeft,
22 | child: Scatter(
23 | delegate: AlignScatterDelegate(alignment: Alignment.bottomRight),
24 | children: List.generate(
25 | count,
26 | (i) => Container(
27 | width: size,
28 | height: size,
29 | key: ValueKey(i),
30 | color: Colors.blue,
31 | ),
32 | ),
33 | ),
34 | ),
35 | );
36 |
37 | await tester.pump();
38 |
39 | for (var i = 0; i < count; i++) {
40 | final key = ValueKey(i);
41 | expect(find.byKey(key), findsOneWidget);
42 | Offset topLeft = tester.getTopLeft(find.byKey(key));
43 | expect(topLeft, Offset(i * size, i * size));
44 | }
45 | });
46 |
47 | testWidgets('topLeft', (WidgetTester tester) async {
48 | final int count = 4;
49 | final double size = 20.0;
50 | await tester.pumpWidget(
51 | Align(
52 | alignment: Alignment.topLeft,
53 | child: Scatter(
54 | delegate: AlignScatterDelegate(alignment: Alignment.topLeft),
55 | children: List.generate(
56 | count,
57 | (i) => Container(
58 | width: size,
59 | height: size,
60 | key: ValueKey(i),
61 | color: Colors.blue,
62 | ),
63 | ),
64 | ),
65 | ),
66 | );
67 |
68 | await tester.pump();
69 |
70 | for (var i = 0; i < count; i++) {
71 | final key = ValueKey(i);
72 | expect(find.byKey(key), findsOneWidget);
73 | Offset topLeft = tester.getTopLeft(find.byKey(key));
74 | expect(topLeft, Offset((count - 1 - i) * size, (count - 1 - i) * size));
75 | }
76 | });
77 | });
78 |
79 | testWidgets('FlutterError is maxChildIteration reached',
80 | (WidgetTester tester) async {
81 | final int count = 2;
82 | final double size = 20.0;
83 | await tester.pumpWidget(
84 | Align(
85 | alignment: Alignment.topLeft,
86 | child: Scatter(
87 | maxChildIteration: 2,
88 | delegate: SamePositionScatterDelegate(),
89 | children: List.generate(
90 | count,
91 | (i) => Container(
92 | width: size,
93 | height: size,
94 | key: ValueKey(i),
95 | color: Colors.blue,
96 | ),
97 | ),
98 | ),
99 | ),
100 | );
101 |
102 | //var t = tester.takeException();
103 | await tester.pump();
104 | expect(tester.takeException(), isFlutterError);
105 | });
106 | }
107 |
--------------------------------------------------------------------------------