Unverified Commit 37f9c541 authored by LongCatIsLooong's avatar LongCatIsLooong Committed by GitHub

Use RenderSliverPadding to inset SliverFillViewport (#45432)

parent 1374a413
...@@ -57,41 +57,6 @@ class RenderSliverFillViewport extends RenderSliverFixedExtentBoxAdaptor { ...@@ -57,41 +57,6 @@ class RenderSliverFillViewport extends RenderSliverFixedExtentBoxAdaptor {
_viewportFraction = value; _viewportFraction = value;
markNeedsLayout(); markNeedsLayout();
} }
double get _padding => (1.0 - viewportFraction) * constraints.viewportMainAxisExtent * 0.5;
@override
double indexToLayoutOffset(double itemExtent, int index) {
return _padding + super.indexToLayoutOffset(itemExtent, index);
}
@override
int getMinChildIndexForScrollOffset(double scrollOffset, double itemExtent) {
return super.getMinChildIndexForScrollOffset(math.max(scrollOffset - _padding, 0.0), itemExtent);
}
@override
int getMaxChildIndexForScrollOffset(double scrollOffset, double itemExtent) {
return super.getMaxChildIndexForScrollOffset(math.max(scrollOffset - _padding, 0.0), itemExtent);
}
@override
double estimateMaxScrollOffset(
SliverConstraints constraints, {
int firstIndex,
int lastIndex,
double leadingScrollOffset,
double trailingScrollOffset,
}) {
final double padding = _padding;
return childManager.estimateMaxScrollOffset(
constraints,
firstIndex: firstIndex,
lastIndex: lastIndex,
leadingScrollOffset: leadingScrollOffset - padding,
trailingScrollOffset: trailingScrollOffset - padding,
) + padding + padding;
}
} }
/// A sliver that contains a single box child that fills the remaining space in /// A sliver that contains a single box child that fills the remaining space in
......
...@@ -195,7 +195,7 @@ abstract class RenderSliverFixedExtentBoxAdaptor extends RenderSliverMultiBoxAda ...@@ -195,7 +195,7 @@ abstract class RenderSliverFixedExtentBoxAdaptor extends RenderSliverMultiBoxAda
if (firstChild == null) { if (firstChild == null) {
if (!addInitialChild(index: firstIndex, layoutOffset: indexToLayoutOffset(itemExtent, firstIndex))) { if (!addInitialChild(index: firstIndex, layoutOffset: indexToLayoutOffset(itemExtent, firstIndex))) {
// There are either no children, or we are past the end of all our children. // There are either no children, or we are past the end of all our children.
// If it is the later, we will need to find the first available child. // If it is the latter, we will need to find the first available child.
double max; double max;
if (childManager.childCount != null) { if (childManager.childCount != null) {
max = computeMaxScrollOffset(constraints, itemExtent); max = computeMaxScrollOffset(constraints, itemExtent);
......
...@@ -12,73 +12,27 @@ import 'debug.dart'; ...@@ -12,73 +12,27 @@ import 'debug.dart';
import 'object.dart'; import 'object.dart';
import 'sliver.dart'; import 'sliver.dart';
/// Inset a [RenderSliver], applying padding on each side. /// Insets a [RenderSliver] by applying [resolvedPadding] on each side.
/// ///
/// A [RenderSliverPadding] object wraps the [SliverGeometry.layoutExtent] of /// A [RenderSliverEdgeInsetsPadding] subclass wraps the [SliverGeometry.layoutExtent]
/// its child. Any incoming [SliverConstraints.overlap] is ignored and not /// of its child. Any incoming [SliverConstraints.overlap] is ignored and not
/// passed on to the child. /// passed on to the child.
/// ///
/// {@template flutter.rendering.sliverPadding.limitation}
/// Applying padding to anything but the most mundane sliver is likely to have /// Applying padding to anything but the most mundane sliver is likely to have
/// undesired effects. For example, wrapping a /// undesired effects. For example, wrapping a [RenderSliverPinnedPersistentHeader]
/// [RenderSliverPinnedPersistentHeader] will cause the app bar to overlap /// will cause the app bar to overlap earlier slivers (contrary to the normal
/// earlier slivers (contrary to the normal behavior of pinned app bars), and /// behavior of pinned app bars), and while the app bar is pinned, the padding
/// while the app bar is pinned, the padding will scroll away. /// will scroll away.
class RenderSliverPadding extends RenderSliver with RenderObjectWithChildMixin<RenderSliver> { /// {@endtemplate}
/// Creates a render object that insets its child in a viewport. abstract class RenderSliverEdgeInsetsPadding extends RenderSliver with RenderObjectWithChildMixin<RenderSliver> {
///
/// The [padding] argument must not be null and must have non-negative insets.
RenderSliverPadding({
@required EdgeInsetsGeometry padding,
TextDirection textDirection,
RenderSliver child,
}) : assert(padding != null),
assert(padding.isNonNegative),
_padding = padding,
_textDirection = textDirection {
this.child = child;
}
EdgeInsets _resolvedPadding;
void _resolve() {
if (_resolvedPadding != null)
return;
_resolvedPadding = padding.resolve(textDirection);
assert(_resolvedPadding.isNonNegative);
}
void _markNeedResolution() {
_resolvedPadding = null;
markNeedsLayout();
}
/// The amount to pad the child in each dimension. /// The amount to pad the child in each dimension.
/// ///
/// If this is set to an [EdgeInsetsDirectional] object, then [textDirection] /// The offsets are specified in terms of visual edges, left, top, right, and
/// must not be null. /// bottom. These values are not affected by the [TextDirection].
EdgeInsetsGeometry get padding => _padding;
EdgeInsetsGeometry _padding;
set padding(EdgeInsetsGeometry value) {
assert(value != null);
assert(padding.isNonNegative);
if (_padding == value)
return;
_padding = value;
_markNeedResolution();
}
/// The text direction with which to resolve [padding].
/// ///
/// This may be changed to null, but only after the [padding] has been changed /// Must not be null or contain negative values when [performLayout] is called.
/// to a value that does not depend on the direction. EdgeInsets get resolvedPadding;
TextDirection get textDirection => _textDirection;
TextDirection _textDirection;
set textDirection(TextDirection value) {
if (_textDirection == value)
return;
_textDirection = value;
_markNeedResolution();
}
/// The padding in the scroll direction on the side nearest the 0.0 scroll direction. /// The padding in the scroll direction on the side nearest the 0.0 scroll direction.
/// ///
...@@ -88,16 +42,16 @@ class RenderSliverPadding extends RenderSliver with RenderObjectWithChildMixin<R ...@@ -88,16 +42,16 @@ class RenderSliverPadding extends RenderSliver with RenderObjectWithChildMixin<R
assert(constraints != null); assert(constraints != null);
assert(constraints.axisDirection != null); assert(constraints.axisDirection != null);
assert(constraints.growthDirection != null); assert(constraints.growthDirection != null);
assert(_resolvedPadding != null); assert(resolvedPadding != null);
switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) { switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) {
case AxisDirection.up: case AxisDirection.up:
return _resolvedPadding.bottom; return resolvedPadding.bottom;
case AxisDirection.right: case AxisDirection.right:
return _resolvedPadding.left; return resolvedPadding.left;
case AxisDirection.down: case AxisDirection.down:
return _resolvedPadding.top; return resolvedPadding.top;
case AxisDirection.left: case AxisDirection.left:
return _resolvedPadding.right; return resolvedPadding.right;
} }
return null; return null;
} }
...@@ -110,16 +64,16 @@ class RenderSliverPadding extends RenderSliver with RenderObjectWithChildMixin<R ...@@ -110,16 +64,16 @@ class RenderSliverPadding extends RenderSliver with RenderObjectWithChildMixin<R
assert(constraints != null); assert(constraints != null);
assert(constraints.axisDirection != null); assert(constraints.axisDirection != null);
assert(constraints.growthDirection != null); assert(constraints.growthDirection != null);
assert(_resolvedPadding != null); assert(resolvedPadding != null);
switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) { switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) {
case AxisDirection.up: case AxisDirection.up:
return _resolvedPadding.top; return resolvedPadding.top;
case AxisDirection.right: case AxisDirection.right:
return _resolvedPadding.right; return resolvedPadding.right;
case AxisDirection.down: case AxisDirection.down:
return _resolvedPadding.bottom; return resolvedPadding.bottom;
case AxisDirection.left: case AxisDirection.left:
return _resolvedPadding.left; return resolvedPadding.left;
} }
return null; return null;
} }
...@@ -133,8 +87,8 @@ class RenderSliverPadding extends RenderSliver with RenderObjectWithChildMixin<R ...@@ -133,8 +87,8 @@ class RenderSliverPadding extends RenderSliver with RenderObjectWithChildMixin<R
double get mainAxisPadding { double get mainAxisPadding {
assert(constraints != null); assert(constraints != null);
assert(constraints.axis != null); assert(constraints.axis != null);
assert(_resolvedPadding != null); assert(resolvedPadding != null);
return _resolvedPadding.along(constraints.axis); return resolvedPadding.along(constraints.axis);
} }
/// The total padding in the cross-axis direction. (In other words, for a /// The total padding in the cross-axis direction. (In other words, for a
...@@ -146,12 +100,12 @@ class RenderSliverPadding extends RenderSliver with RenderObjectWithChildMixin<R ...@@ -146,12 +100,12 @@ class RenderSliverPadding extends RenderSliver with RenderObjectWithChildMixin<R
double get crossAxisPadding { double get crossAxisPadding {
assert(constraints != null); assert(constraints != null);
assert(constraints.axis != null); assert(constraints.axis != null);
assert(_resolvedPadding != null); assert(resolvedPadding != null);
switch (constraints.axis) { switch (constraints.axis) {
case Axis.horizontal: case Axis.horizontal:
return _resolvedPadding.vertical; return resolvedPadding.vertical;
case Axis.vertical: case Axis.vertical:
return _resolvedPadding.horizontal; return resolvedPadding.horizontal;
} }
return null; return null;
} }
...@@ -164,8 +118,7 @@ class RenderSliverPadding extends RenderSliver with RenderObjectWithChildMixin<R ...@@ -164,8 +118,7 @@ class RenderSliverPadding extends RenderSliver with RenderObjectWithChildMixin<R
@override @override
void performLayout() { void performLayout() {
_resolve(); assert(resolvedPadding != null);
assert(_resolvedPadding != null);
final double beforePadding = this.beforePadding; final double beforePadding = this.beforePadding;
final double afterPadding = this.afterPadding; final double afterPadding = this.afterPadding;
final double mainAxisPadding = this.mainAxisPadding; final double mainAxisPadding = this.mainAxisPadding;
...@@ -240,16 +193,16 @@ class RenderSliverPadding extends RenderSliver with RenderObjectWithChildMixin<R ...@@ -240,16 +193,16 @@ class RenderSliverPadding extends RenderSliver with RenderObjectWithChildMixin<R
assert(constraints.growthDirection != null); assert(constraints.growthDirection != null);
switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) { switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) {
case AxisDirection.up: case AxisDirection.up:
childParentData.paintOffset = Offset(_resolvedPadding.left, calculatePaintOffset(constraints, from: _resolvedPadding.bottom + childLayoutGeometry.scrollExtent, to: _resolvedPadding.bottom + childLayoutGeometry.scrollExtent + _resolvedPadding.top)); childParentData.paintOffset = Offset(resolvedPadding.left, calculatePaintOffset(constraints, from: resolvedPadding.bottom + childLayoutGeometry.scrollExtent, to: resolvedPadding.bottom + childLayoutGeometry.scrollExtent + resolvedPadding.top));
break; break;
case AxisDirection.right: case AxisDirection.right:
childParentData.paintOffset = Offset(calculatePaintOffset(constraints, from: 0.0, to: _resolvedPadding.left), _resolvedPadding.top); childParentData.paintOffset = Offset(calculatePaintOffset(constraints, from: 0.0, to: resolvedPadding.left), resolvedPadding.top);
break; break;
case AxisDirection.down: case AxisDirection.down:
childParentData.paintOffset = Offset(_resolvedPadding.left, calculatePaintOffset(constraints, from: 0.0, to: _resolvedPadding.top)); childParentData.paintOffset = Offset(resolvedPadding.left, calculatePaintOffset(constraints, from: 0.0, to: resolvedPadding.top));
break; break;
case AxisDirection.left: case AxisDirection.left:
childParentData.paintOffset = Offset(calculatePaintOffset(constraints, from: _resolvedPadding.right + childLayoutGeometry.scrollExtent, to: _resolvedPadding.right + childLayoutGeometry.scrollExtent + _resolvedPadding.left), _resolvedPadding.top); childParentData.paintOffset = Offset(calculatePaintOffset(constraints, from: resolvedPadding.right + childLayoutGeometry.scrollExtent, to: resolvedPadding.right + childLayoutGeometry.scrollExtent + resolvedPadding.left), resolvedPadding.top);
break; break;
} }
assert(childParentData.paintOffset != null); assert(childParentData.paintOffset != null);
...@@ -289,14 +242,14 @@ class RenderSliverPadding extends RenderSliver with RenderObjectWithChildMixin<R ...@@ -289,14 +242,14 @@ class RenderSliverPadding extends RenderSliver with RenderObjectWithChildMixin<R
assert(constraints != null); assert(constraints != null);
assert(constraints.axisDirection != null); assert(constraints.axisDirection != null);
assert(constraints.growthDirection != null); assert(constraints.growthDirection != null);
assert(_resolvedPadding != null); assert(resolvedPadding != null);
switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) { switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) {
case AxisDirection.up: case AxisDirection.up:
case AxisDirection.down: case AxisDirection.down:
return _resolvedPadding.left; return resolvedPadding.left;
case AxisDirection.left: case AxisDirection.left:
case AxisDirection.right: case AxisDirection.right:
return _resolvedPadding.top; return resolvedPadding.top;
} }
return null; return null;
} }
...@@ -346,6 +299,79 @@ class RenderSliverPadding extends RenderSliver with RenderObjectWithChildMixin<R ...@@ -346,6 +299,79 @@ class RenderSliverPadding extends RenderSliver with RenderObjectWithChildMixin<R
return true; return true;
}()); }());
} }
}
/// Insets a [RenderSliver], applying padding on each side.
///
/// A [RenderSliverPadding] object wraps the [SliverGeometry.layoutExtent] of
/// its child. Any incoming [SliverConstraints.overlap] is ignored and not
/// passed on to the child.
///
/// {@macro flutter.rendering.sliverPadding.limitation}
class RenderSliverPadding extends RenderSliverEdgeInsetsPadding {
/// Creates a render object that insets its child in a viewport.
///
/// The [padding] argument must not be null and must have non-negative insets.
RenderSliverPadding({
@required EdgeInsetsGeometry padding,
TextDirection textDirection,
RenderSliver child,
}) : assert(padding != null),
assert(padding.isNonNegative),
_padding = padding,
_textDirection = textDirection {
this.child = child;
}
@override
EdgeInsets get resolvedPadding => _resolvedPadding;
EdgeInsets _resolvedPadding;
void _resolve() {
if (resolvedPadding != null)
return;
_resolvedPadding = padding.resolve(textDirection);
assert(resolvedPadding.isNonNegative);
}
void _markNeedsResolution() {
_resolvedPadding = null;
markNeedsLayout();
}
/// The amount to pad the child in each dimension.
///
/// If this is set to an [EdgeInsetsDirectional] object, then [textDirection]
/// must not be null.
EdgeInsetsGeometry get padding => _padding;
EdgeInsetsGeometry _padding;
set padding(EdgeInsetsGeometry value) {
assert(value != null);
assert(padding.isNonNegative);
if (_padding == value)
return;
_padding = value;
_markNeedsResolution();
}
/// The text direction with which to resolve [padding].
///
/// This may be changed to null, but only after the [padding] has been changed
/// to a value that does not depend on the direction.
TextDirection get textDirection => _textDirection;
TextDirection _textDirection;
set textDirection(TextDirection value) {
if (_textDirection == value)
return;
_textDirection = value;
_markNeedsResolution();
}
@override
void performLayout() {
_resolve();
super.performLayout();
}
@override @override
void debugFillProperties(DiagnosticPropertiesBuilder properties) { void debugFillProperties(DiagnosticPropertiesBuilder properties) {
......
...@@ -345,8 +345,16 @@ class _PagePosition extends ScrollPositionWithSingleContext implements PageMetri ...@@ -345,8 +345,16 @@ class _PagePosition extends ScrollPositionWithSingleContext implements PageMetri
forcePixels(getPixelsFromPage(oldPage)); forcePixels(getPixelsFromPage(oldPage));
} }
// The amount of offset that will be added to [minScrollExtent] and subtracted
// from [maxScrollExtent], such that every page will properly snap to the center
// of the viewport when viewportFraction is greater than 1.
//
// The value is 0 if viewportFraction is less than or equal to 1, larger than 0
// otherwise.
double get _initialPageOffset => math.max(0, viewportDimension * (viewportFraction - 1) / 2);
double getPageFromPixels(double pixels, double viewportDimension) { double getPageFromPixels(double pixels, double viewportDimension) {
final double actual = math.max(0.0, pixels) / math.max(1.0, viewportDimension * viewportFraction); final double actual = math.max(0.0, pixels - _initialPageOffset) / math.max(1.0, viewportDimension * viewportFraction);
final double round = actual.roundToDouble(); final double round = actual.roundToDouble();
if ((actual - round).abs() < precisionErrorTolerance) { if ((actual - round).abs() < precisionErrorTolerance) {
return round; return round;
...@@ -355,7 +363,7 @@ class _PagePosition extends ScrollPositionWithSingleContext implements PageMetri ...@@ -355,7 +363,7 @@ class _PagePosition extends ScrollPositionWithSingleContext implements PageMetri
} }
double getPixelsFromPage(double page) { double getPixelsFromPage(double page) {
return page * viewportDimension * viewportFraction; return page * viewportDimension * viewportFraction + _initialPageOffset;
} }
@override @override
...@@ -396,6 +404,15 @@ class _PagePosition extends ScrollPositionWithSingleContext implements PageMetri ...@@ -396,6 +404,15 @@ class _PagePosition extends ScrollPositionWithSingleContext implements PageMetri
return result; return result;
} }
@override
bool applyContentDimensions(double minScrollExtent, double maxScrollExtent) {
final double newMinScrollExtent = minScrollExtent + _initialPageOffset;
return super.applyContentDimensions(
newMinScrollExtent,
math.max(newMinScrollExtent, maxScrollExtent - _initialPageOffset),
);
}
@override @override
PageMetrics copyWith({ PageMetrics copyWith({
double minScrollExtent, double minScrollExtent,
......
...@@ -725,6 +725,7 @@ abstract class SliverMultiBoxAdaptorWidget extends SliverWithKeepAliveWidget { ...@@ -725,6 +725,7 @@ abstract class SliverMultiBoxAdaptorWidget extends SliverWithKeepAliveWidget {
}) : assert(delegate != null), }) : assert(delegate != null),
super(key: key); super(key: key);
/// {@template flutter.widgets.sliverMultiBoxAdaptor.delegate}
/// The delegate that provides the children for this widget. /// The delegate that provides the children for this widget.
/// ///
/// The children are constructed lazily using this delegate to avoid creating /// The children are constructed lazily using this delegate to avoid creating
...@@ -735,6 +736,7 @@ abstract class SliverMultiBoxAdaptorWidget extends SliverWithKeepAliveWidget { ...@@ -735,6 +736,7 @@ abstract class SliverMultiBoxAdaptorWidget extends SliverWithKeepAliveWidget {
/// * [SliverChildBuilderDelegate] and [SliverChildListDelegate], which are /// * [SliverChildBuilderDelegate] and [SliverChildListDelegate], which are
/// commonly used subclasses of [SliverChildDelegate] that use a builder /// commonly used subclasses of [SliverChildDelegate] that use a builder
/// callback and an explicit child list, respectively. /// callback and an explicit child list, respectively.
/// {@endtemplate}
final SliverChildDelegate delegate; final SliverChildDelegate delegate;
@override @override
...@@ -1023,7 +1025,7 @@ class SliverGrid extends SliverMultiBoxAdaptorWidget { ...@@ -1023,7 +1025,7 @@ class SliverGrid extends SliverMultiBoxAdaptorWidget {
} }
} }
/// A sliver that contains a multiple box children that each fill the viewport. /// A sliver that contains multiple box children that each fills the viewport.
/// ///
/// [SliverFillViewport] places its children in a linear array along the main /// [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. Each child is sized to fill the viewport, both in the main and cross
...@@ -1038,15 +1040,15 @@ class SliverGrid extends SliverMultiBoxAdaptorWidget { ...@@ -1038,15 +1040,15 @@ class SliverGrid extends SliverMultiBoxAdaptorWidget {
/// the main axis extent of each item. /// the main axis extent of each item.
/// * [SliverList], which does not require its children to have the same /// * [SliverList], which does not require its children to have the same
/// extent in the main axis. /// extent in the main axis.
class SliverFillViewport extends SliverMultiBoxAdaptorWidget { class SliverFillViewport extends StatelessWidget {
/// Creates a sliver whose box children that each fill the viewport. /// Creates a sliver whose box children that each fill the viewport.
const SliverFillViewport({ const SliverFillViewport({
Key key, Key key,
@required SliverChildDelegate delegate, @required this.delegate,
this.viewportFraction = 1.0, this.viewportFraction = 1.0,
}) : assert(viewportFraction != null), }) : assert(viewportFraction != null),
assert(viewportFraction > 0.0), assert(viewportFraction > 0.0),
super(key: key, delegate: delegate); super(key: key);
/// The fraction of the viewport that each child should fill in the main axis. /// The fraction of the viewport that each child should fill in the main axis.
/// ///
...@@ -1055,6 +1057,32 @@ class SliverFillViewport extends SliverMultiBoxAdaptorWidget { ...@@ -1055,6 +1057,32 @@ class SliverFillViewport extends SliverMultiBoxAdaptorWidget {
/// the viewport in the main axis. /// the viewport in the main axis.
final double viewportFraction; 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 @override
RenderSliverFillViewport createRenderObject(BuildContext context) { RenderSliverFillViewport createRenderObject(BuildContext context) {
final SliverMultiBoxAdaptorElement element = context; final SliverMultiBoxAdaptorElement element = context;
...@@ -1067,6 +1095,77 @@ class SliverFillViewport extends SliverMultiBoxAdaptorWidget { ...@@ -1067,6 +1095,77 @@ class SliverFillViewport extends SliverMultiBoxAdaptorWidget {
} }
} }
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();
}
}
/// An element that lazily builds children for a [SliverMultiBoxAdaptorWidget]. /// An element that lazily builds children for a [SliverMultiBoxAdaptorWidget].
/// ///
/// Implements [RenderSliverBoxChildManager], which lets this element manage /// Implements [RenderSliverBoxChildManager], which lets this element manage
......
...@@ -532,8 +532,7 @@ void main() { ...@@ -532,8 +532,7 @@ void main() {
}); });
testWidgets('PageView large viewportFraction', (WidgetTester tester) async { testWidgets('PageView large viewportFraction', (WidgetTester tester) async {
final PageController controller = final PageController controller = PageController(viewportFraction: 5/4);
PageController(viewportFraction: 5/4);
Widget build(PageController controller) { Widget build(PageController controller) {
return Directionality( return Directionality(
...@@ -601,6 +600,89 @@ void main() { ...@@ -601,6 +600,89 @@ void main() {
expect(tester.getTopLeft(find.text('Hawaii')), const Offset(-(4 - 1) * 800 / 2, 0)); expect(tester.getTopLeft(find.text('Hawaii')), const Offset(-(4 - 1) * 800 / 2, 0));
}); });
testWidgets(
'PageView large viewportFraction can scroll to the last page and snap',
(WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/45096.
final PageController controller = PageController(viewportFraction: 5/4);
Widget build(PageController controller) {
return Directionality(
textDirection: TextDirection.ltr,
child: PageView.builder(
controller: controller,
itemCount: 3,
itemBuilder: (BuildContext context, int index) {
return Container(
height: 200.0,
color: index % 2 == 0
? const Color(0xFF0000FF)
: const Color(0xFF00FF00),
child: Text(index.toString()),
);
},
),
);
}
await tester.pumpWidget(build(controller));
expect(tester.getCenter(find.text('0')), const Offset(400, 300));
controller.jumpToPage(2);
await tester.pump();
await tester.pumpAndSettle();
expect(tester.getCenter(find.text('2')), const Offset(400, 300));
});
testWidgets(
'All visible pages are able to receive touch events',
(WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/23873.
final PageController controller = PageController(viewportFraction: 1/4, initialPage: 0);
int tappedIndex;
Widget build() {
return Directionality(
textDirection: TextDirection.ltr,
child: PageView.builder(
controller: controller,
itemCount: 20,
itemBuilder: (BuildContext context, int index) {
return GestureDetector(
onTap: () => tappedIndex = index,
child: SizedBox.expand(child: Text('$index')),
);
},
),
);
}
Iterable<int> visiblePages = const <int> [0, 1, 2];
await tester.pumpWidget(build());
// The first 3 items should be visible and tappable.
for (int index in visiblePages) {
expect(find.text(index.toString()), findsOneWidget);
// The center of page 2's x-coordinate is 800, so we have to manually
// offset it a bit to make sure the tap lands within the screen.
final Offset center = tester.getCenter(find.text('$index')) - const Offset(3, 0);
await tester.tapAt(center);
expect(tappedIndex, index);
}
controller.jumpToPage(19);
await tester.pump();
// The last 3 items should be visible and tappable.
visiblePages = const <int> [17, 18, 19];
for (int index in visiblePages) {
expect(find.text('$index'), findsOneWidget);
await tester.tap(find.text('$index'));
expect(tappedIndex, index);
}
});
testWidgets('PageView does not report page changed on overscroll', (WidgetTester tester) async { testWidgets('PageView does not report page changed on overscroll', (WidgetTester tester) async {
final PageController controller = PageController( final PageController controller = PageController(
initialPage: kStates.length - 1, initialPage: kStates.length - 1,
......
...@@ -64,7 +64,20 @@ void main() { ...@@ -64,7 +64,20 @@ void main() {
expect( expect(
viewport.toStringDeep(minLevel: DiagnosticLevel.info), viewport.toStringDeep(minLevel: DiagnosticLevel.info),
equalsIgnoringHashCodes( equalsIgnoringHashCodes(
'RenderSliverFillViewport#00000 relayoutBoundary=up1\n' '_RenderSliverFractionalPadding#00000 relayoutBoundary=up1\n'
' │ needs compositing\n'
' │ parentData: paintOffset=Offset(0.0, 0.0) (can use size)\n'
' │ constraints: SliverConstraints(AxisDirection.down,\n'
' │ GrowthDirection.forward, ScrollDirection.idle, scrollOffset:\n'
' │ 0.0, remainingPaintExtent: 600.0, crossAxisExtent: 800.0,\n'
' │ crossAxisDirection: AxisDirection.right,\n'
' │ viewportMainAxisExtent: 600.0, remainingCacheExtent: 850.0\n'
' │ cacheOrigin: 0.0 )\n'
' │ geometry: SliverGeometry(scrollExtent: 12000.0, paintExtent:\n'
' │ 600.0, maxPaintExtent: 12000.0, hasVisualOverflow: true,\n'
' │ cacheExtent: 850.0)\n'
' │\n'
' └─child: RenderSliverFillViewport#00000 relayoutBoundary=up2\n'
' │ needs compositing\n' ' │ needs compositing\n'
' │ parentData: paintOffset=Offset(0.0, 0.0) (can use size)\n' ' │ parentData: paintOffset=Offset(0.0, 0.0) (can use size)\n'
' │ constraints: SliverConstraints(AxisDirection.down,\n' ' │ constraints: SliverConstraints(AxisDirection.down,\n'
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment