Refactor SliverFillRemaining (#47379)

This commit is contained in:
Kate Lovett
2020-01-07 12:08:02 -08:00
committed by Flutter GitHub Bot
parent 5005824763
commit 76b21d2867
7 changed files with 1388 additions and 867 deletions

View File

@@ -59,69 +59,90 @@ class RenderSliverFillViewport extends RenderSliverFixedExtentBoxAdaptor {
}
}
/// A sliver that contains a single box child that fills the remaining space in
/// the viewport.
/// A sliver that contains a single box child that contains a scrollable and
/// fills 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.
/// [RenderSliverFillRemainingWithScrollable] 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:
///
/// * [NestedScrollView], which uses this sliver for the inner scrollable.
/// * [RenderSliverFillRemaining], which lays out its
/// non-scrollable child slightly different than this widget.
/// * [RenderSliverFillRemainingAndOverscroll], which incorporates the
/// overscroll into the remaining space to fill.
/// * [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 RenderSliverFillRemainingWithScrollable extends RenderSliverSingleBoxAdapter {
/// Creates a [RenderSliver] that wraps a scrollable [RenderBox] which is
/// sized to fit the remaining space in the viewport.
RenderSliverFillRemainingWithScrollable({ RenderBox child }) : super(child: child);
@override
void performLayout() {
// TODO(Piinks): This may fill too much space for NestedScrollView, https://github.com/flutter/flutter/issues/46028
final double extent = constraints.remainingPaintExtent - math.min(constraints.overlap, 0.0);
if (child != null)
child.layout(constraints.asBoxConstraints(
minExtent: extent,
maxExtent: extent,
));
final double paintedChildSize = calculatePaintOffset(constraints, from: 0.0, to: extent);
assert(paintedChildSize.isFinite);
assert(paintedChildSize >= 0.0);
geometry = SliverGeometry(
scrollExtent: constraints.viewportMainAxisExtent,
paintExtent: paintedChildSize,
maxPaintExtent: paintedChildSize,
hasVisualOverflow: extent > constraints.remainingPaintExtent || constraints.scrollOffset > 0.0,
);
if (child != null)
setChildParentData(child, constraints, geometry);
}
}
/// A sliver that contains a single box child that is non-scrollable and 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:
///
/// * [RenderSliverFillRemainingWithScrollable], which lays out its scrollable
/// child slightly different than this widget.
/// * [RenderSliverFillRemainingAndOverscroll], which incorporates the
/// overscroll into the remaining space to fill.
/// * [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,
this.hasScrollBody = true,
this.fillOverscroll = false,
}) : assert(hasScrollBody != null),
super(child: child);
/// Indicates whether the child has a scrollable body, this value cannot be
/// null.
///
/// Defaults to true such that the child will extend beyond the viewport and
/// scroll, as seen in [NestedScrollView].
///
/// Setting this value to false will allow the child to fill the remainder of
/// the viewport and not extend further. However, if the
/// [precedingScrollExtent] exceeds the size of the viewport, the sliver will
/// defer to the child's size rather than overriding it.
bool hasScrollBody;
/// Indicates whether the child should stretch to fill the overscroll area
/// created by certain scroll physics, such as iOS' default scroll physics.
/// This value cannot be null. This flag is only relevant when the
/// [hasScrollBody] value is false.
///
/// Defaults to false, meaning the default behavior is for the child to
/// maintain its size and not extend into the overscroll area.
bool fillOverscroll;
/// Creates a [RenderSliver] that wraps a non-scrollable [RenderBox] which is
/// sized to fit the remaining space in the viewport.
RenderSliverFillRemaining({ RenderBox child }) : super(child: child);
@override
void performLayout() {
double childExtent;
// The remaining space in the viewportMainAxisExtent. Can be <= 0 if we have
// scrolled beyond the extent of the screen.
double extent = constraints.viewportMainAxisExtent - constraints.precedingScrollExtent;
double maxExtent = constraints.remainingPaintExtent - math.min(constraints.overlap, 0.0);
if (hasScrollBody) {
extent = maxExtent;
if (child != null)
child.layout(
constraints.asBoxConstraints(
minExtent: extent,
maxExtent: extent,
),
parentUsesSize: true,
);
} else if (child != null) {
if (child != null) {
double childExtent;
switch (constraints.axis) {
case Axis.horizontal:
childExtent = child.getMaxIntrinsicWidth(constraints.crossAxisExtent);
@@ -131,34 +152,27 @@ class RenderSliverFillRemaining extends RenderSliverSingleBoxAdapter {
break;
}
if (constraints.precedingScrollExtent > constraints.viewportMainAxisExtent || childExtent > extent)
extent = childExtent;
if (maxExtent < extent)
maxExtent = extent;
if ((fillOverscroll ? maxExtent : extent) > childExtent) {
child.layout(
constraints.asBoxConstraints(
minExtent: extent,
maxExtent: fillOverscroll ? maxExtent : extent,
),
parentUsesSize: true,
);
} else {
child.layout(constraints.asBoxConstraints(), parentUsesSize: true);
}
// If the childExtent is greater than the computed extent, we want to use
// that instead of potentially cutting off the child. This allows us to
// safely specify a maxExtent.
extent = math.max(extent, childExtent);
child.layout(constraints.asBoxConstraints(
minExtent: extent,
maxExtent: extent,
));
}
assert(extent.isFinite,
'The calculated extent for the child of SliverFillRemaining is not finite.'
'This can happen if the child is a scrollable, in which case, the'
'hasScrollBody property of SliverFillRemaining should not be set to'
'false.',
'This can happen if the child is a scrollable, in which case, the'
'hasScrollBody property of SliverFillRemaining should not be set to'
'false.',
);
final double paintedChildSize = calculatePaintOffset(constraints, from: 0.0, to: extent);
assert(paintedChildSize.isFinite);
assert(paintedChildSize >= 0.0);
geometry = SliverGeometry(
scrollExtent: hasScrollBody ? constraints.viewportMainAxisExtent : extent,
scrollExtent: extent,
paintExtent: paintedChildSize,
maxPaintExtent: paintedChildSize,
hasVisualOverflow: extent > constraints.remainingPaintExtent || constraints.scrollOffset > 0.0,
@@ -167,3 +181,79 @@ class RenderSliverFillRemaining extends RenderSliverSingleBoxAdapter {
setChildParentData(child, constraints, geometry);
}
}
/// A sliver that contains a single box child that is non-scrollable and fills
/// the remaining space in the viewport including any overscrolled area.
///
/// [RenderSliverFillRemainingAndOverscroll] sizes its child to fill the
/// viewport in the cross axis and to fill the remaining space in the viewport
/// in the main axis with the overscroll area included.
///
/// 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:
///
/// * [RenderSliverFillRemainingWithScrollable], which lays out its scrollable
/// child without overscroll.
/// * [RenderSliverFillRemaining], which lays out its
/// non-scrollable child without overscroll.
/// * [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 RenderSliverFillRemainingAndOverscroll extends RenderSliverSingleBoxAdapter {
/// Creates a [RenderSliver] that wraps a non-scrollable [RenderBox] which is
/// sized to fit the remaining space plus any overscroll in the viewport.
RenderSliverFillRemainingAndOverscroll({ RenderBox child }) : super(child: child);
@override
void performLayout() {
// The remaining space in the viewportMainAxisExtent. Can be <= 0 if we have
// scrolled beyond the extent of the screen.
double extent = constraints.viewportMainAxisExtent - constraints.precedingScrollExtent;
// The maxExtent includes any overscrolled area. Can be < 0 if we have
// overscroll in the opposite direction, away from the end of the list.
double maxExtent = constraints.remainingPaintExtent - math.min(constraints.overlap, 0.0);
if (child != null) {
double childExtent;
switch (constraints.axis) {
case Axis.horizontal:
childExtent = child.getMaxIntrinsicWidth(constraints.crossAxisExtent);
break;
case Axis.vertical:
childExtent = child.getMaxIntrinsicHeight(constraints.crossAxisExtent);
break;
}
// If the childExtent is greater than the computed extent, we want to use
// that instead of potentially cutting off the child. This allows us to
// safely specify a maxExtent.
extent = math.max(extent, childExtent);
// The extent could be larger than the maxExtent due to a larger child
// size or overscrolling at the top of the scrollable (rather than at the
// end where this sliver is).
maxExtent = math.max(extent, maxExtent);
child.layout(constraints.asBoxConstraints(minExtent: extent, maxExtent: maxExtent));
}
assert(extent.isFinite,
'The calculated extent for the child of SliverFillRemaining is not finite.'
'This can happen if the child is a scrollable, in which case, the'
'hasScrollBody property of SliverFillRemaining should not be set to'
'false.',
);
final double paintedChildSize = calculatePaintOffset(constraints, from: 0.0, to: extent);
assert(paintedChildSize.isFinite);
assert(paintedChildSize >= 0.0);
geometry = SliverGeometry(
scrollExtent: extent,
paintExtent: math.min(maxExtent, constraints.remainingPaintExtent),
maxPaintExtent: maxExtent,
hasVisualOverflow: extent > constraints.remainingPaintExtent || constraints.scrollOffset > 0.0,
);
if (child != null)
setChildParentData(child, constraints, geometry);
}
}

View File

@@ -22,7 +22,7 @@ import 'scroll_metrics.dart';
import 'scroll_physics.dart';
import 'scroll_position.dart';
import 'scroll_view.dart';
import 'sliver.dart';
import 'sliver_fill.dart';
import 'ticker_provider.dart';
import 'viewport.dart';

View File

@@ -25,6 +25,7 @@ import 'scroll_position_with_single_context.dart';
import 'scroll_view.dart';
import 'scrollable.dart';
import 'sliver.dart';
import 'sliver_fill.dart';
import 'viewport.dart';
/// A controller for [PageView].

View File

@@ -1025,148 +1025,6 @@ class SliverGrid extends SliverMultiBoxAdaptorWidget {
}
}
/// A sliver that contains multiple box children that each fills the viewport.
///
/// [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
/// [SliverFixedExtentList.itemExtent].
/// * [SliverPrototypeExtentList], which is similar to [SliverFixedExtentList]
/// except that it uses a prototype list item instead of a pixel value to define
/// the main axis extent of each item.
/// * [SliverList], which does not require its children to have the same
/// extent in the main axis.
class SliverFillViewport extends StatelessWidget {
/// Creates a sliver whose box children that each fill the viewport.
const SliverFillViewport({
Key key,
@required this.delegate,
this.viewportFraction = 1.0,
}) : assert(viewportFraction != null),
assert(viewportFraction > 0.0),
super(key: key);
/// 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.
final double viewportFraction;
/// {@macro flutter.widgets.sliverMultiBoxAdaptor.delegate}
final SliverChildDelegate delegate;
@override
Widget build(BuildContext context) {
return _SliverFractionalPadding(
viewportFraction: (1 - viewportFraction).clamp(0, 1) / 2,
sliver: _SliverFillViewportRenderObjectWidget(
viewportFraction: viewportFraction,
delegate: delegate,
),
);
}
}
class _SliverFillViewportRenderObjectWidget extends SliverMultiBoxAdaptorWidget {
const _SliverFillViewportRenderObjectWidget({
Key key,
@required SliverChildDelegate delegate,
this.viewportFraction = 1.0,
}) : assert(viewportFraction != null),
assert(viewportFraction > 0.0),
super(key: key, delegate: delegate);
final double viewportFraction;
@override
RenderSliverFillViewport createRenderObject(BuildContext context) {
final SliverMultiBoxAdaptorElement element = context as SliverMultiBoxAdaptorElement;
return RenderSliverFillViewport(childManager: element, viewportFraction: viewportFraction);
}
@override
void updateRenderObject(BuildContext context, RenderSliverFillViewport renderObject) {
renderObject.viewportFraction = viewportFraction;
}
}
class _SliverFractionalPadding extends SingleChildRenderObjectWidget {
const _SliverFractionalPadding({
Key key,
this.viewportFraction = 0,
Widget sliver,
}) : assert(viewportFraction != null),
assert(viewportFraction >= 0),
assert(viewportFraction <= 0.5),
super(key: key, child: sliver);
final double viewportFraction;
@override
RenderObject createRenderObject(BuildContext context) => _RenderSliverFractionalPadding(viewportFraction: viewportFraction);
@override
void updateRenderObject(BuildContext context, _RenderSliverFractionalPadding renderObject) {
renderObject.viewportFraction = viewportFraction;
}
}
class _RenderSliverFractionalPadding extends RenderSliverEdgeInsetsPadding {
_RenderSliverFractionalPadding({
double viewportFraction = 0,
}) : assert(viewportFraction != null),
assert(viewportFraction <= 0.5),
assert(viewportFraction >= 0),
_viewportFraction = viewportFraction;
double get viewportFraction => _viewportFraction;
double _viewportFraction;
set viewportFraction(double newValue) {
assert(newValue != null);
if (_viewportFraction == newValue)
return;
_viewportFraction = newValue;
_markNeedsResolution();
}
@override
EdgeInsets get resolvedPadding => _resolvedPadding;
EdgeInsets _resolvedPadding;
void _markNeedsResolution() {
_resolvedPadding = null;
markNeedsLayout();
}
void _resolve() {
if (_resolvedPadding != null)
return;
assert(constraints.axis != null);
final double paddingValue = constraints.viewportMainAxisExtent * viewportFraction;
switch (constraints.axis) {
case Axis.horizontal:
_resolvedPadding = EdgeInsets.symmetric(horizontal: paddingValue);
break;
case Axis.vertical:
_resolvedPadding = EdgeInsets.symmetric(vertical: paddingValue);
break;
}
return;
}
@override
void performLayout() {
_resolve();
super.performLayout();
}
}
/// An element that lazily builds children for a [SliverMultiBoxAdaptorWidget].
///
/// Implements [RenderSliverBoxChildManager], which lets this element manage
@@ -1456,275 +1314,6 @@ class SliverMultiBoxAdaptorElement extends RenderObjectElement implements Render
}
}
/// A sliver that contains a single box child that fills the remaining space in
/// the viewport.
///
/// [SliverFillRemaining] will size its [child] to fill the viewport in the
/// cross axis. The extent of the sliver and its child's size in the main axis
/// is computed conditionally, described in further detail below.
///
/// Typically this will be the last sliver in a viewport, since (by definition)
/// there is never any room for anything beyond this sliver.
///
/// ## Main Axis Extent
///
/// ### When [SliverFillRemaining] has a scrollable child
///
/// The [hasScrollBody] flag indicates whether the sliver's child has a
/// scrollable body. This value is never null, and defaults to true. A common
/// example of this use is a [NestedScrollView]. In this case, the sliver will
/// size its child to fill the maximum available extent.
///
/// ### When [SliverFillRemaining] does not have a scrollable child
///
/// When [hasScrollBody] is set to false, the child's size is taken into account
/// when considering the extent to which it should fill the space. The
/// [precedingScrollExtent] of the [SliverConstraints] is also taken into
/// account in deciding how to layout the sliver.
///
/// * [SliverFillRemaining] will size its [child] to fill the viewport in the
/// main axis if that space is larger than the child's extent, and the
/// [precedingScrollExtent] has not exceeded the main axis extent of the
/// viewport.
///
/// {@animation 250 500 https://flutter.github.io/assets-for-api-docs/assets/widgets/sliver_fill_remaining_sizes_child.mp4}
///
/// {@tool snippet --template=stateless_widget_scaffold}
///
/// In this sample the [SliverFillRemaining] sizes its [child] to fill the
/// remaining extent of the viewport in both axes. The icon is centered in the
/// sliver, and would be in any computed extent for the sliver.
///
/// ```dart
/// Widget build(BuildContext context) {
/// return CustomScrollView(
/// slivers: <Widget>[
/// SliverToBoxAdapter(
/// child: Container(
/// color: Colors.amber[300],
/// height: 150.0,
/// ),
/// ),
/// SliverFillRemaining(
/// hasScrollBody: false,
/// child: Container(
/// color: Colors.blue[100],
/// child: Icon(
/// Icons.sentiment_very_satisfied,
/// size: 75,
/// color: Colors.blue[900],
/// ),
/// ),
/// ),
/// ],
/// );
/// }
/// ```
/// {@end-tool}
///
/// * [SliverFillRemaining] will defer to the size of its [child] if the
/// child's size exceeds the remaining space in the viewport.
///
/// {@animation 250 500 https://flutter.github.io/assets-for-api-docs/assets/widgets/sliver_fill_remaining_defers_to_child.mp4}
///
/// {@tool snippet --template=stateless_widget_scaffold}
///
/// In this sample the [SliverFillRemaining] defers to the size of its [child]
/// because the child's extent exceeds that of the remaining extent of the
/// viewport's main axis.
///
/// ```dart
/// Widget build(BuildContext context) {
/// return CustomScrollView(
/// slivers: <Widget>[
/// SliverFixedExtentList(
/// itemExtent: 100.0,
/// delegate: SliverChildBuilderDelegate(
/// (BuildContext context, int index) {
/// return Container(
/// color: index % 2 == 0
/// ? Colors.amber[200]
/// : Colors.blue[200],
/// );
/// },
/// childCount: 3,
/// ),
/// ),
/// SliverFillRemaining(
/// hasScrollBody: false,
/// child: Container(
/// color: Colors.orange[300],
/// child: Padding(
/// padding: const EdgeInsets.all(50.0),
/// child: FlutterLogo(size: 100),
/// ),
/// ),
/// ),
/// ],
/// );
/// }
/// ```
/// {@end-tool}
///
/// * [SliverFillRemaining] will defer to the size of its [child] if the
/// [precedingScrollExtent] exceeded the length of the viewport's main axis.
///
/// {@animation 250 500 https://flutter.github.io/assets-for-api-docs/assets/widgets/sliver_fill_remaining_scrolled_beyond.mp4}
///
/// {@tool snippet --template=stateless_widget_scaffold}
///
/// In this sample the [SliverFillRemaining] defers to the size of its [child]
/// because the [precedingScrollExtent] of the [SliverConstraints] has gone
/// beyond that of the viewport's main axis.
///
/// ```dart
/// Widget build(BuildContext context) {
/// return CustomScrollView(
/// slivers: <Widget>[
/// SliverFixedExtentList(
/// itemExtent: 130.0,
/// delegate: SliverChildBuilderDelegate(
/// (BuildContext context, int index) {
/// return Container(
/// color: index % 2 == 0
/// ? Colors.indigo[200]
/// : Colors.orange[200],
/// );
/// },
/// childCount: 5,
/// ),
/// ),
/// SliverFillRemaining(
/// hasScrollBody: false,
/// child: Container(
/// child: Padding(
/// padding: const EdgeInsets.all(50.0),
/// child: Icon(
/// Icons.pan_tool,
/// size: 60,
/// color: Colors.blueGrey,
/// ),
/// ),
/// ),
/// ),
/// ],
/// );
/// }
/// ```
/// {@end-tool}
///
/// * For [ScrollPhysics] that allow overscroll, such as
/// [BouncingScrollPhysics], setting the [fillOverscroll] flag to true allows
/// the size of the [child] to _stretch_, filling the overscroll area. It does
/// this regardless of the path chosen to provide the child's size.
///
/// {@animation 250 500 https://flutter.github.io/assets-for-api-docs/assets/widgets/sliver_fill_remaining_fill_overscroll.mp4}
///
/// {@tool snippet --template=stateless_widget_scaffold}
///
/// In this sample the [SliverFillRemaining]'s child stretches to fill the
/// overscroll area when [fillOverscroll] is true. This sample also features a
/// button that is pinned to the bottom of the sliver, regardless of size or
/// overscroll behavior. Try switching [fillOverscroll] to see the difference.
///
/// ```dart
/// Widget build(BuildContext context) {
/// return CustomScrollView(
/// // The ScrollPhysics are overridden here to illustrate the functionality
/// // of fillOverscroll on all devices this sample may be run on.
/// // fillOverscroll only changes the behavior of your layout when applied
/// // to Scrollables that allow for overscroll. BouncingScrollPhysics are
/// // one example, which are provided by default on the iOS platform.
/// physics: BouncingScrollPhysics(),
/// slivers: <Widget>[
/// SliverToBoxAdapter(
/// child: Container(
/// color: Colors.tealAccent[700],
/// height: 150.0,
/// ),
/// ),
/// SliverFillRemaining(
/// hasScrollBody: false,
/// // Switch for different overscroll behavior in your layout.
/// // If your ScrollPhysics do not allow for overscroll, setting
/// // fillOverscroll to true will have no effect.
/// fillOverscroll: true,
/// child: Container(
/// color: Colors.teal[100],
/// child: Align(
/// alignment: Alignment.bottomCenter,
/// child: Padding(
/// padding: const EdgeInsets.all(16.0),
/// child: RaisedButton(
/// onPressed: () {
/// /* Place your onPressed code here! */
/// },
/// child: Text('Bottom Pinned Button!'),
/// ),
/// ),
/// ),
/// ),
/// ),
/// ],
/// );
/// }
/// ```
/// {@end-tool}
///
///
/// 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 {
/// Creates a sliver that fills the remaining space in the viewport.
const SliverFillRemaining({
Key key,
Widget child,
this.hasScrollBody = true,
this.fillOverscroll = false,
}) : assert(hasScrollBody != null),
super(key: key, child: child);
/// Indicates whether the child has a scrollable body, this value cannot be
/// null.
///
/// Defaults to true such that the child will extend beyond the viewport and
/// scroll, as seen in [NestedScrollView].
///
/// Setting this value to false will allow the child to fill the remainder of
/// the viewport and not extend further. However, if the
/// [precedingScrollExtent] of the [SliverContraints] and/or the [child]'s
/// extent exceeds the size of the viewport, the sliver will defer to the
/// child's size rather than overriding it.
final bool hasScrollBody;
/// Indicates whether the child should stretch to fill the overscroll area
/// created by certain scroll physics, such as iOS' default scroll physics.
/// This value cannot be null. This flag is only relevant when the
/// [hasScrollBody] value is false.
///
/// Defaults to false, meaning the default behavior is for the child to
/// maintain its size and not extend into the overscroll area.
final bool fillOverscroll;
@override
RenderSliverFillRemaining createRenderObject(BuildContext context) {
return RenderSliverFillRemaining(
hasScrollBody: hasScrollBody,
fillOverscroll: fillOverscroll,
);
}
@override
void updateRenderObject(BuildContext context, RenderSliverFillRemaining renderObject) {
renderObject.hasScrollBody = hasScrollBody;
renderObject.fillOverscroll = fillOverscroll;
}
}
/// A sliver widget that makes its sliver child partially transparent.
///
/// This class paints its sliver child into an intermediate buffer and then

