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 0000000000000..8a4e1f2c7f72b --- /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 createState() => _MyWidgetState(); +} + +class _MyWidgetState extends State { + final List _alphabet = [ + '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: [ arrow, arrow ], + ); + case AxisDirection.down: + arrow = const Icon(Icons.arrow_downward_rounded); + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ arrow, arrow ], + ); + case AxisDirection.left: + arrow = const Icon(Icons.arrow_back_rounded); + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ arrow, arrow ], + ); + case AxisDirection.right: + arrow = const Icon(Icons.arrow_forward_rounded); + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ 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: [ + 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(Colors.white), + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Radio( + value: AxisDirection.up, + groupValue: _axisDirection, + onChanged: _onAxisDirectionChanged, + ), + const Text('up'), + _spacer, + Radio( + value: AxisDirection.down, + groupValue: _axisDirection, + onChanged: _onAxisDirectionChanged, + ), + const Text('down'), + _spacer, + Radio( + value: AxisDirection.left, + groupValue: _axisDirection, + onChanged: _onAxisDirectionChanged, + ), + const Text('left'), + _spacer, + Radio( + 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: [ + 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 0000000000000..b58c9fc9bb3d1 --- /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 createState() => _MyWidgetState(); +} + +class _MyWidgetState extends State { + final List _alphabet = [ + '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: [ arrow, arrow ], + ); + case AxisDirection.down: + arrow = const Icon(Icons.arrow_downward_rounded); + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ arrow, arrow ], + ); + case AxisDirection.left: + arrow = const Icon(Icons.arrow_back_rounded); + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ arrow, arrow ], + ); + case AxisDirection.right: + arrow = const Icon(Icons.arrow_forward_rounded); + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ 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: [ + 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(Colors.white), + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Radio( + value: AxisDirection.up, + groupValue: _axisDirection, + onChanged: _onAxisDirectionChanged, + ), + const Text('up'), + _spacer, + Radio( + value: AxisDirection.down, + groupValue: _axisDirection, + onChanged: _onAxisDirectionChanged, + ), + const Text('down'), + _spacer, + Radio( + value: AxisDirection.left, + groupValue: _axisDirection, + onChanged: _onAxisDirectionChanged, + ), + const Text('left'), + _spacer, + Radio( + 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: [ + _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 0000000000000..211157d760508 --- /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 createState() => _MyWidgetState(); +} + +class _MyWidgetState extends State { + final List alphabet = [ + '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: [ arrow, arrow ], + ); + case AxisDirection.down: + arrow = const Icon(Icons.arrow_downward_rounded); + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ arrow, arrow ], + ); + case AxisDirection.left: + arrow = const Icon(Icons.arrow_back_rounded); + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ arrow, arrow ], + ); + case AxisDirection.right: + arrow = const Icon(Icons.arrow_forward_rounded); + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ 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: [ + 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(Colors.white), + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Radio( + value: AxisDirection.up, + groupValue: _axisDirection, + onChanged: _onAxisDirectionChanged, + ), + const Text('up'), + spacer, + Radio( + value: AxisDirection.down, + groupValue: _axisDirection, + onChanged: _onAxisDirectionChanged, + ), + const Text('down'), + spacer, + Radio( + value: AxisDirection.left, + groupValue: _axisDirection, + onChanged: _onAxisDirectionChanged, + ), + const Text('left'), + spacer, + Radio( + 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( + 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: [ + 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 0000000000000..0a346c129080f --- /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), 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 && 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 0000000000000..3fd61fee9b12c --- /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 && 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 0000000000000..5e4075c198920 --- /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), 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 && 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 da909a69536c6..8dcee824f38e1 100644 --- a/packages/flutter/lib/src/material/input_chip.dart +++ b/packages/flutter/lib/src/material/input_chip.dart @@ -30,8 +30,8 @@ import 'theme_data.dart'; /// 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 26c67b2f75615..6107e10b2c7c8 100644 --- a/packages/flutter/lib/src/painting/basic_types.dart +++ b/packages/flutter/lib/src/painting/basic_types.dart @@ -111,7 +111,7 @@ enum RenderComparison { /// 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 @@ enum VerticalDirection { 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 590334fd9ec3b..d2451163e06aa 100644 --- a/packages/flutter/lib/src/rendering/sliver.dart +++ b/packages/flutter/lib/src/rendering/sliver.dart @@ -27,15 +27,48 @@ import 'viewport_offset.dart'; /// [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 @@ AxisDirection applyGrowthDirectionToAxisDirection(AxisDirection axisDirection, G } } -/// 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 @@ class SliverConstraints extends Constraints { /// 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 @@ class SliverConstraints extends Constraints { /// /// 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 @@ class SliverConstraints extends Constraints { /// 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 1d31f1999a98c..bf07a880263da 100644 --- a/packages/flutter/lib/src/rendering/viewport.dart +++ b/packages/flutter/lib/src/rendering/viewport.dart @@ -1222,9 +1222,10 @@ abstract class RenderViewportBase _anchor; double _anchor; set anchor(double value) { @@ -1340,10 +1345,15 @@ class RenderViewport extends RenderViewportBase _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 8f5f9794f9c7a..44d5caddc2c90 100644 --- a/packages/flutter/lib/src/rendering/viewport_offset.dart +++ b/packages/flutter/lib/src/rendering/viewport_offset.dart @@ -8,28 +8,58 @@ import 'package:flutter/foundation.dart'; /// 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. + /// 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 up, exposing - /// lower 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 negative scroll offset direction. + /// 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 down, exposing - /// earlier content. + /// [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 @@ abstract class ViewportOffset extends ChangeNotifier { /// 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 53196a835ad04..7dda551ae4435 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 @@ abstract class _AnimatedScrollView extends StatefulWidget { /// {@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 4d8f40ea5d09f..333e97bd64522 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 @@ class NestedScrollView extends StatefulWidget { /// 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 8e2723e69c3a2..9660ddefb3dbe 100644 --- a/packages/flutter/lib/src/widgets/page_view.dart +++ b/packages/flutter/lib/src/widgets/page_view.dart @@ -823,7 +823,10 @@ class PageView extends StatefulWidget { /// {@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 73fa42f0c0b70..dea998277de27 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 @@ class PrimaryScrollController extends InheritedWidget { /// 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 597bd8007ad2f..6f7dd67671a9f 100644 --- a/packages/flutter/lib/src/widgets/scroll_notification.dart +++ b/packages/flutter/lib/src/widgets/scroll_notification.dart @@ -268,8 +268,13 @@ class ScrollEndNotification extends ScrollNotification { } } -/// 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 @@ class UserScrollNotification extends ScrollNotification { }); /// 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 861497d9a3e4e..0e518cf42eba8 100644 --- a/packages/flutter/lib/src/widgets/scroll_view.dart +++ b/packages/flutter/lib/src/widgets/scroll_view.dart @@ -114,7 +114,10 @@ abstract class ScrollView extends StatelessWidget { 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 @@ abstract class ScrollView extends StatelessWidget { /// 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 @@ abstract class ScrollView extends StatelessWidget { /// 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 ababcca80fdd7..e98ad613aebe0 100644 --- a/packages/flutter/lib/src/widgets/scrollbar.dart +++ b/packages/flutter/lib/src/widgets/scrollbar.dart @@ -1087,9 +1087,9 @@ class RawScrollbar extends StatefulWidget { /// * 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 @@ class RawScrollbar extends StatefulWidget { /// * 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 @@ class RawScrollbarState extends State with TickerProv '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 3957f029f7599..818a8a1ad4487 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 @@ class SingleChildScrollView extends StatelessWidget { '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 a4fcb70d1d79b..92aa83211a4b4 100644 --- a/packages/flutter/lib/src/widgets/viewport.dart +++ b/packages/flutter/lib/src/widgets/viewport.dart @@ -96,6 +96,8 @@ class Viewport extends MultiChildRenderObjectWidget { /// 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 @@ class Viewport extends MultiChildRenderObjectWidget { /// 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}