diff --git a/packages/flutter/lib/src/rendering/object.dart b/packages/flutter/lib/src/rendering/object.dart index ee01e421cf..1c267f131f 100644 --- a/packages/flutter/lib/src/rendering/object.dart +++ b/packages/flutter/lib/src/rendering/object.dart @@ -1356,14 +1356,14 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { /// relevant to itself and to any other nodes who happen to know exactly what /// the data means. The parent data is opaque to the child. /// - /// - The parent data field must not be directly set, except by calling - /// [setupParentData] on the parent node. - /// - The parent data can be set before the child is added to the parent, by - /// calling [setupParentData] on the future parent node. - /// - The conventions for using the parent data depend on the layout protocol - /// used between the parent and child. For example, in box layout, the - /// parent data is completely opaque but in sector layout the child is - /// permitted to read some fields of the parent data. + /// * The parent data field must not be directly set, except by calling + /// [setupParentData] on the parent node. + /// * The parent data can be set before the child is added to the parent, by + /// calling [setupParentData] on the future parent node. + /// * The conventions for using the parent data depend on the layout protocol + /// used between the parent and child. For example, in box layout, the + /// parent data is completely opaque but in sector layout the child is + /// permitted to read some fields of the parent data. ParentData parentData; /// Override to setup parent data correctly for your children. diff --git a/packages/flutter/lib/src/rendering/viewport.dart b/packages/flutter/lib/src/rendering/viewport.dart index c9fcaa7158..3cf6d192cf 100644 --- a/packages/flutter/lib/src/rendering/viewport.dart +++ b/packages/flutter/lib/src/rendering/viewport.dart @@ -50,16 +50,29 @@ abstract class RenderAbstractViewport implements RenderObject { double getOffsetToReveal(RenderObject target, double alignment); } -// /// -// /// See also: -// /// -// /// - [RenderSliver], which explains more about the Sliver protocol. -// /// - [RenderBox], which explains more about the Box protocol. -// /// - [RenderSliverToBoxAdapter], which allows a [RenderBox] object to be -// /// placed inside a [RenderSliver] (the opposite of this class). +/// A base class for render objects that are bigger on the inside. +/// +/// This render object provides the shared code for render objects that host +/// [RenderSliver] render objects inside a [RenderBox]. The viewport establishes +/// an [axisDirection], which orients the sliver's coordinate system, which is +/// based on scroll offsets rather than cartesian coordinates. +/// +/// The viewport also listens to an [offset], which determines the +/// [SliverConstraints.scrollOffset] input to the sliver layout protocol. +/// +/// Subclasses typically override [performLayout] and call +/// [layoutChildSequence], perhaps multiple times. +/// +/// See also: +/// +/// * [RenderSliver], which explains more about the Sliver protocol. +/// * [RenderBox], which explains more about the Box protocol. +/// * [RenderSliverToBoxAdapter], which allows a [RenderBox] object to be +/// placed inside a [RenderSliver] (the opposite of this class). abstract class RenderViewportBase> extends RenderBox with ContainerRenderObjectMixin implements RenderAbstractViewport { + /// Initializes fields for subclasses. RenderViewportBase({ AxisDirection axisDirection: AxisDirection.down, @required ViewportOffset offset, @@ -525,15 +538,37 @@ abstract class RenderViewportBase get childrenInHitTestOrder; } -// /// -// /// See also: -// /// -// /// - [RenderSliver], which explains more about the Sliver protocol. -// /// - [RenderBox], which explains more about the Box protocol. -// /// - [RenderSliverToBoxAdapter], which allows a [RenderBox] object to be -// /// placed inside a [RenderSliver] (the opposite of this class). -// /// - [RenderShrinkWrappingViewport], a variant of [RenderViewport] that -// /// shrink-wraps its contents along the main axis. +/// A render object that is bigger on the inside. +/// +/// [RenderViewport] is the visual workhorse of the scrolling machinery. It +/// displays a subset of its children according to its own dimensions and the +/// 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. +/// +/// Slivers that are earlier in the child list than [center] are displayed in +/// reverse order in the reverse [axisDirection] starting from the [center]. For +/// example, if the [axisDirection] is [AxisDirection.down], the first sliver +/// before [center] is placed above the [center]. The slivers that are later in +/// the child list than [center] are placed in order in the [axisDirection]. For +/// example, in the preceeding scenario, the first sliver after [center] is +/// placed below the [center]. +/// +/// [RenderViewport] cannot contain [RenderBox] children directly. Instead, use +/// a [RenderSliverList], [RenderSliverFixedExtentList], [RenderSliverGrid], or +/// a [RenderSliverToBoxAdapter], for example. +/// +/// See also: +/// +/// * [RenderSliver], which explains more about the Sliver protocol. +/// * [RenderBox], which explains more about the Box protocol. +/// * [RenderSliverToBoxAdapter], which allows a [RenderBox] object to be +/// placed inside a [RenderSliver] (the opposite of this class). +/// * [RenderShrinkWrappingViewport], a variant of [RenderViewport] that +/// shrink-wraps its contents along the main axis. class RenderViewport extends RenderViewportBase { /// Creates a viewport for [RenderSliver] objects. /// @@ -944,14 +979,31 @@ class RenderViewport extends RenderViewportBase { /// Creates a viewport (for [RenderSliver] objects) that shrink-wraps its /// contents. diff --git a/packages/flutter/lib/src/widgets/nested_scroll_view.dart b/packages/flutter/lib/src/widgets/nested_scroll_view.dart index 432c3c5b1b..388c554c43 100644 --- a/packages/flutter/lib/src/widgets/nested_scroll_view.dart +++ b/packages/flutter/lib/src/widgets/nested_scroll_view.dart @@ -27,6 +27,9 @@ import 'ticker_provider.dart'; /// Signature used by [NestedScrollView] for building its header. typedef List NestedScrollViewHeaderSliversBuilder(BuildContext context, bool innerBoxIsScrolled); +// TODO(abarth): Make this configurable with a controller. +const double _kInitialScrollOffset = 0.0; + class NestedScrollView extends StatefulWidget { NestedScrollView({ Key key, @@ -44,20 +47,38 @@ class NestedScrollView extends StatefulWidget { // TODO(ianh): we should expose a controller so you can call animateTo, etc. + /// The axis along which the scroll view scrolls. + /// + /// Defaults to [Axis.vertical]. final Axis scrollDirection; + /// Whether the scroll view scrolls in the reading direction. + /// + /// For example, if the reading direction is left-to-right and + /// [scrollDirection] is [Axis.horizontal], then the scroll view scrolls from + /// left to right when [reverse] is false and from right to left when + /// [reverse] is true. + /// + /// Similarly, if [scrollDirection] is [Axis.vertical], then the scroll view + /// scrolls from top to bottom when [reverse] is false and from bottom to top + /// when [reverse] is true. + /// + /// Defaults to false. final bool reverse; + /// How the scroll view should respond to user input. + /// + /// For example, determines how the scroll view continues to animate after the + /// user stops dragging the scroll view. + /// + /// Defaults to matching platform conventions. final ScrollPhysics physics; final NestedScrollViewHeaderSliversBuilder headerSliverBuilder; final Widget body; - double get initialScrollOffset => 0.0; - - @protected - List buildSlivers(BuildContext context, ScrollController innerController, bool bodyIsScrolled) { + List _buildSlivers(BuildContext context, ScrollController innerController, bool bodyIsScrolled) { final List slivers = []; slivers.addAll(headerSliverBuilder(context, bodyIsScrolled)); slivers.add(new SliverFillRemaining( @@ -79,7 +100,7 @@ class _NestedScrollViewState extends State { @override void initState() { super.initState(); - _coordinator = new _NestedScrollCoordinator(context, widget.initialScrollOffset); + _coordinator = new _NestedScrollCoordinator(context, _kInitialScrollOffset); } @override @@ -102,7 +123,7 @@ class _NestedScrollViewState extends State { reverse: widget.reverse, physics: new ClampingScrollPhysics(parent: widget.physics), controller: _coordinator._outerController, - slivers: widget.buildSlivers(context, _coordinator._innerController, _coordinator.hasScrolledBody), + slivers: widget._buildSlivers(context, _coordinator._innerController, _coordinator.hasScrolledBody), ); } } diff --git a/packages/flutter/lib/src/widgets/viewport.dart b/packages/flutter/lib/src/widgets/viewport.dart index e3b6bb17d3..7da4ef5c1d 100644 --- a/packages/flutter/lib/src/widgets/viewport.dart +++ b/packages/flutter/lib/src/widgets/viewport.dart @@ -11,7 +11,44 @@ export 'package:flutter/rendering.dart' show AxisDirection, GrowthDirection; +/// A widget that is bigger on the inside. +/// +/// [Viewport] is the visual workhorse of the scrolling machinery. It displays a +/// subset of its children according to its own dimensions and the given +/// [offset]. As the offset varies, different children are visible through +/// the viewport. +/// +/// [Viewport] 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. +/// +/// Slivers that are earlier in the child list than [center] are displayed in +/// reverse order in the reverse [axisDirection] starting from the [center]. For +/// example, if the [axisDirection] is [AxisDirection.down], the first sliver +/// before [center] is placed above the [center]. The slivers that are later in +/// the child list than [center] are placed in order in the [axisDirection]. For +/// example, in the preceeding scenario, the first sliver after [center] is +/// placed below the [center]. +/// +/// [Viewport] cannot contain box children directly. Instead, use a +/// [SliverList], [SliverFixedExtentList], [SliverGrid], or a +/// [SliverToBoxAdapter], for example. +/// +/// See also: +/// +/// * [ListView], [PageView], [GridView], and [CustomScrollView], which combine +/// [Scrollable] and [Viewport] into widgets that are easier to use. +/// * [SliverToBoxAdapter], which allows a box widget to be placed inside a +/// sliver context (the opposite of this widget). +/// * [ShrinkWrappingViewport], a variant of [Viewport] that shrink-wraps its +/// contents along the main axis. class Viewport extends MultiChildRenderObjectWidget { + /// Creates a widget that is bigger on the inside. + /// + /// The viewport listens to the [offset], which means you do not need to + /// rebuild this widget when the [offset] changes. + /// + /// The [offset] argument must not be null. Viewport({ Key key, this.axisDirection: AxisDirection.down, @@ -24,9 +61,39 @@ class Viewport extends MultiChildRenderObjectWidget { assert(center == null || children.where((Widget child) => child.key == center).length == 1); } + /// The direction in which the [scrollOffset] increases. + /// + /// For example, if the [axisDirection] is [AxisDirection.down], a scroll + /// offset of zero is at the top of the viewport and increases towards the + /// bottom of the viewport. final AxisDirection axisDirection; + + /// The relative position of the zero scroll offset. + /// + /// For example, if [anchor] is 0.5 and the [axisDirection] is + /// [AxisDirection.down] or [AxisDirection.up], then the zero scroll offset is + /// 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. final double anchor; + + /// Which part of the content inside the viewport should be visible. + /// + /// The [ViewportOffset.pixels] value determines the scroll offset that the + /// viewport uses to select which part of its content to display. As the user + /// scrolls the viewport, this value changes, which changes the content that + /// is displayed. + /// + /// Typically a [ScrollPosition]. final ViewportOffset offset; + + /// The first child in the [GrowthDirection.forward] growth direction. + /// + /// 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] must be the key of a child of the viewport. final Key center; @override @@ -46,7 +113,7 @@ class Viewport extends MultiChildRenderObjectWidget { } @override - ViewportElement createElement() => new ViewportElement(this); + _ViewportElement createElement() => new _ViewportElement(this); @override void debugFillDescription(List description) { @@ -62,9 +129,9 @@ class Viewport extends MultiChildRenderObjectWidget { } } -class ViewportElement extends MultiChildRenderObjectElement { +class _ViewportElement extends MultiChildRenderObjectElement { /// Creates an element that uses the given widget as its configuration. - ViewportElement(Viewport widget) : super(widget); + _ViewportElement(Viewport widget) : super(widget); @override Viewport get widget => super.widget; @@ -75,17 +142,16 @@ class ViewportElement extends MultiChildRenderObjectElement { @override void mount(Element parent, dynamic newSlot) { super.mount(parent, newSlot); - updateCenter(); + _updateCenter(); } @override void update(MultiChildRenderObjectWidget newWidget) { super.update(newWidget); - updateCenter(); + _updateCenter(); } - @protected - void updateCenter() { + void _updateCenter() { // TODO(ianh): cache the keys to make this faster if (widget.center != null) { renderObject.center = children.singleWhere( @@ -99,7 +165,39 @@ class ViewportElement extends MultiChildRenderObjectElement { } } +/// A widget that is bigger on the inside and shrink wraps its children in the +/// main axis. +/// +/// [ShrinkWrappingViewport] displays a subset of its children according to its +/// own dimensions and the given [offset]. As the offset varies, different +/// children are visible through the viewport. +/// +/// [ShrinkWrappingViewport] differs from [Viewport] in that [Viewport] expands +/// to fill the main axis whereas [ShrinkWrappingViewport] sizes itself to match +/// its children in the main axis. This shrink wrapping behavior is expensive +/// because the children, and hence the viewport, could potentially change size +/// whenever the [offset] changes (e.g., because of a collapsing header). +/// +/// [ShrinkWrappingViewport] cannot contain box children directly. Instead, use +/// a [SliverList], [SliverFixedExtentList], [SliverGrid], or a +/// [SliverToBoxAdapter], for example. +/// +/// See also: +/// +/// * [ListView], [PageView], [GridView], and [CustomScrollView], which combine +/// [Scrollable] and [ShrinkWrappingViewport] into widgets that are easier to +/// use. +/// * [SliverToBoxAdapter], which allows a box widget to be placed inside a +/// sliver context (the opposite of this widget). +/// * [Viewport], a viewport that does not shrink-wrap its contents class ShrinkWrappingViewport extends MultiChildRenderObjectWidget { + /// Creates a widget that is bigger on the inside and shrink wraps its + /// children in the main axis. + /// + /// The viewport listens to the [offset], which means you do not need to + /// rebuild this widget when the [offset] changes. + /// + /// The [offset] argument must not be null. ShrinkWrappingViewport({ Key key, this.axisDirection: AxisDirection.down, @@ -109,7 +207,21 @@ class ShrinkWrappingViewport extends MultiChildRenderObjectWidget { assert(offset != null); } + /// The direction in which the [scrollOffset] increases. + /// + /// For example, if the [axisDirection] is [AxisDirection.down], a scroll + /// offset of zero is at the top of the viewport and increases towards the + /// bottom of the viewport. final AxisDirection axisDirection; + + /// Which part of the content inside the viewport should be visible. + /// + /// The [ViewportOffset.pixels] value determines the scroll offset that the + /// viewport uses to select which part of its content to display. As the user + /// scrolls the viewport, this value changes, which changes the content that + /// is displayed. + /// + /// Typically a [ScrollPosition]. final ViewportOffset offset; @override