SliverFillRemaining accounts for child size when hasScrollBody is false (#35810)
Fixes the hasScrollBody flag not accounting for child size. Adds the ability to specify over-scroll behavior.
This commit is contained in:
parent
252491f8ac
commit
38e41f5aee
@ -7,6 +7,7 @@ import 'dart:math' as math;
|
|||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
import 'box.dart';
|
import 'box.dart';
|
||||||
|
import 'object.dart';
|
||||||
import 'sliver.dart';
|
import 'sliver.dart';
|
||||||
import 'sliver_fixed_extent_list.dart';
|
import 'sliver_fixed_extent_list.dart';
|
||||||
import 'sliver_multi_box_adaptor.dart';
|
import 'sliver_multi_box_adaptor.dart';
|
||||||
@ -114,34 +115,84 @@ class RenderSliverFillRemaining extends RenderSliverSingleBoxAdapter {
|
|||||||
RenderSliverFillRemaining({
|
RenderSliverFillRemaining({
|
||||||
RenderBox child,
|
RenderBox child,
|
||||||
this.hasScrollBody = true,
|
this.hasScrollBody = true,
|
||||||
|
this.fillOverscroll = false,
|
||||||
}) : assert(hasScrollBody != null),
|
}) : assert(hasScrollBody != null),
|
||||||
super(child: child);
|
super(child: child);
|
||||||
|
|
||||||
/// Whether the child has a scrollable body, this value cannot be null.
|
/// 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
|
/// Defaults to true such that the child will extend beyond the viewport and
|
||||||
/// scroll, as seen in [NestedScrollView].
|
/// scroll, as seen in [NestedScrollView].
|
||||||
///
|
///
|
||||||
/// Setting this value to false will allow the child to fill the remainder of
|
/// Setting this value to false will allow the child to fill the remainder of
|
||||||
/// the viewport and not extend further.
|
/// 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;
|
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;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void performLayout() {
|
void performLayout() {
|
||||||
final double extent = constraints.remainingPaintExtent
|
double childExtent;
|
||||||
- math.min(constraints.overlap, 0.0)
|
double extent = constraints.viewportMainAxisExtent - constraints.precedingScrollExtent;
|
||||||
// Adding the offset for when this SliverFillRemaining is not scrollable,
|
double maxExtent = constraints.remainingPaintExtent - math.min(constraints.overlap, 0.0);
|
||||||
// so it will stretch to fill on overscroll.
|
|
||||||
+ (hasScrollBody ? 0.0 : constraints.scrollOffset);
|
if (hasScrollBody) {
|
||||||
if (child != null)
|
extent = maxExtent;
|
||||||
child.layout(constraints.asBoxConstraints(minExtent: extent, maxExtent: extent), parentUsesSize: true);
|
if (child != null)
|
||||||
|
child.layout(
|
||||||
|
constraints.asBoxConstraints(
|
||||||
|
minExtent: extent,
|
||||||
|
maxExtent: extent,
|
||||||
|
),
|
||||||
|
parentUsesSize: true,
|
||||||
|
);
|
||||||
|
} else if (child != null) {
|
||||||
|
child.layout(constraints.asBoxConstraints(), parentUsesSize: true);
|
||||||
|
|
||||||
|
switch (constraints.axis) {
|
||||||
|
case Axis.horizontal:
|
||||||
|
childExtent = child.size.width;
|
||||||
|
break;
|
||||||
|
case Axis.vertical:
|
||||||
|
childExtent = child.size.height;
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
final double paintedChildSize = calculatePaintOffset(constraints, from: 0.0, to: extent);
|
||||||
assert(paintedChildSize.isFinite);
|
assert(paintedChildSize.isFinite);
|
||||||
assert(paintedChildSize >= 0.0);
|
assert(paintedChildSize >= 0.0);
|
||||||
geometry = SliverGeometry(
|
geometry = SliverGeometry(
|
||||||
// 0.0 can be applied here for cases when there is not scroll body since
|
scrollExtent: hasScrollBody ? constraints.viewportMainAxisExtent : extent,
|
||||||
// SliverFillRemaining will not have any slivers following it.
|
|
||||||
scrollExtent: hasScrollBody ? constraints.viewportMainAxisExtent : 0.0,
|
|
||||||
paintExtent: paintedChildSize,
|
paintExtent: paintedChildSize,
|
||||||
maxPaintExtent: paintedChildSize,
|
maxPaintExtent: paintedChildSize,
|
||||||
hasVisualOverflow: extent > constraints.remainingPaintExtent || constraints.scrollOffset > 0.0,
|
hasVisualOverflow: extent > constraints.remainingPaintExtent || constraints.scrollOffset > 0.0,
|
||||||
|
@ -1369,23 +1369,44 @@ class SliverFillRemaining extends SingleChildRenderObjectWidget {
|
|||||||
Key key,
|
Key key,
|
||||||
Widget child,
|
Widget child,
|
||||||
this.hasScrollBody = true,
|
this.hasScrollBody = true,
|
||||||
|
this.fillOverscroll = false,
|
||||||
}) : assert(hasScrollBody != null),
|
}) : assert(hasScrollBody != null),
|
||||||
super(key: key, child: child);
|
super(key: key, child: child);
|
||||||
|
|
||||||
/// Whether the child has a scrollable body, this value cannot be null.
|
/// 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
|
/// Defaults to true such that the child will extend beyond the viewport and
|
||||||
/// scroll, as seen in [NestedScrollView].
|
/// scroll, as seen in [NestedScrollView].
|
||||||
///
|
///
|
||||||
/// Setting this value to false will allow the child to fill the remainder of
|
/// Setting this value to false will allow the child to fill the remainder of
|
||||||
/// the viewport and not extend further.
|
/// 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.
|
||||||
final bool hasScrollBody;
|
final bool hasScrollBody;
|
||||||
|
|
||||||
@override
|
/// Indicates whether the child should stretch to fill the overscroll area
|
||||||
RenderSliverFillRemaining createRenderObject(BuildContext context) => RenderSliverFillRemaining(hasScrollBody: hasScrollBody);
|
/// 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
|
@override
|
||||||
void updateRenderObject(BuildContext context, RenderSliverFillRemaining renderObject) => renderObject.hasScrollBody = hasScrollBody;
|
RenderSliverFillRemaining createRenderObject(BuildContext context) {
|
||||||
|
return RenderSliverFillRemaining(
|
||||||
|
hasScrollBody: hasScrollBody,
|
||||||
|
fillOverscroll: fillOverscroll,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void updateRenderObject(BuildContext context, RenderSliverFillRemaining renderObject) {
|
||||||
|
renderObject.hasScrollBody = hasScrollBody;
|
||||||
|
renderObject.fillOverscroll = fillOverscroll;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mark a child as needing to stay alive even when it's in a lazy list that
|
/// Mark a child as needing to stay alive even when it's in a lazy list that
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
@ -64,62 +65,368 @@ void main() {
|
|||||||
expect(tester.renderObject<RenderBox>(find.byType(Container)).size.height, equals(500.0));
|
expect(tester.renderObject<RenderBox>(find.byType(Container)).size.height, equals(500.0));
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('SliverFillRemaining does not extend past viewport.', (WidgetTester tester) async {
|
group('SliverFillRemaining - hasScrollBody', () {
|
||||||
final ScrollController controller = ScrollController();
|
final Widget sliverBox = SliverToBoxAdapter(
|
||||||
await tester.pumpWidget(
|
child: Container(
|
||||||
Directionality(
|
color: Colors.amber,
|
||||||
textDirection: TextDirection.ltr,
|
height: 150.0,
|
||||||
child: CustomScrollView(
|
|
||||||
controller: controller,
|
|
||||||
slivers: <Widget>[
|
|
||||||
SliverToBoxAdapter(
|
|
||||||
child: Container(
|
|
||||||
color: Colors.red,
|
|
||||||
height: 150.0,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SliverFillRemaining(
|
|
||||||
child: Container(color: Colors.white),
|
|
||||||
hasScrollBody: false,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
expect(controller.offset, 0.0);
|
Widget boilerplate(List<Widget> slivers, {ScrollController controller}) {
|
||||||
expect(find.byType(Container), findsNWidgets(2));
|
return MaterialApp(
|
||||||
controller.jumpTo(150.0);
|
home: Scaffold(
|
||||||
await tester.pumpAndSettle();
|
body: CustomScrollView(
|
||||||
expect(controller.offset, 0.0);
|
slivers: slivers,
|
||||||
expect(find.byType(Container), findsNWidgets(2));
|
controller: controller,
|
||||||
});
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
testWidgets('SliverFillRemaining scrolls beyond viewport by default.', (WidgetTester tester) async {
|
testWidgets('does not extend past viewport when false', (WidgetTester tester) async {
|
||||||
final ScrollController controller = ScrollController();
|
final ScrollController controller = ScrollController();
|
||||||
await tester.pumpWidget(
|
final List<Widget> slivers = <Widget>[
|
||||||
Directionality(
|
sliverBox,
|
||||||
textDirection: TextDirection.ltr,
|
SliverFillRemaining(
|
||||||
child: CustomScrollView(
|
child: Container(color: Colors.white),
|
||||||
controller: controller,
|
hasScrollBody: false,
|
||||||
slivers: <Widget>[
|
),
|
||||||
SliverToBoxAdapter(
|
];
|
||||||
child: Container(
|
await tester.pumpWidget(boilerplate(slivers, controller: controller));
|
||||||
color: Colors.red,
|
expect(controller.offset, 0.0);
|
||||||
height: 150.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));
|
||||||
|
final RenderBox box = tester.renderObject<RenderBox>(find.byType(Container).last);
|
||||||
|
expect(box.size.height, equals(450));
|
||||||
|
});
|
||||||
|
|
||||||
|
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: () {},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SliverFillRemaining(
|
),
|
||||||
child: Container(color: Colors.white),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
];
|
||||||
);
|
await tester.pumpWidget(boilerplate(slivers));
|
||||||
expect(controller.offset, 0.0);
|
expect(tester.renderObject<RenderBox>(find.byKey(key)).size.height, equals(450));
|
||||||
expect(find.byType(Container), findsNWidgets(2));
|
|
||||||
controller.jumpTo(150.0);
|
// Also check that the button alignment is true to expectations
|
||||||
await tester.pumpAndSettle();
|
final Finder button = find.byType(RaisedButton);
|
||||||
expect(controller.offset, 150.0);
|
expect(tester.getBottomLeft(button).dy, equals(600.0));
|
||||||
expect(find.byType(Container), findsOneWidget);
|
expect(tester.getCenter(button).dx, equals(400.0));
|
||||||
|
});
|
||||||
|
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
await tester.pumpWidget(boilerplate(slivers));
|
||||||
|
final RenderBox box = tester.renderObject<RenderBox>(find.byType(Container).last);
|
||||||
|
expect(box.size.height, equals(600));
|
||||||
|
});
|
||||||
|
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
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));
|
||||||
|
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));
|
||||||
|
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
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));
|
||||||
|
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));
|
||||||
|
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, 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;
|
||||||
|
|
||||||
|
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));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user