Commit 1faaf8ea authored by Adam Barth's avatar Adam Barth Committed by GitHub

Document Viewport and NestedScrollView (#9735)

parent 9fe68ef7
...@@ -1356,14 +1356,14 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { ...@@ -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 /// 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 data means. The parent data is opaque to the child.
/// ///
/// - The parent data field must not be directly set, except by calling /// * The parent data field must not be directly set, except by calling
/// [setupParentData] on the parent node. /// [setupParentData] on the parent node.
/// - The parent data can be set before the child is added to the parent, by /// * The parent data can be set before the child is added to the parent, by
/// calling [setupParentData] on the future parent node. /// calling [setupParentData] on the future parent node.
/// - The conventions for using the parent data depend on the layout protocol /// * The conventions for using the parent data depend on the layout protocol
/// used between the parent and child. For example, in box layout, the /// used between the parent and child. For example, in box layout, the
/// parent data is completely opaque but in sector layout the child is /// parent data is completely opaque but in sector layout the child is
/// permitted to read some fields of the parent data. /// permitted to read some fields of the parent data.
ParentData parentData; ParentData parentData;
/// Override to setup parent data correctly for your children. /// Override to setup parent data correctly for your children.
......
...@@ -50,16 +50,29 @@ abstract class RenderAbstractViewport implements RenderObject { ...@@ -50,16 +50,29 @@ abstract class RenderAbstractViewport implements RenderObject {
double getOffsetToReveal(RenderObject target, double alignment); double getOffsetToReveal(RenderObject target, double alignment);
} }
// /// /// A base class for render objects that are bigger on the inside.
// /// See also: ///
// /// /// This render object provides the shared code for render objects that host
// /// - [RenderSliver], which explains more about the Sliver protocol. /// [RenderSliver] render objects inside a [RenderBox]. The viewport establishes
// /// - [RenderBox], which explains more about the Box protocol. /// an [axisDirection], which orients the sliver's coordinate system, which is
// /// - [RenderSliverToBoxAdapter], which allows a [RenderBox] object to be /// based on scroll offsets rather than cartesian coordinates.
// /// placed inside a [RenderSliver] (the opposite of this class). ///
/// 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<ParentDataClass extends ContainerParentDataMixin<RenderSliver>> abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMixin<RenderSliver>>
extends RenderBox with ContainerRenderObjectMixin<RenderSliver, ParentDataClass> extends RenderBox with ContainerRenderObjectMixin<RenderSliver, ParentDataClass>
implements RenderAbstractViewport { implements RenderAbstractViewport {
/// Initializes fields for subclasses.
RenderViewportBase({ RenderViewportBase({
AxisDirection axisDirection: AxisDirection.down, AxisDirection axisDirection: AxisDirection.down,
@required ViewportOffset offset, @required ViewportOffset offset,
...@@ -525,15 +538,37 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix ...@@ -525,15 +538,37 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix
Iterable<RenderSliver> get childrenInHitTestOrder; Iterable<RenderSliver> get childrenInHitTestOrder;
} }
// /// /// A render object that is bigger on the inside.
// /// See also: ///
// /// /// [RenderViewport] is the visual workhorse of the scrolling machinery. It
// /// - [RenderSliver], which explains more about the Sliver protocol. /// displays a subset of its children according to its own dimensions and the
// /// - [RenderBox], which explains more about the Box protocol. /// given [offset]. As the offset varies, different children are visible through
// /// - [RenderSliverToBoxAdapter], which allows a [RenderBox] object to be /// the viewport.
// /// placed inside a [RenderSliver] (the opposite of this class). ///
// /// - [RenderShrinkWrappingViewport], a variant of [RenderViewport] that /// [RenderViewport] hosts a bidirectional list of slivers, anchored on a
// /// shrink-wraps its contents along the main axis. /// [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<SliverPhysicalContainerParentData> { class RenderViewport extends RenderViewportBase<SliverPhysicalContainerParentData> {
/// Creates a viewport for [RenderSliver] objects. /// Creates a viewport for [RenderSliver] objects.
/// ///
...@@ -944,14 +979,31 @@ class RenderViewport extends RenderViewportBase<SliverPhysicalContainerParentDat ...@@ -944,14 +979,31 @@ class RenderViewport extends RenderViewportBase<SliverPhysicalContainerParentDat
} }
} }
// /// /// A render object that is bigger on the inside and shrink wraps its children
// /// See also: /// in the main axis.
// /// ///
// /// - [RenderViewport], a viewport that does not shrink-wrap its contents /// [RenderShrinkWrappingViewport] displays a subset of its children according
// /// - [RenderSliver], which explains more about the Sliver protocol. /// to its own dimensions and the given [offset]. As the offset varies, different
// /// - [RenderBox], which explains more about the Box protocol. /// children are visible through the viewport.
// /// - [RenderSliverToBoxAdapter], which allows a [RenderBox] object to be ///
// /// placed inside a [RenderSliver] (the opposite of this class). /// [RenderShrinkWrappingViewport] differs from [RenderViewport] in that
/// [RenderViewport] expands to fill the main axis whereas
/// [RenderShrinkWrappingViewport] 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).
///
/// [RenderShrinkWrappingViewport] cannot contain [RenderBox] children directly.
/// Instead, use a [RenderSliverList], [RenderSliverFixedExtentList],
/// [RenderSliverGrid], or a [RenderSliverToBoxAdapter], for example.
///
/// See also:
///
/// * [RenderViewport], a viewport that does not shrink-wrap its contents
/// * [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).
class RenderShrinkWrappingViewport extends RenderViewportBase<SliverLogicalContainerParentData> { class RenderShrinkWrappingViewport extends RenderViewportBase<SliverLogicalContainerParentData> {
/// Creates a viewport (for [RenderSliver] objects) that shrink-wraps its /// Creates a viewport (for [RenderSliver] objects) that shrink-wraps its
/// contents. /// contents.
......
...@@ -27,6 +27,9 @@ import 'ticker_provider.dart'; ...@@ -27,6 +27,9 @@ import 'ticker_provider.dart';
/// Signature used by [NestedScrollView] for building its header. /// Signature used by [NestedScrollView] for building its header.
typedef List<Widget> NestedScrollViewHeaderSliversBuilder(BuildContext context, bool innerBoxIsScrolled); typedef List<Widget> NestedScrollViewHeaderSliversBuilder(BuildContext context, bool innerBoxIsScrolled);
// TODO(abarth): Make this configurable with a controller.
const double _kInitialScrollOffset = 0.0;
class NestedScrollView extends StatefulWidget { class NestedScrollView extends StatefulWidget {
NestedScrollView({ NestedScrollView({
Key key, Key key,
...@@ -44,20 +47,38 @@ class NestedScrollView extends StatefulWidget { ...@@ -44,20 +47,38 @@ class NestedScrollView extends StatefulWidget {
// TODO(ianh): we should expose a controller so you can call animateTo, etc. // 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; 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; 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 ScrollPhysics physics;
final NestedScrollViewHeaderSliversBuilder headerSliverBuilder; final NestedScrollViewHeaderSliversBuilder headerSliverBuilder;
final Widget body; final Widget body;
double get initialScrollOffset => 0.0; List<Widget> _buildSlivers(BuildContext context, ScrollController innerController, bool bodyIsScrolled) {
@protected
List<Widget> buildSlivers(BuildContext context, ScrollController innerController, bool bodyIsScrolled) {
final List<Widget> slivers = <Widget>[]; final List<Widget> slivers = <Widget>[];
slivers.addAll(headerSliverBuilder(context, bodyIsScrolled)); slivers.addAll(headerSliverBuilder(context, bodyIsScrolled));
slivers.add(new SliverFillRemaining( slivers.add(new SliverFillRemaining(
...@@ -79,7 +100,7 @@ class _NestedScrollViewState extends State<NestedScrollView> { ...@@ -79,7 +100,7 @@ class _NestedScrollViewState extends State<NestedScrollView> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_coordinator = new _NestedScrollCoordinator(context, widget.initialScrollOffset); _coordinator = new _NestedScrollCoordinator(context, _kInitialScrollOffset);
} }
@override @override
...@@ -102,7 +123,7 @@ class _NestedScrollViewState extends State<NestedScrollView> { ...@@ -102,7 +123,7 @@ class _NestedScrollViewState extends State<NestedScrollView> {
reverse: widget.reverse, reverse: widget.reverse,
physics: new ClampingScrollPhysics(parent: widget.physics), physics: new ClampingScrollPhysics(parent: widget.physics),
controller: _coordinator._outerController, controller: _coordinator._outerController,
slivers: widget.buildSlivers(context, _coordinator._innerController, _coordinator.hasScrolledBody), slivers: widget._buildSlivers(context, _coordinator._innerController, _coordinator.hasScrolledBody),
); );
} }
} }
......
...@@ -11,7 +11,44 @@ export 'package:flutter/rendering.dart' show ...@@ -11,7 +11,44 @@ export 'package:flutter/rendering.dart' show
AxisDirection, AxisDirection,
GrowthDirection; 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 { 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({ Viewport({
Key key, Key key,
this.axisDirection: AxisDirection.down, this.axisDirection: AxisDirection.down,
...@@ -24,9 +61,39 @@ class Viewport extends MultiChildRenderObjectWidget { ...@@ -24,9 +61,39 @@ class Viewport extends MultiChildRenderObjectWidget {
assert(center == null || children.where((Widget child) => child.key == center).length == 1); 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; 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; 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; 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; final Key center;
@override @override
...@@ -46,7 +113,7 @@ class Viewport extends MultiChildRenderObjectWidget { ...@@ -46,7 +113,7 @@ class Viewport extends MultiChildRenderObjectWidget {
} }
@override @override
ViewportElement createElement() => new ViewportElement(this); _ViewportElement createElement() => new _ViewportElement(this);
@override @override
void debugFillDescription(List<String> description) { void debugFillDescription(List<String> description) {
...@@ -62,9 +129,9 @@ class Viewport extends MultiChildRenderObjectWidget { ...@@ -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. /// Creates an element that uses the given widget as its configuration.
ViewportElement(Viewport widget) : super(widget); _ViewportElement(Viewport widget) : super(widget);
@override @override
Viewport get widget => super.widget; Viewport get widget => super.widget;
...@@ -75,17 +142,16 @@ class ViewportElement extends MultiChildRenderObjectElement { ...@@ -75,17 +142,16 @@ class ViewportElement extends MultiChildRenderObjectElement {
@override @override
void mount(Element parent, dynamic newSlot) { void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot); super.mount(parent, newSlot);
updateCenter(); _updateCenter();
} }
@override @override
void update(MultiChildRenderObjectWidget newWidget) { void update(MultiChildRenderObjectWidget newWidget) {
super.update(newWidget); super.update(newWidget);
updateCenter(); _updateCenter();
} }
@protected void _updateCenter() {
void updateCenter() {
// TODO(ianh): cache the keys to make this faster // TODO(ianh): cache the keys to make this faster
if (widget.center != null) { if (widget.center != null) {
renderObject.center = children.singleWhere( renderObject.center = children.singleWhere(
...@@ -99,7 +165,39 @@ class ViewportElement extends MultiChildRenderObjectElement { ...@@ -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 { 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({ ShrinkWrappingViewport({
Key key, Key key,
this.axisDirection: AxisDirection.down, this.axisDirection: AxisDirection.down,
...@@ -109,7 +207,21 @@ class ShrinkWrappingViewport extends MultiChildRenderObjectWidget { ...@@ -109,7 +207,21 @@ class ShrinkWrappingViewport extends MultiChildRenderObjectWidget {
assert(offset != null); 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; 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; final ViewportOffset offset;
@override @override
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment