showOnScreen Improvements (#18252)
This commit is contained in:
parent
c53245c61d
commit
3b9b5acefc
@ -4,6 +4,7 @@
|
||||
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/animation.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:vector_math/vector_math_64.dart' show Matrix4;
|
||||
|
||||
@ -614,27 +615,50 @@ class RenderListWheelViewport
|
||||
}
|
||||
|
||||
@override
|
||||
double getOffsetToReveal(RenderObject target, double alignment) {
|
||||
final ListWheelParentData parentData = target.parentData;
|
||||
final double centerPosition = parentData.offset.dy;
|
||||
RevealedOffset getOffsetToReveal(RenderObject target, double alignment, {Rect rect}) {
|
||||
// `target` is only fully revealed when in the selected/center position. Therefore,
|
||||
// this method always returns the offset that shows `target` in the center position,
|
||||
// which is the same offset for all `alignment` values.
|
||||
|
||||
if (alignment < 0.5) {
|
||||
return centerPosition + _topScrollMarginExtent * alignment * 2.0;
|
||||
} else if (alignment > 0.5) {
|
||||
return centerPosition - _topScrollMarginExtent * (alignment - 0.5) * 2.0;
|
||||
} else {
|
||||
return centerPosition;
|
||||
}
|
||||
rect ??= target.paintBounds;
|
||||
|
||||
// `child` will be the last RenderObject before the viewport when walking up from `target`.
|
||||
RenderObject child = target;
|
||||
while (child.parent != this)
|
||||
child = child.parent;
|
||||
|
||||
final ListWheelParentData parentData = child.parentData;
|
||||
final double targetOffset = parentData.offset.dy; // the so-called "centerPosition"
|
||||
|
||||
final Matrix4 transform = target.getTransformTo(this);
|
||||
final Rect bounds = MatrixUtils.transformRect(transform, rect);
|
||||
final Rect targetRect = bounds.translate(0.0, (size.height - itemExtent) / 2);
|
||||
|
||||
return new RevealedOffset(offset: targetOffset, rect: targetRect);
|
||||
}
|
||||
|
||||
@override
|
||||
void showOnScreen([RenderObject child]) {
|
||||
if (child != null) {
|
||||
// Shows the child in the selected/center position.
|
||||
offset.jumpTo(getOffsetToReveal(child, 0.5));
|
||||
void showOnScreen({
|
||||
RenderObject descendant,
|
||||
Rect rect,
|
||||
Duration duration = Duration.zero,
|
||||
Curve curve = Curves.ease,
|
||||
}) {
|
||||
if (descendant != null) {
|
||||
// Shows the descendant in the selected/center position.
|
||||
final RevealedOffset revealedOffset = getOffsetToReveal(descendant, 0.5, rect: rect);
|
||||
if (duration == Duration.zero) {
|
||||
offset.jumpTo(revealedOffset.offset);
|
||||
} else {
|
||||
offset.animateTo(revealedOffset.offset, duration: duration, curve: curve);
|
||||
}
|
||||
rect = revealedOffset.rect;
|
||||
}
|
||||
|
||||
// Make sure the viewport itself is on screen.
|
||||
super.showOnScreen();
|
||||
super.showOnScreen(
|
||||
rect: rect,
|
||||
duration: duration,
|
||||
curve: curve,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@
|
||||
import 'dart:developer';
|
||||
import 'dart:ui' as ui show PictureRecorder;
|
||||
|
||||
import 'package:flutter/animation.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/painting.dart';
|
||||
@ -2036,6 +2037,9 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
|
||||
|
||||
/// An estimate of the bounds within which this render object will paint.
|
||||
/// Useful for debugging flags such as [debugPaintLayerBordersEnabled].
|
||||
///
|
||||
/// These are also the bounds used by [showOnScreen] to make a [RenderObject]
|
||||
/// visible on screen.
|
||||
Rect get paintBounds;
|
||||
|
||||
/// Override this method to paint debugging information.
|
||||
@ -2570,14 +2574,35 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
|
||||
@override
|
||||
List<DiagnosticsNode> debugDescribeChildren() => <DiagnosticsNode>[];
|
||||
|
||||
/// Attempt to make this or a descendant RenderObject visible on screen.
|
||||
/// Attempt to make (a portion of) this or a descendant [RenderObject] visible
|
||||
/// on screen.
|
||||
///
|
||||
/// If [child] is provided, that [RenderObject] is made visible. If [child] is
|
||||
/// omitted, this [RenderObject] is made visible.
|
||||
void showOnScreen([RenderObject child]) {
|
||||
/// If `descendant` is provided, that [RenderObject] is made visible. If
|
||||
/// `descendant` is omitted, this [RenderObject] is made visible.
|
||||
///
|
||||
/// The optional `rect` parameter describes which area of that [RenderObject]
|
||||
/// should be shown on screen. If `rect` is null, the entire
|
||||
/// [RenderObject] (as defined by its [paintBounds]) will be revealed. The
|
||||
/// `rect` parameter is interpreted relative to the coordinate system of
|
||||
/// `descendant` if that argument is provided and relative to this
|
||||
/// [RenderObject] otherwise.
|
||||
///
|
||||
/// The `duration` parameter can be set to a non-zero value to bring the
|
||||
/// target object on screen in an animation defined by `curve`.
|
||||
void showOnScreen({
|
||||
RenderObject descendant,
|
||||
Rect rect,
|
||||
Duration duration = Duration.zero,
|
||||
Curve curve = Curves.ease,
|
||||
}) {
|
||||
if (parent is RenderObject) {
|
||||
final RenderObject renderParent = parent;
|
||||
renderParent.showOnScreen(child ?? this);
|
||||
renderParent.showOnScreen(
|
||||
descendant: descendant ?? this,
|
||||
rect: rect,
|
||||
duration: duration,
|
||||
curve: curve,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/animation.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/semantics.dart';
|
||||
@ -40,7 +41,14 @@ abstract class RenderAbstractViewport extends RenderObject {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Returns the offset that would be needed to reveal the target render object.
|
||||
/// Returns the offset that would be needed to reveal the `target`
|
||||
/// [RenderObject].
|
||||
///
|
||||
/// The optional `rect` parameter describes which area of that `target` object
|
||||
/// should be revealed in the viewport. If `rect` is null, the entire
|
||||
/// `target` [RenderObject] (as defined by its [RenderObject.paintBounds])
|
||||
/// will be revealed. If `rect` is provided it has to be given in the
|
||||
/// coordinate system of the `target` object.
|
||||
///
|
||||
/// The `alignment` argument describes where the target should be positioned
|
||||
/// after applying the returned offset. If `alignment` is 0.0, the child must
|
||||
@ -52,7 +60,15 @@ abstract class RenderAbstractViewport extends RenderObject {
|
||||
/// The target might not be a direct child of this viewport but it must be a
|
||||
/// descendant of the viewport and there must not be any other
|
||||
/// [RenderAbstractViewport] objects between the target and this object.
|
||||
double getOffsetToReveal(RenderObject target, double alignment);
|
||||
///
|
||||
/// This method assumes that the content of the viewport moves linearly, i.e.
|
||||
/// when the offset of the viewport is changed by x then `target` also moves
|
||||
/// by x within the viewport.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [RevealedOffset], which describes the return value of this method.
|
||||
RevealedOffset getOffsetToReveal(RenderObject target, double alignment, {Rect rect});
|
||||
|
||||
/// The default value for the cache extent of the viewport.
|
||||
///
|
||||
@ -63,6 +79,59 @@ abstract class RenderAbstractViewport extends RenderObject {
|
||||
static const double defaultCacheExtent = 250.0;
|
||||
}
|
||||
|
||||
/// Return value for [RenderAbstractViewport.getOffsetToReveal].
|
||||
///
|
||||
/// It indicates the [offset] required to reveal an element in a viewport and
|
||||
/// the [rect] position said element would have in the viewport at that
|
||||
/// [offset].
|
||||
class RevealedOffset {
|
||||
|
||||
/// Instantiates a return value for [RenderAbstractViewport.getOffsetToReveal].
|
||||
const RevealedOffset({
|
||||
@required this.offset,
|
||||
@required this.rect,
|
||||
}) : assert(offset != null), assert(rect != null);
|
||||
|
||||
/// Offset for the viewport to reveal a specific element in the viewport.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [RenderAbstractViewport.getOffsetToReveal], which calculates this
|
||||
/// value for a specific element.
|
||||
final double offset;
|
||||
|
||||
/// The [Rect] in the outer coordinate system of the viewport at which the
|
||||
/// to-be-revealed element would be located if the viewport's offset is set
|
||||
/// to [offset].
|
||||
///
|
||||
/// A viewport usually has two coordinate systems and works as an adapter
|
||||
/// between the two:
|
||||
///
|
||||
/// The inner coordinate system has its origin at the top left corner of the
|
||||
/// content that moves inside the viewport. The origin of this coordinate
|
||||
/// system usually moves around relative to the leading edge of the viewport
|
||||
/// when the viewport offset changes.
|
||||
///
|
||||
/// The outer coordinate system has its origin at the top left corner of the
|
||||
/// visible part of the viewport. This origin stays at the same position
|
||||
/// regardless of the current viewport offset.
|
||||
///
|
||||
/// In other words: [rect] describes where the revealed element would be
|
||||
/// located relative to the top left corner of the visible part of the
|
||||
/// viewport if the viewport's offset is set to [offset].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [RenderAbstractViewport.getOffsetToReveal], which calculates this
|
||||
/// value for a specific element.
|
||||
final Rect rect;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '$runtimeType(offset: $offset, rect: $rect)';
|
||||
}
|
||||
}
|
||||
|
||||
/// A base class for render objects that are bigger on the inside.
|
||||
///
|
||||
/// This render object provides the shared code for render objects that host
|
||||
@ -512,14 +581,16 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix
|
||||
}
|
||||
|
||||
@override
|
||||
double getOffsetToReveal(RenderObject target, double alignment) {
|
||||
RevealedOffset getOffsetToReveal(RenderObject target, double alignment, {Rect rect}) {
|
||||
double leadingScrollOffset;
|
||||
double targetMainAxisExtent;
|
||||
RenderObject descendant;
|
||||
rect ??= target.paintBounds;
|
||||
|
||||
if (target is RenderBox) {
|
||||
final RenderBox targetBox = target;
|
||||
|
||||
// The pivot will be the topmost child before we hit a RenderSliver.
|
||||
RenderBox pivot = targetBox;
|
||||
while (pivot.parent is RenderBox)
|
||||
pivot = pivot.parent;
|
||||
@ -527,14 +598,11 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix
|
||||
assert(pivot.parent != null);
|
||||
assert(pivot.parent != this);
|
||||
assert(pivot != this);
|
||||
assert(pivot.parent is RenderSliver); // TODO(abarth): Support other kinds of render objects besides slivers.
|
||||
final RenderSliver pivotParent = pivot.parent;
|
||||
|
||||
final Matrix4 transform = targetBox.getTransformTo(pivot);
|
||||
final Rect bounds = MatrixUtils.transformRect(transform, targetBox.paintBounds);
|
||||
|
||||
target = pivot;
|
||||
// TODO(abarth): Support other kinds of render objects besides slivers.
|
||||
assert(target.parent is RenderSliver);
|
||||
final RenderSliver pivotParent = target.parent;
|
||||
final Rect bounds = MatrixUtils.transformRect(transform, rect);
|
||||
|
||||
final GrowthDirection growthDirection = pivotParent.constraints.growthDirection;
|
||||
switch (applyGrowthDirectionToAxisDirection(axisDirection, growthDirection)) {
|
||||
@ -580,7 +648,7 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix
|
||||
targetMainAxisExtent = targetSliver.geometry.scrollExtent;
|
||||
descendant = targetSliver;
|
||||
} else {
|
||||
return offset.pixels;
|
||||
return new RevealedOffset(offset: offset.pixels, rect: rect);
|
||||
}
|
||||
|
||||
// The child will be the topmost object before we get to the viewport.
|
||||
@ -615,7 +683,29 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix
|
||||
break;
|
||||
}
|
||||
|
||||
return leadingScrollOffset - (mainAxisExtent - targetMainAxisExtent) * alignment;
|
||||
final double targetOffset = leadingScrollOffset - (mainAxisExtent - targetMainAxisExtent) * alignment;
|
||||
final double offsetDifference = offset.pixels - targetOffset;
|
||||
|
||||
final Matrix4 transform = target.getTransformTo(this);
|
||||
applyPaintTransform(child, transform);
|
||||
Rect targetRect = MatrixUtils.transformRect(transform, rect);
|
||||
|
||||
switch (axisDirection) {
|
||||
case AxisDirection.down:
|
||||
targetRect = targetRect.translate(0.0, offsetDifference);
|
||||
break;
|
||||
case AxisDirection.right:
|
||||
targetRect = targetRect.translate(offsetDifference, 0.0);
|
||||
break;
|
||||
case AxisDirection.up:
|
||||
targetRect = targetRect.translate(0.0, -offsetDifference);
|
||||
break;
|
||||
case AxisDirection.left:
|
||||
targetRect = targetRect.translate(-offsetDifference, 0.0);
|
||||
break;
|
||||
}
|
||||
|
||||
return new RevealedOffset(offset: targetOffset, rect: targetRect);
|
||||
}
|
||||
|
||||
/// The offset at which the given `child` should be painted.
|
||||
@ -646,8 +736,6 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO(ianh): semantics - shouldn't walk the invisible children
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
@ -783,42 +871,76 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix
|
||||
Iterable<RenderSliver> get childrenInHitTestOrder;
|
||||
|
||||
@override
|
||||
void showOnScreen([RenderObject child]) {
|
||||
RenderViewportBase.showInViewport(child: child, viewport: this, offset: offset);
|
||||
// Make sure the viewport itself is on screen.
|
||||
super.showOnScreen();
|
||||
void showOnScreen({
|
||||
RenderObject descendant,
|
||||
Rect rect,
|
||||
Duration duration = Duration.zero,
|
||||
Curve curve = Curves.ease,
|
||||
}) {
|
||||
final Rect newRect = RenderViewportBase.showInViewport(
|
||||
descendant: descendant,
|
||||
viewport: this,
|
||||
offset: offset,
|
||||
rect: rect,
|
||||
duration: duration,
|
||||
curve: curve,
|
||||
);
|
||||
super.showOnScreen(
|
||||
rect: newRect,
|
||||
duration: duration,
|
||||
curve: curve,
|
||||
);
|
||||
}
|
||||
|
||||
/// Make the given `child` of the given `viewport` fully visible in the
|
||||
/// `viewport` by manipulating the provided [ViewportOffset] `offset`.
|
||||
/// Make (a portion of) the given `descendant` of the given `viewport` fully
|
||||
/// visible in the `viewport` by manipulating the provided [ViewportOffset]
|
||||
/// `offset`.
|
||||
///
|
||||
/// The optional `rect` parameter describes which area of the `descendant`
|
||||
/// should be shown in the viewport. If `rect` is null, the entire
|
||||
/// `descendant` will be revealed. The `rect` parameter is interpreted
|
||||
/// relative to the coordinate system of `descendant`.
|
||||
///
|
||||
/// The returned [Rect] describes the new location of `descendant` or `rect`
|
||||
/// in the viewport after it has been revealed. See [RevealedOffset.rect]
|
||||
/// for a full definition of this [Rect].
|
||||
///
|
||||
/// The parameters `viewport` and `offset` are required and cannot be null.
|
||||
/// If `child` is null this is a no-op.
|
||||
static void showInViewport({
|
||||
RenderObject child,
|
||||
/// If `descendant` is null, this is a no-op and `rect` is returned.
|
||||
///
|
||||
/// If both `decedent` and `rect` are null, null is returned because there is
|
||||
/// nothing to be shown in the viewport.
|
||||
///
|
||||
/// The `duration` parameter can be set to a non-zero value to animate the
|
||||
/// target object into the viewport with an animation defined by `curve`.
|
||||
static Rect showInViewport({
|
||||
RenderObject descendant,
|
||||
Rect rect,
|
||||
@required RenderAbstractViewport viewport,
|
||||
@required ViewportOffset offset,
|
||||
Duration duration = Duration.zero,
|
||||
Curve curve = Curves.ease,
|
||||
}) {
|
||||
assert(viewport != null);
|
||||
assert(offset != null);
|
||||
if (child == null) {
|
||||
return;
|
||||
if (descendant == null) {
|
||||
return rect;
|
||||
}
|
||||
final double leadingEdgeOffset = viewport.getOffsetToReveal(child, 0.0);
|
||||
final double trailingEdgeOffset = viewport.getOffsetToReveal(child, 1.0);
|
||||
final RevealedOffset leadingEdgeOffset = viewport.getOffsetToReveal(descendant, 0.0, rect: rect);
|
||||
final RevealedOffset trailingEdgeOffset = viewport.getOffsetToReveal(descendant, 1.0, rect: rect);
|
||||
final double currentOffset = offset.pixels;
|
||||
|
||||
// scrollOffset
|
||||
// 0 +---------+
|
||||
// | |
|
||||
// _ | |
|
||||
// viewport position | | |
|
||||
// with `child` at | | | _
|
||||
// trailing edge |_ | xxxxxxx | | viewport position
|
||||
// | | | with `child` at
|
||||
// | | _| leading edge
|
||||
// | |
|
||||
// 800 +---------+
|
||||
// scrollOffset
|
||||
// 0 +---------+
|
||||
// | |
|
||||
// _ | |
|
||||
// viewport position | | |
|
||||
// with `descendant` at | | | _
|
||||
// trailing edge |_ | xxxxxxx | | viewport position
|
||||
// | | | with `descendant` at
|
||||
// | | _| leading edge
|
||||
// | |
|
||||
// 800 +---------+
|
||||
//
|
||||
// `trailingEdgeOffset`: Distance from scrollOffset 0 to the start of the
|
||||
// viewport on the left in image above.
|
||||
@ -829,19 +951,36 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix
|
||||
// to `trailingEdgeOffset`, the one on the right by setting it to
|
||||
// `leadingEdgeOffset`.
|
||||
|
||||
assert(leadingEdgeOffset >= trailingEdgeOffset);
|
||||
|
||||
if (currentOffset > leadingEdgeOffset) {
|
||||
// `child` currently starts above the leading edge and can be shown fully
|
||||
// on screen by scrolling down (which means: moving viewport up).
|
||||
offset.jumpTo(leadingEdgeOffset);
|
||||
} else if (currentOffset < trailingEdgeOffset ) {
|
||||
// `child currently ends below the trailing edge and can be shown fully
|
||||
// on screen by scrolling up (which means: moving viewport down)
|
||||
offset.jumpTo(trailingEdgeOffset);
|
||||
RevealedOffset targetOffset;
|
||||
if (leadingEdgeOffset.offset < trailingEdgeOffset.offset) {
|
||||
// `descendant` is too big to be visible on screen in its entirety. Let's
|
||||
// align it with the edge that requires the least amount of scrolling.
|
||||
final double leadingEdgeDiff = (offset.pixels - leadingEdgeOffset.offset).abs();
|
||||
final double trailingEdgeDiff = (offset.pixels - trailingEdgeOffset.offset).abs();
|
||||
targetOffset = leadingEdgeDiff < trailingEdgeDiff ? leadingEdgeOffset : trailingEdgeOffset;
|
||||
} else if (currentOffset > leadingEdgeOffset.offset) {
|
||||
// `descendant` currently starts above the leading edge and can be shown
|
||||
// fully on screen by scrolling down (which means: moving viewport up).
|
||||
targetOffset = leadingEdgeOffset;
|
||||
} else if (currentOffset < trailingEdgeOffset.offset) {
|
||||
// `descendant currently ends below the trailing edge and can be shown
|
||||
// fully on screen by scrolling up (which means: moving viewport down)
|
||||
targetOffset = trailingEdgeOffset;
|
||||
} else {
|
||||
// `descendant` is between leading and trailing edge and hence already
|
||||
// fully shown on screen. No action necessary.
|
||||
final Matrix4 transform = descendant.getTransformTo(viewport.parent);
|
||||
return MatrixUtils.transformRect(transform, rect ?? descendant.paintBounds);
|
||||
}
|
||||
// else: `child` is between leading and trailing edge and hence already
|
||||
// fully shown on screen. No action necessary.
|
||||
|
||||
assert(targetOffset != null);
|
||||
|
||||
if (duration == Duration.zero) {
|
||||
offset.jumpTo(targetOffset.offset);
|
||||
} else {
|
||||
offset.animateTo(targetOffset.offset, duration: duration, curve: curve);
|
||||
}
|
||||
return targetOffset.rect;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,9 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/animation.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
/// The direction of a scroll, relative to the positive scroll offset axis given
|
||||
@ -157,7 +160,7 @@ abstract class ViewportOffset extends ChangeNotifier {
|
||||
/// [jumpTo] applies the change immediately and notifies its listeners.
|
||||
void correctBy(double correction);
|
||||
|
||||
/// Jumps the scroll position from its current value to the given value,
|
||||
/// Jumps [pixels] from its current value to the given value,
|
||||
/// without animation, and without checking if the new value is in range.
|
||||
///
|
||||
/// See also:
|
||||
@ -166,6 +169,18 @@ abstract class ViewportOffset extends ChangeNotifier {
|
||||
/// and that defers the notification of its listeners until after layout.
|
||||
void jumpTo(double pixels);
|
||||
|
||||
/// Animates [pixels] from its current value to the given value.
|
||||
///
|
||||
/// The returned [Future] will complete when the animation ends, whether it
|
||||
/// completed successfully or whether it was interrupted prematurely.
|
||||
///
|
||||
/// The duration must not be zero. To jump to a particular value without an
|
||||
/// animation, use [jumpTo].
|
||||
Future<Null> animateTo(double to, {
|
||||
@required Duration duration,
|
||||
@required Curve curve,
|
||||
});
|
||||
|
||||
/// The direction in which the user is trying to change [pixels], relative to
|
||||
/// the viewport's [RenderViewport.axisDirection].
|
||||
///
|
||||
@ -227,6 +242,12 @@ class _FixedViewportOffset extends ViewportOffset {
|
||||
// Do nothing, viewport is fixed.
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Null> animateTo(double to, {
|
||||
@required Duration duration,
|
||||
@required Curve curve,
|
||||
}) async => null;
|
||||
|
||||
@override
|
||||
ScrollDirection get userScrollDirection => ScrollDirection.idle;
|
||||
}
|
||||
|
@ -497,7 +497,7 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics {
|
||||
final RenderAbstractViewport viewport = RenderAbstractViewport.of(object);
|
||||
assert(viewport != null);
|
||||
|
||||
final double target = viewport.getOffsetToReveal(object, alignment).clamp(minScrollExtent, maxScrollExtent);
|
||||
final double target = viewport.getOffsetToReveal(object, alignment).offset.clamp(minScrollExtent, maxScrollExtent);
|
||||
|
||||
if (target == pixels)
|
||||
return new Future<Null>.value();
|
||||
@ -544,6 +544,7 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics {
|
||||
/// animation, use [jumpTo].
|
||||
///
|
||||
/// The animation is typically handled by an [DrivenScrollActivity].
|
||||
@override
|
||||
Future<Null> animateTo(double to, {
|
||||
@required Duration duration,
|
||||
@required Curve curve,
|
||||
|
@ -484,17 +484,19 @@ class _RenderSingleChildViewport extends RenderBox with RenderObjectWithChildMix
|
||||
offset.applyContentDimensions(_minScrollExtent, _maxScrollExtent);
|
||||
}
|
||||
|
||||
Offset get _paintOffset {
|
||||
Offset get _paintOffset => _paintOffsetForPosition(offset.pixels);
|
||||
|
||||
Offset _paintOffsetForPosition(double position) {
|
||||
assert(axisDirection != null);
|
||||
switch (axisDirection) {
|
||||
case AxisDirection.up:
|
||||
return new Offset(0.0, _offset.pixels - child.size.height + size.height);
|
||||
return new Offset(0.0, position - child.size.height + size.height);
|
||||
case AxisDirection.down:
|
||||
return new Offset(0.0, -_offset.pixels);
|
||||
return new Offset(0.0, -position);
|
||||
case AxisDirection.left:
|
||||
return new Offset(_offset.pixels - child.size.width + size.width, 0.0);
|
||||
return new Offset(position - child.size.width + size.width, 0.0);
|
||||
case AxisDirection.right:
|
||||
return new Offset(-_offset.pixels, 0.0);
|
||||
return new Offset(-position, 0.0);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@ -544,13 +546,14 @@ class _RenderSingleChildViewport extends RenderBox with RenderObjectWithChildMix
|
||||
}
|
||||
|
||||
@override
|
||||
double getOffsetToReveal(RenderObject target, double alignment) {
|
||||
RevealedOffset getOffsetToReveal(RenderObject target, double alignment, {Rect rect}) {
|
||||
rect ??= target.paintBounds;
|
||||
if (target is! RenderBox)
|
||||
return offset.pixels;
|
||||
return new RevealedOffset(offset: offset.pixels, rect: rect);
|
||||
|
||||
final RenderBox targetBox = target;
|
||||
final Matrix4 transform = targetBox.getTransformTo(this);
|
||||
final Rect bounds = MatrixUtils.transformRect(transform, targetBox.paintBounds);
|
||||
final Rect bounds = MatrixUtils.transformRect(transform, rect);
|
||||
final Size contentSize = child.size;
|
||||
|
||||
double leadingScrollOffset;
|
||||
@ -581,14 +584,31 @@ class _RenderSingleChildViewport extends RenderBox with RenderObjectWithChildMix
|
||||
break;
|
||||
}
|
||||
|
||||
return leadingScrollOffset - (mainAxisExtent - targetMainAxisExtent) * alignment;
|
||||
final double targetOffset = leadingScrollOffset - (mainAxisExtent - targetMainAxisExtent) * alignment;
|
||||
final Rect targetRect = bounds.shift(_paintOffsetForPosition(targetOffset));
|
||||
return new RevealedOffset(offset: targetOffset, rect: targetRect);
|
||||
}
|
||||
|
||||
@override
|
||||
void showOnScreen([RenderObject child]) {
|
||||
RenderViewportBase.showInViewport(child: child, viewport: this, offset: offset);
|
||||
// Make sure the viewport itself is on screen.
|
||||
super.showOnScreen();
|
||||
void showOnScreen({
|
||||
RenderObject descendant,
|
||||
Rect rect,
|
||||
Duration duration = Duration.zero,
|
||||
Curve curve = Curves.ease,
|
||||
}) {
|
||||
final Rect newRect = RenderViewportBase.showInViewport(
|
||||
descendant: descendant,
|
||||
viewport: this,
|
||||
offset: offset,
|
||||
rect: rect,
|
||||
duration: duration,
|
||||
curve: curve,
|
||||
);
|
||||
super.showOnScreen(
|
||||
rect: newRect,
|
||||
duration: duration,
|
||||
curve: curve,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
|
540
packages/flutter/test/rendering/viewport_test.dart
Normal file
540
packages/flutter/test/rendering/viewport_test.dart
Normal file
@ -0,0 +1,540 @@
|
||||
// Copyright 2018 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Viewport getOffsetToReveal - down', (WidgetTester tester) async {
|
||||
List<Widget> children;
|
||||
await tester.pumpWidget(
|
||||
new Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Center(
|
||||
child: Container(
|
||||
height: 200.0,
|
||||
width: 300.0,
|
||||
child: new ListView(
|
||||
controller: new ScrollController(initialScrollOffset: 300.0),
|
||||
children: children = new List<Widget>.generate(20, (int i) {
|
||||
return new Container(
|
||||
height: 100.0,
|
||||
width: 300.0,
|
||||
child: new Text('Tile $i'),
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final RenderAbstractViewport viewport = tester.allRenderObjects.firstWhere((RenderObject r) => r is RenderAbstractViewport);
|
||||
|
||||
final RenderObject target = tester.renderObject(find.byWidget(children[5], skipOffstage: false));
|
||||
RevealedOffset revealed = viewport.getOffsetToReveal(target, 0.0);
|
||||
expect(revealed.offset, 500.0);
|
||||
expect(revealed.rect, new Rect.fromLTWH(0.0, 0.0, 300.0, 100.0));
|
||||
|
||||
revealed = viewport.getOffsetToReveal(target, 1.0);
|
||||
expect(revealed.offset, 400.0);
|
||||
expect(revealed.rect, new Rect.fromLTWH(0.0, 100.0, 300.0, 100.0));
|
||||
|
||||
revealed = viewport.getOffsetToReveal(target, 0.0, rect: new Rect.fromLTWH(40.0, 40.0, 10.0, 10.0));
|
||||
expect(revealed.offset, 540.0);
|
||||
expect(revealed.rect, new Rect.fromLTWH(40.0, 0.0, 10.0, 10.0));
|
||||
|
||||
revealed = viewport.getOffsetToReveal(target, 1.0, rect: new Rect.fromLTWH(40.0, 40.0, 10.0, 10.0));
|
||||
expect(revealed.offset, 350.0);
|
||||
expect(revealed.rect, new Rect.fromLTWH(40.0, 190.0, 10.0, 10.0));
|
||||
});
|
||||
|
||||
testWidgets('Viewport getOffsetToReveal - right', (WidgetTester tester) async {
|
||||
List<Widget> children;
|
||||
|
||||
await tester.pumpWidget(
|
||||
new Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Center(
|
||||
child: Container(
|
||||
height: 300.0,
|
||||
width: 200.0,
|
||||
child: new ListView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
controller: new ScrollController(initialScrollOffset: 300.0),
|
||||
children: children = new List<Widget>.generate(20, (int i) {
|
||||
return new Container(
|
||||
height: 300.0,
|
||||
width: 100.0,
|
||||
child: new Text('Tile $i'),
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final RenderAbstractViewport viewport = tester.allRenderObjects.firstWhere((RenderObject r) => r is RenderAbstractViewport);
|
||||
|
||||
final RenderObject target = tester.renderObject(find.byWidget(children[5], skipOffstage: false));
|
||||
RevealedOffset revealed = viewport.getOffsetToReveal(target, 0.0);
|
||||
expect(revealed.offset, 500.0);
|
||||
expect(revealed.rect, new Rect.fromLTWH(0.0, 0.0, 100.0, 300.0));
|
||||
|
||||
revealed = viewport.getOffsetToReveal(target, 1.0);
|
||||
expect(revealed.offset, 400.0);
|
||||
expect(revealed.rect, new Rect.fromLTWH(100.0, 0.0, 100.0, 300.0));
|
||||
|
||||
revealed = viewport.getOffsetToReveal(target, 0.0, rect: new Rect.fromLTWH(40.0, 40.0, 10.0, 10.0));
|
||||
expect(revealed.offset, 540.0);
|
||||
expect(revealed.rect, new Rect.fromLTWH(0.0, 40.0, 10.0, 10.0));
|
||||
|
||||
revealed = viewport.getOffsetToReveal(target, 1.0, rect: new Rect.fromLTWH(40.0, 40.0, 10.0, 10.0));
|
||||
expect(revealed.offset, 350.0);
|
||||
expect(revealed.rect, new Rect.fromLTWH(190.0, 40.0, 10.0, 10.0));
|
||||
});
|
||||
|
||||
testWidgets('Viewport getOffsetToReveal - up', (WidgetTester tester) async {
|
||||
List<Widget> children;
|
||||
|
||||
await tester.pumpWidget(
|
||||
new Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Center(
|
||||
child: Container(
|
||||
height: 200.0,
|
||||
width: 300.0,
|
||||
child: new ListView(
|
||||
controller: new ScrollController(initialScrollOffset: 300.0),
|
||||
reverse: true,
|
||||
children: children = new List<Widget>.generate(20, (int i) {
|
||||
return new Container(
|
||||
height: 100.0,
|
||||
width: 300.0,
|
||||
child: new Text('Tile $i'),
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final RenderAbstractViewport viewport = tester.allRenderObjects.firstWhere((RenderObject r) => r is RenderAbstractViewport);
|
||||
|
||||
final RenderObject target = tester.renderObject(find.byWidget(children[5], skipOffstage: false));
|
||||
RevealedOffset revealed = viewport.getOffsetToReveal(target, 0.0);
|
||||
expect(revealed.offset, 500.0);
|
||||
expect(revealed.rect, new Rect.fromLTWH(0.0, 100.0, 300.0, 100.0));
|
||||
|
||||
revealed = viewport.getOffsetToReveal(target, 1.0);
|
||||
expect(revealed.offset, 400.0);
|
||||
expect(revealed.rect, new Rect.fromLTWH(0.0, 0.0, 300.0, 100.0));
|
||||
|
||||
revealed = viewport.getOffsetToReveal(target, 0.0, rect: new Rect.fromLTWH(40.0, 40.0, 10.0, 10.0));
|
||||
expect(revealed.offset, 550.0);
|
||||
expect(revealed.rect, new Rect.fromLTWH(40.0, 190.0, 10.0, 10.0));
|
||||
|
||||
revealed = viewport.getOffsetToReveal(target, 1.0, rect: new Rect.fromLTWH(40.0, 40.0, 10.0, 10.0));
|
||||
expect(revealed.offset, 360.0);
|
||||
expect(revealed.rect, new Rect.fromLTWH(40.0, 0.0, 10.0, 10.0));
|
||||
});
|
||||
|
||||
testWidgets('Viewport getOffsetToReveal - left', (WidgetTester tester) async {
|
||||
List<Widget> children;
|
||||
|
||||
await tester.pumpWidget(
|
||||
new Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Center(
|
||||
child: Container(
|
||||
height: 300.0,
|
||||
width: 200.0,
|
||||
child: new ListView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
reverse: true,
|
||||
controller: new ScrollController(initialScrollOffset: 300.0),
|
||||
children: children = new List<Widget>.generate(20, (int i) {
|
||||
return new Container(
|
||||
height: 300.0,
|
||||
width: 100.0,
|
||||
child: new Text('Tile $i'),
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final RenderAbstractViewport viewport = tester.allRenderObjects.firstWhere((RenderObject r) => r is RenderAbstractViewport);
|
||||
|
||||
final RenderObject target = tester.renderObject(find.byWidget(children[5], skipOffstage: false));
|
||||
RevealedOffset revealed = viewport.getOffsetToReveal(target, 0.0);
|
||||
expect(revealed.offset, 500.0);
|
||||
expect(revealed.rect, new Rect.fromLTWH(100.0, 0.0, 100.0, 300.0));
|
||||
|
||||
revealed = viewport.getOffsetToReveal(target, 1.0);
|
||||
expect(revealed.offset, 400.0);
|
||||
expect(revealed.rect, new Rect.fromLTWH(0.0, 0.0, 100.0, 300.0));
|
||||
|
||||
revealed = viewport.getOffsetToReveal(target, 0.0, rect: new Rect.fromLTWH(40.0, 40.0, 10.0, 10.0));
|
||||
expect(revealed.offset, 550.0);
|
||||
expect(revealed.rect, new Rect.fromLTWH(190.0, 40.0, 10.0, 10.0));
|
||||
|
||||
revealed = viewport.getOffsetToReveal(target, 1.0, rect: new Rect.fromLTWH(40.0, 40.0, 10.0, 10.0));
|
||||
expect(revealed.offset, 360.0);
|
||||
expect(revealed.rect, new Rect.fromLTWH(0.0, 40.0, 10.0, 10.0));
|
||||
});
|
||||
|
||||
testWidgets('Nested Viewports showOnScreen', (WidgetTester tester) async {
|
||||
final List<List<Widget>> children = new List<List<Widget>>(10);
|
||||
final List<ScrollController> controllersX = new List<ScrollController>.generate(10, (int i) => new ScrollController(initialScrollOffset: 400.0));
|
||||
final ScrollController controllerY = new ScrollController(initialScrollOffset: 400.0);
|
||||
|
||||
/// Builds a gird:
|
||||
///
|
||||
/// <- x ->
|
||||
/// 0 1 2 3 4 5 6 7 8 9
|
||||
/// 0 c c c c c c c c c c
|
||||
/// 1 c c c c c c c c c c
|
||||
/// 2 c c c c c c c c c c
|
||||
/// 3 c c c c c c c c c c y
|
||||
/// 4 c c c c v v c c c c
|
||||
/// 5 c c c c v v c c c c
|
||||
/// 6 c c c c c c c c c c
|
||||
/// 7 c c c c c c c c c c
|
||||
/// 8 c c c c c c c c c c
|
||||
/// 9 c c c c c c c c c c
|
||||
///
|
||||
/// Each c is a 100x100 container, v are containers visible in initial
|
||||
/// viewport.
|
||||
|
||||
await tester.pumpWidget(
|
||||
new Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Center(
|
||||
child: Container(
|
||||
height: 200.0,
|
||||
width: 200.0,
|
||||
child: new ListView(
|
||||
controller: controllerY,
|
||||
children: new List<Widget>.generate(10, (int y) {
|
||||
return Container(
|
||||
height: 100.0,
|
||||
child: new ListView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
controller: controllersX[y],
|
||||
children: children[y] = new List<Widget>.generate(10, (int x) {
|
||||
return new Container(
|
||||
height: 100.0,
|
||||
width: 100.0,
|
||||
child: new Text('$x,$y'),
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Already in viewport
|
||||
tester.renderObject(find.byWidget(children[4][4], skipOffstage: false)).showOnScreen();
|
||||
await tester.pumpAndSettle();
|
||||
expect(controllersX[4].offset, 400.0);
|
||||
expect(controllerY.offset, 400.0);
|
||||
|
||||
controllersX[4].jumpTo(400.0);
|
||||
controllerY.jumpTo(400.0);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Above viewport
|
||||
tester.renderObject(find.byWidget(children[3][4], skipOffstage: false)).showOnScreen();
|
||||
await tester.pumpAndSettle();
|
||||
expect(controllersX[3].offset, 400.0);
|
||||
expect(controllerY.offset, 300.0);
|
||||
|
||||
controllersX[3].jumpTo(400.0);
|
||||
controllerY.jumpTo(400.0);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Below viewport
|
||||
tester.renderObject(find.byWidget(children[6][4], skipOffstage: false)).showOnScreen();
|
||||
await tester.pumpAndSettle();
|
||||
expect(controllersX[6].offset, 400.0);
|
||||
expect(controllerY.offset, 500.0);
|
||||
|
||||
controllersX[6].jumpTo(400.0);
|
||||
controllerY.jumpTo(400.0);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Left of viewport
|
||||
tester.renderObject(find.byWidget(children[4][3], skipOffstage: false)).showOnScreen();
|
||||
await tester.pumpAndSettle();
|
||||
expect(controllersX[4].offset, 300.0);
|
||||
expect(controllerY.offset, 400.0);
|
||||
|
||||
controllersX[4].jumpTo(400.0);
|
||||
controllerY.jumpTo(400.0);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Right of viewport
|
||||
tester.renderObject(find.byWidget(children[4][6], skipOffstage: false)).showOnScreen();
|
||||
await tester.pumpAndSettle();
|
||||
expect(controllersX[4].offset, 500.0);
|
||||
expect(controllerY.offset, 400.0);
|
||||
|
||||
controllersX[4].jumpTo(400.0);
|
||||
controllerY.jumpTo(400.0);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Above and left of viewport
|
||||
tester.renderObject(find.byWidget(children[3][3], skipOffstage: false)).showOnScreen();
|
||||
await tester.pumpAndSettle();
|
||||
expect(controllersX[3].offset, 300.0);
|
||||
expect(controllerY.offset, 300.0);
|
||||
|
||||
controllersX[3].jumpTo(400.0);
|
||||
controllerY.jumpTo(400.0);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Below and left of viewport
|
||||
tester.renderObject(find.byWidget(children[6][3], skipOffstage: false)).showOnScreen();
|
||||
await tester.pumpAndSettle();
|
||||
expect(controllersX[6].offset, 300.0);
|
||||
expect(controllerY.offset, 500.0);
|
||||
|
||||
controllersX[6].jumpTo(400.0);
|
||||
controllerY.jumpTo(400.0);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Above and right of viewport
|
||||
tester.renderObject(find.byWidget(children[3][6], skipOffstage: false)).showOnScreen();
|
||||
await tester.pumpAndSettle();
|
||||
expect(controllersX[3].offset, 500.0);
|
||||
expect(controllerY.offset, 300.0);
|
||||
|
||||
controllersX[3].jumpTo(400.0);
|
||||
controllerY.jumpTo(400.0);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Below and right of viewport
|
||||
tester.renderObject(find.byWidget(children[6][6], skipOffstage: false)).showOnScreen();
|
||||
await tester.pumpAndSettle();
|
||||
expect(controllersX[6].offset, 500.0);
|
||||
expect(controllerY.offset, 500.0);
|
||||
|
||||
controllersX[6].jumpTo(400.0);
|
||||
controllerY.jumpTo(400.0);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Below and right of viewport with animations
|
||||
tester.renderObject(find.byWidget(children[6][6], skipOffstage: false)).showOnScreen(duration: const Duration(seconds: 2));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
expect(tester.hasRunningAnimations, isTrue);
|
||||
expect(controllersX[6].offset, greaterThan(400.0));
|
||||
expect(controllersX[6].offset, lessThan(500.0));
|
||||
expect(controllerY.offset, greaterThan(400.0));
|
||||
expect(controllerY.offset, lessThan(500.0));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controllersX[6].offset, 500.0);
|
||||
expect(controllerY.offset, 500.0);
|
||||
});
|
||||
|
||||
group('Nested viewports (same orientation) showOnScreen', () {
|
||||
List<Widget> children;
|
||||
|
||||
Future<Null> buildNestedScroller({WidgetTester tester, ScrollController inner, ScrollController outer}) {
|
||||
return tester.pumpWidget(
|
||||
new Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Center(
|
||||
child: Container(
|
||||
height: 200.0,
|
||||
width: 300.0,
|
||||
child: new ListView(
|
||||
controller: outer,
|
||||
children: <Widget>[
|
||||
new Container(
|
||||
height: 200.0,
|
||||
),
|
||||
new Container(
|
||||
height: 200.0,
|
||||
width: 300.0,
|
||||
child: new ListView(
|
||||
controller: inner,
|
||||
children: children = new List<Widget>.generate(10, (int i) {
|
||||
return new Container(
|
||||
height: 100.0,
|
||||
width: 300.0,
|
||||
child: new Text('$i'),
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
new Container(
|
||||
height: 200.0,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('in view in inner, but not in outer', (WidgetTester tester) async {
|
||||
final ScrollController inner = new ScrollController();
|
||||
final ScrollController outer = new ScrollController();
|
||||
await buildNestedScroller(
|
||||
tester: tester,
|
||||
inner: inner,
|
||||
outer: outer,
|
||||
);
|
||||
expect(outer.offset, 0.0);
|
||||
expect(inner.offset, 0.0);
|
||||
|
||||
tester.renderObject(find.byWidget(children[0], skipOffstage: false)).showOnScreen();
|
||||
await tester.pumpAndSettle();
|
||||
expect(inner.offset, 0.0);
|
||||
expect(outer.offset, 100.0);
|
||||
});
|
||||
|
||||
testWidgets('not in view of neither inner nor outer', (WidgetTester tester) async {
|
||||
final ScrollController inner = new ScrollController();
|
||||
final ScrollController outer = new ScrollController();
|
||||
await buildNestedScroller(
|
||||
tester: tester,
|
||||
inner: inner,
|
||||
outer: outer,
|
||||
);
|
||||
expect(outer.offset, 0.0);
|
||||
expect(inner.offset, 0.0);
|
||||
|
||||
tester.renderObject(find.byWidget(children[4], skipOffstage: false)).showOnScreen();
|
||||
await tester.pumpAndSettle();
|
||||
expect(inner.offset, 300.0);
|
||||
expect(outer.offset, 200.0);
|
||||
});
|
||||
|
||||
testWidgets('in view in inner and outer', (WidgetTester tester) async {
|
||||
final ScrollController inner = new ScrollController(initialScrollOffset: 200.0);
|
||||
final ScrollController outer = new ScrollController(initialScrollOffset: 200.0);
|
||||
await buildNestedScroller(
|
||||
tester: tester,
|
||||
inner: inner,
|
||||
outer: outer,
|
||||
);
|
||||
expect(outer.offset, 200.0);
|
||||
expect(inner.offset, 200.0);
|
||||
|
||||
tester.renderObject(find.byWidget(children[2])).showOnScreen();
|
||||
await tester.pumpAndSettle();
|
||||
expect(outer.offset, 200.0);
|
||||
expect(inner.offset, 200.0);
|
||||
});
|
||||
|
||||
testWidgets('inner shown in outer, but item not visible', (WidgetTester tester) async {
|
||||
final ScrollController inner = new ScrollController(initialScrollOffset: 200.0);
|
||||
final ScrollController outer = new ScrollController(initialScrollOffset: 200.0);
|
||||
await buildNestedScroller(
|
||||
tester: tester,
|
||||
inner: inner,
|
||||
outer: outer,
|
||||
);
|
||||
expect(outer.offset, 200.0);
|
||||
expect(inner.offset, 200.0);
|
||||
|
||||
tester.renderObject(find.byWidget(children[5], skipOffstage: false)).showOnScreen();
|
||||
await tester.pumpAndSettle();
|
||||
expect(outer.offset, 200.0);
|
||||
expect(inner.offset, 400.0);
|
||||
});
|
||||
|
||||
testWidgets('inner half shown in outer, item only visible in inner', (WidgetTester tester) async {
|
||||
final ScrollController inner = new ScrollController();
|
||||
final ScrollController outer = new ScrollController(initialScrollOffset: 100.0);
|
||||
await buildNestedScroller(
|
||||
tester: tester,
|
||||
inner: inner,
|
||||
outer: outer,
|
||||
);
|
||||
expect(outer.offset, 100.0);
|
||||
expect(inner.offset, 0.0);
|
||||
|
||||
tester.renderObject(find.byWidget(children[1])).showOnScreen();
|
||||
await tester.pumpAndSettle();
|
||||
expect(outer.offset, 200.0);
|
||||
expect(inner.offset, 0.0);
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets('Viewport showOnScreen with objects larger than viewport', (WidgetTester tester) async {
|
||||
List<Widget> children;
|
||||
ScrollController controller;
|
||||
|
||||
await tester.pumpWidget(
|
||||
new Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Center(
|
||||
child: Container(
|
||||
height: 200.0,
|
||||
child: new ListView(
|
||||
controller: controller = new ScrollController(initialScrollOffset: 300.0),
|
||||
children: children = new List<Widget>.generate(20, (int i) {
|
||||
return new Container(
|
||||
height: 300.0,
|
||||
child: new Text('Tile $i'),
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(controller.offset, 300.0);
|
||||
|
||||
// Already aligned with leading edge, nothing happens.
|
||||
tester.renderObject(find.byWidget(children[1], skipOffstage: false)).showOnScreen();
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.offset, 300.0);
|
||||
|
||||
// Above leading edge aligns trailing edges
|
||||
tester.renderObject(find.byWidget(children[0], skipOffstage: false)).showOnScreen();
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.offset, 100.0);
|
||||
|
||||
// Below trailing edge aligns leading edges
|
||||
tester.renderObject(find.byWidget(children[1], skipOffstage: false)).showOnScreen();
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.offset, 300.0);
|
||||
|
||||
controller.jumpTo(250.0);
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.offset, 250.0);
|
||||
|
||||
// Partly visible across leading edge aligns trailing edges
|
||||
tester.renderObject(find.byWidget(children[0], skipOffstage: false)).showOnScreen();
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.offset, 100.0);
|
||||
|
||||
controller.jumpTo(150.0);
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.offset, 150.0);
|
||||
|
||||
// Partly visible across trailing edge aligns leading edges
|
||||
tester.renderObject(find.byWidget(children[1], skipOffstage: false)).showOnScreen();
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.offset, 300.0);
|
||||
});
|
||||
}
|
@ -743,4 +743,127 @@ void main() {
|
||||
debugDefaultTargetPlatformOverride = null;
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets('ListWheelScrollView getOffsetToReveal', (WidgetTester tester) async {
|
||||
List<Widget> outerChildren;
|
||||
final List<Widget> innerChildren = new List<Widget>(10);
|
||||
|
||||
await tester.pumpWidget(
|
||||
new Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Center(
|
||||
child: Container(
|
||||
height: 500.0,
|
||||
width: 300.0,
|
||||
child: new ListWheelScrollView(
|
||||
controller: new ScrollController(initialScrollOffset: 300.0),
|
||||
itemExtent: 100.0,
|
||||
children: outerChildren = new List<Widget>.generate(10, (int i) {
|
||||
return new Container(
|
||||
child: new Center(
|
||||
child: innerChildren[i] = new Container(
|
||||
height: 50.0,
|
||||
width: 50.0,
|
||||
child: new Text('Item $i'),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final RenderListWheelViewport viewport = tester.allRenderObjects.firstWhere((RenderObject r) => r is RenderListWheelViewport);
|
||||
|
||||
// direct child of viewport
|
||||
RenderObject target = tester.renderObject(find.byWidget(outerChildren[5]));
|
||||
RevealedOffset revealed = viewport.getOffsetToReveal(target, 0.0);
|
||||
expect(revealed.offset, 500.0);
|
||||
expect(revealed.rect, new Rect.fromLTWH(0.0, 200.0, 300.0, 100.0));
|
||||
|
||||
revealed = viewport.getOffsetToReveal(target, 1.0);
|
||||
expect(revealed.offset, 500.0);
|
||||
expect(revealed.rect, new Rect.fromLTWH(0.0, 200.0, 300.0, 100.0));
|
||||
|
||||
revealed = viewport.getOffsetToReveal(target, 0.0, rect: new Rect.fromLTWH(40.0, 40.0, 10.0, 10.0));
|
||||
expect(revealed.offset, 500.0);
|
||||
expect(revealed.rect, new Rect.fromLTWH(40.0, 240.0, 10.0, 10.0));
|
||||
|
||||
revealed = viewport.getOffsetToReveal(target, 1.0, rect: new Rect.fromLTWH(40.0, 40.0, 10.0, 10.0));
|
||||
expect(revealed.offset, 500.0);
|
||||
expect(revealed.rect, new Rect.fromLTWH(40.0, 240.0, 10.0, 10.0));
|
||||
|
||||
// descendant of viewport, not direct child
|
||||
target = tester.renderObject(find.byWidget(innerChildren[5]));
|
||||
revealed = viewport.getOffsetToReveal(target, 0.0);
|
||||
expect(revealed.offset, 500.0);
|
||||
expect(revealed.rect, new Rect.fromLTWH(125.0, 225.0, 50.0, 50.0));
|
||||
|
||||
revealed = viewport.getOffsetToReveal(target, 1.0);
|
||||
expect(revealed.offset, 500.0);
|
||||
expect(revealed.rect, new Rect.fromLTWH(125.0, 225.0, 50.0, 50.0));
|
||||
|
||||
revealed = viewport.getOffsetToReveal(target, 0.0, rect: new Rect.fromLTWH(40.0, 40.0, 10.0, 10.0));
|
||||
expect(revealed.offset, 500.0);
|
||||
expect(revealed.rect, new Rect.fromLTWH(165.0, 265.0, 10.0, 10.0));
|
||||
|
||||
revealed = viewport.getOffsetToReveal(target, 1.0, rect: new Rect.fromLTWH(40.0, 40.0, 10.0, 10.0));
|
||||
expect(revealed.offset, 500.0);
|
||||
expect(revealed.rect, new Rect.fromLTWH(165.0, 265.0, 10.0, 10.0));
|
||||
});
|
||||
|
||||
testWidgets('ListWheelScrollView showOnScreen', (WidgetTester tester) async {
|
||||
List<Widget> outerChildren;
|
||||
final List<Widget> innerChildren = new List<Widget>(10);
|
||||
ScrollController controller;
|
||||
|
||||
await tester.pumpWidget(
|
||||
new Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Center(
|
||||
child: Container(
|
||||
height: 500.0,
|
||||
width: 300.0,
|
||||
child: new ListWheelScrollView(
|
||||
controller: controller = new ScrollController(initialScrollOffset: 300.0),
|
||||
itemExtent: 100.0,
|
||||
children:
|
||||
outerChildren = new List<Widget>.generate(10, (int i) {
|
||||
return new Container(
|
||||
child: new Center(
|
||||
child: innerChildren[i] = new Container(
|
||||
height: 50.0,
|
||||
width: 50.0,
|
||||
child: new Text('Item $i'),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(controller.offset, 300.0);
|
||||
|
||||
tester.renderObject(find.byWidget(outerChildren[5])).showOnScreen();
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.offset, 500.0);
|
||||
|
||||
tester.renderObject(find.byWidget(innerChildren[9])).showOnScreen();
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.offset, 900.0);
|
||||
|
||||
tester.renderObject(find.byWidget(outerChildren[7])).showOnScreen(duration: const Duration(seconds: 2));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
expect(tester.hasRunningAnimations, isTrue);
|
||||
expect(controller.offset, lessThan(900.0));
|
||||
expect(controller.offset, greaterThan(700.0));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.offset, 700.0);
|
||||
});
|
||||
}
|
||||
|
@ -5,6 +5,7 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'semantics_tester.dart';
|
||||
@ -341,4 +342,487 @@ void main() {
|
||||
|
||||
semantics.dispose();
|
||||
});
|
||||
|
||||
testWidgets('SingleChildScrollView getOffsetToReveal - down', (WidgetTester tester) async {
|
||||
List<Widget> children;
|
||||
await tester.pumpWidget(
|
||||
new Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Center(
|
||||
child: Container(
|
||||
height: 200.0,
|
||||
width: 300.0,
|
||||
child: new SingleChildScrollView(
|
||||
controller: new ScrollController(initialScrollOffset: 300.0),
|
||||
child: new Column(
|
||||
children: children = new List<Widget>.generate(20, (int i) {
|
||||
return new Container(
|
||||
height: 100.0,
|
||||
width: 300.0,
|
||||
child: new Text('Tile $i'),
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final RenderAbstractViewport viewport = tester.allRenderObjects.firstWhere((RenderObject r) => r is RenderAbstractViewport);
|
||||
|
||||
final RenderObject target = tester.renderObject(find.byWidget(children[5]));
|
||||
RevealedOffset revealed = viewport.getOffsetToReveal(target, 0.0);
|
||||
expect(revealed.offset, 500.0);
|
||||
expect(revealed.rect, new Rect.fromLTWH(0.0, 0.0, 300.0, 100.0));
|
||||
|
||||
revealed = viewport.getOffsetToReveal(target, 1.0);
|
||||
expect(revealed.offset, 400.0);
|
||||
expect(revealed.rect, new Rect.fromLTWH(0.0, 100.0, 300.0, 100.0));
|
||||
|
||||
revealed = viewport.getOffsetToReveal(target, 0.0, rect: new Rect.fromLTWH(40.0, 40.0, 10.0, 10.0));
|
||||
expect(revealed.offset, 540.0);
|
||||
expect(revealed.rect, new Rect.fromLTWH(40.0, 0.0, 10.0, 10.0));
|
||||
|
||||
revealed = viewport.getOffsetToReveal(target, 1.0, rect: new Rect.fromLTWH(40.0, 40.0, 10.0, 10.0));
|
||||
expect(revealed.offset, 350.0);
|
||||
expect(revealed.rect, new Rect.fromLTWH(40.0, 190.0, 10.0, 10.0));
|
||||
});
|
||||
|
||||
testWidgets('SingleChildScrollView getOffsetToReveal - up', (WidgetTester tester) async {
|
||||
final List<Widget> children = new List<Widget>.generate(20, (int i) {
|
||||
return new Container(
|
||||
height: 100.0,
|
||||
width: 300.0,
|
||||
child: new Text('Tile $i'),
|
||||
);
|
||||
});
|
||||
await tester.pumpWidget(
|
||||
new Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Center(
|
||||
child: Container(
|
||||
height: 200.0,
|
||||
width: 300.0,
|
||||
child: new SingleChildScrollView(
|
||||
controller: new ScrollController(initialScrollOffset: 300.0),
|
||||
reverse: true,
|
||||
child: new Column(
|
||||
children: children.reversed.toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final RenderAbstractViewport viewport = tester.allRenderObjects.firstWhere((RenderObject r) => r is RenderAbstractViewport);
|
||||
|
||||
final RenderObject target = tester.renderObject(find.byWidget(children[5]));
|
||||
RevealedOffset revealed = viewport.getOffsetToReveal(target, 0.0);
|
||||
expect(revealed.offset, 500.0);
|
||||
expect(revealed.rect, new Rect.fromLTWH(0.0, 100.0, 300.0, 100.0));
|
||||
|
||||
revealed = viewport.getOffsetToReveal(target, 1.0);
|
||||
expect(revealed.offset, 400.0);
|
||||
expect(revealed.rect, new Rect.fromLTWH(0.0, 0.0, 300.0, 100.0));
|
||||
|
||||
revealed = viewport.getOffsetToReveal(target, 0.0, rect: new Rect.fromLTWH(40.0, 40.0, 10.0, 10.0));
|
||||
expect(revealed.offset, 550.0);
|
||||
expect(revealed.rect, new Rect.fromLTWH(40.0, 190.0, 10.0, 10.0));
|
||||
|
||||
revealed = viewport.getOffsetToReveal(target, 1.0, rect: new Rect.fromLTWH(40.0, 40.0, 10.0, 10.0));
|
||||
expect(revealed.offset, 360.0);
|
||||
expect(revealed.rect, new Rect.fromLTWH(40.0, 0.0, 10.0, 10.0));
|
||||
});
|
||||
|
||||
testWidgets('SingleChildScrollView getOffsetToReveal - right', (WidgetTester tester) async {
|
||||
List<Widget> children;
|
||||
|
||||
await tester.pumpWidget(
|
||||
new Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Center(
|
||||
child: Container(
|
||||
height: 300.0,
|
||||
width: 200.0,
|
||||
child: new SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
controller: new ScrollController(initialScrollOffset: 300.0),
|
||||
child: new Row(
|
||||
children: children = new List<Widget>.generate(20, (int i) {
|
||||
return new Container(
|
||||
height: 300.0,
|
||||
width: 100.0,
|
||||
child: new Text('Tile $i'),
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final RenderAbstractViewport viewport = tester.allRenderObjects.firstWhere((RenderObject r) => r is RenderAbstractViewport);
|
||||
|
||||
final RenderObject target = tester.renderObject(find.byWidget(children[5]));
|
||||
RevealedOffset revealed = viewport.getOffsetToReveal(target, 0.0);
|
||||
expect(revealed.offset, 500.0);
|
||||
expect(revealed.rect, new Rect.fromLTWH(0.0, 0.0, 100.0, 300.0));
|
||||
|
||||
revealed = viewport.getOffsetToReveal(target, 1.0);
|
||||
expect(revealed.offset, 400.0);
|
||||
expect(revealed.rect, new Rect.fromLTWH(100.0, 0.0, 100.0, 300.0));
|
||||
|
||||
revealed = viewport.getOffsetToReveal(target, 0.0, rect: new Rect.fromLTWH(40.0, 40.0, 10.0, 10.0));
|
||||
expect(revealed.offset, 540.0);
|
||||
expect(revealed.rect, new Rect.fromLTWH(0.0, 40.0, 10.0, 10.0));
|
||||
|
||||
revealed = viewport.getOffsetToReveal(target, 1.0, rect: new Rect.fromLTWH(40.0, 40.0, 10.0, 10.0));
|
||||
expect(revealed.offset, 350.0);
|
||||
expect(revealed.rect, new Rect.fromLTWH(190.0, 40.0, 10.0, 10.0));
|
||||
});
|
||||
|
||||
testWidgets('SingleChildScrollView getOffsetToReveal - left', (WidgetTester tester) async {
|
||||
final List<Widget> children = new List<Widget>.generate(20, (int i) {
|
||||
return new Container(
|
||||
height: 300.0,
|
||||
width: 100.0,
|
||||
child: new Text('Tile $i'),
|
||||
);
|
||||
});
|
||||
|
||||
await tester.pumpWidget(
|
||||
new Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Center(
|
||||
child: Container(
|
||||
height: 300.0,
|
||||
width: 200.0,
|
||||
child: new SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
reverse: true,
|
||||
controller: new ScrollController(initialScrollOffset: 300.0),
|
||||
child: new Row(
|
||||
children: children.reversed.toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final RenderAbstractViewport viewport = tester.allRenderObjects.firstWhere((RenderObject r) => r is RenderAbstractViewport);
|
||||
|
||||
final RenderObject target = tester.renderObject(find.byWidget(children[5]));
|
||||
RevealedOffset revealed = viewport.getOffsetToReveal(target, 0.0);
|
||||
expect(revealed.offset, 500.0);
|
||||
expect(revealed.rect, new Rect.fromLTWH(100.0, 0.0, 100.0, 300.0));
|
||||
|
||||
revealed = viewport.getOffsetToReveal(target, 1.0);
|
||||
expect(revealed.offset, 400.0);
|
||||
expect(revealed.rect, new Rect.fromLTWH(0.0, 0.0, 100.0, 300.0));
|
||||
|
||||
revealed = viewport.getOffsetToReveal(target, 0.0, rect: new Rect.fromLTWH(40.0, 40.0, 10.0, 10.0));
|
||||
expect(revealed.offset, 550.0);
|
||||
expect(revealed.rect, new Rect.fromLTWH(190.0, 40.0, 10.0, 10.0));
|
||||
|
||||
revealed = viewport.getOffsetToReveal(target, 1.0, rect: new Rect.fromLTWH(40.0, 40.0, 10.0, 10.0));
|
||||
expect(revealed.offset, 360.0);
|
||||
expect(revealed.rect, new Rect.fromLTWH(0.0, 40.0, 10.0, 10.0));
|
||||
});
|
||||
|
||||
testWidgets('Nested SingleChildScrollView showOnScreen', (WidgetTester tester) async {
|
||||
final List<List<Widget>> children = new List<List<Widget>>(10);
|
||||
ScrollController controllerX;
|
||||
ScrollController controllerY;
|
||||
|
||||
/// Builds a gird:
|
||||
///
|
||||
/// <- x ->
|
||||
/// 0 1 2 3 4 5 6 7 8 9
|
||||
/// 0 c c c c c c c c c c
|
||||
/// 1 c c c c c c c c c c
|
||||
/// 2 c c c c c c c c c c
|
||||
/// 3 c c c c c c c c c c y
|
||||
/// 4 c c c c v v c c c c
|
||||
/// 5 c c c c v v c c c c
|
||||
/// 6 c c c c c c c c c c
|
||||
/// 7 c c c c c c c c c c
|
||||
/// 8 c c c c c c c c c c
|
||||
/// 9 c c c c c c c c c c
|
||||
///
|
||||
/// Each c is a 100x100 container, v are containers visible in initial
|
||||
/// viewport.
|
||||
|
||||
await tester.pumpWidget(
|
||||
new Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Center(
|
||||
child: Container(
|
||||
height: 200.0,
|
||||
width: 200.0,
|
||||
child: new SingleChildScrollView(
|
||||
controller: controllerY = new ScrollController(initialScrollOffset: 400.0),
|
||||
child: new SingleChildScrollView(
|
||||
controller: controllerX = new ScrollController(initialScrollOffset: 400.0),
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: new Column(
|
||||
children: new List<Widget>.generate(10, (int y) {
|
||||
return new Row(
|
||||
children: children[y] = new List<Widget>.generate(10, (int x) {
|
||||
return new Container(
|
||||
height: 100.0,
|
||||
width: 100.0,
|
||||
);
|
||||
})
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(controllerX.offset, 400.0);
|
||||
expect(controllerY.offset, 400.0);
|
||||
|
||||
// Already in viewport
|
||||
tester.renderObject(find.byWidget(children[4][4])).showOnScreen();
|
||||
await tester.pumpAndSettle();
|
||||
expect(controllerX.offset, 400.0);
|
||||
expect(controllerY.offset, 400.0);
|
||||
|
||||
controllerX.jumpTo(400.0);
|
||||
controllerY.jumpTo(400.0);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Above viewport
|
||||
tester.renderObject(find.byWidget(children[3][4])).showOnScreen();
|
||||
await tester.pumpAndSettle();
|
||||
expect(controllerX.offset, 400.0);
|
||||
expect(controllerY.offset, 300.0);
|
||||
|
||||
controllerX.jumpTo(400.0);
|
||||
controllerY.jumpTo(400.0);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Below viewport
|
||||
tester.renderObject(find.byWidget(children[6][4])).showOnScreen();
|
||||
await tester.pumpAndSettle();
|
||||
expect(controllerX.offset, 400.0);
|
||||
expect(controllerY.offset, 500.0);
|
||||
|
||||
controllerX.jumpTo(400.0);
|
||||
controllerY.jumpTo(400.0);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Left of viewport
|
||||
tester.renderObject(find.byWidget(children[4][3])).showOnScreen();
|
||||
await tester.pumpAndSettle();
|
||||
expect(controllerX.offset, 300.0);
|
||||
expect(controllerY.offset, 400.0);
|
||||
|
||||
controllerX.jumpTo(400.0);
|
||||
controllerY.jumpTo(400.0);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Right of viewport
|
||||
tester.renderObject(find.byWidget(children[4][6])).showOnScreen();
|
||||
await tester.pumpAndSettle();
|
||||
expect(controllerX.offset, 500.0);
|
||||
expect(controllerY.offset, 400.0);
|
||||
|
||||
controllerX.jumpTo(400.0);
|
||||
controllerY.jumpTo(400.0);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Above and left of viewport
|
||||
tester.renderObject(find.byWidget(children[3][3])).showOnScreen();
|
||||
await tester.pumpAndSettle();
|
||||
expect(controllerX.offset, 300.0);
|
||||
expect(controllerY.offset, 300.0);
|
||||
|
||||
controllerX.jumpTo(400.0);
|
||||
controllerY.jumpTo(400.0);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Below and left of viewport
|
||||
tester.renderObject(find.byWidget(children[6][3])).showOnScreen();
|
||||
await tester.pumpAndSettle();
|
||||
expect(controllerX.offset, 300.0);
|
||||
expect(controllerY.offset, 500.0);
|
||||
|
||||
controllerX.jumpTo(400.0);
|
||||
controllerY.jumpTo(400.0);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Above and right of viewport
|
||||
tester.renderObject(find.byWidget(children[3][6])).showOnScreen();
|
||||
await tester.pumpAndSettle();
|
||||
expect(controllerX.offset, 500.0);
|
||||
expect(controllerY.offset, 300.0);
|
||||
|
||||
controllerX.jumpTo(400.0);
|
||||
controllerY.jumpTo(400.0);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Below and right of viewport
|
||||
tester.renderObject(find.byWidget(children[6][6])).showOnScreen();
|
||||
await tester.pumpAndSettle();
|
||||
expect(controllerX.offset, 500.0);
|
||||
expect(controllerY.offset, 500.0);
|
||||
|
||||
controllerX.jumpTo(400.0);
|
||||
controllerY.jumpTo(400.0);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Below and right of viewport with animations
|
||||
tester.renderObject(find.byWidget(children[6][6])).showOnScreen(duration: const Duration(seconds: 2));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
expect(tester.hasRunningAnimations, isTrue);
|
||||
expect(controllerX.offset, greaterThan(400.0));
|
||||
expect(controllerX.offset, lessThan(500.0));
|
||||
expect(controllerY.offset, greaterThan(400.0));
|
||||
expect(controllerY.offset, lessThan(500.0));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controllerX.offset, 500.0);
|
||||
expect(controllerY.offset, 500.0);
|
||||
});
|
||||
|
||||
group('Nested SingleChildScrollView (same orientation) showOnScreen', () {
|
||||
List<Widget> children;
|
||||
|
||||
Future<Null> buildNestedScroller({WidgetTester tester, ScrollController inner, ScrollController outer}) {
|
||||
return tester.pumpWidget(
|
||||
new Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Center(
|
||||
child: Container(
|
||||
height: 200.0,
|
||||
width: 300.0,
|
||||
child: new SingleChildScrollView(
|
||||
controller: outer,
|
||||
child: new Column(
|
||||
children: <Widget>[
|
||||
new Container(
|
||||
height: 200.0,
|
||||
),
|
||||
new Container(
|
||||
height: 200.0,
|
||||
width: 300.0,
|
||||
child: new SingleChildScrollView(
|
||||
controller: inner,
|
||||
child: new Column(
|
||||
children: children = new List<Widget>.generate(10, (int i) {
|
||||
return new Container(
|
||||
height: 100.0,
|
||||
width: 300.0,
|
||||
child: new Text('$i'),
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
new Container(
|
||||
height: 200.0,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('in view in inner, but not in outer', (WidgetTester tester) async {
|
||||
final ScrollController inner = new ScrollController();
|
||||
final ScrollController outer = new ScrollController();
|
||||
await buildNestedScroller(
|
||||
tester: tester,
|
||||
inner: inner,
|
||||
outer: outer,
|
||||
);
|
||||
expect(outer.offset, 0.0);
|
||||
expect(inner.offset, 0.0);
|
||||
|
||||
tester.renderObject(find.byWidget(children[0])).showOnScreen();
|
||||
await tester.pumpAndSettle();
|
||||
expect(inner.offset, 0.0);
|
||||
expect(outer.offset, 100.0);
|
||||
});
|
||||
|
||||
testWidgets('not in view of neither inner nor outer', (WidgetTester tester) async {
|
||||
final ScrollController inner = new ScrollController();
|
||||
final ScrollController outer = new ScrollController();
|
||||
await buildNestedScroller(
|
||||
tester: tester,
|
||||
inner: inner,
|
||||
outer: outer,
|
||||
);
|
||||
expect(outer.offset, 0.0);
|
||||
expect(inner.offset, 0.0);
|
||||
|
||||
tester.renderObject(find.byWidget(children[5])).showOnScreen();
|
||||
await tester.pumpAndSettle();
|
||||
expect(inner.offset, 400.0);
|
||||
expect(outer.offset, 200.0);
|
||||
});
|
||||
|
||||
testWidgets('in view in inner and outer', (WidgetTester tester) async {
|
||||
final ScrollController inner = new ScrollController(initialScrollOffset: 200.0);
|
||||
final ScrollController outer = new ScrollController(initialScrollOffset: 200.0);
|
||||
await buildNestedScroller(
|
||||
tester: tester,
|
||||
inner: inner,
|
||||
outer: outer,
|
||||
);
|
||||
expect(outer.offset, 200.0);
|
||||
expect(inner.offset, 200.0);
|
||||
|
||||
tester.renderObject(find.byWidget(children[2])).showOnScreen();
|
||||
await tester.pumpAndSettle();
|
||||
expect(outer.offset, 200.0);
|
||||
expect(inner.offset, 200.0);
|
||||
});
|
||||
|
||||
testWidgets('inner shown in outer, but item not visible', (WidgetTester tester) async {
|
||||
final ScrollController inner = new ScrollController(initialScrollOffset: 200.0);
|
||||
final ScrollController outer = new ScrollController(initialScrollOffset: 200.0);
|
||||
await buildNestedScroller(
|
||||
tester: tester,
|
||||
inner: inner,
|
||||
outer: outer,
|
||||
);
|
||||
expect(outer.offset, 200.0);
|
||||
expect(inner.offset, 200.0);
|
||||
|
||||
tester.renderObject(find.byWidget(children[5])).showOnScreen();
|
||||
await tester.pumpAndSettle();
|
||||
expect(outer.offset, 200.0);
|
||||
expect(inner.offset, 400.0);
|
||||
});
|
||||
|
||||
testWidgets('inner half shown in outer, item only visible in inner', (WidgetTester tester) async {
|
||||
final ScrollController inner = new ScrollController();
|
||||
final ScrollController outer = new ScrollController(initialScrollOffset: 100.0);
|
||||
await buildNestedScroller(
|
||||
tester: tester,
|
||||
inner: inner,
|
||||
outer: outer,
|
||||
);
|
||||
expect(outer.offset, 100.0);
|
||||
expect(inner.offset, 0.0);
|
||||
|
||||
tester.renderObject(find.byWidget(children[1])).showOnScreen();
|
||||
await tester.pumpAndSettle();
|
||||
expect(outer.offset, 200.0);
|
||||
expect(inner.offset, 0.0);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user