update the scrollbar that support always show the track even not on hover (#90178)
diff --git a/packages/flutter/lib/src/material/scrollbar.dart b/packages/flutter/lib/src/material/scrollbar.dart
index 8cdc51f..b56fa1a 100644
--- a/packages/flutter/lib/src/material/scrollbar.dart
+++ b/packages/flutter/lib/src/material/scrollbar.dart
@@ -77,6 +77,7 @@
required this.child,
this.controller,
this.isAlwaysShown,
+ this.trackVisibility,
this.showTrackOnHover,
this.hoverThickness,
this.thickness,
@@ -95,11 +96,26 @@
/// {@macro flutter.widgets.Scrollbar.isAlwaysShown}
final bool? isAlwaysShown;
+ /// Controls the track visibility.
+ ///
+ /// If this property is null, then [ScrollbarThemeData.trackVisibility] of
+ /// [ThemeData.scrollbarTheme] is used. If that is also null, the default value
+ /// is false.
+ ///
+ /// If the track visibility is related to the scrollbar's material state,
+ /// use the global [ScrollbarThemeData.trackVisibility] or override the
+ /// sub-tree's theme data.
+ ///
+ /// [showTrackOnHover] can be replaced by this and will be deprecated.
+ final bool? trackVisibility;
+
/// Controls if the track will show on hover and remain, including during drag.
///
/// If this property is null, then [ScrollbarThemeData.showTrackOnHover] of
/// [ThemeData.scrollbarTheme] is used. If that is also null, the default value
/// is false.
+ ///
+ /// This will be deprecated, and [trackVisibility] is recommended.
final bool? showTrackOnHover;
/// The thickness of the scrollbar when a hover state is active and
@@ -153,6 +169,7 @@
return _MaterialScrollbar(
controller: controller,
isAlwaysShown: isAlwaysShown,
+ trackVisibility: trackVisibility,
showTrackOnHover: showTrackOnHover,
hoverThickness: hoverThickness,
thickness: thickness,
@@ -171,6 +188,7 @@
required Widget child,
ScrollController? controller,
bool? isAlwaysShown,
+ this.trackVisibility,
this.showTrackOnHover,
this.hoverThickness,
double? thickness,
@@ -193,6 +211,7 @@
scrollbarOrientation: scrollbarOrientation,
);
+ final bool? trackVisibility;
final bool? showTrackOnHover;
final double? hoverThickness;
@@ -217,6 +236,13 @@
bool get _showTrackOnHover => widget.showTrackOnHover ?? _scrollbarTheme.showTrackOnHover ?? false;
+ MaterialStateProperty<bool> get _trackVisibility => MaterialStateProperty.resolveWith((Set<MaterialState> states) {
+ if (states.contains(MaterialState.hovered) && _showTrackOnHover) {
+ return true;
+ }
+ return widget.trackVisibility ?? _scrollbarTheme.trackVisibility?.resolve(states) ?? false;
+ });
+
Set<MaterialState> get _states => <MaterialState>{
if (_dragIsActive) MaterialState.dragged,
if (_hoverIsActive) MaterialState.hovered,
@@ -251,7 +277,7 @@
// If the track is visible, the thumb color hover animation is ignored and
// changes immediately.
- if (states.contains(MaterialState.hovered) && _showTrackOnHover)
+ if (_trackVisibility.resolve(states))
return _scrollbarTheme.thumbColor?.resolve(states) ?? hoverColor;
return Color.lerp(
@@ -266,7 +292,7 @@
final Color onSurface = _colorScheme.onSurface;
final Brightness brightness = _colorScheme.brightness;
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
- if (states.contains(MaterialState.hovered) && _showTrackOnHover) {
+ if (_trackVisibility.resolve(states)) {
return _scrollbarTheme.trackColor?.resolve(states)
?? (brightness == Brightness.light
? onSurface.withOpacity(0.03)
@@ -280,7 +306,7 @@
final Color onSurface = _colorScheme.onSurface;
final Brightness brightness = _colorScheme.brightness;
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
- if (states.contains(MaterialState.hovered) && _showTrackOnHover) {
+ if (_trackVisibility.resolve(states)) {
return _scrollbarTheme.trackBorderColor?.resolve(states)
?? (brightness == Brightness.light
? onSurface.withOpacity(0.1)
@@ -292,7 +318,7 @@
MaterialStateProperty<double> get _thickness {
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
- if (states.contains(MaterialState.hovered) && _showTrackOnHover)
+ if (states.contains(MaterialState.hovered) && _trackVisibility.resolve(states))
return widget.hoverThickness
?? _scrollbarTheme.thickness?.resolve(states)
?? _kScrollbarThicknessWithTrack;
diff --git a/packages/flutter/lib/src/material/scrollbar_theme.dart b/packages/flutter/lib/src/material/scrollbar_theme.dart
index 02c9414..b16b3bf 100644
--- a/packages/flutter/lib/src/material/scrollbar_theme.dart
+++ b/packages/flutter/lib/src/material/scrollbar_theme.dart
@@ -32,6 +32,7 @@
/// Creates a theme that can be used for [ThemeData.scrollbarTheme].
const ScrollbarThemeData({
this.thickness,
+ this.trackVisibility,
this.showTrackOnHover,
this.isAlwaysShown,
this.radius,
@@ -51,6 +52,10 @@
/// * [MaterialState.hovered] on web and desktop platforms.
final MaterialStateProperty<double?>? thickness;
+ /// Overrides the default value of [Scrollbar.trackVisibility] in all
+ /// descendant [Scrollbar] widgets.
+ final MaterialStateProperty<bool?>? trackVisibility;
+
/// Overrides the default value of [Scrollbar.showTrackOnHover] in all
/// descendant [Scrollbar] widgets.
final bool? showTrackOnHover;
@@ -122,6 +127,7 @@
/// new values.
ScrollbarThemeData copyWith({
MaterialStateProperty<double?>? thickness,
+ MaterialStateProperty<bool?>? trackVisibility,
bool? showTrackOnHover,
bool? isAlwaysShown,
bool? interactive,
@@ -135,6 +141,7 @@
}) {
return ScrollbarThemeData(
thickness: thickness ?? this.thickness,
+ trackVisibility: trackVisibility ?? this.trackVisibility,
showTrackOnHover: showTrackOnHover ?? this.showTrackOnHover,
isAlwaysShown: isAlwaysShown ?? this.isAlwaysShown,
interactive: interactive ?? this.interactive,
@@ -157,9 +164,10 @@
assert(t != null);
return ScrollbarThemeData(
thickness: _lerpProperties<double?>(a?.thickness, b?.thickness, t, lerpDouble),
- showTrackOnHover: t < 0.5 ? a?.showTrackOnHover : b?.showTrackOnHover,
- isAlwaysShown: t < 0.5 ? a?.isAlwaysShown : b?.isAlwaysShown,
- interactive: t < 0.5 ? a?.interactive : b?.interactive,
+ trackVisibility: _lerpProperties<bool?>(a?.trackVisibility, b?.trackVisibility, t, _lerpBool),
+ showTrackOnHover: _lerpBool(a?.showTrackOnHover, b?.showTrackOnHover, t),
+ isAlwaysShown: _lerpBool(a?.isAlwaysShown, b?.isAlwaysShown, t),
+ interactive: _lerpBool(a?.interactive, b?.interactive, t),
radius: Radius.lerp(a?.radius, b?.radius, t),
thumbColor: _lerpProperties<Color?>(a?.thumbColor, b?.thumbColor, t, Color.lerp),
trackColor: _lerpProperties<Color?>(a?.trackColor, b?.trackColor, t, Color.lerp),
@@ -174,6 +182,7 @@
int get hashCode {
return hashValues(
thickness,
+ trackVisibility,
showTrackOnHover,
isAlwaysShown,
interactive,
@@ -195,6 +204,7 @@
return false;
return other is ScrollbarThemeData
&& other.thickness == thickness
+ && other.trackVisibility == trackVisibility
&& other.showTrackOnHover == showTrackOnHover
&& other.isAlwaysShown == isAlwaysShown
&& other.interactive == interactive
@@ -211,6 +221,7 @@
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
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));
properties.add(DiagnosticsProperty<bool>('isAlwaysShown', isAlwaysShown, defaultValue: null));
properties.add(DiagnosticsProperty<bool>('interactive', interactive, defaultValue: null));
@@ -252,6 +263,8 @@
}
}
+bool? _lerpBool(bool? a, bool? b, double t) => t < 0.5 ? a : b;
+
/// Applies a scrollbar theme to descendant [Scrollbar] widgets.
///
/// Descendant widgets obtain the current theme's [ScrollbarThemeData] using
diff --git a/packages/flutter/test/material/scrollbar_theme_test.dart b/packages/flutter/test/material/scrollbar_theme_test.dart
index 7aa97ab..1032e32 100644
--- a/packages/flutter/test/material/scrollbar_theme_test.dart
+++ b/packages/flutter/test/material/scrollbar_theme_test.dart
@@ -574,6 +574,52 @@
}),
);
+ testWidgets('ScrollbarThemeData.trackVisibility test', (WidgetTester tester) async {
+ final ScrollController scrollController = ScrollController();
+ bool? _getTrackVisibility(Set<MaterialState> states) {
+ return true;
+ }
+ await tester.pumpWidget(
+ MaterialApp(
+ theme: ThemeData().copyWith(
+ scrollbarTheme: _scrollbarTheme(
+ trackVisibility: MaterialStateProperty.resolveWith(_getTrackVisibility),
+ ),
+ ),
+ home: ScrollConfiguration(
+ behavior: const NoScrollbarBehavior(),
+ child: Scrollbar(
+ isAlwaysShown: true,
+ showTrackOnHover: true,
+ controller: scrollController,
+ child: SingleChildScrollView(
+ controller: scrollController,
+ child: const SizedBox(width: 4000.0, height: 4000.0),
+ ),
+ ),
+ ),
+ ),
+ );
+ await tester.pumpAndSettle();
+
+ expect(
+ find.byType(Scrollbar),
+ paints
+ ..rect(color: const Color(0x08000000))
+ ..line(
+ strokeWidth: 1.0,
+ color: const Color(0x1a000000),
+ )
+ ..rrect(color: const Color(0xff4caf50)),
+ );
+ }, variant: const TargetPlatformVariant(<TargetPlatform>{
+ TargetPlatform.linux,
+ TargetPlatform.macOS,
+ TargetPlatform.windows,
+ TargetPlatform.fuchsia,
+ }),
+ );
+
testWidgets('Default ScrollbarTheme debugFillProperties', (WidgetTester tester) async {
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
const ScrollbarThemeData().debugFillProperties(builder);
@@ -636,6 +682,7 @@
ScrollbarThemeData _scrollbarTheme({
MaterialStateProperty<double?>? thickness,
+ MaterialStateProperty<bool?>? trackVisibility,
bool showTrackOnHover = true,
bool isAlwaysShown = true,
Radius radius = const Radius.circular(6.0),
@@ -648,6 +695,7 @@
}) {
return ScrollbarThemeData(
thickness: thickness ?? MaterialStateProperty.resolveWith(_getThickness),
+ trackVisibility: trackVisibility,
showTrackOnHover: showTrackOnHover,
isAlwaysShown: isAlwaysShown,
radius: radius,