@@ -75,21 +75,59 @@ class PassiveListViewWidget extends StatelessWidget {
7575 }
7676
7777 /// Sizes the list by [ViewportLayout] around [builder] .
78+ ///
79+ /// A wrapping list is sized by [WrapNodeBox] instead, and the flag given
80+ /// to [builder] says whether its runs scroll along the cross axis.
7881 Widget box (ViewportContentBuilder builder, {required bool canShrinkWrap}) =>
79- ViewportNodeBox (
80- node: node,
82+ node.properties.isWrapping
83+ ? WrapNodeBox (node: node, builder: builder)
84+ : ViewportNodeBox (
85+ node: node,
86+ scrollDirection: node.scrollDirection,
87+ canShrinkWrap: canShrinkWrap,
88+ builder: builder,
89+ );
90+
91+ /// Builds one item, placed as the editor lays it out; see [ListItemBox] .
92+ /// The item of a wrapping list keeps its own size; see [WrapItemBox] .
93+ Widget buildItem (BaseNode item, BuildContext context) {
94+ final Widget child = manager.buildWidgetByID (
95+ item.id,
96+ context,
97+ settings: settings,
98+ );
99+ if (node.properties.isWrapping) {
100+ return WrapItemBox (
101+ item: item,
81102 scrollDirection: node.scrollDirection,
82- canShrinkWrap : canShrinkWrap ,
83- builder : builder ,
103+ itemExtent : node.properties.itemExtent ,
104+ child : child ,
84105 );
106+ }
107+ return ListItemBox (
108+ item: item,
109+ scrollDirection: node.scrollDirection,
110+ itemExtent: node.properties.itemExtent,
111+ child: child,
112+ );
113+ }
85114
86- /// Builds one item, placed as the editor lays it out; see [ListItemBox] .
87- Widget buildItem (BaseNode item, BuildContext context) => ListItemBox (
88- item: item,
89- scrollDirection: node.scrollDirection,
90- itemExtent: node.properties.itemExtent,
91- child: manager.buildWidgetByID (item.id, context, settings: settings),
92- );
115+ /// The items of a wrapping list; [scrolls] comes from [WrapNodeBox] .
116+ Widget wrapped (bool scrolls, int itemCount, IndexedWidgetBuilder builder) =>
117+ WrappedItems (
118+ itemCount: itemCount,
119+ itemBuilder: builder,
120+ direction: node.scrollDirection.flutterAxis,
121+ spacing: node.properties.wrapSpacing,
122+ runSpacing: node.properties.wrapRunSpacing,
123+ alignment: node.properties.wrapAlignment.flutterWrapAlignment,
124+ padding: node.padding.flutterEdgeInsets,
125+ scrolls: scrolls,
126+ physics: node.physics.flutterScrollPhysics (false ),
127+ keyboardDismissBehavior:
128+ node.keyboardDismissBehavior.flutterKeyboardDismissBehavior,
129+ clipBehavior: node.clipsContent ? Clip .hardEdge : Clip .none,
130+ );
93131
94132 @override
95133 Widget build (BuildContext context) {
@@ -170,6 +208,23 @@ class PassiveListViewWidget extends StatelessWidget {
170208 final List <QueryDocumentSnapshot <Map >> docs = snapshot.docs;
171209 final itemCount = docs.length;
172210
211+ // A wrapping list shows the loaded page, without paging on.
212+ if (node.properties.isWrapping) {
213+ return wrapped (shrinkWrap, itemCount, (context, index) {
214+ final doc = docs[index];
215+ return IndexedItemProvider (
216+ key: ValueKey (index),
217+ item: IndexedItem (index, {'id' : doc.id, ...doc.data ()}),
218+ child: Builder (
219+ builder: (context) => KeyedSubtree (
220+ key: ValueKey (index),
221+ child: buildItem (item, context),
222+ ),
223+ ),
224+ );
225+ });
226+ }
227+
173228 // Item Count always exists.
174229 return ListViewBuilder (
175230 primary: node.primary,
@@ -294,6 +349,15 @@ class PassiveListViewWidget extends StatelessWidget {
294349 );
295350 }
296351
352+ // A wrapping list flows its items onto further lines, all of them built,
353+ // so an endless one shows nothing.
354+ if (node.properties.isWrapping) {
355+ return box (
356+ canShrinkWrap: true ,
357+ (context, scrolls) => wrapped (scrolls, itemCount ?? 0 , itemBuilder),
358+ );
359+ }
360+
297361 // An endless list with items of free extent ends after a viewport's
298362 // worth of items that take no space, so they cannot be built without
299363 // limit; see [EndlessList]. A fixed item extent is bounded by Flutter.
@@ -455,7 +519,6 @@ class ListViewItemSeparator extends StatelessWidget {
455519 }
456520}
457521
458-
459522/// The items of a stacked list: drawn on top of each other in the list's
460523/// box, each at its top center, the first item on top.
461524class StackedItems extends StatelessWidget {
@@ -493,3 +556,93 @@ class StackedItems extends StatelessWidget {
493556 ],
494557 );
495558}
559+
560+ /// The items of a wrapping list: laid out by a [Wrap] along [direction] ,
561+ /// flowing onto further lines (runs) when a line is full, each item at its
562+ /// own size.
563+ ///
564+ /// With [scrolls] , the runs scroll along the cross axis when they don't fit
565+ /// the box; otherwise the box grows with them.
566+ class WrappedItems extends StatelessWidget {
567+ /// Creates a [WrappedItems] .
568+ const WrappedItems ({
569+ super .key,
570+ required this .itemCount,
571+ required this .itemBuilder,
572+ this .direction = Axis .horizontal,
573+ this .spacing = 0 ,
574+ this .runSpacing = 0 ,
575+ this .alignment = WrapAlignment .start,
576+ this .padding = EdgeInsets .zero,
577+ this .scrolls = false ,
578+ this .physics,
579+ this .keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior .manual,
580+ this .clipBehavior = Clip .none,
581+ });
582+
583+ /// How many items.
584+ final int itemCount;
585+
586+ /// Builds an item.
587+ final IndexedWidgetBuilder itemBuilder;
588+
589+ /// The wrap direction: the list's scroll direction.
590+ final Axis direction;
591+
592+ /// The gap between two items of a line.
593+ final double spacing;
594+
595+ /// The gap between two lines.
596+ final double runSpacing;
597+
598+ /// How the items of a line are placed along it.
599+ final WrapAlignment alignment;
600+
601+ /// The list's padding, around the runs.
602+ final EdgeInsets padding;
603+
604+ /// Whether the runs scroll along the cross axis.
605+ final bool scrolls;
606+
607+ /// The physics of that scroll.
608+ final ScrollPhysics ? physics;
609+
610+ /// See [ScrollView.keyboardDismissBehavior] .
611+ final ScrollViewKeyboardDismissBehavior keyboardDismissBehavior;
612+
613+ /// Whether the runs are clipped to the box.
614+ final Clip clipBehavior;
615+
616+ @override
617+ Widget build (BuildContext context) {
618+ final Widget wrap = Wrap (
619+ direction: direction,
620+ spacing: spacing,
621+ runSpacing: runSpacing,
622+ alignment: alignment,
623+ children: [
624+ for (int index = 0 ; index < itemCount; index++ )
625+ itemBuilder (context, index),
626+ ],
627+ );
628+ if (scrolls) {
629+ return DraggableScrollConfiguration (
630+ child: SingleChildScrollView (
631+ primary: false ,
632+ scrollDirection: direction == Axis .horizontal
633+ ? Axis .vertical
634+ : Axis .horizontal,
635+ padding: padding,
636+ physics: physics,
637+ keyboardDismissBehavior: keyboardDismissBehavior,
638+ clipBehavior: clipBehavior,
639+ child: wrap,
640+ ),
641+ );
642+ }
643+ final Widget padded = Padding (padding: padding, child: wrap);
644+ return clipBehavior == Clip .none
645+ ? padded
646+ : ClipRect (clipBehavior: clipBehavior, child: padded);
647+ }
648+ }
0 commit comments