Unverified Commit 2a64fdbc authored by Kate Lovett's avatar Kate Lovett Committed by GitHub

Restore adaptive nature to new Scrollbar (#73899)

parent 527a2611
......@@ -26,6 +26,9 @@ const Duration _kScrollbarTimeToFade = Duration(milliseconds: 600);
///
/// {@macro flutter.widgets.Scrollbar}
///
/// Dynamically changes to an iOS style scrollbar that looks like
/// [CupertinoScrollbar] on the iOS platform.
///
/// The color of the Scrollbar will change when dragged. A hover animation is
/// also triggered when used on web and desktop platforms. A scrollbar track
/// can also been drawn when triggered by a hover event, which is controlled by
......@@ -42,7 +45,7 @@ const Duration _kScrollbarTimeToFade = Duration(milliseconds: 600);
/// * [CupertinoScrollbar], an iOS style scrollbar.
/// * [ListView], which displays a linear, scrollable list of children.
/// * [GridView], which displays a 2 dimensional, scrollable array of children.
class Scrollbar extends RawScrollbar {
class Scrollbar extends StatefulWidget {
/// Creates a material design scrollbar that by default will connect to the
/// closest Scrollable descendant of [child].
///
......@@ -58,6 +61,90 @@ class Scrollbar extends RawScrollbar {
/// except for when executing on [TargetPlatform.android], which will render the
/// thumb without a radius.
const Scrollbar({
Key? key,
required this.child,
this.controller,
this.isAlwaysShown,
this.showTrackOnHover,
this.hoverThickness,
this.thickness,
this.radius,
}) : super(key: key);
/// {@macro flutter.widgets.Scrollbar.child}
final Widget child;
/// {@macro flutter.widgets.Scrollbar.controller}
final ScrollController? controller;
/// {@macro flutter.widgets.Scrollbar.isAlwaysShown}
final bool? isAlwaysShown;
/// 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.
final bool? showTrackOnHover;
/// The thickness of the scrollbar when a hover state is active and
/// [showTrackOnHover] is true.
///
/// If this property is null, then [ScrollbarThemeData.thickness] of
/// [ThemeData.scrollbarTheme] is used to resolve a thickness. If that is also
/// null, the default value is 12.0 pixels.
final double? hoverThickness;
/// The thickness of the scrollbar in the cross axis of the scrollable.
///
/// If null, the default value is platform dependent. On [TargetPlatform.android],
/// the default thickness is 4.0 pixels. On [TargetPlatform.iOS],
/// [CupertinoScrollbar.defaultThickness] is used. The remaining platforms have a
/// default thickness of 8.0 pixels.
final double? thickness;
/// The color of the scrollbar thumb.
///
/// If null, the default value is platform dependent. On [TargetPlatform.android],
/// no radius is applied to the scrollbar thumb. On [TargetPlatform.iOS],
/// [CupertinoScrollbar.defaultRadius] is used. The remaining platforms have a
/// default [Radius.circular] of 8.0 pixels.
final Radius? radius;
@override
_ScrollbarState createState() => _ScrollbarState();
}
class _ScrollbarState extends State<Scrollbar> {
bool get _useCupertinoScrollbar => Theme.of(context).platform == TargetPlatform.iOS;
@override
Widget build(BuildContext context) {
if (_useCupertinoScrollbar) {
return CupertinoScrollbar(
child: widget.child,
isAlwaysShown: widget.isAlwaysShown ?? false,
thickness: widget.thickness ?? CupertinoScrollbar.defaultThickness,
thicknessWhileDragging: widget.thickness ?? CupertinoScrollbar.defaultThicknessWhileDragging,
radius: widget.radius ?? CupertinoScrollbar.defaultRadius,
radiusWhileDragging: widget.radius ?? CupertinoScrollbar.defaultRadiusWhileDragging,
controller: widget.controller,
);
}
return _MaterialScrollbar(
child: widget.child,
controller: widget.controller,
isAlwaysShown: widget.isAlwaysShown,
showTrackOnHover: widget.showTrackOnHover,
hoverThickness: widget.hoverThickness,
thickness: widget.thickness,
radius: widget.radius,
);
}
}
class _MaterialScrollbar extends RawScrollbar {
const _MaterialScrollbar({
Key? key,
required Widget child,
ScrollController? controller,
......@@ -78,26 +165,14 @@ class Scrollbar extends RawScrollbar {
pressDuration: Duration.zero,
);
/// 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.
final bool? showTrackOnHover;
/// The thickness of the scrollbar when a hover state is active and
/// [showTrackOnHover] is true.
///
/// If this property is null, then [ScrollbarThemeData.thickness] of
/// [ThemeData.scrollbarTheme] is used to resolve a thickness. If that is also
/// null, the default value is 12.0 pixels.
final double? hoverThickness;
@override
_ScrollbarState createState() => _ScrollbarState();
_MaterialScrollbarState createState() => _MaterialScrollbarState();
}
class _ScrollbarState extends RawScrollbarState<Scrollbar> {
class _MaterialScrollbarState extends RawScrollbarState<_MaterialScrollbar> {
late AnimationController _hoverAnimationController;
bool _dragIsActive = false;
bool _hoverIsActive = false;
......
......@@ -613,14 +613,17 @@ class RawScrollbar extends StatefulWidget {
assert(pressDuration != null),
super(key: key);
/// {@template flutter.widgets.Scrollbar.child}
/// The widget below this widget in the tree.
///
/// The scrollbar will be stacked on top of this child. This child (and its
/// subtree) should include a source of [ScrollNotification] notifications.
///
/// Typically a [ListView] or [CustomScrollView].
/// {@endtemplate}
final Widget child;
/// {@template flutter.widgets.Scrollbar.controller}
/// The [ScrollController] used to implement Scrollbar dragging.
///
/// If nothing is passed to controller, the default behavior is to automatically
......@@ -670,8 +673,10 @@ class RawScrollbar extends StatefulWidget {
/// }
/// ```
/// {@end-tool}
/// {@endtemplate}
final ScrollController? controller;
/// {@template flutter.widgets.Scrollbar.isAlwaysShown}
/// Indicates that the scrollbar should be visible, even when a scroll is not
/// underway.
///
......@@ -727,6 +732,7 @@ class RawScrollbar extends StatefulWidget {
/// }
/// ```
/// {@end-tool}
/// {@endtemplate}
final bool? isAlwaysShown;
/// The [Radius] of the scrollbar thumb's rounded rectangle corners.
......
......@@ -4,6 +4,8 @@
import 'dart:ui' as ui;
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter_test/flutter_test.dart';
......@@ -919,7 +921,7 @@ void main() {
},
variant: const TargetPlatformVariant(<TargetPlatform>{
TargetPlatform.iOS,
TargetPlatform.linux,
}),
);
......@@ -989,4 +991,72 @@ void main() {
TargetPlatform.fuchsia,
}),
);
testWidgets('Adaptive scrollbar', (WidgetTester tester) async {
Widget viewWithScroll(TargetPlatform platform) {
return _buildBoilerplate(
child: Theme(
data: ThemeData(
platform: platform
),
child: const Scrollbar(
child: SingleChildScrollView(
child: SizedBox(width: 4000.0, height: 4000.0),
),
),
),
);
}
await tester.pumpWidget(viewWithScroll(TargetPlatform.android));
await tester.drag(find.byType(SingleChildScrollView), const Offset(0.0, -10.0));
await tester.pump();
// Scrollbar fully showing
await tester.pump(const Duration(milliseconds: 500));
expect(find.byType(Scrollbar), paints..rect());
await tester.pumpWidget(viewWithScroll(TargetPlatform.iOS));
final TestGesture gesture = await tester.startGesture(
tester.getCenter(find.byType(SingleChildScrollView))
);
await gesture.moveBy(const Offset(0.0, -10.0));
await tester.drag(find.byType(SingleChildScrollView), const Offset(0.0, -10.0));
await tester.pump();
await tester.pump(const Duration(milliseconds: 200));
expect(find.byType(Scrollbar), paints..rrect());
expect(find.byType(CupertinoScrollbar), paints..rrect());
await gesture.up();
await tester.pumpAndSettle();
});
testWidgets('Scrollbar passes controller to CupertinoScrollbar', (WidgetTester tester) async {
final ScrollController controller = ScrollController();
Widget viewWithScroll(TargetPlatform? platform) {
return _buildBoilerplate(
child: Theme(
data: ThemeData(
platform: platform
),
child: Scrollbar(
controller: controller,
child: const SingleChildScrollView(
child: SizedBox(width: 4000.0, height: 4000.0),
),
),
),
);
}
await tester.pumpWidget(viewWithScroll(debugDefaultTargetPlatformOverride));
final TestGesture gesture = await tester.startGesture(
tester.getCenter(find.byType(SingleChildScrollView))
);
await gesture.moveBy(const Offset(0.0, -10.0));
await tester.drag(find.byType(SingleChildScrollView), const Offset(0.0, -10.0));
await tester.pump();
await tester.pump(const Duration(milliseconds: 200));
expect(find.byType(CupertinoScrollbar), paints..rrect());
final CupertinoScrollbar scrollbar = tester.widget<CupertinoScrollbar>(find.byType(CupertinoScrollbar));
expect(scrollbar.controller, isNotNull);
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS }));
}
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