├── .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 | [![Pub](https://img.shields.io/pub/v/flutter_scatter.svg)](https://pub.dartlang.org/packages/flutter_scatter) 6 | [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=QTT34M25RDNL6) 7 | 8 | Can be used to create word clouds: 9 | ![Word Cloud](https://raw.githubusercontent.com/letsar/flutter_scatter/master/doc/images/word_cloud.png) 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 [![Pub](https://img.shields.io/pub/v/flutter_scatter.svg)](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 | ![Ellipse](https://raw.githubusercontent.com/letsar/flutter_scatter/master/doc/images/circle.png) 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 | ![fillGaps to false](https://raw.githubusercontent.com/letsar/flutter_scatter/master/doc/images/word_cloud_not_filled.png) 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 | ![Spirals](https://raw.githubusercontent.com/letsar/flutter_scatter/master/doc/images/spiral.png) 76 | 77 | ### Alignments 78 | 79 | * AlignScatterDelegate 80 | 81 | ![Alignments](https://raw.githubusercontent.com/letsar/flutter_scatter/master/doc/images/align.png) 82 | 83 | ### Ellipses 84 | 85 | * EllipseScatterDelegate 86 | 87 | ![Ellipses](https://raw.githubusercontent.com/letsar/flutter_scatter/master/doc/images/ellipse.png) 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 | --------------------------------------------------------------------------------