ListView.builder builds rows as they become visible, so a list can hold
thousands of items. This one holds a thousand tasks, and a ListController
created with initialIndex: 24 starts with task 25 highlighted and visible:
Choose a task: its green ✓ stays until you choose another, and
Selected reads None until you do. The browsing highlight moves
independently. Try Scroll to 500, then Go to 25. Scrolling leaves the
current item alone; Go to 25 brings it back into view. Neither selects a
task.
onFocusedItemChanged reports arrow-key or pointer navigation; onSelect
reports the choice. ListController.currentIndex remembers the current item
when focus moves to a button. Its highlight stays visible, and the demo says
Focused: outside list.
Changing currentIndex from code updates the list without firing either
interaction callback. A controller listener observes changes from any source.
Create the controller once in State and dispose it in the state’s dispose,
as _TaskBrowserState does. A ListView disposes only a controller it created
itself; it never disposes one you pass in.
Page Up / Page Down move by a page; Home / End move to the first or last
item. The wheel and scrollbar scroll without moving focus or selecting an item.
The scrollbar moves in terminal-cell steps; on a short track over many items,
the wheel and arrow keys give finer control.
ListController is a Notifier, so a NotifierBuilder or listener can follow
it, as the demo’s Current and Showing lines do. These are the members
most apps use:
Member
What it does
ListController(initialIndex: …)
Starts with that item current and scrolled into view. Without it, the list starts on its first item; null starts with no current item
currentIndex
The highlighted item. Setting it moves the highlight and scrolls it into view without selecting the item or calling onFocusedItemChanged
jumpToIndex(index)
Scrolls that item to the start of the viewport, as far as the content allows. The highlight stays put
scrollBy(cells)
Scrolls by a number of cells
jumpToEnd()
Scrolls to the end of the last item
visibleRange
The first and last visible item indices, or null before layout
atStart, atEnd
Whether the viewport shows the start or the end of the content
A log, a chat transcript, or command output grows at its end. Create the
controller with ListController(followTail: true): the list starts at the end
and keeps the newest item in view while the viewport stays at the end.
Press Append a few times; the log stays on the newest step. Then click the
log, press Home to read from the top, and append again. Moving away from
the end pauses following: isFollowing becomes false, and unseenCount
counts the entries that arrived since. Latest calls jumpToEnd(), which
returns to the end and resumes following. Scrolling back to the end yourself
does the same.
Grow last entry makes the newest entry taller. While the list is following,
it keeps the end of that entry in view, even once it is taller than the
viewport.
By default, a list tracks positions. When items move, the highlight stays at
the same index, which now shows a different item. If items can be inserted,
removed, or reordered, give ListView.builder an itemKeyBuilder that returns
each item’s stable identity, such as a database ID:
Press Reverse order. The highlight stays on Build the prototype as it
moves from the second row to the third, and currentIndex follows it. Keys
also keep the viewport anchored on the same items, and each mounted row keeps
its widget state as its item moves. Each key must be unique within the list.
ListView.separated accepts itemKeyBuilder too.
Set scrollDirection: Axis.horizontal to arrange list items from left to right.
Use ← / → to browse, then Enter or click to select. The scrollbar runs
along the bottom edge:
ScrollView accepts the same property for wide content. This report keeps each
line intact. Try Home / End to reach its first and last columns:
Both support sideways trackpad gestures, Shift + wheel, and dragging the
scrollbar. A regular vertical wheel can still scroll a surrounding vertical
pane. Page Up / Page Down move along the chosen axis.
Controllers work in either direction: ScrollController.offset counts cells
from the start, and ListController.currentIndex identifies the current item.
Give a horizontal list a bounded width; with a scrollbar, also bound its
height, as these examples do.
Use DataTable to compare items across named columns, such as a task’s name
and status. It builds visible text cells on demand and keeps the header in
place as you scroll.
Try ↑ / ↓ and Page Down to browse, then click or press Enter to
select a row:
For spreadsheet-style interaction, the DataTable examples
show selecting and copying cell ranges.
Scroll these eight numbered lines. The counter and TOP / BOTTOM markers
show exactly where the four-row viewport is.
With Bubble (leave pane), press ↓ at BOTTOM: focus moves to Next.
Choose Contain (stay in pane) from Edge behavior, Tab back into the pane,
and try again. Now ↓ stays in the pane; Tab still leaves it.
EdgeBehavior.bubble lets an unused arrow move focus onward.
EdgeBehavior.contain keeps it in the pane. Lists have the same option.
ScrollView lays out its whole child. Give it a bounded height with SizedBox
or Expanded; keep large collections in ListView.builder.
Scroll Recent to the end, then keep scrolling: the outer All notes pane
moves and reveals older months. Enable Keep scrolling in Recent and try
again. EdgeBehavior.contain keeps wheel input in the inner pane at its edge.
EdgeBehavior.bubble is the default: an ancestor can use wheel input the inner
pane cannot consume. Containment is useful when scrolling a small pane should
leave the surrounding view in place.
The sources and tests above are the files behind the live demos. The list
demos live in
website/examples/lib/lists
and their tests in
website/examples/test/lists.
To run those tests and the nested scrolling one, use this command from the root
of a clone of the Fleury repository: