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
7471ff8c
Unverified
Commit
7471ff8c
authored
May 21, 2018
by
Michael Goderbauer
Committed by
GitHub
May 21, 2018
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
showOnScreen doesn't trigger scroll if item is already fully on screen (#17729)
parent
eda3167a
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
254 additions
and
43 deletions
+254
-43
app_bar.dart
packages/flutter/lib/src/material/app_bar.dart
+1
-1
viewport.dart
packages/flutter/lib/src/rendering/viewport.dart
+56
-15
single_child_scroll_view.dart
...ges/flutter/lib/src/widgets/single_child_scroll_view.dart
+1
-13
scrollable_semantics_test.dart
packages/flutter/test/widgets/scrollable_semantics_test.dart
+196
-14
No files found.
packages/flutter/lib/src/material/app_bar.dart
View file @
7471ff8c
...
...
@@ -920,7 +920,7 @@ class SliverAppBar extends StatefulWidget {
/// Whether the app bar should remain visible at the start of the scroll view.
///
/// The app bar can still expand an contract as the user scrolls, but it will
/// The app bar can still expand an
d
contract as the user scrolls, but it will
/// remain visible rather than being scrolled out of view.
final
bool
pinned
;
...
...
packages/flutter/lib/src/rendering/viewport.dart
View file @
7471ff8c
...
...
@@ -784,24 +784,65 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix
@override
void
showOnScreen
([
RenderObject
child
])
{
// Logic duplicated in [_RenderSingleChildViewport.showOnScreen].
if
(
child
!=
null
)
{
// TODO(goderbauer): Don't scroll if it is already visible.
// TODO(goderbauer): Don't guess if we need to align at leading or trailing edge.
// Move viewport the smallest distance to bring [child] on screen.
final
double
leadingEdgeOffset
=
getOffsetToReveal
(
child
,
0.0
);
final
double
trailingEdgeOffset
=
getOffsetToReveal
(
child
,
1.0
);
final
double
currentOffset
=
offset
.
pixels
;
// TODO(goderbauer): Don't scroll if that puts us outside of viewport's bounds.
if
((
currentOffset
-
leadingEdgeOffset
).
abs
()
<
(
currentOffset
-
trailingEdgeOffset
).
abs
())
{
offset
.
jumpTo
(
leadingEdgeOffset
);
}
else
{
offset
.
jumpTo
(
trailingEdgeOffset
);
}
}
RenderViewportBase
.
showInViewport
(
child:
child
,
viewport:
this
,
offset:
offset
);
// Make sure the viewport itself is on screen.
super
.
showOnScreen
();
}
/// Make the given `child` of the given `viewport` fully visible in the
/// `viewport` by manipulating the provided [ViewportOffset] `offset`.
///
/// 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
,
@required
RenderAbstractViewport
viewport
,
@required
ViewportOffset
offset
,
})
{
assert
(
viewport
!=
null
);
assert
(
offset
!=
null
);
if
(
child
==
null
)
{
return
;
}
final
double
leadingEdgeOffset
=
viewport
.
getOffsetToReveal
(
child
,
0.0
);
final
double
trailingEdgeOffset
=
viewport
.
getOffsetToReveal
(
child
,
1.0
);
final
double
currentOffset
=
offset
.
pixels
;
// scrollOffset
// 0 +---------+
// | |
// _ | |
// viewport position | | |
// with `child` at | | | _
// trailing edge |_ | xxxxxxx | | viewport position
// | | | with `child` 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`.
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
);
}
// else: `child` is between leading and trailing edge and hence already
// fully shown on screen. No action necessary.
}
}
/// A render object that is bigger on the inside.
...
...
packages/flutter/lib/src/widgets/single_child_scroll_view.dart
View file @
7471ff8c
...
...
@@ -586,19 +586,7 @@ class _RenderSingleChildViewport extends RenderBox with RenderObjectWithChildMix
@override
void
showOnScreen
([
RenderObject
child
])
{
// Logic duplicated in [RenderViewportBase.showOnScreen].
if
(
child
!=
null
)
{
// Move viewport the smallest distance to bring [child] on screen.
final
double
leadingEdgeOffset
=
getOffsetToReveal
(
child
,
0.0
);
final
double
trailingEdgeOffset
=
getOffsetToReveal
(
child
,
1.0
);
final
double
currentOffset
=
offset
.
pixels
;
if
((
currentOffset
-
leadingEdgeOffset
).
abs
()
<
(
currentOffset
-
trailingEdgeOffset
).
abs
())
{
offset
.
jumpTo
(
leadingEdgeOffset
);
}
else
{
offset
.
jumpTo
(
trailingEdgeOffset
);
}
}
RenderViewportBase
.
showInViewport
(
child:
child
,
viewport:
this
,
offset:
offset
);
// Make sure the viewport itself is on screen.
super
.
showOnScreen
();
}
...
...
packages/flutter/test/widgets/scrollable_semantics_test.dart
View file @
7471ff8c
...
...
@@ -137,12 +137,6 @@ void main() {
await
tester
.
pump
(
const
Duration
(
seconds:
5
));
expect
(
tester
.
getTopLeft
(
find
.
byWidget
(
containers
.
first
)).
dy
,
kExpandedAppBarHeight
);
final
int
secondContainerId
=
tester
.
renderObject
(
find
.
byWidget
(
containers
[
1
])).
debugSemantics
.
id
;
tester
.
binding
.
pipelineOwner
.
semanticsOwner
.
performAction
(
secondContainerId
,
SemanticsAction
.
showOnScreen
);
await
tester
.
pump
();
await
tester
.
pump
(
const
Duration
(
seconds:
5
));
expect
(
tester
.
getTopLeft
(
find
.
byWidget
(
containers
[
1
])).
dy
,
kExpandedAppBarHeight
);
semantics
.
dispose
();
});
...
...
@@ -168,7 +162,7 @@ void main() {
});
final
ScrollController
scrollController
=
new
ScrollController
(
initialScrollOffset:
kItemHeight
/
2
,
initialScrollOffset:
2.5
*
kItemHeight
,
);
await
tester
.
pumpWidget
(
new
Directionality
(
...
...
@@ -195,7 +189,7 @@ void main() {
),
));
expect
(
scrollController
.
offset
,
kItemHeight
/
2
);
expect
(
scrollController
.
offset
,
2.5
*
kItemHeight
);
final
int
id0
=
tester
.
renderObject
(
find
.
byWidget
(
children
[
0
])).
debugSemantics
.
id
;
tester
.
binding
.
pipelineOwner
.
semanticsOwner
.
performAction
(
id0
,
SemanticsAction
.
showOnScreen
);
...
...
@@ -203,12 +197,6 @@ void main() {
await
tester
.
pump
(
const
Duration
(
seconds:
5
));
expect
(
tester
.
getTopLeft
(
find
.
byWidget
(
children
[
0
])).
dy
,
kToolbarHeight
);
final
int
id1
=
tester
.
renderObject
(
find
.
byWidget
(
children
[
1
])).
debugSemantics
.
id
;
tester
.
binding
.
pipelineOwner
.
semanticsOwner
.
performAction
(
id1
,
SemanticsAction
.
showOnScreen
);
await
tester
.
pump
();
await
tester
.
pump
(
const
Duration
(
seconds:
5
));
expect
(
tester
.
getTopLeft
(
find
.
byWidget
(
children
[
1
])).
dy
,
kToolbarHeight
);
semantics
.
dispose
();
});
...
...
@@ -399,6 +387,200 @@ void main() {
semantics
.
dispose
();
});
group
(
'showOnScreen'
,
()
{
const
double
kItemHeight
=
100.0
;
List
<
Widget
>
children
;
ScrollController
scrollController
;
Widget
widgetUnderTest
;
setUp
(()
{
children
=
new
List
<
Widget
>.
generate
(
10
,
(
int
i
)
{
return
new
MergeSemantics
(
child:
new
Container
(
height:
kItemHeight
,
child:
new
Text
(
'container
$i
'
),
),
);
});
scrollController
=
new
ScrollController
(
initialScrollOffset:
kItemHeight
/
2
,
);
widgetUnderTest
=
new
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
new
Center
(
child:
new
Container
(
height:
2
*
kItemHeight
,
child:
new
ListView
(
controller:
scrollController
,
children:
children
,
),
),
),
);
});
testWidgets
(
'brings item above leading edge to leading edge'
,
(
WidgetTester
tester
)
async
{
semantics
=
new
SemanticsTester
(
tester
);
// enables semantics tree generation
await
tester
.
pumpWidget
(
widgetUnderTest
);
expect
(
scrollController
.
offset
,
kItemHeight
/
2
);
final
int
firstContainerId
=
tester
.
renderObject
(
find
.
byWidget
(
children
.
first
)).
debugSemantics
.
id
;
tester
.
binding
.
pipelineOwner
.
semanticsOwner
.
performAction
(
firstContainerId
,
SemanticsAction
.
showOnScreen
);
await
tester
.
pumpAndSettle
();
expect
(
scrollController
.
offset
,
0.0
);
semantics
.
dispose
();
});
testWidgets
(
'brings item below trailing edge to trailing edge'
,
(
WidgetTester
tester
)
async
{
semantics
=
new
SemanticsTester
(
tester
);
// enables semantics tree generation
await
tester
.
pumpWidget
(
widgetUnderTest
);
expect
(
scrollController
.
offset
,
kItemHeight
/
2
);
final
int
firstContainerId
=
tester
.
renderObject
(
find
.
byWidget
(
children
[
2
])).
debugSemantics
.
id
;
tester
.
binding
.
pipelineOwner
.
semanticsOwner
.
performAction
(
firstContainerId
,
SemanticsAction
.
showOnScreen
);
await
tester
.
pumpAndSettle
();
expect
(
scrollController
.
offset
,
kItemHeight
);
semantics
.
dispose
();
});
testWidgets
(
'does not change position of items already fully on-screen'
,
(
WidgetTester
tester
)
async
{
semantics
=
new
SemanticsTester
(
tester
);
// enables semantics tree generation
await
tester
.
pumpWidget
(
widgetUnderTest
);
expect
(
scrollController
.
offset
,
kItemHeight
/
2
);
final
int
firstContainerId
=
tester
.
renderObject
(
find
.
byWidget
(
children
[
1
])).
debugSemantics
.
id
;
tester
.
binding
.
pipelineOwner
.
semanticsOwner
.
performAction
(
firstContainerId
,
SemanticsAction
.
showOnScreen
);
await
tester
.
pumpAndSettle
();
expect
(
scrollController
.
offset
,
kItemHeight
/
2
);
semantics
.
dispose
();
});
});
group
(
'showOnScreen with negative children'
,
()
{
const
double
kItemHeight
=
100.0
;
List
<
Widget
>
children
;
ScrollController
scrollController
;
Widget
widgetUnderTest
;
setUp
(()
{
final
Key
center
=
new
GlobalKey
();
children
=
new
List
<
Widget
>.
generate
(
10
,
(
int
i
)
{
return
new
SliverToBoxAdapter
(
key:
i
==
5
?
center
:
null
,
child:
new
MergeSemantics
(
key:
new
ValueKey
<
int
>(
i
),
child:
new
Container
(
height:
kItemHeight
,
child:
new
Text
(
'container
$i
'
),
),
),
);
});
scrollController
=
new
ScrollController
(
initialScrollOffset:
-
2.5
*
kItemHeight
,
);
// 'container 0' is at offset -500
// 'container 1' is at offset -400
// 'container 2' is at offset -300
// 'container 3' is at offset -200
// 'container 4' is at offset -100
// 'container 5' is at offset 0
widgetUnderTest
=
new
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
new
Center
(
child:
new
Container
(
height:
2
*
kItemHeight
,
child:
new
Scrollable
(
controller:
scrollController
,
viewportBuilder:
(
BuildContext
context
,
ViewportOffset
offset
)
{
return
new
Viewport
(
cacheExtent:
0.0
,
offset:
offset
,
center:
center
,
slivers:
children
);
},
),
),
),
);
});
testWidgets
(
'brings item above leading edge to leading edge'
,
(
WidgetTester
tester
)
async
{
semantics
=
new
SemanticsTester
(
tester
);
// enables semantics tree generation
await
tester
.
pumpWidget
(
widgetUnderTest
);
expect
(
scrollController
.
offset
,
-
250.0
);
final
int
firstContainerId
=
tester
.
renderObject
(
find
.
byKey
(
const
ValueKey
<
int
>(
2
))).
debugSemantics
.
id
;
tester
.
binding
.
pipelineOwner
.
semanticsOwner
.
performAction
(
firstContainerId
,
SemanticsAction
.
showOnScreen
);
await
tester
.
pumpAndSettle
();
expect
(
scrollController
.
offset
,
-
300.0
);
semantics
.
dispose
();
});
testWidgets
(
'brings item below trailing edge to trailing edge'
,
(
WidgetTester
tester
)
async
{
semantics
=
new
SemanticsTester
(
tester
);
// enables semantics tree generation
await
tester
.
pumpWidget
(
widgetUnderTest
);
expect
(
scrollController
.
offset
,
-
250.0
);
final
int
firstContainerId
=
tester
.
renderObject
(
find
.
byKey
(
const
ValueKey
<
int
>(
4
))).
debugSemantics
.
id
;
tester
.
binding
.
pipelineOwner
.
semanticsOwner
.
performAction
(
firstContainerId
,
SemanticsAction
.
showOnScreen
);
await
tester
.
pumpAndSettle
();
expect
(
scrollController
.
offset
,
-
200.0
);
semantics
.
dispose
();
});
testWidgets
(
'does not change position of items already fully on-screen'
,
(
WidgetTester
tester
)
async
{
semantics
=
new
SemanticsTester
(
tester
);
// enables semantics tree generation
await
tester
.
pumpWidget
(
widgetUnderTest
);
expect
(
scrollController
.
offset
,
-
250.0
);
final
int
firstContainerId
=
tester
.
renderObject
(
find
.
byKey
(
const
ValueKey
<
int
>(
3
))).
debugSemantics
.
id
;
tester
.
binding
.
pipelineOwner
.
semanticsOwner
.
performAction
(
firstContainerId
,
SemanticsAction
.
showOnScreen
);
await
tester
.
pumpAndSettle
();
expect
(
scrollController
.
offset
,
-
250.0
);
semantics
.
dispose
();
});
});
}
Future
<
Null
>
flingUp
(
WidgetTester
tester
,
{
int
repetitions:
1
})
=>
fling
(
tester
,
const
Offset
(
0.0
,
-
200.0
),
repetitions
);
...
...
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