Skip to content

Commit

Permalink
Apply PrimaryScrollController updates to SingleChildScrollView (#106430)
Browse files Browse the repository at this point in the history
  • Loading branch information
Piinks authored Jun 23, 2022
1 parent 3022db2 commit 9f4b9bf
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ class ScrollMetricsDemoState extends State<ScrollMetricsDemo> {
height: windowSize,
width: double.infinity,
child: const SingleChildScrollView(
primary: true,
child: FlutterLogo(
size: 300.0,
),
Expand Down
39 changes: 17 additions & 22 deletions packages/flutter/lib/src/widgets/single_child_scroll_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ class SingleChildScrollView extends StatelessWidget {
this.scrollDirection = Axis.vertical,
this.reverse = false,
this.padding,
bool? primary,
this.primary,
this.physics,
this.controller,
this.child,
Expand All @@ -153,11 +153,12 @@ class SingleChildScrollView extends StatelessWidget {
}) : assert(scrollDirection != null),
assert(dragStartBehavior != null),
assert(clipBehavior != null),
assert(!(controller != null && (primary ?? false)),
'Primary ScrollViews obtain their ScrollController via inheritance from a PrimaryScrollController widget. '
'You cannot both set primary to true and pass an explicit controller.',
),
primary = primary ?? controller == null && identical(scrollDirection, Axis.vertical);
assert(
!(controller != null && (primary ?? false)),
'Primary ScrollViews obtain their ScrollController via inheritance '
'from a PrimaryScrollController widget. You cannot both set primary to '
'true and pass an explicit controller.',
);

/// The axis along which the scroll view scrolls.
///
Expand Down Expand Up @@ -195,20 +196,8 @@ class SingleChildScrollView extends StatelessWidget {
/// [ScrollController.animateTo]).
final ScrollController? controller;

/// Whether this is the primary scroll view associated with the parent
/// [PrimaryScrollController].
///
/// When true, the scroll view is used for default [ScrollAction]s. If a
/// ScrollAction is not handled by an otherwise focused part of the application,
/// the ScrollAction will be evaluated using this scroll view, for example,
/// when executing [Shortcuts] key events like page up and down.
///
/// On iOS, this identifies the scroll view that will scroll to top in
/// response to a tap in the status bar.
///
/// Defaults to true when [scrollDirection] is vertical and [controller] is
/// not specified.
final bool primary;
/// {@macro flutter.widgets.scroll_view.primary}
final bool? primary;

/// How the scroll view should respond to user input.
///
Expand Down Expand Up @@ -248,9 +237,13 @@ class SingleChildScrollView extends StatelessWidget {
if (padding != null) {
contents = Padding(padding: padding!, child: contents);
}
final ScrollController? scrollController = primary
final bool effectivePrimary = primary
?? controller == null && PrimaryScrollController.shouldInherit(context, scrollDirection);

final ScrollController? scrollController = effectivePrimary
? PrimaryScrollController.of(context)
: controller;

Widget scrollable = Scrollable(
dragStartBehavior: dragStartBehavior,
axisDirection: axisDirection,
Expand Down Expand Up @@ -280,7 +273,9 @@ class SingleChildScrollView extends StatelessWidget {
);
}

return primary && scrollController != null
return effectivePrimary && scrollController != null
// Further descendant ScrollViews will not inherit the same
// PrimaryScrollController
? PrimaryScrollController.none(child: scrollable)
: scrollable;
}
Expand Down
1 change: 1 addition & 0 deletions packages/flutter/test/widgets/scrollbar_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1079,6 +1079,7 @@ void main() {
isAlwaysShown: true,
controller: scrollController,
child: const SingleChildScrollView(
primary: true,
child: SizedBox(width: 4000.0, height: 4000.0),
),
),
Expand Down
43 changes: 37 additions & 6 deletions packages/flutter/test/widgets/single_child_scroll_view_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,19 @@ class TestScrollController extends ScrollController {
}
}

Widget primaryScrollControllerBoilerplate({ required Widget child, required ScrollController controller }) {
return Directionality(
textDirection: TextDirection.ltr,
child: MediaQuery(
data: const MediaQueryData(),
child: PrimaryScrollController(
controller: controller,
child: child,
),
),
);
}

void main() {
testWidgets('SingleChildScrollView overflow and clipRect test', (WidgetTester tester) async {
// the test widowSize is Size(800.0, 600.0)
Expand Down Expand Up @@ -210,23 +223,41 @@ void main() {
));
});

testWidgets('Vertical SingleChildScrollViews are primary by default', (WidgetTester tester) async {
testWidgets('Vertical SingleChildScrollViews are not primary by default', (WidgetTester tester) async {
const SingleChildScrollView view = SingleChildScrollView();
expect(view.primary, isTrue);
expect(view.primary, isNull);
});

testWidgets('Horizontal SingleChildScrollViews are non-primary by default', (WidgetTester tester) async {
testWidgets('Horizontal SingleChildScrollViews are not primary by default', (WidgetTester tester) async {
const SingleChildScrollView view = SingleChildScrollView(scrollDirection: Axis.horizontal);
expect(view.primary, isFalse);
expect(view.primary, isNull);
});

testWidgets('SingleChildScrollViews with controllers are non-primary by default', (WidgetTester tester) async {
testWidgets('SingleChildScrollViews with controllers are not primary by default', (WidgetTester tester) async {
final SingleChildScrollView view = SingleChildScrollView(
controller: ScrollController(),
);
expect(view.primary, isFalse);
expect(view.primary, isNull);
});

testWidgets('Vertical SingleChildScrollViews use PrimaryScrollController by default on mobile', (WidgetTester tester) async {
final ScrollController controller = ScrollController();
await tester.pumpWidget(primaryScrollControllerBoilerplate(
child: const SingleChildScrollView(),
controller: controller,
));
expect(controller.hasClients, isTrue);
}, variant: TargetPlatformVariant.mobile());

testWidgets("Vertical SingleChildScrollViews don't use PrimaryScrollController by default on desktop", (WidgetTester tester) async {
final ScrollController controller = ScrollController();
await tester.pumpWidget(primaryScrollControllerBoilerplate(
child: const SingleChildScrollView(),
controller: controller,
));
expect(controller.hasClients, isFalse);
}, variant: TargetPlatformVariant.desktop());

testWidgets('Nested scrollables have a null PrimaryScrollController', (WidgetTester tester) async {
const Key innerKey = Key('inner');
final ScrollController primaryScrollController = ScrollController();
Expand Down

0 comments on commit 9f4b9bf

Please sign in to comment.