Unverified Commit e7772d0e authored by xubaolin's avatar xubaolin Committed by GitHub

Reland "Improve the ScrollBar behavior when nested (#71843)" (#74104)

parent d3257053
...@@ -61,6 +61,7 @@ class CupertinoScrollbar extends RawScrollbar { ...@@ -61,6 +61,7 @@ class CupertinoScrollbar extends RawScrollbar {
this.thicknessWhileDragging = defaultThicknessWhileDragging, this.thicknessWhileDragging = defaultThicknessWhileDragging,
Radius radius = defaultRadius, Radius radius = defaultRadius,
this.radiusWhileDragging = defaultRadiusWhileDragging, this.radiusWhileDragging = defaultRadiusWhileDragging,
ScrollNotificationPredicate? notificationPredicate,
}) : assert(thickness != null), }) : assert(thickness != null),
assert(thickness < double.infinity), assert(thickness < double.infinity),
assert(thicknessWhileDragging != null), assert(thicknessWhileDragging != null),
...@@ -77,6 +78,7 @@ class CupertinoScrollbar extends RawScrollbar { ...@@ -77,6 +78,7 @@ class CupertinoScrollbar extends RawScrollbar {
fadeDuration: _kScrollbarFadeDuration, fadeDuration: _kScrollbarFadeDuration,
timeToFade: _kScrollbarTimeToFade, timeToFade: _kScrollbarTimeToFade,
pressDuration: const Duration(milliseconds: 100), pressDuration: const Duration(milliseconds: 100),
notificationPredicate: notificationPredicate ?? defaultScrollNotificationPredicate,
); );
/// Default value for [thickness] if it's not specified in [CupertinoScrollbar]. /// Default value for [thickness] if it's not specified in [CupertinoScrollbar].
......
...@@ -69,6 +69,7 @@ class Scrollbar extends StatefulWidget { ...@@ -69,6 +69,7 @@ class Scrollbar extends StatefulWidget {
this.hoverThickness, this.hoverThickness,
this.thickness, this.thickness,
this.radius, this.radius,
this.notificationPredicate,
}) : super(key: key); }) : super(key: key);
/// {@macro flutter.widgets.Scrollbar.child} /// {@macro flutter.widgets.Scrollbar.child}
...@@ -111,6 +112,9 @@ class Scrollbar extends StatefulWidget { ...@@ -111,6 +112,9 @@ class Scrollbar extends StatefulWidget {
/// default [Radius.circular] of 8.0 pixels. /// default [Radius.circular] of 8.0 pixels.
final Radius? radius; final Radius? radius;
/// {@macro flutter.widgets.Scrollbar.notificationPredicate}
final ScrollNotificationPredicate? notificationPredicate;
@override @override
_ScrollbarState createState() => _ScrollbarState(); _ScrollbarState createState() => _ScrollbarState();
} }
...@@ -129,6 +133,7 @@ class _ScrollbarState extends State<Scrollbar> { ...@@ -129,6 +133,7 @@ class _ScrollbarState extends State<Scrollbar> {
radius: widget.radius ?? CupertinoScrollbar.defaultRadius, radius: widget.radius ?? CupertinoScrollbar.defaultRadius,
radiusWhileDragging: widget.radius ?? CupertinoScrollbar.defaultRadiusWhileDragging, radiusWhileDragging: widget.radius ?? CupertinoScrollbar.defaultRadiusWhileDragging,
controller: widget.controller, controller: widget.controller,
notificationPredicate: widget.notificationPredicate,
); );
} }
return _MaterialScrollbar( return _MaterialScrollbar(
...@@ -139,6 +144,7 @@ class _ScrollbarState extends State<Scrollbar> { ...@@ -139,6 +144,7 @@ class _ScrollbarState extends State<Scrollbar> {
hoverThickness: widget.hoverThickness, hoverThickness: widget.hoverThickness,
thickness: widget.thickness, thickness: widget.thickness,
radius: widget.radius, radius: widget.radius,
notificationPredicate: widget.notificationPredicate,
); );
} }
} }
...@@ -153,6 +159,7 @@ class _MaterialScrollbar extends RawScrollbar { ...@@ -153,6 +159,7 @@ class _MaterialScrollbar extends RawScrollbar {
this.hoverThickness, this.hoverThickness,
double? thickness, double? thickness,
Radius? radius, Radius? radius,
ScrollNotificationPredicate? notificationPredicate,
}) : super( }) : super(
key: key, key: key,
child: child, child: child,
...@@ -163,6 +170,7 @@ class _MaterialScrollbar extends RawScrollbar { ...@@ -163,6 +170,7 @@ class _MaterialScrollbar extends RawScrollbar {
fadeDuration: _kScrollbarFadeDuration, fadeDuration: _kScrollbarFadeDuration,
timeToFade: _kScrollbarTimeToFade, timeToFade: _kScrollbarTimeToFade,
pressDuration: Duration.zero, pressDuration: Duration.zero,
notificationPredicate: notificationPredicate ?? defaultScrollNotificationPredicate,
); );
final bool? showTrackOnHover; final bool? showTrackOnHover;
......
...@@ -565,6 +565,10 @@ class ScrollbarPainter extends ChangeNotifier implements CustomPainter { ...@@ -565,6 +565,10 @@ class ScrollbarPainter extends ChangeNotifier implements CustomPainter {
/// visible without the fade animation. This requires that a [ScrollController] /// visible without the fade animation. This requires that a [ScrollController]
/// is provided to [controller], or that the [PrimaryScrollController] is available. /// is provided to [controller], or that the [PrimaryScrollController] is available.
/// ///
/// If the scrollbar is wrapped around multiple [ScrollView]s, it only responds to
/// the nearest scrollView and shows the corresponding scrollbar thumb by default.
/// Set [notificationPredicate] to something else for more complicated behaviors.
///
/// Scrollbars are interactive and will also use the [PrimaryScrollController] if /// Scrollbars are interactive and will also use the [PrimaryScrollController] if
/// a [controller] is not set. Scrollbar thumbs can be dragged along the main axis /// a [controller] is not set. Scrollbar thumbs can be dragged along the main axis
/// of the [ScrollView] to change the [ScrollPosition]. Tapping along the track /// of the [ScrollView] to change the [ScrollPosition]. Tapping along the track
...@@ -607,6 +611,7 @@ class RawScrollbar extends StatefulWidget { ...@@ -607,6 +611,7 @@ class RawScrollbar extends StatefulWidget {
this.fadeDuration = _kScrollbarFadeDuration, this.fadeDuration = _kScrollbarFadeDuration,
this.timeToFade = _kScrollbarTimeToFade, this.timeToFade = _kScrollbarTimeToFade,
this.pressDuration = Duration.zero, this.pressDuration = Duration.zero,
this.notificationPredicate = defaultScrollNotificationPredicate,
}) : assert(child != null), }) : assert(child != null),
assert(fadeDuration != null), assert(fadeDuration != null),
assert(timeToFade != null), assert(timeToFade != null),
...@@ -767,6 +772,16 @@ class RawScrollbar extends StatefulWidget { ...@@ -767,6 +772,16 @@ class RawScrollbar extends StatefulWidget {
/// Cannot be null, defaults to [Duration.zero]. /// Cannot be null, defaults to [Duration.zero].
final Duration pressDuration; final Duration pressDuration;
/// {@template flutter.widgets.Scrollbar.notificationPredicate}
/// A check that specifies whether a [ScrollNotification] should be
/// handled by this widget.
///
/// By default, checks whether `notification.depth == 0`. That means if the
/// scrollbar is wrapped around multiple [ScrollView]s, it only responds to the
/// nearest scrollView and shows the corresponding scrollbar thumb.
/// {@endtemplate}
final ScrollNotificationPredicate notificationPredicate;
@override @override
RawScrollbarState<RawScrollbar> createState() => RawScrollbarState<RawScrollbar>(); RawScrollbarState<RawScrollbar> createState() => RawScrollbarState<RawScrollbar>();
} }
...@@ -1031,6 +1046,8 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv ...@@ -1031,6 +1046,8 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv
} }
bool _handleScrollNotification(ScrollNotification notification) { bool _handleScrollNotification(ScrollNotification notification) {
if (!widget.notificationPredicate(notification))
return false;
final ScrollMetrics metrics = notification.metrics; final ScrollMetrics metrics = notification.metrics;
if (metrics.maxScrollExtent <= metrics.minScrollExtent) if (metrics.maxScrollExtent <= metrics.minScrollExtent)
......
...@@ -1059,4 +1059,66 @@ void main() { ...@@ -1059,4 +1059,66 @@ void main() {
final CupertinoScrollbar scrollbar = tester.widget<CupertinoScrollbar>(find.byType(CupertinoScrollbar)); final CupertinoScrollbar scrollbar = tester.widget<CupertinoScrollbar>(find.byType(CupertinoScrollbar));
expect(scrollbar.controller, isNotNull); expect(scrollbar.controller, isNotNull);
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS })); }, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS }));
testWidgets("Scrollbar doesn't show when scroll the inner scrollable widget", (WidgetTester tester) async {
final GlobalKey key1 = GlobalKey();
final GlobalKey key2 = GlobalKey();
final GlobalKey outerKey = GlobalKey();
final GlobalKey innerKey = GlobalKey();
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: MediaQuery(
data: const MediaQueryData(),
child: Scrollbar(
key: key2,
notificationPredicate: null,
child: SingleChildScrollView(
key: outerKey,
child: SizedBox(
height: 1000.0,
width: double.infinity,
child: Column(
children: <Widget>[
Scrollbar(
key: key1,
notificationPredicate: null,
child: SizedBox(
height: 300.0,
width: double.infinity,
child: SingleChildScrollView(
key: innerKey,
child: const SizedBox(
key: Key('Inner scrollable'),
height: 1000.0,
width: double.infinity,
),
),
),
),
],
),
),
),
),
),
),
);
// Drag the inner scrollable widget.
await tester.drag(find.byKey(innerKey), const Offset(0.0, -25.0));
await tester.pump();
// Scrollbar fully showing.
await tester.pump(const Duration(milliseconds: 500));
expect(
tester.renderObject(find.byKey(key2)),
paintsExactlyCountTimes(#drawRect, 2), // Each bar will call [drawRect] twice.
);
expect(
tester.renderObject(find.byKey(key1)),
paintsExactlyCountTimes(#drawRect, 2),
);
}, variant: TargetPlatformVariant.all());
} }
...@@ -804,4 +804,67 @@ void main() { ...@@ -804,4 +804,67 @@ void main() {
), ),
); );
}); });
// Regression test for https://github.com/flutter/flutter/issues/66444
testWidgets("RawScrollbar doesn't show when scroll the inner scrollable widget", (WidgetTester tester) async {
final GlobalKey key1 = GlobalKey();
final GlobalKey key2 = GlobalKey();
final GlobalKey outerKey = GlobalKey();
final GlobalKey innerKey = GlobalKey();
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: MediaQuery(
data: const MediaQueryData(),
child: RawScrollbar(
key: key2,
thumbColor: const Color(0x11111111),
child: SingleChildScrollView(
key: outerKey,
child: SizedBox(
height: 1000.0,
width: double.infinity,
child: Column(
children: <Widget>[
RawScrollbar(
key: key1,
thumbColor: const Color(0x22222222),
child: SizedBox(
height: 300.0,
width: double.infinity,
child: SingleChildScrollView(
key: innerKey,
child: const SizedBox(
key: Key('Inner scrollable'),
height: 1000.0,
width: double.infinity,
),
),
),
),
],
),
),
),
),
),
),
);
// Drag the inner scrollable widget.
await tester.drag(find.byKey(innerKey), const Offset(0.0, -25.0));
await tester.pump();
// Scrollbar fully showing.
await tester.pump(const Duration(milliseconds: 500));
expect(
tester.renderObject(find.byKey(key2)),
paintsExactlyCountTimes(#drawRect, 2), // Each bar will call [drawRect] twice.
);
expect(
tester.renderObject(find.byKey(key1)),
paintsExactlyCountTimes(#drawRect, 2),
);
});
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment