Deprecate Scrollbar isAlwaysShown -> thumbVisibility (#96957)

This commit is contained in:
Kate Lovett 2022-01-24 16:25:15 -06:00 committed by GitHub
parent 2f77111cf5
commit 03da339ffe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 657 additions and 74 deletions

View file

@ -45,7 +45,7 @@ class _MyStatefulWidgetState extends State<MyStatefulWidget> {
radius: const Radius.circular(34.0),
radiusWhileDragging: Radius.zero,
controller: _controllerOne,
isAlwaysShown: true,
thumbVisibility: true,
child: ListView.builder(
controller: _controllerOne,
itemCount: 120,

View file

@ -39,7 +39,7 @@ class _MyStatefulWidgetState extends State<MyStatefulWidget> {
Widget build(BuildContext context) {
return Scrollbar(
controller: _controllerOne,
isAlwaysShown: true,
thumbVisibility: true,
child: GridView.builder(
controller: _controllerOne,
itemCount: 120,

View file

@ -41,7 +41,7 @@ class ScrollMetricsDemoState extends State<ScrollMetricsDemo> {
return false;
},
child: Scrollbar(
isAlwaysShown: true,
thumbVisibility: true,
child: SizedBox(
height: windowSize,
width: double.infinity,

View file

@ -50,7 +50,7 @@ class _MyStatefulWidgetState extends State<MyStatefulWidget> {
// unique scroll controller to this scroll view prevents it
// from attaching to the PrimaryScrollController.
child: Scrollbar(
isAlwaysShown: true,
thumbVisibility: true,
controller: _firstController,
child: ListView.builder(
controller: _firstController,
@ -68,7 +68,7 @@ class _MyStatefulWidgetState extends State<MyStatefulWidget> {
// ScrollController, so it is using the
// PrimaryScrollController.
child: Scrollbar(
isAlwaysShown: true,
thumbVisibility: true,
child: ListView.builder(
itemCount: 100,
itemBuilder: (BuildContext context, int index) {

View file

@ -39,7 +39,7 @@ class _MyStatefulWidgetState extends State<MyStatefulWidget> {
Widget build(BuildContext context) {
return RawScrollbar(
controller: _controllerOne,
isAlwaysShown: true,
thumbVisibility: true,
child: GridView.builder(
controller: _controllerOne,
itemCount: 120,

View file

@ -33,7 +33,7 @@ class MyStatelessWidget extends StatelessWidget {
side: BorderSide(color: Colors.brown, width: 3.0)),
thickness: 15.0,
thumbColor: Colors.blue,
isAlwaysShown: true,
thumbVisibility: true,
child: ListView(
physics: const BouncingScrollPhysics(),
children: List<Text>.generate(

View file

@ -14,6 +14,110 @@
version: 1
transforms:
# Changes made in https://github.com/flutter/flutter/pull/96957
- title: "Migrate to 'thumbVisibility'"
date: 2022-01-20
element:
uris: [ 'cupertino.dart' ]
field: 'isAlwaysShown'
inClass: 'CupertinoScrollbar'
changes:
- kind: 'rename'
newName: 'thumbVisibility'
# Changes made in https://github.com/flutter/flutter/pull/96957
- title: "Migrate to 'thumbVisibility'"
date: 2022-01-20
element:
uris: [ 'cupertino.dart' ]
constructor: ''
inClass: 'CupertinoScrollbar'
changes:
- kind: 'renameParameter'
oldName: 'isAlwaysShown'
newName: 'thumbVisibility'
# Changes made in https://github.com/flutter/flutter/pull/96957
- title: "Migrate to 'thumbVisibility'"
date: 2022-01-20
element:
uris: [ 'material.dart' ]
field: 'isAlwaysShown'
inClass: 'Scrollbar'
changes:
- kind: 'rename'
newName: 'thumbVisibility'
# Changes made in https://github.com/flutter/flutter/pull/96957
- title: "Migrate to 'thumbVisibility'"
date: 2022-01-20
element:
uris: [ 'material.dart' ]
constructor: ''
inClass: 'Scrollbar'
changes:
- kind: 'renameParameter'
oldName: 'isAlwaysShown'
newName: 'thumbVisibility'
# Changes made in https://github.com/flutter/flutter/pull/96957
- title: "Migrate to 'thumbVisibility'"
date: 2022-01-20
element:
uris: [ 'material.dart' ]
field: 'isAlwaysShown'
inClass: 'ScrollbarThemeData'
changes:
- kind: 'rename'
newName: 'thumbVisibility'
# Changes made in https://github.com/flutter/flutter/pull/96957
- title: "Migrate to 'thumbVisibility'"
date: 2022-01-20
element:
uris: [ 'material.dart' ]
constructor: ''
inClass: 'ScrollbarThemeData'
changes:
- kind: 'renameParameter'
oldName: 'isAlwaysShown'
newName: 'thumbVisibility'
# Changes made in https://github.com/flutter/flutter/pull/96957
- title: "Migrate to 'thumbVisibility'"
date: 2022-01-20
element:
uris: [ 'material.dart' ]
method: 'copyWith'
inClass: 'ScrollbarThemeData'
changes:
- kind: 'renameParameter'
oldName: 'isAlwaysShown'
newName: 'thumbVisibility'
# Changes made in https://github.com/flutter/flutter/pull/96957
- title: "Migrate to 'thumbVisibility'"
date: 2022-01-20
element:
uris: [ 'material.dart', 'widgets.dart', 'cupertino.dart' ]
field: 'isAlwaysShown'
inClass: 'RawScrollbar'
changes:
- kind: 'rename'
newName: 'thumbVisibility'
# Changes made in https://github.com/flutter/flutter/pull/96957
- title: "Migrate to 'thumbVisibility'"
date: 2022-01-20
element:
uris: [ 'material.dart', 'widgets.dart', 'cupertino.dart' ]
constructor: ''
inClass: 'RawScrollbar'
changes:
- kind: 'renameParameter'
oldName: 'isAlwaysShown'
newName: 'thumbVisibility'
# Changes made in https://github.com/flutter/flutter/pull/96115
- title: "Migrate 'Icons.pie_chart_outlined' to 'Icons.pie_chart_outline'"
date: 2022-01-04

View file

@ -51,9 +51,10 @@ const double _kScrollbarCrossAxisMargin = 3.0;
/// {@end-tool}
///
/// {@tool dartpad}
/// When `isAlwaysShown` is true, the scrollbar thumb will remain visible without the
/// When [thumbVisibility] is true, the scrollbar thumb will remain visible without the
/// fade animation. This requires that a [ScrollController] is provided to controller,
/// or that the [PrimaryScrollController] is available.
/// or that the [PrimaryScrollController] is available. [isAlwaysShown] is
/// deprecated in favor of `thumbVisibility`.
///
/// ** See code in examples/api/lib/cupertino/scrollbar/cupertino_scrollbar.1.dart **
/// {@end-tool}
@ -74,24 +75,34 @@ class CupertinoScrollbar extends RawScrollbar {
Key? key,
required Widget child,
ScrollController? controller,
bool isAlwaysShown = false,
bool? thumbVisibility,
double thickness = defaultThickness,
this.thicknessWhileDragging = defaultThicknessWhileDragging,
Radius radius = defaultRadius,
this.radiusWhileDragging = defaultRadiusWhileDragging,
ScrollNotificationPredicate? notificationPredicate,
ScrollbarOrientation? scrollbarOrientation,
@Deprecated(
'Use thumbVisibility instead. '
'This feature was deprecated after v2.9.0-1.0.pre.',
)
bool? isAlwaysShown,
}) : assert(thickness != null),
assert(thickness < double.infinity),
assert(thicknessWhileDragging != null),
assert(thicknessWhileDragging < double.infinity),
assert(radius != null),
assert(radiusWhileDragging != null),
assert(
isAlwaysShown == null || thumbVisibility == null,
'Scrollbar thumb appearance should only be controlled with thumbVisibility, '
'isAlwaysShown is deprecated.'
),
super(
key: key,
child: child,
controller: controller,
isAlwaysShown: isAlwaysShown,
thumbVisibility: isAlwaysShown ?? thumbVisibility ?? false,
thickness: thickness,
radius: radius,
fadeDuration: _kScrollbarFadeDuration,

View file

@ -303,7 +303,7 @@ class _DropdownMenuState<T> extends State<_DropdownMenu<T>> {
child: PrimaryScrollController(
controller: widget.route.scrollController!,
child: Scrollbar(
isAlwaysShown: true,
thumbVisibility: true,
child: ListView(
padding: kMaterialListPadding,
shrinkWrap: true,

View file

@ -27,26 +27,30 @@ const Duration _kScrollbarTimeToFade = Duration(milliseconds: 600);
///
/// {@macro flutter.widgets.Scrollbar}
///
/// Dynamically changes to an iOS style scrollbar that looks like
/// [CupertinoScrollbar] on the iOS platform.
/// Dynamically changes to a [CupertinoScrollbar], an iOS style scrollbar, by
/// default on the iOS platform.
///
/// The color of the Scrollbar will change when dragged. A hover animation is
/// also triggered when used on web and desktop platforms. A scrollbar track
/// can also been drawn when triggered by a hover event, which is controlled by
/// [showTrackOnHover]. The thickness of the track and scrollbar thumb will
/// become larger when hovering, unless overridden by [hoverThickness].
/// The color of the Scrollbar thumb will change when [MaterialState.dragged],
/// or [MaterialState.hovered] on desktop and web platforms. These stateful
/// color choices can be changed using [ScrollbarThemeData.thumbColor].
///
/// A scrollbar track can been drawn when triggered by a hover event, which is
/// controlled by [showTrackOnHover]. The thickness of the track and scrollbar
/// thumb will become larger when hovering, unless overridden by
/// [hoverThickness].
///
/// {@tool dartpad}
/// This sample shows a [Scrollbar] that executes a fade animation as scrolling occurs.
/// The Scrollbar will fade into view as the user scrolls, and fade out when scrolling stops.
/// This sample shows a [Scrollbar] that executes a fade animation as scrolling
/// occurs. The Scrollbar will fade into view as the user scrolls, and fade out
/// when scrolling stops.
///
/// ** See code in examples/api/lib/material/scrollbar/scrollbar.0.dart **
/// {@end-tool}
///
/// {@tool dartpad}
/// When isAlwaysShown is true, the scrollbar thumb will remain visible without the
/// fade animation. This requires that a ScrollController is provided to controller,
/// or that the PrimaryScrollController is available.
/// When [thumbVisibility] is true, the scrollbar thumb will remain visible
/// without the fade animation. This requires that a [ScrollController] is
/// provided to controller, or that the [PrimaryScrollController] is available.
///
/// ** See code in examples/api/lib/material/scrollbar/scrollbar.1.dart **
/// {@end-tool}
@ -78,7 +82,7 @@ class Scrollbar extends StatelessWidget {
Key? key,
required this.child,
this.controller,
this.isAlwaysShown,
this.thumbVisibility,
this.trackVisibility,
this.showTrackOnHover,
this.hoverThickness,
@ -87,7 +91,17 @@ class Scrollbar extends StatelessWidget {
this.notificationPredicate,
this.interactive,
this.scrollbarOrientation,
}) : super(key: key);
@Deprecated(
'Use thumbVisibility instead. '
'This feature was deprecated after v2.9.0-1.0.pre.',
)
this.isAlwaysShown,
}) : assert(
thumbVisibility == null || isAlwaysShown == null,
'Scrollbar thumb appearance should only be controlled with thumbVisibility, '
'isAlwaysShown is deprecated.'
),
super(key: key);
/// {@macro flutter.widgets.Scrollbar.child}
final Widget child;
@ -95,7 +109,27 @@ class Scrollbar extends StatelessWidget {
/// {@macro flutter.widgets.Scrollbar.controller}
final ScrollController? controller;
/// {@macro flutter.widgets.Scrollbar.thumbVisibility}
///
/// If this property is null, then [ScrollbarThemeData.thumbVisibility] of
/// [ThemeData.scrollbarTheme] is used. If that is also null, the default value
/// is false.
///
/// If the thumb visibility is related to the scrollbar's material state,
/// use the global [ScrollbarThemeData.thumbVisibility] or override the
/// sub-tree's theme data.
///
/// Replaces deprecated [isAlwaysShown].
final bool? thumbVisibility;
/// {@macro flutter.widgets.Scrollbar.isAlwaysShown}
///
/// To show the scrollbar thumb based on a [MaterialState], use
/// [ScrollbarThemeData.thumbVisibility].
@Deprecated(
'Use thumbVisibility instead. '
'This feature was deprecated after v2.9.0-1.0.pre.',
)
final bool? isAlwaysShown;
/// Controls the track visibility.
@ -157,7 +191,7 @@ class Scrollbar extends StatelessWidget {
Widget build(BuildContext context) {
if (Theme.of(context).platform == TargetPlatform.iOS) {
return CupertinoScrollbar(
isAlwaysShown: isAlwaysShown ?? false,
thumbVisibility: isAlwaysShown ?? thumbVisibility ?? false,
thickness: thickness ?? CupertinoScrollbar.defaultThickness,
thicknessWhileDragging: thickness ?? CupertinoScrollbar.defaultThicknessWhileDragging,
radius: radius ?? CupertinoScrollbar.defaultRadius,
@ -170,7 +204,7 @@ class Scrollbar extends StatelessWidget {
}
return _MaterialScrollbar(
controller: controller,
isAlwaysShown: isAlwaysShown,
thumbVisibility: isAlwaysShown ?? thumbVisibility,
trackVisibility: trackVisibility,
showTrackOnHover: showTrackOnHover,
hoverThickness: hoverThickness,
@ -189,7 +223,7 @@ class _MaterialScrollbar extends RawScrollbar {
Key? key,
required Widget child,
ScrollController? controller,
bool? isAlwaysShown,
bool? thumbVisibility,
this.trackVisibility,
this.showTrackOnHover,
this.hoverThickness,
@ -202,7 +236,7 @@ class _MaterialScrollbar extends RawScrollbar {
key: key,
child: child,
controller: controller,
isAlwaysShown: isAlwaysShown,
thumbVisibility: thumbVisibility,
thickness: thickness,
radius: radius,
fadeDuration: _kScrollbarFadeDuration,
@ -231,7 +265,7 @@ class _MaterialScrollbarState extends RawScrollbarState<_MaterialScrollbar> {
late bool _useAndroidScrollbar;
@override
bool get showScrollbar => widget.isAlwaysShown ?? _scrollbarTheme.isAlwaysShown ?? false;
bool get showScrollbar => widget.thumbVisibility ?? _scrollbarTheme.thumbVisibility?.resolve(_states) ?? _scrollbarTheme.isAlwaysShown ?? false;
@override
bool get enableGestures => widget.interactive ?? _scrollbarTheme.interactive ?? !_useAndroidScrollbar;

View file

@ -13,15 +13,15 @@ import 'theme.dart';
/// Defines default property values for descendant [Scrollbar] widgets.
///
/// Descendant widgets obtain the current [ScrollbarThemeData] object with
/// `ScrollbarTheme.of(context)`. Instances of [ScrollbarThemeData] can be customized
/// with [ScrollbarThemeData.copyWith].
/// `ScrollbarTheme.of(context)`. Instances of [ScrollbarThemeData] can be
/// customized with [ScrollbarThemeData.copyWith].
///
/// Typically the [ScrollbarThemeData] of a [ScrollbarTheme] is specified as part of the overall
/// [Theme] with [ThemeData.scrollbarTheme].
/// Typically the [ScrollbarThemeData] of a [ScrollbarTheme] is specified as
/// part of the overall [Theme] with [ThemeData.scrollbarTheme].
///
/// All [ScrollbarThemeData] properties are `null` by default. When null, the [Scrollbar]
/// computes its own default values, typically based on the overall theme's
/// [ThemeData.colorScheme].
/// All [ScrollbarThemeData] properties are `null` by default. When null, the
/// [Scrollbar] computes its own default values, typically based on the overall
/// theme's [ThemeData.colorScheme].
///
/// See also:
///
@ -31,10 +31,10 @@ import 'theme.dart';
class ScrollbarThemeData with Diagnosticable {
/// Creates a theme that can be used for [ThemeData.scrollbarTheme].
const ScrollbarThemeData({
this.thumbVisibility,
this.thickness,
this.trackVisibility,
this.showTrackOnHover,
this.isAlwaysShown,
this.radius,
this.thumbColor,
this.trackColor,
@ -43,7 +43,22 @@ class ScrollbarThemeData with Diagnosticable {
this.mainAxisMargin,
this.minThumbLength,
this.interactive,
});
@Deprecated(
'Use thumbVisibility instead. '
'This feature was deprecated after v2.9.0-1.0.pre.',
)
this.isAlwaysShown,
}) : assert(
isAlwaysShown == null || thumbVisibility == null,
'Scrollbar thumb appearance should only be controlled with thumbVisibility, '
'isAlwaysShown is deprecated.'
);
/// Overrides the default value of [Scrollbar.thumbVisibility] in all
/// descendant [Scrollbar] widgets.
///
/// Replaces deprecated [isAlwaysShown].
final MaterialStateProperty<bool?>? thumbVisibility;
/// Overrides the default value of [Scrollbar.thickness] in all
/// descendant [Scrollbar] widgets.
@ -62,6 +77,12 @@ class ScrollbarThemeData with Diagnosticable {
/// Overrides the default value of [Scrollbar.isAlwaysShown] in all
/// descendant [Scrollbar] widgets.
///
/// Deprecated in favor of [thumbVisibility].
@Deprecated(
'Use thumbVisibility instead. '
'This feature was deprecated after v2.9.0-1.0.pre.',
)
final bool? isAlwaysShown;
/// Overrides the default value of [Scrollbar.interactive] in all
@ -126,10 +147,10 @@ class ScrollbarThemeData with Diagnosticable {
/// Creates a copy of this object with the given fields replaced with the
/// new values.
ScrollbarThemeData copyWith({
MaterialStateProperty<bool?>? thumbVisibility,
MaterialStateProperty<double?>? thickness,
MaterialStateProperty<bool?>? trackVisibility,
bool? showTrackOnHover,
bool? isAlwaysShown,
bool? interactive,
Radius? radius,
MaterialStateProperty<Color?>? thumbColor,
@ -138,8 +159,14 @@ class ScrollbarThemeData with Diagnosticable {
double? crossAxisMargin,
double? mainAxisMargin,
double? minThumbLength,
@Deprecated(
'Use thumbVisibility instead. '
'This feature was deprecated after v2.9.0-1.0.pre.',
)
bool? isAlwaysShown,
}) {
return ScrollbarThemeData(
thumbVisibility: thumbVisibility ?? this.thumbVisibility,
thickness: thickness ?? this.thickness,
trackVisibility: trackVisibility ?? this.trackVisibility,
showTrackOnHover: showTrackOnHover ?? this.showTrackOnHover,
@ -163,6 +190,7 @@ class ScrollbarThemeData with Diagnosticable {
static ScrollbarThemeData lerp(ScrollbarThemeData? a, ScrollbarThemeData? b, double t) {
assert(t != null);
return ScrollbarThemeData(
thumbVisibility: _lerpProperties<bool?>(a?.thumbVisibility, b?.thumbVisibility, t, _lerpBool),
thickness: _lerpProperties<double?>(a?.thickness, b?.thickness, t, lerpDouble),
trackVisibility: _lerpProperties<bool?>(a?.trackVisibility, b?.trackVisibility, t, _lerpBool),
showTrackOnHover: _lerpBool(a?.showTrackOnHover, b?.showTrackOnHover, t),
@ -181,6 +209,7 @@ class ScrollbarThemeData with Diagnosticable {
@override
int get hashCode {
return hashValues(
thumbVisibility,
thickness,
trackVisibility,
showTrackOnHover,
@ -203,6 +232,7 @@ class ScrollbarThemeData with Diagnosticable {
if (other.runtimeType != runtimeType)
return false;
return other is ScrollbarThemeData
&& other.thumbVisibility == thumbVisibility
&& other.thickness == thickness
&& other.trackVisibility == trackVisibility
&& other.showTrackOnHover == showTrackOnHover
@ -220,6 +250,7 @@ class ScrollbarThemeData with Diagnosticable {
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<MaterialStateProperty<bool?>>('thumbVisibility', thumbVisibility, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<double?>>('thickness', thickness, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<bool?>>('trackVisibility', trackVisibility, defaultValue: null));
properties.add(DiagnosticsProperty<bool>('showTrackOnHover', showTrackOnHover, defaultValue: null));

View file

@ -767,7 +767,7 @@ class ScrollbarPainter extends ChangeNotifier implements CustomPainter {
/// visible.
///
/// By default, the thumb will fade in and out as the child scroll view
/// scrolls. When [isAlwaysShown] is true, the scrollbar thumb will remain
/// scrolls. When [thumbVisibility] is true, the scrollbar thumb will remain
/// visible without the fade animation. This requires that the [ScrollController]
/// associated with the Scrollable widget is provided to [controller], or that
/// the [PrimaryScrollController] is being used by that Scrollable widget.
@ -843,7 +843,7 @@ class ScrollbarPainter extends ChangeNotifier implements CustomPainter {
/// {@end-tool}
///
/// {@tool dartpad}
/// When `isAlwaysShown` is true, the scrollbar thumb will remain visible without
/// When `thumbVisibility` is true, the scrollbar thumb will remain visible without
/// the fade animation. This requires that a [ScrollController] is provided to
/// `controller` for both the [RawScrollbar] and the [GridView].
/// Alternatively, the [PrimaryScrollController] can be used automatically so long
@ -868,7 +868,7 @@ class RawScrollbar extends StatefulWidget {
Key? key,
required this.child,
this.controller,
this.isAlwaysShown,
this.thumbVisibility,
this.shape,
this.radius,
this.thickness,
@ -882,8 +882,18 @@ class RawScrollbar extends StatefulWidget {
this.interactive,
this.scrollbarOrientation,
this.mainAxisMargin = 0.0,
this.crossAxisMargin = 0.0
this.crossAxisMargin = 0.0,
@Deprecated(
'Use thumbVisibility instead. '
'This feature was deprecated after v2.9.0-1.0.pre.',
)
this.isAlwaysShown,
}) : assert(child != null),
assert(
thumbVisibility == null || isAlwaysShown == null,
'Scrollbar thumb appearance should only be controlled with thumbVisibility, '
'isAlwaysShown is deprecated.'
),
assert(minThumbLength != null),
assert(minThumbLength >= 0),
assert(minOverscrollLength == null || minOverscrollLength <= minThumbLength),
@ -962,9 +972,9 @@ class RawScrollbar extends StatefulWidget {
/// {@endtemplate}
final ScrollController? controller;
/// {@template flutter.widgets.Scrollbar.isAlwaysShown}
/// Indicates that the scrollbar should be visible, even when a scroll is not
/// underway.
/// {@template flutter.widgets.Scrollbar.thumbVisibility}
/// Indicates that the scrollbar thumb should be visible, even when a scroll
/// is not underway.
///
/// When false, the scrollbar will be shown during scrolling
/// and will fade out otherwise.
@ -996,7 +1006,7 @@ class RawScrollbar extends StatefulWidget {
/// SizedBox(
/// height: 200,
/// child: Scrollbar(
/// isAlwaysShown: true,
/// thumbVisibility: true,
/// controller: _controllerOne,
/// child: ListView.builder(
/// controller: _controllerOne,
@ -1010,7 +1020,7 @@ class RawScrollbar extends StatefulWidget {
/// SizedBox(
/// height: 200,
/// child: CupertinoScrollbar(
/// isAlwaysShown: true,
/// thumbVisibility: true,
/// controller: _controllerTwo,
/// child: SingleChildScrollView(
/// controller: _controllerTwo,
@ -1036,7 +1046,95 @@ class RawScrollbar extends StatefulWidget {
/// scroll view associated with the parent [PrimaryScrollController].
/// * [PrimaryScrollController], which associates a [ScrollController] with
/// a subtree.
///
/// Replaces deprecated [isAlwaysShown].
/// {@endtemplate}
///
/// Subclass [Scrollbar] can hide and show the scrollbar thumb in response to
/// [MaterialState]s by using [ScrollbarThemeData.thumbVisibility].
final bool? thumbVisibility;
/// {@template flutter.widgets.Scrollbar.isAlwaysShown}
/// Indicates that the scrollbar thumb should be visible, even when a scroll
/// is not underway.
///
/// When false, the scrollbar will be shown during scrolling
/// and will fade out otherwise.
///
/// When true, the scrollbar will always be visible and never fade out. This
/// requires that the Scrollbar can access the [ScrollController] of the
/// associated Scrollable widget. This can either be the provided [controller],
/// or the [PrimaryScrollController] of the current context.
///
/// * When providing a controller, the same ScrollController must also be
/// provided to the associated Scrollable widget.
/// * The [PrimaryScrollController] is used by default for a [ScrollView]
/// that has not been provided a [ScrollController] and that has an
/// [Axis.vertical] [ScrollDirection]. This automatic behavior does not
/// apply to those with a ScrollDirection of Axis.horizontal. To explicitly
/// use the PrimaryScrollController, set [ScrollView.primary] to true.
///
/// Defaults to false when null.
///
/// {@tool snippet}
///
/// ```dart
/// final ScrollController _controllerOne = ScrollController();
/// final ScrollController _controllerTwo = ScrollController();
///
/// Widget build(BuildContext context) {
/// return Column(
/// children: <Widget>[
/// SizedBox(
/// height: 200,
/// child: Scrollbar(
/// thumbVisibility: true,
/// controller: _controllerOne,
/// child: ListView.builder(
/// controller: _controllerOne,
/// itemCount: 120,
/// itemBuilder: (BuildContext context, int index) {
/// return Text('item $index');
/// },
/// ),
/// ),
/// ),
/// SizedBox(
/// height: 200,
/// child: CupertinoScrollbar(
/// thumbVisibility: true,
/// controller: _controllerTwo,
/// child: SingleChildScrollView(
/// controller: _controllerTwo,
/// child: const SizedBox(
/// height: 2000,
/// width: 500,
/// child: Placeholder(),
/// ),
/// ),
/// ),
/// ),
/// ],
/// );
/// }
/// ```
/// {@end-tool}
///
/// See also:
///
/// * [RawScrollbarState.showScrollbar], an overridable getter which uses
/// this value to override the default behavior.
/// * [ScrollView.primary], which indicates whether the ScrollView is the primary
/// scroll view associated with the parent [PrimaryScrollController].
/// * [PrimaryScrollController], which associates a [ScrollController] with
/// a subtree.
///
/// This is deprecated, [thumbVisibility] should be used instead.
/// {@endtemplate}
@Deprecated(
'Use thumbVisibility instead. '
'This feature was deprecated after v2.9.0-1.0.pre.',
)
final bool? isAlwaysShown;
/// The [OutlinedBorder] of the scrollbar's thumb.
@ -1197,13 +1295,14 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv
/// Subclasses can override this getter to make its value depend on an inherited
/// theme.
///
/// Defaults to false when [RawScrollbar.isAlwaysShown] is null.
/// Defaults to false when [RawScrollbar.isAlwaysShown] or
/// [RawScrollbar.thumbVisibility] is null.
///
/// See also:
///
/// * [RawScrollbar.isAlwaysShown], which overrides the default behavior.
@protected
bool get showScrollbar => widget.isAlwaysShown ?? false;
bool get showScrollbar => widget.isAlwaysShown ?? widget.thumbVisibility ?? false;
/// Overridable getter to indicate is gestures should be enabled on the
/// scrollbar.
@ -1283,8 +1382,10 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv
: 'provided ScrollController';
String when = '';
if (showScrollbar) {
if (widget.isAlwaysShown ?? false) {
when = 'Scrollbar.isAlwaysShown is true';
} else if (widget.thumbVisibility ?? false) {
when = 'Scrollbar.thumbVisibility is true';
} else if (enableGestures) {
when = 'the scrollbar is interactive';
} else {
@ -1385,8 +1486,9 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv
@override
void didUpdateWidget(T oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.isAlwaysShown != oldWidget.isAlwaysShown) {
if (widget.isAlwaysShown == true) {
if (widget.isAlwaysShown != oldWidget.isAlwaysShown
|| widget.thumbVisibility != oldWidget.thumbVisibility) {
if (widget.isAlwaysShown == true || widget.thumbVisibility == true) {
assert(_debugScheduleCheckHasValidScrollPosition());
_fadeoutTimer?.cancel();
_fadeoutAnimationController.animateTo(1.0);

View file

@ -340,9 +340,66 @@ void main() {
await tester.pump(kScrollbarFadeDuration);
});
testWidgets(
'When isAlwaysShown is true, must pass a controller or find PrimaryScrollController',
(WidgetTester tester) async {
testWidgets('When thumbVisibility is true, must pass a controller or find PrimaryScrollController', (WidgetTester tester) async {
Widget viewWithScroll() {
return const Directionality(
textDirection: TextDirection.ltr,
child: MediaQuery(
data: MediaQueryData(),
child: CupertinoScrollbar(
thumbVisibility: true,
child: SingleChildScrollView(
child: SizedBox(
width: 4000.0,
height: 4000.0,
),
),
),
),
);
}
await tester.pumpWidget(viewWithScroll());
final AssertionError exception = tester.takeException() as AssertionError;
expect(exception, isAssertionError);
},
);
testWidgets('When thumbVisibility is true, must pass a controller or find PrimaryScrollController that is attached to a scroll view', (WidgetTester tester) async {
final ScrollController controller = ScrollController();
Widget viewWithScroll() {
return Directionality(
textDirection: TextDirection.ltr,
child: MediaQuery(
data: const MediaQueryData(),
child: CupertinoScrollbar(
controller: controller,
thumbVisibility: true,
child: const SingleChildScrollView(
child: SizedBox(
width: 4000.0,
height: 4000.0,
),
),
),
),
);
}
final FlutterExceptionHandler? handler = FlutterError.onError;
FlutterErrorDetails? error;
FlutterError.onError = (FlutterErrorDetails details) {
error = details;
};
await tester.pumpWidget(viewWithScroll());
expect(error, isNotNull);
FlutterError.onError = handler;
},
);
testWidgets('When isAlwaysShown is true, must pass a controller or find PrimaryScrollController', (WidgetTester tester) async {
Widget viewWithScroll() {
return const Directionality(
textDirection: TextDirection.ltr,
@ -367,10 +424,7 @@ void main() {
},
);
testWidgets(
'When isAlwaysShown is true, '
'must pass a controller or find PrimaryScrollController that is attached to a scroll view',
(WidgetTester tester) async {
testWidgets('When isAlwaysShown is true, must pass a controller or find PrimaryScrollController that is attached to a scroll view', (WidgetTester tester) async {
final ScrollController controller = ScrollController();
Widget viewWithScroll() {
return Directionality(
@ -404,9 +458,74 @@ void main() {
},
);
testWidgets(
'On first render with isAlwaysShown: true, the thumb shows with PrimaryScrollController',
(WidgetTester tester) async {
testWidgets('On first render with thumbVisibility: true, the thumb shows with PrimaryScrollController', (WidgetTester tester) async {
final ScrollController controller = ScrollController();
Widget viewWithScroll() {
return Directionality(
textDirection: TextDirection.ltr,
child: MediaQuery(
data: const MediaQueryData(),
child: PrimaryScrollController(
controller: controller,
child: Builder(
builder: (BuildContext context) {
return const CupertinoScrollbar(
thumbVisibility: true,
child: SingleChildScrollView(
primary: true,
child: SizedBox(
width: 4000.0,
height: 4000.0,
),
),
);
},
),
),
),
);
}
await tester.pumpWidget(viewWithScroll());
await tester.pumpAndSettle();
expect(find.byType(CupertinoScrollbar), paints..rect());
},
);
testWidgets('On first render with thumbVisibility: true, the thumb shows', (WidgetTester tester) async {
final ScrollController controller = ScrollController();
Widget viewWithScroll() {
return Directionality(
textDirection: TextDirection.ltr,
child: MediaQuery(
data: const MediaQueryData(),
child: PrimaryScrollController(
controller: controller,
child: CupertinoScrollbar(
thumbVisibility: true,
controller: controller,
child: const SingleChildScrollView(
child: SizedBox(
width: 4000.0,
height: 4000.0,
),
),
),
),
),
);
}
await tester.pumpWidget(viewWithScroll());
// The scrollbar measures its size on the first frame
// and renders starting in the second,
//
// so pumpAndSettle a frame to allow it to appear.
await tester.pumpAndSettle();
expect(find.byType(CupertinoScrollbar), paints..rrect());
});
testWidgets('On first render with isAlwaysShown: true, the thumb shows with PrimaryScrollController', (WidgetTester tester) async {
final ScrollController controller = ScrollController();
Widget viewWithScroll() {
return Directionality(
@ -501,9 +620,7 @@ void main() {
expect(find.byType(CupertinoScrollbar), isNot(paints..rect()));
});
testWidgets(
'With isAlwaysShown: true, fling a scroll. While it is still scrolling, set isAlwaysShown: false. The thumb should not fade out until the scrolling stops.',
(WidgetTester tester) async {
testWidgets('With isAlwaysShown: true, fling a scroll. While it is still scrolling, set isAlwaysShown: false. The thumb should not fade out until the scrolling stops.', (WidgetTester tester) async {
final ScrollController controller = ScrollController();
bool isAlwaysShown = true;
Widget viewWithScroll() {

View file

@ -159,6 +159,112 @@ void main() {
expect(canvas.invocations.isEmpty, isTrue);
});
testWidgets('When thumbVisibility is true, must pass a controller or find PrimaryScrollController', (WidgetTester tester) async {
Widget viewWithScroll() {
return _buildBoilerplate(
child: Theme(
data: ThemeData(),
child: const Scrollbar(
thumbVisibility: true,
child: SingleChildScrollView(
child: SizedBox(
width: 4000.0,
height: 4000.0,
),
),
),
),
);
}
await tester.pumpWidget(viewWithScroll());
final AssertionError exception = tester.takeException() as AssertionError;
expect(exception, isAssertionError);
});
testWidgets('When thumbVisibility is true, must pass a controller that is attached to a scroll view or find PrimaryScrollController', (WidgetTester tester) async {
final ScrollController controller = ScrollController();
Widget viewWithScroll() {
return _buildBoilerplate(
child: Theme(
data: ThemeData(),
child: Scrollbar(
thumbVisibility: true,
controller: controller,
child: const SingleChildScrollView(
child: SizedBox(
width: 4000.0,
height: 4000.0,
),
),
),
),
);
}
await tester.pumpWidget(viewWithScroll());
final AssertionError exception = tester.takeException() as AssertionError;
expect(exception, isAssertionError);
});
testWidgets('On first render with thumbVisibility: true, the thumb shows', (WidgetTester tester) async {
final ScrollController controller = ScrollController();
Widget viewWithScroll() {
return _buildBoilerplate(
child: Theme(
data: ThemeData(),
child: Scrollbar(
thumbVisibility: true,
controller: controller,
child: SingleChildScrollView(
controller: controller,
child: const SizedBox(
width: 4000.0,
height: 4000.0,
),
),
),
),
);
}
await tester.pumpWidget(viewWithScroll());
await tester.pumpAndSettle();
expect(find.byType(Scrollbar), paints..rect());
});
testWidgets('On first render with thumbVisibility: true, the thumb shows with PrimaryScrollController', (WidgetTester tester) async {
final ScrollController controller = ScrollController();
Widget viewWithScroll() {
return _buildBoilerplate(
child: Theme(
data: ThemeData(),
child: PrimaryScrollController(
controller: controller,
child: Builder(
builder: (BuildContext context) {
return const Scrollbar(
thumbVisibility: true,
child: SingleChildScrollView(
primary: true,
child: SizedBox(
width: 4000.0,
height: 4000.0,
),
),
);
},
),
),
),
);
}
await tester.pumpWidget(viewWithScroll());
await tester.pumpAndSettle();
expect(find.byType(Scrollbar), paints..rect());
});
testWidgets(
'When isAlwaysShown is true, must pass a controller or find PrimaryScrollController',
(WidgetTester tester) async {

View file

@ -320,7 +320,7 @@ void main() {
child: Scrollbar(
thickness: thickness,
hoverThickness: hoverThickness,
isAlwaysShown: true,
thumbVisibility: true,
showTrackOnHover: showTrackOnHover,
radius: radius,
controller: scrollController,
@ -637,7 +637,7 @@ void main() {
ScrollbarThemeData(
thickness: MaterialStateProperty.resolveWith(_getThickness),
showTrackOnHover: true,
isAlwaysShown: true,
thumbVisibility: MaterialStateProperty.resolveWith(_getThumbVisibility),
radius: const Radius.circular(3.0),
thumbColor: MaterialStateProperty.resolveWith(_getThumbColor),
trackColor: MaterialStateProperty.resolveWith(_getTrackColor),
@ -653,16 +653,16 @@ void main() {
.toList();
expect(description, <String>[
"thumbVisibility: Instance of '_MaterialStatePropertyWith<bool?>'",
"thickness: Instance of '_MaterialStatePropertyWith<double?>'",
'showTrackOnHover: true',
'isAlwaysShown: true',
'radius: Radius.circular(3.0)',
"thumbColor: Instance of '_MaterialStatePropertyWith<Color?>'",
"trackColor: Instance of '_MaterialStatePropertyWith<Color?>'",
"trackBorderColor: Instance of '_MaterialStatePropertyWith<Color?>'",
'crossAxisMargin: 3.0',
'mainAxisMargin: 6.0',
'minThumbLength: 120.0',
'minThumbLength: 120.0'
]);
// On the web, Dart doubles and ints are backed by the same kind of object because
@ -684,7 +684,7 @@ ScrollbarThemeData _scrollbarTheme({
MaterialStateProperty<double?>? thickness,
MaterialStateProperty<bool?>? trackVisibility,
bool showTrackOnHover = true,
bool isAlwaysShown = true,
MaterialStateProperty<bool?>? thumbVisibility,
Radius radius = const Radius.circular(6.0),
MaterialStateProperty<Color?>? thumbColor,
MaterialStateProperty<Color?>? trackColor,
@ -697,7 +697,7 @@ ScrollbarThemeData _scrollbarTheme({
thickness: thickness ?? MaterialStateProperty.resolveWith(_getThickness),
trackVisibility: trackVisibility,
showTrackOnHover: showTrackOnHover,
isAlwaysShown: isAlwaysShown,
thumbVisibility: thumbVisibility,
radius: radius,
thumbColor: thumbColor ?? MaterialStateProperty.resolveWith(_getThumbColor),
trackColor: trackColor ?? MaterialStateProperty.resolveWith(_getTrackColor),
@ -714,6 +714,8 @@ double? _getThickness(Set<MaterialState> states) {
return 10.0;
}
bool? _getThumbVisibility(Set<MaterialState> states) => true;
Color? _getThumbColor(Set<MaterialState> states) {
if (states.contains(MaterialState.dragged))
return Colors.red;

View file

@ -1304,6 +1304,44 @@ void main() {
);
});
testWidgets('RawScrollbar.thumbVisibility asserts that a ScrollPosition is attached', (WidgetTester tester) async {
final FlutterExceptionHandler? handler = FlutterError.onError;
FlutterErrorDetails? error;
FlutterError.onError = (FlutterErrorDetails details) {
error = details;
};
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: MediaQuery(
data: const MediaQueryData(),
child: RawScrollbar(
thumbVisibility: true,
controller: ScrollController(),
thumbColor: const Color(0x11111111),
child: const SingleChildScrollView(
child: SizedBox(
height: 1000.0,
width: 50.0,
),
),
),
),
),
);
await tester.pumpAndSettle();
expect(error, isNotNull);
final AssertionError exception = error!.exception as AssertionError;
expect(
exception.message,
contains("The Scrollbar's ScrollController has no ScrollPosition attached."),
);
FlutterError.onError = handler;
});
testWidgets('RawScrollbar.isAlwaysShown asserts that a ScrollPosition is attached', (WidgetTester tester) async {
final FlutterExceptionHandler? handler = FlutterError.onError;
FlutterErrorDetails? error;

View file

@ -220,4 +220,10 @@ void main() {
OverscrollIndicatorNotification notification = OverscrollIndicatorNotification(leading: true);
notification = OverscrollIndicatorNotification(error: '');
notification.disallowGlow();
// Changes made in https://github.com/flutter/flutter/pull/96957
CupertinoScrollbar scrollbar = CupertinoScrollbar(isAlwaysShown: true);
bool nowShowing = scrollbar.isAlwaysShown;
RawScrollbar rawScrollbar = RawScrollbar(isAlwaysShown: true);
nowShowing = rawScrollbar.isAlwaysShown;
}

View file

@ -220,4 +220,10 @@ void main() {
OverscrollIndicatorNotification notification = OverscrollIndicatorNotification(leading: true);
notification = OverscrollIndicatorNotification(error: '');
notification.disallowIndicator();
// Changes made in https://github.com/flutter/flutter/pull/96957
CupertinoScrollbar scrollbar = CupertinoScrollbar(thumbVisibility: true);
bool nowShowing = scrollbar.thumbVisibility;
RawScrollbar rawScrollbar = RawScrollbar(thumbVisibility: true);
nowShowing = rawScrollbar.thumbVisibility;
}

View file

@ -521,4 +521,13 @@ void main() {
// Changes made in https://github.com/flutter/flutter/pull/96115
Icon icon = Icons.pie_chart_outlined;
// Changes made in https://github.com/flutter/flutter/pull/96957
Scrollbar scrollbar = Scrollbar(isAlwaysShown: true);
bool nowShowing = scrollbar.isAlwaysShown;
ScrollbarThemeData scrollbarTheme = ScrollbarThemeData(isAlwaysShown: nowShowing);
scrollbarTheme.copyWith(isAlwaysShown: nowShowing);
scrollbarTheme.isAlwaysShown;
RawScrollbar rawScrollbar = RawScrollbar(isAlwaysShown: true);
nowShowing = rawScrollbar.isAlwaysShown;
}

View file

@ -494,4 +494,13 @@ void main() {
// Changes made in https://github.com/flutter/flutter/pull/96115
Icon icon = Icons.pie_chart_outline;
// Changes made in https://github.com/flutter/flutter/pull/96957
Scrollbar scrollbar = Scrollbar(thumbVisibility: true);
bool nowShowing = scrollbar.thumbVisibility;
ScrollbarThemeData scrollbarTheme = ScrollbarThemeData(thumbVisibility: nowShowing);
scrollbarTheme.copyWith(thumbVisibility: nowShowing);
scrollbarTheme.thumbVisibility;
RawScrollbar rawScrollbar = RawScrollbar(thumbVisibility: true);
nowShowing = rawScrollbar.thumbVisibility;
}

View file

@ -177,4 +177,8 @@ void main() {
final OverscrollIndicatorNotification notification = OverscrollIndicatorNotification(leading: true);
final OverscrollIndicatorNotification notification = OverscrollIndicatorNotification(error: '');
notification.disallowGlow();
// Changes made in https://github.com/flutter/flutter/pull/96957
RawScrollbar rawScrollbar = RawScrollbar(isAlwaysShown: true);
nowShowing = rawScrollbar.isAlwaysShown;
}

View file

@ -177,4 +177,8 @@ void main() {
final OverscrollIndicatorNotification notification = OverscrollIndicatorNotification(leading: true);
final OverscrollIndicatorNotification notification = OverscrollIndicatorNotification(error: '');
notification.disallowIndicator();
// Changes made in https://github.com/flutter/flutter/pull/96957
RawScrollbar rawScrollbar = RawScrollbar(thumbVisibility: true);
nowShowing = rawScrollbar.thumbVisibility;
}