Fix docs and error messages for scroll directions + sample code (#123819)
Fix docs and error messages for scroll directions + sample code
diff --git a/examples/api/lib/painting/axis_direction/axis_direction.0.dart b/examples/api/lib/painting/axis_direction/axis_direction.0.dart
new file mode 100644
index 0000000..8a4e1f2
--- /dev/null
+++ b/examples/api/lib/painting/axis_direction/axis_direction.0.dart
@@ -0,0 +1,191 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Flutter code sample for [AxisDirection]s.
+
+import 'package:flutter/material.dart';
+
+void main() => runApp(const ExampleApp());
+
+class ExampleApp extends StatelessWidget {
+ const ExampleApp({super.key});
+
+ static const String _title = 'Flutter Code Sample';
+
+ @override
+ Widget build(BuildContext context) {
+ return const MaterialApp(
+ title: _title,
+ home: MyWidget(),
+ );
+ }
+}
+
+class MyWidget extends StatefulWidget {
+ const MyWidget({ super.key });
+
+ @override
+ State<MyWidget> createState() => _MyWidgetState();
+}
+
+class _MyWidgetState extends State<MyWidget> {
+ final List<String> _alphabet = <String>[
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
+ 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
+ ];
+ final Widget _spacer = const SizedBox.square(dimension: 10);
+ AxisDirection _axisDirection = AxisDirection.down;
+
+ Widget _getArrows() {
+ final Widget arrow;
+ switch(_axisDirection) {
+ case AxisDirection.up:
+ arrow = const Icon(Icons.arrow_upward_rounded);
+ return Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: <Widget>[ arrow, arrow ],
+ );
+ case AxisDirection.down:
+ arrow = const Icon(Icons.arrow_downward_rounded);
+ return Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: <Widget>[ arrow, arrow ],
+ );
+ case AxisDirection.left:
+ arrow = const Icon(Icons.arrow_back_rounded);
+ return Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: <Widget>[ arrow, arrow ],
+ );
+ case AxisDirection.right:
+ arrow = const Icon(Icons.arrow_forward_rounded);
+ return Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: <Widget>[ arrow, arrow ],
+ );
+ }
+ }
+
+ void _onAxisDirectionChanged(AxisDirection? axisDirection) {
+ if (axisDirection != null && axisDirection != _axisDirection) {
+ setState(() {
+ // Respond to change in axis direction.
+ _axisDirection = axisDirection;
+ });
+ }
+ }
+
+ Widget _getLeading() {
+ return Container(
+ color: Colors.blue[100],
+ padding: const EdgeInsets.all(8.0),
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: <Widget>[
+ Text(axisDirectionToAxis(_axisDirection).toString()),
+ _spacer,
+ Text(_axisDirection.toString()),
+ _spacer,
+ const Text('GrowthDirection.forward'),
+ _spacer,
+ _getArrows(),
+ ],
+ ),
+ );
+ }
+
+ Widget _getRadioRow() {
+ return DefaultTextStyle(
+ style: const TextStyle(fontWeight: FontWeight.bold, color: Colors.white),
+ child: RadioTheme(
+ data: RadioThemeData(
+ fillColor: MaterialStateProperty.all<Color>(Colors.white),
+ ),
+ child: Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceAround,
+ children: <Widget>[
+ Radio<AxisDirection>(
+ value: AxisDirection.up,
+ groupValue: _axisDirection,
+ onChanged: _onAxisDirectionChanged,
+ ),
+ const Text('up'),
+ _spacer,
+ Radio<AxisDirection>(
+ value: AxisDirection.down,
+ groupValue: _axisDirection,
+ onChanged: _onAxisDirectionChanged,
+ ),
+ const Text('down'),
+ _spacer,
+ Radio<AxisDirection>(
+ value: AxisDirection.left,
+ groupValue: _axisDirection,
+ onChanged: _onAxisDirectionChanged,
+ ),
+ const Text('left'),
+ _spacer,
+ Radio<AxisDirection>(
+ value: AxisDirection.right,
+ groupValue: _axisDirection,
+ onChanged: _onAxisDirectionChanged,
+ ),
+ const Text('right'),
+ _spacer,
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ title: const Text('AxisDirections'),
+ bottom: PreferredSize(
+ preferredSize: const Size.fromHeight(50),
+ child: Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: _getRadioRow(),
+ ),
+ ),
+ ),
+ // Also works for ListView.builder, which creates a SliverList for itself.
+ // A CustomScrollView allows multiple slivers to be composed together.
+ body: CustomScrollView(
+ // This method is available to conveniently determine if an scroll
+ // view is reversed by its AxisDirection.
+ reverse: axisDirectionIsReversed(_axisDirection),
+ // This method is available to conveniently convert an AxisDirection
+ // into its Axis.
+ scrollDirection: axisDirectionToAxis(_axisDirection),
+ slivers: <Widget>[
+ SliverList.builder(
+ itemCount: 27,
+ itemBuilder: (BuildContext context, int index) {
+ final Widget child;
+ if (index == 0) {
+ child = _getLeading();
+ } else {
+ child = Container(
+ color: index.isEven ? Colors.amber[100] : Colors.amberAccent,
+ padding: const EdgeInsets.all(8.0),
+ child: Center(child: Text(_alphabet[index - 1])),
+ );
+ }
+ return Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: child,
+ );
+ }
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/examples/api/lib/rendering/growth_direction/growth_direction.0.dart b/examples/api/lib/rendering/growth_direction/growth_direction.0.dart
new file mode 100644
index 0000000..b58c9fc
--- /dev/null
+++ b/examples/api/lib/rendering/growth_direction/growth_direction.0.dart
@@ -0,0 +1,225 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Flutter code sample for [GrowthDirection]s.
+
+import 'package:flutter/material.dart';
+import 'package:flutter/rendering.dart';
+
+void main() => runApp(const ExampleApp());
+
+class ExampleApp extends StatelessWidget {
+ const ExampleApp({super.key});
+
+ static const String _title = 'Flutter Code Sample';
+
+ @override
+ Widget build(BuildContext context) {
+ return const MaterialApp(
+ title: _title,
+ home: MyWidget(),
+ );
+ }
+}
+
+class MyWidget extends StatefulWidget {
+ const MyWidget({ super.key });
+
+ @override
+ State<MyWidget> createState() => _MyWidgetState();
+}
+
+class _MyWidgetState extends State<MyWidget> {
+ final List<String> _alphabet = <String>[
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
+ 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
+ ];
+ final Widget _spacer = const SizedBox.square(dimension: 10);
+ final UniqueKey _center = UniqueKey();
+ AxisDirection _axisDirection = AxisDirection.down;
+
+ Widget _getArrows(AxisDirection axisDirection) {
+ final Widget arrow;
+ switch(axisDirection) {
+ case AxisDirection.up:
+ arrow = const Icon(Icons.arrow_upward_rounded);
+ return Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: <Widget>[ arrow, arrow ],
+ );
+ case AxisDirection.down:
+ arrow = const Icon(Icons.arrow_downward_rounded);
+ return Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: <Widget>[ arrow, arrow ],
+ );
+ case AxisDirection.left:
+ arrow = const Icon(Icons.arrow_back_rounded);
+ return Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: <Widget>[ arrow, arrow ],
+ );
+ case AxisDirection.right:
+ arrow = const Icon(Icons.arrow_forward_rounded);
+ return Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: <Widget>[ arrow, arrow ],
+ );
+ }
+ }
+
+ void _onAxisDirectionChanged(AxisDirection? axisDirection) {
+ if (axisDirection != null && axisDirection != _axisDirection) {
+ setState(() {
+ // Respond to change in axis direction.
+ _axisDirection = axisDirection;
+ });
+ }
+ }
+
+ Widget _getLeading(SliverConstraints constraints, bool isForward) {
+ return Container(
+ color: isForward ? Colors.orange[300] : Colors.green[400],
+ padding: const EdgeInsets.all(8.0),
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: <Widget>[
+ Text(constraints.axis.toString()),
+ _spacer,
+ Text(constraints.axisDirection.toString()),
+ _spacer,
+ Text(constraints.growthDirection.toString()),
+ _spacer,
+ _getArrows(isForward
+ ? _axisDirection
+ // This method is available to conveniently flip an AxisDirection
+ // into its opposite direction.
+ : flipAxisDirection(_axisDirection),
+ ),
+ ],
+ ),
+ );
+ }
+
+ Widget _getRadioRow() {
+ return DefaultTextStyle(
+ style: const TextStyle(fontWeight: FontWeight.bold, color: Colors.white),
+ child: RadioTheme(
+ data: RadioThemeData(
+ fillColor: MaterialStateProperty.all<Color>(Colors.white),
+ ),
+ child: Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceAround,
+ children: <Widget>[
+ Radio<AxisDirection>(
+ value: AxisDirection.up,
+ groupValue: _axisDirection,
+ onChanged: _onAxisDirectionChanged,
+ ),
+ const Text('up'),
+ _spacer,
+ Radio<AxisDirection>(
+ value: AxisDirection.down,
+ groupValue: _axisDirection,
+ onChanged: _onAxisDirectionChanged,
+ ),
+ const Text('down'),
+ _spacer,
+ Radio<AxisDirection>(
+ value: AxisDirection.left,
+ groupValue: _axisDirection,
+ onChanged: _onAxisDirectionChanged,
+ ),
+ const Text('left'),
+ _spacer,
+ Radio<AxisDirection>(
+ value: AxisDirection.right,
+ groupValue: _axisDirection,
+ onChanged: _onAxisDirectionChanged,
+ ),
+ const Text('right'),
+ _spacer,
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+
+ Widget _getList({ required bool isForward }) {
+ // The SliverLayoutBuilder is not necessary, and is here to allow us to see
+ // the SliverConstraints & directional information that is provided to the
+ // SliverList when laying out.
+ return SliverLayoutBuilder(
+ builder: (BuildContext context, SliverConstraints constraints) {
+ return SliverList.builder(
+ itemCount: 27,
+ itemBuilder: (BuildContext context, int index) {
+ final Widget child;
+ if (index == 0) {
+ child = _getLeading(constraints, isForward);
+ } else {
+ child = Container(
+ color: isForward
+ ? (index.isEven ? Colors.amber[100] : Colors.amberAccent)
+ : (index.isEven ? Colors.green[100] : Colors.lightGreen),
+ padding: const EdgeInsets.all(8.0),
+ child: Center(child: Text(_alphabet[index - 1])),
+ );
+ }
+ return Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: child,
+ );
+ },
+ );
+ },
+ );
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ title: const Text('GrowthDirections'),
+ bottom: PreferredSize(
+ preferredSize: const Size.fromHeight(50),
+ child: Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: _getRadioRow(),
+ ),
+ ),
+ ),
+ body: CustomScrollView(
+ // This method is available to conveniently determine if an scroll
+ // view is reversed by its AxisDirection.
+ reverse: axisDirectionIsReversed(_axisDirection),
+ // This method is available to conveniently convert an AxisDirection
+ // into its Axis.
+ scrollDirection: axisDirectionToAxis(_axisDirection),
+ // Places the leading edge of the center sliver in the middle of the
+ // viewport. Changing this value between 0.0 (the default) and 1.0
+ // changes the position of the inflection point between GrowthDirections
+ // in the viewport when the slivers are laid out.
+ anchor: 0.5,
+ center: _center,
+ slivers: <Widget>[
+ _getList(isForward: false),
+ SliverToBoxAdapter(
+ // This sliver will be located at the anchor. The scroll position
+ // will progress in either direction from this point.
+ key: _center,
+ child: const Padding(
+ padding: EdgeInsets.all(8.0),
+ child: Center(child: Text('0', style: TextStyle(fontWeight: FontWeight.bold))),
+ ),
+ ),
+ _getList(isForward: true),
+ ],
+ ),
+ );
+ }
+}
diff --git a/examples/api/lib/rendering/scroll_direction/scroll_direction.0.dart b/examples/api/lib/rendering/scroll_direction/scroll_direction.0.dart
new file mode 100644
index 0000000..211157d
--- /dev/null
+++ b/examples/api/lib/rendering/scroll_direction/scroll_direction.0.dart
@@ -0,0 +1,207 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Flutter code sample for [ScrollDirection].
+
+import 'package:flutter/material.dart';
+import 'package:flutter/rendering.dart';
+
+void main() => runApp(const ExampleApp());
+
+class ExampleApp extends StatelessWidget {
+ const ExampleApp({super.key});
+
+ static const String _title = 'Flutter Code Sample';
+
+ @override
+ Widget build(BuildContext context) {
+ return const MaterialApp(
+ title: _title,
+ home: MyWidget(),
+ );
+ }
+}
+
+class MyWidget extends StatefulWidget {
+ const MyWidget({ super.key });
+
+ @override
+ State<MyWidget> createState() => _MyWidgetState();
+}
+
+class _MyWidgetState extends State<MyWidget> {
+ final List<String> alphabet = <String>[
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
+ 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
+ ];
+ final Widget spacer = const SizedBox.square(dimension: 10);
+ ScrollDirection scrollDirection = ScrollDirection.idle;
+ AxisDirection _axisDirection = AxisDirection.down;
+
+ Widget _getArrows() {
+ final Widget arrow;
+ switch(_axisDirection) {
+ case AxisDirection.up:
+ arrow = const Icon(Icons.arrow_upward_rounded);
+ return Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: <Widget>[ arrow, arrow ],
+ );
+ case AxisDirection.down:
+ arrow = const Icon(Icons.arrow_downward_rounded);
+ return Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: <Widget>[ arrow, arrow ],
+ );
+ case AxisDirection.left:
+ arrow = const Icon(Icons.arrow_back_rounded);
+ return Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: <Widget>[ arrow, arrow ],
+ );
+ case AxisDirection.right:
+ arrow = const Icon(Icons.arrow_forward_rounded);
+ return Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: <Widget>[ arrow, arrow ],
+ );
+ }
+ }
+
+ void _onAxisDirectionChanged(AxisDirection? axisDirection) {
+ if (axisDirection != null && axisDirection != _axisDirection) {
+ setState(() {
+ // Respond to change in axis direction.
+ _axisDirection = axisDirection;
+ });
+ }
+ }
+
+ Widget _getLeading() {
+ return Container(
+ color: Colors.blue[100],
+ padding: const EdgeInsets.all(8.0),
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: <Widget>[
+ Text(axisDirectionToAxis(_axisDirection).toString()),
+ spacer,
+ Text(_axisDirection.toString()),
+ spacer,
+ const Text('GrowthDirection.forward'),
+ spacer,
+ Text(scrollDirection.toString()),
+ spacer,
+ _getArrows(),
+ ],
+ ),
+ );
+ }
+
+ Widget _getRadioRow() {
+ return DefaultTextStyle(
+ style: const TextStyle(fontWeight: FontWeight.bold, color: Colors.white),
+ child: RadioTheme(
+ data: RadioThemeData(
+ fillColor: MaterialStateProperty.all<Color>(Colors.white),
+ ),
+ child: Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceAround,
+ children: <Widget>[
+ Radio<AxisDirection>(
+ value: AxisDirection.up,
+ groupValue: _axisDirection,
+ onChanged: _onAxisDirectionChanged,
+ ),
+ const Text('up'),
+ spacer,
+ Radio<AxisDirection>(
+ value: AxisDirection.down,
+ groupValue: _axisDirection,
+ onChanged: _onAxisDirectionChanged,
+ ),
+ const Text('down'),
+ spacer,
+ Radio<AxisDirection>(
+ value: AxisDirection.left,
+ groupValue: _axisDirection,
+ onChanged: _onAxisDirectionChanged,
+ ),
+ const Text('left'),
+ spacer,
+ Radio<AxisDirection>(
+ value: AxisDirection.right,
+ groupValue: _axisDirection,
+ onChanged: _onAxisDirectionChanged,
+ ),
+ const Text('right'),
+ spacer,
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+
+ bool _handleNotification(UserScrollNotification notification) {
+ if (notification.direction != scrollDirection) {
+ setState((){
+ scrollDirection = notification.direction;
+ });
+ }
+ return false;
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ title: const Text('ScrollDirections'),
+ bottom: PreferredSize(
+ preferredSize: const Size.fromHeight(50),
+ child: Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: _getRadioRow(),
+ ),
+ ),
+ ),
+ body: NotificationListener<UserScrollNotification>(
+ onNotification: _handleNotification,
+ // Also works for ListView.builder, which creates a SliverList for itself.
+ // A CustomScrollView allows multiple slivers to be composed together.
+ child: CustomScrollView(
+ // This method is available to conveniently determine if an scroll
+ // view is reversed by its AxisDirection.
+ reverse: axisDirectionIsReversed(_axisDirection),
+ // This method is available to conveniently convert an AxisDirection
+ // into its Axis.
+ scrollDirection: axisDirectionToAxis(_axisDirection),
+ slivers: <Widget>[
+ SliverList.builder(
+ itemCount: 27,
+ itemBuilder: (BuildContext context, int index) {
+ final Widget child;
+ if (index == 0) {
+ child = _getLeading();
+ } else {
+ child = Container(
+ color: index.isEven ? Colors.amber[100] : Colors.amberAccent,
+ padding: const EdgeInsets.all(8.0),
+ child: Center(child: Text(alphabet[index - 1])),
+ );
+ }
+ return Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: child,
+ );
+ }
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+}
diff --git a/examples/api/test/painting/axis_direction.0_test.dart b/examples/api/test/painting/axis_direction.0_test.dart
new file mode 100644
index 0000000..0a346c1
--- /dev/null
+++ b/examples/api/test/painting/axis_direction.0_test.dart
@@ -0,0 +1,37 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter/material.dart';
+import 'package:flutter/rendering.dart';
+import 'package:flutter_api_samples/painting/axis_direction/axis_direction.0.dart' as example;
+import 'package:flutter_test/flutter_test.dart';
+
+void main() {
+ testWidgets('Example app has radio buttons to toggle AxisDirection', (WidgetTester tester) async {
+ await tester.pumpWidget(
+ const example.ExampleApp(),
+ );
+
+ expect(find.byType(Radio<AxisDirection>), findsNWidgets(4));
+ final RenderViewport viewport = tester.renderObject(find.byType(Viewport));
+ expect(find.text('AxisDirection.down'), findsOneWidget);
+ expect(find.text('Axis.vertical'), findsOneWidget);
+ expect(find.text('GrowthDirection.forward'), findsOneWidget);
+ expect(find.byIcon(Icons.arrow_downward_rounded), findsNWidgets(2));
+ expect(viewport.axisDirection, AxisDirection.down);
+
+ await tester.tap(
+ find.byWidgetPredicate((Widget widget) {
+ return widget is Radio<AxisDirection> && widget.value == AxisDirection.up;
+ })
+ );
+ await tester.pumpAndSettle();
+
+ expect(find.text('AxisDirection.up'), findsOneWidget);
+ expect(find.text('Axis.vertical'), findsOneWidget);
+ expect(find.text('GrowthDirection.forward'), findsOneWidget);
+ expect(find.byIcon(Icons.arrow_upward_rounded), findsNWidgets(2));
+ expect(viewport.axisDirection, AxisDirection.up);
+ });
+}
diff --git a/examples/api/test/rendering/growth_direction.0_test.dart b/examples/api/test/rendering/growth_direction.0_test.dart
new file mode 100644
index 0000000..3fd61fe
--- /dev/null
+++ b/examples/api/test/rendering/growth_direction.0_test.dart
@@ -0,0 +1,42 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter/material.dart';
+import 'package:flutter/rendering.dart';
+import 'package:flutter_api_samples/rendering/growth_direction/growth_direction.0.dart' as example;
+import 'package:flutter_test/flutter_test.dart';
+
+void main() {
+ testWidgets('Example app has GrowthDirections represented', (WidgetTester tester) async {
+ await tester.pumpWidget(
+ const example.ExampleApp(),
+ );
+
+ final RenderViewport viewport = tester.renderObject(find.byType(Viewport));
+ expect(find.text('AxisDirection.down'), findsNWidgets(2));
+ expect(find.text('Axis.vertical'), findsNWidgets(2));
+ expect(find.text('GrowthDirection.forward'), findsOneWidget);
+ expect(find.text('GrowthDirection.reverse'), findsOneWidget);
+ expect(find.byIcon(Icons.arrow_upward_rounded), findsNWidgets(2));
+ expect(find.byIcon(Icons.arrow_downward_rounded), findsNWidgets(2));
+ expect(viewport.axisDirection, AxisDirection.down);
+ expect(viewport.anchor, 0.5);
+ expect(viewport.center, isNotNull);
+
+ await tester.tap(
+ find.byWidgetPredicate((Widget widget) {
+ return widget is Radio<AxisDirection> && widget.value == AxisDirection.up;
+ })
+ );
+ await tester.pumpAndSettle();
+
+ expect(find.text('AxisDirection.up'), findsNWidgets(2));
+ expect(find.text('Axis.vertical'), findsNWidgets(2));
+ expect(find.text('GrowthDirection.forward'), findsOneWidget);
+ expect(find.text('GrowthDirection.reverse'), findsOneWidget);
+ expect(find.byIcon(Icons.arrow_upward_rounded), findsNWidgets(2));
+ expect(find.byIcon(Icons.arrow_downward_rounded), findsNWidgets(2));
+ expect(viewport.axisDirection, AxisDirection.up);
+ });
+}
diff --git a/examples/api/test/rendering/scroll_direction.0_test.dart b/examples/api/test/rendering/scroll_direction.0_test.dart
new file mode 100644
index 0000000..5e4075c
--- /dev/null
+++ b/examples/api/test/rendering/scroll_direction.0_test.dart
@@ -0,0 +1,39 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter/material.dart';
+import 'package:flutter/rendering.dart';
+import 'package:flutter_api_samples/rendering/scroll_direction/scroll_direction.0.dart' as example;
+import 'package:flutter_test/flutter_test.dart';
+
+void main() {
+ testWidgets('Example app has ScrollDirection represented', (WidgetTester tester) async {
+ await tester.pumpWidget(
+ const example.ExampleApp(),
+ );
+
+ expect(find.byType(Radio<AxisDirection>), findsNWidgets(4));
+ final RenderViewport viewport = tester.renderObject(find.byType(Viewport));
+ expect(find.text('AxisDirection.down'), findsOneWidget);
+ expect(find.text('Axis.vertical'), findsOneWidget);
+ expect(find.text('GrowthDirection.forward'), findsOneWidget);
+ expect(find.text('ScrollDirection.idle'), findsOneWidget);
+ expect(find.byIcon(Icons.arrow_downward_rounded), findsNWidgets(2));
+ expect(viewport.axisDirection, AxisDirection.down);
+
+ await tester.tap(
+ find.byWidgetPredicate((Widget widget) {
+ return widget is Radio<AxisDirection> && widget.value == AxisDirection.up;
+ })
+ );
+ await tester.pumpAndSettle();
+
+ expect(find.text('AxisDirection.up'), findsOneWidget);
+ expect(find.text('Axis.vertical'), findsOneWidget);
+ expect(find.text('GrowthDirection.forward'), findsOneWidget);
+ expect(find.text('ScrollDirection.idle'), findsOneWidget);
+ expect(find.byIcon(Icons.arrow_upward_rounded), findsNWidgets(2));
+ expect(viewport.axisDirection, AxisDirection.up);
+ });
+}
diff --git a/packages/flutter/lib/src/material/input_chip.dart b/packages/flutter/lib/src/material/input_chip.dart
index da909a6..8dcee82 100644
--- a/packages/flutter/lib/src/material/input_chip.dart
+++ b/packages/flutter/lib/src/material/input_chip.dart
@@ -30,8 +30,8 @@
/// Input chips work together with other UI elements. They can appear:
///
/// * In a [Wrap] widget.
-/// * In a horizontally scrollable list, like a [ListView] whose
-/// scrollDirection is [Axis.horizontal].
+/// * In a horizontally scrollable list, for example configured such as a
+/// [ListView] with [ListView.scrollDirection] set to [Axis.horizontal].
///
/// {@tool dartpad}
/// This example shows how to create [InputChip]s with [onSelected] and
diff --git a/packages/flutter/lib/src/painting/basic_types.dart b/packages/flutter/lib/src/painting/basic_types.dart
index 26c67b2..6107e10 100644
--- a/packages/flutter/lib/src/painting/basic_types.dart
+++ b/packages/flutter/lib/src/painting/basic_types.dart
@@ -111,7 +111,7 @@
/// See also:
///
/// * [AxisDirection], which is a directional version of this enum (with values
-/// light left and right, rather than just horizontal).
+/// like left and right, rather than just horizontal).
/// * [TextDirection], which disambiguates between left-to-right horizontal
/// content and right-to-left horizontal content.
enum Axis {
@@ -167,33 +167,102 @@
down,
}
-/// A direction along either the horizontal or vertical [Axis].
+/// A direction along either the horizontal or vertical [Axis] in which the
+/// origin, or zero position, is determined.
+///
+/// This value relates to the direction in which the scroll offset increases
+/// from the origin. This value does not represent the direction of user input
+/// that may be modifying the scroll offset, such as from a drag. For the
+/// active scrolling direction, see [ScrollDirection].
+///
+/// {@tool dartpad}
+/// This sample shows a [CustomScrollView], with [Radio] buttons in the
+/// [AppBar.bottom] that change the [AxisDirection] to illustrate different
+/// configurations.
+///
+/// ** See code in examples/api/lib/painting/axis_direction/axis_direction.0.dart **
+/// {@end-tool}
+///
+/// See also:
+///
+/// * [ScrollDirection], the direction of active scrolling, relative to the positive
+/// scroll offset axis given by an [AxisDirection] and a [GrowthDirection].
+/// * [GrowthDirection], the direction in which slivers and their content are
+/// ordered, relative to the scroll offset axis as specified by
+/// [AxisDirection].
+/// * [CustomScrollView.anchor], the relative position of the zero scroll
+/// offset in a viewport and inflection point for [AxisDirection]s of the
+/// same cardinal [Axis].
+/// * [axisDirectionIsReversed], which returns whether traveling along the
+/// given axis direction visits coordinates along that axis in numerically
+/// decreasing order.
enum AxisDirection {
- /// Zero is at the bottom and positive values are above it: `⇈`
+ /// A direction in the [Axis.vertical] where zero is at the bottom and
+ /// positive values are above it: `⇈`
///
/// Alphabetical content with a [GrowthDirection.forward] would have the A at
- /// the bottom and the Z at the top. This is an unusual configuration.
+ /// the bottom and the Z at the top.
+ ///
+ /// For example, the behavior of a [ListView] with [ListView.reverse] set to
+ /// true would have this axis direction.
+ ///
+ /// See also:
+ ///
+ /// * [axisDirectionIsReversed], which returns whether traveling along the
+ /// given axis direction visits coordinates along that axis in numerically
+ /// decreasing order.
up,
- /// Zero is on the left and positive values are to the right of it: `⇉`
+ /// A direction in the [Axis.horizontal] where zero is on the left and
+ /// positive values are to the right of it: `⇉`
///
/// Alphabetical content with a [GrowthDirection.forward] would have the A on
/// the left and the Z on the right. This is the ordinary reading order for a
/// horizontal set of tabs in an English application, for example.
+ ///
+ /// For example, the behavior of a [ListView] with [ListView.scrollDirection]
+ /// set to [Axis.horizontal] would have this axis direction.
+ ///
+ /// See also:
+ ///
+ /// * [axisDirectionIsReversed], which returns whether traveling along the
+ /// given axis direction visits coordinates along that axis in numerically
+ /// decreasing order.
right,
- /// Zero is at the top and positive values are below it: `⇊`
+ /// A direction in the [Axis.vertical] where zero is at the top and positive
+ /// values are below it: `⇊`
///
/// Alphabetical content with a [GrowthDirection.forward] would have the A at
/// the top and the Z at the bottom. This is the ordinary reading order for a
/// vertical list.
+ ///
+ /// For example, the default behavior of a [ListView] would have this axis
+ /// direction.
+ ///
+ /// See also:
+ ///
+ /// * [axisDirectionIsReversed], which returns whether traveling along the
+ /// given axis direction visits coordinates along that axis in numerically
+ /// decreasing order.
down,
- /// Zero is to the right and positive values are to the left of it: `⇇`
+ /// A direction in the [Axis.horizontal] where zero is to the right and
+ /// positive values are to the left of it: `⇇`
///
/// Alphabetical content with a [GrowthDirection.forward] would have the A at
/// the right and the Z at the left. This is the ordinary reading order for a
/// horizontal set of tabs in a Hebrew application, for example.
+ ///
+ /// For example, the behavior of a [ListView] with [ListView.scrollDirection]
+ /// set to [Axis.horizontal] and [ListView.reverse] set to true would have
+ /// this axis direction.
+ ///
+ /// See also:
+ ///
+ /// * [axisDirectionIsReversed], which returns whether traveling along the
+ /// given axis direction visits coordinates along that axis in numerically
+ /// decreasing order.
left,
}
diff --git a/packages/flutter/lib/src/rendering/sliver.dart b/packages/flutter/lib/src/rendering/sliver.dart
index 590334f..d245116 100644
--- a/packages/flutter/lib/src/rendering/sliver.dart
+++ b/packages/flutter/lib/src/rendering/sliver.dart
@@ -27,15 +27,48 @@
/// [GrowthDirection.reverse] would have the Z at the top (at scroll offset
/// zero) and the A below it.
///
-/// The direction in which the scroll offset increases is given by
-/// [applyGrowthDirectionToAxisDirection].
+/// {@template flutter.rendering.GrowthDirection.sample}
+/// Most scroll views by default are ordered [GrowthDirection.forward].
+/// Changing the default values of [ScrollView.anchor],
+/// [ScrollView.center], or both, can configure a scroll view for
+/// [GrowthDirection.reverse].
+///
+/// {@tool dartpad}
+/// This sample shows a [CustomScrollView], with [Radio] buttons in the
+/// [AppBar.bottom] that change the [AxisDirection] to illustrate different
+/// configurations. The [CustomScrollView.anchor] and [CustomScrollView.center]
+/// properties are also set to have the 0 scroll offset positioned in the middle
+/// of the viewport, with [GrowthDirection.forward] and [GrowthDirection.reverse]
+/// illustrated on either side. The sliver that shares the
+/// [CustomScrollView.center] key is positioned at the [CustomScrollView.anchor].
+///
+/// ** See code in examples/api/lib/rendering/growth_direction/growth_direction.0.dart **
+/// {@end-tool}
+/// {@endtemplate}
+///
+/// See also:
+///
+/// * [applyGrowthDirectionToAxisDirection], which returns the direction in
+/// which the scroll offset increases.
enum GrowthDirection {
/// This sliver's contents are ordered in the same direction as the
- /// [AxisDirection].
+ /// [AxisDirection]. For example, a vertical alphabetical list that is going
+ /// [AxisDirection.down] with a [GrowthDirection.forward] would have the A at
+ /// the top and the Z at the bottom, with the A adjacent to the origin.
+ ///
+ /// See also:
+ ///
+ /// * [applyGrowthDirectionToAxisDirection], which returns the direction in
+ /// which the scroll offset increases.
forward,
/// This sliver's contents are ordered in the opposite direction of the
/// [AxisDirection].
+ ///
+ /// See also:
+ ///
+ /// * [applyGrowthDirectionToAxisDirection], which returns the direction in
+ /// which the scroll offset increases.
reverse,
}
@@ -57,11 +90,12 @@
}
}
-/// Flips the [ScrollDirection] if the [GrowthDirection] is [GrowthDirection.reverse].
+/// Flips the [ScrollDirection] if the [GrowthDirection] is
+/// [GrowthDirection.reverse].
///
/// Specifically, returns `scrollDirection` if `scrollDirection` is
-/// [GrowthDirection.forward], otherwise returns [flipScrollDirection] applied to
-/// `scrollDirection`.
+/// [GrowthDirection.forward], otherwise returns [flipScrollDirection] applied
+/// to `scrollDirection`.
///
/// This function is useful in [RenderSliver] subclasses that are given both an
/// [ScrollDirection] and a [GrowthDirection] and wish to compute the
@@ -135,6 +169,14 @@
/// The direction in which the [scrollOffset] and [remainingPaintExtent]
/// increase.
+ ///
+ /// {@tool dartpad}
+ /// This sample shows a [CustomScrollView], with [Radio] buttons in the
+ /// [AppBar.bottom] that change the [AxisDirection] to illustrate different
+ /// configurations.
+ ///
+ /// ** See code in examples/api/lib/painting/axis_direction/axis_direction.0.dart **
+ /// {@end-tool}
final AxisDirection axisDirection;
/// The direction in which the contents of slivers are ordered, relative to
@@ -158,14 +200,16 @@
///
/// Normally, the absolute zero offset is determined by the viewport's
/// [RenderViewport.center] and [RenderViewport.anchor] properties.
+ ///
+ /// {@macro flutter.rendering.GrowthDirection.sample}
final GrowthDirection growthDirection;
/// The direction in which the user is attempting to scroll, relative to the
/// [axisDirection] and [growthDirection].
///
- /// For example, if [growthDirection] is [GrowthDirection.reverse] and
+ /// For example, if [growthDirection] is [GrowthDirection.forward] and
/// [axisDirection] is [AxisDirection.down], then a
- /// [ScrollDirection.forward] means that the user is scrolling up, in the
+ /// [ScrollDirection.reverse] means that the user is scrolling down, in the
/// positive [scrollOffset] direction.
///
/// If the _user_ is not scrolling, this will return [ScrollDirection.idle]
@@ -176,6 +220,8 @@
/// scroll offset. For example, [RenderSliverFloatingPersistentHeader] will
/// only expand a floating app bar when the [userScrollDirection] is in the
/// positive scroll offset direction.
+ ///
+ /// {@macro flutter.rendering.ScrollDirection.sample}
final ScrollDirection userScrollDirection;
/// The scroll offset, in this sliver's coordinate system, that corresponds to
diff --git a/packages/flutter/lib/src/rendering/viewport.dart b/packages/flutter/lib/src/rendering/viewport.dart
index 1d31f19..bf07a88 100644
--- a/packages/flutter/lib/src/rendering/viewport.dart
+++ b/packages/flutter/lib/src/rendering/viewport.dart
@@ -1222,9 +1222,10 @@
/// given [offset]. As the offset varies, different children are visible through
/// the viewport.
///
-/// [RenderViewport] hosts a bidirectional list of slivers, anchored on a
-/// [center] sliver, which is placed at the zero scroll offset. The center
-/// widget is displayed in the viewport according to the [anchor] property.
+/// [RenderViewport] hosts a bidirectional list of slivers in a single shared
+/// [Axis], anchored on a [center] sliver, which is placed at the zero scroll
+/// offset. The center widget is displayed in the viewport according to the
+/// [anchor] property.
///
/// Slivers that are earlier in the child list than [center] are displayed in
/// reverse order in the reverse [axisDirection] starting from the [center]. For
@@ -1234,6 +1235,8 @@
/// example, in the preceding scenario, the first sliver after [center] is
/// placed below the [center].
///
+/// {@macro flutter.rendering.GrowthDirection.sample}
+///
/// [RenderViewport] cannot contain [RenderBox] children directly. Instead, use
/// a [RenderSliverList], [RenderSliverFixedExtentList], [RenderSliverGrid], or
/// a [RenderSliverToBoxAdapter], for example.
@@ -1323,6 +1326,8 @@
/// vertically centered within the viewport. If the [anchor] is 1.0, and the
/// [axisDirection] is [AxisDirection.right], then the zero scroll offset is
/// on the left edge of the viewport.
+ ///
+ /// {@macro flutter.rendering.GrowthDirection.sample}
double get anchor => _anchor;
double _anchor;
set anchor(double value) {
@@ -1340,10 +1345,15 @@
/// [ViewportOffset.pixels] of [offset] is `0`.
///
/// Children after [center] will be placed in the [axisDirection] relative to
- /// the [center]. Children before [center] will be placed in the opposite of
- /// the [axisDirection] relative to the [center].
+ /// the [center].
///
- /// The [center] must be a child of the viewport.
+ /// Children before [center] will be placed in the opposite of
+ /// the [axisDirection] relative to the [center]. These children above
+ /// [center] will have a growth direction of [GrowthDirection.reverse].
+ ///
+ /// The [center] must be a direct child of the viewport.
+ ///
+ /// {@macro flutter.rendering.GrowthDirection.sample}
RenderSliver? get center => _center;
RenderSliver? _center;
set center(RenderSliver? value) {
diff --git a/packages/flutter/lib/src/rendering/viewport_offset.dart b/packages/flutter/lib/src/rendering/viewport_offset.dart
index 8f5f979..44d5cad 100644
--- a/packages/flutter/lib/src/rendering/viewport_offset.dart
+++ b/packages/flutter/lib/src/rendering/viewport_offset.dart
@@ -8,28 +8,58 @@
/// The direction of a scroll, relative to the positive scroll offset axis given
/// by an [AxisDirection] and a [GrowthDirection].
///
-/// This contrasts to [GrowthDirection] in that it has a third value, [idle],
-/// for the case where no scroll is occurring.
+/// This is similar to [GrowthDirection], but contrasts in that it has a third
+/// value, [idle], for the case where no scroll is occurring.
///
/// This is used by [RenderSliverFloatingPersistentHeader] to only expand when
/// the user is scrolling in the same direction as the detected scroll offset
/// change.
+///
+/// {@template flutter.rendering.ScrollDirection.sample}
+/// {@tool dartpad}
+/// This sample shows a [CustomScrollView], with [Radio] buttons in the
+/// [AppBar.bottom] that change the [AxisDirection] to illustrate different
+/// configurations. With a [NotificationListener] to listen to
+/// [UserScrollNotification]s, which occur when the [ScrollDirection] changes
+/// or stops.
+///
+/// ** See code in examples/api/lib/rendering/scroll_direction/scroll_direction.0.dart **
+/// {@end-tool}
+/// {@endtemplate}
+///
+/// See also:
+///
+/// * [AxisDirection], which is a directional version of this enum (with values
+/// like left and right, rather than just horizontal).
+/// * [GrowthDirection], the direction in which slivers and their content are
+/// ordered, relative to the scroll offset axis as specified by
+/// [AxisDirection].
+/// * [UserScrollNotification], which will notify listeners when the
+/// [ScrollDirection] changes.
enum ScrollDirection {
/// No scrolling is underway.
idle,
- /// Scrolling is happening in the positive scroll offset direction.
- ///
- /// For example, for the [GrowthDirection.forward] part of a vertical
- /// [AxisDirection.down] list, this means the content is moving up, exposing
- /// lower content.
- forward,
-
/// Scrolling is happening in the negative scroll offset direction.
///
/// For example, for the [GrowthDirection.forward] part of a vertical
- /// [AxisDirection.down] list, this means the content is moving down, exposing
- /// earlier content.
+ /// [AxisDirection.down] list, which is the default directional configuration
+ /// of all scroll views, this means the content is going down, exposing
+ /// earlier content as it approaches the zero position.
+ ///
+ /// An anecdote for this most common case is 'forward is toward' the zero
+ /// position.
+ forward,
+
+ /// Scrolling is happening in the positive scroll offset direction.
+ ///
+ /// For example, for the [GrowthDirection.forward] part of a vertical
+ /// [AxisDirection.down] list, which is the default directional configuration
+ /// of all scroll views, this means the content is moving up, exposing
+ /// lower content.
+ ///
+ /// An anecdote for this most common case is reversing, or backing away, from
+ /// the zero position.
reverse,
}
@@ -216,6 +246,8 @@
/// For example, [RenderSliverFloatingPersistentHeader] will only expand a
/// floating app bar when the [userScrollDirection] is in the positive scroll
/// offset direction.
+ ///
+ /// {@macro flutter.rendering.ScrollDirection.sample}
ScrollDirection get userScrollDirection;
/// Whether a viewport is allowed to change [pixels] implicitly to respond to
diff --git a/packages/flutter/lib/src/widgets/animated_scroll_view.dart b/packages/flutter/lib/src/widgets/animated_scroll_view.dart
index 53196a8..7dda551 100644
--- a/packages/flutter/lib/src/widgets/animated_scroll_view.dart
+++ b/packages/flutter/lib/src/widgets/animated_scroll_view.dart
@@ -401,9 +401,7 @@
/// {@endtemplate}
final int initialItemCount;
- /// The axis along which the scroll view scrolls.
- ///
- /// Defaults to [Axis.vertical].
+ /// {@macro flutter.widgets.scroll_view.scrollDirection}
final Axis scrollDirection;
/// Whether the scroll view scrolls in the reading direction.
diff --git a/packages/flutter/lib/src/widgets/nested_scroll_view.dart b/packages/flutter/lib/src/widgets/nested_scroll_view.dart
index 4d8f40e..333e97b 100644
--- a/packages/flutter/lib/src/widgets/nested_scroll_view.dart
+++ b/packages/flutter/lib/src/widgets/nested_scroll_view.dart
@@ -196,9 +196,7 @@
/// scroll view is scrolled.
final ScrollController? controller;
- /// The axis along which the scroll view scrolls.
- ///
- /// Defaults to [Axis.vertical].
+ /// {@macro flutter.widgets.scroll_view.scrollDirection}
final Axis scrollDirection;
/// Whether the scroll view scrolls in the reading direction.
diff --git a/packages/flutter/lib/src/widgets/page_view.dart b/packages/flutter/lib/src/widgets/page_view.dart
index 8e2723e..9660dde 100644
--- a/packages/flutter/lib/src/widgets/page_view.dart
+++ b/packages/flutter/lib/src/widgets/page_view.dart
@@ -823,7 +823,10 @@
/// {@macro flutter.widgets.scrollable.restorationId}
final String? restorationId;
- /// The axis along which the page view scrolls.
+ /// The [Axis] along which the scroll view's offset increases with each page.
+ ///
+ /// For the direction in which active scrolling may be occurring, see
+ /// [ScrollDirection].
///
/// Defaults to [Axis.horizontal].
final Axis scrollDirection;
diff --git a/packages/flutter/lib/src/widgets/primary_scroll_controller.dart b/packages/flutter/lib/src/widgets/primary_scroll_controller.dart
index 73fa42f..dea9982 100644
--- a/packages/flutter/lib/src/widgets/primary_scroll_controller.dart
+++ b/packages/flutter/lib/src/widgets/primary_scroll_controller.dart
@@ -84,6 +84,9 @@
/// PrimaryScrollController.none into the tree to prevent further descendant
/// ScrollViews from inheriting the current PrimaryScrollController.
///
+ /// For the direction in which active scrolling may be occurring, see
+ /// [ScrollDirection].
+ ///
/// Defaults to [Axis.vertical].
final Axis? scrollDirection;
diff --git a/packages/flutter/lib/src/widgets/scroll_notification.dart b/packages/flutter/lib/src/widgets/scroll_notification.dart
index 597bd80..6f7dd67 100644
--- a/packages/flutter/lib/src/widgets/scroll_notification.dart
+++ b/packages/flutter/lib/src/widgets/scroll_notification.dart
@@ -268,8 +268,13 @@
}
}
-/// A notification that the user has changed the direction in which they are
-/// scrolling.
+/// A notification that the user has changed the [ScrollDirection] in which they
+/// are scrolling, or have stopped scrolling.
+///
+/// For the direction that the [ScrollView] is oriented to, and the direction
+/// contents are being laid out in, see [AxisDirection] & [GrowthDirection].
+///
+/// {@macro flutter.rendering.ScrollDirection.sample}
///
/// See also:
///
@@ -284,6 +289,13 @@
});
/// The direction in which the user is scrolling.
+ ///
+ /// This does not represent the current [AxisDirection] or [GrowthDirection]
+ /// of the [Viewport], which respectively represent the direction that the
+ /// scroll offset is increasing in, and the direction that contents are being
+ /// laid out in.
+ ///
+ /// {@macro flutter.rendering.ScrollDirection.sample}
final ScrollDirection direction;
@override
diff --git a/packages/flutter/lib/src/widgets/scroll_view.dart b/packages/flutter/lib/src/widgets/scroll_view.dart
index 861497d..0e518cf 100644
--- a/packages/flutter/lib/src/widgets/scroll_view.dart
+++ b/packages/flutter/lib/src/widgets/scroll_view.dart
@@ -114,7 +114,10 @@
physics = physics ?? ((primary ?? false) || (primary == null && controller == null && identical(scrollDirection, Axis.vertical)) ? const AlwaysScrollableScrollPhysics() : null);
/// {@template flutter.widgets.scroll_view.scrollDirection}
- /// The axis along which the scroll view scrolls.
+ /// The [Axis] along which the scroll view's offset increases.
+ ///
+ /// For the direction in which active scrolling may be occurring, see
+ /// [ScrollDirection].
///
/// Defaults to [Axis.vertical].
/// {@endtemplate}
@@ -276,6 +279,23 @@
/// supports [center]; for that class, the given key must be the key of one of
/// the slivers in the [CustomScrollView.slivers] list.
///
+ /// Most scroll views by default are ordered [GrowthDirection.forward].
+ /// Changing the default values of [ScrollView.anchor],
+ /// [ScrollView.center], or both, can configure a scroll view for
+ /// [GrowthDirection.reverse].
+ ///
+ /// {@tool dartpad}
+ /// This sample shows a [CustomScrollView], with [Radio] buttons in the
+ /// [AppBar.bottom] that change the [AxisDirection] to illustrate different
+ /// configurations. The [CustomScrollView.anchor] and [CustomScrollView.center]
+ /// properties are also set to have the 0 scroll offset positioned in the middle
+ /// of the viewport, with [GrowthDirection.forward] and [GrowthDirection.reverse]
+ /// illustrated on either side. The sliver that shares the
+ /// [CustomScrollView.center] key is positioned at the [CustomScrollView.anchor].
+ ///
+ /// ** See code in examples/api/lib/rendering/growth_direction/growth_direction.0.dart **
+ /// {@end-tool}
+ ///
/// See also:
///
/// * [anchor], which controls where the [center] as aligned in the viewport.
@@ -290,6 +310,23 @@
/// within the viewport. If the [anchor] is 1.0, and the axis direction is
/// [AxisDirection.right], then the zero scroll offset is on the left edge of
/// the viewport.
+ ///
+ /// Most scroll views by default are ordered [GrowthDirection.forward].
+ /// Changing the default values of [ScrollView.anchor],
+ /// [ScrollView.center], or both, can configure a scroll view for
+ /// [GrowthDirection.reverse].
+ ///
+ /// {@tool dartpad}
+ /// This sample shows a [CustomScrollView], with [Radio] buttons in the
+ /// [AppBar.bottom] that change the [AxisDirection] to illustrate different
+ /// configurations. The [CustomScrollView.anchor] and [CustomScrollView.center]
+ /// properties are also set to have the 0 scroll offset positioned in the middle
+ /// of the viewport, with [GrowthDirection.forward] and [GrowthDirection.reverse]
+ /// illustrated on either side. The sliver that shares the
+ /// [CustomScrollView.center] key is positioned at the [CustomScrollView.anchor].
+ ///
+ /// ** See code in examples/api/lib/rendering/growth_direction/growth_direction.0.dart **
+ /// {@end-tool}
/// {@endtemplate}
final double anchor;
diff --git a/packages/flutter/lib/src/widgets/scrollbar.dart b/packages/flutter/lib/src/widgets/scrollbar.dart
index ababcca..e98ad61 100644
--- a/packages/flutter/lib/src/widgets/scrollbar.dart
+++ b/packages/flutter/lib/src/widgets/scrollbar.dart
@@ -1087,9 +1087,9 @@
/// * When providing a controller, the same ScrollController must also be
/// provided to the associated Scrollable widget.
/// * The [PrimaryScrollController] is used by default for a [ScrollView]
- /// that has not been provided a [ScrollController] and that has an
- /// [Axis.vertical] [ScrollDirection]. This automatic behavior does not
- /// apply to those with a ScrollDirection of Axis.horizontal. To explicitly
+ /// that has not been provided a [ScrollController] and that has a
+ /// [ScrollView.scrollDirection] of [Axis.vertical]. This automatic
+ /// behavior does not apply to those with [Axis.horizontal]. To explicitly
/// use the PrimaryScrollController, set [ScrollView.primary] to true.
///
/// Defaults to false when null.
@@ -1172,9 +1172,9 @@
/// * When providing a controller, the same ScrollController must also be
/// provided to the associated Scrollable widget.
/// * The [PrimaryScrollController] is used by default for a [ScrollView]
- /// that has not been provided a [ScrollController] and that has an
- /// [Axis.vertical] [ScrollDirection]. This automatic behavior does not
- /// apply to those with a ScrollDirection of Axis.horizontal. To explicitly
+ /// that has not been provided a [ScrollController] and that has a
+ /// [ScrollView.scrollDirection] of [Axis.vertical]. This automatic
+ /// behavior does not apply to those with Axis.horizontal. To explicitly
/// use the PrimaryScrollController, set [ScrollView.primary] to true.
///
/// Defaults to false when null.
@@ -1576,7 +1576,7 @@
'ScrollController should be associated with the ScrollView that '
'the Scrollbar is being applied to.'
'${tryPrimary
- ? 'A ScrollView with an Axis.vertical ScrollDirection on mobile '
+ ? 'When ScrollView.scrollDirection is Axis.vertical on mobile '
'platforms will automatically use the '
'PrimaryScrollController if the user has not provided a '
'ScrollController. To use the PrimaryScrollController '
diff --git a/packages/flutter/lib/src/widgets/single_child_scroll_view.dart b/packages/flutter/lib/src/widgets/single_child_scroll_view.dart
index 3957f02..818a8a1 100644
--- a/packages/flutter/lib/src/widgets/single_child_scroll_view.dart
+++ b/packages/flutter/lib/src/widgets/single_child_scroll_view.dart
@@ -158,9 +158,7 @@
'true and pass an explicit controller.',
);
- /// The axis along which the scroll view scrolls.
- ///
- /// Defaults to [Axis.vertical].
+ /// {@macro flutter.widgets.scroll_view.scrollDirection}
final Axis scrollDirection;
/// Whether the scroll view scrolls in the reading direction.
diff --git a/packages/flutter/lib/src/widgets/viewport.dart b/packages/flutter/lib/src/widgets/viewport.dart
index a4fcb70..92aa832 100644
--- a/packages/flutter/lib/src/widgets/viewport.dart
+++ b/packages/flutter/lib/src/widgets/viewport.dart
@@ -96,6 +96,8 @@
/// vertically centered within the viewport. If the [anchor] is 1.0, and the
/// [axisDirection] is [AxisDirection.right], then the zero scroll offset is
/// on the left edge of the viewport.
+ ///
+ /// {@macro flutter.rendering.GrowthDirection.sample}
final double anchor;
/// Which part of the content inside the viewport should be visible.
@@ -115,6 +117,8 @@
/// the [axisDirection] relative to the [center].
///
/// The [center] must be the key of a child of the viewport.
+ ///
+ /// {@macro flutter.rendering.GrowthDirection.sample}
final Key? center;
/// {@macro flutter.rendering.RenderViewportBase.cacheExtent}