Skip to content

Commit 970ba7a

Browse files
authored
[flutter_adaptive_scaffold] Add expanded and extra large breakpoints (#7300)
This aligns the material3 specs for window sizes with the official docs: https://m3.material.io/foundations/layout/applying-layout/expanded. This adds checks for height based breakpoints: https://developer.android.com/develop/ui/compose/layouts/adaptive/window-size-classes I would like to rename `smallBody` to `compactBody` to align that as well but that would be a breaking change. Let me know if you want that to happen. *List which issues are fixed by this PR. You must list at least one issue.* flutter/flutter#118932
1 parent dc7a553 commit 970ba7a

17 files changed

Lines changed: 1299 additions & 217 deletions

packages/flutter_adaptive_scaffold/CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
## 0.2.0
2+
3+
* Add breakpoints for mediumLarge and extraLarge.
4+
* Add height and orientation based breakpoint checks.
5+
* **BREAKING CHANGES**:
6+
* Removes `WidthPlatformBreakpoint`
7+
* Breakpoints can now be constructed directly with `Breakpoint`
8+
* Checks for `andUp` or `platform` can be done as parameter: `Breakpoint.small(andUp: true, platform: Breakpoint.mobile)`
9+
110
## 0.1.12
211

312
* Updates minimum supported SDK version to Flutter 3.22/Dart 3.4.

packages/flutter_adaptive_scaffold/README.md

Lines changed: 119 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,19 @@ flutter run --release
1717

1818
## AdaptiveScaffold
1919

20-
AdaptiveScaffold implements the basic visual layout structure for Material
20+
`AdaptiveScaffold` implements the basic visual layout structure for Material
2121
Design 3 that adapts to a variety of screens. It provides a preset of layout,
2222
including positions and animations, by handling macro changes in navigational
2323
elements and bodies based on the current features of the screen, namely screen
2424
width and platform. For example, the navigational elements would be a
25-
BottomNavigationBar on a small mobile device and a NavigationRail on larger
25+
`BottomNavigationBar` on a small mobile device and a `NavigationRail` on larger
2626
devices. The body is the primary screen that takes up the space left by the
2727
navigational elements. The secondaryBody acts as an option to split the space
2828
between two panes for purposes such as having a detail view. There is some
2929
automatic functionality with foldables to handle the split between panels
30-
properly. AdaptiveScaffold is much simpler to use but is not the best if you
30+
properly. `AdaptiveScaffold` is much simpler to use but is not the best if you
3131
would like high customizability. Apps that would like more refined layout and/or
32-
animation should use AdaptiveLayout.
32+
animation should use `AdaptiveLayout`.
3333

3434
### Example Usage
3535

@@ -52,10 +52,12 @@ Widget build(BuildContext context) {
5252
// An option to override the default transition duration.
5353
transitionDuration: Duration(milliseconds: _transitionDuration),
5454
// An option to override the default breakpoints used for small, medium,
55-
// and large.
56-
smallBreakpoint: const WidthPlatformBreakpoint(end: 700),
57-
mediumBreakpoint: const WidthPlatformBreakpoint(begin: 700, end: 1000),
58-
largeBreakpoint: const WidthPlatformBreakpoint(begin: 1000),
55+
// mediumLarge, large, and extraLarge.
56+
smallBreakpoint: const Breakpoint(endWidth: 700),
57+
mediumBreakpoint: const Breakpoint(beginWidth: 700, endWidth: 1000),
58+
mediumLargeBreakpoint: const Breakpoint(beginWidth: 1000, endWidth: 1200),
59+
largeBreakpoint: const Breakpoint(beginWidth: 1200, endWidth: 1600),
60+
extraLargeBreakpoint: const Breakpoint(beginWidth: 1600),
5961
useDrawer: false,
6062
selectedIndex: _selectedTab,
6163
onSelectedIndexChange: (int index) {
@@ -90,19 +92,33 @@ Widget build(BuildContext context) {
9092
label: 'Inbox',
9193
),
9294
],
93-
body: (_) => GridView.count(crossAxisCount: 2, children: children),
9495
smallBody: (_) => ListView.builder(
9596
itemCount: children.length,
9697
itemBuilder: (_, int idx) => children[idx],
9798
),
99+
body: (_) => GridView.count(crossAxisCount: 2, children: children),
100+
mediumLargeBody: (_) =>
101+
GridView.count(crossAxisCount: 3, children: children),
102+
largeBody: (_) => GridView.count(crossAxisCount: 4, children: children),
103+
extraLargeBody: (_) =>
104+
GridView.count(crossAxisCount: 5, children: children),
98105
// Define a default secondaryBody.
99-
secondaryBody: (_) => Container(
100-
color: const Color.fromARGB(255, 234, 158, 192),
101-
),
102106
// Override the default secondaryBody during the smallBreakpoint to be
103107
// empty. Must use AdaptiveScaffold.emptyBuilder to ensure it is properly
104108
// overridden.
105109
smallSecondaryBody: AdaptiveScaffold.emptyBuilder,
110+
secondaryBody: (_) => Container(
111+
color: const Color.fromARGB(255, 234, 158, 192),
112+
),
113+
mediumLargeSecondaryBody: (_) => Container(
114+
color: const Color.fromARGB(255, 234, 158, 192),
115+
),
116+
largeSecondaryBody: (_) => Container(
117+
color: const Color.fromARGB(255, 234, 158, 192),
118+
),
119+
extraLargeSecondaryBody: (_) => Container(
120+
color: const Color.fromARGB(255, 234, 158, 192),
121+
),
106122
);
107123
}
108124
```
@@ -115,16 +131,16 @@ customizability at a cost of more lines of code.
115131
### AdaptiveLayout
116132

117133
!["AdaptiveLayout's Assigned Slots Displayed on Screen"](example/demo_files/screenSlots.png)
118-
AdaptiveLayout is the top-level widget class that arranges the layout of the
134+
`AdaptiveLayout` is the top-level widget class that arranges the layout of the
119135
slots and their animation, similar to Scaffold. It takes in several LayoutSlots
120-
and returns an appropriate layout based on the diagram above. AdaptiveScaffold
121-
is built upon AdaptiveLayout internally but abstracts some of the complexity
136+
and returns an appropriate layout based on the diagram above. `AdaptiveScaffold`
137+
is built upon `AdaptiveLayout` internally but abstracts some of the complexity
122138
with presets based on the Material 3 Design specification.
123139

124140
### SlotLayout
125141

126-
SlotLayout handles the adaptivity or the changes between widgets at certain
127-
Breakpoints. It also holds the logic for animating between breakpoints. It takes
142+
`SlotLayout` handles the adaptivity or the changes between widgets at certain
143+
`Breakpoints`. It also holds the logic for animating between breakpoints. It takes
128144
SlotLayoutConfigs mapped to Breakpoints in a config and displays a widget based
129145
on that information.
130146

@@ -169,6 +185,39 @@ return AdaptiveLayout(
169185
unSelectedLabelTextStyle: navRailTheme.unselectedLabelTextStyle,
170186
),
171187
),
188+
Breakpoints.mediumLarge: SlotLayout.from(
189+
key: const Key('Primary Navigation MediumLarge'),
190+
inAnimation: AdaptiveScaffold.leftOutIn,
191+
builder: (_) => AdaptiveScaffold.standardNavigationRail(
192+
selectedIndex: selectedNavigation,
193+
onDestinationSelected: (int newIndex) {
194+
setState(() {
195+
selectedNavigation = newIndex;
196+
});
197+
},
198+
extended: true,
199+
leading: Row(
200+
mainAxisAlignment: MainAxisAlignment.spaceAround,
201+
children: <Widget>[
202+
Text(
203+
'REPLY',
204+
style: headerColor,
205+
),
206+
const Icon(Icons.menu_open)
207+
],
208+
),
209+
destinations: destinations
210+
.map((NavigationDestination destination) =>
211+
AdaptiveScaffold.toRailDestination(destination))
212+
.toList(),
213+
trailing: trailingNavRail,
214+
backgroundColor: navRailTheme.backgroundColor,
215+
selectedIconTheme: navRailTheme.selectedIconTheme,
216+
unselectedIconTheme: navRailTheme.unselectedIconTheme,
217+
selectedLabelTextStyle: navRailTheme.selectedLabelTextStyle,
218+
unSelectedLabelTextStyle: navRailTheme.unselectedLabelTextStyle,
219+
),
220+
),
172221
Breakpoints.large: SlotLayout.from(
173222
key: const Key('Primary Navigation Large'),
174223
inAnimation: AdaptiveScaffold.leftOutIn,
@@ -180,14 +229,47 @@ return AdaptiveLayout(
180229
});
181230
},
182231
extended: true,
183-
leading: const Row(
232+
leading: Row(
233+
mainAxisAlignment: MainAxisAlignment.spaceAround,
234+
children: <Widget>[
235+
Text(
236+
'REPLY',
237+
style: headerColor,
238+
),
239+
const Icon(Icons.menu_open)
240+
],
241+
),
242+
destinations: destinations
243+
.map((NavigationDestination destination) =>
244+
AdaptiveScaffold.toRailDestination(destination))
245+
.toList(),
246+
trailing: trailingNavRail,
247+
backgroundColor: navRailTheme.backgroundColor,
248+
selectedIconTheme: navRailTheme.selectedIconTheme,
249+
unselectedIconTheme: navRailTheme.unselectedIconTheme,
250+
selectedLabelTextStyle: navRailTheme.selectedLabelTextStyle,
251+
unSelectedLabelTextStyle: navRailTheme.unselectedLabelTextStyle,
252+
),
253+
),
254+
Breakpoints.extraLarge: SlotLayout.from(
255+
key: const Key('Primary Navigation ExtraLarge'),
256+
inAnimation: AdaptiveScaffold.leftOutIn,
257+
builder: (_) => AdaptiveScaffold.standardNavigationRail(
258+
selectedIndex: selectedNavigation,
259+
onDestinationSelected: (int newIndex) {
260+
setState(() {
261+
selectedNavigation = newIndex;
262+
});
263+
},
264+
extended: true,
265+
leading: Row(
184266
mainAxisAlignment: MainAxisAlignment.spaceAround,
185267
children: <Widget>[
186268
Text(
187269
'REPLY',
188-
style: TextStyle(color: Color.fromARGB(255, 255, 201, 197)),
270+
style: headerColor,
189271
),
190-
Icon(Icons.menu_open)
272+
const Icon(Icons.menu_open)
191273
],
192274
),
193275
destinations: destinations
@@ -215,11 +297,26 @@ return AdaptiveLayout(
215297
itemBuilder: (BuildContext context, int index) => children[index],
216298
),
217299
),
218-
Breakpoints.mediumAndUp: SlotLayout.from(
300+
Breakpoints.medium: SlotLayout.from(
219301
key: const Key('Body Medium'),
220302
builder: (_) =>
221303
GridView.count(crossAxisCount: 2, children: children),
222-
)
304+
),
305+
Breakpoints.mediumLarge: SlotLayout.from(
306+
key: const Key('Body MediumLarge'),
307+
builder: (_) =>
308+
GridView.count(crossAxisCount: 3, children: children),
309+
),
310+
Breakpoints.large: SlotLayout.from(
311+
key: const Key('Body Large'),
312+
builder: (_) =>
313+
GridView.count(crossAxisCount: 4, children: children),
314+
),
315+
Breakpoints.extraLarge: SlotLayout.from(
316+
key: const Key('Body ExtraLarge'),
317+
builder: (_) =>
318+
GridView.count(crossAxisCount: 5, children: children),
319+
),
223320
},
224321
),
225322
// BottomNavigation is only active in small views defined as under 600 dp

packages/flutter_adaptive_scaffold/example/lib/adaptive_layout_demo.dart

Lines changed: 89 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ class _MyHomePageState extends State<MyHomePage> {
4848
});
4949
}
5050

51+
final TextStyle headerColor =
52+
const TextStyle(color: Color.fromARGB(255, 255, 201, 197));
53+
5154
@override
5255
Widget build(BuildContext context) {
5356
final NavigationRailThemeData navRailTheme =
@@ -200,6 +203,39 @@ class _MyHomePageState extends State<MyHomePage> {
200203
unSelectedLabelTextStyle: navRailTheme.unselectedLabelTextStyle,
201204
),
202205
),
206+
Breakpoints.mediumLarge: SlotLayout.from(
207+
key: const Key('Primary Navigation MediumLarge'),
208+
inAnimation: AdaptiveScaffold.leftOutIn,
209+
builder: (_) => AdaptiveScaffold.standardNavigationRail(
210+
selectedIndex: selectedNavigation,
211+
onDestinationSelected: (int newIndex) {
212+
setState(() {
213+
selectedNavigation = newIndex;
214+
});
215+
},
216+
extended: true,
217+
leading: Row(
218+
mainAxisAlignment: MainAxisAlignment.spaceAround,
219+
children: <Widget>[
220+
Text(
221+
'REPLY',
222+
style: headerColor,
223+
),
224+
const Icon(Icons.menu_open)
225+
],
226+
),
227+
destinations: destinations
228+
.map((NavigationDestination destination) =>
229+
AdaptiveScaffold.toRailDestination(destination))
230+
.toList(),
231+
trailing: trailingNavRail,
232+
backgroundColor: navRailTheme.backgroundColor,
233+
selectedIconTheme: navRailTheme.selectedIconTheme,
234+
unselectedIconTheme: navRailTheme.unselectedIconTheme,
235+
selectedLabelTextStyle: navRailTheme.selectedLabelTextStyle,
236+
unSelectedLabelTextStyle: navRailTheme.unselectedLabelTextStyle,
237+
),
238+
),
203239
Breakpoints.large: SlotLayout.from(
204240
key: const Key('Primary Navigation Large'),
205241
inAnimation: AdaptiveScaffold.leftOutIn,
@@ -211,14 +247,47 @@ class _MyHomePageState extends State<MyHomePage> {
211247
});
212248
},
213249
extended: true,
214-
leading: const Row(
250+
leading: Row(
215251
mainAxisAlignment: MainAxisAlignment.spaceAround,
216252
children: <Widget>[
217253
Text(
218254
'REPLY',
219-
style: TextStyle(color: Color.fromARGB(255, 255, 201, 197)),
255+
style: headerColor,
220256
),
221-
Icon(Icons.menu_open)
257+
const Icon(Icons.menu_open)
258+
],
259+
),
260+
destinations: destinations
261+
.map((NavigationDestination destination) =>
262+
AdaptiveScaffold.toRailDestination(destination))
263+
.toList(),
264+
trailing: trailingNavRail,
265+
backgroundColor: navRailTheme.backgroundColor,
266+
selectedIconTheme: navRailTheme.selectedIconTheme,
267+
unselectedIconTheme: navRailTheme.unselectedIconTheme,
268+
selectedLabelTextStyle: navRailTheme.selectedLabelTextStyle,
269+
unSelectedLabelTextStyle: navRailTheme.unselectedLabelTextStyle,
270+
),
271+
),
272+
Breakpoints.extraLarge: SlotLayout.from(
273+
key: const Key('Primary Navigation ExtraLarge'),
274+
inAnimation: AdaptiveScaffold.leftOutIn,
275+
builder: (_) => AdaptiveScaffold.standardNavigationRail(
276+
selectedIndex: selectedNavigation,
277+
onDestinationSelected: (int newIndex) {
278+
setState(() {
279+
selectedNavigation = newIndex;
280+
});
281+
},
282+
extended: true,
283+
leading: Row(
284+
mainAxisAlignment: MainAxisAlignment.spaceAround,
285+
children: <Widget>[
286+
Text(
287+
'REPLY',
288+
style: headerColor,
289+
),
290+
const Icon(Icons.menu_open)
222291
],
223292
),
224293
destinations: destinations
@@ -246,11 +315,26 @@ class _MyHomePageState extends State<MyHomePage> {
246315
itemBuilder: (BuildContext context, int index) => children[index],
247316
),
248317
),
249-
Breakpoints.mediumAndUp: SlotLayout.from(
318+
Breakpoints.medium: SlotLayout.from(
250319
key: const Key('Body Medium'),
251320
builder: (_) =>
252321
GridView.count(crossAxisCount: 2, children: children),
253-
)
322+
),
323+
Breakpoints.mediumLarge: SlotLayout.from(
324+
key: const Key('Body MediumLarge'),
325+
builder: (_) =>
326+
GridView.count(crossAxisCount: 3, children: children),
327+
),
328+
Breakpoints.large: SlotLayout.from(
329+
key: const Key('Body Large'),
330+
builder: (_) =>
331+
GridView.count(crossAxisCount: 4, children: children),
332+
),
333+
Breakpoints.extraLarge: SlotLayout.from(
334+
key: const Key('Body ExtraLarge'),
335+
builder: (_) =>
336+
GridView.count(crossAxisCount: 5, children: children),
337+
),
254338
},
255339
),
256340
// BottomNavigation is only active in small views defined as under 600 dp

0 commit comments

Comments
 (0)