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 {
/// 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.
......
......@@ -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<ParentDataClass extends ContainerParentDataMixin<RenderSliver>>
extends RenderBox with ContainerRenderObjectMixin<RenderSliver, ParentDataClass>
implements RenderAbstractViewport {
/// Initializes fields for subclasses.
RenderViewportBase({
AxisDirection axisDirection: AxisDirection.down,
@required ViewportOffset offset,
......@@ -525,15 +538,37 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix
Iterable<RenderSliver> 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<SliverPhysicalContainerParentData> {
/// Creates a viewport for [RenderSliver] objects.
///
......@@ -944,14 +979,31 @@ class RenderViewport extends RenderViewportBase<SliverPhysicalContainerParentDat
}
}
// ///
// /// 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).
/// A render object that is bigger on the inside and shrink wraps its children
/// in the main axis.
///
/// [RenderShrinkWrappingViewport] 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.
///
/// [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> {
/// Creates a viewport (for [RenderSliver] objects) that shrink-wraps its
/// contents.
......
......@@ -27,6 +27,9 @@ import 'ticker_provider.dart';
/// Signature used by [NestedScrollView] for building its header.
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 {
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<Widget> buildSlivers(BuildContext context, ScrollController innerController, bool bodyIsScrolled) {
List<Widget> _buildSlivers(BuildContext context, ScrollController innerController, bool bodyIsScrolled) {
final List<Widget> slivers = <Widget>[];
slivers.addAll(headerSliverBuilder(context, bodyIsScrolled));
slivers.add(new SliverFillRemaining(
......@@ -79,7 +100,7 @@ class _NestedScrollViewState extends State<NestedScrollView> {
@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<NestedScrollView> {
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),
);
}
}
......
......@@ -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<String> 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
......
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