View File

@@ -0,0 +1,467 @@
// Copyright 2014 The Flutter 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:flutter/rendering.dart';
import 'basic.dart';
import 'framework.dart';
import 'sliver.dart';
/// A sliver that contains multiple box children that each fills the viewport.
///
/// [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
/// [SliverFixedExtentList.itemExtent].
/// * [SliverPrototypeExtentList], which is similar to [SliverFixedExtentList]
/// except that it uses a prototype list item instead of a pixel value to define
/// the main axis extent of each item.
/// * [SliverList], which does not require its children to have the same
/// extent in the main axis.
class SliverFillViewport extends StatelessWidget {
/// Creates a sliver whose box children that each fill the viewport.
const SliverFillViewport({
Key key,
@required this.delegate,
this.viewportFraction = 1.0,
}) : assert(viewportFraction != null),
assert(viewportFraction > 0.0),
super(key: key);
/// 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.
final double viewportFraction;
/// {@macro flutter.widgets.sliverMultiBoxAdaptor.delegate}
final SliverChildDelegate delegate;
@override
Widget build(BuildContext context) {
return _SliverFractionalPadding(
viewportFraction: (1 - viewportFraction).clamp(0, 1) / 2,
sliver: _SliverFillViewportRenderObjectWidget(
viewportFraction: viewportFraction,
delegate: delegate,
),
);
}
}
class _SliverFillViewportRenderObjectWidget extends SliverMultiBoxAdaptorWidget {
const _SliverFillViewportRenderObjectWidget({
Key key,
@required SliverChildDelegate delegate,
this.viewportFraction = 1.0,
}) : assert(viewportFraction != null),
assert(viewportFraction > 0.0),
super(key: key, delegate: delegate);
final double viewportFraction;
@override
RenderSliverFillViewport createRenderObject(BuildContext context) {
final SliverMultiBoxAdaptorElement element = context as SliverMultiBoxAdaptorElement;
return RenderSliverFillViewport(childManager: element, viewportFraction: viewportFraction);
}
@override
void updateRenderObject(BuildContext context, RenderSliverFillViewport renderObject) {
renderObject.viewportFraction = viewportFraction;
}
}
class _SliverFractionalPadding extends SingleChildRenderObjectWidget {
const _SliverFractionalPadding({
this.viewportFraction = 0,
Widget sliver,
}) : assert(viewportFraction != null),
assert(viewportFraction >= 0),
assert(viewportFraction <= 0.5),
super(child: sliver);
final double viewportFraction;
@override
RenderObject createRenderObject(BuildContext context) => _RenderSliverFractionalPadding(viewportFraction: viewportFraction);
@override
void updateRenderObject(BuildContext context, _RenderSliverFractionalPadding renderObject) {
renderObject.viewportFraction = viewportFraction;
}
}
class _RenderSliverFractionalPadding extends RenderSliverEdgeInsetsPadding {
_RenderSliverFractionalPadding({
double viewportFraction = 0,
}) : assert(viewportFraction != null),
assert(viewportFraction <= 0.5),
assert(viewportFraction >= 0),
_viewportFraction = viewportFraction;
double get viewportFraction => _viewportFraction;
double _viewportFraction;
set viewportFraction(double newValue) {
assert(newValue != null);
if (_viewportFraction == newValue)
return;
_viewportFraction = newValue;
_markNeedsResolution();
}
@override
EdgeInsets get resolvedPadding => _resolvedPadding;
EdgeInsets _resolvedPadding;
void _markNeedsResolution() {
_resolvedPadding = null;
markNeedsLayout();
}
void _resolve() {
if (_resolvedPadding != null)
return;
assert(constraints.axis != null);
final double paddingValue = constraints.viewportMainAxisExtent * viewportFraction;
switch (constraints.axis) {
case Axis.horizontal:
_resolvedPadding = EdgeInsets.symmetric(horizontal: paddingValue);
break;
case Axis.vertical:
_resolvedPadding = EdgeInsets.symmetric(vertical: paddingValue);
break;
}
return;
}
@override
void performLayout() {
_resolve();
super.performLayout();
}
}
/// A sliver that contains a single box child that fills the remaining space in
/// the viewport.
///
/// [SliverFillRemaining] will size its [child] to fill the viewport in the
/// cross axis. The extent of the sliver and its child's size in the main axis
/// is computed conditionally, described in further detail below.
///
/// Typically this will be the last sliver in a viewport, since (by definition)
/// there is never any room for anything beyond this sliver.
///
/// ## Main Axis Extent
///
/// ### When [SliverFillRemaining] has a scrollable child
///
/// The [hasScrollBody] flag indicates whether the sliver's child has a
/// scrollable body. This value is never null, and defaults to true. A common
/// example of this use is a [NestedScrollView]. In this case, the sliver will
/// size its child to fill the maximum available extent.
///
/// ### When [SliverFillRemaining] does not have a scrollable child
///
/// When [hasScrollBody] is set to false, the child's size is taken into account
/// when considering the extent to which it should fill the space. The extent to
/// which the preceding slivers have been scrolled is also taken into
/// account in deciding how to layout this sliver.
///
/// [SliverFillRemaining] will size its [child] to fill the viewport in the
/// main axis if that space is larger than the child's extent, and the
/// the amount of space that has been scrolled beforehand has not exceeded the
/// main axis extent of the viewport.
///
/// {@animation 250 500 https://flutter.github.io/assets-for-api-docs/assets/widgets/sliver_fill_remaining_sizes_child.mp4}
///
/// {@tool snippet --template=stateless_widget_scaffold}
///
/// In this sample the [SliverFillRemaining] sizes its [child] to fill the
/// remaining extent of the viewport in both axes. The icon is centered in the
/// sliver, and would be in any computed extent for the sliver.
///
/// ```dart
/// Widget build(BuildContext context) {
/// return CustomScrollView(
/// slivers: <Widget>[
/// SliverToBoxAdapter(
/// child: Container(
/// color: Colors.amber[300],
/// height: 150.0,
/// ),
/// ),
/// SliverFillRemaining(
/// hasScrollBody: false,
/// child: Container(
/// color: Colors.blue[100],
/// child: Icon(
/// Icons.sentiment_very_satisfied,
/// size: 75,
/// color: Colors.blue[900],
/// ),
/// ),
/// ),
/// ],
/// );
/// }
/// ```
/// {@end-tool}
///
/// [SliverFillRemaining] will defer to the size of its [child] if the
/// child's size exceeds the remaining space in the viewport.
///
/// {@animation 250 500 https://flutter.github.io/assets-for-api-docs/assets/widgets/sliver_fill_remaining_defers_to_child.mp4}
///
/// {@tool snippet --template=stateless_widget_scaffold}
///
/// In this sample the [SliverFillRemaining] defers to the size of its [child]
/// because the child's extent exceeds that of the remaining extent of the
/// viewport's main axis.
///
/// ```dart
/// Widget build(BuildContext context) {
/// return CustomScrollView(
/// slivers: <Widget>[
/// SliverFixedExtentList(
/// itemExtent: 100.0,
/// delegate: SliverChildBuilderDelegate(
/// (BuildContext context, int index) {
/// return Container(
/// color: index % 2 == 0
/// ? Colors.amber[200]
/// : Colors.blue[200],
/// );
/// },
/// childCount: 3,
/// ),
/// ),
/// SliverFillRemaining(
/// hasScrollBody: false,
/// child: Container(
/// color: Colors.orange[300],
/// child: Padding(
/// padding: const EdgeInsets.all(50.0),
/// child: FlutterLogo(size: 100),
/// ),
/// ),
/// ),
/// ],
/// );
/// }
/// ```
/// {@end-tool}
///
/// [SliverFillRemaining] will defer to the size of its [child] if the
/// [precedingScrollExtent] exceeded the length of the viewport's main axis.
///
/// {@animation 250 500 https://flutter.github.io/assets-for-api-docs/assets/widgets/sliver_fill_remaining_scrolled_beyond.mp4}
///
/// {@tool snippet --template=stateless_widget_scaffold}
///
/// In this sample the [SliverFillRemaining] defers to the size of its [child]
/// because the [precedingScrollExtent] of the [SliverConstraints] has gone
/// beyond that of the viewport's main axis.
///
/// ```dart
/// Widget build(BuildContext context) {
/// return CustomScrollView(
/// slivers: <Widget>[
/// SliverFixedExtentList(
/// itemExtent: 130.0,
/// delegate: SliverChildBuilderDelegate(
/// (BuildContext context, int index) {
/// return Container(
/// color: index % 2 == 0
/// ? Colors.indigo[200]
/// : Colors.orange[200],
/// );
/// },
/// childCount: 5,
/// ),
/// ),
/// SliverFillRemaining(
/// hasScrollBody: false,
/// child: Container(
/// child: Padding(
/// padding: const EdgeInsets.all(50.0),
/// child: Icon(
/// Icons.pan_tool,
/// size: 60,
/// color: Colors.blueGrey,
/// ),
/// ),
/// ),
/// ),
/// ],
/// );
/// }
/// ```
/// {@end-tool}
///
/// For [ScrollPhysics] that allow overscroll, such as
/// [BouncingScrollPhysics], setting the [fillOverscroll] flag to true allows
/// the size of the [child] to _stretch_, filling the overscroll area. It does
/// this regardless of the path chosen to provide the child's size.
///
/// {@animation 250 500 https://flutter.github.io/assets-for-api-docs/assets/widgets/sliver_fill_remaining_fill_overscroll.mp4}
///
/// {@tool snippet --template=stateless_widget_scaffold}
///
/// In this sample the [SliverFillRemaining]'s child stretches to fill the
/// overscroll area when [fillOverscroll] is true. This sample also features a
/// button that is pinned to the bottom of the sliver, regardless of size or
/// overscroll behavior. Try switching [fillOverscroll] to see the difference.
///
/// ```dart
/// Widget build(BuildContext context) {
/// return CustomScrollView(
/// // The ScrollPhysics are overridden here to illustrate the functionality
/// // of fillOverscroll on all devices this sample may be run on.
/// // fillOverscroll only changes the behavior of your layout when applied
/// // to Scrollables that allow for overscroll. BouncingScrollPhysics are
/// // one example, which are provided by default on the iOS platform.
/// physics: BouncingScrollPhysics(),
/// slivers: <Widget>[
/// SliverToBoxAdapter(
/// child: Container(
/// color: Colors.tealAccent[700],
/// height: 150.0,
/// ),
/// ),
/// SliverFillRemaining(
/// hasScrollBody: false,
/// // Switch for different overscroll behavior in your layout.
/// // If your ScrollPhysics do not allow for overscroll, setting
/// // fillOverscroll to true will have no effect.
/// fillOverscroll: true,
/// child: Container(
/// color: Colors.teal[100],
/// child: Align(
/// alignment: Alignment.bottomCenter,
/// child: Padding(
/// padding: const EdgeInsets.all(16.0),
/// child: RaisedButton(
/// onPressed: () {
/// /* Place your onPressed code here! */
/// },
/// child: Text('Bottom Pinned Button!'),
/// ),
/// ),
/// ),
/// ),
/// ),
/// ],
/// );
/// }
/// ```
/// {@end-tool}
///
///
/// 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 StatelessWidget {
/// Creates a sliver that fills the remaining space in the viewport.
const SliverFillRemaining({
Key key,
this.child,
this.hasScrollBody = true,
this.fillOverscroll = false,
}) : assert(hasScrollBody != null),
assert(fillOverscroll != null),
super(key: key);
/// Doc
final Widget child;
/// Indicates whether the child has a scrollable body, this value cannot be
/// null.
///
/// Defaults to true such that the child will extend beyond the viewport and
/// scroll, as seen in [NestedScrollView].
///
/// Setting this value to false will allow the child to fill the remainder of
/// the viewport and not extend further. However, if the
/// [precedingScrollExtent] of the [SliverConstraints] and/or the [child]'s
/// extent exceeds the size of the viewport, the sliver will defer to the
/// child's size rather than overriding it.
final bool hasScrollBody;
/// Indicates whether the child should stretch to fill the overscroll area
/// created by certain scroll physics, such as iOS' default scroll physics.
/// This value cannot be null. This flag is only relevant when the
/// [hasScrollBody] value is false.
///
/// Defaults to false, meaning the default behavior is for the child to
/// maintain its size and not extend into the overscroll area.
final bool fillOverscroll;
@override
Widget build(BuildContext context) {
if (hasScrollBody)
return _SliverFillRemainingWithScrollable(child: child);
if (!fillOverscroll)
return _SliverFillRemainingWithoutScrollable(child: child);
return _SliverFillRemainingAndOverscroll(child: child);
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(
DiagnosticsProperty<Widget>(
'child',
child,
)
);
final List<String> flags = <String>[
if (hasScrollBody) 'scrollable',
if (fillOverscroll) 'fillOverscroll',
];
if (flags.isEmpty)
flags.add('nonscrollable');
properties.add(IterableProperty<String>('mode', flags));
}
}
class _SliverFillRemainingWithScrollable extends SingleChildRenderObjectWidget {
const _SliverFillRemainingWithScrollable({
Key key,
Widget child,
}) : super(key: key, child: child);
@override
RenderSliverFillRemainingWithScrollable createRenderObject(BuildContext context) => RenderSliverFillRemainingWithScrollable();
}
class _SliverFillRemainingWithoutScrollable extends SingleChildRenderObjectWidget {
const _SliverFillRemainingWithoutScrollable({
Key key,
Widget child,
}) : super(key: key, child: child);
@override
RenderSliverFillRemaining createRenderObject(BuildContext context) => RenderSliverFillRemaining();
}
class _SliverFillRemainingAndOverscroll extends SingleChildRenderObjectWidget {
const _SliverFillRemainingAndOverscroll({
Key key,
Widget child,
}) : super(key: key, child: child);
@override
RenderSliverFillRemainingAndOverscroll createRenderObject(BuildContext context) => RenderSliverFillRemainingAndOverscroll();
}

View File

@@ -95,6 +95,7 @@ export 'src/widgets/shortcuts.dart';
export 'src/widgets/single_child_scroll_view.dart';
export 'src/widgets/size_changed_layout_notifier.dart';
export 'src/widgets/sliver.dart';
export 'src/widgets/sliver_fill.dart';
export 'src/widgets/sliver_layout_builder.dart';
export 'src/widgets/sliver_persistent_header.dart';
export 'src/widgets/sliver_prototype_extent_list.dart';

File diff suppressed because it is too large Load Diff