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,