Default Scrollbars on Desktop
Summary
ScrollBehavior
s now automatically apply Scrollbar
s to
scrolling widgets on desktop platforms - Mac, Windows and Linux.
Context
Prior to this change, Scrollbar
s were applied to scrolling widgets
manually by the developer across all platforms. This did not match
developer expectations when executing Flutter applications on desktop platforms.
Now, the inherited ScrollBehavior
applies a Scrollbar
automatically
to most scrolling widgets. This is similar to how GlowingOverscrollIndicator
is created by ScrollBehavior
. The few widgets that are exempt from this
behavior are listed below.
To provide better management and control of this feature, ScrollBehavior
has also been updated. The buildViewportChrome
method, which applied
a GlowingOverscrollIndicator
, has been deprecated. Instead, ScrollBehavior
now supports individual methods for decorating the viewport, buildScrollbar
and buildOverscrollIndicator
. These methods can be overridden to control
what is built around the scrollable.
Further more, ScrollBehavior
subclasses MaterialScrollBehavior
and
CupertinoScrollBehavior
have been made public, allowing developers to extend
and build upon the other existing ScrollBehavior
s in the framework. These
subclasses were previously private.
Description of change
The previous approach called on developers to create their own Scrollbar
s on
all platforms. In some use cases, a ScrollController
would need to be provided
to the Scrollbar
and the scrollable widget.
final ScrollController controller = ScrollController();
Scrollbar(
controller: controller,
child: ListView.builder(
controller: controller,
itemBuilder: (BuildContext context, int index) {
return Text('Item $index');
}
)
);
The ScrollBehavior
now applies the Scrollbar
automatically
when executing on desktop, and handles providing the ScrollController
to the Scrollbar
for you.
final ScrollController controller = ScrollController();
ListView.builder(
controller: controller,
itemBuilder: (BuildContext context, int index) {
return Text('Item $index');
}
);
Some widgets in the framework are exempt from this automatic Scrollbar
application. They
are:
-
EditableText
, whenmaxLines
is 1. ListWheelScrollView
PageView
NestedScrollView
Since these widgets manually override the inherited ScrollBehavior
to remove Scrollbar
s, all of these widgets now have a scrollBehavior
parameter so that one can be provided to use instead of the override.
This change did not cause any test failures, crashes, or error messages
in the course of development, but it may result in two Scrollbar
s
being rendered in your application if you are manually adding Scrollbar
s
on desktop platforms.
If you are seeing this in your application, there are several ways to control and configure this feature.
-
Remove the manually applied
Scrollbar
s in your application when running on desktop. -
Extend
ScrollBehavior
,MaterialScrollBehavior
, orCupertinoScrollBehavior
to modify the default behavior.- With your own
ScrollBehavior
, you can apply it app-wide by settingMaterialApp.scrollBehavior
orCupertinoApp.scrollBehavior
. - Or, if you wish to only apply it to specific widgets, add a
ScrollConfiguration
above the widget in question with your customScrollBehavior
.
- With your own
Your scrollable widgets then inherits this and reflects this behavior.
-
Instead of creating your own
ScrollBehavior
, another option for changing the default behavior is to copy the existingScrollBehavior
, and toggle the desired feature.- Create a
ScrollConfiguration
in your widget tree, and provide a modified copy of the existingScrollBehavior
in the current context usingcopyWith
.
- Create a
Migration guide
Scrollbar
s on desktop
Removing manual Code before migration:
final ScrollController controller = ScrollController();
Scrollbar(
controller: controller,
child: ListView.builder(
controller: controller,
itemBuilder: (BuildContext context, int index) {
return Text('Item $index');
}
)
);
Code after migration:
final ScrollController controller = ScrollController();
final Widget child = ListView.builder(
controller: controller,
itemBuilder: (BuildContext context, int index) {
return Text('Item $index');
}
);
// Only manually add a `Scrollbar` when not on desktop platforms.
// Or, see other migrations for changing `ScrollBehavior`.
switch (currentPlatform) {
case TargetPlatform.linux:
case TargetPlatform.macOS:
case TargetPlatform.windows:
return child;
case TargetPlatform.android:
case TargetPlatform.fuchsia:
case TargetPlatform.iOS:
return Scrollbar(
controller: controller,
child: child;
);
}
ScrollBehavior
for your application
Setting a custom Code before migration:
// MaterialApps previously had a private ScrollBehavior.
MaterialApp(
// ...
);
Code after migration:
// MaterialApps previously had a private ScrollBehavior.
// This is available to extend now.
class MyCustomScrollBehavior extends MaterialScrollBehavior {
// Override behavior methods like buildOverscrollIndicator and buildScrollbar
}
// ScrollBehavior can now be configured for an entire application.
MaterialApp(
scrollBehavior: MyCustomScrollBehavior(),
// ...
);
ScrollBehavior
for a specific widget
Setting a custom Code before migration:
final ScrollController controller = ScrollController();
ListView.builder(
controller: controller,
itemBuilder: (BuildContext context, int index) {
return Text('Item $index');
}
);
Code after migration:
// MaterialApps previously had a private ScrollBehavior.
// This is available to extend now.
class MyCustomScrollBehavior extends MaterialScrollBehavior {
// Override behavior methods like buildOverscrollIndicator and buildScrollbar
}
// ScrollBehavior can be set for a specific widget.
final ScrollController controller = ScrollController();
ScrollConfiguration(
behavior: MyCustomScrollBehavior(),
child: ListView.builder(
controller: controller,
itemBuilder: (BuildContext context, int index) {
return Text('Item $index');
}
),
);
ScrollBehavior
Copy and modify existing Code before migration:
final ScrollController controller = ScrollController();
ListView.builder(
controller: controller,
itemBuilder: (BuildContext context, int index) {
return Text('Item $index');
}
);
Code after migration:
// ScrollBehavior can be copied and adjusted.
final ScrollController controller = ScrollController();
ScrollConfiguration(
behavior: ScrollConfiguration.of(context).copyWith(scrollbars: false),
child: ListView.builder(
controller: controller,
itemBuilder: (BuildContext context, int index) {
return Text('Item $index');
}
),
);
Timeline
Landed in version: 2.2.0-10.0.pre
In stable release: 2.2.0
References
API documentation:
ScrollConfiguration
ScrollBehavior
MaterialScrollBehavior
CupertinoScrollBehavior
Scrollbar
CupertinoScrollbar
Relevant issues:
Relevant PRs: