Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
76b21d28
Commit
76b21d28
authored
Jan 07, 2020
by
Kate Lovett
Committed by
Flutter GitHub Bot
Jan 07, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Refactor SliverFillRemaining (#47379)
parent
50058247
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
1404 additions
and
883 deletions
+1404
-883
sliver_fill.dart
packages/flutter/lib/src/rendering/sliver_fill.dart
+155
-65
nested_scroll_view.dart
packages/flutter/lib/src/widgets/nested_scroll_view.dart
+1
-1
page_view.dart
packages/flutter/lib/src/widgets/page_view.dart
+1
-0
sliver.dart
packages/flutter/lib/src/widgets/sliver.dart
+0
-411
sliver_fill.dart
packages/flutter/lib/src/widgets/sliver_fill.dart
+467
-0
widgets.dart
packages/flutter/lib/widgets.dart
+1
-0
sliver_fill_remaining_test.dart
...ages/flutter/test/widgets/sliver_fill_remaining_test.dart
+779
-406
No files found.
packages/flutter/lib/src/rendering/sliver_fill.dart
View file @
76b21d28
...
...
@@ -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
);
}
}
packages/flutter/lib/src/widgets/nested_scroll_view.dart
View file @
76b21d28
...
...
@@ -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'
;
...
...
packages/flutter/lib/src/widgets/page_view.dart
View file @
76b21d28
...
...
@@ -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].
...
...
packages/flutter/lib/src/widgets/sliver.dart
View file @
76b21d28
...
...
@@ -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
...
...
packages/flutter/lib/src/widgets/sliver_fill.dart
0 → 100644
View file @
76b21d28
// 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
();
}
packages/flutter/lib/widgets.dart
View file @
76b21d28
...
...
@@ -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'
;
...
...
packages/flutter/test/widgets/sliver_fill_remaining_test.dart
View file @
76b21d28
...
...
@@ -8,442 +8,815 @@ import 'package:flutter/material.dart';
import
'package:flutter/widgets.dart'
;
void
main
(
)
{
testWidgets
(
'SliverFillRemaining - no siblings'
,
(
WidgetTester
tester
)
async
{
final
ScrollController
controller
=
ScrollController
();
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
CustomScrollView
(
controller:
controller
,
slivers:
<
Widget
>[
SliverFillRemaining
(
child:
Container
()),
],
),
),
);
expect
(
tester
.
renderObject
<
RenderBox
>(
find
.
byType
(
Container
)).
size
.
height
,
equals
(
600.0
));
controller
.
jumpTo
(
50.0
);
await
tester
.
pump
();
expect
(
tester
.
renderObject
<
RenderBox
>(
find
.
byType
(
Container
)).
size
.
height
,
equals
(
600.0
));
controller
.
jumpTo
(-
100.0
);
await
tester
.
pump
();
expect
(
tester
.
renderObject
<
RenderBox
>(
find
.
byType
(
Container
)).
size
.
height
,
equals
(
600.0
));
controller
.
jumpTo
(
0.0
);
await
tester
.
pump
();
expect
(
tester
.
renderObject
<
RenderBox
>(
find
.
byType
(
Container
)).
size
.
height
,
equals
(
600.0
));
});
testWidgets
(
'SliverFillRemaining - one sibling'
,
(
WidgetTester
tester
)
async
{
final
ScrollController
controller
=
ScrollController
();
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
CustomScrollView
(
// Helpers
final
Widget
sliverBox
=
SliverToBoxAdapter
(
child:
Container
(
color:
Colors
.
amber
,
height:
150.0
,
width:
150
,
),
);
Widget
boilerplate
(
List
<
Widget
>
slivers
,
{
ScrollController
controller
,
Axis
scrollDirection
=
Axis
.
vertical
,
})
{
return
MaterialApp
(
home:
Scaffold
(
body:
CustomScrollView
(
scrollDirection:
scrollDirection
,
slivers:
slivers
,
controller:
controller
,
slivers:
<
Widget
>[
const
SliverToBoxAdapter
(
child:
SizedBox
(
height:
100.0
)),
SliverFillRemaining
(
child:
Container
()),
],
),
),
);
expect
(
tester
.
renderObject
<
RenderBox
>(
find
.
byType
(
Container
)).
size
.
height
,
equals
(
500.0
));
controller
.
jumpTo
(
50.0
);
await
tester
.
pump
();
expect
(
tester
.
renderObject
<
RenderBox
>(
find
.
byType
(
Container
)).
size
.
height
,
equals
(
550.0
));
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
));
});
group
(
'SliverFillRemaining - hasScrollBody'
,
()
{
final
Widget
sliverBox
=
SliverToBoxAdapter
(
child:
Container
(
color:
Colors
.
amber
,
height:
150.0
,
width:
150
,
),
);
Widget
boilerplate
(
List
<
Widget
>
slivers
,
{
ScrollController
controller
,
Axis
scrollDirection
=
Axis
.
vertical
,
})
{
return
MaterialApp
(
home:
Scaffold
(
body:
CustomScrollView
(
scrollDirection:
scrollDirection
,
slivers:
slivers
,
controller:
controller
,
}
group
(
'SliverFillRemaining'
,
()
{
group
(
'hasScrollBody: true, default'
,
()
{
testWidgets
(
'no siblings'
,
(
WidgetTester
tester
)
async
{
final
ScrollController
controller
=
ScrollController
();
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
CustomScrollView
(
controller:
controller
,
slivers:
<
Widget
>[
SliverFillRemaining
(
child:
Container
()),
],
),
),
),
);
}
testWidgets
(
'does not extend past viewport when false'
,
(
WidgetTester
tester
)
async
{
final
ScrollController
controller
=
ScrollController
();
final
List
<
Widget
>
slivers
=
<
Widget
>[
sliverBox
,
SliverFillRemaining
(
child:
Container
(
color:
Colors
.
white
),
hasScrollBody:
false
,
),
];
await
tester
.
pumpWidget
(
boilerplate
(
slivers
,
controller:
controller
));
expect
(
controller
.
offset
,
0.0
);
expect
(
find
.
byType
(
Container
),
findsNWidgets
(
2
));
controller
.
jumpTo
(
150.0
);
await
tester
.
pumpAndSettle
();
expect
(
controller
.
offset
,
0.0
);
expect
(
find
.
byType
(
Container
),
findsNWidgets
(
2
));
});
testWidgets
(
'scrolls beyond viewport by default'
,
(
WidgetTester
tester
)
async
{
final
ScrollController
controller
=
ScrollController
();
final
List
<
Widget
>
slivers
=
<
Widget
>[
sliverBox
,
SliverFillRemaining
(
child:
Container
(
color:
Colors
.
white
),
),
];
await
tester
.
pumpWidget
(
boilerplate
(
slivers
,
controller:
controller
));
expect
(
controller
.
offset
,
0.0
);
expect
(
find
.
byType
(
Container
),
findsNWidgets
(
2
));
controller
.
jumpTo
(
150.0
);
await
tester
.
pumpAndSettle
();
expect
(
controller
.
offset
,
150.0
);
expect
(
find
.
byType
(
Container
),
findsOneWidget
);
});
// SliverFillRemaining considers child size when hasScrollBody: false
testWidgets
(
'child without size is sized by extent when false'
,
(
WidgetTester
tester
)
async
{
final
List
<
Widget
>
slivers
=
<
Widget
>[
sliverBox
,
SliverFillRemaining
(
hasScrollBody:
false
,
child:
Container
(
color:
Colors
.
blue
),
),
];
await
tester
.
pumpWidget
(
boilerplate
(
slivers
));
RenderBox
box
=
tester
.
renderObject
<
RenderBox
>(
find
.
byType
(
Container
).
last
);
expect
(
box
.
size
.
height
,
equals
(
450
));
await
tester
.
pumpWidget
(
boilerplate
(
slivers
,
scrollDirection:
Axis
.
horizontal
));
box
=
tester
.
renderObject
<
RenderBox
>(
find
.
byType
(
Container
).
last
);
expect
(
box
.
size
.
width
,
equals
(
650
));
});
testWidgets
(
'child with size is sized by extent when false'
,
(
WidgetTester
tester
)
async
{
final
GlobalKey
key
=
GlobalKey
();
final
List
<
Widget
>
slivers
=
<
Widget
>[
sliverBox
,
SliverFillRemaining
(
hasScrollBody:
false
,
child:
Container
(
key:
key
,
color:
Colors
.
blue
,
child:
Align
(
alignment:
Alignment
.
bottomCenter
,
child:
RaisedButton
(
child:
const
Text
(
'bottomCenter button'
),
onPressed:
()
{},
),
);
expect
(
tester
.
renderObject
<
RenderBox
>(
find
.
byType
(
Container
)).
size
.
height
,
equals
(
600.0
),
);
controller
.
jumpTo
(
50.0
);
await
tester
.
pump
();
expect
(
tester
.
renderObject
<
RenderBox
>(
find
.
byType
(
Container
)).
size
.
height
,
equals
(
600.0
),
);
controller
.
jumpTo
(-
100.0
);
await
tester
.
pump
();
expect
(
tester
.
renderObject
<
RenderBox
>(
find
.
byType
(
Container
)).
size
.
height
,
equals
(
600.0
),
);
controller
.
jumpTo
(
0.0
);
await
tester
.
pump
();
expect
(
tester
.
renderObject
<
RenderBox
>(
find
.
byType
(
Container
)).
size
.
height
,
equals
(
600.0
),
);
});
testWidgets
(
'one sibling'
,
(
WidgetTester
tester
)
async
{
final
ScrollController
controller
=
ScrollController
();
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
CustomScrollView
(
controller:
controller
,
slivers:
<
Widget
>[
const
SliverToBoxAdapter
(
child:
SizedBox
(
height:
100.0
)),
SliverFillRemaining
(
child:
Container
()),
],
),
),
),
];
await
tester
.
pumpWidget
(
boilerplate
(
slivers
));
expect
(
tester
.
renderObject
<
RenderBox
>(
find
.
byKey
(
key
)).
size
.
height
,
equals
(
450
));
// Also check that the button alignment is true to expectations
final
Finder
button
=
find
.
byType
(
RaisedButton
);
expect
(
tester
.
getBottomLeft
(
button
).
dy
,
equals
(
600.0
));
expect
(
tester
.
getCenter
(
button
).
dx
,
equals
(
400.0
));
await
tester
.
pumpWidget
(
boilerplate
(
slivers
,
scrollDirection:
Axis
.
horizontal
));
expect
(
tester
.
renderObject
<
RenderBox
>(
find
.
byKey
(
key
)).
size
.
width
,
equals
(
650
));
});
testWidgets
(
'extent is overridden by child with larger size when false'
,
(
WidgetTester
tester
)
async
{
final
List
<
Widget
>
slivers
=
<
Widget
>[
sliverBox
,
SliverFillRemaining
(
hasScrollBody:
false
,
child:
Container
(
color:
Colors
.
blue
,
height:
600
,
width:
1000
,
);
expect
(
tester
.
renderObject
<
RenderBox
>(
find
.
byType
(
Container
)).
size
.
height
,
equals
(
500.0
),
);
controller
.
jumpTo
(
50.0
);
await
tester
.
pump
();
expect
(
tester
.
renderObject
<
RenderBox
>(
find
.
byType
(
Container
)).
size
.
height
,
equals
(
550.0
),
);
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
),
);
});
testWidgets
(
'scrolls beyond viewportMainAxisExtent'
,
(
WidgetTester
tester
)
async
{
final
ScrollController
controller
=
ScrollController
();
final
List
<
Widget
>
slivers
=
<
Widget
>[
sliverBox
,
SliverFillRemaining
(
child:
Container
(
color:
Colors
.
white
),
),
),
]
;
await
tester
.
pumpWidget
(
boilerplate
(
slivers
)
);
RenderBox
box
=
tester
.
renderObject
<
RenderBox
>(
find
.
byType
(
Container
).
last
);
expect
(
box
.
size
.
height
,
equals
(
600
)
);
await
tester
.
pumpWidget
(
boilerplate
(
slivers
,
scrollDirection:
Axis
.
horizontal
)
);
box
=
tester
.
renderObject
<
RenderBox
>(
find
.
byType
(
Container
).
las
t
);
expect
(
box
.
size
.
width
,
equals
(
1000
)
);
];
await
tester
.
pumpWidget
(
boilerplate
(
slivers
,
controller:
controller
))
;
expect
(
controller
.
offset
,
0.0
);
expect
(
find
.
byType
(
Container
),
findsNWidgets
(
2
)
);
controller
.
jumpTo
(
150.0
);
await
tester
.
pumpAndSettle
();
expect
(
controller
.
offset
,
150.0
);
expect
(
find
.
byType
(
Container
),
findsOneWidge
t
);
}
);
});
testWidgets
(
'extent is overridden by child size if precedingScrollExtent > viewportMainAxisExtent when false'
,
(
WidgetTester
tester
)
async
{
final
GlobalKey
key
=
GlobalKey
();
final
List
<
Widget
>
slivers
=
<
Widget
>[
SliverFixedExtentList
(
itemExtent:
150
,
delegate:
SliverChildBuilderDelegate
(
(
BuildContext
context
,
int
index
)
=>
Container
(
color:
Colors
.
amber
),
childCount:
5
,
group
(
'hasScrollBody: false'
,
()
{
testWidgets
(
'does not extend past viewportMainAxisExtent'
,
(
WidgetTester
tester
)
async
{
final
ScrollController
controller
=
ScrollController
();
final
List
<
Widget
>
slivers
=
<
Widget
>[
sliverBox
,
SliverFillRemaining
(
child:
Container
(
color:
Colors
.
white
),
hasScrollBody:
false
,
),
),
SliverFillRemaining
(
hasScrollBody:
false
,
child:
Container
(
key:
key
,
color:
Colors
.
blue
[
300
],
child:
Align
(
alignment:
Alignment
.
center
,
child:
Padding
(
padding:
const
EdgeInsets
.
all
(
50.0
),
];
await
tester
.
pumpWidget
(
boilerplate
(
slivers
,
controller:
controller
));
expect
(
controller
.
offset
,
0.0
);
expect
(
find
.
byType
(
Container
),
findsNWidgets
(
2
));
controller
.
jumpTo
(
150.0
);
await
tester
.
pumpAndSettle
();
expect
(
controller
.
offset
,
0.0
);
expect
(
find
.
byType
(
Container
),
findsNWidgets
(
2
));
});
testWidgets
(
'child without size is sized by extent'
,
(
WidgetTester
tester
)
async
{
final
List
<
Widget
>
slivers
=
<
Widget
>[
sliverBox
,
SliverFillRemaining
(
hasScrollBody:
false
,
child:
Container
(
color:
Colors
.
blue
),
),
];
await
tester
.
pumpWidget
(
boilerplate
(
slivers
));
RenderBox
box
=
tester
.
renderObject
<
RenderBox
>(
find
.
byType
(
Container
).
last
);
expect
(
box
.
size
.
height
,
equals
(
450
));
await
tester
.
pumpWidget
(
boilerplate
(
slivers
,
scrollDirection:
Axis
.
horizontal
,
));
box
=
tester
.
renderObject
<
RenderBox
>(
find
.
byType
(
Container
).
last
);
expect
(
box
.
size
.
width
,
equals
(
650
));
});
testWidgets
(
'child with smaller size is sized by extent'
,
(
WidgetTester
tester
)
async
{
final
GlobalKey
key
=
GlobalKey
();
final
List
<
Widget
>
slivers
=
<
Widget
>[
sliverBox
,
SliverFillRemaining
(
hasScrollBody:
false
,
child:
Container
(
key:
key
,
color:
Colors
.
blue
,
child:
Align
(
alignment:
Alignment
.
bottomCenter
,
child:
RaisedButton
(
child:
const
Text
(
'
c
enter button'
),
child:
const
Text
(
'
bottomC
enter button'
),
onPressed:
()
{},
),
),
),
),
),
];
await
tester
.
pumpWidget
(
boilerplate
(
slivers
));
await
tester
.
drag
(
find
.
byType
(
Scrollable
),
const
Offset
(
0.0
,
-
750.0
));
await
tester
.
pump
();
expect
(
tester
.
renderObject
<
RenderBox
>(
find
.
byKey
(
key
)).
size
.
height
,
equals
(
148.0
));
// Also check that the button alignment is true to expectations
final
Finder
button
=
find
.
byType
(
RaisedButton
);
expect
(
tester
.
getBottomLeft
(
button
).
dy
,
equals
(
550.0
));
expect
(
tester
.
getCenter
(
button
).
dx
,
equals
(
400.0
));
});
// iOS/Similar scroll physics when hasScrollBody: false & fillOverscroll: true behavior
testWidgets
(
'child without size is sized by extent and overscroll'
,
(
WidgetTester
tester
)
async
{
debugDefaultTargetPlatformOverride
=
TargetPlatform
.
iOS
;
final
List
<
Widget
>
slivers
=
<
Widget
>[
sliverBox
,
SliverFillRemaining
(
hasScrollBody:
false
,
fillOverscroll:
true
,
child:
Container
(
color:
Colors
.
blue
),
),
];
await
tester
.
pumpWidget
(
boilerplate
(
slivers
));
final
RenderBox
box1
=
tester
.
renderObject
<
RenderBox
>(
find
.
byType
(
Container
).
last
);
expect
(
box1
.
size
.
height
,
equals
(
450
));
await
tester
.
drag
(
find
.
byType
(
Scrollable
),
const
Offset
(
0.0
,
-
50.0
));
await
tester
.
pump
();
final
RenderBox
box2
=
tester
.
renderObject
<
RenderBox
>(
find
.
byType
(
Container
).
last
);
expect
(
box2
.
size
.
height
,
greaterThan
(
450
));
debugDefaultTargetPlatformOverride
=
null
;
});
testWidgets
(
'child with size is overridden and sized by extent and overscroll'
,
(
WidgetTester
tester
)
async
{
debugDefaultTargetPlatformOverride
=
TargetPlatform
.
iOS
;
final
GlobalKey
key
=
GlobalKey
();
final
List
<
Widget
>
slivers
=
<
Widget
>[
sliverBox
,
SliverFillRemaining
(
hasScrollBody:
false
,
fillOverscroll:
true
,
child:
Container
(
key:
key
,
color:
Colors
.
blue
,
child:
Align
(
alignment:
Alignment
.
bottomCenter
,
child:
RaisedButton
(
child:
const
Text
(
'bottomCenter button'
),
onPressed:
()
{},
),
];
await
tester
.
pumpWidget
(
boilerplate
(
slivers
));
expect
(
tester
.
renderObject
<
RenderBox
>(
find
.
byKey
(
key
)).
size
.
height
,
equals
(
450
),
);
// Also check that the button alignment is true to expectations
final
Finder
button
=
find
.
byType
(
RaisedButton
);
expect
(
tester
.
getBottomLeft
(
button
).
dy
,
equals
(
600.0
));
expect
(
tester
.
getCenter
(
button
).
dx
,
equals
(
400.0
));
// Check Axis.horizontal
await
tester
.
pumpWidget
(
boilerplate
(
slivers
,
scrollDirection:
Axis
.
horizontal
,
));
expect
(
tester
.
renderObject
<
RenderBox
>(
find
.
byKey
(
key
)).
size
.
width
,
equals
(
650
),
);
});
testWidgets
(
'extent is overridden by child with larger size'
,
(
WidgetTester
tester
)
async
{
final
List
<
Widget
>
slivers
=
<
Widget
>[
sliverBox
,
SliverFillRemaining
(
hasScrollBody:
false
,
child:
Container
(
color:
Colors
.
blue
,
height:
600
,
width:
1000
,
),
),
),
];
await
tester
.
pumpWidget
(
boilerplate
(
slivers
));
expect
(
tester
.
renderObject
<
RenderBox
>(
find
.
byKey
(
key
)).
size
.
height
,
equals
(
450
));
await
tester
.
drag
(
find
.
byType
(
Scrollable
),
const
Offset
(
0.0
,
-
50.0
));
await
tester
.
pump
();
expect
(
tester
.
renderObject
<
RenderBox
>(
find
.
byKey
(
key
)).
size
.
height
,
greaterThan
(
450
));
// Also check that the button alignment is true to expectations, even with
// child stretching to fill overscroll
final
Finder
button
=
find
.
byType
(
RaisedButton
);
expect
(
tester
.
getBottomLeft
(
button
).
dy
,
equals
(
600.0
));
expect
(
tester
.
getCenter
(
button
).
dx
,
equals
(
400.0
));
debugDefaultTargetPlatformOverride
=
null
;
});
testWidgets
(
'extent is overridden by child size and overscroll if precedingScrollExtent > viewportMainAxisExtent'
,
(
WidgetTester
tester
)
async
{
debugDefaultTargetPlatformOverride
=
TargetPlatform
.
iOS
;
final
GlobalKey
key
=
GlobalKey
();
final
ScrollController
controller
=
ScrollController
();
final
List
<
Widget
>
slivers
=
<
Widget
>[
SliverFixedExtentList
(
itemExtent:
150
,
delegate:
SliverChildBuilderDelegate
(
(
BuildContext
context
,
int
index
)
=>
Container
(
color:
Colors
.
amber
),
childCount:
5
,
];
await
tester
.
pumpWidget
(
boilerplate
(
slivers
));
RenderBox
box
=
tester
.
renderObject
<
RenderBox
>(
find
.
byType
(
Container
).
last
);
expect
(
box
.
size
.
height
,
equals
(
600
));
await
tester
.
pumpWidget
(
boilerplate
(
slivers
,
scrollDirection:
Axis
.
horizontal
,
));
box
=
tester
.
renderObject
<
RenderBox
>(
find
.
byType
(
Container
).
last
);
expect
(
box
.
size
.
width
,
equals
(
1000
));
});
testWidgets
(
'extent is overridden by child size if precedingScrollExtent > viewportMainAxisExtent'
,
(
WidgetTester
tester
)
async
{
final
GlobalKey
key
=
GlobalKey
();
final
List
<
Widget
>
slivers
=
<
Widget
>[
SliverFixedExtentList
(
itemExtent:
150
,
delegate:
SliverChildBuilderDelegate
(
(
BuildContext
context
,
int
index
)
=>
Container
(
color:
Colors
.
amber
),
childCount:
5
,
),
),
),
SliverFillRemaining
(
hasScrollBody:
false
,
fillOverscroll:
true
,
child:
Container
(
key:
key
,
color:
Colors
.
blue
[
300
],
child:
Align
(
alignment:
Alignment
.
center
,
child:
Padding
(
padding:
const
EdgeInsets
.
all
(
50.0
),
child:
RaisedButton
(
child:
const
Text
(
'center button'
),
onPressed:
()
{},
SliverFillRemaining
(
hasScrollBody:
false
,
child:
Container
(
key:
key
,
color:
Colors
.
blue
[
300
],
child:
Align
(
alignment:
Alignment
.
center
,
child:
Padding
(
padding:
const
EdgeInsets
.
all
(
50.0
),
child:
RaisedButton
(
child:
const
Text
(
'center button'
),
onPressed:
()
{},
),
),
),
),
),
),
];
await
tester
.
pumpWidget
(
boilerplate
(
slivers
,
controller:
controller
));
// Scroll to the end
controller
.
jumpTo
(
controller
.
position
.
maxScrollExtent
);
await
tester
.
pump
();
expect
(
tester
.
renderObject
<
RenderBox
>(
find
.
byKey
(
key
)).
size
.
height
,
equals
(
148.0
));
// Check that the button alignment is true to expectations
final
Finder
button
=
find
.
byType
(
RaisedButton
);
expect
(
tester
.
getBottomLeft
(
button
).
dy
,
equals
(
550.0
));
expect
(
tester
.
getCenter
(
button
).
dx
,
equals
(
400.0
));
debugDefaultTargetPlatformOverride
=
null
;
// Drag for overscroll
await
tester
.
drag
(
find
.
byType
(
Scrollable
),
const
Offset
(
0.0
,
-
50.0
));
await
tester
.
pump
();
expect
(
tester
.
renderObject
<
RenderBox
>(
find
.
byKey
(
key
)).
size
.
height
,
greaterThan
(
148.0
));
// Check that the button alignment is still centered in stretched child
expect
(
tester
.
getBottomLeft
(
button
).
dy
,
lessThan
(
550.0
));
expect
(
tester
.
getCenter
(
button
).
dx
,
equals
(
400.0
));
debugDefaultTargetPlatformOverride
=
null
;
});
// Android/Other scroll physics when hasScrollBody: false, ignores fillOverscroll: true
testWidgets
(
'child without size is sized by extent, fillOverscroll is ignored'
,
(
WidgetTester
tester
)
async
{
final
List
<
Widget
>
slivers
=
<
Widget
>[
sliverBox
,
SliverFillRemaining
(
hasScrollBody:
false
,
fillOverscroll:
true
,
child:
Container
(
color:
Colors
.
blue
),
),
];
await
tester
.
pumpWidget
(
boilerplate
(
slivers
));
final
RenderBox
box1
=
tester
.
renderObject
<
RenderBox
>(
find
.
byType
(
Container
).
last
);
expect
(
box1
.
size
.
height
,
equals
(
450
));
await
tester
.
drag
(
find
.
byType
(
Scrollable
),
const
Offset
(
0.0
,
-
50.0
));
await
tester
.
pump
();
final
RenderBox
box2
=
tester
.
renderObject
<
RenderBox
>(
find
.
byType
(
Container
).
last
);
expect
(
box2
.
size
.
height
,
equals
(
450
));
});
testWidgets
(
'child with size is overridden and sized by extent, fillOverscroll is ignored'
,
(
WidgetTester
tester
)
async
{
final
GlobalKey
key
=
GlobalKey
();
final
List
<
Widget
>
slivers
=
<
Widget
>[
sliverBox
,
SliverFillRemaining
(
hasScrollBody:
false
,
fillOverscroll:
true
,
child:
Container
(
key:
key
,
color:
Colors
.
blue
,
child:
Align
(
alignment:
Alignment
.
bottomCenter
,
child:
RaisedButton
(
child:
const
Text
(
'bottomCenter button'
),
onPressed:
()
{},
),
];
await
tester
.
pumpWidget
(
boilerplate
(
slivers
));
await
tester
.
drag
(
find
.
byType
(
Scrollable
),
const
Offset
(
0.0
,
-
750.0
));
await
tester
.
pump
();
expect
(
tester
.
renderObject
<
RenderBox
>(
find
.
byKey
(
key
)).
size
.
height
,
equals
(
148.0
),
);
// Also check that the button alignment is true to expectations
final
Finder
button
=
find
.
byType
(
RaisedButton
);
expect
(
tester
.
getBottomLeft
(
button
).
dy
,
equals
(
550.0
));
expect
(
tester
.
getCenter
(
button
).
dx
,
equals
(
400.0
));
});
testWidgets
(
'alignment with a flexible works'
,
(
WidgetTester
tester
)
async
{
debugDefaultTargetPlatformOverride
=
TargetPlatform
.
iOS
;
final
GlobalKey
key
=
GlobalKey
();
final
List
<
Widget
>
slivers
=
<
Widget
>[
sliverBox
,
SliverFillRemaining
(
hasScrollBody:
false
,
child:
Column
(
key:
key
,
mainAxisSize:
MainAxisSize
.
min
,
children:
<
Widget
>[
const
Flexible
(
child:
Center
(
child:
FlutterLogo
(
size:
100
)),
fit:
FlexFit
.
loose
,
),
RaisedButton
(
child:
const
Text
(
'Bottom'
),
onPressed:
()
{},
),
]
),
),
),
];
await
tester
.
pumpWidget
(
boilerplate
(
slivers
));
expect
(
tester
.
renderObject
<
RenderBox
>(
find
.
byKey
(
key
)).
size
.
height
,
equals
(
450
));
await
tester
.
drag
(
find
.
byType
(
Scrollable
),
const
Offset
(
0.0
,
-
50.0
));
await
tester
.
pump
();
expect
(
tester
.
renderObject
<
RenderBox
>(
find
.
byKey
(
key
)).
size
.
height
,
equals
(
450
));
// Also check that the button alignment is true to expectations
final
Finder
button
=
find
.
byType
(
RaisedButton
);
expect
(
tester
.
getBottomLeft
(
button
).
dy
,
equals
(
600.0
));
expect
(
tester
.
getCenter
(
button
).
dx
,
equals
(
400.0
));
});
testWidgets
(
'extent is overridden by child size if precedingScrollExtent > viewportMainAxisExtent, fillOverscroll is ignored'
,
(
WidgetTester
tester
)
async
{
final
GlobalKey
key
=
GlobalKey
();
final
ScrollController
controller
=
ScrollController
();
final
List
<
Widget
>
slivers
=
<
Widget
>[
SliverFixedExtentList
(
itemExtent:
150
,
delegate:
SliverChildBuilderDelegate
(
(
BuildContext
context
,
int
index
)
=>
Container
(
color:
Colors
.
amber
),
childCount:
5
,
),
),
SliverFillRemaining
(
hasScrollBody:
false
,
fillOverscroll:
true
,
child:
Container
(
key:
key
,
color:
Colors
.
blue
[
300
],
child:
Align
(
alignment:
Alignment
.
center
,
child:
Padding
(
padding:
const
EdgeInsets
.
all
(
50.0
),
child:
RaisedButton
(
child:
const
Text
(
'center button'
),
onPressed:
()
{},
];
await
tester
.
pumpWidget
(
boilerplate
(
slivers
));
expect
(
tester
.
renderObject
<
RenderBox
>(
find
.
byKey
(
key
)).
size
.
height
,
equals
(
450
),
);
// Check that the logo alignment is true to expectations
final
Finder
logo
=
find
.
byType
(
FlutterLogo
);
expect
(
tester
.
renderObject
<
RenderBox
>(
logo
).
size
,
const
Size
(
100.0
,
100.0
),
);
expect
(
tester
.
getCenter
(
logo
),
const
Offset
(
400.0
,
351.0
));
// Also check that the button alignment is true to expectations
final
Finder
button
=
find
.
byType
(
RaisedButton
);
expect
(
tester
.
renderObject
<
RenderBox
>(
button
).
size
,
const
Size
(
116.0
,
48.0
),
);
expect
(
tester
.
getBottomLeft
(
button
).
dy
,
equals
(
600.0
));
expect
(
tester
.
getCenter
(
button
).
dx
,
equals
(
400.0
));
// Overscroll and see that alignment and size is maintained
await
tester
.
drag
(
find
.
byType
(
Scrollable
),
const
Offset
(
0.0
,
-
50.0
));
await
tester
.
pump
();
expect
(
tester
.
renderObject
<
RenderBox
>(
find
.
byKey
(
key
)).
size
.
height
,
equals
(
450
),
);
expect
(
tester
.
renderObject
<
RenderBox
>(
logo
).
size
,
const
Size
(
100.0
,
100.0
),
);
expect
(
tester
.
getCenter
(
logo
).
dy
,
lessThan
(
351.0
));
expect
(
tester
.
renderObject
<
RenderBox
>(
button
).
size
,
const
Size
(
116.0
,
48.0
),
);
expect
(
tester
.
getBottomLeft
(
button
).
dy
,
lessThan
(
600.0
));
expect
(
tester
.
getCenter
(
button
).
dx
,
equals
(
400.0
));
debugDefaultTargetPlatformOverride
=
null
;
});
group
(
'fillOverscroll: true, relevant platforms'
,
()
{
testWidgets
(
'child without size is sized by extent and overscroll'
,
(
WidgetTester
tester
)
async
{
debugDefaultTargetPlatformOverride
=
TargetPlatform
.
iOS
;
final
List
<
Widget
>
slivers
=
<
Widget
>[
sliverBox
,
SliverFillRemaining
(
hasScrollBody:
false
,
fillOverscroll:
true
,
child:
Container
(
color:
Colors
.
blue
),
),
];
// Check size
await
tester
.
pumpWidget
(
boilerplate
(
slivers
));
final
RenderBox
box1
=
tester
.
renderObject
<
RenderBox
>(
find
.
byType
(
Container
).
last
);
expect
(
box1
.
size
.
height
,
equals
(
450
));
// Overscroll and check size
await
tester
.
drag
(
find
.
byType
(
Scrollable
),
const
Offset
(
0.0
,
-
50.0
));
await
tester
.
pump
();
final
RenderBox
box2
=
tester
.
renderObject
<
RenderBox
>(
find
.
byType
(
Container
).
last
);
expect
(
box2
.
size
.
height
,
greaterThan
(
450
));
// Ensure overscroll retracts to original size after releasing gesture
await
tester
.
pumpAndSettle
();
final
RenderBox
box3
=
tester
.
renderObject
<
RenderBox
>(
find
.
byType
(
Container
).
last
);
expect
(
box3
.
size
.
height
,
equals
(
450
));
debugDefaultTargetPlatformOverride
=
null
;
});
testWidgets
(
'child with smaller size is overridden and sized by extent and overscroll'
,
(
WidgetTester
tester
)
async
{
debugDefaultTargetPlatformOverride
=
TargetPlatform
.
iOS
;
final
GlobalKey
key
=
GlobalKey
();
final
List
<
Widget
>
slivers
=
<
Widget
>[
sliverBox
,
SliverFillRemaining
(
hasScrollBody:
false
,
fillOverscroll:
true
,
child:
Container
(
key:
key
,
color:
Colors
.
blue
,
child:
Align
(
alignment:
Alignment
.
bottomCenter
,
child:
RaisedButton
(
child:
const
Text
(
'bottomCenter button'
),
onPressed:
()
{},
),
),
),
),
),
),
];
await
tester
.
pumpWidget
(
boilerplate
(
slivers
,
controller:
controller
));
// Scroll to the end
controller
.
jumpTo
(
controller
.
position
.
maxScrollExtent
);
await
tester
.
pump
();
expect
(
tester
.
renderObject
<
RenderBox
>(
find
.
byKey
(
key
)).
size
.
height
,
equals
(
148.0
));
// Check that the button alignment is true to expectations
final
Finder
button
=
find
.
byType
(
RaisedButton
);
expect
(
tester
.
getBottomLeft
(
button
).
dy
,
equals
(
550.0
));
expect
(
tester
.
getCenter
(
button
).
dx
,
equals
(
400.0
));
await
tester
.
drag
(
find
.
byType
(
Scrollable
),
const
Offset
(
0.0
,
-
50.0
));
await
tester
.
pump
();
expect
(
tester
.
renderObject
<
RenderBox
>(
find
.
byKey
(
key
)).
size
.
height
,
equals
(
148.0
));
// Check that the button alignment is still centered in stretched child
expect
(
tester
.
getBottomLeft
(
button
).
dy
,
equals
(
550.0
));
expect
(
tester
.
getCenter
(
button
).
dx
,
equals
(
400.0
));
];
await
tester
.
pumpWidget
(
boilerplate
(
slivers
));
expect
(
tester
.
renderObject
<
RenderBox
>(
find
.
byKey
(
key
)).
size
.
height
,
equals
(
450
),
);
await
tester
.
drag
(
find
.
byType
(
Scrollable
),
const
Offset
(
0.0
,
-
50.0
));
await
tester
.
pump
();
expect
(
tester
.
renderObject
<
RenderBox
>(
find
.
byKey
(
key
)).
size
.
height
,
greaterThan
(
450
),
);
// Also check that the button alignment is true to expectations, even with
// child stretching to fill overscroll
final
Finder
button
=
find
.
byType
(
RaisedButton
);
expect
(
tester
.
getBottomLeft
(
button
).
dy
,
equals
(
600.0
));
expect
(
tester
.
getCenter
(
button
).
dx
,
equals
(
400.0
));
// Ensure overscroll retracts to original size after releasing gesture
await
tester
.
pumpAndSettle
();
expect
(
tester
.
renderObject
<
RenderBox
>(
find
.
byKey
(
key
)).
size
.
height
,
equals
(
450
),
);
debugDefaultTargetPlatformOverride
=
null
;
});
testWidgets
(
'extent is overridden by child size and overscroll if precedingScrollExtent > viewportMainAxisExtent'
,
(
WidgetTester
tester
)
async
{
debugDefaultTargetPlatformOverride
=
TargetPlatform
.
iOS
;
final
GlobalKey
key
=
GlobalKey
();
final
ScrollController
controller
=
ScrollController
();
final
List
<
Widget
>
slivers
=
<
Widget
>[
SliverFixedExtentList
(
itemExtent:
150
,
delegate:
SliverChildBuilderDelegate
(
(
BuildContext
context
,
int
index
)
=>
Container
(
color:
Colors
.
amber
),
childCount:
5
,
),
),
SliverFillRemaining
(
hasScrollBody:
false
,
fillOverscroll:
true
,
child:
Container
(
key:
key
,
color:
Colors
.
blue
[
300
],
child:
Align
(
alignment:
Alignment
.
center
,
child:
Padding
(
padding:
const
EdgeInsets
.
all
(
50.0
),
child:
RaisedButton
(
child:
const
Text
(
'center button'
),
onPressed:
()
{},
),
),
),
),
),
];
await
tester
.
pumpWidget
(
boilerplate
(
slivers
,
controller:
controller
));
// Scroll to the end
controller
.
jumpTo
(
controller
.
position
.
maxScrollExtent
);
await
tester
.
pump
();
expect
(
tester
.
renderObject
<
RenderBox
>(
find
.
byKey
(
key
)).
size
.
height
,
equals
(
148.0
),
);
// Check that the button alignment is true to expectations
final
Finder
button
=
find
.
byType
(
RaisedButton
);
expect
(
tester
.
getBottomLeft
(
button
).
dy
,
equals
(
550.0
));
expect
(
tester
.
getCenter
(
button
).
dx
,
equals
(
400.0
));
debugDefaultTargetPlatformOverride
=
null
;
// Drag for overscroll
await
tester
.
drag
(
find
.
byType
(
Scrollable
),
const
Offset
(
0.0
,
-
50.0
));
await
tester
.
pump
();
expect
(
tester
.
renderObject
<
RenderBox
>(
find
.
byKey
(
key
)).
size
.
height
,
greaterThan
(
148.0
),
);
// Check that the button alignment is still centered in stretched child
expect
(
tester
.
getBottomLeft
(
button
).
dy
,
lessThan
(
550.0
));
expect
(
tester
.
getCenter
(
button
).
dx
,
equals
(
400.0
));
// Ensure overscroll retracts to original size after releasing gesture
await
tester
.
pumpAndSettle
();
expect
(
tester
.
renderObject
<
RenderBox
>(
find
.
byKey
(
key
)).
size
.
height
,
equals
(
148.0
),
);
debugDefaultTargetPlatformOverride
=
null
;
});
testWidgets
(
'fillOverscroll works when child has no size and precedingScrollExtent > viewportMainAxisExtent'
,
(
WidgetTester
tester
)
async
{
debugDefaultTargetPlatformOverride
=
TargetPlatform
.
iOS
;
final
GlobalKey
key
=
GlobalKey
();
final
ScrollController
controller
=
ScrollController
();
final
List
<
Widget
>
slivers
=
<
Widget
>[
SliverFixedExtentList
(
itemExtent:
150
,
delegate:
SliverChildBuilderDelegate
(
(
BuildContext
context
,
int
index
)
=>
Container
(
color:
Colors
.
amber
),
childCount:
5
,
),
),
SliverFillRemaining
(
hasScrollBody:
false
,
fillOverscroll:
true
,
child:
Container
(
key:
key
,
color:
Colors
.
blue
,
),
),
];
await
tester
.
pumpWidget
(
boilerplate
(
slivers
,
controller:
controller
));
const
BoxDecoration
amberBox
=
BoxDecoration
(
color:
Colors
.
amber
);
const
BoxDecoration
blueBox
=
BoxDecoration
(
color:
Colors
.
blue
);
// Scroll to bottom
controller
.
jumpTo
(
controller
.
position
.
maxScrollExtent
);
await
tester
.
pump
();
// Check item at the end of the list
expect
(
find
.
byKey
(
key
),
findsNothing
);
expect
(
tester
.
widgetList
<
DecoratedBox
>(
find
.
byType
(
DecoratedBox
)).
last
.
decoration
,
amberBox
,
);
// Overscroll
await
tester
.
drag
(
find
.
byType
(
Scrollable
),
const
Offset
(
0.0
,
-
50.0
));
await
tester
.
pump
();
// Check for new item at the end of the now overscrolled list
expect
(
find
.
byKey
(
key
),
findsOneWidget
);
expect
(
tester
.
widgetList
<
DecoratedBox
>(
find
.
byType
(
DecoratedBox
)).
last
.
decoration
,
blueBox
,
);
// Ensure overscroll retracts to original size after releasing gesture
await
tester
.
pumpAndSettle
();
expect
(
find
.
byKey
(
key
),
findsNothing
);
expect
(
tester
.
widgetList
<
DecoratedBox
>(
find
.
byType
(
DecoratedBox
)).
last
.
decoration
,
amberBox
,
);
debugDefaultTargetPlatformOverride
=
null
;
});
testWidgets
(
'alignment with a flexible works with fillOverscroll'
,
(
WidgetTester
tester
)
async
{
debugDefaultTargetPlatformOverride
=
TargetPlatform
.
iOS
;
final
GlobalKey
key
=
GlobalKey
();
final
List
<
Widget
>
slivers
=
<
Widget
>[
sliverBox
,
SliverFillRemaining
(
hasScrollBody:
false
,
fillOverscroll:
true
,
child:
Column
(
key:
key
,
mainAxisSize:
MainAxisSize
.
min
,
children:
<
Widget
>[
const
Flexible
(
child:
Center
(
child:
FlutterLogo
(
size:
100
)),
fit:
FlexFit
.
loose
,
),
RaisedButton
(
child:
const
Text
(
'Bottom'
),
onPressed:
()
{},
),
]
),
),
];
await
tester
.
pumpWidget
(
boilerplate
(
slivers
));
expect
(
tester
.
renderObject
<
RenderBox
>(
find
.
byKey
(
key
)).
size
.
height
,
equals
(
450
),
);
// Check that the logo alignment is true to expectations.
final
Finder
logo
=
find
.
byType
(
FlutterLogo
);
expect
(
tester
.
renderObject
<
RenderBox
>(
logo
).
size
,
const
Size
(
100.0
,
100.0
),
);
expect
(
tester
.
getCenter
(
logo
),
const
Offset
(
400.0
,
351.0
));
// Also check that the button alignment is true to expectations.
final
Finder
button
=
find
.
byType
(
RaisedButton
);
expect
(
tester
.
renderObject
<
RenderBox
>(
button
).
size
,
const
Size
(
116.0
,
48.0
),
);
expect
(
tester
.
getBottomLeft
(
button
).
dy
,
equals
(
600.0
));
expect
(
tester
.
getCenter
(
button
).
dx
,
equals
(
400.0
));
// Overscroll and see that logo alignment shifts to maintain center as
// container stretches with overscroll, button remains aligned at the
// bottom.
await
tester
.
drag
(
find
.
byType
(
Scrollable
),
const
Offset
(
0.0
,
-
50.0
));
await
tester
.
pump
();
expect
(
tester
.
renderObject
<
RenderBox
>(
find
.
byKey
(
key
)).
size
.
height
,
greaterThan
(
450
),
);
expect
(
tester
.
renderObject
<
RenderBox
>(
logo
).
size
,
const
Size
(
100.0
,
100.0
),
);
expect
(
tester
.
getCenter
(
logo
).
dy
,
lessThan
(
351.0
));
expect
(
tester
.
renderObject
<
RenderBox
>(
button
).
size
,
const
Size
(
116.0
,
48.0
),
);
expect
(
tester
.
getBottomLeft
(
button
).
dy
,
equals
(
600.0
));
expect
(
tester
.
getCenter
(
button
).
dx
,
equals
(
400.0
));
// Ensure overscroll retracts to original position when gesture is
// released.
await
tester
.
pumpAndSettle
();
expect
(
tester
.
renderObject
<
RenderBox
>(
find
.
byKey
(
key
)).
size
.
height
,
equals
(
450
),
);
expect
(
tester
.
renderObject
<
RenderBox
>(
logo
).
size
,
const
Size
(
100.0
,
100.0
),
);
expect
(
tester
.
getCenter
(
logo
),
const
Offset
(
400.0
,
351.0
));
expect
(
tester
.
renderObject
<
RenderBox
>(
button
).
size
,
const
Size
(
116.0
,
48.0
),
);
expect
(
tester
.
getBottomLeft
(
button
).
dy
,
equals
(
600.0
));
expect
(
tester
.
getCenter
(
button
).
dx
,
equals
(
400.0
));
debugDefaultTargetPlatformOverride
=
null
;
});
});
group
(
'fillOverscroll: true, is ignored on irrevelant platforms'
,
()
{
// Android/Other scroll physics when hasScrollBody: false, ignores fillOverscroll: true
testWidgets
(
'child without size is sized by extent'
,
(
WidgetTester
tester
)
async
{
final
List
<
Widget
>
slivers
=
<
Widget
>[
sliverBox
,
SliverFillRemaining
(
hasScrollBody:
false
,
fillOverscroll:
true
,
child:
Container
(
color:
Colors
.
blue
),
),
];
await
tester
.
pumpWidget
(
boilerplate
(
slivers
));
final
RenderBox
box1
=
tester
.
renderObject
<
RenderBox
>(
find
.
byType
(
Container
).
last
);
expect
(
box1
.
size
.
height
,
equals
(
450
));
await
tester
.
drag
(
find
.
byType
(
Scrollable
),
const
Offset
(
0.0
,
-
50.0
));
await
tester
.
pump
();
final
RenderBox
box2
=
tester
.
renderObject
<
RenderBox
>(
find
.
byType
(
Container
).
last
);
expect
(
box2
.
size
.
height
,
equals
(
450
));
});
testWidgets
(
'child with size is overridden and sized by extent'
,
(
WidgetTester
tester
)
async
{
final
GlobalKey
key
=
GlobalKey
();
final
List
<
Widget
>
slivers
=
<
Widget
>[
sliverBox
,
SliverFillRemaining
(
hasScrollBody:
false
,
fillOverscroll:
true
,
child:
Container
(
key:
key
,
color:
Colors
.
blue
,
child:
Align
(
alignment:
Alignment
.
bottomCenter
,
child:
RaisedButton
(
child:
const
Text
(
'bottomCenter button'
),
onPressed:
()
{},
),
),
),
),
];
await
tester
.
pumpWidget
(
boilerplate
(
slivers
));
expect
(
tester
.
renderObject
<
RenderBox
>(
find
.
byKey
(
key
)).
size
.
height
,
equals
(
450
),
);
await
tester
.
drag
(
find
.
byType
(
Scrollable
),
const
Offset
(
0.0
,
-
50.0
));
await
tester
.
pump
();
expect
(
tester
.
renderObject
<
RenderBox
>(
find
.
byKey
(
key
)).
size
.
height
,
equals
(
450
),
);
// Also check that the button alignment is true to expectations
final
Finder
button
=
find
.
byType
(
RaisedButton
);
expect
(
tester
.
getBottomLeft
(
button
).
dy
,
equals
(
600.0
));
expect
(
tester
.
getCenter
(
button
).
dx
,
equals
(
400.0
));
});
testWidgets
(
'extent is overridden by child size if precedingScrollExtent > viewportMainAxisExtent'
,
(
WidgetTester
tester
)
async
{
final
GlobalKey
key
=
GlobalKey
();
final
ScrollController
controller
=
ScrollController
();
final
List
<
Widget
>
slivers
=
<
Widget
>[
SliverFixedExtentList
(
itemExtent:
150
,
delegate:
SliverChildBuilderDelegate
(
(
BuildContext
context
,
int
index
)
=>
Container
(
color:
Colors
.
amber
),
childCount:
5
,
),
),
SliverFillRemaining
(
hasScrollBody:
false
,
fillOverscroll:
true
,
child:
Container
(
key:
key
,
color:
Colors
.
blue
[
300
],
child:
Align
(
alignment:
Alignment
.
center
,
child:
Padding
(
padding:
const
EdgeInsets
.
all
(
50.0
),
child:
RaisedButton
(
child:
const
Text
(
'center button'
),
onPressed:
()
{},
),
),
),
),
),
];
await
tester
.
pumpWidget
(
boilerplate
(
slivers
,
controller:
controller
));
// Scroll to the end
controller
.
jumpTo
(
controller
.
position
.
maxScrollExtent
);
await
tester
.
pump
();
expect
(
tester
.
renderObject
<
RenderBox
>(
find
.
byKey
(
key
)).
size
.
height
,
equals
(
148.0
),
);
// Check that the button alignment is true to expectations
final
Finder
button
=
find
.
byType
(
RaisedButton
);
expect
(
tester
.
getBottomLeft
(
button
).
dy
,
equals
(
550.0
));
expect
(
tester
.
getCenter
(
button
).
dx
,
equals
(
400.0
));
await
tester
.
drag
(
find
.
byType
(
Scrollable
),
const
Offset
(
0.0
,
-
50.0
));
await
tester
.
pump
();
expect
(
tester
.
renderObject
<
RenderBox
>(
find
.
byKey
(
key
)).
size
.
height
,
equals
(
148.0
),
);
// Check that the button alignment is still centered
expect
(
tester
.
getBottomLeft
(
button
).
dy
,
equals
(
550.0
));
expect
(
tester
.
getCenter
(
button
).
dx
,
equals
(
400.0
));
});
testWidgets
(
'child has no size and precedingScrollExtent > viewportMainAxisExtent'
,
(
WidgetTester
tester
)
async
{
final
GlobalKey
key
=
GlobalKey
();
final
ScrollController
controller
=
ScrollController
();
final
List
<
Widget
>
slivers
=
<
Widget
>[
SliverFixedExtentList
(
itemExtent:
150
,
delegate:
SliverChildBuilderDelegate
(
(
BuildContext
context
,
int
index
)
=>
Container
(
color:
Colors
.
amber
),
childCount:
5
,
),
),
SliverFillRemaining
(
hasScrollBody:
false
,
fillOverscroll:
true
,
child:
Container
(
key:
key
,
color:
Colors
.
blue
,
),
),
];
await
tester
.
pumpWidget
(
boilerplate
(
slivers
,
controller:
controller
));
const
BoxDecoration
amberBox
=
BoxDecoration
(
color:
Colors
.
amber
);
// Scroll to bottom
controller
.
jumpTo
(
controller
.
position
.
maxScrollExtent
);
await
tester
.
pump
();
// End of list
expect
(
find
.
byKey
(
key
),
findsNothing
);
expect
(
tester
.
widgetList
<
DecoratedBox
>(
find
.
byType
(
DecoratedBox
)).
last
.
decoration
,
amberBox
,
);
// Overscroll
await
tester
.
drag
(
find
.
byType
(
Scrollable
),
const
Offset
(
0.0
,
-
50.0
));
await
tester
.
pump
();
expect
(
find
.
byKey
(
key
),
findsNothing
);
expect
(
tester
.
widgetList
<
DecoratedBox
>(
find
.
byType
(
DecoratedBox
)).
last
.
decoration
,
amberBox
,
);
});
});
});
});
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment