Repository: madewithfelt/flutter_layout_grid
Branch: main
Commit: e05d48cb919f
Files: 75
Total size: 410.3 KB
Directory structure:
gitextract_6dhq12ku/
├── .github/
│ └── workflows/
│ └── flutter-test.yml
├── .gitignore
├── .metadata
├── .vscode/
│ └── launch.json
├── CHANGELOG.md
├── LICENSE
├── README.md
├── analysis_options.yaml
├── example/
│ ├── .gitignore
│ ├── .metadata
│ ├── analysis_options.yaml
│ ├── lib/
│ │ ├── app_layout.dart
│ │ ├── basic.dart
│ │ ├── drag_and_drop_example.dart
│ │ ├── flutter_layout_grid_example.dart
│ │ ├── overlapping_items.dart
│ │ ├── periodic_table.dart
│ │ ├── periodic_table_data.json
│ │ ├── responsive_app_layout.dart
│ │ ├── scrabble.dart
│ │ ├── semantic_ordering.dart
│ │ └── support/
│ │ └── inner_shadow.dart
│ ├── linux/
│ │ ├── .gitignore
│ │ ├── CMakeLists.txt
│ │ ├── flutter/
│ │ │ ├── CMakeLists.txt
│ │ │ ├── generated_plugin_registrant.cc
│ │ │ ├── generated_plugin_registrant.h
│ │ │ └── generated_plugins.cmake
│ │ ├── main.cc
│ │ ├── my_application.cc
│ │ └── my_application.h
│ ├── macos/
│ │ ├── .gitignore
│ │ ├── Flutter/
│ │ │ ├── Flutter-Debug.xcconfig
│ │ │ └── Flutter-Release.xcconfig
│ │ ├── Podfile
│ │ ├── Runner/
│ │ │ ├── AppDelegate.swift
│ │ │ ├── Assets.xcassets/
│ │ │ │ └── AppIcon.appiconset/
│ │ │ │ └── Contents.json
│ │ │ ├── Base.lproj/
│ │ │ │ └── MainMenu.xib
│ │ │ ├── Configs/
│ │ │ │ ├── AppInfo.xcconfig
│ │ │ │ ├── Debug.xcconfig
│ │ │ │ ├── Release.xcconfig
│ │ │ │ └── Warnings.xcconfig
│ │ │ ├── DebugProfile.entitlements
│ │ │ ├── Info.plist
│ │ │ ├── MainFlutterWindow.swift
│ │ │ └── Release.entitlements
│ │ ├── Runner.xcodeproj/
│ │ │ ├── project.pbxproj
│ │ │ ├── project.xcworkspace/
│ │ │ │ ├── contents.xcworkspacedata
│ │ │ │ └── xcshareddata/
│ │ │ │ └── IDEWorkspaceChecks.plist
│ │ │ └── xcshareddata/
│ │ │ └── xcschemes/
│ │ │ └── Runner.xcscheme
│ │ └── Runner.xcworkspace/
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata/
│ │ └── IDEWorkspaceChecks.plist
│ └── pubspec.yaml
├── lib/
│ ├── flutter_layout_grid.dart
│ └── src/
│ ├── foundation/
│ │ ├── box.dart
│ │ ├── collections.dart
│ │ └── placement.dart
│ ├── helpers.dart
│ ├── rendering/
│ │ ├── debug.dart
│ │ ├── layout_grid.dart
│ │ ├── placement.dart
│ │ └── track_size.dart
│ └── widgets/
│ ├── layout_grid.dart
│ └── placement.dart
├── pubspec.yaml
├── test/
│ ├── accessibility_test.dart
│ ├── areas_parsing_test.dart
│ ├── ensure_debug_flags_off_test.dart
│ ├── golden_test.dart
│ ├── grid_sizing_test.dart
│ ├── invalidation_test.dart
│ ├── placement_test.dart
│ ├── test_helpers.dart
│ └── track_size_test.dart
└── tool/
└── ci_test.sh
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/flutter-test.yml
================================================
name: test
on:
push:
pull_request:
branches:
- main
schedule:
# Run the quality job at 4am every day
- cron: '0 4 * * *'
jobs:
test:
# This job will run on ubuntu virtual machine
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
# Setup the flutter environment.
- uses: subosito/flutter-action@v2
with:
channel: 'stable'
# Get flutter dependencies.
- run: flutter pub get
# Check for any formatting issues in the code.
- run: dart format --set-exit-if-changed .
# Statically analyze the Dart code for any errors.
- run: flutter analyze .
# Do the same for examples
- run: flutter analyze .
working-directory: ./example
# Run widget tests for our flutter project.
- run: flutter test
================================================
FILE: .gitignore
================================================
# Libraries shouldn't have their locks committed
pubspec.lock
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
**/generated_plugin_registrant.dart
.packages
.pub-cache/
.pub/
build/
flutter_*.png
linked_*.ds
unlinked.ds
unlinked_spec.ds
test/failures
# Android related
**/android/**/gradle-wrapper.jar
**/android/.gradle
**/android/captures/
**/android/gradlew
**/android/gradlew.bat
**/android/local.properties
**/android/**/GeneratedPluginRegistrant.java
# iOS/XCode related
**/ios/**/*.mode1v3
**/ios/**/*.mode2v3
**/ios/**/*.moved-aside
**/ios/**/*.pbxuser
**/ios/**/*.perspectivev3
**/ios/**/*sync/
**/ios/**/.sconsign.dblite
**/ios/**/.tags*
**/ios/**/.vagrant/
**/ios/**/DerivedData/
**/ios/**/Icon?
**/ios/**/Pods/
**/ios/**/.symlinks/
**/ios/**/profile
**/ios/**/xcuserdata
**/ios/.generated/
**/ios/Flutter/App.framework
**/ios/Flutter/Flutter.framework
**/ios/Flutter/Flutter.podspec
**/ios/Flutter/Generated.xcconfig
**/ios/Flutter/app.flx
**/ios/Flutter/app.zip
**/ios/Flutter/flutter_assets/
**/ios/Flutter/flutter_export_environment.sh
**/ios/ServiceDefinitions.json
**/ios/Runner/GeneratedPluginRegistrant.*
# macOS
**/macos/Flutter/GeneratedPluginRegistrant.swift
# Exceptions to above rules.
!**/ios/**/default.mode1v3
!**/ios/**/default.mode2v3
!**/ios/**/default.pbxuser
!**/ios/**/default.perspectivev3
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
.vs/
================================================
FILE: .metadata
================================================
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: e3742b6e410aa5e9ea34b60dfbdd9a923c5a1fe8
channel: master
project_type: library
================================================
FILE: .vscode/launch.json
================================================
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Periodic Table",
"program": "example/lib/periodic_table.dart",
"type": "dart",
"request": "launch",
},
{
"name": "Periodic Table (Profile)",
"program": "example/lib/periodic_table.dart",
"type": "dart",
"flutterMode": "profile",
"request": "launch",
},
{
"name": "Piet",
"program": "example/lib/flutter_layout_grid.dart",
"type": "dart",
"request": "launch",
},
{
"name": "Basic",
"program": "example/lib/basic.dart",
"request": "launch",
"type": "dart"
},
{
"name": "Profile Basic",
"program": "example/lib/basic.dart",
"flutterMode": "profile",
"request": "launch",
"type": "dart"
}
]
}
================================================
FILE: CHANGELOG.md
================================================
## [2.0.8]
* Fix deprecated `withOpacity()` calls in example app to resolve CI build failures
## [2.0.7]
* Update tests to work with breaking `test` framework changes
## [2.0.6]
* Perform a pass through the latest analysis warnings
* Upgrade dependencies
## [2.0.5]
* Replace deprecated EnumProperty with DiagnosticsProperty
(https://github.com/shyndman/flutter_layout_grid/pull/95), courtesy of
[@Kristijan505](https://github.com/Kristijan505)
## [2.0.4]
* Fix https://github.com/shyndman/flutter_layout_grid/issues/91
## [2.0.3]
* Correct the lower bound of the Flutter dependency to reflect API usage
## [2.0.2]
* Replace the usage of the deprecated `hashValues` with `Object.hash`
## [2.0.1]
* Fix grid sizing when gaps are involved (thanks to
[@dsyrstad](https://github.com/dsyrstad))
## [2.0.0]
* Improve implicit track sizing behavior for multi-line text (thanks to
[@klondikedragon](https://github.com/klondikedragon))
* Upgrade flutter_lints to 2.0.0, and fix newly found issues
## [1.0.6]
* Remove outdated messaging from README. Flutter v1.14.0 is ancient at this
point.
## [1.0.5]
* Migrate project to use flutter_lints, courtesy of @domesticmouse
## [1.0.4]
* Emit a more helpful assertion message when an areas string's row or column
count doesn't match rowSizes.length or columnSizes.length
* Fix a couple analysis warnings
## [1.0.3]
* Format Dart files (seems like the formatter has changed recently)
## [1.0.2]
* Improve a class comment
## [1.0.1]
* Update README to point to latest version
## [1.0.0]
Although there's more I want to add, the library is solid enough. It's time to
mark this thing as 1.0.
## [1.0.0-nullsafety.6]
* Make RenderLayoutGrid.lastGridSizing a public field
## [1.0.0-nullsafety.5]
* Reorganize example project
## [1.0.0-nullsafety.4]
* Improve performance of periodic table example
## [1.0.0-nullsafety.3]
* Update Scrabble screenshot to follow game rules (middle square must be
occupied)
## [1.0.0-nullsafety.2]
* Add hashCode to TrackSize subclasses
## [1.0.0-nullsafety.1]
* Fix screenshots for pub.dev
## [1.0.0-nullsafety.0]
* Full support for null-safety
* Replacement of templateColumnSizes and templateRowSizes with columnSizes and
rowSizes
## [0.11.0]
* Tons of bug fixes in track sizing
* Documentation overhaul
* Performance improvements (should now lay out far less frequently)
* Helpers and extension methods for row/column sizing
* New Scrabble example
* More tests
Sorry, but I had to break semver with this release. Check out
`1.0.0-nullsafety.0` for the null-safe version.
## [0.10.5]
* Remove the use of extension methods
## [0.10.4]
* Massive overhaul in layout algorithm, fixing a number of serious issues
* Debug painting support, where tracks and gaps are drawn differently
* Child overflow indicators
* Debug printing, behind a flag
* Cool new periodic table example
Technically, some of these changes are breaking from an API perspective, but
I think that it's unlikely that people run into them. I originally wanted to
publish under a new minor version, but `pub publish` is giving me issues because
of my nullsafe prerelease.
## [0.10.3]
* Mention nullsafety release in pubspec
* Format code using latest formatter
* Fix a lint
## [0.10.2]
* Add support for negative row/column gaps (thanks @daohoangson!)
## [0.10.1]
* Graduate 0.10.1-dev.0 to the release version now that Flutter 1.17 is out
## [0.10.1-dev.0]
* Invalidate placement in more situations
## [0.10.0-dev.2]
* Correct Flutter version dependency in pubspec
## [0.10.0-dev.1]
* Make AutoPlacement class a little more enum-like, by adding a toString() that
resembles Dart enums and a static .values field
## [0.10.0-dev.0]
* Added support for Flutter v1.14.0+
## [0.9.4]
* Invalidate placement in more situations
## [0.9.3]
* Correct Flutter dependency in pubspec
## [0.9.2]
* Make AutoPlacement class a little more enum-like, by adding a toString() that
resembles Dart enums and a static .values field
## [0.9.1]
* Mention the prerelease version supporting Flutter v1.14.0+ in the README
## [0.9.0]
* Reverted support for Flutter v1.14.0+, because it won't be stable for awhile.
Flutter v1.14.0+ support is published as 0.10.0-dev.0
## [0.8.0]
* Added support for Flutter v1.14.0+
## [0.7.0]
* Add extension method support for grid item placement —
Widget.withGridPlacement
## [0.6.3]
* Fix broken badge links in README
## [0.6.2]
* Fix several bugs in the examples
* Add intrinsic-size computing functions. I don't know if they're right yet,
but it's a start.
## [0.6.1]
* Size grid minimally if an infinite constraint is provided
## [0.6.0]
* Supply grid items with loose constraints, not tight
## [0.5.3]
* README tweak
## [0.5.2]
* Add a license (MIT)
## [0.5.1]
* Dependency version fix
## [0.5.0]
* First version. See the README.
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2019 Scott Hyndman
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# Flutter Layout Grid
[](https://pub.dev/packages/flutter_layout_grid)
[](https://github.com/shyndman/flutter_layout_grid/actions?query=workflow%3Atest)
A powerful grid layout system for Flutter, optimized for complex user interface
design.
_Click images to see their code_
---
✨Featuring:✨
- 📐 Fixed, flexible, and content-sized rows and columns
([docs](#sizing-of-columns-and-rows))
- 👇 Precise control over placement of items, including the ability to span
rows, columns, and overlap items
([docs](#positioning-child-widgets-in-the-layoutgrid))
- 💬 Named grid areas for descriptive positioning of children
([docs](#naming-areas-of-the-grid))
- 🦾 A configurable automatic grid item placement algorithm, capable of sparse
and dense packing across rows and columns ([docs](#automatic-child-placement))
- 🔚 Right-to-left support, driven by ambient `Directionality` or configuration
- ♿ Accessibility considerations (**this is your responsibility** as a frontend
developer, so please read [docs](#accessibility-and-placement) and learn
related technologies)
- 🩳 Extension methods and helper functions for descriptive, and short, layout
code
- 🐛 Debugging aids, including widget property listings in
[DevTools](https://flutter.dev/docs/development/tools/devtools/overview),
Debug Painting, and visual indication of child overflow
Inspired by (and largely based on), the excellent [CSS Grid
Layout](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout) spec.
## Getting Started
All the terminology used in this library is shared with the CSS Grid Layout
spec. If youʼre unfamiliar, I recommend taking a look at [MDNʼs glossary of grid
terms](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout#Glossary_entries).
For inclusion in your pubspec, see
[pub.dev](https://pub.dev/packages/flutter_layout_grid/install), or copy
the code below:
```yaml
dependencies:
flutter_layout_grid: ^2.0.0
```
## Example
#### Visual:
#### Code:
```dart
import 'package:flutter_layout_grid/flutter_layout_grid.dart';
class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
color: background,
child: LayoutGrid(
// ASCII-art named areas 🔥
areas: '''
header header header
nav content aside
nav content .
footer footer footer
''',
// Concise track sizing extension methods 🔥
columnSizes: [152.px, 1.fr, 152.px],
rowSizes: [
112.px,
auto,
1.fr,
64.px,
],
// Column and row gaps! 🔥
columnGap: 12,
rowGap: 12,
// Handy grid placement extension methods on Widget 🔥
children: [
Header().inGridArea('header'),
Navigation().inGridArea('nav'),
Content().inGridArea('content'),
Aside().inGridArea('aside'),
Footer().inGridArea('footer'),
],
),
);
}
}
```
This example is available at
[`example/app_layout.dart`](https://github.com/shyndman/flutter_layout_grid/tree/main/example/lib/app_layout.dart).
For a similar example that includes responsive behavior, check out
[`example/responsive_app_layout.dart`](https://github.com/shyndman/flutter_layout_grid/tree/main/example/lib/responsive_app_layout.dart).
## Sizing of Columns and Rows
The sizes of the gridʼs columns and rows are set using
`LayoutGrid.columnSizes` and `LayoutGrid.rowSizes`.
Hereʼs what a 4⨉3 grid might look like (4 columns, 3 rows):
```dart
LayoutGrid(
columnSizes: [4.5.fr, 100.px, auto, 1.fr],
rowSizes: [
auto,
100.px,
1.fr,
],
)
```
Each element of `columnSizes` and `rowSizes` represents the function used to
size a column or row (collectively known as **"track sizes"**).
There are currently three way to size rows and columns:
| Class Name | Description | Usage |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------- |
| `FixedTrackSize` | Occupies a specific number of pixels on an axis | `FixedTrackSize(64)`
`fixed(64)`
`64.px` |
| `FlexibleSizeTrack` | Fills remaining space after the initial layout has completed | `FlexibleTrackSize(1.5)`
`flexible(1.5)`
`1.5.fr` |
| `IntrinsicContentTrackSize` | Sized to contain its itemsʼ contents. Will also expand to fill available space, once `FlexibleTrackSize` tracks have been given the opportunity. | `IntrinsicContentTrackSize()`
`intrinsic()`
`auto` |
Technically, you can also define your own, but probably shouldnʼt as the API
will likely be evolving as I tackle
([#25](https://github.com/shyndman/flutter_layout_grid/issues/25))
([`minmax()`](https://developer.mozilla.org/en-US/docs/Web/CSS/minmax)
support).
## Naming areas of the grid
A gridʼs rows and columns can be sliced into areas — rectangular regions
containing one or more grid cells. These areas can be named (_optionally_), and
used to place gridʼs children. The areas are named using an ASCII-art string
provided to the `areas` parameter.
```dart
LayoutGrid(
areas: '''
header header
nav content
footer footer
''',
// ...
)
```
> Note: We use the same format as CSSʼs
> [`grid-template-areas`](https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-areas),
> except we use a multiline string.
If an `areas` argument has been provided to a grid, you must specify the same
number of sizes using `columnSizes` and `rowSizes` elements. For the example
above:
```dart
LayoutGrid(
areas: '''
header header
nav content
footer footer
''',
// 2 columns, 3 rows, just like the areas string
columnSizes: [
auto, // contributes width to [nav, header, footer]
1.fr, // contributes width to [content, header, footer]
],
rowSizes: [
96.px, // contributes height to [header]
1.fr, // contributes height to [nav, content]
72.px, // contributes height to [footer]
],
children: [
// ...
],
)
```
Grid children can be assigned to named areas using the `NamedAreaGridPlacement`
widget. For more information, see [assigning the child to a named
area](#child-placement-in-named-areas).
## Positioning child widgets in the `LayoutGrid`
Once you have a grid, you have to tell its `children` which rows and columns
they should occupy. There are three ways of doing this:
- [Specifying row and column indexes](#child-placement-by-row-and-column-indexes)
- [Assigning the child to a named area](#child-placement-in-named-areas)
- [Using automatic placement](#automatic-child-placement)
### Child placement by row and column indexes
A gridʼs child can be instructed to occupy a specific set of columns and rows
by using the `GridPlacement` widget.
For example, letʼs say you had a 4⨉3 grid, and you wanted a widget to be
positioned from column 1–4 and row 0–2:
```dart
LayoutGrid(
columnSizes: [1.fr, 1.fr, 1.fr, 1.fr],
rowSizes: [
1.fr,
1.fr,
1.fr,
],
children: [
GridPlacement(
columnStart: 1,
columnSpan: 3,
rowStart: 0,
rowSpan: 2,
child: MyWidget(),
),
// Alternatively, an extension method on Widget is available
MyWidget().withGridPlacement(
columnStart: 1,
columnSpan: 3,
rowStart: 0,
rowSpan: 2,
),
],
)
```
`GridPlacement` also has a super power — all of its parameters are optional.
If, for example, you do not specify a `rowStart`, the [automatic placement
algorithm](#automatic-child-placement) will attempt to place the child in the
first vacant spot that it can find.
### Child placement in named areas
If your grid has [named areas](#naming-areas-of-the-grid) defined, you can
place children in those areas using the `NamedAreaGridPlacement` widget. For
example:
```dart
LayoutGrid(
areas: '''
red red blue
red red blue
. . blue
''',
// Note that the number of columns and rows matches the grid above (3x3)
columnSizes: [64.px, 64.px, 64.px],
rowSizes: [
64.px,
64.px,
64.px,
],
children: [
// Using NamedAreaGridPlacement constructor
NamedAreaGridPlacement(
areaName: 'red',
child: Container(color: Colors.red),
),
// Alternatively, an extension method on Widget is available
Container(color: Colors.red).inGridArea('red'),
],
)
```
**NOTE:** If a `NamedAreaGridPlacement` references a named area that doesnʼt
exist, it will not be displayed in the grid. This can be helpful when switching
between responsive layouts.
### Automatic child placement
Grid children can be placed into rows and columns automatically based on partial
or non-existent placement information.
The algorithm responsible for automatic placement has several modes, selected
through the `LayoutGrid.autoPlacement` parameter. The behavior of these modes
are identical to those supported by CSSʼs grid, described [described
here](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout/Auto-placement_in_CSS_Grid_Layout).
#### When no placement information is provided
If a child is provided to the grid without being wrapped in a `GridPlacement` or
`NamedAreaGridPlacement`, it will be allotted a single cell (1⨉1), and placed
into the first vacant cell in the grid.
#### When partial placement information is provided
All of the `GridPlacement` widgetʼs parameters are optional. By specifying
additional positioning or spanning information with
`columnStart`/`columnSpan`/`rowStart`/`rowSpan` parameters, more
constraints are fed into the placement algorithm.
For example, if `columnStart` is provided, but not `rowStart`, the placement
algorithm will walk across the gridʼs rows until it finds a vacant area, in the
specified column, that accommodates the child.
[Read more about CSSʼs grid placement
algorithm](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout/Auto-placement_in_CSS_Grid_Layout)
### Accessibility and Placement
Take note that the meaning you convey visually through placement may not be
clear when presented by assitive technologies, as Flutter defaults to exposing
information in source order.
In situations where your semantic (visual) ordering differs from ordering in the
source, the ordering can be configured via the `Semantics` widgetʼs
[`sortKey`](https://api.flutter.dev/flutter/semantics/SemanticsSortKey-class.html)
parameter.
For an example of this in practice, see
[example/semantic_ordering.dart](https://github.com/shyndman/flutter_layout_grid/tree/main/example/lib/semantic_ordering.dart).
Automatic semantic ordering is currently being explored in
[#50](https://github.com/shyndman/flutter_layout_grid/issues/50).
## Differences from CSS Grid Layout
Things in CSS Grid Layout that are not supported:
- Negative row/column starts/ends. In CSS, these values refer to positions
relative to the end of a gridʼs axis. Handy, but weʼre not there yet.
([#5](https://github.com/shyndman/flutter_layout_grid/issues/5))
- Any cells outside of the explicit grid. If an item is placed outside of the
area defined by your template rows/columns, we will throw an error. Support
for automatic addition of rows and columns to accommodate out of bound items
is being considered.
([#7](https://github.com/shyndman/flutter_layout_grid/issues/7))
- minmax(), percentages, aspect ratios track sizing
([#25](https://github.com/shyndman/flutter_layout_grid/issues/25))
Differences:
- In `flutter_layout_grid`, flexible tracks do not account for their contentʼs
base sizes as they do in CSS. Itʼs expensive to measure, and I opted for
speed.
- Flexible tracks whose flex factors sum to < 1
## Roadmap
- [x] Tests! (we now have a decent suite going)
- [x] Named template areas, for friendlier item placement
- [ ] Improved track sizing, including minimum/maximums and aspect ratios
- [ ] The ability to specify row and column gaps at specific line locations via
a delegate
- [ ] Implicit grid support (automatic growth along an axis as children are
added)
- [x] Performance improvements, as soon as I can get this profiler running(!!!)
================================================
FILE: analysis_options.yaml
================================================
include: package:flutter_lints/flutter.yaml
analyzer:
errors:
missing_required_param: error
unawaited_futures: warning
unused_import: error
unused_local_variable: warning
================================================
FILE: example/.gitignore
================================================
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
/build/
# Web related
lib/generated_plugin_registrant.dart
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release
================================================
FILE: example/.metadata
================================================
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: 097d3313d8e2c7f901932d63e537c1acefb87800
channel: stable
project_type: app
================================================
FILE: example/analysis_options.yaml
================================================
include: package:flutter_lints/flutter.yaml
================================================
FILE: example/lib/app_layout.dart
================================================
import 'package:flutter/material.dart';
import 'package:flutter_layout_grid/flutter_layout_grid.dart';
void main() {
runApp(const DesktopLayoutApp());
}
class DesktopLayoutApp extends StatelessWidget {
const DesktopLayoutApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return WidgetsApp(
color: Colors.white,
debugShowCheckedModeBanner: false,
builder: (context, child) {
return const DesktopLayout();
});
}
}
class DesktopLayout extends StatelessWidget {
const DesktopLayout({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
color: Colors.grey[400],
child: LayoutGrid(
areas: '''
header header header
nav content aside
nav content .
footer footer footer
''',
// A number of extension methods are provided for concise track sizing
columnSizes: [152.px, 1.fr, 152.px],
rowSizes: [
112.px,
auto,
1.fr,
64.px,
],
children: [
const Header().inGridArea('header'),
const Navigation().inGridArea('nav'),
const Content().inGridArea('content'),
const Aside().inGridArea('aside'),
const Footer().inGridArea('footer'),
],
),
);
}
}
class Header extends StatelessWidget {
const Header({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) => Container(color: Colors.red);
}
class Navigation extends StatelessWidget {
const Navigation({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) => Container(color: Colors.purple);
}
class Content extends StatelessWidget {
const Content({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) => Container(color: Colors.grey[300]);
}
class Aside extends StatelessWidget {
const Aside({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) =>
Container(color: Colors.grey[600], width: 184);
}
class Footer extends StatelessWidget {
const Footer({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) => Container(color: Colors.deepPurple);
}
================================================
FILE: example/lib/basic.dart
================================================
import 'package:flutter/material.dart';
import 'package:flutter_layout_grid/flutter_layout_grid.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Layout Grid Desktop Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
body: Container(
color: Colors.white,
child: const LayoutGridExample(),
),
),
);
}
}
class LayoutGridExample extends StatelessWidget {
const LayoutGridExample({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return SizedBox(
width: double.infinity,
height: double.infinity,
child: LayoutGrid(
columnGap: 12,
rowGap: 12,
columnSizes: [1.fr, 1.fr, 1.fr, 0.75.fr],
rowSizes: [
32.px,
32.px,
32.px,
],
children: [
GridPlacement(
rowStart: 0,
columnStart: 0,
columnSpan: 4,
child: Container(
color: Colors.blue,
padding: const EdgeInsets.all(8),
),
),
GridPlacement(
rowStart: 1,
columnStart: 0,
columnSpan: 3,
child: Container(
color: Colors.blue,
padding: const EdgeInsets.all(8),
),
),
GridPlacement(
rowStart: 1,
columnStart: 3,
child: Container(
color: Colors.blue,
padding: const EdgeInsets.all(8),
),
),
GridPlacement(
rowStart: 2,
columnStart: 0,
child: Container(
color: Colors.blue,
padding: const EdgeInsets.all(8),
),
),
GridPlacement(
rowStart: 2,
columnStart: 1,
child: Container(
color: Colors.blue,
padding: const EdgeInsets.all(8),
),
),
GridPlacement(
rowStart: 2,
columnStart: 2,
child: Container(
color: Colors.blue,
padding: const EdgeInsets.all(8),
),
),
GridPlacement(
rowStart: 2,
columnStart: 3,
child: Container(
color: Colors.blue,
padding: const EdgeInsets.all(8),
),
),
],
),
);
}
}
================================================
FILE: example/lib/drag_and_drop_example.dart
================================================
// ignore_for_file: use_key_in_widget_constructors
import 'package:flutter/material.dart';
import 'package:flutter_layout_grid/flutter_layout_grid.dart';
void main() {
runApp(DragAndDropApp());
}
const cellSize = 32.0;
const columnCount = 16;
const rowCount = 16;
class DragAndDropExample extends StatefulWidget {
@override
State createState() => _DragAndDropExampleState();
}
class _DragAndDropExampleState extends State {
/// The [Draggable] and [DragTarget] need to be associated with some type of
/// data (through their type argument, and `void` doesn't cut it). We keep it
/// simple and use a key, since we don't actually need to communicate anything
/// about the dragged data.
Key draggableKey = UniqueKey();
/// Current position of the [DraggableGridItem].
GridPosition draggablePosition = GridPosition(0, 0);
void gridItemMoved(GridPosition position) {
setState(() {
draggablePosition = position;
});
}
@override
Widget build(BuildContext context) {
return LayoutGrid(
columnGap: 0,
rowGap: 0,
columnSizes: repeat(columnCount, [cellSize.px]),
rowSizes: repeat(rowCount, [cellSize.px]),
children: [
// Fill the grid with a `DragTarget` per cell
for (int i = 0; i < columnCount; i++)
for (int j = 0; j < rowCount; j++)
Cell(
column: i,
row: j,
cellBecameOccupied: gridItemMoved,
).withGridPlacement(columnStart: i, rowStart: j),
// And a single Draggable, positioned according to the
// `draggablePosition` field.
DraggableGridItem(
key: draggableKey,
).withGridPlacement(
columnStart: draggablePosition.x,
rowStart: draggablePosition.y,
)
],
);
}
}
/// A square that can be dragged between grid cells.
class DraggableGridItem extends StatelessWidget {
const DraggableGridItem({
required Key key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final square = Container(
margin: const EdgeInsets.all(1),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Colors.blue[300]!, Colors.blue[700]!],
),
),
);
return Draggable(
data: key,
feedback: Opacity(
opacity: 0.6,
child: Transform.scale(
scale: 1.2,
// SizedBox is required here, because the feedback widget isn't bound
// by a cell and wants to be zero-sized.
child: SizedBox(
width: cellSize,
height: cellSize,
child: square,
),
),
),
// Fade a bit for style
childWhenDragging: Opacity(
opacity: 0.25,
child: square,
),
child: square,
);
}
}
/// Acts as a position that can be occupied by the [DraggableGridItem] widget.
class Cell extends StatefulWidget {
const Cell({
Key? key,
required this.column,
required this.row,
required this.cellBecameOccupied,
}) : super(key: key);
final int column;
final int row;
final DragTargetAccept cellBecameOccupied;
@override
State createState() => _CellState();
}
class _CellState extends State| {
bool isDragHovering = false;
@override
Widget build(BuildContext context) {
return DragTarget(
onAcceptWithDetails: (_) {
setState(() => isDragHovering = false);
widget.cellBecameOccupied(GridPosition(widget.column, widget.row));
},
onMove: (details) => setState(() => isDragHovering = true),
onLeave: (details) => setState(() => isDragHovering = false),
builder: (context, candidateData, rejectedData) {
return Container(
margin: const EdgeInsets.all(1),
decoration: BoxDecoration(
border: isDragHovering
? Border.all(
color: Colors.purple[400]!,
width: 2,
)
: Border.all(
color: Colors.grey[400]!,
),
),
);
},
);
}
}
class GridPosition {
GridPosition(this.x, this.y);
final int x;
final int y;
}
class DragAndDropApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Drag and Drop Example',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
body: Container(
color: Colors.white,
child: Center(
child: DragAndDropExample(),
),
),
),
);
}
}
================================================
FILE: example/lib/flutter_layout_grid_example.dart
================================================
import 'package:flutter/material.dart';
import 'package:flutter_layout_grid/flutter_layout_grid.dart';
void main() {
runApp(const PietNamedAreasApp());
}
const cellRed = Color(0xffc73232);
const cellMustard = Color(0xffd7aa22);
const cellGrey = Color(0xffcfd4e0);
const cellBlue = Color(0xff1553be);
const background = Color(0xff242830);
class PietPainting extends StatelessWidget {
const PietPainting({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
color: background,
child: LayoutGrid(
columnGap: 12,
rowGap: 12,
areas: '''
r R B B B
r R Y Y Y
y R Y Y Y
y R g b yy
''',
// A number of extension methods are provided for concise track sizing
columnSizes: [1.0.fr, 3.5.fr, 1.3.fr, 1.3.fr, 1.3.fr],
rowSizes: [
1.0.fr,
0.3.fr,
1.5.fr,
1.2.fr,
],
children: [
// Column 1
gridArea('r').containing(Container(color: cellRed)),
gridArea('y').containing(Container(color: cellMustard)),
// Column 2
gridArea('R').containing(Container(color: cellRed)),
// Column 3
gridArea('B').containing(Container(color: cellBlue)),
gridArea('Y').containing(Container(color: cellMustard)),
gridArea('g').containing(Container(color: cellGrey)),
// Column 4
gridArea('b').containing(Container(color: cellBlue)),
// Column 5
gridArea('yy').containing(Container(color: cellMustard)),
],
),
);
}
}
class PietNamedAreasApp extends StatelessWidget {
const PietNamedAreasApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return WidgetsApp(
title: 'Layout Grid Desktop Example',
debugShowCheckedModeBanner: false,
color: Colors.white,
builder: (context, child) => const PietPainting(),
);
}
}
================================================
FILE: example/lib/overlapping_items.dart
================================================
import 'package:flutter/material.dart';
import 'package:flutter_layout_grid/flutter_layout_grid.dart';
void main() {
runApp(const OverlappingItemsApp());
}
class OverlappingItemsApp extends StatelessWidget {
const OverlappingItemsApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return WidgetsApp(
color: Colors.white,
debugShowCheckedModeBanner: false,
builder: (context, child) {
return const OverlappingItems();
});
}
}
class OverlappingItems extends StatelessWidget {
const OverlappingItems({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(32.0),
child: LayoutGrid(
columnSizes: repeat(7, [auto]),
rowSizes: repeat(7, [auto]),
children: [
// Children are drawn in order, making this the background.
ColoredBox(
color: Colors.grey[400]!,
margin: const EdgeInsets.all(24.0),
).withGridPlacement(
columnStart: 0,
columnSpan: 7,
rowStart: 0,
rowSpan: 7,
),
ColoredBox(color: Colors.blue[400]!).withGridPlacement(
columnStart: 0,
columnSpan: 3,
rowStart: 0,
rowSpan: 3,
),
ColoredBox(color: Colors.yellow[400]!).withGridPlacement(
columnStart: 2,
columnSpan: 3,
rowStart: 2,
rowSpan: 3,
),
ColoredBox(color: Colors.red[300]!).withGridPlacement(
columnStart: 4,
columnSpan: 3,
rowStart: 4,
rowSpan: 3,
),
],
),
);
}
}
class ColoredBox extends StatelessWidget {
const ColoredBox({
Key? key,
required this.color,
this.margin = EdgeInsets.zero,
}) : super(key: key);
final Color color;
final EdgeInsets margin;
@override
Widget build(BuildContext context) {
return Container(
margin: margin,
decoration: BoxDecoration(
color: color,
),
);
}
}
================================================
FILE: example/lib/periodic_table.dart
================================================
// periodic_table_json.json courtesy of
// https://github.com/Bowserinator/Periodic-Table-JSON
//
// Based on the work by Mike Golus
// https://www.csscodelab.com/html-css-only-periodic-table-of-elements/
import 'dart:convert';
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_layout_grid/flutter_layout_grid.dart';
void main() {
runApp(const PeriodicTableApp());
}
class PeriodicTableApp extends StatelessWidget {
const PeriodicTableApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return DecoratedBox(
decoration: const BoxDecoration(
color: Color(0xff101318),
),
child: WidgetsApp(
title: 'Periodic Table',
color: Colors.white,
debugShowCheckedModeBanner: false,
builder: (_, __) {
return LayoutBuilder(builder: (_, constraints) {
_viewportSize = constraints.biggest;
// PeriodicTableWidget has many AtomicElementWidget widgets
// as part of its descendant widget tree, and they use global
// variable _viewportSize during build, so it's not safe to use
// const. If it were const, then this widget tree would only be
// constructed once and would not adapt as the viewport size
// changed.
// ignore: prefer_const_constructors
return SingleChildScrollView(child: PeriodicTableWidget());
});
},
),
);
}
}
class PeriodicTableWidget extends StatefulWidget {
const PeriodicTableWidget({Key? key}) : super(key: key);
@override
State createState() => _PeriodicTableWidgetState();
}
class _PeriodicTableWidgetState extends State {
late Future tableFuture;
@override
void initState() {
super.initState();
tableFuture = loadPeriodicTable();
}
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 1.5.vw, vertical: 1.vw),
child: FutureBuilder(
future: tableFuture,
builder: (_, AsyncSnapshot snapshot) {
return snapshot.hasData
? _buildGrid(snapshot.data!)
: const SizedBox();
},
),
);
}
Widget _buildGrid(PeriodicTable table) {
return LayoutGrid(
gridFit: GridFit.loose,
columnSizes: repeat(table.numColumns, [1.fr]),
rowSizes: repeat(table.numRows, [auto]),
columnGap: 0.4.vw,
rowGap: 0.4.vw,
children: [
for (final e in table.elements)
AspectRatio(
aspectRatio: 40.1 / 42.4,
child: AtomicElementWidget(
key: ValueKey(e.symbol),
element: e,
),
).withGridPlacement(columnStart: e.x, rowStart: e.y),
],
);
}
}
const categoryColorMapping = {
AtomicElementCategory.actinide: Color(0xffc686cc),
AtomicElementCategory.alkaliMetal: Color(0xffecbe59),
AtomicElementCategory.alkalineEarthMetal: Color(0xffdee955),
AtomicElementCategory.lanthanide: Color(0xffec77a3),
AtomicElementCategory.metalloid: Color(0xff3aefb6),
AtomicElementCategory.nobleGas: Color(0xff759fff),
AtomicElementCategory.otherNonmetal: Color(0xff52ee61),
AtomicElementCategory.postTransitionMetal: Color(0xff4cddf3),
AtomicElementCategory.transitionMetal: Color(0xfffd8572),
AtomicElementCategory.unknown: Color(0xffcccccc),
};
class AtomicElementWidget extends StatelessWidget {
const AtomicElementWidget({Key? key, required this.element})
: super(key: key);
final AtomicElement element;
@override
Widget build(BuildContext context) {
final elementColor = categoryColorMapping[element.category]!;
final elementTextStyle = TextStyle(
color: elementColor,
shadows: [
Shadow(color: elementColor, offset: Offset.zero, blurRadius: 0.3.vw),
],
);
return Container(
decoration: BoxDecoration(
border: Border.all(
width: 0.2.vw,
color: elementColor,
),
),
// Some viewport sizes give us slight overflows, which can be attributed
// to rounding errors. So we use a stack and allow overflow on the bottom
// edge.
child: Stack(
clipBehavior: Clip.hardEdge,
children: [
Positioned.fill(
bottom: null,
child: _buildElementDetails(elementTextStyle),
),
],
),
);
}
Column _buildElementDetails(TextStyle elementTextStyle) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Padding(
padding: EdgeInsets.fromLTRB(0.3.vw, 0.15.vw, 0, 0),
child: Text(
element.number.toString(),
style: elementTextStyle.copyWith(fontSize: 0.5.vw),
textAlign: TextAlign.left,
),
),
Text(
element.symbol,
style: elementTextStyle.copyWith(fontSize: 1.9.vw),
textAlign: TextAlign.center,
softWrap: false,
),
Text(
element.name,
style: elementTextStyle.copyWith(fontSize: 0.65.vw),
textAlign: TextAlign.center,
softWrap: false,
),
Padding(
padding: EdgeInsets.fromLTRB(0.0, 0.2.vw, 0.0, 0.3.vw),
child: Text(
element.formattedMass,
style: elementTextStyle.copyWith(fontSize: 0.5.vw),
textAlign: TextAlign.center,
),
),
],
);
}
}
Future loadPeriodicTable() async {
final elementsJson = const JsonCodec().decode(await rootBundle
.loadString('lib/periodic_table_data.json'))['elements'] as List;
return PeriodicTable(elementsJson
.cast | |