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
Expand all
Hide 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
This diff is collapsed.
Click to expand it.
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
This diff is collapsed.
Click to expand it.
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