Use RenderSliverPadding to inset SliverFillViewport (#45432)
This commit is contained in:
parent
1374a41313
commit
37f9c54116
@ -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
|
|||||||
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';
|
|||||||
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
|
|||||||
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
|
|||||||
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
|
|||||||
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
|
|||||||
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
|
|||||||
|
|
||||||
@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
|
|||||||
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
|
|||||||
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
|
|||||||
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
|
|||||||
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
|
|||||||
}
|
}
|
||||||
|
|
||||||
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
|
|||||||
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 {
|
|||||||
}) : 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 {
|
|||||||
/// * [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 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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,9 +1040,40 @@ 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,
|
||||||
|
@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,
|
Key key,
|
||||||
@required SliverChildDelegate delegate,
|
@required SliverChildDelegate delegate,
|
||||||
this.viewportFraction = 1.0,
|
this.viewportFraction = 1.0,
|
||||||
@ -1048,11 +1081,6 @@ class SliverFillViewport extends SliverMultiBoxAdaptorWidget {
|
|||||||
assert(viewportFraction > 0.0),
|
assert(viewportFraction > 0.0),
|
||||||
super(key: key, delegate: delegate);
|
super(key: key, delegate: delegate);
|
||||||
|
|
||||||
/// 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;
|
final double viewportFraction;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -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() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
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() {
|
|||||||
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,7 @@ 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'
|
' │ 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'
|
||||||
@ -76,58 +76,71 @@ void main() {
|
|||||||
' │ geometry: SliverGeometry(scrollExtent: 12000.0, paintExtent:\n'
|
' │ geometry: SliverGeometry(scrollExtent: 12000.0, paintExtent:\n'
|
||||||
' │ 600.0, maxPaintExtent: 12000.0, hasVisualOverflow: true,\n'
|
' │ 600.0, maxPaintExtent: 12000.0, hasVisualOverflow: true,\n'
|
||||||
' │ cacheExtent: 850.0)\n'
|
' │ cacheExtent: 850.0)\n'
|
||||||
' │ currently live children: 0 to 1\n'
|
|
||||||
' │\n'
|
' │\n'
|
||||||
' ├─child with index 0: RenderRepaintBoundary#00000\n'
|
' └─child: RenderSliverFillViewport#00000 relayoutBoundary=up2\n'
|
||||||
' │ │ needs compositing\n'
|
|
||||||
' │ │ parentData: index=0; layoutOffset=0.0\n'
|
|
||||||
' │ │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
|
|
||||||
' │ │ layer: OffsetLayer#00000\n'
|
|
||||||
' │ │ size: Size(800.0, 600.0)\n'
|
|
||||||
' │ │ metrics: 66.7% useful (1 bad vs 2 good)\n'
|
|
||||||
' │ │ diagnosis: insufficient data to draw conclusion (less than five\n'
|
|
||||||
' │ │ repaints)\n'
|
|
||||||
' │ │\n'
|
|
||||||
' │ └─child: RenderParagraph#00000\n'
|
|
||||||
' │ │ parentData: <none> (can use size)\n'
|
|
||||||
' │ │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
|
|
||||||
' │ │ semantics node: SemanticsNode#2\n'
|
|
||||||
' │ │ size: Size(800.0, 600.0)\n'
|
|
||||||
' │ │ textAlign: start\n'
|
|
||||||
' │ │ textDirection: ltr\n'
|
|
||||||
' │ │ softWrap: wrapping at box width\n'
|
|
||||||
' │ │ overflow: clip\n'
|
|
||||||
' │ │ maxLines: unlimited\n'
|
|
||||||
' │ ╘═╦══ text ═══\n'
|
|
||||||
' │ ║ TextSpan:\n'
|
|
||||||
' │ ║ <all styles inherited>\n'
|
|
||||||
' │ ║ "0"\n'
|
|
||||||
' │ ╚═══════════\n'
|
|
||||||
' └─child with index 1: RenderRepaintBoundary#00000\n'
|
|
||||||
' │ needs compositing\n'
|
' │ needs compositing\n'
|
||||||
' │ parentData: index=1; layoutOffset=600.0\n'
|
' │ parentData: paintOffset=Offset(0.0, 0.0) (can use size)\n'
|
||||||
' │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
|
' │ constraints: SliverConstraints(AxisDirection.down,\n'
|
||||||
' │ layer: OffsetLayer#00000 DETACHED\n'
|
' │ GrowthDirection.forward, ScrollDirection.idle, scrollOffset:\n'
|
||||||
' │ size: Size(800.0, 600.0)\n'
|
' │ 0.0, remainingPaintExtent: 600.0, crossAxisExtent: 800.0,\n'
|
||||||
' │ metrics: 50.0% useful (1 bad vs 1 good)\n'
|
' │ crossAxisDirection: AxisDirection.right,\n'
|
||||||
' │ diagnosis: insufficient data to draw conclusion (less than five\n'
|
' │ viewportMainAxisExtent: 600.0, remainingCacheExtent: 850.0\n'
|
||||||
' │ repaints)\n'
|
' │ cacheOrigin: 0.0 )\n'
|
||||||
|
' │ geometry: SliverGeometry(scrollExtent: 12000.0, paintExtent:\n'
|
||||||
|
' │ 600.0, maxPaintExtent: 12000.0, hasVisualOverflow: true,\n'
|
||||||
|
' │ cacheExtent: 850.0)\n'
|
||||||
|
' │ currently live children: 0 to 1\n'
|
||||||
' │\n'
|
' │\n'
|
||||||
' └─child: RenderParagraph#00000\n'
|
' ├─child with index 0: RenderRepaintBoundary#00000\n'
|
||||||
' │ parentData: <none> (can use size)\n'
|
' │ │ needs compositing\n'
|
||||||
|
' │ │ parentData: index=0; layoutOffset=0.0\n'
|
||||||
|
' │ │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
|
||||||
|
' │ │ layer: OffsetLayer#00000\n'
|
||||||
|
' │ │ size: Size(800.0, 600.0)\n'
|
||||||
|
' │ │ metrics: 66.7% useful (1 bad vs 2 good)\n'
|
||||||
|
' │ │ diagnosis: insufficient data to draw conclusion (less than five\n'
|
||||||
|
' │ │ repaints)\n'
|
||||||
|
' │ │\n'
|
||||||
|
' │ └─child: RenderParagraph#00000\n'
|
||||||
|
' │ │ parentData: <none> (can use size)\n'
|
||||||
|
' │ │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
|
||||||
|
' │ │ semantics node: SemanticsNode#2\n'
|
||||||
|
' │ │ size: Size(800.0, 600.0)\n'
|
||||||
|
' │ │ textAlign: start\n'
|
||||||
|
' │ │ textDirection: ltr\n'
|
||||||
|
' │ │ softWrap: wrapping at box width\n'
|
||||||
|
' │ │ overflow: clip\n'
|
||||||
|
' │ │ maxLines: unlimited\n'
|
||||||
|
' │ ╘═╦══ text ═══\n'
|
||||||
|
' │ ║ TextSpan:\n'
|
||||||
|
' │ ║ <all styles inherited>\n'
|
||||||
|
' │ ║ "0"\n'
|
||||||
|
' │ ╚═══════════\n'
|
||||||
|
' └─child with index 1: RenderRepaintBoundary#00000\n'
|
||||||
|
' │ needs compositing\n'
|
||||||
|
' │ parentData: index=1; layoutOffset=600.0\n'
|
||||||
' │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
|
' │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
|
||||||
' │ semantics node: SemanticsNode#3\n'
|
' │ layer: OffsetLayer#00000 DETACHED\n'
|
||||||
' │ size: Size(800.0, 600.0)\n'
|
' │ size: Size(800.0, 600.0)\n'
|
||||||
' │ textAlign: start\n'
|
' │ metrics: 50.0% useful (1 bad vs 1 good)\n'
|
||||||
' │ textDirection: ltr\n'
|
' │ diagnosis: insufficient data to draw conclusion (less than five\n'
|
||||||
' │ softWrap: wrapping at box width\n'
|
' │ repaints)\n'
|
||||||
' │ overflow: clip\n'
|
' │\n'
|
||||||
' │ maxLines: unlimited\n'
|
' └─child: RenderParagraph#00000\n'
|
||||||
' ╘═╦══ text ═══\n'
|
' │ parentData: <none> (can use size)\n'
|
||||||
' ║ TextSpan:\n'
|
' │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
|
||||||
' ║ <all styles inherited>\n'
|
' │ semantics node: SemanticsNode#3\n'
|
||||||
' ║ "1"\n'
|
' │ size: Size(800.0, 600.0)\n'
|
||||||
' ╚═══════════\n'
|
' │ textAlign: start\n'
|
||||||
|
' │ textDirection: ltr\n'
|
||||||
|
' │ softWrap: wrapping at box width\n'
|
||||||
|
' │ overflow: clip\n'
|
||||||
|
' │ maxLines: unlimited\n'
|
||||||
|
' ╘═╦══ text ═══\n'
|
||||||
|
' ║ TextSpan:\n'
|
||||||
|
' ║ <all styles inherited>\n'
|
||||||
|
' ║ "1"\n'
|
||||||
|
' ╚═══════════\n'
|
||||||
''
|
''
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user