Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
80fb7bd2
Unverified
Commit
80fb7bd2
authored
Sep 27, 2023
by
Kate Lovett
Committed by
GitHub
Sep 27, 2023
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Support ensureVisible/showOnScreen/showInViewport for 2D Scrolling (#135182)
parent
47f12cae
Changes
9
Show whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
1026 additions
and
58 deletions
+1026
-58
list_wheel_viewport.dart
packages/flutter/lib/src/rendering/list_wheel_viewport.dart
+9
-2
viewport.dart
packages/flutter/lib/src/rendering/viewport.dart
+80
-41
scroll_position.dart
packages/flutter/lib/src/widgets/scroll_position.dart
+21
-3
scrollable.dart
packages/flutter/lib/src/widgets/scrollable.dart
+89
-2
single_child_scroll_view.dart
...ges/flutter/lib/src/widgets/single_child_scroll_view.dart
+11
-1
two_dimensional_viewport.dart
...ges/flutter/lib/src/widgets/two_dimensional_viewport.dart
+269
-9
ensure_visible_test.dart
packages/flutter/test/widgets/ensure_visible_test.dart
+277
-0
two_dimensional_utils.dart
packages/flutter/test/widgets/two_dimensional_utils.dart
+1
-0
two_dimensional_viewport_test.dart
...s/flutter/test/widgets/two_dimensional_viewport_test.dart
+269
-0
No files found.
packages/flutter/lib/src/rendering/list_wheel_viewport.dart
View file @
80fb7bd2
...
...
@@ -1121,11 +1121,18 @@ class RenderListWheelViewport
}
@override
RevealedOffset
getOffsetToReveal
(
RenderObject
target
,
double
alignment
,
{
Rect
?
rect
})
{
RevealedOffset
getOffsetToReveal
(
RenderObject
target
,
double
alignment
,
{
Rect
?
rect
,
Axis
?
axis
,
})
{
// One dimensional viewport has only one axis, it should match if it has
// been provided.
assert
(
axis
==
null
||
axis
==
Axis
.
vertical
);
// `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.
rect
??=
target
.
paintBounds
;
// `child` will be the last RenderObject before the viewport when walking up from `target`.
...
...
packages/flutter/lib/src/rendering/viewport.dart
View file @
80fb7bd2
...
...
@@ -108,10 +108,22 @@ abstract interface class RenderAbstractViewport extends RenderObject {
/// when the offset of the viewport is changed by x then `target` also moves
/// by x within the viewport.
///
/// The optional [Axis] is used by
/// [RenderTwoDimensionalViewport.getOffsetToReveal] to
/// determine which of the two axes to compute an offset for. One dimensional
/// subclasses like [RenderViewportBase] and [RenderListWheelViewport] will
/// assert in debug builds if the `axis` value is provided and does not match
/// the single [Axis] that viewport is configured for.
///
/// See also:
///
/// * [RevealedOffset], which describes the return value of this method.
RevealedOffset
getOffsetToReveal
(
RenderObject
target
,
double
alignment
,
{
Rect
?
rect
});
RevealedOffset
getOffsetToReveal
(
RenderObject
target
,
double
alignment
,
{
Rect
?
rect
,
Axis
?
axis
,
});
/// The default value for the cache extent of the viewport.
///
...
...
@@ -169,6 +181,56 @@ class RevealedOffset {
/// value for a specific element.
final
Rect
rect
;
/// Determines which provided leading or trailing edge of the viewport, as
/// [RevealedOffset]s, will be used for [RenderViewportBase.showInViewport]
/// accounting for the size and already visible portion of the [RenderObject]
/// that is being revealed.
///
/// Also used by [RenderTwoDimensionalViewport.showInViewport] for each
/// horizontal and vertical [Axis].
///
/// If the target [RenderObject] is already fully visible, this will return
/// null.
static
RevealedOffset
?
clampOffset
({
required
RevealedOffset
leadingEdgeOffset
,
required
RevealedOffset
trailingEdgeOffset
,
required
double
currentOffset
,
})
{
// 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.
// `leadingEdgeOffset`: Distance from scrollOffset 0 to the start of the
// viewport on the right in image above.
//
// The viewport position on the left is achieved by setting `offset.pixels`
// to `trailingEdgeOffset`, the one on the right by setting it to
// `leadingEdgeOffset`.
final
bool
inverted
=
leadingEdgeOffset
.
offset
<
trailingEdgeOffset
.
offset
;
final
RevealedOffset
smaller
;
final
RevealedOffset
larger
;
(
smaller
,
larger
)
=
inverted
?
(
leadingEdgeOffset
,
trailingEdgeOffset
)
:
(
trailingEdgeOffset
,
leadingEdgeOffset
);
if
(
currentOffset
>
larger
.
offset
)
{
return
larger
;
}
else
if
(
currentOffset
<
smaller
.
offset
)
{
return
smaller
;
}
else
{
return
null
;
}
}
@override
String
toString
()
{
return
'
${objectRuntimeType(this, 'RevealedOffset')}
(offset:
$offset
, rect:
$rect
)'
;
...
...
@@ -753,7 +815,17 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix
}
@override
RevealedOffset
getOffsetToReveal
(
RenderObject
target
,
double
alignment
,
{
Rect
?
rect
})
{
RevealedOffset
getOffsetToReveal
(
RenderObject
target
,
double
alignment
,
{
Rect
?
rect
,
Axis
?
axis
,
})
{
// One dimensional viewport has only one axis, it should match if it has
// been provided.
axis
??=
this
.
axis
;
assert
(
axis
==
this
.
axis
);
// Steps to convert `rect` (from a RenderBox coordinate system) to its
// scroll offset within this viewport (not in the exact order):
//
...
...
@@ -1164,44 +1236,12 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix
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 `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.
// `leadingEdgeOffset`: Distance from scrollOffset 0 to the start of the
// viewport on the right in image above.
//
// The viewport position on the left is achieved by setting `offset.pixels`
// to `trailingEdgeOffset`, the one on the right by setting it to
// `leadingEdgeOffset`.
final
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
{
final
RevealedOffset
?
targetOffset
=
RevealedOffset
.
clampOffset
(
leadingEdgeOffset:
leadingEdgeOffset
,
trailingEdgeOffset:
trailingEdgeOffset
,
currentOffset:
currentOffset
,
);
if
(
targetOffset
==
null
)
{
// `descendant` is between leading and trailing edge and hence already
// fully shown on screen. No action necessary.
assert
(
viewport
.
parent
!=
null
);
...
...
@@ -1209,7 +1249,6 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix
return
MatrixUtils
.
transformRect
(
transform
,
rect
??
descendant
.
paintBounds
);
}
offset
.
moveTo
(
targetOffset
.
offset
,
duration:
duration
,
curve:
curve
);
return
targetOffset
.
rect
;
}
...
...
packages/flutter/lib/src/widgets/scroll_position.dart
View file @
80fb7bd2
...
...
@@ -810,14 +810,32 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics {
double
target
;
switch
(
_applyAxisDirectionToAlignmentPolicy
(
alignmentPolicy
))
{
case
ScrollPositionAlignmentPolicy
.
explicit
:
target
=
clampDouble
(
viewport
.
getOffsetToReveal
(
object
,
alignment
,
rect:
targetRect
).
offset
,
minScrollExtent
,
maxScrollExtent
);
target
=
viewport
.
getOffsetToReveal
(
object
,
alignment
,
rect:
targetRect
,
axis:
axis
,
).
offset
;
target
=
clampDouble
(
target
,
minScrollExtent
,
maxScrollExtent
);
case
ScrollPositionAlignmentPolicy
.
keepVisibleAtEnd
:
target
=
clampDouble
(
viewport
.
getOffsetToReveal
(
object
,
1.0
,
rect:
targetRect
).
offset
,
minScrollExtent
,
maxScrollExtent
);
target
=
viewport
.
getOffsetToReveal
(
object
,
1.0
,
// Aligns to end
rect:
targetRect
,
axis:
axis
,
).
offset
;
target
=
clampDouble
(
target
,
minScrollExtent
,
maxScrollExtent
);
if
(
target
<
pixels
)
{
target
=
pixels
;
}
case
ScrollPositionAlignmentPolicy
.
keepVisibleAtStart
:
target
=
clampDouble
(
viewport
.
getOffsetToReveal
(
object
,
0.0
,
rect:
targetRect
).
offset
,
minScrollExtent
,
maxScrollExtent
);
target
=
viewport
.
getOffsetToReveal
(
object
,
0.0
,
// Aligns to start
rect:
targetRect
,
axis:
axis
,
).
offset
;
target
=
clampDouble
(
target
,
minScrollExtent
,
maxScrollExtent
);
if
(
target
>
pixels
)
{
target
=
pixels
;
}
...
...
packages/flutter/lib/src/widgets/scrollable.dart
View file @
80fb7bd2
...
...
@@ -44,6 +44,13 @@ typedef ViewportBuilder = Widget Function(BuildContext context, ViewportOffset p
/// which the scrollable content is displayed.
typedef
TwoDimensionalViewportBuilder
=
Widget
Function
(
BuildContext
context
,
ViewportOffset
verticalPosition
,
ViewportOffset
horizontalPosition
);
// The return type of _performEnsureVisible.
//
// The list of futures represents each pending ScrollPosition call to
// ensureVisible. The returned ScrollableState's context is used to find the
// next potential ancestor Scrollable.
typedef
_EnsureVisibleResults
=
(
List
<
Future
<
void
>>,
ScrollableState
);
/// A widget that manages scrolling in one dimension and informs the [Viewport]
/// through which the content is viewed.
///
...
...
@@ -441,6 +448,10 @@ class Scrollable extends StatefulWidget {
/// Scrolls the scrollables that enclose the given context so as to make the
/// given context visible.
///
/// If the [Scrollable] of the provided [BuildContext] is a
/// [TwoDimensionalScrollable], both vertical and horizontal axes will ensure
/// the target is made visible.
static
Future
<
void
>
ensureVisible
(
BuildContext
context
,
{
double
alignment
=
0.0
,
...
...
@@ -459,14 +470,16 @@ class Scrollable extends StatefulWidget {
RenderObject
?
targetRenderObject
;
ScrollableState
?
scrollable
=
Scrollable
.
maybeOf
(
context
);
while
(
scrollable
!=
null
)
{
futures
.
add
(
scrollable
.
position
.
ensureVisible
(
final
List
<
Future
<
void
>>
newFutures
;
(
newFutures
,
scrollable
)
=
scrollable
.
_performEnsureVisible
(
context
.
findRenderObject
()!,
alignment:
alignment
,
duration:
duration
,
curve:
curve
,
alignmentPolicy:
alignmentPolicy
,
targetRenderObject:
targetRenderObject
,
));
);
futures
.
addAll
(
newFutures
);
targetRenderObject
=
targetRenderObject
??
context
.
findRenderObject
();
context
=
scrollable
.
context
;
...
...
@@ -1011,6 +1024,28 @@ class ScrollableState extends State<Scrollable> with TickerProviderStateMixin, R
return
result
;
}
// Returns the Future from calling ensureVisible for the ScrollPosition, as
// as well as this ScrollableState instance so its context can be used to
// check for other ancestor Scrollables in executing ensureVisible.
_EnsureVisibleResults
_performEnsureVisible
(
RenderObject
object
,
{
double
alignment
=
0.0
,
Duration
duration
=
Duration
.
zero
,
Curve
curve
=
Curves
.
ease
,
ScrollPositionAlignmentPolicy
alignmentPolicy
=
ScrollPositionAlignmentPolicy
.
explicit
,
RenderObject
?
targetRenderObject
,
})
{
final
Future
<
void
>
ensureVisibleFuture
=
position
.
ensureVisible
(
object
,
alignment:
alignment
,
duration:
duration
,
curve:
curve
,
alignmentPolicy:
alignmentPolicy
,
targetRenderObject:
targetRenderObject
,
);
return
(<
Future
<
void
>>[
ensureVisibleFuture
],
this
);
}
@override
void
debugFillProperties
(
DiagnosticPropertiesBuilder
properties
)
{
super
.
debugFillProperties
(
properties
);
...
...
@@ -2040,6 +2075,25 @@ class _VerticalOuterDimension extends Scrollable {
class
_VerticalOuterDimensionState
extends
ScrollableState
{
DiagonalDragBehavior
get
diagonalDragBehavior
=>
(
widget
as
_VerticalOuterDimension
).
diagonalDragBehavior
;
// Implemented in the _HorizontalInnerDimension instead.
@override
_EnsureVisibleResults
_performEnsureVisible
(
RenderObject
object
,
{
double
alignment
=
0.0
,
Duration
duration
=
Duration
.
zero
,
Curve
curve
=
Curves
.
ease
,
ScrollPositionAlignmentPolicy
alignmentPolicy
=
ScrollPositionAlignmentPolicy
.
explicit
,
RenderObject
?
targetRenderObject
,
})
{
assert
(
false
,
'The _performEnsureVisible method was called for the vertical scrollable '
'of a TwoDimensionalScrollable. This should not happen as the horizontal '
'scrollable handles both axes.'
);
return
(<
Future
<
void
>>[],
this
);
}
@override
void
setCanDrag
(
bool
value
)
{
switch
(
diagonalDragBehavior
)
{
...
...
@@ -2119,6 +2173,39 @@ class _HorizontalInnerDimensionState extends ScrollableState {
super
.
didChangeDependencies
();
}
// Returns the Future from calling ensureVisible for the ScrollPosition, as
// as well as the vertical ScrollableState instance so its context can be
// used to check for other ancestor Scrollables in executing ensureVisible.
@override
_EnsureVisibleResults
_performEnsureVisible
(
RenderObject
object
,
{
double
alignment
=
0.0
,
Duration
duration
=
Duration
.
zero
,
Curve
curve
=
Curves
.
ease
,
ScrollPositionAlignmentPolicy
alignmentPolicy
=
ScrollPositionAlignmentPolicy
.
explicit
,
RenderObject
?
targetRenderObject
,
})
{
final
List
<
Future
<
void
>>
newFutures
=
<
Future
<
void
>>[];
newFutures
.
add
(
position
.
ensureVisible
(
object
,
alignment:
alignment
,
duration:
duration
,
curve:
curve
,
alignmentPolicy:
alignmentPolicy
,
));
newFutures
.
add
(
verticalScrollable
.
position
.
ensureVisible
(
object
,
alignment:
alignment
,
duration:
duration
,
curve:
curve
,
alignmentPolicy:
alignmentPolicy
,
));
return
(
newFutures
,
verticalScrollable
);
}
void
_evaluateLockedAxis
(
Offset
offset
)
{
assert
(
lastDragOffset
!=
null
);
final
Offset
offsetDelta
=
lastDragOffset
!
-
offset
;
...
...
packages/flutter/lib/src/widgets/single_child_scroll_view.dart
View file @
80fb7bd2
...
...
@@ -592,7 +592,17 @@ class _RenderSingleChildViewport extends RenderBox with RenderObjectWithChildMix
}
@override
RevealedOffset
getOffsetToReveal
(
RenderObject
target
,
double
alignment
,
{
Rect
?
rect
})
{
RevealedOffset
getOffsetToReveal
(
RenderObject
target
,
double
alignment
,
{
Rect
?
rect
,
Axis
?
axis
,
})
{
// One dimensional viewport has only one axis, it should match if it has
// been provided.
axis
??=
this
.
axis
;
assert
(
axis
==
this
.
axis
);
rect
??=
target
.
paintBounds
;
if
(
target
is
!
RenderBox
)
{
return
RevealedOffset
(
offset:
offset
.
pixels
,
rect:
rect
);
...
...
packages/flutter/lib/src/widgets/two_dimensional_viewport.dart
View file @
80fb7bd2
...
...
@@ -4,6 +4,7 @@
import
'dart:math'
as
math
;
import
'package:flutter/animation.dart'
;
import
'package:flutter/rendering.dart'
;
import
'framework.dart'
;
...
...
@@ -497,7 +498,6 @@ class TwoDimensionalViewportParentData extends ParentData with KeepAliveParentD
///
/// Subclasses should not override [performLayout], as it handles housekeeping
/// on either side of the call to [layoutChildSequence].
// TODO(Piinks): ensureVisible https://github.com/flutter/flutter/issues/126299
abstract class RenderTwoDimensionalViewport extends RenderBox implements RenderAbstractViewport {
/// Initializes fields for subclasses.
///
...
...
@@ -848,11 +848,7 @@ abstract class RenderTwoDimensionalViewport extends RenderBox implements RenderA
RenderBox
?
child
=
_firstChild
;
while
(
child
!=
null
)
{
final
TwoDimensionalViewportParentData
childParentData
=
parentDataOf
(
child
);
// TODO(Piinks): When ensure visible is supported, remove this isVisible
// condition.
if
(
childParentData
.
isVisible
)
{
visitor
(
child
);
}
child
=
childParentData
.
_nextSibling
;
}
// Do not visit children in [_keepAliveBucket].
...
...
@@ -920,10 +916,274 @@ abstract class RenderTwoDimensionalViewport extends RenderBox implements RenderA
}
}
@protected
@override
RevealedOffset
getOffsetToReveal
(
RenderObject
target
,
double
alignment
,
{
Rect
?
rect
})
{
// TODO(Piinks): Add this back in follow up change (ensureVisible), https://github.com/flutter/flutter/issues/126299
return
const
RevealedOffset
(
offset:
0.0
,
rect:
Rect
.
zero
);
RevealedOffset
getOffsetToReveal
(
RenderObject
target
,
double
alignment
,
{
Rect
?
rect
,
Axis
?
axis
,
})
{
// We must know which axis we are revealing for, since RevealedOffset
// refers to only one of two scroll positions.
assert
(
axis
!=
null
);
final
(
double
offset
,
AxisDirection
axisDirection
)
=
switch
(
axis
!)
{
Axis
.
vertical
=>
(
verticalOffset
.
pixels
,
verticalAxisDirection
),
Axis
.
horizontal
=>
(
horizontalOffset
.
pixels
,
horizontalAxisDirection
),
};
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
!;
}
assert
(
child
.
parent
==
this
);
final
RenderBox
box
=
child
as
RenderBox
;
final
Rect
rectLocal
=
MatrixUtils
.
transformRect
(
target
.
getTransformTo
(
child
),
rect
);
final
double
targetMainAxisExtent
;
double
leadingScrollOffset
=
offset
;
// The scroll offset of `rect` within `child`.
switch
(
axisDirection
)
{
case
AxisDirection
.
up
:
leadingScrollOffset
+=
child
.
size
.
height
-
rectLocal
.
bottom
;
targetMainAxisExtent
=
rectLocal
.
height
;
case
AxisDirection
.
right
:
leadingScrollOffset
+=
rectLocal
.
left
;
targetMainAxisExtent
=
rectLocal
.
width
;
case
AxisDirection
.
down
:
leadingScrollOffset
+=
rectLocal
.
top
;
targetMainAxisExtent
=
rectLocal
.
height
;
case
AxisDirection
.
left
:
leadingScrollOffset
+=
child
.
size
.
width
-
rectLocal
.
right
;
targetMainAxisExtent
=
rectLocal
.
width
;
}
// The scroll offset in the viewport to `rect`.
final
TwoDimensionalViewportParentData
childParentData
=
parentDataOf
(
box
);
leadingScrollOffset
+=
switch
(
axisDirection
)
{
AxisDirection
.
down
=>
childParentData
.
paintOffset
!.
dy
,
AxisDirection
.
up
=>
viewportDimension
.
height
-
childParentData
.
paintOffset
!.
dy
-
box
.
size
.
height
,
AxisDirection
.
right
=>
childParentData
.
paintOffset
!.
dx
,
AxisDirection
.
left
=>
viewportDimension
.
width
-
childParentData
.
paintOffset
!.
dx
-
box
.
size
.
width
,
};
// This step assumes the viewport's layout is up-to-date, i.e., if
// the position is changed after the last performLayout, the new scroll
// position will not be accounted for.
final
Matrix4
transform
=
target
.
getTransformTo
(
this
);
Rect
targetRect
=
MatrixUtils
.
transformRect
(
transform
,
rect
);
final
double
mainAxisExtent
=
switch
(
axisDirectionToAxis
(
axisDirection
))
{
Axis
.
horizontal
=>
viewportDimension
.
width
,
Axis
.
vertical
=>
viewportDimension
.
height
,
};
final
double
targetOffset
=
leadingScrollOffset
-
(
mainAxisExtent
-
targetMainAxisExtent
)
*
alignment
;
final
double
offsetDifference
=
switch
(
axisDirectionToAxis
(
axisDirection
)){
Axis
.
vertical
=>
verticalOffset
.
pixels
-
targetOffset
,
Axis
.
horizontal
=>
horizontalOffset
.
pixels
-
targetOffset
,
};
switch
(
axisDirection
)
{
case
AxisDirection
.
down
:
targetRect
=
targetRect
.
translate
(
0.0
,
offsetDifference
);
case
AxisDirection
.
right
:
targetRect
=
targetRect
.
translate
(
offsetDifference
,
0.0
);
case
AxisDirection
.
up
:
targetRect
=
targetRect
.
translate
(
0.0
,
-
offsetDifference
);
case
AxisDirection
.
left
:
targetRect
=
targetRect
.
translate
(-
offsetDifference
,
0.0
);
}
final
RevealedOffset
revealedOffset
=
RevealedOffset
(
offset:
targetOffset
,
rect:
targetRect
,
);
return
revealedOffset
;
}
@override
void
showOnScreen
({
RenderObject
?
descendant
,
Rect
?
rect
,
Duration
duration
=
Duration
.
zero
,
Curve
curve
=
Curves
.
ease
,
})
{
// It is possible for one and not both axes to allow for implicit scrolling,
// so handling is split between the options for allowed implicit scrolling.
final
bool
allowHorizontal
=
horizontalOffset
.
allowImplicitScrolling
;
final
bool
allowVertical
=
verticalOffset
.
allowImplicitScrolling
;
AxisDirection
?
axisDirection
;
switch
((
allowHorizontal
,
allowVertical
))
{
case
(
true
,
true
):
// Both allow implicit scrolling.
break
;
case
(
false
,
true
):
// Only the vertical Axis allows implicit scrolling.
axisDirection
=
verticalAxisDirection
;
case
(
true
,
false
):
// Only the horizontal Axis allows implicit scrolling.
axisDirection
=
horizontalAxisDirection
;
case
(
false
,
false
):
// Neither axis allows for implicit scrolling.
return
super
.
showOnScreen
(
descendant:
descendant
,
rect:
rect
,
duration:
duration
,
curve:
curve
,
);
}
final
Rect
?
newRect
=
RenderTwoDimensionalViewport
.
showInViewport
(
descendant:
descendant
,
viewport:
this
,
axisDirection:
axisDirection
,
rect:
rect
,
duration:
duration
,
curve:
curve
,
);
super
.
showOnScreen
(
rect:
newRect
,
duration:
duration
,
curve:
curve
,
);
}
/// Make (a portion of) the given `descendant` of the given `viewport` fully
/// visible in one or both dimensions of the `viewport` by manipulating the
/// [ViewportOffset]s.
///
/// The `axisDirection` determines from which axes the `descendant` will be
/// revealed. When the `axisDirection` is null, both axes will be updated to
/// reveal the descendant.
///
/// 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 parameter `viewport` is required and cannot be null. If `descendant`
/// is null, this is a no-op and `rect` is returned.
///
/// If both `descendant` 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`.
///
/// See also:
///
/// * [RenderObject.showOnScreen], overridden by
/// [RenderTwoDimensionalViewport] to delegate to this method.
static
Rect
?
showInViewport
({
RenderObject
?
descendant
,
Rect
?
rect
,
required
RenderTwoDimensionalViewport
viewport
,
Duration
duration
=
Duration
.
zero
,
Curve
curve
=
Curves
.
ease
,
AxisDirection
?
axisDirection
,
})
{
if
(
descendant
==
null
)
{
return
rect
;
}
Rect
?
showVertical
(
Rect
?
rect
)
{
return
RenderTwoDimensionalViewport
.
_showInViewportForAxisDirection
(
descendant:
descendant
,
viewport:
viewport
,
axis:
Axis
.
vertical
,
rect:
rect
,
duration:
duration
,
curve:
curve
,
);
}
Rect
?
showHorizontal
(
Rect
?
rect
)
{
return
RenderTwoDimensionalViewport
.
_showInViewportForAxisDirection
(
descendant:
descendant
,
viewport:
viewport
,
axis:
Axis
.
horizontal
,
rect:
rect
,
duration:
duration
,
curve:
curve
,
);
}
switch
(
axisDirection
)
{
case
AxisDirection
.
left
:
case
AxisDirection
.
right
:
return
showHorizontal
(
rect
);
case
AxisDirection
.
up
:
case
AxisDirection
.
down
:
return
showVertical
(
rect
);
case
null
:
// Update rect after revealing in one axis before revealing in the next.
rect
=
showHorizontal
(
rect
)
??
rect
;
// We only return the final rect after both have been revealed.
rect
=
showVertical
(
rect
);
if
(
rect
==
null
)
{
// `descendant` is between leading and trailing edge and hence already
// fully shown on screen.
assert
(
viewport
.
parent
!=
null
);
final
Matrix4
transform
=
descendant
.
getTransformTo
(
viewport
.
parent
);
return
MatrixUtils
.
transformRect
(
transform
,
rect
??
descendant
.
paintBounds
,
);
}
return
rect
;
}
}
static
Rect
?
_showInViewportForAxisDirection
({
required
RenderObject
descendant
,
Rect
?
rect
,
required
RenderTwoDimensionalViewport
viewport
,
required
Axis
axis
,
Duration
duration
=
Duration
.
zero
,
Curve
curve
=
Curves
.
ease
,
})
{
final
ViewportOffset
offset
=
switch
(
axis
)
{
Axis
.
vertical
=>
viewport
.
verticalOffset
,
Axis
.
horizontal
=>
viewport
.
horizontalOffset
,
};
final
RevealedOffset
leadingEdgeOffset
=
viewport
.
getOffsetToReveal
(
descendant
,
0.0
,
rect:
rect
,
axis:
axis
,
);
final
RevealedOffset
trailingEdgeOffset
=
viewport
.
getOffsetToReveal
(
descendant
,
1.0
,
rect:
rect
,
axis:
axis
,
);
final
double
currentOffset
=
offset
.
pixels
;
final
RevealedOffset
?
targetOffset
=
RevealedOffset
.
clampOffset
(
leadingEdgeOffset:
leadingEdgeOffset
,
trailingEdgeOffset:
trailingEdgeOffset
,
currentOffset:
currentOffset
,
);
if
(
targetOffset
==
null
)
{
// Already visible in this axis.
return
null
;
}
offset
.
moveTo
(
targetOffset
.
offset
,
duration:
duration
,
curve:
curve
);
return
targetOffset
.
rect
;
}
/// Should be used by subclasses to invalidate any cached metrics for the
...
...
packages/flutter/test/widgets/ensure_visible_test.dart
View file @
80fb7bd2
...
...
@@ -9,6 +9,8 @@ import 'package:flutter/widgets.dart';
import
'package:flutter_test/flutter_test.dart'
;
import
'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart'
;
import
'two_dimensional_utils.dart'
;
Finder
findKey
(
int
i
)
=>
find
.
byKey
(
ValueKey
<
int
>(
i
),
skipOffstage:
false
);
Widget
buildSingleChildScrollView
(
Axis
scrollDirection
,
{
bool
reverse
=
false
})
{
...
...
@@ -1051,4 +1053,279 @@ void main() {
expect
(
tester
.
getTopLeft
(
findKey
(-
3
)).
dy
,
equals
(
100.0
));
});
});
group
(
'TwoDimensionalViewport ensureVisible'
,
()
{
Finder
findKey
(
ChildVicinity
vicinity
)
{
return
find
.
byKey
(
ValueKey
<
ChildVicinity
>(
vicinity
));
}
BuildContext
findContext
(
WidgetTester
tester
,
ChildVicinity
vicinity
)
{
return
tester
.
element
(
findKey
(
vicinity
));
}
testWidgets
(
'Axis.vertical'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
simpleBuilderTest
(
useCacheExtent:
true
));
Scrollable
.
ensureVisible
(
findContext
(
tester
,
const
ChildVicinity
(
xIndex:
0
,
yIndex:
0
)),
);
await
tester
.
pump
();
expect
(
tester
.
getTopLeft
(
findKey
(
const
ChildVicinity
(
xIndex:
0
,
yIndex:
0
))).
dy
,
equals
(
0.0
),
);
// (0, 3) is in the cache extent, and will be brought into view next
expect
(
tester
.
getTopLeft
(
findKey
(
const
ChildVicinity
(
xIndex:
0
,
yIndex:
3
))).
dy
,
equals
(
600.0
),
);
Scrollable
.
ensureVisible
(
findContext
(
tester
,
const
ChildVicinity
(
xIndex:
0
,
yIndex:
3
)),
);
await
tester
.
pump
();
// Now in view at top edge of viewport
expect
(
tester
.
getTopLeft
(
findKey
(
const
ChildVicinity
(
xIndex:
0
,
yIndex:
3
))).
dy
,
equals
(
0.0
),
);
// If already visible, no change
Scrollable
.
ensureVisible
(
findContext
(
tester
,
const
ChildVicinity
(
xIndex:
0
,
yIndex:
3
)),
);
await
tester
.
pump
();
expect
(
tester
.
getTopLeft
(
findKey
(
const
ChildVicinity
(
xIndex:
0
,
yIndex:
3
))).
dy
,
equals
(
0.0
),
);
});
testWidgets
(
'Axis.horizontal'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
simpleBuilderTest
(
useCacheExtent:
true
));
Scrollable
.
ensureVisible
(
findContext
(
tester
,
const
ChildVicinity
(
xIndex:
1
,
yIndex:
0
)),
);
await
tester
.
pump
();
expect
(
tester
.
getTopLeft
(
findKey
(
const
ChildVicinity
(
xIndex:
1
,
yIndex:
0
))).
dx
,
equals
(
0.0
),
);
// (5, 0) is now in the cache extent, and will be brought into view next
expect
(
tester
.
getTopLeft
(
findKey
(
const
ChildVicinity
(
xIndex:
5
,
yIndex:
0
))).
dx
,
equals
(
800.0
),
);
Scrollable
.
ensureVisible
(
findContext
(
tester
,
const
ChildVicinity
(
xIndex:
5
,
yIndex:
0
)),
alignmentPolicy:
ScrollPositionAlignmentPolicy
.
keepVisibleAtEnd
,
);
await
tester
.
pump
();
// Now in view at trailing edge of viewport
expect
(
tester
.
getTopLeft
(
findKey
(
const
ChildVicinity
(
xIndex:
5
,
yIndex:
0
))).
dx
,
equals
(
600.0
),
);
// If already in position, no change
Scrollable
.
ensureVisible
(
findContext
(
tester
,
const
ChildVicinity
(
xIndex:
5
,
yIndex:
0
)),
alignmentPolicy:
ScrollPositionAlignmentPolicy
.
keepVisibleAtEnd
,
);
await
tester
.
pump
();
expect
(
tester
.
getTopLeft
(
findKey
(
const
ChildVicinity
(
xIndex:
5
,
yIndex:
0
))).
dx
,
equals
(
600.0
),
);
});
testWidgets
(
'both axes'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
simpleBuilderTest
(
useCacheExtent:
true
));
Scrollable
.
ensureVisible
(
findContext
(
tester
,
const
ChildVicinity
(
xIndex:
1
,
yIndex:
1
)),
);
await
tester
.
pump
();
expect
(
tester
.
getRect
(
findKey
(
const
ChildVicinity
(
xIndex:
1
,
yIndex:
1
))),
const
Rect
.
fromLTRB
(
0.0
,
0.0
,
200.0
,
200.0
),
);
// (5, 4) is in the cache extent, and will be brought into view next
expect
(
tester
.
getRect
(
findKey
(
const
ChildVicinity
(
xIndex:
5
,
yIndex:
4
))),
const
Rect
.
fromLTRB
(
800.0
,
600.0
,
1000.0
,
800.0
),
);
Scrollable
.
ensureVisible
(
findContext
(
tester
,
const
ChildVicinity
(
xIndex:
5
,
yIndex:
4
)),
alignment:
1.0
,
// Same as ScrollAlignmentPolicy.keepVisibleAtEnd
);
await
tester
.
pump
();
// Now in view at bottom trailing corner of viewport
expect
(
tester
.
getRect
(
findKey
(
const
ChildVicinity
(
xIndex:
5
,
yIndex:
4
))),
const
Rect
.
fromLTRB
(
600.0
,
400.0
,
800.0
,
600.0
),
);
// If already visible, no change
Scrollable
.
ensureVisible
(
findContext
(
tester
,
const
ChildVicinity
(
xIndex:
5
,
yIndex:
4
)),
alignment:
1.0
,
);
await
tester
.
pump
();
expect
(
tester
.
getRect
(
findKey
(
const
ChildVicinity
(
xIndex:
5
,
yIndex:
4
))),
const
Rect
.
fromLTRB
(
600.0
,
400.0
,
800.0
,
600.0
),
);
});
testWidgets
(
'Axis.vertical reverse'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
simpleBuilderTest
(
verticalDetails:
const
ScrollableDetails
.
vertical
(
reverse:
true
),
useCacheExtent:
true
,
));
expect
(
tester
.
getTopLeft
(
findKey
(
const
ChildVicinity
(
xIndex:
0
,
yIndex:
0
))).
dy
,
equals
(
400.0
),
);
Scrollable
.
ensureVisible
(
findContext
(
tester
,
const
ChildVicinity
(
xIndex:
0
,
yIndex:
0
)),
);
await
tester
.
pump
();
// Already visible so no change.
expect
(
tester
.
getTopLeft
(
findKey
(
const
ChildVicinity
(
xIndex:
0
,
yIndex:
0
))).
dy
,
equals
(
400.0
),
);
// (0, 3) is in the cache extent, and will be brought into view next
expect
(
tester
.
getTopLeft
(
findKey
(
const
ChildVicinity
(
xIndex:
0
,
yIndex:
3
))).
dy
,
equals
(-
200.0
),
);
Scrollable
.
ensureVisible
(
findContext
(
tester
,
const
ChildVicinity
(
xIndex:
0
,
yIndex:
3
)),
);
await
tester
.
pump
();
// Now in view at bottom edge of viewport since we are reversed
expect
(
tester
.
getTopLeft
(
findKey
(
const
ChildVicinity
(
xIndex:
0
,
yIndex:
3
))).
dy
,
equals
(
400.0
),
);
// If already visible, no change
Scrollable
.
ensureVisible
(
findContext
(
tester
,
const
ChildVicinity
(
xIndex:
0
,
yIndex:
3
)),
);
await
tester
.
pump
();
expect
(
tester
.
getTopLeft
(
findKey
(
const
ChildVicinity
(
xIndex:
0
,
yIndex:
3
))).
dy
,
equals
(
400.0
),
);
});
testWidgets
(
'Axis.horizontal reverse'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
simpleBuilderTest
(
horizontalDetails:
const
ScrollableDetails
.
horizontal
(
reverse:
true
),
useCacheExtent:
true
,
));
expect
(
tester
.
getTopLeft
(
findKey
(
const
ChildVicinity
(
xIndex:
0
,
yIndex:
0
))).
dx
,
equals
(
600.0
),
);
Scrollable
.
ensureVisible
(
findContext
(
tester
,
const
ChildVicinity
(
xIndex:
0
,
yIndex:
0
)),
);
await
tester
.
pump
();
// Already visible so no change.
expect
(
tester
.
getTopLeft
(
findKey
(
const
ChildVicinity
(
xIndex:
0
,
yIndex:
0
))).
dx
,
equals
(
600.0
),
);
// (4, 0) is in the cache extent, and will be brought into view next
expect
(
tester
.
getTopLeft
(
findKey
(
const
ChildVicinity
(
xIndex:
4
,
yIndex:
0
))).
dx
,
equals
(-
200.0
),
);
Scrollable
.
ensureVisible
(
findContext
(
tester
,
const
ChildVicinity
(
xIndex:
4
,
yIndex:
0
)),
);
await
tester
.
pump
();
expect
(
tester
.
getTopLeft
(
findKey
(
const
ChildVicinity
(
xIndex:
4
,
yIndex:
0
))).
dx
,
equals
(
200.0
),
);
// If already visible, no change
Scrollable
.
ensureVisible
(
findContext
(
tester
,
const
ChildVicinity
(
xIndex:
4
,
yIndex:
0
)),
);
await
tester
.
pump
();
expect
(
tester
.
getTopLeft
(
findKey
(
const
ChildVicinity
(
xIndex:
4
,
yIndex:
0
))).
dx
,
equals
(
200.0
),
);
});
testWidgets
(
'both axes reverse'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
simpleBuilderTest
(
verticalDetails:
const
ScrollableDetails
.
vertical
(
reverse:
true
),
horizontalDetails:
const
ScrollableDetails
.
horizontal
(
reverse:
true
),
useCacheExtent:
true
,
));
Scrollable
.
ensureVisible
(
findContext
(
tester
,
const
ChildVicinity
(
xIndex:
1
,
yIndex:
1
)),
);
await
tester
.
pump
();
expect
(
tester
.
getRect
(
findKey
(
const
ChildVicinity
(
xIndex:
1
,
yIndex:
1
))),
const
Rect
.
fromLTRB
(
600.0
,
400.0
,
800.0
,
600.0
),
);
// (5, 4) is in the cache extent, and will be brought into view next
expect
(
tester
.
getRect
(
findKey
(
const
ChildVicinity
(
xIndex:
5
,
yIndex:
4
))),
const
Rect
.
fromLTRB
(-
200.0
,
-
200.0
,
0.0
,
0.0
),
);
Scrollable
.
ensureVisible
(
findContext
(
tester
,
const
ChildVicinity
(
xIndex:
5
,
yIndex:
4
)),
alignment:
1.0
,
// Same as ScrollAlignmentPolicy.keepVisibleAtEnd
);
await
tester
.
pump
();
// Now in view at trailing corner of viewport
expect
(
tester
.
getRect
(
findKey
(
const
ChildVicinity
(
xIndex:
5
,
yIndex:
4
))),
const
Rect
.
fromLTRB
(
0.0
,
0.0
,
200.0
,
200.0
),
);
// If already visible, no change
Scrollable
.
ensureVisible
(
findContext
(
tester
,
const
ChildVicinity
(
xIndex:
5
,
yIndex:
4
)),
alignment:
1.0
,
);
await
tester
.
pump
();
expect
(
tester
.
getRect
(
findKey
(
const
ChildVicinity
(
xIndex:
5
,
yIndex:
4
))),
const
Rect
.
fromLTRB
(
0.0
,
0.0
,
200.0
,
200.0
),
);
});
});
}
packages/flutter/test/widgets/two_dimensional_utils.dart
View file @
80fb7bd2
...
...
@@ -17,6 +17,7 @@ final TwoDimensionalChildBuilderDelegate builderDelegate = TwoDimensionalChildBu
maxYIndex:
5
,
builder:
(
BuildContext
context
,
ChildVicinity
vicinity
)
{
return
Container
(
key:
ValueKey
<
ChildVicinity
>(
vicinity
),
color:
vicinity
.
xIndex
.
isEven
&&
vicinity
.
yIndex
.
isEven
?
Colors
.
amber
[
100
]
:
(
vicinity
.
xIndex
.
isOdd
&&
vicinity
.
yIndex
.
isOdd
...
...
packages/flutter/test/widgets/two_dimensional_viewport_test.dart
View file @
80fb7bd2
...
...
@@ -2365,6 +2365,275 @@ void main() {
),
);
},
variant:
TargetPlatformVariant
.
all
());
group
(
'TwoDimensionalViewport showOnScreen & showInViewport'
,
()
{
Finder
findKey
(
ChildVicinity
vicinity
)
{
return
find
.
byKey
(
ValueKey
<
ChildVicinity
>(
vicinity
));
}
testWidgets
(
'Axis.vertical'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
simpleBuilderTest
(
useCacheExtent:
true
));
// Child visible at origin
expect
(
tester
.
getTopLeft
(
findKey
(
const
ChildVicinity
(
xIndex:
0
,
yIndex:
0
))).
dy
,
equals
(
0.0
),
);
tester
.
renderObject
(
find
.
byKey
(
const
ValueKey
<
ChildVicinity
>(
ChildVicinity
(
xIndex:
0
,
yIndex:
0
)),
skipOffstage:
false
,
)).
showOnScreen
();
await
tester
.
pump
();
expect
(
tester
.
getTopLeft
(
findKey
(
const
ChildVicinity
(
xIndex:
0
,
yIndex:
0
))).
dy
,
equals
(
0.0
),
);
// (0, 3) is in the cache extent, and will be brought into view next
expect
(
tester
.
getTopLeft
(
findKey
(
const
ChildVicinity
(
xIndex:
0
,
yIndex:
3
))).
dy
,
equals
(
600.0
),
);
tester
.
renderObject
(
find
.
byKey
(
const
ValueKey
<
ChildVicinity
>(
ChildVicinity
(
xIndex:
0
,
yIndex:
3
)),
skipOffstage:
false
,
)).
showOnScreen
();
await
tester
.
pump
();
// Now in view
expect
(
tester
.
getTopLeft
(
findKey
(
const
ChildVicinity
(
xIndex:
0
,
yIndex:
3
))).
dy
,
equals
(
400.0
),
);
// If already visible, no change
tester
.
renderObject
(
find
.
byKey
(
const
ValueKey
<
ChildVicinity
>(
ChildVicinity
(
xIndex:
0
,
yIndex:
3
)),
skipOffstage:
false
,
)).
showOnScreen
();
await
tester
.
pump
();
expect
(
tester
.
getTopLeft
(
findKey
(
const
ChildVicinity
(
xIndex:
0
,
yIndex:
3
))).
dy
,
equals
(
400.0
),
);
});
testWidgets
(
'Axis.horizontal'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
simpleBuilderTest
(
useCacheExtent:
true
));
tester
.
renderObject
(
find
.
byKey
(
const
ValueKey
<
ChildVicinity
>(
ChildVicinity
(
xIndex:
1
,
yIndex:
0
)),
skipOffstage:
false
,
)).
showOnScreen
();
await
tester
.
pump
();
expect
(
tester
.
getTopLeft
(
findKey
(
const
ChildVicinity
(
xIndex:
1
,
yIndex:
0
))).
dx
,
equals
(
200.0
),
// No change since already fully visible
);
// (5, 0) is now in the cache extent, and will be brought into view next
expect
(
tester
.
getTopLeft
(
findKey
(
const
ChildVicinity
(
xIndex:
5
,
yIndex:
0
))).
dx
,
equals
(
1000.0
),
);
tester
.
renderObject
(
find
.
byKey
(
const
ValueKey
<
ChildVicinity
>(
ChildVicinity
(
xIndex:
5
,
yIndex:
0
)),
skipOffstage:
false
,
)).
showOnScreen
();
await
tester
.
pump
();
// Now in view
expect
(
tester
.
getTopLeft
(
findKey
(
const
ChildVicinity
(
xIndex:
5
,
yIndex:
0
))).
dx
,
equals
(
600.0
),
);
// If already in position, no change
tester
.
renderObject
(
find
.
byKey
(
const
ValueKey
<
ChildVicinity
>(
ChildVicinity
(
xIndex:
5
,
yIndex:
0
)),
skipOffstage:
false
,
)).
showOnScreen
();
await
tester
.
pump
();
expect
(
tester
.
getTopLeft
(
findKey
(
const
ChildVicinity
(
xIndex:
5
,
yIndex:
0
))).
dx
,
equals
(
600.0
),
);
});
testWidgets
(
'both axes'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
simpleBuilderTest
(
useCacheExtent:
true
));
tester
.
renderObject
(
find
.
byKey
(
const
ValueKey
<
ChildVicinity
>(
ChildVicinity
(
xIndex:
1
,
yIndex:
1
)),
skipOffstage:
false
,
)).
showOnScreen
();
await
tester
.
pump
();
expect
(
tester
.
getRect
(
findKey
(
const
ChildVicinity
(
xIndex:
1
,
yIndex:
1
))),
const
Rect
.
fromLTRB
(
200.0
,
200.0
,
400.0
,
400.0
),
);
// (5, 4) is in the cache extent, and will be brought into view next
expect
(
tester
.
getRect
(
findKey
(
const
ChildVicinity
(
xIndex:
5
,
yIndex:
4
))),
const
Rect
.
fromLTRB
(
1000.0
,
800.0
,
1200.0
,
1000.0
),
);
tester
.
renderObject
(
find
.
byKey
(
const
ValueKey
<
ChildVicinity
>(
ChildVicinity
(
xIndex:
5
,
yIndex:
4
)),
skipOffstage:
false
,
)).
showOnScreen
();
await
tester
.
pump
();
// Now in view
expect
(
tester
.
getRect
(
findKey
(
const
ChildVicinity
(
xIndex:
5
,
yIndex:
4
))),
const
Rect
.
fromLTRB
(
600.0
,
200.0
,
800.0
,
400.0
),
);
// If already visible, no change
tester
.
renderObject
(
find
.
byKey
(
const
ValueKey
<
ChildVicinity
>(
ChildVicinity
(
xIndex:
5
,
yIndex:
4
)),
skipOffstage:
false
,
)).
showOnScreen
();
await
tester
.
pump
();
expect
(
tester
.
getRect
(
findKey
(
const
ChildVicinity
(
xIndex:
5
,
yIndex:
4
))),
const
Rect
.
fromLTRB
(
600.0
,
200.0
,
800.0
,
400.0
),
);
});
testWidgets
(
'Axis.vertical reverse'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
simpleBuilderTest
(
verticalDetails:
const
ScrollableDetails
.
vertical
(
reverse:
true
),
useCacheExtent:
true
,
));
expect
(
tester
.
getTopLeft
(
findKey
(
const
ChildVicinity
(
xIndex:
0
,
yIndex:
0
))).
dy
,
equals
(
400.0
),
);
tester
.
renderObject
(
find
.
byKey
(
const
ValueKey
<
ChildVicinity
>(
ChildVicinity
(
xIndex:
0
,
yIndex:
0
)),
skipOffstage:
false
,
)).
showOnScreen
();
await
tester
.
pump
();
// Already visible so no change.
expect
(
tester
.
getTopLeft
(
findKey
(
const
ChildVicinity
(
xIndex:
0
,
yIndex:
0
))).
dy
,
equals
(
400.0
),
);
// (0, 3) is in the cache extent, and will be brought into view next
expect
(
tester
.
getTopLeft
(
findKey
(
const
ChildVicinity
(
xIndex:
0
,
yIndex:
3
))).
dy
,
equals
(-
200.0
),
);
tester
.
renderObject
(
find
.
byKey
(
const
ValueKey
<
ChildVicinity
>(
ChildVicinity
(
xIndex:
0
,
yIndex:
3
)),
skipOffstage:
false
,
)).
showOnScreen
();
await
tester
.
pump
();
// Now in view
expect
(
tester
.
getTopLeft
(
findKey
(
const
ChildVicinity
(
xIndex:
0
,
yIndex:
3
))).
dy
,
equals
(
0.0
),
);
// If already visible, no change
tester
.
renderObject
(
find
.
byKey
(
const
ValueKey
<
ChildVicinity
>(
ChildVicinity
(
xIndex:
0
,
yIndex:
3
)),
skipOffstage:
false
,
)).
showOnScreen
();
await
tester
.
pump
();
expect
(
tester
.
getTopLeft
(
findKey
(
const
ChildVicinity
(
xIndex:
0
,
yIndex:
3
))).
dy
,
equals
(
0.0
),
);
});
testWidgets
(
'Axis.horizontal reverse'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
simpleBuilderTest
(
horizontalDetails:
const
ScrollableDetails
.
horizontal
(
reverse:
true
),
useCacheExtent:
true
,
));
expect
(
tester
.
getTopLeft
(
findKey
(
const
ChildVicinity
(
xIndex:
0
,
yIndex:
0
))).
dx
,
equals
(
600.0
),
);
tester
.
renderObject
(
find
.
byKey
(
const
ValueKey
<
ChildVicinity
>(
ChildVicinity
(
xIndex:
0
,
yIndex:
0
)),
skipOffstage:
false
,
)).
showOnScreen
();
await
tester
.
pump
();
// Already visible so no change.
expect
(
tester
.
getTopLeft
(
findKey
(
const
ChildVicinity
(
xIndex:
0
,
yIndex:
0
))).
dx
,
equals
(
600.0
),
);
// (4, 0) is in the cache extent, and will be brought into view next
expect
(
tester
.
getTopLeft
(
findKey
(
const
ChildVicinity
(
xIndex:
4
,
yIndex:
0
))).
dx
,
equals
(-
200.0
),
);
tester
.
renderObject
(
find
.
byKey
(
const
ValueKey
<
ChildVicinity
>(
ChildVicinity
(
xIndex:
4
,
yIndex:
0
)),
skipOffstage:
false
,
)).
showOnScreen
();
await
tester
.
pump
();
expect
(
tester
.
getTopLeft
(
findKey
(
const
ChildVicinity
(
xIndex:
4
,
yIndex:
0
))).
dx
,
equals
(
0.0
),
);
// If already visible, no change
tester
.
renderObject
(
find
.
byKey
(
const
ValueKey
<
ChildVicinity
>(
ChildVicinity
(
xIndex:
4
,
yIndex:
0
)),
skipOffstage:
false
,
)).
showOnScreen
();
await
tester
.
pump
();
expect
(
tester
.
getTopLeft
(
findKey
(
const
ChildVicinity
(
xIndex:
4
,
yIndex:
0
))).
dx
,
equals
(
0.0
),
);
});
testWidgets
(
'both axes reverse'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
simpleBuilderTest
(
verticalDetails:
const
ScrollableDetails
.
vertical
(
reverse:
true
),
horizontalDetails:
const
ScrollableDetails
.
horizontal
(
reverse:
true
),
useCacheExtent:
true
,
));
tester
.
renderObject
(
find
.
byKey
(
const
ValueKey
<
ChildVicinity
>(
ChildVicinity
(
xIndex:
1
,
yIndex:
1
)),
skipOffstage:
false
,
)).
showOnScreen
();
await
tester
.
pump
();
expect
(
tester
.
getRect
(
findKey
(
const
ChildVicinity
(
xIndex:
1
,
yIndex:
1
))),
const
Rect
.
fromLTRB
(
400.0
,
200.0
,
600.0
,
400.0
),
);
// (5, 4) is in the cache extent, and will be brought into view next
expect
(
tester
.
getRect
(
findKey
(
const
ChildVicinity
(
xIndex:
5
,
yIndex:
4
))),
const
Rect
.
fromLTRB
(-
400.0
,
-
400.0
,
-
200.0
,
-
200.0
),
);
tester
.
renderObject
(
find
.
byKey
(
const
ValueKey
<
ChildVicinity
>(
ChildVicinity
(
xIndex:
5
,
yIndex:
4
)),
skipOffstage:
false
,
)).
showOnScreen
();
await
tester
.
pump
();
// Now in view
expect
(
tester
.
getRect
(
findKey
(
const
ChildVicinity
(
xIndex:
5
,
yIndex:
4
))),
const
Rect
.
fromLTRB
(
0.0
,
200.0
,
200.0
,
400.0
),
);
// If already visible, no change
tester
.
renderObject
(
find
.
byKey
(
const
ValueKey
<
ChildVicinity
>(
ChildVicinity
(
xIndex:
5
,
yIndex:
4
)),
skipOffstage:
false
,
)).
showOnScreen
();
await
tester
.
pump
();
expect
(
tester
.
getRect
(
findKey
(
const
ChildVicinity
(
xIndex:
5
,
yIndex:
4
))),
const
Rect
.
fromLTRB
(
0.0
,
200.0
,
200.0
,
400.0
),
);
});
});
});
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment