Expose minThumbLength, crossAxisMargin, and minOverscrollLength on RawScrollbar (#85338)
diff --git a/packages/flutter/lib/src/widgets/scrollbar.dart b/packages/flutter/lib/src/widgets/scrollbar.dart
index efa022a..7e327de 100644
--- a/packages/flutter/lib/src/widgets/scrollbar.dart
+++ b/packages/flutter/lib/src/widgets/scrollbar.dart
@@ -196,7 +196,8 @@
notifyListeners();
}
- /// Distance from the scrollbar's side to the nearest edge in logical pixels.
+ /// Distance from the scrollbar thumb to the nearest cross axis edge
+ /// in logical pixels.
///
/// Must not be null and defaults to 0.
double get crossAxisMargin => _crossAxisMargin;
@@ -244,7 +245,7 @@
}
- /// The preferred smallest size the scrollbar can shrink to when the total
+ /// The preferred smallest size the scrollbar thumb can shrink to when the total
/// scrollable extent is large, the current visible viewport is small, and the
/// viewport is not overscrolled.
///
@@ -253,8 +254,8 @@
/// `double.infinity`, it will not be respected if
/// [ScrollMetrics.viewportDimension] and [mainAxisMargin] are finite.
///
- /// Mustn't be null and the value has to be within the range of 0 to
- /// [minOverscrollLength], inclusive. Defaults to 18.0.
+ /// Mustn't be null and the value has to be greater or equal to
+ /// [minOverscrollLength], which in turn is >= 0. Defaults to 18.0.
double get minLength => _minLength;
double _minLength;
set minLength(double value) {
@@ -266,7 +267,7 @@
notifyListeners();
}
- /// The preferred smallest size the scrollbar can shrink to when viewport is
+ /// The preferred smallest size the scrollbar thumb can shrink to when viewport is
/// overscrolled.
///
/// When overscrolling, the size of the scrollbar may shrink to a smaller size
@@ -275,7 +276,7 @@
/// the [ScrollMetrics.viewportDimension] and [mainAxisMargin] are finite.
///
/// The value is less than or equal to [minLength] and greater than or equal to 0.
- /// If unspecified or set to null, it will defaults to the value of [minLength].
+ /// When null, it will default to the value of [minLength].
double get minOverscrollLength => _minOverscrollLength;
double _minOverscrollLength;
set minOverscrollLength(double value) {
@@ -857,6 +858,8 @@
this.radius,
this.thickness,
this.thumbColor,
+ this.minThumbLength = _kMinThumbExtent,
+ this.minOverscrollLength,
this.fadeDuration = _kScrollbarFadeDuration,
this.timeToFade = _kScrollbarTimeToFade,
this.pressDuration = Duration.zero,
@@ -864,11 +867,17 @@
this.interactive,
this.scrollbarOrientation,
this.mainAxisMargin = 0.0,
+ this.crossAxisMargin = 0.0
}) : assert(child != null),
+ assert(minThumbLength != null),
+ assert(minThumbLength >= 0),
+ assert(minOverscrollLength == null || minOverscrollLength <= minThumbLength),
+ assert(minOverscrollLength == null || minOverscrollLength >= 0),
assert(fadeDuration != null),
assert(timeToFade != null),
assert(pressDuration != null),
assert(mainAxisMargin != null),
+ assert(crossAxisMargin != null),
super(key: key);
/// {@template flutter.widgets.Scrollbar.child}
@@ -1030,6 +1039,34 @@
/// If null, defaults to Color(0x66BCBCBC).
final Color? thumbColor;
+ /// The preferred smallest size the scrollbar thumb can shrink to when the total
+ /// scrollable extent is large, the current visible viewport is small, and the
+ /// viewport is not overscrolled.
+ ///
+ /// The size of the scrollbar's thumb may shrink to a smaller size than [minThumbLength]
+ /// to fit in the available paint area (e.g., when [minThumbLength] is greater
+ /// than [ScrollMetrics.viewportDimension] and [mainAxisMargin] combined).
+ ///
+ /// Mustn't be null and the value has to be greater or equal to
+ /// [minOverscrollLength], which in turn is >= 0. Defaults to 18.0.
+ final double minThumbLength;
+
+ /// The preferred smallest size the scrollbar thumb can shrink to when viewport is
+ /// overscrolled.
+ ///
+ /// When overscrolling, the size of the scrollbar's thumb may shrink to a smaller size
+ /// than [minOverscrollLength] to fit in the available paint area (e.g., when
+ /// [minOverscrollLength] is greater than [ScrollMetrics.viewportDimension] and
+ /// [mainAxisMargin] combined).
+ ///
+ /// Overscrolling can be made possible by setting the `physics` property
+ /// of the `child` Widget to a `BouncingScrollPhysics`, which is a special
+ /// `ScrollPhysics` that allows overscrolling.
+ ///
+ /// The value is less than or equal to [minThumbLength] and greater than or equal to 0.
+ /// When null, it will default to the value of [minThumbLength].
+ final double? minOverscrollLength;
+
/// The [Duration] of the fade animation.
///
/// Cannot be null, defaults to a [Duration] of 300 milliseconds.
@@ -1085,6 +1122,12 @@
/// Mustn't be null and defaults to 0.
final double mainAxisMargin;
+ /// Distance from the scrollbar thumb side to the nearest cross axis edge
+ /// in logical pixels.
+ ///
+ /// Must not be null and defaults to 0.
+ final double crossAxisMargin;
+
@override
RawScrollbarState<RawScrollbar> createState() => RawScrollbarState<RawScrollbar>();
}
@@ -1154,10 +1197,13 @@
);
scrollbarPainter = ScrollbarPainter(
color: widget.thumbColor ?? const Color(0x66BCBCBC),
+ minLength: widget.minThumbLength,
+ minOverscrollLength: widget.minOverscrollLength ?? widget.minThumbLength,
thickness: widget.thickness ?? _kScrollbarThickness,
fadeoutOpacityAnimation: _fadeoutOpacityAnimation,
scrollbarOrientation: widget.scrollbarOrientation,
mainAxisMargin: widget.mainAxisMargin,
+ crossAxisMargin: widget.crossAxisMargin
);
}
@@ -1294,7 +1340,10 @@
..radius = widget.radius
..padding = MediaQuery.of(context).padding
..scrollbarOrientation = widget.scrollbarOrientation
- ..mainAxisMargin = widget.mainAxisMargin;
+ ..mainAxisMargin = widget.mainAxisMargin
+ ..crossAxisMargin = widget.crossAxisMargin
+ ..minLength = widget.minThumbLength
+ ..minOverscrollLength = widget.minOverscrollLength ?? widget.minThumbLength;
}
@override
diff --git a/packages/flutter/test/widgets/scrollbar_test.dart b/packages/flutter/test/widgets/scrollbar_test.dart
index daa4ac6..73aa3c1 100644
--- a/packages/flutter/test/widgets/scrollbar_test.dart
+++ b/packages/flutter/test/widgets/scrollbar_test.dart
@@ -1482,4 +1482,87 @@
..rect(rect: const Rect.fromLTRB(794.0, 10.0, 800.0, 358.0))
);
});
+ testWidgets('minThumbLength property of RawScrollbar is respected', (WidgetTester tester) async {
+ final ScrollController scrollController = ScrollController();
+ await tester.pumpWidget(
+ Directionality(
+ textDirection: TextDirection.ltr,
+ child: MediaQuery(
+ data: const MediaQueryData(),
+ child: RawScrollbar(
+ controller: scrollController,
+ minThumbLength: 21,
+ minOverscrollLength: 8,
+ isAlwaysShown: true,
+ child: SingleChildScrollView(
+ controller: scrollController,
+ child: const SizedBox(width: 1000.0, height: 50000.0),
+ ),
+ ),
+ )));
+ await tester.pumpAndSettle();
+ expect(
+ find.byType(RawScrollbar),
+ paints
+ ..rect(rect: const Rect.fromLTRB(794.0, 0.0, 800.0, 600.0)) // track
+ ..rect(rect: const Rect.fromLTRB(794.0, 0.0, 800.0, 21.0))); // thumb
+ });
+
+ testWidgets('crossAxisMargin property of RawScrollbar is respected', (WidgetTester tester) async {
+ final ScrollController scrollController = ScrollController();
+ await tester.pumpWidget(
+ Directionality(
+ textDirection: TextDirection.ltr,
+ child: MediaQuery(
+ data: const MediaQueryData(),
+ child: RawScrollbar(
+ controller: scrollController,
+ crossAxisMargin: 30,
+ isAlwaysShown: true,
+ child: SingleChildScrollView(
+ controller: scrollController,
+ child: const SizedBox(width: 1000.0, height: 1000.0),
+ ),
+ ),
+ )));
+ await tester.pumpAndSettle();
+ expect(
+ find.byType(RawScrollbar),
+ paints
+ ..rect(rect: const Rect.fromLTRB(734.0, 0.0, 800.0, 600.0))
+ ..rect(rect: const Rect.fromLTRB(764.0, 0.0, 770.0, 360.0)));
+ });
+
+ testWidgets('minOverscrollLength property of RawScrollbar is respected', (WidgetTester tester) async {
+ final ScrollController scrollController = ScrollController();
+ await tester.pumpWidget(
+ Directionality(
+ textDirection: TextDirection.ltr,
+ child: MediaQuery(
+ data: const MediaQueryData(),
+ child: RawScrollbar(
+ controller: scrollController,
+ isAlwaysShown: true,
+ minOverscrollLength: 8.0,
+ minThumbLength: 36.0,
+ child: SingleChildScrollView(
+ physics: const BouncingScrollPhysics(),
+ controller: scrollController,
+ child: const SizedBox(height: 10000),
+ )
+ ),
+ )
+ )
+ );
+ await tester.pumpAndSettle();
+ final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byType(RawScrollbar)));
+ await gesture.moveBy(const Offset(0, 1000));
+ await tester.pump();
+ expect(
+ find.byType(RawScrollbar),
+ paints
+ ..rect(rect: const Rect.fromLTRB(794.0, 0.0, 800.0, 600.0))
+ ..rect(rect: const Rect.fromLTRB(794.0, 0.0, 800.0, 8.0)));
+ });
+
}