diff --git a/packages/flutter/lib/rendering.dart b/packages/flutter/lib/rendering.dart index c60d959ef0..b457989416 100644 --- a/packages/flutter/lib/rendering.dart +++ b/packages/flutter/lib/rendering.dart @@ -46,7 +46,7 @@ export 'src/rendering/semantics.dart'; export 'src/rendering/shifted_box.dart'; export 'src/rendering/sliver.dart'; export 'src/rendering/sliver_app_bar.dart'; -export 'src/rendering/sliver_block.dart'; +export 'src/rendering/sliver_fixed_extent_list.dart'; export 'src/rendering/sliver_grid.dart'; export 'src/rendering/sliver_list.dart'; export 'src/rendering/sliver_multi_box_adaptor.dart'; @@ -55,8 +55,8 @@ export 'src/rendering/stack.dart'; export 'src/rendering/table.dart'; export 'src/rendering/tweens.dart'; export 'src/rendering/view.dart'; -export 'src/rendering/viewport_offset.dart'; export 'src/rendering/viewport.dart'; +export 'src/rendering/viewport_offset.dart'; export 'package:flutter/foundation.dart' show VoidCallback, diff --git a/packages/flutter/lib/src/material/dialog.dart b/packages/flutter/lib/src/material/dialog.dart index 78d5a1028a..76cbbba9c3 100644 --- a/packages/flutter/lib/src/material/dialog.dart +++ b/packages/flutter/lib/src/material/dialog.dart @@ -71,7 +71,8 @@ class Dialog extends StatelessWidget { /// /// If the content is too large to fit on the screen vertically, the dialog will /// display the title and the actions and let the content overflow. Consider -/// using a scrolling widget, such as [Block], for [content] to avoid overflow. +/// using a scrolling widget, such as [ScrollList], for [content] to avoid +/// overflow. /// /// For dialogs that offer the user a choice between several options, consider /// using a [SimpleDialog]. @@ -113,9 +114,9 @@ class AlertDialog extends StatelessWidget { /// The (optional) content of the dialog is displayed in the center of the /// dialog in a lighter font. /// - /// Typically, this is a [Block] containing the contents of the dialog. Using - /// a [Block] ensures that the contents can scroll if they are too big to fit - /// on the display. + /// Typically, this is a [ScrollList] containing the contents of the dialog. + /// Using a [ScrollList] ensures that the contents can scroll if they are too + /// big to fit on the display. final Widget content; /// Padding around the content. @@ -263,8 +264,8 @@ class SimpleDialog extends StatelessWidget { /// padding will be provided. final EdgeInsets titlePadding; - /// The (optional) content of the dialog is displayed in a [Block] underneath - /// the title. + /// The (optional) content of the dialog is displayed in a + /// [SingleChildScrollView] underneath the title. /// /// Typically a list of [SimpleDialogOption]s. final List children; @@ -305,7 +306,7 @@ class SimpleDialog extends StatelessWidget { child: new Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, - children: body + children: body, ) ) ) diff --git a/packages/flutter/lib/src/material/drawer.dart b/packages/flutter/lib/src/material/drawer.dart index 4ca3d4cabb..310d989dfd 100644 --- a/packages/flutter/lib/src/material/drawer.dart +++ b/packages/flutter/lib/src/material/drawer.dart @@ -31,7 +31,7 @@ const Duration _kBaseSettleDuration = const Duration(milliseconds: 246); /// the side of the screen and displays a list of items that the user can /// interact with. /// -/// Typically, the child of the drawer is a [Block] whose first child is a +/// Typically, the child of the drawer is a [SliverList] whose first child is a /// [DrawerHeader] that displays status information about the current user. /// /// The [Scaffold] automatically shows an appropriate [IconButton], and handles @@ -67,7 +67,7 @@ class Drawer extends StatelessWidget { /// The widget below this widget in the tree. /// - /// Typically a [Block]. + /// Typically a [SliverList]. final Widget child; @override diff --git a/packages/flutter/lib/src/material/list.dart b/packages/flutter/lib/src/material/list.dart index 457a0356ce..0ec908a36c 100644 --- a/packages/flutter/lib/src/material/list.dart +++ b/packages/flutter/lib/src/material/list.dart @@ -48,8 +48,8 @@ Map kListItemExtent = const /// /// See also: /// -/// * [Block], which shows heterogeneous widgets in a list and makes the list -/// scrollable if necessary. +/// * [SliverList], which shows heterogeneous widgets in a list and makes the +/// list scrollable if necessary. /// * [ListItem], to show content in a [MaterialList] using material design /// conventions. /// * [ScrollableList], on which this widget is based. diff --git a/packages/flutter/lib/src/material/scaffold.dart b/packages/flutter/lib/src/material/scaffold.dart index 4aba53c80a..ab1edafca7 100644 --- a/packages/flutter/lib/src/material/scaffold.dart +++ b/packages/flutter/lib/src/material/scaffold.dart @@ -347,10 +347,8 @@ class Scaffold extends StatefulWidget { /// /// If you have a column of widgets that should normally fit on the screen, /// but may overflow and would in such cases need to scroll, consider using a - /// [Block] as the body of the scaffold. - /// - /// If you have a list of items, consider using a [LazyBlock], - /// [LazyScrollableList], or [MaterialList] as the body of the scaffold. + /// [ScrollList] as the body of the scaffold. This is also a good choice for + /// the case where your body is a scrollable list. final Widget body; /// A button displayed floating above [body], in the bottom right corner. diff --git a/packages/flutter/lib/src/rendering/sliver_block.dart b/packages/flutter/lib/src/rendering/sliver_block.dart deleted file mode 100644 index 9d0e0524f2..0000000000 --- a/packages/flutter/lib/src/rendering/sliver_block.dart +++ /dev/null @@ -1,215 +0,0 @@ -// Copyright 2017 The Chromium 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/foundation.dart'; -import 'package:meta/meta.dart'; - -import 'box.dart'; -import 'sliver.dart'; -import 'sliver_multi_box_adaptor.dart'; - -class RenderSliverBlock extends RenderSliverMultiBoxAdaptor { - RenderSliverBlock({ - @required RenderSliverBoxChildManager childManager - }) : super(childManager: childManager); - - @override - void performLayout() { - assert(childManager.debugAssertChildListLocked()); - double scrollOffset = constraints.scrollOffset; - assert(scrollOffset >= 0.0); - double remainingPaintExtent = constraints.remainingPaintExtent; - assert(remainingPaintExtent >= 0.0); - double targetEndScrollOffset = scrollOffset + remainingPaintExtent; - BoxConstraints childConstraints = constraints.asBoxConstraints(); - int leadingGarbage = 0; - int trailingGarbage = 0; - bool reachedEnd = false; - - // This algorithm in principle is straight-forward: find the first child - // that overlaps the given scrollOffset, creating more children at the top - // of the list if necessary, then walk down the list updating and laying out - // each child and adding more at the end if necessary until we have enough - // children to cover the entire viewport. - // - // It is complicated by one minor issue, which is that any time you update - // or create a child, it's possible that the some of the children that - // haven't yet been laid out will be removed, leaving the list in an - // inconsistent state, and requiring that missing nodes be recreated. - // - // To keep this mess tractable, this algorithm starts from what is currently - // the first child, if any, and then walks up and/or down from there, so - // that the nodes that might get removed are always at the edges of what has - // already been laid out. - - // Make sure we have at least one child to start from. - if (firstChild == null) { - if (!addInitialChild()) { - // There are no children. - geometry = SliverGeometry.zero; - return; - } - } - - // We have at least one child. - - // These variables track the range of children that we have laid out. Within - // this range, the children have consecutive indices. Outside this range, - // it's possible for a child to get removed without notice. - RenderBox leadingChildWithLayout, trailingChildWithLayout; - - // Find the last child that is at or before the scrollOffset. - RenderBox earliestUsefulChild = firstChild; - for (double earliestScrollOffset = childScrollOffset(earliestUsefulChild); - earliestScrollOffset > scrollOffset; - earliestScrollOffset = childScrollOffset(earliestUsefulChild)) { - // We have to add children before the earliestUsefulChild. - earliestUsefulChild = insertAndLayoutLeadingChild(childConstraints, parentUsesSize: true); - if (earliestUsefulChild == null) { - // We ran out of children before reaching the scroll offset. - // We must inform our parent that this sliver cannot fulfill - // its contract and that we need a scroll offset correction. - geometry = new SliverGeometry( - scrollOffsetCorrection: -childScrollOffset(firstChild), - ); - return; - } - final SliverMultiBoxAdaptorParentData childParentData = earliestUsefulChild.parentData; - childParentData.scrollOffset = earliestScrollOffset - paintExtentOf(firstChild); - assert(earliestUsefulChild == firstChild); - leadingChildWithLayout = earliestUsefulChild; - trailingChildWithLayout ??= earliestUsefulChild; - } - - // At this point, earliestUsefulChild is the first child, and is a child - // whose scrollOffset is at or before the scrollOffset, and - // leadingChildWithLayout and trailingChildWithLayout are either null or - // cover a range of render boxes that we have laid out with the first being - // the same as earliestUsefulChild and the last being either at or after the - // scroll offset. - - assert(earliestUsefulChild == firstChild); - assert(childScrollOffset(earliestUsefulChild) <= scrollOffset); - - // Make sure we've laid out at least one child. - if (leadingChildWithLayout == null) { - earliestUsefulChild.layout(childConstraints, parentUsesSize: true); - leadingChildWithLayout = earliestUsefulChild; - trailingChildWithLayout = earliestUsefulChild; - } - - // Here, earliestUsefulChild is still the first child, it's got a - // scrollOffset that is at or before our actual scrollOffset, and it has - // been laid out, and is in fact our leadingChildWithLayout. It's possible - // that some children beyond that one have also been laid out. - - bool inLayoutRange = true; - RenderBox child = earliestUsefulChild; - int index = indexOf(child); - double endScrollOffset = childScrollOffset(child) + paintExtentOf(child); - bool advance() { // returns true if we advanced, false if we have no more children - // This function is used in two different places below, to avoid code duplication. - assert(child != null); - if (child == trailingChildWithLayout) - inLayoutRange = false; - child = childAfter(child); - if (child == null) - inLayoutRange = false; - index += 1; - if (!inLayoutRange) { - if (child == null || indexOf(child) != index) { - // We are missing a child. Insert it (and lay it out) if possible. - child = insertAndLayoutChild(childConstraints, - after: trailingChildWithLayout, - parentUsesSize: true, - ); - if (child == null) { - // We have run out of children. - return false; - } - } else { - // Lay out the child. - child.layout(childConstraints, parentUsesSize: true); - } - trailingChildWithLayout = child; - } - assert(child != null); - final SliverMultiBoxAdaptorParentData childParentData = child.parentData; - childParentData.scrollOffset = endScrollOffset; - assert(childParentData.index == index); - endScrollOffset = childScrollOffset(child) + paintExtentOf(child); - return true; - } - - // Find the first child that ends after the scroll offset. - while (endScrollOffset < scrollOffset) { - leadingGarbage += 1; - if (!advance()) { - assert(leadingGarbage == childCount); - assert(child == null); - // we want to make sure we keep the last child around so we know the end scroll offset - collectGarbage(leadingGarbage - 1, 0); - assert(firstChild == lastChild); - final double extent = childScrollOffset(lastChild) + paintExtentOf(lastChild); - geometry = new SliverGeometry( - scrollExtent: extent, - paintExtent: 0.0, - maxPaintExtent: extent, - ); - return; - } - } - - // Now find the first child that ends after our end. - while (endScrollOffset < targetEndScrollOffset) { - if (!advance()) { - reachedEnd = true; - break; - } - } - - // Finally count up all the remaining children and label them as garbage. - if (child != null) { - child = childAfter(child); - while (child != null) { - trailingGarbage += 1; - child = childAfter(child); - } - } - - // At this point everything should be good to go, we just have to clean up - // the garbage and report the geometry. - - collectGarbage(leadingGarbage, trailingGarbage); - - assert(debugAssertChildListIsNonEmptyAndContiguous()); - double estimatedMaxScrollOffset; - if (reachedEnd) { - estimatedMaxScrollOffset = endScrollOffset; - } else { - estimatedMaxScrollOffset = childManager.estimateMaxScrollOffset( - constraints, - firstIndex: indexOf(firstChild), - lastIndex: indexOf(lastChild), - leadingScrollOffset: childScrollOffset(firstChild), - trailingScrollOffset: endScrollOffset, - ); - assert(estimatedMaxScrollOffset >= endScrollOffset - childScrollOffset(firstChild)); - } - final double paintedExtent = calculatePaintOffset( - constraints, - from: childScrollOffset(firstChild), - to: endScrollOffset, - ); - geometry = new SliverGeometry( - scrollExtent: estimatedMaxScrollOffset, - paintExtent: paintedExtent, - maxPaintExtent: estimatedMaxScrollOffset, - // Conservative to avoid flickering away the clip during scroll. - hasVisualOverflow: endScrollOffset > targetEndScrollOffset || constraints.scrollOffset > 0.0, - ); - - assert(childManager.debugAssertChildListLocked()); - } -} diff --git a/packages/flutter/lib/src/rendering/sliver_fixed_extent_list.dart b/packages/flutter/lib/src/rendering/sliver_fixed_extent_list.dart new file mode 100644 index 0000000000..0bccb34930 --- /dev/null +++ b/packages/flutter/lib/src/rendering/sliver_fixed_extent_list.dart @@ -0,0 +1,152 @@ +// Copyright 2017 The Chromium 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 'dart:math' as math; + +import 'package:meta/meta.dart'; + +import 'box.dart'; +import 'sliver.dart'; +import 'sliver_multi_box_adaptor.dart'; + +abstract class RenderSliverFixedExtentBoxAdaptor extends RenderSliverMultiBoxAdaptor { + RenderSliverFixedExtentBoxAdaptor({ + @required RenderSliverBoxChildManager childManager, + }) : super(childManager: childManager); + + /// The main-axis extent of each item. + double get itemExtent; + + @override + void performLayout() { + assert(childManager.debugAssertChildListLocked()); + + final double itemExtent = this.itemExtent; + double indexToScrollOffset(int index) => itemExtent * index; + + final double scrollOffset = constraints.scrollOffset; + assert(scrollOffset >= 0.0); + final double remainingPaintExtent = constraints.remainingPaintExtent; + assert(remainingPaintExtent >= 0.0); + final double targetEndScrollOffset = scrollOffset + remainingPaintExtent; + + BoxConstraints childConstraints = constraints.asBoxConstraints( + minExtent: itemExtent, + maxExtent: itemExtent, + ); + + final int firstIndex = math.max(0, scrollOffset ~/ itemExtent); + final int targetLastIndex = math.max(0, (targetEndScrollOffset / itemExtent).ceil() - 1); + + if (firstChild != null) { + final int oldFirstIndex = indexOf(firstChild); + final int oldLastIndex = indexOf(lastChild); + final int leadingGarbage = (firstIndex - oldFirstIndex).clamp(0, childCount); + final int trailingGarbage = (oldLastIndex - targetLastIndex).clamp(0, childCount); + if (leadingGarbage + trailingGarbage > 0) + collectGarbage(leadingGarbage, trailingGarbage); + } + + if (firstChild == null) { + if (!addInitialChild(index: firstIndex, scrollOffset: indexToScrollOffset(firstIndex))) { + // There are no children. + geometry = SliverGeometry.zero; + return; + } + } + + RenderBox trailingChildWithLayout; + + for (int index = indexOf(firstChild) - 1; index >= firstIndex; --index) { + final RenderBox child = insertAndLayoutLeadingChild(childConstraints); + final SliverMultiBoxAdaptorParentData childParentData = child.parentData; + childParentData.scrollOffset = indexToScrollOffset(index); + assert(childParentData.index == index); + trailingChildWithLayout ??= child; + } + + assert(childScrollOffset(firstChild) <= scrollOffset); + + if (trailingChildWithLayout == null) { + firstChild.layout(childConstraints); + trailingChildWithLayout = firstChild; + } + + while (indexOf(trailingChildWithLayout) < targetLastIndex) { + RenderBox child = childAfter(trailingChildWithLayout); + if (child == null) { + child = insertAndLayoutChild(childConstraints, after: trailingChildWithLayout); + if (child == null) { + // We have run out of children. + break; + } + } else { + child.layout(childConstraints); + } + trailingChildWithLayout = child; + assert(child != null); + final SliverMultiBoxAdaptorParentData childParentData = child.parentData; + childParentData.scrollOffset = indexToScrollOffset(childParentData.index); + } + + final int lastIndex = indexOf(lastChild); + final double leadingScrollOffset = indexToScrollOffset(firstIndex); + final double trailingScrollOffset = indexToScrollOffset(lastIndex + 1); + + assert(debugAssertChildListIsNonEmptyAndContiguous()); + assert(indexOf(firstChild) == firstIndex); + assert(lastIndex <= targetLastIndex); + + final double estimatedMaxScrollOffset = childManager.estimateMaxScrollOffset( + constraints, + firstIndex: firstIndex, + lastIndex: lastIndex, + leadingScrollOffset: leadingScrollOffset, + trailingScrollOffset: trailingScrollOffset, + ); + + final double paintedExtent = calculatePaintOffset( + constraints, + from: leadingScrollOffset, + to: trailingScrollOffset, + ); + + geometry = new SliverGeometry( + scrollExtent: estimatedMaxScrollOffset, + paintExtent: paintedExtent, + maxPaintExtent: estimatedMaxScrollOffset, + // Conservative to avoid flickering away the clip during scroll. + hasVisualOverflow: lastIndex >= targetLastIndex || constraints.scrollOffset > 0.0, + ); + + assert(childManager.debugAssertChildListLocked()); + } +} + +class RenderSliverFixedExtentList extends RenderSliverFixedExtentBoxAdaptor { + RenderSliverFixedExtentList({ + @required RenderSliverBoxChildManager childManager, + double itemExtent, + }) : _itemExtent = itemExtent, super(childManager: childManager); + + @override + double get itemExtent => _itemExtent; + double _itemExtent; + set itemExtent (double newValue) { + assert(newValue != null); + if (_itemExtent == newValue) + return; + _itemExtent = newValue; + markNeedsLayout(); + } +} + +class RenderSliverFill extends RenderSliverFixedExtentBoxAdaptor { + RenderSliverFill({ + @required RenderSliverBoxChildManager childManager, + }) : super(childManager: childManager); + + @override + double get itemExtent => constraints.viewportMainAxisExtent; +} diff --git a/packages/flutter/lib/src/rendering/sliver_list.dart b/packages/flutter/lib/src/rendering/sliver_list.dart index 67fcbb69eb..522634c9f4 100644 --- a/packages/flutter/lib/src/rendering/sliver_list.dart +++ b/packages/flutter/lib/src/rendering/sliver_list.dart @@ -2,151 +2,214 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:math' as math; - +import 'package:flutter/foundation.dart'; import 'package:meta/meta.dart'; import 'box.dart'; import 'sliver.dart'; import 'sliver_multi_box_adaptor.dart'; -abstract class RenderSliverFixedExtentBoxAdaptor extends RenderSliverMultiBoxAdaptor { - RenderSliverFixedExtentBoxAdaptor({ - @required RenderSliverBoxChildManager childManager, +class RenderSliverList extends RenderSliverMultiBoxAdaptor { + RenderSliverList({ + @required RenderSliverBoxChildManager childManager }) : super(childManager: childManager); - /// The main-axis extent of each item. - double get itemExtent; - @override void performLayout() { assert(childManager.debugAssertChildListLocked()); - - final double itemExtent = this.itemExtent; - double indexToScrollOffset(int index) => itemExtent * index; - - final double scrollOffset = constraints.scrollOffset; + double scrollOffset = constraints.scrollOffset; assert(scrollOffset >= 0.0); - final double remainingPaintExtent = constraints.remainingPaintExtent; + double remainingPaintExtent = constraints.remainingPaintExtent; assert(remainingPaintExtent >= 0.0); - final double targetEndScrollOffset = scrollOffset + remainingPaintExtent; + double targetEndScrollOffset = scrollOffset + remainingPaintExtent; + BoxConstraints childConstraints = constraints.asBoxConstraints(); + int leadingGarbage = 0; + int trailingGarbage = 0; + bool reachedEnd = false; - BoxConstraints childConstraints = constraints.asBoxConstraints( - minExtent: itemExtent, - maxExtent: itemExtent, - ); - - final int firstIndex = math.max(0, scrollOffset ~/ itemExtent); - final int targetLastIndex = math.max(0, (targetEndScrollOffset / itemExtent).ceil() - 1); - - if (firstChild != null) { - final int oldFirstIndex = indexOf(firstChild); - final int oldLastIndex = indexOf(lastChild); - final int leadingGarbage = (firstIndex - oldFirstIndex).clamp(0, childCount); - final int trailingGarbage = (oldLastIndex - targetLastIndex).clamp(0, childCount); - if (leadingGarbage + trailingGarbage > 0) - collectGarbage(leadingGarbage, trailingGarbage); - } + // This algorithm in principle is straight-forward: find the first child + // that overlaps the given scrollOffset, creating more children at the top + // of the list if necessary, then walk down the list updating and laying out + // each child and adding more at the end if necessary until we have enough + // children to cover the entire viewport. + // + // It is complicated by one minor issue, which is that any time you update + // or create a child, it's possible that the some of the children that + // haven't yet been laid out will be removed, leaving the list in an + // inconsistent state, and requiring that missing nodes be recreated. + // + // To keep this mess tractable, this algorithm starts from what is currently + // the first child, if any, and then walks up and/or down from there, so + // that the nodes that might get removed are always at the edges of what has + // already been laid out. + // Make sure we have at least one child to start from. if (firstChild == null) { - if (!addInitialChild(index: firstIndex, scrollOffset: indexToScrollOffset(firstIndex))) { + if (!addInitialChild()) { // There are no children. geometry = SliverGeometry.zero; return; } } - RenderBox trailingChildWithLayout; + // We have at least one child. - for (int index = indexOf(firstChild) - 1; index >= firstIndex; --index) { - final RenderBox child = insertAndLayoutLeadingChild(childConstraints); - final SliverMultiBoxAdaptorParentData childParentData = child.parentData; - childParentData.scrollOffset = indexToScrollOffset(index); - assert(childParentData.index == index); - trailingChildWithLayout ??= child; - } + // These variables track the range of children that we have laid out. Within + // this range, the children have consecutive indices. Outside this range, + // it's possible for a child to get removed without notice. + RenderBox leadingChildWithLayout, trailingChildWithLayout; - assert(childScrollOffset(firstChild) <= scrollOffset); - - if (trailingChildWithLayout == null) { - firstChild.layout(childConstraints); - trailingChildWithLayout = firstChild; - } - - while (indexOf(trailingChildWithLayout) < targetLastIndex) { - RenderBox child = childAfter(trailingChildWithLayout); - if (child == null) { - child = insertAndLayoutChild(childConstraints, after: trailingChildWithLayout); - if (child == null) { - // We have run out of children. - break; - } - } else { - child.layout(childConstraints); + // Find the last child that is at or before the scrollOffset. + RenderBox earliestUsefulChild = firstChild; + for (double earliestScrollOffset = childScrollOffset(earliestUsefulChild); + earliestScrollOffset > scrollOffset; + earliestScrollOffset = childScrollOffset(earliestUsefulChild)) { + // We have to add children before the earliestUsefulChild. + earliestUsefulChild = insertAndLayoutLeadingChild(childConstraints, parentUsesSize: true); + if (earliestUsefulChild == null) { + // We ran out of children before reaching the scroll offset. + // We must inform our parent that this sliver cannot fulfill + // its contract and that we need a scroll offset correction. + geometry = new SliverGeometry( + scrollOffsetCorrection: -childScrollOffset(firstChild), + ); + return; + } + final SliverMultiBoxAdaptorParentData childParentData = earliestUsefulChild.parentData; + childParentData.scrollOffset = earliestScrollOffset - paintExtentOf(firstChild); + assert(earliestUsefulChild == firstChild); + leadingChildWithLayout = earliestUsefulChild; + trailingChildWithLayout ??= earliestUsefulChild; + } + + // At this point, earliestUsefulChild is the first child, and is a child + // whose scrollOffset is at or before the scrollOffset, and + // leadingChildWithLayout and trailingChildWithLayout are either null or + // cover a range of render boxes that we have laid out with the first being + // the same as earliestUsefulChild and the last being either at or after the + // scroll offset. + + assert(earliestUsefulChild == firstChild); + assert(childScrollOffset(earliestUsefulChild) <= scrollOffset); + + // Make sure we've laid out at least one child. + if (leadingChildWithLayout == null) { + earliestUsefulChild.layout(childConstraints, parentUsesSize: true); + leadingChildWithLayout = earliestUsefulChild; + trailingChildWithLayout = earliestUsefulChild; + } + + // Here, earliestUsefulChild is still the first child, it's got a + // scrollOffset that is at or before our actual scrollOffset, and it has + // been laid out, and is in fact our leadingChildWithLayout. It's possible + // that some children beyond that one have also been laid out. + + bool inLayoutRange = true; + RenderBox child = earliestUsefulChild; + int index = indexOf(child); + double endScrollOffset = childScrollOffset(child) + paintExtentOf(child); + bool advance() { // returns true if we advanced, false if we have no more children + // This function is used in two different places below, to avoid code duplication. + assert(child != null); + if (child == trailingChildWithLayout) + inLayoutRange = false; + child = childAfter(child); + if (child == null) + inLayoutRange = false; + index += 1; + if (!inLayoutRange) { + if (child == null || indexOf(child) != index) { + // We are missing a child. Insert it (and lay it out) if possible. + child = insertAndLayoutChild(childConstraints, + after: trailingChildWithLayout, + parentUsesSize: true, + ); + if (child == null) { + // We have run out of children. + return false; + } + } else { + // Lay out the child. + child.layout(childConstraints, parentUsesSize: true); + } + trailingChildWithLayout = child; } - trailingChildWithLayout = child; assert(child != null); final SliverMultiBoxAdaptorParentData childParentData = child.parentData; - childParentData.scrollOffset = indexToScrollOffset(childParentData.index); + childParentData.scrollOffset = endScrollOffset; + assert(childParentData.index == index); + endScrollOffset = childScrollOffset(child) + paintExtentOf(child); + return true; } - final int lastIndex = indexOf(lastChild); - final double leadingScrollOffset = indexToScrollOffset(firstIndex); - final double trailingScrollOffset = indexToScrollOffset(lastIndex + 1); + // Find the first child that ends after the scroll offset. + while (endScrollOffset < scrollOffset) { + leadingGarbage += 1; + if (!advance()) { + assert(leadingGarbage == childCount); + assert(child == null); + // we want to make sure we keep the last child around so we know the end scroll offset + collectGarbage(leadingGarbage - 1, 0); + assert(firstChild == lastChild); + final double extent = childScrollOffset(lastChild) + paintExtentOf(lastChild); + geometry = new SliverGeometry( + scrollExtent: extent, + paintExtent: 0.0, + maxPaintExtent: extent, + ); + return; + } + } + + // Now find the first child that ends after our end. + while (endScrollOffset < targetEndScrollOffset) { + if (!advance()) { + reachedEnd = true; + break; + } + } + + // Finally count up all the remaining children and label them as garbage. + if (child != null) { + child = childAfter(child); + while (child != null) { + trailingGarbage += 1; + child = childAfter(child); + } + } + + // At this point everything should be good to go, we just have to clean up + // the garbage and report the geometry. + + collectGarbage(leadingGarbage, trailingGarbage); assert(debugAssertChildListIsNonEmptyAndContiguous()); - assert(indexOf(firstChild) == firstIndex); - assert(lastIndex <= targetLastIndex); - - final double estimatedMaxScrollOffset = childManager.estimateMaxScrollOffset( - constraints, - firstIndex: firstIndex, - lastIndex: lastIndex, - leadingScrollOffset: leadingScrollOffset, - trailingScrollOffset: trailingScrollOffset, - ); - + double estimatedMaxScrollOffset; + if (reachedEnd) { + estimatedMaxScrollOffset = endScrollOffset; + } else { + estimatedMaxScrollOffset = childManager.estimateMaxScrollOffset( + constraints, + firstIndex: indexOf(firstChild), + lastIndex: indexOf(lastChild), + leadingScrollOffset: childScrollOffset(firstChild), + trailingScrollOffset: endScrollOffset, + ); + assert(estimatedMaxScrollOffset >= endScrollOffset - childScrollOffset(firstChild)); + } final double paintedExtent = calculatePaintOffset( constraints, - from: leadingScrollOffset, - to: trailingScrollOffset, + from: childScrollOffset(firstChild), + to: endScrollOffset, ); - geometry = new SliverGeometry( scrollExtent: estimatedMaxScrollOffset, paintExtent: paintedExtent, maxPaintExtent: estimatedMaxScrollOffset, // Conservative to avoid flickering away the clip during scroll. - hasVisualOverflow: lastIndex >= targetLastIndex || constraints.scrollOffset > 0.0, + hasVisualOverflow: endScrollOffset > targetEndScrollOffset || constraints.scrollOffset > 0.0, ); assert(childManager.debugAssertChildListLocked()); } } - -class RenderSliverList extends RenderSliverFixedExtentBoxAdaptor { - RenderSliverList({ - @required RenderSliverBoxChildManager childManager, - double itemExtent, - }) : _itemExtent = itemExtent, super(childManager: childManager); - - @override - double get itemExtent => _itemExtent; - double _itemExtent; - set itemExtent (double newValue) { - assert(newValue != null); - if (_itemExtent == newValue) - return; - _itemExtent = newValue; - markNeedsLayout(); - } -} - -class RenderSliverFill extends RenderSliverFixedExtentBoxAdaptor { - RenderSliverFill({ - @required RenderSliverBoxChildManager childManager, - }) : super(childManager: childManager); - - @override - double get itemExtent => constraints.viewportMainAxisExtent; -} diff --git a/packages/flutter/lib/src/widgets/basic.dart b/packages/flutter/lib/src/widgets/basic.dart index 074421eb6f..d974fc5e53 100644 --- a/packages/flutter/lib/src/widgets/basic.dart +++ b/packages/flutter/lib/src/widgets/basic.dart @@ -1482,14 +1482,12 @@ class SliverPadding extends SingleChildRenderObjectWidget { /// A widget that uses the block layout algorithm for its children. /// -/// This widget is rarely used directly. Instead, consider using [Block], which -/// combines the block layout algorithm with scrolling behavior. +/// This widget is rarely used directly. Instead, consider using [SliverList], +/// which combines a similar layout algorithm with scrolling behavior, or +/// [Column], which gives you more flexible control over the layout of a +/// vertical set of boxes. /// /// For details about the block layout algorithm, see [RenderBlockBase]. -/// -/// See also: -/// -/// * [Block], which combines block layout with scrolling. class BlockBody extends MultiChildRenderObjectWidget { /// Creates a block layout widget. /// @@ -2001,7 +1999,7 @@ class GridPlacementData extends ParentDataWidget children; - RenderSliverBlock createRenderObject() { + RenderSliverList createRenderObject() { assert(_renderObject == null); - _renderObject = new RenderSliverBlock(childManager: this); + _renderObject = new RenderSliverList(childManager: this); return _renderObject; } @@ -62,7 +62,7 @@ class TestRenderSliverBoxChildManager extends RenderSliverBoxChildManager { } void main() { - test('RenderSliverBlock basic test - down', () { + test('RenderSliverList basic test - down', () { RenderObject inner; RenderBox a, b, c, d, e; TestRenderSliverBoxChildManager childManager = new TestRenderSliverBoxChildManager( @@ -136,7 +136,7 @@ void main() { expect(e.attached, false); }); - test('RenderSliverBlock basic test - up', () { + test('RenderSliverList basic test - up', () { RenderObject inner; RenderBox a, b, c, d, e; TestRenderSliverBoxChildManager childManager = new TestRenderSliverBoxChildManager( diff --git a/packages/flutter/test/widgets/block_test.dart b/packages/flutter/test/widgets/block_test.dart index 04d574eff6..9c49ed1cf2 100644 --- a/packages/flutter/test/widgets/block_test.dart +++ b/packages/flutter/test/widgets/block_test.dart @@ -134,13 +134,13 @@ void main() { await tester.pumpWidget(new TestScrollable( slivers: [ - new SliverBlock( + new SliverList( delegate: delegate, ), ], )); - final SliverMultiBoxAdaptorElement element = tester.element(find.byType(SliverBlock)); + final SliverMultiBoxAdaptorElement element = tester.element(find.byType(SliverList)); final double maxScrollOffset = element.estimateMaxScrollOffset( null, diff --git a/packages/flutter/test/widgets/slivers_block_global_key_test.dart b/packages/flutter/test/widgets/slivers_block_global_key_test.dart index 76a0a6a1be..cee37d82c0 100644 --- a/packages/flutter/test/widgets/slivers_block_global_key_test.dart +++ b/packages/flutter/test/widgets/slivers_block_global_key_test.dart @@ -27,7 +27,7 @@ Future test(WidgetTester tester, double offset, List keys) { return tester.pumpWidget(new Viewport2( offset: new ViewportOffset.fixed(offset), slivers: [ - new SliverBlock( + new SliverList( delegate: new SliverChildListDelegate(keys.map((int key) { return new SizedBox(key: new GlobalObjectKey(key), height: 100.0, child: new GenerationText(key)); }).toList()), diff --git a/packages/flutter/test/widgets/slivers_block_test.dart b/packages/flutter/test/widgets/slivers_block_test.dart index 29f88e47a4..39c400e929 100644 --- a/packages/flutter/test/widgets/slivers_block_test.dart +++ b/packages/flutter/test/widgets/slivers_block_test.dart @@ -12,7 +12,7 @@ Future test(WidgetTester tester, double offset) { return tester.pumpWidget(new Viewport2( offset: new ViewportOffset.fixed(offset), slivers: [ - new SliverBlock( + new SliverList( delegate: new SliverChildListDelegate([ new SizedBox(height: 400.0, child: new Text('a')), new SizedBox(height: 400.0, child: new Text('b')), @@ -77,7 +77,7 @@ void main() { await tester.pumpWidget(new Viewport2( offset: offset, slivers: [ - new SliverBlock( + new SliverList( delegate: new SliverChildListDelegate([ new SizedBox(height: 251.0, child: new Text('a')), new SizedBox(height: 252.0, child: new Text('b')), @@ -94,7 +94,7 @@ void main() { await tester.pumpWidget(new Viewport2( offset: offset, slivers: [ - new SliverBlock( + new SliverList( delegate: new SliverChildListDelegate([ new SizedBox(key: key1, height: 253.0, child: new Text('c')), new SizedBox(height: 251.0, child: new Text('a')), @@ -111,7 +111,7 @@ void main() { await tester.pumpWidget(new Viewport2( offset: offset, slivers: [ - new SliverBlock( + new SliverList( delegate: new SliverChildListDelegate([ new SizedBox(height: 251.0, child: new Text('a')), new SizedBox(key: key1, height: 253.0, child: new Text('c')), @@ -128,7 +128,7 @@ void main() { await tester.pumpWidget(new Viewport2( offset: offset, slivers: [ - new SliverBlock( + new SliverList( delegate: new SliverChildListDelegate([ new SizedBox(height: 251.0, child: new Text('a')), new SizedBox(height: 252.0, child: new Text('b')), @@ -143,7 +143,7 @@ void main() { await tester.pumpWidget(new Viewport2( offset: offset, slivers: [ - new SliverBlock( + new SliverList( delegate: new SliverChildListDelegate([ new SizedBox(height: 251.0, child: new Text('a')), new SizedBox(key: key1, height: 253.0, child: new Text('c')), @@ -209,7 +209,7 @@ void main() { await tester.pumpWidget(new Viewport2( offset: new ViewportOffset.zero(), slivers: [ - new SliverBlock( + new SliverList( delegate: new SliverChildListDelegate([ new SizedBox(height: 400.0, child: new Text('a')), ]), @@ -222,7 +222,7 @@ void main() { await tester.pumpWidget(new Viewport2( offset: new ViewportOffset.fixed(100.0), slivers: [ - new SliverBlock( + new SliverList( delegate: new SliverChildListDelegate([ new SizedBox(height: 400.0, child: new Text('a')), ]), @@ -235,7 +235,7 @@ void main() { await tester.pumpWidget(new Viewport2( offset: new ViewportOffset.fixed(100.0), slivers: [ - new SliverBlock( + new SliverList( delegate: new SliverChildListDelegate([ new SizedBox(height: 4000.0, child: new Text('a')), ]), @@ -248,7 +248,7 @@ void main() { await tester.pumpWidget(new Viewport2( offset: new ViewportOffset.zero(), slivers: [ - new SliverBlock( + new SliverList( delegate: new SliverChildListDelegate([ new SizedBox(height: 4000.0, child: new Text('a')), ]), diff --git a/packages/flutter/test/widgets/slivers_evil_test.dart b/packages/flutter/test/widgets/slivers_evil_test.dart index 6744d9aeb6..e764a7c88f 100644 --- a/packages/flutter/test/widgets/slivers_evil_test.dart +++ b/packages/flutter/test/widgets/slivers_evil_test.dart @@ -117,7 +117,7 @@ void main() { new SliverToBoxAdapter(child: new Container(height: 520.0)), new SliverAppBar(delegate: new TestSliverAppBarDelegate(150.0), floating: true), new SliverToBoxAdapter(child: new Container(height: 5.0)), - new SliverBlock( + new SliverList( delegate: new SliverChildListDelegate([ new Container(height: 50.0), new Container(height: 50.0),