Commit 1b948cbb authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

Rename SliverFill to SliverFillViewport and add SliverFillRemaining (#9194)

parent e355c601
......@@ -50,6 +50,7 @@ export 'src/rendering/rotated_box.dart';
export 'src/rendering/semantics.dart';
export 'src/rendering/shifted_box.dart';
export 'src/rendering/sliver.dart';
export 'src/rendering/sliver_fill.dart';
export 'src/rendering/sliver_fixed_extent_list.dart';
export 'src/rendering/sliver_grid.dart';
export 'src/rendering/sliver_list.dart';
......
......@@ -412,7 +412,7 @@ class SliverConstraints extends Constraints {
'scrollOffset: ${scrollOffset.toStringAsFixed(1)}, '
'remainingPaintExtent: ${remainingPaintExtent.toStringAsFixed(1)}, ' +
(overlap != 0.0 ? 'overlap: ${overlap.toStringAsFixed(1)}, ' : '') +
'crossAxisExtent: ${crossAxisExtent.toStringAsFixed(1)}' +
'crossAxisExtent: ${crossAxisExtent.toStringAsFixed(1)}, ' +
'viewportMainAxisExtent: ${viewportMainAxisExtent.toStringAsFixed(1)}' +
')';
}
......@@ -1218,19 +1218,19 @@ abstract class RenderSliverHelpers implements RenderSliver {
// ADAPTER FOR RENDER BOXES INSIDE SLIVERS
// Transitions from the RenderSliver world to the RenderBox world.
/// A [RenderSliver] that contains a single [RenderBox].
///
/// The child will not be laid out if it is not visible.
/// An abstract class for [RenderSliver]s that contains a single [RenderBox].
///
/// See also:
///
/// * [RenderSliver], which explains more about the Sliver protocol.
/// * [RenderBox], which explains more about the Box protocol.
/// * [RenderViewport], which allows [RenderSliver] objects to be placed inside
/// a [RenderBox] (the opposite of this class).
class RenderSliverToBoxAdapter extends RenderSliver with RenderObjectWithChildMixin<RenderBox>, RenderSliverHelpers {
/// * [RenderSliverToBoxAdapter], which extends this class to size the child
/// according to its preferred size.
/// * [RenderSliverFillRemaining], which extends this class to size the child
/// to fill the remaining space in the viewport.
abstract class RenderSliverSingleBoxAdapter extends RenderSliver with RenderObjectWithChildMixin<RenderBox>, RenderSliverHelpers {
/// Creates a [RenderSliver] that wraps a [RenderBox].
RenderSliverToBoxAdapter({
RenderSliverSingleBoxAdapter({
RenderBox child,
}) {
this.child = child;
......@@ -1242,34 +1242,7 @@ class RenderSliverToBoxAdapter extends RenderSliver with RenderObjectWithChildMi
child.parentData = new SliverPhysicalParentData();
}
@override
void performLayout() {
if (child == null) {
geometry = SliverGeometry.zero;
return;
}
child.layout(constraints.asBoxConstraints(), parentUsesSize: true);
double childExtent;
switch (constraints.axis) {
case Axis.horizontal:
childExtent = child.size.width;
break;
case Axis.vertical:
childExtent = child.size.height;
break;
}
assert(childExtent != null);
final double paintedChildSize = calculatePaintOffset(constraints, from: 0.0, to: childExtent);
assert(paintedChildSize.isFinite);
assert(paintedChildSize >= 0.0);
geometry = new SliverGeometry(
scrollExtent: childExtent,
paintExtent: paintedChildSize,
maxPaintExtent: childExtent,
hitTestExtent: paintedChildSize,
hasVisualOverflow: childExtent > constraints.remainingPaintExtent || constraints.scrollOffset > 0.0,
);
void setChildParentData(RenderObject child, SliverConstraints constraints, SliverGeometry geometry) {
final SliverPhysicalParentData childParentData = child.parentData;
assert(constraints.axisDirection != null);
assert(constraints.growthDirection != null);
......@@ -1321,3 +1294,52 @@ class RenderSliverToBoxAdapter extends RenderSliver with RenderObjectWithChildMi
// TODO(ianh): semantics - shouldn't walk the invisible children
}
/// A [RenderSliver] that contains a single [RenderBox].
///
/// The child will not be laid out if it is not visible. It is sized according
/// to the child's preferences in the main axis, and with a tight constraint
/// forcing it to the dimensions of the viewport in the cross axis.
///
/// See also:
///
/// * [RenderSliver], which explains more about the Sliver protocol.
/// * [RenderBox], which explains more about the Box protocol.
/// * [RenderViewport], which allows [RenderSliver] objects to be placed inside
/// a [RenderBox] (the opposite of this class).
class RenderSliverToBoxAdapter extends RenderSliverSingleBoxAdapter {
/// Creates a [RenderSliver] that wraps a [RenderBox].
RenderSliverToBoxAdapter({
RenderBox child,
}) : super(child: child);
@override
void performLayout() {
if (child == null) {
geometry = SliverGeometry.zero;
return;
}
child.layout(constraints.asBoxConstraints(), parentUsesSize: true);
double childExtent;
switch (constraints.axis) {
case Axis.horizontal:
childExtent = child.size.width;
break;
case Axis.vertical:
childExtent = child.size.height;
break;
}
assert(childExtent != null);
final double paintedChildSize = calculatePaintOffset(constraints, from: 0.0, to: childExtent);
assert(paintedChildSize.isFinite);
assert(paintedChildSize >= 0.0);
geometry = new SliverGeometry(
scrollExtent: childExtent,
paintExtent: paintedChildSize,
maxPaintExtent: childExtent,
hitTestExtent: paintedChildSize,
hasVisualOverflow: childExtent > constraints.remainingPaintExtent || constraints.scrollOffset > 0.0,
);
setChildParentData(child, constraints, geometry);
}
}
// 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:flutter/foundation.dart';
import 'box.dart';
import 'sliver.dart';
import 'sliver_fixed_extent_list.dart';
import 'sliver_multi_box_adaptor.dart';
/// A sliver that contains multiple box children that each fill the viewport.
///
/// [RenderSliverFillViewport] places its children in a linear array along the
/// main axis. Each child is sized to fill the viewport, both in the main and
/// cross axis. A [viewportFraction] factor can be provided to size the children
/// to a multiple of the viewport's main axis dimension (typically a fraction
/// less than 1.0).
///
/// See also:
///
/// * [RenderSliverFillRemaining], which sizes the children based on the
/// remaining space rather than the viewport itself.
/// * [RenderSliverFixedExtentList], which has a configurable [itemExtent].
/// * [RenderSliverList], which does not require its children to have the same
/// * /// extent in the main axis.
class RenderSliverFillViewport extends RenderSliverFixedExtentBoxAdaptor {
/// Creates a sliver that contains multiple box children that each fill the
/// viewport.
///
/// The [childManager] argument must not be null.
RenderSliverFillViewport({
@required RenderSliverBoxChildManager childManager,
double viewportFraction: 1.0,
}) : _viewportFraction = viewportFraction, super(childManager: childManager) {
assert(viewportFraction != null);
assert(viewportFraction > 0.0);
}
@override
double get itemExtent => constraints.viewportMainAxisExtent * viewportFraction;
/// The fraction of the viewport that each child should fill in the main axis.
///
/// If this fraction is less than 1.0, more than one child will be visible at
/// once. If this fraction is greater than 1.0, each child will be larger than
/// the viewport in the main axis.
double get viewportFraction => _viewportFraction;
double _viewportFraction;
set viewportFraction(double value) {
assert(value != null);
if (_viewportFraction == value)
return;
_viewportFraction = value;
markNeedsLayout();
}
double get _padding => (1.0 - viewportFraction) * constraints.viewportMainAxisExtent * 0.5;
@override
double indexToLayoutOffset(double itemExtent, int index) {
return _padding + super.indexToLayoutOffset(itemExtent, index);
}
@override
int getMinChildIndexForScrollOffset(double scrollOffset, double itemExtent) {
return super.getMinChildIndexForScrollOffset(math.max(scrollOffset - _padding, 0.0), itemExtent);
}
@override
int getMaxChildIndexForScrollOffset(double scrollOffset, double itemExtent) {
return super.getMaxChildIndexForScrollOffset(math.max(scrollOffset - _padding, 0.0), itemExtent);
}
@override
double estimateMaxScrollOffset(SliverConstraints constraints, {
int firstIndex,
int lastIndex,
double leadingScrollOffset,
double trailingScrollOffset,
}) {
final double padding = _padding;
return childManager.estimateMaxScrollOffset(
constraints,
firstIndex: firstIndex,
lastIndex: lastIndex,
leadingScrollOffset: leadingScrollOffset - padding,
trailingScrollOffset: trailingScrollOffset - padding,
) + padding + padding;
}
}
/// A sliver that contains a single box child that fills the remaining space in
/// the viewport.
///
/// [RenderSliverFillRemaining] sizes its child to fill the viewport in the
/// cross axis and to fill the remaining space in the viewport in the main axis.
///
/// Typically this will be the last sliver in a viewport, since (by definition)
/// there is never any room for anything beyond this sliver.
///
/// See also:
///
/// * [RenderSliverFillViewport], which sizes its children based on the
/// size of the viewport, regardless of what else is in the scroll view.
/// * [RenderSliverList], which shows a list of variable-sized children in a
/// viewport.
class RenderSliverFillRemaining extends RenderSliverSingleBoxAdapter {
/// Creates a [RenderSliver] that wraps a [RenderBox] which is sized to fit
/// the remaining space in the viewport.
RenderSliverFillRemaining({
RenderBox child,
}) : super(child: child);
@override
void performLayout() {
final double size = constraints.remainingPaintExtent - math.min(constraints.overlap, 0.0);
if (child != null)
child.layout(constraints.asBoxConstraints(minExtent: size, maxExtent: size), parentUsesSize: true);
final double paintedChildSize = calculatePaintOffset(constraints, from: 0.0, to: size);
assert(paintedChildSize.isFinite);
assert(paintedChildSize >= 0.0);
geometry = new SliverGeometry(
scrollExtent: constraints.viewportMainAxisExtent,
paintExtent: paintedChildSize,
maxPaintExtent: paintedChildSize,
hasVisualOverflow: size > constraints.remainingPaintExtent || constraints.scrollOffset > 0.0,
);
if (child != null)
setChildParentData(child, constraints, geometry);
}
}
......@@ -28,8 +28,10 @@ import 'sliver_multi_box_adaptor.dart';
/// See also:
///
/// * [RenderSliverFixedExtentList], which has a configurable [itemExtent].
/// * [RenderSliverFill], which determines the [itemExtent] based on
/// * [RenderSliverFillViewport], which determines the [itemExtent] based on
/// [SliverConstraints.viewportMainAxisExtent].
/// * [RenderSliverFillRemaining], which determines the [itemExtent] based on
/// [SliverConstraints.remainingPaintExtent].
/// * [RenderSliverList], which does not require its children to have the same
/// extent in the main axis.
abstract class RenderSliverFixedExtentBoxAdaptor extends RenderSliverMultiBoxAdaptor {
......@@ -225,10 +227,12 @@ abstract class RenderSliverFixedExtentBoxAdaptor extends RenderSliverMultiBoxAda
///
/// See also:
///
/// * [RenderSliverFill], which determines the [itemExtent] based on
/// [SliverConstraints.viewportMainAxisExtent].
/// * [RenderSliverList], which does not require its children to have the same
/// extent in the main axis.
/// * [RenderSliverFillViewport], which determines the [itemExtent] based on
/// [SliverConstraints.viewportMainAxisExtent].
/// * [RenderSliverFillRemaining], which determines the [itemExtent] based on
/// [SliverConstraints.remainingPaintExtent].
class RenderSliverFixedExtentList extends RenderSliverFixedExtentBoxAdaptor {
/// Creates a sliver that contains multiple box children that have a given
/// extent in the main axis.
......@@ -250,80 +254,3 @@ class RenderSliverFixedExtentList extends RenderSliverFixedExtentBoxAdaptor {
markNeedsLayout();
}
}
/// A sliver that contains a multiple box children that each fill the viewport.
///
/// [RenderSliverFill] places its children in a linear array along the main
/// axis. Each child is sized to fill the viewport, both in the main and cross
/// axis.
///
/// See also:
///
/// * [RenderSliverFixedExtentList], which has a configurable [itemExtent].
/// * [RenderSliverList], which does not require its children to have the same
/// extent in the main axis.
class RenderSliverFill extends RenderSliverFixedExtentBoxAdaptor {
/// Creates a sliver that contains a multiple box children that each fill the
/// viewport.
///
/// The [childManager] argument must not be null.
RenderSliverFill({
@required RenderSliverBoxChildManager childManager,
double viewportFraction: 1.0,
}) : _viewportFraction = viewportFraction, super(childManager: childManager) {
assert(viewportFraction != null);
assert(viewportFraction > 0.0);
}
@override
double get itemExtent => constraints.viewportMainAxisExtent * viewportFraction;
/// The fraction of the viewport that each child should fill in the main axis.
///
/// If this fraction is less than 1.0, more than one child will be visible at
/// once. If this fraction is greater than 1.0, each child will be larger than
/// the viewport in the main axis.
double get viewportFraction => _viewportFraction;
double _viewportFraction;
set viewportFraction(double value) {
assert(value != null);
if (_viewportFraction == value)
return;
_viewportFraction = value;
markNeedsLayout();
}
double get _padding => (1.0 - viewportFraction) * constraints.viewportMainAxisExtent * 0.5;
@override
double indexToLayoutOffset(double itemExtent, int index) {
return _padding + super.indexToLayoutOffset(itemExtent, index);
}
@override
int getMinChildIndexForScrollOffset(double scrollOffset, double itemExtent) {
return super.getMinChildIndexForScrollOffset(math.max(scrollOffset - _padding, 0.0), itemExtent);
}
@override
int getMaxChildIndexForScrollOffset(double scrollOffset, double itemExtent) {
return super.getMaxChildIndexForScrollOffset(math.max(scrollOffset - _padding, 0.0), itemExtent);
}
@override
double estimateMaxScrollOffset(SliverConstraints constraints, {
int firstIndex,
int lastIndex,
double leadingScrollOffset,
double trailingScrollOffset,
}) {
final double padding = _padding;
return childManager.estimateMaxScrollOffset(
constraints,
firstIndex: firstIndex,
lastIndex: lastIndex,
leadingScrollOffset: leadingScrollOffset - padding,
trailingScrollOffset: trailingScrollOffset - padding,
) + padding + padding;
}
}
......@@ -426,7 +426,7 @@ class _PageViewState extends State<PageView> {
axisDirection: axisDirection,
offset: offset,
slivers: <Widget>[
new SliverFill(
new SliverFillViewport(
viewportFraction: config.controller.viewportFraction,
delegate: config.childrenDelegate
),
......
......@@ -334,7 +334,7 @@ class SliverList extends SliverMultiBoxAdaptorWidget {
///
/// See also:
///
/// * [SliverFill], which determines the [itemExtent] based on
/// * [SliverFillViewport], which determines the [itemExtent] based on
/// [SliverConstraints.viewportMainAxisExtent].
/// * [SliverList], which does not require its children to have the same
/// extent in the main axis.
......@@ -416,17 +416,18 @@ class SliverGrid extends SliverMultiBoxAdaptorWidget {
/// A sliver that contains a multiple box children that each fill the viewport.
///
/// [SliverFill] places its children in a linear array along the main axis. Each
/// child is sized to fill the viewport, both in the main and cross axis.
/// [SliverFillViewport] places its children in a linear array along the main
/// axis. Each child is sized to fill the viewport, both in the main and cross
/// axis.
///
/// See also:
///
/// * [SliverFixedExtentList], which has a configurable [itemExtent].
/// * [SliverList], which does not require its children to have the same
/// extent in the main axis.
class SliverFill extends SliverMultiBoxAdaptorWidget {
class SliverFillViewport extends SliverMultiBoxAdaptorWidget {
/// Creates a sliver whose box children that each fill the viewport.
SliverFill({
SliverFillViewport({
Key key,
@required SliverChildDelegate delegate,
this.viewportFraction: 1.0,
......@@ -443,13 +444,13 @@ class SliverFill extends SliverMultiBoxAdaptorWidget {
final double viewportFraction;
@override
RenderSliverFill createRenderObject(BuildContext context) {
RenderSliverFillViewport createRenderObject(BuildContext context) {
final SliverMultiBoxAdaptorElement element = context;
return new RenderSliverFill(childManager: element, viewportFraction: viewportFraction);
return new RenderSliverFillViewport(childManager: element, viewportFraction: viewportFraction);
}
@override
void updateRenderObject(BuildContext context, RenderSliverFill renderObject) {
void updateRenderObject(BuildContext context, RenderSliverFillViewport renderObject) {
renderObject.viewportFraction = viewportFraction;
}
}
......@@ -675,3 +676,28 @@ class SliverMultiBoxAdaptorElement extends RenderObjectElement implements Render
_childElements.values.toList().forEach(visitor);
}
}
/// A sliver that contains a single box child that fills the remaining space in
/// the viewport.
///
/// [SliverFillRemaining] sizes its child to fill the viewport in the cross axis
/// and to fill the remaining space in the viewport in the main axis.
///
/// Typically this will be the last sliver in a viewport, since (by definition)
/// there is never any room for anything beyond this sliver.
///
/// See also:
///
/// * [SliverFillViewport], which sizes its children based on the
/// size of the viewport, regardless of what else is in the scroll view.
/// * [SliverList], which shows a list of variable-sized children in a
/// viewport.
class SliverFillRemaining extends SingleChildRenderObjectWidget {
SliverFillRemaining({
Key key,
Widget child,
}) : super(key: key, child: child);
@override
RenderSliverFillRemaining createRenderObject(BuildContext context) => new RenderSliverFillRemaining();
}
......@@ -6,43 +6,54 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/widgets.dart';
void main() {
testWidgets('SliverFillRemaining control test', (WidgetTester tester) async {
final List<Widget> children = new List<Widget>.generate(20, (int i) {
return new Container(child: new Text('$i'));
});
testWidgets('SliverFillRemaining - no siblings', (WidgetTester tester) async {
final ScrollController controller = new ScrollController();
await tester.pumpWidget(
new CustomScrollView(
controller: controller,
slivers: <Widget>[
new SliverFill(
delegate: new SliverChildListDelegate(children),
),
new SliverFillRemaining(child: new Container()),
],
),
);
expect(tester.renderObject<RenderBox>(find.byType(Container)).size.height, equals(600.0));
final RenderBox box = tester.renderObject<RenderBox>(find.byType(Container).first);
expect(box.size.height, equals(600.0));
controller.jumpTo(50.0);
await tester.pump();
expect(tester.renderObject<RenderBox>(find.byType(Container)).size.height, equals(600.0));
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);
expect(find.text('2'), findsNothing);
controller.jumpTo(-100.0);
await tester.pump();
expect(tester.renderObject<RenderBox>(find.byType(Container)).size.height, equals(600.0));
await tester.drag(find.byType(Scrollable), const Offset(0.0, -700.0));
controller.jumpTo(0.0);
await tester.pump();
expect(tester.renderObject<RenderBox>(find.byType(Container)).size.height, equals(600.0));
});
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
expect(find.text('2'), findsOneWidget);
expect(find.text('3'), findsNothing);
expect(find.text('4'), findsNothing);
testWidgets('SliverFillRemaining - one sibling', (WidgetTester tester) async {
final ScrollController controller = new ScrollController();
await tester.pumpWidget(
new CustomScrollView(
controller: controller,
slivers: <Widget>[
new SliverToBoxAdapter(child: new SizedBox(height: 100.0)),
new SliverFillRemaining(child: new Container()),
],
),
);
expect(tester.renderObject<RenderBox>(find.byType(Container)).size.height, equals(500.0));
await tester.drag(find.byType(Scrollable), const Offset(0.0, 200.0));
controller.jumpTo(50.0);
await tester.pump();
expect(tester.renderObject<RenderBox>(find.byType(Container)).size.height, equals(550.0));
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsOneWidget);
expect(find.text('2'), findsNothing);
expect(find.text('3'), findsNothing);
controller.jumpTo(-100.0);
await tester.pump();
expect(tester.renderObject<RenderBox>(find.byType(Container)).size.height, equals(400.0)); // (!)
controller.jumpTo(0.0);
await tester.pump();
expect(tester.renderObject<RenderBox>(find.byType(Container)).size.height, equals(500.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_test/flutter_test.dart';
import 'package:flutter/widgets.dart';
void main() {
testWidgets('SliverFillRemaining control test', (WidgetTester tester) async {
final List<Widget> children = new List<Widget>.generate(20, (int i) {
return new Container(child: new Text('$i'));
});
await tester.pumpWidget(
new CustomScrollView(
slivers: <Widget>[
new SliverFillViewport(
delegate: new SliverChildListDelegate(children),
),
],
),
);
final RenderBox box = tester.renderObject<RenderBox>(find.byType(Container).first);
expect(box.size.height, equals(600.0));
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);
expect(find.text('2'), findsNothing);
await tester.drag(find.byType(Scrollable), const Offset(0.0, -700.0));
await tester.pump();
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
expect(find.text('2'), findsOneWidget);
expect(find.text('3'), findsNothing);
expect(find.text('4'), findsNothing);
await tester.drag(find.byType(Scrollable), const Offset(0.0, 200.0));
await tester.pump();
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsOneWidget);
expect(find.text('2'), findsNothing);
expect(find.text('3'), findsNothing);
await tester.drag(find.byType(Scrollable), const Offset(0.0, 700.0));
await tester.pump();
final RenderBox box2 = tester.renderObject<RenderBox>(find.byType(Container).first);
expect(box2.size.height, equals(600.0));
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);
expect(find.text('2'), findsNothing);
expect(find.text('3'), findsNothing);
});
}
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