mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
[new feature] Add support for a RawScrollbar.shape (#85652)
This commit is contained in:
parent
f5dd3d9d05
commit
bb278d1da2
@ -85,10 +85,12 @@ class ScrollbarPainter extends ChangeNotifier implements CustomPainter {
|
||||
double mainAxisMargin = 0.0,
|
||||
double crossAxisMargin = 0.0,
|
||||
Radius? radius,
|
||||
OutlinedBorder? shape,
|
||||
double minLength = _kMinThumbExtent,
|
||||
double? minOverscrollLength,
|
||||
ScrollbarOrientation? scrollbarOrientation,
|
||||
}) : assert(color != null),
|
||||
assert(radius == null || shape == null),
|
||||
assert(thickness != null),
|
||||
assert(fadeoutOpacityAnimation != null),
|
||||
assert(mainAxisMargin != null),
|
||||
@ -103,6 +105,7 @@ class ScrollbarPainter extends ChangeNotifier implements CustomPainter {
|
||||
_textDirection = textDirection,
|
||||
_thickness = thickness,
|
||||
_radius = radius,
|
||||
_shape = shape,
|
||||
_padding = padding,
|
||||
_mainAxisMargin = mainAxisMargin,
|
||||
_crossAxisMargin = crossAxisMargin,
|
||||
@ -217,6 +220,7 @@ class ScrollbarPainter extends ChangeNotifier implements CustomPainter {
|
||||
Radius? get radius => _radius;
|
||||
Radius? _radius;
|
||||
set radius(Radius? value) {
|
||||
assert(shape == null || value == null);
|
||||
if (radius == value)
|
||||
return;
|
||||
|
||||
@ -224,6 +228,26 @@ class ScrollbarPainter extends ChangeNotifier implements CustomPainter {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// The [OutlinedBorder] of the scrollbar's thumb.
|
||||
///
|
||||
/// Only one of [radius] and [shape] may be specified. For a rounded rectangle,
|
||||
/// it's simplest to just specify [radius]. By default, the scrollbar thumb's
|
||||
/// shape is a simple rectangle.
|
||||
///
|
||||
/// If [shape] is specified, the thumb will take the shape of the passed
|
||||
/// [OutlinedBorder] and fill itself with [color] (or grey if it
|
||||
/// is unspecified).
|
||||
///
|
||||
OutlinedBorder? get shape => _shape;
|
||||
OutlinedBorder? _shape;
|
||||
set shape(OutlinedBorder? value){
|
||||
assert(radius == null || value == null);
|
||||
if(shape == value)
|
||||
return;
|
||||
|
||||
_shape = value;
|
||||
notifyListeners();
|
||||
}
|
||||
/// The amount of space by which to inset the scrollbar's start and end, as
|
||||
/// well as its side to the nearest edge, in logical pixels.
|
||||
///
|
||||
@ -447,10 +471,20 @@ class ScrollbarPainter extends ChangeNotifier implements CustomPainter {
|
||||
);
|
||||
|
||||
_thumbRect = Offset(x, y) & thumbSize;
|
||||
if (radius == null)
|
||||
canvas.drawRect(_thumbRect!, _paintThumb);
|
||||
else
|
||||
|
||||
if (radius != null) {
|
||||
canvas.drawRRect(RRect.fromRectAndRadius(_thumbRect!, radius!), _paintThumb);
|
||||
return;
|
||||
}
|
||||
|
||||
if (shape == null) {
|
||||
canvas.drawRect(_thumbRect!, _paintThumb);
|
||||
return;
|
||||
}
|
||||
|
||||
final Path outerPath = shape!.getOuterPath(_thumbRect!);
|
||||
canvas.drawPath(outerPath, _paintThumb);
|
||||
shape!.paint(canvas, _thumbRect!);
|
||||
}
|
||||
|
||||
double _thumbExtent() {
|
||||
@ -776,6 +810,7 @@ class RawScrollbar extends StatefulWidget {
|
||||
required this.child,
|
||||
this.controller,
|
||||
this.isAlwaysShown,
|
||||
this.shape,
|
||||
this.radius,
|
||||
this.thickness,
|
||||
this.thumbColor,
|
||||
@ -795,6 +830,7 @@ class RawScrollbar extends StatefulWidget {
|
||||
assert(minOverscrollLength == null || minOverscrollLength <= minThumbLength),
|
||||
assert(minOverscrollLength == null || minOverscrollLength >= 0),
|
||||
assert(fadeDuration != null),
|
||||
assert(radius == null || shape == null),
|
||||
assert(timeToFade != null),
|
||||
assert(pressDuration != null),
|
||||
assert(mainAxisMargin != null),
|
||||
@ -944,6 +980,39 @@ class RawScrollbar extends StatefulWidget {
|
||||
/// {@endtemplate}
|
||||
final bool? isAlwaysShown;
|
||||
|
||||
/// The [OutlinedBorder] of the scrollbar's thumb.
|
||||
///
|
||||
/// Only one of [radius] and [shape] may be specified. For a rounded rectangle,
|
||||
/// it's simplest to just specify [radius]. By default, the scrollbar thumb's
|
||||
/// shape is a simple rectangle.
|
||||
///
|
||||
/// If [shape] is specified, the thumb will take the shape of the passed
|
||||
/// [OutlinedBorder] and fill itself with [thumbColor] (or grey if it
|
||||
/// is unspecified).
|
||||
///
|
||||
/// Here is an example of using a [StadiumBorder] for drawing the [shape] of the
|
||||
/// thumb in a [RawScrollbar]:
|
||||
///
|
||||
/// {@tool dartpad --template=stateless_widget_material}
|
||||
/// ```dart
|
||||
/// Widget build(BuildContext context) {
|
||||
/// return Scaffold(
|
||||
/// body: RawScrollbar(
|
||||
/// child: ListView(
|
||||
/// children: List<Text>.generate(100, (int index) => Text((index * index).toString())),
|
||||
/// physics: const BouncingScrollPhysics(),
|
||||
/// ),
|
||||
/// shape: const StadiumBorder(side: BorderSide(color: Colors.brown, width: 3.0)),
|
||||
/// thickness: 15.0,
|
||||
/// thumbColor: Colors.blue,
|
||||
/// isAlwaysShown: true,
|
||||
/// ),
|
||||
/// );
|
||||
/// }
|
||||
/// ```
|
||||
/// {@end-tool}
|
||||
final OutlinedBorder? shape;
|
||||
|
||||
/// The [Radius] of the scrollbar thumb's rounded rectangle corners.
|
||||
///
|
||||
/// Scrollbar will be rectangular if [radius] is null, which is the default
|
||||
@ -1124,6 +1193,7 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv
|
||||
fadeoutOpacityAnimation: _fadeoutOpacityAnimation,
|
||||
scrollbarOrientation: widget.scrollbarOrientation,
|
||||
mainAxisMargin: widget.mainAxisMargin,
|
||||
shape: widget.shape,
|
||||
crossAxisMargin: widget.crossAxisMargin
|
||||
);
|
||||
}
|
||||
@ -1253,6 +1323,7 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv
|
||||
..padding = MediaQuery.of(context).padding
|
||||
..scrollbarOrientation = widget.scrollbarOrientation
|
||||
..mainAxisMargin = widget.mainAxisMargin
|
||||
..shape = widget.shape
|
||||
..crossAxisMargin = widget.crossAxisMargin
|
||||
..minLength = widget.minThumbLength
|
||||
..minOverscrollLength = widget.minOverscrollLength ?? widget.minThumbLength;
|
||||
|
@ -1445,6 +1445,7 @@ void main() {
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('ScrollbarPainter asserts if scrollbarOrientation is used with wrong axisDirection', (WidgetTester tester) async {
|
||||
final ScrollbarPainter painter = ScrollbarPainter(
|
||||
color: _kScrollbarColor,
|
||||
@ -1492,6 +1493,44 @@ void main() {
|
||||
..rect(rect: const Rect.fromLTRB(794.0, 10.0, 800.0, 358.0))
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('shape property of RawScrollbar can draw a BeveledRectangleBorder', (WidgetTester tester) async {
|
||||
final ScrollController scrollController = ScrollController();
|
||||
await tester.pumpWidget(
|
||||
Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: MediaQuery(
|
||||
data: const MediaQueryData(),
|
||||
child: RawScrollbar(
|
||||
shape: const BeveledRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(8.0))
|
||||
),
|
||||
controller: scrollController,
|
||||
isAlwaysShown: true,
|
||||
child: SingleChildScrollView(
|
||||
controller: scrollController,
|
||||
child: const SizedBox(height: 1000.0),
|
||||
),
|
||||
),
|
||||
)));
|
||||
await tester.pumpAndSettle();
|
||||
expect(
|
||||
find.byType(RawScrollbar),
|
||||
paints
|
||||
..rect(rect: const Rect.fromLTRB(794.0, 0.0, 800.0, 600.0))
|
||||
..path(
|
||||
includes: const <Offset>[
|
||||
Offset(797.0, 0.0),
|
||||
Offset(797.0, 18.0),
|
||||
],
|
||||
excludes: const <Offset>[
|
||||
Offset(796.0, 0.0),
|
||||
Offset(798.0, 0.0),
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('minThumbLength property of RawScrollbar is respected', (WidgetTester tester) async {
|
||||
final ScrollController scrollController = ScrollController();
|
||||
await tester.pumpWidget(
|
||||
@ -1518,6 +1557,42 @@ void main() {
|
||||
..rect(rect: const Rect.fromLTRB(794.0, 0.0, 800.0, 21.0))); // thumb
|
||||
});
|
||||
|
||||
testWidgets('shape property of RawScrollbar can draw a CircleBorder', (WidgetTester tester) async {
|
||||
final ScrollController scrollController = ScrollController();
|
||||
await tester.pumpWidget(
|
||||
Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: MediaQuery(
|
||||
data: const MediaQueryData(),
|
||||
child: RawScrollbar(
|
||||
shape: const CircleBorder(side: BorderSide(width: 2.0)),
|
||||
thickness: 36.0,
|
||||
controller: scrollController,
|
||||
isAlwaysShown: true,
|
||||
child: SingleChildScrollView(
|
||||
controller: scrollController,
|
||||
child: const SizedBox(height: 1000.0, width: 1000),
|
||||
),
|
||||
),
|
||||
)));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
find.byType(RawScrollbar),
|
||||
paints
|
||||
..path(
|
||||
includes: const <Offset>[
|
||||
Offset(782.0, 180.0),
|
||||
Offset(782.0, 180.0 - 18.0),
|
||||
Offset(782.0 + 18.0, 180),
|
||||
Offset(782.0, 180.0 + 18.0),
|
||||
Offset(782.0 - 18.0, 180),
|
||||
],
|
||||
)
|
||||
..circle(x: 782.0, y: 180.0, radius: 17.0, strokeWidth: 2.0)
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('crossAxisMargin property of RawScrollbar is respected', (WidgetTester tester) async {
|
||||
final ScrollController scrollController = ScrollController();
|
||||
await tester.pumpWidget(
|
||||
@ -1543,6 +1618,40 @@ void main() {
|
||||
..rect(rect: const Rect.fromLTRB(764.0, 0.0, 770.0, 360.0)));
|
||||
});
|
||||
|
||||
testWidgets('shape property of RawScrollbar can draw a RoundedRectangleBorder', (WidgetTester tester) async {
|
||||
final ScrollController scrollController = ScrollController();
|
||||
await tester.pumpWidget(
|
||||
Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: MediaQuery(
|
||||
data: const MediaQueryData(),
|
||||
child: RawScrollbar(
|
||||
thickness: 20,
|
||||
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.only(topLeft: Radius.circular(8))),
|
||||
controller: scrollController,
|
||||
isAlwaysShown: true,
|
||||
child: SingleChildScrollView(
|
||||
controller: scrollController,
|
||||
child: const SizedBox(height: 1000.0, width: 1000.0),
|
||||
),
|
||||
),
|
||||
)));
|
||||
await tester.pumpAndSettle();
|
||||
expect(
|
||||
find.byType(RawScrollbar),
|
||||
paints
|
||||
..rect(rect: const Rect.fromLTRB(780.0, 0.0, 800.0, 600.0))
|
||||
..path(
|
||||
includes: const <Offset>[
|
||||
Offset(800.0, 0.0),
|
||||
],
|
||||
excludes: const <Offset>[
|
||||
Offset(780.0, 0.0),
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('minOverscrollLength property of RawScrollbar is respected', (WidgetTester tester) async {
|
||||
final ScrollController scrollController = ScrollController();
|
||||
await tester.pumpWidget(
|
||||
@ -1575,6 +1684,32 @@ void main() {
|
||||
..rect(rect: const Rect.fromLTRB(794.0, 0.0, 800.0, 8.0)));
|
||||
});
|
||||
|
||||
testWidgets('not passing any shape or radius to RawScrollbar will draw the usual rectangular thumb', (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,
|
||||
child: SingleChildScrollView(
|
||||
controller: scrollController,
|
||||
child: const SizedBox(height: 1000.0),
|
||||
),
|
||||
),
|
||||
)));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
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, 360.0))
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('The bar can show or hide when the viewport size change', (WidgetTester tester) async {
|
||||
final ScrollController scrollController = ScrollController();
|
||||
Widget buildFrame(double height) {
|
||||
|
Loading…
Reference in New Issue
Block a user