Default `PrimaryScrollController` on Desktop
Summary
#The PrimaryScrollController
API has been updated to no longer automatically
attach to vertical ScrollView
s on desktop platforms.
Context
#Prior to this change, ScrollView.primary
would default to true if a
ScrollView
had an Axis.vertical
scroll direction and a ScrollController
had not already been provided. This allowed for common UI patterns, like the
scroll-to-top function on iOS to work out of the box for Flutter apps.
On desktop however, this default would often cause the following assertion error:
ScrollController attached to multiple ScrollViews.
While it is common for a mobile application to display one ScrollView
at a time,
desktop UI patterns are more likely to display multiple ScrollView
s
side-by-side. The prior implementation of PrimaryScrollController
conflicted
with this pattern, resulting in an often unhelpful error message. To remedy this,
the PrimaryScrollController
has been updated with additional parameters as
well as better error messaging across multiple widgets that depend on it.
Description of change
#The previous implementation of ScrollView
resulted in primary
being true by
default for all vertical ScrollView
s that did not already have a
ScrollController
, on all platforms. This default behavior was not always clear,
particularly because it is separate from the PrimaryScrollController
itself.
// Previously, this ListView would always result in primary being true,
// and attached to the PrimaryScrollController on all platforms.
Scaffold(
body: ListView.builder(
itemBuilder: (BuildContext context, int index) {
return Text('Item $index');
}
),
);
The implementation changes ScrollView.primary
to be nullable, with the fallback
decision-making being relocated to the PrimaryScrollController
.
When primary
is null, and no ScrollController
has been provided, the ScrollView
will look up the PrimaryScrollController
and instead call shouldInherit
to
determine if the given ScrollView
should use the PrimaryScrollController
.
The new members of the PrimaryScrollController
class,
automaticallyInheritForPlatforms
and scrollDirection
, are evaluated in
shouldInherit
, allowing users clarity and control over the
PrimaryScrollController
's behavior.
By default, backwards compatibility is maintained for mobile platforms.
PrimaryScrollController.shouldInherit
returns true for vertical
ScrollView
s. On desktop, this returns false by default.
// Only on mobile platforms will this attach to the PrimaryScrollController by
// default.
Scaffold(
body: ListView.builder(
itemBuilder: (BuildContext context, int index) {
return Text('Item $index');
}
),
);
To change the default, users can set ScrollView.primary
true or false to
explicitly manage the PrimaryScrollController
for an individual ScrollView
.
For behavior across multiple ScrollView
s, the PrimaryScrollController
is now
configurable by setting the specific platform, as well as the scroll direction
that is preferred for inheritance.
Widgets that use the PrimaryScrollController
, such as NestedScrollView
,
Scrollbar
, and DropdownMenuButton
will experience no change to existing
functionality. Features like the iOS scroll-to-top will also continue to work as
expected without any migration.
ScrollAction
s, and ScrollIntent
s on desktop are the only classes affected by
this change, requiring migration. By default, the PrimaryScrollController
is
used to execute fallback keyboard scrolling Shortcuts
if the current Focus
is
contained within a Scrollable
. Since displaying more than one ScrollView
side-by-side is common on desktop platforms, it isn't possible for
Flutter to decide "Which ScrollView
should be primary in this view and receive
the keyboard scroll action?"
If more than one ScrollView
was present previous to this change, the same
assertion (ScrollController attached to multiple ScrollViews.
) would be thrown.
Now, on desktop platforms, users need to specify primary: true
to
designate which ScrollView
is the fallback to receive unhandled keyboard
Shortcuts
.
Migration guide
#Code before migration:
// These side-by-side ListViews would throw errors from Scrollbars and
// ScrollActions previously due to the PrimaryScrollController.
Scaffold(
body: LayoutBuilder(
builder: (context, constraints) {
return Row(
children: [
SizedBox(
height: constraints.maxHeight,
width: constraints.maxWidth / 2,
child: ListView.builder(
itemBuilder: (BuildContext context, int index) {
return Text('List 1 - Item $index');
}
),
),
SizedBox(
height: constraints.maxHeight,
width: constraints.maxWidth / 2,
child: ListView.builder(
itemBuilder: (BuildContext context, int index) {
return Text('List 2 - Item $index');
}
),
),
]
);
},
),
);
Code after migration:
// These side-by-side ListViews will no longer throw errors, but for
// default ScrollActions, one will need to be designated as primary.
Scaffold(
body: LayoutBuilder(
builder: (context, constraints) {
return Row(
children: [
SizedBox(
height: constraints.maxHeight,
width: constraints.maxWidth / 2,
child: ListView.builder(
// This ScrollView will use the PrimaryScrollController
primary: true,
itemBuilder: (BuildContext context, int index) {
return Text('List 1 - Item $index');
}
),
),
SizedBox(
height: constraints.maxHeight,
width: constraints.maxWidth / 2,
child: ListView.builder(
itemBuilder: (BuildContext context, int index) {
return Text('List 2 - Item $index');
}
),
),
]
);
},
),
);
Timeline
#Landed in version: 3.3.0-0.0.pre
In stable release: 3.3
References
#API documentation:
Design document:
Relevant issues:
Relevant PRs:
除非另有说明,本文档之所提及适用于 Flutter 的最新稳定版本,本页面最后更新时间: 2024-05-14。 查看文档源码 或者 为本页面内容提出建议。