ListView
A scrollable, keyboard-navigable list of items, vertical by default or
horizontal with scrollDirection.
See also: ScrollView for one tall non-list child · DataTable for rows with named columns.
Learn more: Lists & scrolling
Import: package:fleury/fleury.dart, or package:fleury/fleury_core.dart in browser code.
ListView.builder( itemCount: rows.length, itemBuilder: (context, i, highlighted) => Text(rows[i].label),)Details
Section titled “Details”Two ways to populate the list:
ListView(children: [...])— eager. Every child widget is built upfront on each rebuild; the layout/paint pass only visits items that fit in the viewport. Best when you have a bounded set of widgets you already constructed.ListView.builder(itemCount: N, itemBuilder: (context, index, highlighted) {})— lazy. Only items currently within the viewport are mounted as element subtrees; items scroll into/out of the mounted set as the user navigates. Supports variable sizes along the scrolling axis. Best for long lists where most items are off-screen (file pickers, log viewers, completion menus).
When focused, the list handles its main-axis arrows (Up and Down, or Left and Right in a horizontal list), PageUp, PageDown, Home, End, and Enter:
- The arrows move the current item by one, PageUp and PageDown by the
number of items in view, and Home and End to the first and last item.
A move reports
onFocusedItemChangedand scrolls the item into view. - Enter or a completed click selects the current item via
onSelect. - An arrow or page key pressed on the first item toward the start, or on
the last toward the end, follows
edgeBehavior:containconsumes the key,bubblereturns it to the focus chain so an ancestorKeyBindings(e.g. one coordinating sidebar + main pane focus traversal) can react. From any other item, a page key stops at the first or last item. - A list without a current item (
selectable: false, or a controller with no cursor) scrolls instead: the arrows by one cell and PageUp and PageDown by a viewport, followingedgeBehavioronce scrolled to an end, while Home and End jump to the start and end.
itemBuilder receives (context, index, highlighted) for each visible item.
The current item remains highlighted when keyboard focus leaves the list.
The controller’s ListController.currentIndex identifies that item.
Setting it moves the cursor without selecting an item or taking focus.
All constructors apply the theme’s selection style to the current row’s text. Plain Text children need no cursor styling. Explicit child styles override that default; the builder flag is available for custom decoration.
Constructors
Section titled “Constructors”ListView()
Section titled “ListView()”Eager constructor: build all items upfront from a fixed list of widgets. Use when you have a bounded set of widgets already constructed. The current row is highlighted automatically.
| Parameter | Type | Default | Description |
|---|---|---|---|
controller: | ListController? | — | External controller. If null, the widget creates its own and disposes it on unmount. |
focusNode: | FocusNode? | — | External FocusNode. Provide one when a parent needs to drive focus (e.g. Tab cycling between sidebar and main pane). If null, the widget creates its own and disposes it on unmount. |
children: | List<Widget> | required | Pre-built widgets (eager form). |
autofocus: | bool | false | Whether to request focus on first mount. |
selectable: | bool | true | Whether the list owns a row cursor and selects rows with click or Enter. False keeps it scrollable without a row cursor. Interactive child widgets keep their own input behavior. |
edgeBehavior: | EdgeBehavior | EdgeBehavior.bubble | How main-axis arrows and wheel gestures behave at an edge. Cross-axis input can reach other controls or a surrounding scroll view. |
onSelect: | void Function(int index)? | — | Selects an item on Enter or a completed click, including repeated choices. Browsing, scrolling, and controller writes do not call this. Empty lists and lists without a cursor cannot select an item. |
onFocusedItemChanged: | void Function(int index)? | — | Reports user input moving the cursor to a different item. More |
scrollbar: | bool | false | When true, wrap the list in a Scrollbar gutter that reflects the visible item range and lets the mouse drag/click to scroll. A one-line opt-in: the bar shares this list’s controller, so there is nothing extra to wire. See Scrollbar.list. More |
scrollDirection: | Axis | Axis.vertical | Layout and scrolling axis. Items are measured at their natural extent along this axis and constrained to the viewport on the other axis. |
addRepaintBoundaries: | bool | true | Whether each item gets its own RepaintBoundary, so an update inside one item (a row’s setState, a line of streaming output) repaints only that item rather than the whole list. Items stay interactive and accessible either way. More |
onFocusedItemChanged: Programmatic controller writes and identity-preserving data updates do not call this callback.scrollbar: The gutter needs a bounded cross axis: width for a vertical list, height for a horizontal list. The list itself needs a bounded main axis.addRepaintBoundaries: Turn it off only for lists of trivially cheap items, where the per-item boundaries would cost more than the painting they save.
ListView.builder()
Section titled “ListView.builder()”Lazy constructor: build items on demand by index, mount only the
visible ones. Each item builder invocation receives a highlighted
flag for styling the current row.
| Parameter | Type | Default | Description |
|---|---|---|---|
controller: | ListController? | — | External controller. If null, the widget creates its own and disposes it on unmount. |
focusNode: | FocusNode? | — | External FocusNode. Provide one when a parent needs to drive focus (e.g. Tab cycling between sidebar and main pane). If null, the widget creates its own and disposes it on unmount. |
itemCount: | int | required | Number of items (lazy form). |
itemBuilder: | Widget Function(BuildContext, int, bool) | required | Per-index widget builder (lazy form). Invoked with (context, index, highlighted). |
itemKeyBuilder: | ListItemKeyBuilder? | — | Returns a stable key for the data item at each index. Supply it when items can move (prepends, removals, filters, reorders) so the current item, the scroll position, and the state of mounted items follow each item rather than its index. Keys must be unique within the list and have stable equality and hash codes; a duplicate key throws a StateError. More |
autofocus: | bool | false | Whether to request focus on first mount. |
selectable: | bool | true | Whether the list owns a row cursor and selects rows with click or Enter. False keeps it scrollable without a row cursor. Interactive child widgets keep their own input behavior. |
edgeBehavior: | EdgeBehavior | EdgeBehavior.bubble | How main-axis arrows and wheel gestures behave at an edge. Cross-axis input can reach other controls or a surrounding scroll view. |
onSelect: | void Function(int index)? | — | Selects an item on Enter or a completed click, including repeated choices. Browsing, scrolling, and controller writes do not call this. Empty lists and lists without a cursor cannot select an item. |
onFocusedItemChanged: | void Function(int index)? | — | Reports user input moving the cursor to a different item. More |
scrollbar: | bool | false | When true, wrap the list in a Scrollbar gutter that reflects the visible item range and lets the mouse drag/click to scroll. A one-line opt-in: the bar shares this list’s controller, so there is nothing extra to wire. See Scrollbar.list. More |
scrollDirection: | Axis | Axis.vertical | Layout and scrolling axis. Items are measured at their natural extent along this axis and constrained to the viewport on the other axis. |
addRepaintBoundaries: | bool | true | Whether each item gets its own RepaintBoundary, so an update inside one item (a row’s setState, a line of streaming output) repaints only that item rather than the whole list. Items stay interactive and accessible either way. More |
-
itemKeyBuilder: This is data identity only: it does not install a FleuryKeyon the row or create a semantic identifier. Add those at the item-widget layer when the application needs either contract.Fleury reads all item keys once on mount and whenever the parent supplies an updated ListView. Checking keys takes O(itemCount) time; unchanged ordered keys reuse the existing reverse lookup. Changed keys rebuild it in O(itemCount) time and space. Row widgets are still built and laid out only as needed.
ListView.separated()
Section titled “ListView.separated()”Lazy constructor with separators — the TUI analogue of Flutter’s
ListView.separated. separatorBuilder is called for each gap i
— the space between item i and item i + 1, so 0 <= i <= itemCount - 2 — and may return null to omit that gap’s separator
(e.g. a day divider shown only when the day actually changes).
Separators never take the cursor and hold no index of their own — the
list still addresses exactly itemCount items, and arrow / Home / End
navigation walks items only. Each follows its item along scrollDirection,
and only the item is a tap target. A separator cannot move the cursor or
select the item it trails.
| Parameter | Type | Default | Description |
|---|---|---|---|
controller: | ListController? | — | External controller. If null, the widget creates its own and disposes it on unmount. |
focusNode: | FocusNode? | — | External FocusNode. Provide one when a parent needs to drive focus (e.g. Tab cycling between sidebar and main pane). If null, the widget creates its own and disposes it on unmount. |
itemCount: | int | required | Number of items (lazy form). |
itemBuilder: | Widget Function(BuildContext, int, bool) | required | Per-index widget builder (lazy form). Invoked with (context, index, highlighted). |
separatorBuilder: | Widget? Function(BuildContext, int) | required | Per-gap separator builder (ListView.separated form). Called with (context, i) for the gap between item i and item i + 1; may return null to omit that separator. |
itemKeyBuilder: | ListItemKeyBuilder? | — | Returns a stable key for the data item at each index. Supply it when items can move (prepends, removals, filters, reorders) so the current item, the scroll position, and the state of mounted items follow each item rather than its index. Keys must be unique within the list and have stable equality and hash codes; a duplicate key throws a StateError. More |
autofocus: | bool | false | Whether to request focus on first mount. |
selectable: | bool | true | Whether the list owns a row cursor and selects rows with click or Enter. False keeps it scrollable without a row cursor. Interactive child widgets keep their own input behavior. |
edgeBehavior: | EdgeBehavior | EdgeBehavior.bubble | How main-axis arrows and wheel gestures behave at an edge. Cross-axis input can reach other controls or a surrounding scroll view. |
onSelect: | void Function(int index)? | — | Selects an item on Enter or a completed click, including repeated choices. Browsing, scrolling, and controller writes do not call this. Empty lists and lists without a cursor cannot select an item. |
onFocusedItemChanged: | void Function(int index)? | — | Reports user input moving the cursor to a different item. More |
scrollbar: | bool | false | When true, wrap the list in a Scrollbar gutter that reflects the visible item range and lets the mouse drag/click to scroll. A one-line opt-in: the bar shares this list’s controller, so there is nothing extra to wire. See Scrollbar.list. More |
scrollDirection: | Axis | Axis.vertical | Layout and scrolling axis. Items are measured at their natural extent along this axis and constrained to the viewport on the other axis. |
addRepaintBoundaries: | bool | true | Whether each item gets its own RepaintBoundary, so an update inside one item (a row’s setState, a line of streaming output) repaints only that item rather than the whole list. Items stay interactive and accessible either way. More |
Source
Section titled “Source”ListView is defined in packages/fleury/lib/src/widgets/list_view.dart.
Category: Lists & data · All widgets