Exposing inner controller of NestedScrollView (#49004)
This commit is contained in:
parent
2dc71a343f
commit
bc8bfb10f8
@ -120,9 +120,10 @@ typedef NestedScrollViewHeaderSliversBuilder = List<Widget> Function(BuildContex
|
||||
/// top: false,
|
||||
/// bottom: false,
|
||||
/// child: Builder(
|
||||
/// // This Builder is needed to provide a BuildContext that is "inside"
|
||||
/// // the NestedScrollView, so that sliverOverlapAbsorberHandleFor() can
|
||||
/// // find the NestedScrollView.
|
||||
/// // This Builder is needed to provide a BuildContext that is
|
||||
/// // "inside" the NestedScrollView, so that
|
||||
/// // sliverOverlapAbsorberHandleFor() can find the
|
||||
/// // NestedScrollView.
|
||||
/// builder: (BuildContext context) {
|
||||
/// return CustomScrollView(
|
||||
/// // The "controller" and "primary" members should be left
|
||||
@ -136,7 +137,8 @@ typedef NestedScrollViewHeaderSliversBuilder = List<Widget> Function(BuildContex
|
||||
/// key: PageStorageKey<String>(name),
|
||||
/// slivers: <Widget>[
|
||||
/// SliverOverlapInjector(
|
||||
/// // This is the flip side of the SliverOverlapAbsorber above.
|
||||
/// // This is the flip side of the SliverOverlapAbsorber
|
||||
/// // above.
|
||||
/// handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||
/// ),
|
||||
/// SliverPadding(
|
||||
@ -268,7 +270,10 @@ class NestedScrollView extends StatefulWidget {
|
||||
/// documentation.
|
||||
static SliverOverlapAbsorberHandle sliverOverlapAbsorberHandleFor(BuildContext context) {
|
||||
final _InheritedNestedScrollView target = context.dependOnInheritedWidgetOfExactType<_InheritedNestedScrollView>();
|
||||
assert(target != null, 'NestedScrollView.sliverOverlapAbsorberHandleFor must be called with a context that contains a NestedScrollView.');
|
||||
assert(
|
||||
target != null,
|
||||
'NestedScrollView.sliverOverlapAbsorberHandleFor must be called with a context that contains a NestedScrollView.',
|
||||
);
|
||||
return target.state._absorberHandle;
|
||||
}
|
||||
|
||||
@ -285,18 +290,93 @@ class NestedScrollView extends StatefulWidget {
|
||||
}
|
||||
|
||||
@override
|
||||
_NestedScrollViewState createState() => _NestedScrollViewState();
|
||||
NestedScrollViewState createState() => NestedScrollViewState();
|
||||
}
|
||||
|
||||
class _NestedScrollViewState extends State<NestedScrollView> {
|
||||
/// The [State] for a [NestedScrollView].
|
||||
///
|
||||
/// The [ScrollController]s, [innerController] and [outerController], of the
|
||||
/// [NestedScrollView]'s children may be accessed through its state. This is
|
||||
/// useful for obtaining respective scroll positions in the [NestedScrollView].
|
||||
///
|
||||
/// If you want to access the inner or outer scroll controller of a
|
||||
/// [NestedScrollView], you can get its [NestedScrollViewState] by supplying a
|
||||
/// `GlobalKey<NestedScrollViewState>` to the [NestedScrollView.key] parameter).
|
||||
///
|
||||
/// {@tool sample --template=stateless_widget_material}
|
||||
/// [NestedScrollViewState] can be obtained using a [GlobalKey].
|
||||
/// Using the following setup, you can access the inner scroll controller
|
||||
/// using `globalKey.currentState.innerController`.
|
||||
///
|
||||
/// ```dart preamble
|
||||
/// final GlobalKey<NestedScrollViewState> globalKey = GlobalKey();
|
||||
/// ```
|
||||
/// ```dart
|
||||
/// @override
|
||||
/// Widget build(BuildContext context) {
|
||||
/// return NestedScrollView(
|
||||
/// key: globalKey,
|
||||
/// headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
|
||||
/// return <Widget>[
|
||||
/// SliverAppBar(
|
||||
/// title: Text('NestedScrollViewState Demo!'),
|
||||
/// ),
|
||||
/// ];
|
||||
/// },
|
||||
/// body: CustomScrollView(
|
||||
/// // Body slivers go here!
|
||||
/// ),
|
||||
/// );
|
||||
/// }
|
||||
///
|
||||
/// ScrollController get innerController {
|
||||
/// return globalKey.currentState.innerController;
|
||||
/// }
|
||||
/// ```
|
||||
/// {@end-tool}
|
||||
class NestedScrollViewState extends State<NestedScrollView> {
|
||||
final SliverOverlapAbsorberHandle _absorberHandle = SliverOverlapAbsorberHandle();
|
||||
|
||||
/// The [ScrollController] provided to the [ScrollView] in
|
||||
/// [NestedScrollView.body].
|
||||
///
|
||||
/// Manipulating the [ScrollPosition] of this controller pushes the outer
|
||||
/// header sliver(s) up and out of view. The position of the [outerController]
|
||||
/// will be set to [ScrollPosition.maxScrollExtent], unless you use
|
||||
/// [ScrollPosition.setPixels].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [outerController], which exposes the [ScrollController] used by the
|
||||
/// the sliver(s) contained in [NestedScrollView.headerSliverBuilder].
|
||||
ScrollController get innerController => _coordinator._innerController;
|
||||
|
||||
/// The [ScrollController] provided to the [ScrollView] in
|
||||
/// [NestedScrollView.headerSliverBuilder].
|
||||
///
|
||||
/// This is equivalent to [NestedScrollView.controller], if provided.
|
||||
///
|
||||
/// Manipulating the [ScrollPosition] of this controller pushes the inner body
|
||||
/// sliver(s) down. The position of the [innerController] will be set to
|
||||
/// [ScrollPosition.minScrollExtent], unless you use
|
||||
/// [ScrollPosition.setPixels]. Visually, the inner body will be scrolled to
|
||||
/// its beginning.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [innerController], which exposes the [ScrollController] used by the
|
||||
/// [ScrollView] contained in [NestedScrollView.body].
|
||||
ScrollController get outerController => _coordinator._outerController;
|
||||
|
||||
_NestedScrollCoordinator _coordinator;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_coordinator = _NestedScrollCoordinator(this, widget.controller, _handleHasScrolledBodyChanged);
|
||||
_coordinator = _NestedScrollCoordinator(
|
||||
this, widget.controller,
|
||||
_handleHasScrolledBodyChanged,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -348,8 +428,8 @@ class _NestedScrollViewState extends State<NestedScrollView> {
|
||||
scrollDirection: widget.scrollDirection,
|
||||
reverse: widget.reverse,
|
||||
physics: widget.physics != null
|
||||
? widget.physics.applyTo(const ClampingScrollPhysics())
|
||||
: const ClampingScrollPhysics(),
|
||||
? widget.physics.applyTo(const ClampingScrollPhysics())
|
||||
: const ClampingScrollPhysics(),
|
||||
controller: _coordinator._outerController,
|
||||
slivers: widget._buildSlivers(
|
||||
context,
|
||||
@ -410,7 +490,7 @@ class _InheritedNestedScrollView extends InheritedWidget {
|
||||
assert(child != null),
|
||||
super(key: key, child: child);
|
||||
|
||||
final _NestedScrollViewState state;
|
||||
final NestedScrollViewState state;
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(_InheritedNestedScrollView old) => state != old.state;
|
||||
@ -469,11 +549,19 @@ typedef _NestedScrollActivityGetter = ScrollActivity Function(_NestedScrollPosit
|
||||
class _NestedScrollCoordinator implements ScrollActivityDelegate, ScrollHoldController {
|
||||
_NestedScrollCoordinator(this._state, this._parent, this._onHasScrolledBodyChanged) {
|
||||
final double initialScrollOffset = _parent?.initialScrollOffset ?? 0.0;
|
||||
_outerController = _NestedScrollController(this, initialScrollOffset: initialScrollOffset, debugLabel: 'outer');
|
||||
_innerController = _NestedScrollController(this, initialScrollOffset: 0.0, debugLabel: 'inner');
|
||||
_outerController = _NestedScrollController(
|
||||
this,
|
||||
initialScrollOffset: initialScrollOffset,
|
||||
debugLabel: 'outer',
|
||||
);
|
||||
_innerController = _NestedScrollController(
|
||||
this,
|
||||
initialScrollOffset: 0.0,
|
||||
debugLabel: 'inner',
|
||||
);
|
||||
}
|
||||
|
||||
final _NestedScrollViewState _state;
|
||||
final NestedScrollViewState _state;
|
||||
ScrollController _parent;
|
||||
final VoidCallback _onHasScrolledBodyChanged;
|
||||
|
||||
@ -550,14 +638,22 @@ class _NestedScrollCoordinator implements ScrollActivityDelegate, ScrollHoldCont
|
||||
|
||||
@override
|
||||
void goIdle() {
|
||||
beginActivity(_createIdleScrollActivity(_outerPosition), _createIdleScrollActivity);
|
||||
beginActivity(
|
||||
_createIdleScrollActivity(_outerPosition),
|
||||
_createIdleScrollActivity,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void goBallistic(double velocity) {
|
||||
beginActivity(
|
||||
createOuterBallisticScrollActivity(velocity),
|
||||
(_NestedScrollPosition position) => createInnerBallisticScrollActivity(position, velocity),
|
||||
(_NestedScrollPosition position) {
|
||||
return createInnerBallisticScrollActivity(
|
||||
position,
|
||||
velocity,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@ -593,7 +689,10 @@ class _NestedScrollCoordinator implements ScrollActivityDelegate, ScrollHoldCont
|
||||
if (innerPosition == null) {
|
||||
// It's either just us or a velocity=0 situation.
|
||||
return _outerPosition.createBallisticScrollActivity(
|
||||
_outerPosition.physics.createBallisticSimulation(_outerPosition, velocity),
|
||||
_outerPosition.physics.createBallisticSimulation(
|
||||
_outerPosition,
|
||||
velocity,
|
||||
),
|
||||
mode: _NestedBallisticScrollActivityMode.independent,
|
||||
);
|
||||
}
|
||||
@ -611,7 +710,9 @@ class _NestedScrollCoordinator implements ScrollActivityDelegate, ScrollHoldCont
|
||||
ScrollActivity createInnerBallisticScrollActivity(_NestedScrollPosition position, double velocity) {
|
||||
return position.createBallisticScrollActivity(
|
||||
position.physics.createBallisticSimulation(
|
||||
velocity == 0 ? position as ScrollMetrics : _getMetrics(position, velocity),
|
||||
velocity == 0
|
||||
? position as ScrollMetrics
|
||||
: _getMetrics(position, velocity),
|
||||
velocity,
|
||||
),
|
||||
mode: _NestedBallisticScrollActivityMode.inner,
|
||||
@ -622,7 +723,10 @@ class _NestedScrollCoordinator implements ScrollActivityDelegate, ScrollHoldCont
|
||||
assert(innerPosition != null);
|
||||
double pixels, minRange, maxRange, correctionOffset, extra;
|
||||
if (innerPosition.pixels == innerPosition.minScrollExtent) {
|
||||
pixels = _outerPosition.pixels.clamp(_outerPosition.minScrollExtent, _outerPosition.maxScrollExtent) as double; // TODO(ianh): gracefully handle out-of-range outer positions
|
||||
pixels = _outerPosition.pixels.clamp(
|
||||
_outerPosition.minScrollExtent,
|
||||
_outerPosition.maxScrollExtent,
|
||||
) as double; // TODO(ianh): gracefully handle out-of-range outer positions
|
||||
minRange = _outerPosition.minScrollExtent;
|
||||
maxRange = _outerPosition.maxScrollExtent;
|
||||
assert(minRange <= maxRange);
|
||||
@ -688,7 +792,10 @@ class _NestedScrollCoordinator implements ScrollActivityDelegate, ScrollHoldCont
|
||||
|
||||
double unnestOffset(double value, _NestedScrollPosition source) {
|
||||
if (source == _outerPosition)
|
||||
return value.clamp(_outerPosition.minScrollExtent, _outerPosition.maxScrollExtent) as double;
|
||||
return value.clamp(
|
||||
_outerPosition.minScrollExtent,
|
||||
_outerPosition.maxScrollExtent,
|
||||
) as double;
|
||||
if (value < source.minScrollExtent)
|
||||
return value - source.minScrollExtent + _outerPosition.minScrollExtent;
|
||||
return value - source.minScrollExtent + _outerPosition.maxScrollExtent;
|
||||
@ -696,7 +803,10 @@ class _NestedScrollCoordinator implements ScrollActivityDelegate, ScrollHoldCont
|
||||
|
||||
double nestOffset(double value, _NestedScrollPosition target) {
|
||||
if (target == _outerPosition)
|
||||
return value.clamp(_outerPosition.minScrollExtent, _outerPosition.maxScrollExtent) as double;
|
||||
return value.clamp(
|
||||
_outerPosition.minScrollExtent,
|
||||
_outerPosition.maxScrollExtent,
|
||||
) as double;
|
||||
if (value < _outerPosition.minScrollExtent)
|
||||
return value - _outerPosition.minScrollExtent + target.minScrollExtent;
|
||||
if (value > _outerPosition.maxScrollExtent)
|
||||
@ -711,7 +821,10 @@ class _NestedScrollCoordinator implements ScrollActivityDelegate, ScrollHoldCont
|
||||
for (final _NestedScrollPosition position in _innerPositions) {
|
||||
if (!position.haveDimensions)
|
||||
return;
|
||||
maxInnerExtent = math.max(maxInnerExtent, position.maxScrollExtent - position.minScrollExtent);
|
||||
maxInnerExtent = math.max(
|
||||
maxInnerExtent,
|
||||
position.maxScrollExtent - position.minScrollExtent,
|
||||
);
|
||||
}
|
||||
_outerPosition.updateCanDrag(maxInnerExtent);
|
||||
}
|
||||
@ -758,7 +871,10 @@ class _NestedScrollCoordinator implements ScrollActivityDelegate, ScrollHoldCont
|
||||
|
||||
ScrollHoldController hold(VoidCallback holdCancelCallback) {
|
||||
beginActivity(
|
||||
HoldScrollActivity(delegate: _outerPosition, onHoldCanceled: holdCancelCallback),
|
||||
HoldScrollActivity(
|
||||
delegate: _outerPosition,
|
||||
onHoldCanceled: holdCancelCallback,
|
||||
),
|
||||
(_NestedScrollPosition position) => HoldScrollActivity(delegate: position),
|
||||
);
|
||||
return this;
|
||||
@ -786,7 +902,9 @@ class _NestedScrollCoordinator implements ScrollActivityDelegate, ScrollHoldCont
|
||||
|
||||
@override
|
||||
void applyUserOffset(double delta) {
|
||||
updateUserScrollDirection(delta > 0.0 ? ScrollDirection.forward : ScrollDirection.reverse);
|
||||
updateUserScrollDirection(
|
||||
delta > 0.0 ? ScrollDirection.forward : ScrollDirection.reverse
|
||||
);
|
||||
assert(delta != 0.0);
|
||||
if (_innerPositions.isEmpty) {
|
||||
_outerPosition.applyFullDragUpdate(delta);
|
||||
@ -805,7 +923,8 @@ class _NestedScrollCoordinator implements ScrollActivityDelegate, ScrollHoldCont
|
||||
}
|
||||
} else {
|
||||
// dragging "down" - delta is positive
|
||||
// prioritize the inner views, so that the inner content will move before the app bar grows
|
||||
// prioritize the inner views, so that the inner content will move before
|
||||
// the app bar grows
|
||||
double outerDelta = 0.0; // it will go positive if it changes
|
||||
final List<double> overscrolls = <double>[];
|
||||
final List<_NestedScrollPosition> innerPositions = _innerPositions.toList();
|
||||
@ -831,7 +950,9 @@ class _NestedScrollCoordinator implements ScrollActivityDelegate, ScrollHoldCont
|
||||
}
|
||||
|
||||
void updateParent() {
|
||||
_outerPosition?.setParent(_parent ?? PrimaryScrollController.of(_state.context));
|
||||
_outerPosition?.setParent(
|
||||
_parent ?? PrimaryScrollController.of(_state.context)
|
||||
);
|
||||
}
|
||||
|
||||
@mustCallSuper
|
||||
@ -984,9 +1105,13 @@ class _NestedScrollPosition extends ScrollPosition implements ScrollActivityDele
|
||||
// One is if the physics allow it, via applyFullDragUpdate (see below). An
|
||||
// overscroll situation can also be forced, e.g. if the scroll position is
|
||||
// artificially set using the scroll controller.
|
||||
final double min = delta < 0.0 ? -double.infinity : math.min(minScrollExtent, pixels);
|
||||
final double min = delta < 0.0
|
||||
? -double.infinity
|
||||
: math.min(minScrollExtent, pixels);
|
||||
// The logic for max is equivalent but on the other side.
|
||||
final double max = delta > 0.0 ? double.infinity : math.max(maxScrollExtent, pixels);
|
||||
final double max = delta > 0.0
|
||||
? double.infinity
|
||||
: math.max(maxScrollExtent, pixels);
|
||||
final double oldPixels = pixels;
|
||||
final double newPixels = (pixels - delta).clamp(min, max) as double;
|
||||
final double clampedDelta = newPixels - pixels;
|
||||
@ -1007,7 +1132,10 @@ class _NestedScrollPosition extends ScrollPosition implements ScrollActivityDele
|
||||
assert(delta != 0.0);
|
||||
final double oldPixels = pixels;
|
||||
// Apply friction:
|
||||
final double newPixels = pixels - physics.applyPhysicsToUserOffset(this, delta);
|
||||
final double newPixels = pixels - physics.applyPhysicsToUserOffset(
|
||||
this,
|
||||
delta,
|
||||
);
|
||||
if (oldPixels == newPixels)
|
||||
return 0.0; // delta must have been so small we dropped it during floating point addition
|
||||
// Check for overscroll:
|
||||
@ -1050,7 +1178,8 @@ class _NestedScrollPosition extends ScrollPosition implements ScrollActivityDele
|
||||
beginActivity(IdleScrollActivity(this));
|
||||
}
|
||||
|
||||
// This is called by activities when they finish their work and want to go ballistic.
|
||||
// This is called by activities when they finish their work and want to go
|
||||
// ballistic.
|
||||
@override
|
||||
void goBallistic(double velocity) {
|
||||
Simulation simulation;
|
||||
@ -1075,9 +1204,20 @@ class _NestedScrollPosition extends ScrollPosition implements ScrollActivityDele
|
||||
assert(metrics != null);
|
||||
if (metrics.minRange == metrics.maxRange)
|
||||
return IdleScrollActivity(this);
|
||||
return _NestedOuterBallisticScrollActivity(coordinator, this, metrics, simulation, context.vsync);
|
||||
return _NestedOuterBallisticScrollActivity(
|
||||
coordinator,
|
||||
this,
|
||||
metrics,
|
||||
simulation,
|
||||
context.vsync,
|
||||
);
|
||||
case _NestedBallisticScrollActivityMode.inner:
|
||||
return _NestedInnerBallisticScrollActivity(coordinator, this, simulation, context.vsync);
|
||||
return _NestedInnerBallisticScrollActivity(
|
||||
coordinator,
|
||||
this,
|
||||
simulation,
|
||||
context.vsync,
|
||||
);
|
||||
case _NestedBallisticScrollActivityMode.independent:
|
||||
return BallisticScrollActivity(this, simulation, context.vsync);
|
||||
}
|
||||
@ -1090,7 +1230,11 @@ class _NestedScrollPosition extends ScrollPosition implements ScrollActivityDele
|
||||
@required Duration duration,
|
||||
@required Curve curve,
|
||||
}) {
|
||||
return coordinator.animateTo(coordinator.unnestOffset(to, this), duration: duration, curve: curve);
|
||||
return coordinator.animateTo(
|
||||
coordinator.unnestOffset(to, this),
|
||||
duration: duration,
|
||||
curve: curve,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -1157,12 +1301,18 @@ class _NestedInnerBallisticScrollActivity extends BallisticScrollActivity {
|
||||
|
||||
@override
|
||||
void resetActivity() {
|
||||
delegate.beginActivity(coordinator.createInnerBallisticScrollActivity(delegate, velocity));
|
||||
delegate.beginActivity(coordinator.createInnerBallisticScrollActivity(
|
||||
delegate,
|
||||
velocity,
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
void applyNewDimensions() {
|
||||
delegate.beginActivity(coordinator.createInnerBallisticScrollActivity(delegate, velocity));
|
||||
delegate.beginActivity(coordinator.createInnerBallisticScrollActivity(
|
||||
delegate,
|
||||
velocity,
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
@ -1190,12 +1340,16 @@ class _NestedOuterBallisticScrollActivity extends BallisticScrollActivity {
|
||||
|
||||
@override
|
||||
void resetActivity() {
|
||||
delegate.beginActivity(coordinator.createOuterBallisticScrollActivity(velocity));
|
||||
delegate.beginActivity(
|
||||
coordinator.createOuterBallisticScrollActivity(velocity)
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void applyNewDimensions() {
|
||||
delegate.beginActivity(coordinator.createOuterBallisticScrollActivity(velocity));
|
||||
delegate.beginActivity(
|
||||
coordinator.createOuterBallisticScrollActivity(velocity)
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -1290,7 +1444,10 @@ class SliverOverlapAbsorberHandle extends ChangeNotifier {
|
||||
double _scrollExtent;
|
||||
|
||||
void _setExtents(double layoutValue, double scrollValue) {
|
||||
assert(_writers == 1, 'Multiple RenderSliverOverlapAbsorbers have been provided the same SliverOverlapAbsorberHandle.');
|
||||
assert(
|
||||
_writers == 1,
|
||||
'Multiple RenderSliverOverlapAbsorbers have been provided the same SliverOverlapAbsorberHandle.',
|
||||
);
|
||||
_layoutExtent = layoutValue;
|
||||
_scrollExtent = scrollValue;
|
||||
}
|
||||
@ -1434,7 +1591,10 @@ class RenderSliverOverlapAbsorber extends RenderSliver with RenderObjectWithChil
|
||||
|
||||
@override
|
||||
void performLayout() {
|
||||
assert(handle._writers == 1, 'A SliverOverlapAbsorberHandle cannot be passed to multiple RenderSliverOverlapAbsorber objects at the same time.');
|
||||
assert(
|
||||
handle._writers == 1,
|
||||
'A SliverOverlapAbsorberHandle cannot be passed to multiple RenderSliverOverlapAbsorber objects at the same time.',
|
||||
);
|
||||
if (child == null) {
|
||||
geometry = const SliverGeometry();
|
||||
return;
|
||||
@ -1453,7 +1613,10 @@ class RenderSliverOverlapAbsorber extends RenderSliver with RenderObjectWithChil
|
||||
hasVisualOverflow: childLayoutGeometry.hasVisualOverflow,
|
||||
scrollOffsetCorrection: childLayoutGeometry.scrollOffsetCorrection,
|
||||
);
|
||||
handle._setExtents(childLayoutGeometry.maxScrollObstructionExtent, childLayoutGeometry.maxScrollObstructionExtent);
|
||||
handle._setExtents(
|
||||
childLayoutGeometry.maxScrollObstructionExtent,
|
||||
childLayoutGeometry.maxScrollObstructionExtent,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -1464,7 +1627,11 @@ class RenderSliverOverlapAbsorber extends RenderSliver with RenderObjectWithChil
|
||||
@override
|
||||
bool hitTestChildren(SliverHitTestResult result, { @required double mainAxisPosition, @required double crossAxisPosition }) {
|
||||
if (child != null)
|
||||
return child.hitTest(result, mainAxisPosition: mainAxisPosition, crossAxisPosition: crossAxisPosition);
|
||||
return child.hitTest(
|
||||
result,
|
||||
mainAxisPosition: mainAxisPosition,
|
||||
crossAxisPosition: crossAxisPosition,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1599,7 +1766,10 @@ class RenderSliverOverlapInjector extends RenderSliver {
|
||||
void performLayout() {
|
||||
_currentLayoutExtent = handle.layoutExtent;
|
||||
_currentMaxExtent = handle.layoutExtent;
|
||||
final double clampedLayoutExtent = math.min(_currentLayoutExtent - constraints.scrollOffset, constraints.remainingPaintExtent);
|
||||
final double clampedLayoutExtent = math.min(
|
||||
_currentLayoutExtent - constraints.scrollOffset,
|
||||
constraints.remainingPaintExtent,
|
||||
);
|
||||
geometry = SliverGeometry(
|
||||
scrollExtent: _currentLayoutExtent,
|
||||
paintExtent: math.max(0.0, clampedLayoutExtent),
|
||||
@ -1631,7 +1801,14 @@ class RenderSliverOverlapInjector extends RenderSliver {
|
||||
break;
|
||||
}
|
||||
for (int index = -2; index <= 2; index += 1) {
|
||||
paintZigZag(context.canvas, paint, start - delta * index.toDouble(), end - delta * index.toDouble(), 10, 10.0);
|
||||
paintZigZag(
|
||||
context.canvas,
|
||||
paint,
|
||||
start - delta * index.toDouble(),
|
||||
end - delta * index.toDouble(),
|
||||
10,
|
||||
10.0,
|
||||
);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@ -1680,7 +1857,10 @@ class NestedScrollViewViewport extends Viewport {
|
||||
RenderNestedScrollViewViewport createRenderObject(BuildContext context) {
|
||||
return RenderNestedScrollViewViewport(
|
||||
axisDirection: axisDirection,
|
||||
crossAxisDirection: crossAxisDirection ?? Viewport.getDefaultCrossAxisDirection(context, axisDirection),
|
||||
crossAxisDirection: crossAxisDirection ?? Viewport.getDefaultCrossAxisDirection(
|
||||
context,
|
||||
axisDirection,
|
||||
),
|
||||
anchor: anchor,
|
||||
offset: offset,
|
||||
handle: handle,
|
||||
@ -1691,7 +1871,10 @@ class NestedScrollViewViewport extends Viewport {
|
||||
void updateRenderObject(BuildContext context, RenderNestedScrollViewViewport renderObject) {
|
||||
renderObject
|
||||
..axisDirection = axisDirection
|
||||
..crossAxisDirection = crossAxisDirection ?? Viewport.getDefaultCrossAxisDirection(context, axisDirection)
|
||||
..crossAxisDirection = crossAxisDirection ?? Viewport.getDefaultCrossAxisDirection(
|
||||
context,
|
||||
axisDirection,
|
||||
)
|
||||
..anchor = anchor
|
||||
..offset = offset
|
||||
..handle = handle;
|
||||
@ -1709,7 +1892,8 @@ class NestedScrollViewViewport extends Viewport {
|
||||
/// This viewport takes a [SliverOverlapAbsorberHandle] and notifies it any time
|
||||
/// the viewport needs to recompute its layout (e.g. when it is scrolled).
|
||||
class RenderNestedScrollViewViewport extends RenderViewport {
|
||||
/// Create a variant of [RenderViewport] that has a [SliverOverlapAbsorberHandle].
|
||||
/// Create a variant of [RenderViewport] that has a
|
||||
/// [SliverOverlapAbsorberHandle].
|
||||
///
|
||||
/// The [handle] must not be null.
|
||||
RenderNestedScrollViewViewport({
|
||||
|
@ -22,7 +22,12 @@ class _CustomPhysics extends ClampingScrollPhysics {
|
||||
}
|
||||
}
|
||||
|
||||
Widget buildTest({ ScrollController controller, String title = 'TTTTTTTT' }) {
|
||||
Widget buildTest({
|
||||
ScrollController controller,
|
||||
String title = 'TTTTTTTT',
|
||||
Key key,
|
||||
bool expanded = true,
|
||||
}) {
|
||||
return Localizations(
|
||||
locale: const Locale('en', 'US'),
|
||||
delegates: const <LocalizationsDelegate<dynamic>>[
|
||||
@ -38,6 +43,7 @@ Widget buildTest({ ScrollController controller, String title = 'TTTTTTTT' }) {
|
||||
body: DefaultTabController(
|
||||
length: 4,
|
||||
child: NestedScrollView(
|
||||
key: key,
|
||||
dragStartBehavior: DragStartBehavior.down,
|
||||
controller: controller,
|
||||
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
|
||||
@ -45,7 +51,7 @@ Widget buildTest({ ScrollController controller, String title = 'TTTTTTTT' }) {
|
||||
SliverAppBar(
|
||||
title: Text(title),
|
||||
pinned: true,
|
||||
expandedHeight: 200.0,
|
||||
expandedHeight: expanded ? 200.0 : 0.0,
|
||||
forceElevated: innerBoxIsScrolled,
|
||||
bottom: const TabBar(
|
||||
tabs: <Tab>[
|
||||
@ -119,7 +125,10 @@ void main() {
|
||||
final Offset point1 = tester.getCenter(find.text('aaa1'));
|
||||
await tester.dragFrom(point1, const Offset(0.0, 200.0));
|
||||
await tester.pump();
|
||||
expect(tester.renderObject<RenderBox>(find.byType(AppBar)).size.height, 200.0);
|
||||
expect(
|
||||
tester.renderObject<RenderBox>(find.byType(AppBar)).size.height,
|
||||
200.0,
|
||||
);
|
||||
await tester.flingFrom(point1, const Offset(0.0, -80.0), 50000.0);
|
||||
await tester.pump(const Duration(milliseconds: 20));
|
||||
final Offset point2 = tester.getCenter(find.text('aaa1'));
|
||||
@ -128,6 +137,7 @@ void main() {
|
||||
// the following expectation should switch to 200.0.
|
||||
expect(tester.renderObject<RenderBox>(find.byType(AppBar)).size.height, 120.0);
|
||||
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
|
||||
|
||||
testWidgets('NestedScrollView overscroll and release and hold', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(buildTest());
|
||||
expect(find.text('aaa2'), findsOneWidget);
|
||||
@ -147,11 +157,14 @@ void main() {
|
||||
expect(find.text('aaa2'), findsNothing);
|
||||
await tester.pump(const Duration(milliseconds: 1000));
|
||||
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
|
||||
|
||||
testWidgets('NestedScrollView overscroll and release', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(buildTest());
|
||||
expect(find.text('aaa2'), findsOneWidget);
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
final TestGesture gesture1 = await tester.startGesture(tester.getCenter(find.text('aaa1')));
|
||||
final TestGesture gesture1 = await tester.startGesture(
|
||||
tester.getCenter(find.text('aaa1'))
|
||||
);
|
||||
await gesture1.moveBy(const Offset(0.0, 200.0));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('aaa2'), findsNothing);
|
||||
@ -169,19 +182,31 @@ void main() {
|
||||
expect(find.text('aaa3'), findsNothing);
|
||||
expect(find.text('bbb1'), findsNothing);
|
||||
await tester.pump(const Duration(milliseconds: 250));
|
||||
expect(tester.renderObject<RenderBox>(find.byType(AppBar)).size.height, 200.0);
|
||||
expect(
|
||||
tester.renderObject<RenderBox>(find.byType(AppBar)).size.height,
|
||||
200.0,
|
||||
);
|
||||
|
||||
await tester.drag(find.text('AA'), const Offset(0.0, -20.0));
|
||||
await tester.pump(const Duration(milliseconds: 250));
|
||||
expect(tester.renderObject<RenderBox>(find.byType(AppBar)).size.height, 180.0);
|
||||
expect(
|
||||
tester.renderObject<RenderBox>(find.byType(AppBar)).size.height,
|
||||
180.0,
|
||||
);
|
||||
|
||||
await tester.drag(find.text('AA'), const Offset(0.0, -20.0));
|
||||
await tester.pump(const Duration(milliseconds: 250));
|
||||
expect(tester.renderObject<RenderBox>(find.byType(AppBar)).size.height, 160.0);
|
||||
expect(
|
||||
tester.renderObject<RenderBox>(find.byType(AppBar)).size.height,
|
||||
160.0,
|
||||
);
|
||||
|
||||
await tester.drag(find.text('AA'), const Offset(0.0, -20.0));
|
||||
await tester.pump(const Duration(milliseconds: 250));
|
||||
expect(tester.renderObject<RenderBox>(find.byType(AppBar)).size.height, 140.0);
|
||||
expect(
|
||||
tester.renderObject<RenderBox>(find.byType(AppBar)).size.height,
|
||||
140.0,
|
||||
);
|
||||
|
||||
expect(find.text('aaa4'), findsNothing);
|
||||
await tester.pump(const Duration(milliseconds: 250));
|
||||
@ -203,17 +228,25 @@ void main() {
|
||||
await tester.pumpAndSettle(const Duration(milliseconds: 250));
|
||||
expect(find.text('bbb1'), findsNothing);
|
||||
expect(find.text('ccc1'), findsOneWidget);
|
||||
expect(tester.renderObject<RenderBox>(find.byType(AppBar)).size.height, minHeight);
|
||||
expect(
|
||||
tester.renderObject<RenderBox>(find.byType(AppBar)).size.height,
|
||||
minHeight,
|
||||
);
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 250));
|
||||
await tester.fling(find.text('AA'), const Offset(0.0, 50.0), 10000.0);
|
||||
await tester.pumpAndSettle(const Duration(milliseconds: 250));
|
||||
expect(find.text('ccc1'), findsOneWidget);
|
||||
expect(tester.renderObject<RenderBox>(find.byType(AppBar)).size.height, 200.0);
|
||||
expect(
|
||||
tester.renderObject<RenderBox>(find.byType(AppBar)).size.height,
|
||||
200.0,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('NestedScrollView with a ScrollController', (WidgetTester tester) async {
|
||||
final ScrollController controller = ScrollController(initialScrollOffset: 50.0);
|
||||
final ScrollController controller = ScrollController(
|
||||
initialScrollOffset: 50.0,
|
||||
);
|
||||
|
||||
double scrollOffset;
|
||||
controller.addListener(() {
|
||||
@ -226,26 +259,45 @@ void main() {
|
||||
expect(controller.position.maxScrollExtent, 200.0);
|
||||
|
||||
// The appbar's expandedHeight - initialScrollOffset = 150.
|
||||
expect(tester.renderObject<RenderBox>(find.byType(AppBar)).size.height, 150.0);
|
||||
expect(
|
||||
tester.renderObject<RenderBox>(find.byType(AppBar)).size.height,
|
||||
150.0,
|
||||
);
|
||||
|
||||
// Fully expand the appbar by scrolling (no animation) to 0.0.
|
||||
controller.jumpTo(0.0);
|
||||
await tester.pumpAndSettle();
|
||||
expect(scrollOffset, 0.0);
|
||||
expect(tester.renderObject<RenderBox>(find.byType(AppBar)).size.height, 200.0);
|
||||
expect(
|
||||
tester.renderObject<RenderBox>(find.byType(AppBar)).size.height,
|
||||
200.0,
|
||||
);
|
||||
|
||||
// Scroll back to 50.0 animating over 100ms.
|
||||
controller.animateTo(50.0, duration: const Duration(milliseconds: 100), curve: Curves.linear);
|
||||
controller.animateTo(
|
||||
50.0,
|
||||
duration: const Duration(milliseconds: 100),
|
||||
curve: Curves.linear,
|
||||
);
|
||||
await tester.pump();
|
||||
await tester.pump();
|
||||
expect(scrollOffset, 0.0);
|
||||
expect(tester.renderObject<RenderBox>(find.byType(AppBar)).size.height, 200.0);
|
||||
expect(
|
||||
tester.renderObject<RenderBox>(find.byType(AppBar)).size.height,
|
||||
200.0,
|
||||
);
|
||||
await tester.pump(const Duration(milliseconds: 50)); // 50ms - halfway to scroll offset = 50.0.
|
||||
expect(scrollOffset, 25.0);
|
||||
expect(tester.renderObject<RenderBox>(find.byType(AppBar)).size.height, 175.0);
|
||||
expect(
|
||||
tester.renderObject<RenderBox>(find.byType(AppBar)).size.height,
|
||||
175.0,
|
||||
);
|
||||
await tester.pump(const Duration(milliseconds: 50)); // 100ms - all the way to scroll offset = 50.0.
|
||||
expect(scrollOffset, 50.0);
|
||||
expect(tester.renderObject<RenderBox>(find.byType(AppBar)).size.height, 150.0);
|
||||
expect(
|
||||
tester.renderObject<RenderBox>(find.byType(AppBar)).size.height,
|
||||
150.0,
|
||||
);
|
||||
|
||||
// Scroll to the end, (we're not scrolling to the end of the list that contains aaa1,
|
||||
// just to the end of the outer scrollview). Verify that the first item in each tab
|
||||
@ -288,12 +340,18 @@ void main() {
|
||||
expect(find.text('Page0'), findsOneWidget);
|
||||
expect(find.text('Page1'), findsNothing);
|
||||
expect(find.text('Page2'), findsNothing);
|
||||
expect(tester.renderObject<RenderBox>(find.byType(AppBar)).size.height, 200.0);
|
||||
expect(
|
||||
tester.renderObject<RenderBox>(find.byType(AppBar)).size.height,
|
||||
200.0,
|
||||
);
|
||||
|
||||
// A scroll collapses Page0's appbar to 150.0.
|
||||
controller.jumpTo(50.0);
|
||||
await tester.pumpAndSettle();
|
||||
expect(tester.renderObject<RenderBox>(find.byType(AppBar)).size.height, 150.0);
|
||||
expect(
|
||||
tester.renderObject<RenderBox>(find.byType(AppBar)).size.height,
|
||||
150.0,
|
||||
);
|
||||
|
||||
// Fling to Page1. Page1's appbar height is the same as the appbar for Page0.
|
||||
await tester.fling(find.text('Page0'), const Offset(-100.0, 0.0), 10000.0);
|
||||
@ -301,19 +359,28 @@ void main() {
|
||||
expect(find.text('Page0'), findsNothing);
|
||||
expect(find.text('Page1'), findsOneWidget);
|
||||
expect(find.text('Page2'), findsNothing);
|
||||
expect(tester.renderObject<RenderBox>(find.byType(AppBar)).size.height, 150.0);
|
||||
expect(
|
||||
tester.renderObject<RenderBox>(find.byType(AppBar)).size.height,
|
||||
150.0,
|
||||
);
|
||||
|
||||
// Expand Page1's appbar and then fling to Page2. Page2's appbar appears
|
||||
// fully expanded.
|
||||
controller.jumpTo(0.0);
|
||||
await tester.pumpAndSettle();
|
||||
expect(tester.renderObject<RenderBox>(find.byType(AppBar)).size.height, 200.0);
|
||||
expect(
|
||||
tester.renderObject<RenderBox>(find.byType(AppBar)).size.height,
|
||||
200.0,
|
||||
);
|
||||
await tester.fling(find.text('Page1'), const Offset(-100.0, 0.0), 10000.0);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('Page0'), findsNothing);
|
||||
expect(find.text('Page1'), findsNothing);
|
||||
expect(find.text('Page2'), findsOneWidget);
|
||||
expect(tester.renderObject<RenderBox>(find.byType(AppBar)).size.height, 200.0);
|
||||
expect(
|
||||
tester.renderObject<RenderBox>(find.byType(AppBar)).size.height,
|
||||
200.0,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('NestedScrollViews with custom physics', (WidgetTester tester) async {
|
||||
@ -347,7 +414,10 @@ void main() {
|
||||
final Offset point1 = tester.getCenter(find.text('AA'));
|
||||
await tester.dragFrom(point1, const Offset(0.0, 200.0));
|
||||
await tester.pump(const Duration(milliseconds: 20));
|
||||
final Offset point2 = tester.getCenter(find.text('AA', skipOffstage: false));
|
||||
final Offset point2 = tester.getCenter(find.text(
|
||||
'AA',
|
||||
skipOffstage: false,
|
||||
));
|
||||
expect(point1.dy, greaterThan(point2.dy));
|
||||
});
|
||||
|
||||
@ -368,29 +438,31 @@ void main() {
|
||||
// These are the slivers that show up in the "outer" scroll view.
|
||||
return <Widget>[
|
||||
SliverOverlapAbsorber(
|
||||
// This widget takes the overlapping behavior of the SliverAppBar,
|
||||
// and redirects it to the SliverOverlapInjector below. If it is
|
||||
// missing, then it is possible for the nested "inner" scroll view
|
||||
// below to end up under the SliverAppBar even when the inner
|
||||
// scroll view thinks it has not been scrolled.
|
||||
// This is not necessary if the "headerSliverBuilder" only builds
|
||||
// widgets that do not overlap the next sliver.
|
||||
// This widget takes the overlapping behavior of the
|
||||
// SliverAppBar, and redirects it to the SliverOverlapInjector
|
||||
// below. If it is missing, then it is possible for the nested
|
||||
// "inner" scroll view below to end up under the SliverAppBar
|
||||
// even when the inner scroll view thinks it has not been
|
||||
// scrolled. This is not necessary if the
|
||||
// "headerSliverBuilder" only builds widgets that do not
|
||||
// overlap the next sliver.
|
||||
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||
sliver: SliverAppBar(
|
||||
title: const Text('Books'), // This is the title in the app bar.
|
||||
pinned: true,
|
||||
expandedHeight: 150.0,
|
||||
// The "forceElevated" property causes the SliverAppBar to show
|
||||
// a shadow. The "innerBoxIsScrolled" parameter is true when the
|
||||
// inner scroll view is scrolled beyond its "zero" point, i.e.
|
||||
// when it appears to be scrolled below the SliverAppBar.
|
||||
// Without this, there are cases where the shadow would appear
|
||||
// or not appear inappropriately, because the SliverAppBar is
|
||||
// not actually aware of the precise position of the inner
|
||||
// scroll views.
|
||||
// The "forceElevated" property causes the SliverAppBar to
|
||||
// show a shadow. The "innerBoxIsScrolled" parameter is true
|
||||
// when the inner scroll view is scrolled beyond its "zero"
|
||||
// point, i.e. when it appears to be scrolled below the
|
||||
// SliverAppBar. Without this, there are cases where the
|
||||
// shadow would appear or not appear inappropriately,
|
||||
// because the SliverAppBar is not actually aware of the
|
||||
// precise position of the inner scroll views.
|
||||
forceElevated: innerBoxIsScrolled,
|
||||
bottom: TabBar(
|
||||
// These are the widgets to put in each tab in the tab bar.
|
||||
// These are the widgets to put in each tab in the tab
|
||||
// bar.
|
||||
tabs: _tabs.map<Widget>((String name) => Tab(text: name)).toList(),
|
||||
dragStartBehavior: DragStartBehavior.down,
|
||||
),
|
||||
@ -406,24 +478,27 @@ void main() {
|
||||
top: false,
|
||||
bottom: false,
|
||||
child: Builder(
|
||||
// This Builder is needed to provide a BuildContext that is "inside"
|
||||
// the NestedScrollView, so that sliverOverlapAbsorberHandleFor() can
|
||||
// find the NestedScrollView.
|
||||
// This Builder is needed to provide a BuildContext that is
|
||||
// "inside" the NestedScrollView, so that
|
||||
// sliverOverlapAbsorberHandleFor() can find the
|
||||
// NestedScrollView.
|
||||
builder: (BuildContext context) {
|
||||
return CustomScrollView(
|
||||
// The "controller" and "primary" members should be left
|
||||
// unset, so that the NestedScrollView can control this
|
||||
// inner scroll view.
|
||||
// If the "controller" property is set, then this scroll
|
||||
// view will not be associated with the NestedScrollView.
|
||||
// The PageStorageKey should be unique to this ScrollView;
|
||||
// it allows the list to remember its scroll position when
|
||||
// the tab view is not on the screen.
|
||||
// view will not be associated with the
|
||||
// NestedScrollView. The PageStorageKey should be unique
|
||||
// to this ScrollView; it allows the list to remember
|
||||
// its scroll position when the tab view is not on the
|
||||
// screen.
|
||||
key: PageStorageKey<String>(name),
|
||||
dragStartBehavior: DragStartBehavior.down,
|
||||
slivers: <Widget>[
|
||||
SliverOverlapInjector(
|
||||
// This is the flip side of the SliverOverlapAbsorber above.
|
||||
// This is the flip side of the
|
||||
// SliverOverlapAbsorber above.
|
||||
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||
),
|
||||
SliverPadding(
|
||||
@ -431,24 +506,27 @@ void main() {
|
||||
// In this example, the inner scroll view has
|
||||
// fixed-height list items, hence the use of
|
||||
// SliverFixedExtentList. However, one could use any
|
||||
// sliver widget here, e.g. SliverList or SliverGrid.
|
||||
// sliver widget here, e.g. SliverList or
|
||||
// SliverGrid.
|
||||
sliver: SliverFixedExtentList(
|
||||
// The items in this example are fixed to 48 pixels
|
||||
// high. This matches the Material Design spec for
|
||||
// ListTile widgets.
|
||||
// The items in this example are fixed to 48
|
||||
// pixels high. This matches the Material Design
|
||||
// spec for ListTile widgets.
|
||||
itemExtent: 48.0,
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) {
|
||||
// This builder is called for each child.
|
||||
// In this example, we just number each list item.
|
||||
// In this example, we just number each list
|
||||
// item.
|
||||
return ListTile(
|
||||
title: Text('Item $index'),
|
||||
);
|
||||
},
|
||||
// The childCount of the SliverChildBuilderDelegate
|
||||
// specifies how many children this inner list
|
||||
// has. In this example, each tab has a list of
|
||||
// exactly 30 items, but this is arbitrary.
|
||||
// The childCount of the
|
||||
// SliverChildBuilderDelegate specifies how many
|
||||
// children this inner list has. In this
|
||||
// example, each tab has a list of exactly 30
|
||||
// items, but this is arbitrary.
|
||||
childCount: 30,
|
||||
),
|
||||
),
|
||||
@ -498,7 +576,9 @@ void main() {
|
||||
expect(find.text('Item 18'), findsNothing);
|
||||
_checkPhysicalLayer(elevation: 0);
|
||||
// scroll down
|
||||
final TestGesture gesture0 = await tester.startGesture(tester.getCenter(find.text('Item 2')));
|
||||
final TestGesture gesture0 = await tester.startGesture(
|
||||
tester.getCenter(find.text('Item 2'))
|
||||
);
|
||||
await gesture0.moveBy(const Offset(0.0, -120.0)); // tiny bit more than the pinned app bar height (56px * 2)
|
||||
await tester.pump();
|
||||
expect(buildCount, expectedBuildCount);
|
||||
@ -515,7 +595,9 @@ void main() {
|
||||
expect(buildCount, expectedBuildCount);
|
||||
_checkPhysicalLayer(elevation: 4);
|
||||
// scroll down
|
||||
final TestGesture gesture1 = await tester.startGesture(tester.getCenter(find.text('Item 2')));
|
||||
final TestGesture gesture1 = await tester.startGesture(
|
||||
tester.getCenter(find.text('Item 2'))
|
||||
);
|
||||
await gesture1.moveBy(const Offset(0.0, -800.0));
|
||||
await tester.pump();
|
||||
expect(buildCount, expectedBuildCount);
|
||||
@ -527,15 +609,22 @@ void main() {
|
||||
expect(buildCount, expectedBuildCount);
|
||||
_checkPhysicalLayer(elevation: 4);
|
||||
// swipe left to bring in tap on the right
|
||||
final TestGesture gesture2 = await tester.startGesture(tester.getCenter(find.byType(NestedScrollView)));
|
||||
final TestGesture gesture2 = await tester.startGesture(
|
||||
tester.getCenter(find.byType(NestedScrollView))
|
||||
);
|
||||
await gesture2.moveBy(const Offset(-400.0, 0.0));
|
||||
await tester.pump();
|
||||
expect(buildCount, expectedBuildCount);
|
||||
expect(find.text('Item 18'), findsOneWidget);
|
||||
expect(find.text('Item 2'), findsOneWidget);
|
||||
expect(find.text('Item 0'), findsOneWidget);
|
||||
expect(tester.getTopLeft(find.ancestor(of: find.text('Item 0'), matching: find.byType(ListTile))).dy,
|
||||
tester.getBottomLeft(find.byType(AppBar)).dy + 8.0);
|
||||
expect(tester.getTopLeft(
|
||||
find.ancestor(
|
||||
of: find.text('Item 0'),
|
||||
matching: find.byType(ListTile),
|
||||
)).dy,
|
||||
tester.getBottomLeft(find.byType(AppBar)).dy + 8.0,
|
||||
);
|
||||
_checkPhysicalLayer(elevation: 4);
|
||||
await gesture2.up();
|
||||
await tester.pump(); // start sideways scroll
|
||||
@ -552,7 +641,9 @@ void main() {
|
||||
await tester.pump(const Duration(seconds: 1)); // just checking we don't rebuild...
|
||||
expect(buildCount, expectedBuildCount);
|
||||
// peek left to see it's still in the right place
|
||||
final TestGesture gesture3 = await tester.startGesture(tester.getCenter(find.byType(NestedScrollView)));
|
||||
final TestGesture gesture3 = await tester.startGesture(
|
||||
tester.getCenter(find.byType(NestedScrollView))
|
||||
);
|
||||
await gesture3.moveBy(const Offset(400.0, 0.0));
|
||||
await tester.pump(); // bring the left page into view
|
||||
expect(buildCount, expectedBuildCount);
|
||||
@ -577,7 +668,9 @@ void main() {
|
||||
expect(buildCount, expectedBuildCount);
|
||||
_checkPhysicalLayer(elevation: 0);
|
||||
// scroll back up
|
||||
final TestGesture gesture4 = await tester.startGesture(tester.getCenter(find.byType(NestedScrollView)));
|
||||
final TestGesture gesture4 = await tester.startGesture(
|
||||
tester.getCenter(find.byType(NestedScrollView))
|
||||
);
|
||||
await gesture4.moveBy(const Offset(0.0, 200.0)); // expands the appbar again
|
||||
await tester.pump();
|
||||
expect(buildCount, expectedBuildCount);
|
||||
@ -589,7 +682,9 @@ void main() {
|
||||
expect(buildCount, expectedBuildCount);
|
||||
_checkPhysicalLayer(elevation: 0);
|
||||
// peek left to see it's now back at zero
|
||||
final TestGesture gesture5 = await tester.startGesture(tester.getCenter(find.byType(NestedScrollView)));
|
||||
final TestGesture gesture5 = await tester.startGesture(
|
||||
tester.getCenter(find.byType(NestedScrollView))
|
||||
);
|
||||
await gesture5.moveBy(const Offset(400.0, 0.0));
|
||||
await tester.pump(); // bring the left page into view
|
||||
await tester.pump(); // shadow would come back starting here, but there's no shadow to show
|
||||
@ -640,39 +735,467 @@ void main() {
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(tester.getRect(find.byKey(key1)), const Rect.fromLTWH(0.0, 0.0, 800.0, 100.0));
|
||||
expect(tester.getRect(find.byKey(key2)), const Rect.fromLTWH(0.0, 100.0, 800.0, 1000.0));
|
||||
final TestGesture gesture = await tester.startGesture(const Offset(10.0, 10.0));
|
||||
expect(
|
||||
tester.getRect(find.byKey(key1)),
|
||||
const Rect.fromLTWH(0.0, 0.0, 800.0, 100.0),
|
||||
);
|
||||
expect(
|
||||
tester.getRect(find.byKey(key2)),
|
||||
const Rect.fromLTWH(0.0, 100.0, 800.0, 1000.0),
|
||||
);
|
||||
final TestGesture gesture = await tester.startGesture(
|
||||
const Offset(10.0, 10.0)
|
||||
);
|
||||
await gesture.moveBy(const Offset(0.0, -10.0)); // scroll up
|
||||
await tester.pump();
|
||||
expect(tester.getRect(find.byKey(key1)), const Rect.fromLTWH(0.0, -10.0, 800.0, 100.0));
|
||||
expect(tester.getRect(find.byKey(key2)), const Rect.fromLTWH(0.0, 90.0, 800.0, 1000.0));
|
||||
expect(
|
||||
tester.getRect(find.byKey(key1)),
|
||||
const Rect.fromLTWH(0.0, -10.0, 800.0, 100.0),
|
||||
);
|
||||
expect(
|
||||
tester.getRect(find.byKey(key2)),
|
||||
const Rect.fromLTWH(0.0, 90.0, 800.0, 1000.0),
|
||||
);
|
||||
await gesture.moveBy(const Offset(0.0, 10.0)); // scroll back to origin
|
||||
await tester.pump();
|
||||
expect(tester.getRect(find.byKey(key1)), const Rect.fromLTWH(0.0, 0.0, 800.0, 100.0));
|
||||
expect(tester.getRect(find.byKey(key2)), const Rect.fromLTWH(0.0, 100.0, 800.0, 1000.0));
|
||||
expect(
|
||||
tester.getRect(find.byKey(key1)),
|
||||
const Rect.fromLTWH(0.0, 0.0, 800.0, 100.0),
|
||||
);
|
||||
expect(
|
||||
tester.getRect(find.byKey(key2)),
|
||||
const Rect.fromLTWH(0.0, 100.0, 800.0, 1000.0),
|
||||
);
|
||||
await gesture.moveBy(const Offset(0.0, 10.0)); // overscroll
|
||||
await gesture.moveBy(const Offset(0.0, 10.0)); // overscroll
|
||||
await gesture.moveBy(const Offset(0.0, 10.0)); // overscroll
|
||||
await tester.pump();
|
||||
expect(tester.getRect(find.byKey(key1)), const Rect.fromLTWH(0.0, 0.0, 800.0, 100.0));
|
||||
expect(
|
||||
tester.getRect(find.byKey(key1)),
|
||||
const Rect.fromLTWH(0.0, 0.0, 800.0, 100.0),
|
||||
);
|
||||
expect(tester.getRect(find.byKey(key2)).top, greaterThan(100.0));
|
||||
expect(tester.getRect(find.byKey(key2)).top, lessThan(130.0));
|
||||
await gesture.moveBy(const Offset(0.0, -1.0)); // scroll back a little
|
||||
await tester.pump();
|
||||
expect(tester.getRect(find.byKey(key1)), const Rect.fromLTWH(0.0, -1.0, 800.0, 100.0));
|
||||
expect(
|
||||
tester.getRect(find.byKey(key1)),
|
||||
const Rect.fromLTWH(0.0, -1.0, 800.0, 100.0),
|
||||
);
|
||||
expect(tester.getRect(find.byKey(key2)).top, greaterThan(100.0));
|
||||
expect(tester.getRect(find.byKey(key2)).top, lessThan(129.0));
|
||||
await gesture.moveBy(const Offset(0.0, -10.0)); // scroll back a lot
|
||||
await tester.pump();
|
||||
expect(tester.getRect(find.byKey(key1)), const Rect.fromLTWH(0.0, -11.0, 800.0, 100.0));
|
||||
expect(
|
||||
tester.getRect(find.byKey(key1)),
|
||||
const Rect.fromLTWH(0.0, -11.0, 800.0, 100.0),
|
||||
);
|
||||
await gesture.moveBy(const Offset(0.0, 20.0)); // overscroll again
|
||||
await tester.pump();
|
||||
expect(tester.getRect(find.byKey(key1)), const Rect.fromLTWH(0.0, 0.0, 800.0, 100.0));
|
||||
expect(
|
||||
tester.getRect(find.byKey(key1)),
|
||||
const Rect.fromLTWH(0.0, 0.0, 800.0, 100.0),
|
||||
);
|
||||
await gesture.up();
|
||||
debugDefaultTargetPlatformOverride = null;
|
||||
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
|
||||
|
||||
group('NestedScrollViewState exposes inner and outer controllers', () {
|
||||
testWidgets('Scrolling by less than the outer extent does not scroll the inner body', (WidgetTester tester) async {
|
||||
final GlobalKey<NestedScrollViewState> globalKey = GlobalKey();
|
||||
await tester.pumpWidget(buildTest(
|
||||
key: globalKey,
|
||||
expanded: false,
|
||||
));
|
||||
|
||||
double appBarHeight = tester.renderObject<RenderBox>(find.byType(AppBar)).size.height;
|
||||
expect(appBarHeight, 104.0);
|
||||
final double scrollExtent = appBarHeight - 50.0;
|
||||
expect(globalKey.currentState.outerController.offset, 0.0);
|
||||
expect(globalKey.currentState.innerController.offset, 0.0);
|
||||
|
||||
// The scroll gesture should occur in the inner body, so the whole
|
||||
// scroll view is scrolled.
|
||||
final TestGesture gesture = await tester.startGesture(Offset(
|
||||
0.0,
|
||||
appBarHeight + 1.0,
|
||||
));
|
||||
await gesture.moveBy(Offset(0.0, -scrollExtent));
|
||||
await tester.pump();
|
||||
|
||||
appBarHeight = tester.renderObject<RenderBox>(find.byType(AppBar)).size.height;
|
||||
// This is not an expanded AppBar.
|
||||
expect(appBarHeight, 104.0);
|
||||
// The outer scroll controller should show an offset of the applied
|
||||
// scrollExtent.
|
||||
expect(globalKey.currentState.outerController.offset, 54.0);
|
||||
// the inner scroll controller should not have scrolled.
|
||||
expect(globalKey.currentState.innerController.offset, 0.0);
|
||||
});
|
||||
|
||||
testWidgets('Scrolling by exactly the outer extent does not scroll the inner body', (WidgetTester tester) async {
|
||||
final GlobalKey<NestedScrollViewState> globalKey = GlobalKey();
|
||||
await tester.pumpWidget(buildTest(
|
||||
key: globalKey,
|
||||
expanded: false,
|
||||
));
|
||||
|
||||
double appBarHeight = tester.renderObject<RenderBox>(find.byType(AppBar)).size.height;
|
||||
expect(appBarHeight, 104.0);
|
||||
final double scrollExtent = appBarHeight;
|
||||
expect(globalKey.currentState.outerController.offset, 0.0);
|
||||
expect(globalKey.currentState.innerController.offset, 0.0);
|
||||
|
||||
// The scroll gesture should occur in the inner body, so the whole
|
||||
// scroll view is scrolled.
|
||||
final TestGesture gesture = await tester.startGesture(Offset(
|
||||
0.0,
|
||||
appBarHeight + 1.0,
|
||||
));
|
||||
await gesture.moveBy(Offset(0.0, -scrollExtent));
|
||||
await tester.pump();
|
||||
|
||||
appBarHeight = tester.renderObject<RenderBox>(find.byType(AppBar)).size.height;
|
||||
// This is not an expanded AppBar.
|
||||
expect(appBarHeight, 104.0);
|
||||
// The outer scroll controller should show an offset of the applied
|
||||
// scrollExtent.
|
||||
expect(globalKey.currentState.outerController.offset, 104.0);
|
||||
// the inner scroll controller should not have scrolled.
|
||||
expect(globalKey.currentState.innerController.offset, 0.0);
|
||||
});
|
||||
|
||||
testWidgets('Scrolling by greater than the outer extent scrolls the inner body', (WidgetTester tester) async {
|
||||
final GlobalKey<NestedScrollViewState> globalKey = GlobalKey();
|
||||
await tester.pumpWidget(buildTest(
|
||||
key: globalKey,
|
||||
expanded: false,
|
||||
));
|
||||
|
||||
double appBarHeight = tester.renderObject<RenderBox>(find.byType(AppBar)).size.height;
|
||||
expect(appBarHeight, 104.0);
|
||||
final double scrollExtent = appBarHeight + 50.0;
|
||||
expect(globalKey.currentState.outerController.offset, 0.0);
|
||||
expect(globalKey.currentState.innerController.offset, 0.0);
|
||||
|
||||
// The scroll gesture should occur in the inner body, so the whole
|
||||
// scroll view is scrolled.
|
||||
final TestGesture gesture = await tester.startGesture(Offset(
|
||||
0.0,
|
||||
appBarHeight + 1.0,
|
||||
));
|
||||
await gesture.moveBy(Offset(0.0, -scrollExtent));
|
||||
await tester.pump();
|
||||
|
||||
appBarHeight = tester.renderObject<RenderBox>(find.byType(AppBar)).size.height;
|
||||
// This is not an expanded AppBar.
|
||||
expect(appBarHeight, 104.0);
|
||||
// The outer scroll controller should show an offset of the applied
|
||||
// scrollExtent.
|
||||
expect(globalKey.currentState.outerController.offset, appBarHeight);
|
||||
// the inner scroll controller should have scrolled equivalent to the
|
||||
// difference between the applied scrollExtent and the outer extent.
|
||||
expect(
|
||||
globalKey.currentState.innerController.offset,
|
||||
scrollExtent - appBarHeight,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('scrolling by less than the expanded outer extent does not scroll the inner body', (WidgetTester tester) async {
|
||||
final GlobalKey<NestedScrollViewState> globalKey = GlobalKey();
|
||||
await tester.pumpWidget(buildTest(key: globalKey));
|
||||
|
||||
double appBarHeight = tester.renderObject<RenderBox>(find.byType(AppBar)).size.height;
|
||||
expect(appBarHeight, 200.0);
|
||||
final double scrollExtent = appBarHeight - 50.0;
|
||||
expect(globalKey.currentState.outerController.offset, 0.0);
|
||||
expect(globalKey.currentState.innerController.offset, 0.0);
|
||||
|
||||
// The scroll gesture should occur in the inner body, so the whole
|
||||
// scroll view is scrolled.
|
||||
final TestGesture gesture = await tester.startGesture(Offset(
|
||||
0.0,
|
||||
appBarHeight + 1.0,
|
||||
));
|
||||
await gesture.moveBy(Offset(0.0, -scrollExtent));
|
||||
await tester.pump();
|
||||
|
||||
appBarHeight = tester.renderObject<RenderBox>(find.byType(AppBar)).size.height;
|
||||
// This is an expanding AppBar.
|
||||
expect(appBarHeight, 104.0);
|
||||
// The outer scroll controller should show an offset of the applied
|
||||
// scrollExtent.
|
||||
expect(globalKey.currentState.outerController.offset, 150.0);
|
||||
// the inner scroll controller should not have scrolled.
|
||||
expect(globalKey.currentState.innerController.offset, 0.0);
|
||||
});
|
||||
|
||||
testWidgets('scrolling by exactly the expanded outer extent does not scroll the inner body', (WidgetTester tester) async {
|
||||
final GlobalKey<NestedScrollViewState> globalKey = GlobalKey();
|
||||
await tester.pumpWidget(buildTest(key: globalKey));
|
||||
|
||||
double appBarHeight = tester.renderObject<RenderBox>(find.byType(AppBar)).size.height;
|
||||
expect(appBarHeight, 200.0);
|
||||
final double scrollExtent = appBarHeight;
|
||||
expect(globalKey.currentState.outerController.offset, 0.0);
|
||||
expect(globalKey.currentState.innerController.offset, 0.0);
|
||||
|
||||
// The scroll gesture should occur in the inner body, so the whole
|
||||
// scroll view is scrolled.
|
||||
final TestGesture gesture = await tester.startGesture(Offset(
|
||||
0.0,
|
||||
appBarHeight + 1.0,
|
||||
));
|
||||
await gesture.moveBy(Offset(0.0, -scrollExtent));
|
||||
await tester.pump();
|
||||
|
||||
appBarHeight = tester.renderObject<RenderBox>(find.byType(AppBar)).size.height;
|
||||
// This is an expanding AppBar.
|
||||
expect(appBarHeight, 104.0);
|
||||
// The outer scroll controller should show an offset of the applied
|
||||
// scrollExtent.
|
||||
expect(globalKey.currentState.outerController.offset, 200.0);
|
||||
// the inner scroll controller should not have scrolled.
|
||||
expect(globalKey.currentState.innerController.offset, 0.0);
|
||||
});
|
||||
|
||||
testWidgets('scrolling by greater than the expanded outer extent scrolls the inner body', (WidgetTester tester) async {
|
||||
final GlobalKey<NestedScrollViewState> globalKey = GlobalKey();
|
||||
await tester.pumpWidget(buildTest(key: globalKey));
|
||||
|
||||
double appBarHeight = tester.renderObject<RenderBox>(find.byType(AppBar)).size.height;
|
||||
expect(appBarHeight, 200.0);
|
||||
final double scrollExtent = appBarHeight + 50.0;
|
||||
expect(globalKey.currentState.outerController.offset, 0.0);
|
||||
expect(globalKey.currentState.innerController.offset, 0.0);
|
||||
|
||||
// The scroll gesture should occur in the inner body, so the whole
|
||||
// scroll view is scrolled.
|
||||
final TestGesture gesture = await tester.startGesture(Offset(
|
||||
0.0,
|
||||
appBarHeight + 1.0,
|
||||
));
|
||||
await gesture.moveBy(Offset(0.0, -scrollExtent));
|
||||
await tester.pump();
|
||||
|
||||
appBarHeight = tester.renderObject<RenderBox>(find.byType(AppBar)).size.height;
|
||||
// This is an expanding AppBar.
|
||||
expect(appBarHeight, 104.0);
|
||||
// The outer scroll controller should show an offset of the applied
|
||||
// scrollExtent.
|
||||
expect(globalKey.currentState.outerController.offset, 200.0);
|
||||
// the inner scroll controller should have scrolled equivalent to the
|
||||
// difference between the applied scrollExtent and the outer extent.
|
||||
expect(globalKey.currentState.innerController.offset, 50.0);
|
||||
});
|
||||
|
||||
testWidgets('NestedScrollViewState.outerController should correspond to NestedScrollView.controller', (
|
||||
WidgetTester tester) async {
|
||||
final GlobalKey<NestedScrollViewState> globalKey = GlobalKey();
|
||||
final ScrollController scrollController = ScrollController();
|
||||
|
||||
await tester.pumpWidget(buildTest(
|
||||
controller: scrollController,
|
||||
key: globalKey,
|
||||
));
|
||||
|
||||
// Scroll to compare offsets between controllers.
|
||||
final TestGesture gesture = await tester.startGesture(const Offset(
|
||||
0.0,
|
||||
100.0,
|
||||
));
|
||||
await gesture.moveBy(const Offset(0.0, -100.0));
|
||||
await tester.pump();
|
||||
|
||||
expect(
|
||||
scrollController.offset,
|
||||
globalKey.currentState.outerController.offset,
|
||||
);
|
||||
expect(
|
||||
tester.widget<NestedScrollView>(find.byType(NestedScrollView)).controller.offset,
|
||||
globalKey.currentState.outerController.offset,
|
||||
);
|
||||
});
|
||||
|
||||
group('manipulating controllers when', () {
|
||||
testWidgets('outer: not scrolled, inner: not scrolled', (WidgetTester tester) async {
|
||||
final GlobalKey<NestedScrollViewState> globalKey1 = GlobalKey();
|
||||
await tester.pumpWidget(buildTest(
|
||||
key: globalKey1,
|
||||
expanded: false,
|
||||
));
|
||||
expect(globalKey1.currentState.outerController.position.pixels, 0.0);
|
||||
expect(globalKey1.currentState.innerController.position.pixels, 0.0);
|
||||
final double appBarHeight = tester.renderObject<RenderBox>(find.byType(AppBar)).size.height;
|
||||
|
||||
// Manipulating Inner
|
||||
globalKey1.currentState.innerController.jumpTo(100.0);
|
||||
expect(globalKey1.currentState.innerController.position.pixels, 100.0);
|
||||
expect(
|
||||
globalKey1.currentState.outerController.position.pixels,
|
||||
appBarHeight,
|
||||
);
|
||||
globalKey1.currentState.innerController.jumpTo(0.0);
|
||||
expect(globalKey1.currentState.innerController.position.pixels, 0.0);
|
||||
expect(
|
||||
globalKey1.currentState.outerController.position.pixels,
|
||||
appBarHeight,
|
||||
);
|
||||
|
||||
// Reset
|
||||
final GlobalKey<NestedScrollViewState> globalKey2 = GlobalKey();
|
||||
await tester.pumpWidget(buildTest(
|
||||
key: globalKey2,
|
||||
expanded: false,
|
||||
));
|
||||
expect(globalKey2.currentState.outerController.position.pixels, 0.0);
|
||||
expect(globalKey2.currentState.innerController.position.pixels, 0.0);
|
||||
|
||||
// Manipulating Outer
|
||||
globalKey2.currentState.outerController.jumpTo(100.0);
|
||||
expect(globalKey2.currentState.innerController.position.pixels, 0.0);
|
||||
expect(globalKey2.currentState.outerController.position.pixels, 100.0);
|
||||
globalKey2.currentState.outerController.jumpTo(0.0);
|
||||
expect(globalKey2.currentState.innerController.position.pixels, 0.0);
|
||||
expect(globalKey2.currentState.outerController.position.pixels, 0.0);
|
||||
});
|
||||
|
||||
testWidgets('outer: not scrolled, inner: scrolled', (WidgetTester tester) async {
|
||||
final GlobalKey<NestedScrollViewState> globalKey1 = GlobalKey();
|
||||
await tester.pumpWidget(buildTest(
|
||||
key: globalKey1,
|
||||
expanded: false,
|
||||
));
|
||||
expect(globalKey1.currentState.outerController.position.pixels, 0.0);
|
||||
globalKey1.currentState.innerController.position.setPixels(10.0);
|
||||
expect(globalKey1.currentState.innerController.position.pixels, 10.0);
|
||||
final double appBarHeight = tester.renderObject<RenderBox>(find.byType(AppBar)).size.height;
|
||||
|
||||
// Manipulating Inner
|
||||
globalKey1.currentState.innerController.jumpTo(100.0);
|
||||
expect(globalKey1.currentState.innerController.position.pixels, 100.0);
|
||||
expect(
|
||||
globalKey1.currentState.outerController.position.pixels,
|
||||
appBarHeight,
|
||||
);
|
||||
globalKey1.currentState.innerController.jumpTo(0.0);
|
||||
expect(globalKey1.currentState.innerController.position.pixels, 0.0);
|
||||
expect(
|
||||
globalKey1.currentState.outerController.position.pixels,
|
||||
appBarHeight,
|
||||
);
|
||||
|
||||
// Reset
|
||||
final GlobalKey<NestedScrollViewState> globalKey2 = GlobalKey();
|
||||
await tester.pumpWidget(buildTest(
|
||||
key: globalKey2,
|
||||
expanded: false,
|
||||
));
|
||||
expect(globalKey2.currentState.outerController.position.pixels, 0.0);
|
||||
globalKey2.currentState.innerController.position.setPixels(10.0);
|
||||
expect(globalKey2.currentState.innerController.position.pixels, 10.0);
|
||||
|
||||
// Manipulating Outer
|
||||
globalKey2.currentState.outerController.jumpTo(100.0);
|
||||
expect(globalKey2.currentState.innerController.position.pixels, 0.0);
|
||||
expect(globalKey2.currentState.outerController.position.pixels, 100.0);
|
||||
globalKey2.currentState.outerController.jumpTo(0.0);
|
||||
expect(globalKey2.currentState.innerController.position.pixels, 0.0);
|
||||
expect(globalKey2.currentState.outerController.position.pixels, 0.0);
|
||||
});
|
||||
|
||||
testWidgets('outer: scrolled, inner: not scrolled', (WidgetTester tester) async {
|
||||
final GlobalKey<NestedScrollViewState> globalKey1 = GlobalKey();
|
||||
await tester.pumpWidget(buildTest(
|
||||
key: globalKey1,
|
||||
expanded: false,
|
||||
));
|
||||
expect(globalKey1.currentState.innerController.position.pixels, 0.0);
|
||||
globalKey1.currentState.outerController.position.setPixels(10.0);
|
||||
expect(globalKey1.currentState.outerController.position.pixels, 10.0);
|
||||
final double appBarHeight = tester.renderObject<RenderBox>(find.byType(AppBar)).size.height;
|
||||
|
||||
// Manipulating Inner
|
||||
globalKey1.currentState.innerController.jumpTo(100.0);
|
||||
expect(globalKey1.currentState.innerController.position.pixels, 100.0);
|
||||
expect(
|
||||
globalKey1.currentState.outerController.position.pixels,
|
||||
appBarHeight,
|
||||
);
|
||||
globalKey1.currentState.innerController.jumpTo(0.0);
|
||||
expect(globalKey1.currentState.innerController.position.pixels, 0.0);
|
||||
expect(
|
||||
globalKey1.currentState.outerController.position.pixels,
|
||||
appBarHeight,
|
||||
);
|
||||
|
||||
// Reset
|
||||
final GlobalKey<NestedScrollViewState> globalKey2 = GlobalKey();
|
||||
await tester.pumpWidget(buildTest(
|
||||
key: globalKey2,
|
||||
expanded: false,
|
||||
));
|
||||
expect(globalKey2.currentState.innerController.position.pixels, 0.0);
|
||||
globalKey2.currentState.outerController.position.setPixels(10.0);
|
||||
expect(globalKey2.currentState.outerController.position.pixels, 10.0);
|
||||
|
||||
// Manipulating Outer
|
||||
globalKey2.currentState.outerController.jumpTo(100.0);
|
||||
expect(globalKey2.currentState.innerController.position.pixels, 0.0);
|
||||
expect(globalKey2.currentState.outerController.position.pixels, 100.0);
|
||||
globalKey2.currentState.outerController.jumpTo(0.0);
|
||||
expect(globalKey2.currentState.innerController.position.pixels, 0.0);
|
||||
expect(globalKey2.currentState.outerController.position.pixels, 0.0);
|
||||
});
|
||||
|
||||
testWidgets('outer: scrolled, inner: scrolled', (WidgetTester tester) async {
|
||||
final GlobalKey<NestedScrollViewState> globalKey1 = GlobalKey();
|
||||
await tester.pumpWidget(buildTest(
|
||||
key: globalKey1,
|
||||
expanded: false,
|
||||
));
|
||||
globalKey1.currentState.innerController.position.setPixels(10.0);
|
||||
expect(globalKey1.currentState.innerController.position.pixels, 10.0);
|
||||
globalKey1.currentState.outerController.position.setPixels(10.0);
|
||||
expect(globalKey1.currentState.outerController.position.pixels, 10.0);
|
||||
final double appBarHeight = tester.renderObject<RenderBox>(find.byType(AppBar)).size.height;
|
||||
|
||||
// Manipulating Inner
|
||||
globalKey1.currentState.innerController.jumpTo(100.0);
|
||||
expect(globalKey1.currentState.innerController.position.pixels, 100.0);
|
||||
expect(
|
||||
globalKey1.currentState.outerController.position.pixels,
|
||||
appBarHeight,
|
||||
);
|
||||
globalKey1.currentState.innerController.jumpTo(0.0);
|
||||
expect(globalKey1.currentState.innerController.position.pixels, 0.0);
|
||||
expect(
|
||||
globalKey1.currentState.outerController.position.pixels,
|
||||
appBarHeight,
|
||||
);
|
||||
|
||||
// Reset
|
||||
final GlobalKey<NestedScrollViewState> globalKey2 = GlobalKey();
|
||||
await tester.pumpWidget(buildTest(
|
||||
key: globalKey2,
|
||||
expanded: false,
|
||||
));
|
||||
globalKey2.currentState.innerController.position.setPixels(10.0);
|
||||
expect(globalKey2.currentState.innerController.position.pixels, 10.0);
|
||||
globalKey2.currentState.outerController.position.setPixels(10.0);
|
||||
expect(globalKey2.currentState.outerController.position.pixels, 10.0);
|
||||
|
||||
// Manipulating Outer
|
||||
globalKey2.currentState.outerController.jumpTo(100.0);
|
||||
expect(globalKey2.currentState.innerController.position.pixels, 0.0);
|
||||
expect(globalKey2.currentState.outerController.position.pixels, 100.0);
|
||||
globalKey2.currentState.outerController.jumpTo(0.0);
|
||||
expect(globalKey2.currentState.innerController.position.pixels, 0.0);
|
||||
expect(globalKey2.currentState.outerController.position.pixels, 0.0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Regression test for https://github.com/flutter/flutter/issues/39963.
|
||||
testWidgets('NestedScrollView with SliverOverlapAbsorber in or out of the first screen', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(const _TestLayoutExtentIsNegative(1));
|
||||
|
Loading…
x
Reference in New Issue
Block a user