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
b07203a8
Commit
b07203a8
authored
Feb 10, 2016
by
Adam Barth
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1749 from abarth/viewport_scroll_anchor
Teach ScrollableList about scroll anchors
parents
244239d9
d6ae53fe
Changes
5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
211 additions
and
41 deletions
+211
-41
list.dart
packages/flutter/lib/src/rendering/list.dart
+1
-1
pageable_list.dart
packages/flutter/lib/src/widgets/pageable_list.dart
+5
-4
scrollable_list.dart
packages/flutter/lib/src/widgets/scrollable_list.dart
+75
-21
virtual_viewport.dart
packages/flutter/lib/src/widgets/virtual_viewport.dart
+22
-10
scrollable_list_horizontal_test.dart
.../flutter/test/widget/scrollable_list_horizontal_test.dart
+108
-5
No files found.
packages/flutter/lib/src/rendering/list.dart
View file @
b07203a8
...
...
@@ -140,7 +140,7 @@ class RenderList extends RenderVirtualViewport<ListParentData> {
switch
(
scrollDirection
)
{
case
Axis
.
vertical
:
itemWidth
=
math
.
max
(
0
,
size
.
width
-
(
padding
==
null
?
0.0
:
padding
.
horizontal
));
itemWidth
=
math
.
max
(
0
.0
,
size
.
width
-
(
padding
==
null
?
0.0
:
padding
.
horizontal
));
itemHeight
=
itemExtent
??
size
.
height
;
y
=
padding
!=
null
?
padding
.
top
:
0.0
;
dy
=
itemHeight
;
...
...
packages/flutter/lib/src/widgets/pageable_list.dart
View file @
b07203a8
...
...
@@ -230,15 +230,16 @@ class _PageViewportElement extends VirtualViewportElement<PageViewport> {
double
get
startOffsetLimit
=>
_repaintOffsetLimit
;
double
_repaintOffsetLimit
;
double
get
paintOffset
{
double
scrollOffsetToPixelOffset
(
double
scrollOffset
)
{
if
(
_containerExtent
==
null
)
return
0.0
;
return
-(
widget
.
startOffset
-
startOffsetBase
)
*
_containerExtent
;
return
super
.
scrollOffsetToPixelOffset
(
scrollOffset
)
*
_containerExtent
;
}
void
updateRenderObject
(
PageViewport
oldWidget
)
{
renderObject
.
scrollDirection
=
widget
.
scrollDirection
;
renderObject
.
overlayPainter
=
widget
.
overlayPainter
;
renderObject
..
scrollDirection
=
widget
.
scrollDirection
..
overlayPainter
=
widget
.
overlayPainter
;
super
.
updateRenderObject
(
oldWidget
);
}
...
...
packages/flutter/lib/src/widgets/scrollable_list.dart
View file @
b07203a8
...
...
@@ -16,6 +16,7 @@ class ScrollableList extends Scrollable {
Key
key
,
double
initialScrollOffset
,
Axis
scrollDirection:
Axis
.
vertical
,
ViewportAnchor
scrollAnchor:
ViewportAnchor
.
start
,
ScrollListener
onScroll
,
SnapOffsetCallback
snapOffsetCallback
,
double
snapAlignmentOffset:
0.0
,
...
...
@@ -28,6 +29,7 @@ class ScrollableList extends Scrollable {
key:
key
,
initialScrollOffset:
initialScrollOffset
,
scrollDirection:
scrollDirection
,
scrollAnchor:
scrollAnchor
,
onScroll:
onScroll
,
snapOffsetCallback:
snapOffsetCallback
,
snapAlignmentOffset:
snapAlignmentOffset
...
...
@@ -79,6 +81,7 @@ class _ScrollableListState extends ScrollableState<ScrollableList> {
onExtentsChanged:
_handleExtentsChanged
,
startOffset:
scrollOffset
,
scrollDirection:
config
.
scrollDirection
,
scrollAnchor:
config
.
scrollAnchor
,
itemExtent:
config
.
itemExtent
,
itemsWrap:
config
.
itemsWrap
,
padding:
config
.
padding
,
...
...
@@ -93,6 +96,7 @@ class _VirtualListViewport extends VirtualViewport {
this
.
onExtentsChanged
,
this
.
startOffset
,
this
.
scrollDirection
,
this
.
scrollAnchor
,
this
.
itemExtent
,
this
.
itemsWrap
,
this
.
padding
,
...
...
@@ -105,6 +109,7 @@ class _VirtualListViewport extends VirtualViewport {
final
ExtentsChangedCallback
onExtentsChanged
;
final
double
startOffset
;
final
Axis
scrollDirection
;
final
ViewportAnchor
scrollAnchor
;
final
double
itemExtent
;
final
bool
itemsWrap
;
final
EdgeDims
padding
;
...
...
@@ -135,6 +140,7 @@ class _VirtualListViewportElement extends VirtualViewportElement<_VirtualListVie
void
updateRenderObject
(
_VirtualListViewport
oldWidget
)
{
renderObject
..
scrollDirection
=
widget
.
scrollDirection
..
scrollAnchor
=
widget
.
scrollAnchor
..
itemExtent
=
widget
.
itemExtent
..
padding
=
widget
.
padding
..
overlayPainter
=
widget
.
overlayPainter
;
...
...
@@ -144,36 +150,76 @@ class _VirtualListViewportElement extends VirtualViewportElement<_VirtualListVie
double
_contentExtent
;
double
_containerExtent
;
double
_getContainerExtentFromRenderObject
()
{
switch
(
widget
.
scrollDirection
)
{
case
Axis
.
vertical
:
return
renderObject
.
size
.
height
;
case
Axis
.
horizontal
:
return
renderObject
.
size
.
width
;
}
}
void
layout
(
BoxConstraints
constraints
)
{
final
int
length
=
renderObject
.
virtualChildCount
;
final
double
itemExtent
=
widget
.
itemExtent
;
final
EdgeDims
padding
=
widget
.
padding
??
EdgeDims
.
zero
;
final
Size
containerSize
=
renderObject
.
size
;
double
contentExtent
=
length
==
null
?
double
.
INFINITY
:
widget
.
itemExtent
*
length
+
padding
.
top
+
padding
.
bottom
;
double
containerExtent
=
_getContainerExtentFromRenderObject
();
double
containerExtent
;
double
contentExtent
;
double
leadingPadding
;
_materializedChildBase
=
math
.
max
(
0
,
(
widget
.
startOffset
-
padding
.
top
)
~/
itemExtent
);
int
materializedChildLimit
=
math
.
max
(
0
,
((
widget
.
startOffset
+
containerExtent
)
/
itemExtent
).
ceil
());
switch
(
widget
.
scrollDirection
)
{
case
Axis
.
vertical
:
containerExtent
=
containerSize
.
height
;
contentExtent
=
length
==
null
?
double
.
INFINITY
:
widget
.
itemExtent
*
length
+
padding
.
vertical
;
switch
(
widget
.
scrollAnchor
)
{
case
ViewportAnchor
.
start
:
leadingPadding
=
padding
.
top
;
break
;
case
ViewportAnchor
.
end
:
leadingPadding
=
padding
.
bottom
;
break
;
}
break
;
case
Axis
.
horizontal
:
containerExtent
=
renderObject
.
size
.
width
;
contentExtent
=
length
==
null
?
double
.
INFINITY
:
widget
.
itemExtent
*
length
+
padding
.
horizontal
;
switch
(
widget
.
scrollAnchor
)
{
case
ViewportAnchor
.
start
:
leadingPadding
=
padding
.
left
;
break
;
case
ViewportAnchor
.
end
:
leadingPadding
=
padding
.
right
;
break
;
}
break
;
}
if
(!
widget
.
itemsWrap
&&
length
!=
null
)
{
_materializedChildBase
=
math
.
min
(
length
,
_materializedChildBase
);
materializedChildLimit
=
math
.
min
(
length
,
materializedChildLimit
);
}
else
if
(
length
==
0
)
{
materializedChildLimit
=
_materializedChildBase
;
if
(
length
==
0
)
{
_materializedChildBase
=
0
;
_materializedChildCount
=
0
;
_startOffsetBase
=
0.0
;
_startOffsetLimit
=
double
.
INFINITY
;
}
else
{
int
startItem
=
math
.
max
(
0
,
(
widget
.
startOffset
+
leadingPadding
)
~/
itemExtent
);
int
limitItem
=
math
.
max
(
0
,
((
widget
.
startOffset
+
leadingPadding
+
containerExtent
)
/
itemExtent
).
ceil
());
if
(!
widget
.
itemsWrap
&&
length
!=
null
)
{
startItem
=
math
.
min
(
length
,
startItem
);
limitItem
=
math
.
min
(
length
,
limitItem
);
}
_materializedChildBase
=
startItem
;
_materializedChildCount
=
limitItem
-
startItem
;
_startOffsetBase
=
startItem
*
itemExtent
;
_startOffsetLimit
=
limitItem
*
itemExtent
-
containerExtent
;
if
(
widget
.
scrollAnchor
==
ViewportAnchor
.
end
)
_materializedChildBase
=
(
length
-
_materializedChildBase
-
_materializedChildCount
)
%
length
;
}
_materializedChildCount
=
materializedChildLimit
-
_materializedChildBase
;
_startOffsetBase
=
_materializedChildBase
*
itemExtent
;
_startOffsetLimit
=
materializedChildLimit
*
itemExtent
-
containerExtent
;
Size
materializedContentSize
;
switch
(
widget
.
scrollDirection
)
{
case
Axis
.
vertical
:
materializedContentSize
=
new
Size
(
containerSize
.
width
,
_materializedChildCount
*
itemExtent
);
break
;
case
Axis
.
horizontal
:
materializedContentSize
=
new
Size
(
_materializedChildCount
*
itemExtent
,
containerSize
.
height
);
break
;
}
renderObject
.
dimensions
=
new
ViewportDimensions
(
containerSize:
containerSize
,
contentSize:
materializedContentSize
);
super
.
layout
(
constraints
);
...
...
@@ -190,6 +236,7 @@ class ListViewport extends _VirtualListViewport with VirtualViewportIterableMixi
ExtentsChangedCallback
onExtentsChanged
,
double
startOffset:
0.0
,
Axis
scrollDirection:
Axis
.
vertical
,
ViewportAnchor
scrollAnchor:
ViewportAnchor
.
start
,
double
itemExtent
,
bool
itemsWrap:
false
,
EdgeDims
padding
,
...
...
@@ -199,6 +246,7 @@ class ListViewport extends _VirtualListViewport with VirtualViewportIterableMixi
onExtentsChanged
,
startOffset
,
scrollDirection
,
scrollAnchor
,
itemExtent
,
itemsWrap
,
padding
,
...
...
@@ -218,6 +266,7 @@ class ScrollableLazyList extends Scrollable {
Key
key
,
double
initialScrollOffset
,
Axis
scrollDirection:
Axis
.
vertical
,
ViewportAnchor
scrollAnchor:
ViewportAnchor
.
start
,
ScrollListener
onScroll
,
SnapOffsetCallback
snapOffsetCallback
,
double
snapAlignmentOffset:
0.0
,
...
...
@@ -230,12 +279,14 @@ class ScrollableLazyList extends Scrollable {
key:
key
,
initialScrollOffset:
initialScrollOffset
,
scrollDirection:
scrollDirection
,
scrollAnchor:
scrollAnchor
,
onScroll:
onScroll
,
snapOffsetCallback:
snapOffsetCallback
,
snapAlignmentOffset:
snapAlignmentOffset
)
{
assert
(
itemExtent
!=
null
);
assert
(
itemBuilder
!=
null
);
assert
(
itemCount
!=
null
||
scrollAnchor
==
ViewportAnchor
.
start
);
}
final
double
itemExtent
;
...
...
@@ -282,6 +333,7 @@ class _ScrollableLazyListState extends ScrollableState<ScrollableLazyList> {
onExtentsChanged:
_handleExtentsChanged
,
startOffset:
scrollOffset
,
scrollDirection:
config
.
scrollDirection
,
scrollAnchor:
config
.
scrollAnchor
,
itemExtent:
config
.
itemExtent
,
itemCount:
config
.
itemCount
,
itemBuilder:
config
.
itemBuilder
,
...
...
@@ -296,6 +348,7 @@ class LazyListViewport extends _VirtualListViewport with VirtualViewportLazyMixi
ExtentsChangedCallback
onExtentsChanged
,
double
startOffset:
0.0
,
Axis
scrollDirection:
Axis
.
vertical
,
ViewportAnchor
scrollAnchor:
ViewportAnchor
.
start
,
double
itemExtent
,
EdgeDims
padding
,
Painter
overlayPainter
,
...
...
@@ -305,6 +358,7 @@ class LazyListViewport extends _VirtualListViewport with VirtualViewportLazyMixi
onExtentsChanged
,
startOffset
,
scrollDirection
,
scrollAnchor
,
itemExtent
,
false
,
// Don't support wrapping yet.
padding
,
...
...
packages/flutter/lib/src/widgets/virtual_viewport.dart
View file @
b07203a8
...
...
@@ -13,7 +13,6 @@ typedef void ExtentsChangedCallback(double contentExtent, double containerExtent
abstract
class
VirtualViewport
extends
RenderObjectWidget
{
double
get
startOffset
;
Axis
get
scrollDirection
;
_WidgetProvider
_createWidgetProvider
();
}
...
...
@@ -33,7 +32,27 @@ abstract class VirtualViewportElement<T extends VirtualViewport> extends RenderO
double
get
startOffsetBase
;
double
get
startOffsetLimit
;
double
get
paintOffset
=>
-(
widget
.
startOffset
-
startOffsetBase
);
/// Returns the pixel offset for a scroll offset, accounting for the scroll
/// anchor.
double
scrollOffsetToPixelOffset
(
double
scrollOffset
)
{
switch
(
renderObject
.
scrollAnchor
)
{
case
ViewportAnchor
.
start
:
return
-
scrollOffset
;
case
ViewportAnchor
.
end
:
return
scrollOffset
;
}
}
/// Returns a two-dimensional representation of the scroll offset, accounting
/// for the scroll direction and scroll anchor.
Offset
scrollOffsetToPixelDelta
(
double
scrollOffset
)
{
switch
(
renderObject
.
scrollDirection
)
{
case
Axis
.
horizontal
:
return
new
Offset
(
scrollOffsetToPixelOffset
(
scrollOffset
),
0.0
);
case
Axis
.
vertical
:
return
new
Offset
(
0.0
,
scrollOffsetToPixelOffset
(
scrollOffset
));
}
}
List
<
Element
>
_materializedChildren
=
const
<
Element
>[];
...
...
@@ -71,14 +90,7 @@ abstract class VirtualViewportElement<T extends VirtualViewport> extends RenderO
}
void
_updatePaintOffset
()
{
switch
(
widget
.
scrollDirection
)
{
case
Axis
.
vertical
:
renderObject
.
paintOffset
=
new
Offset
(
0.0
,
paintOffset
);
break
;
case
Axis
.
horizontal
:
renderObject
.
paintOffset
=
new
Offset
(
paintOffset
,
0.0
);
break
;
}
renderObject
.
paintOffset
=
scrollOffsetToPixelDelta
(
widget
.
startOffset
-
startOffsetBase
);
}
void
updateRenderObject
(
T
oldWidget
)
{
...
...
packages/flutter/test/widget/scrollable_list_horizontal_test.dart
View file @
b07203a8
...
...
@@ -9,13 +9,14 @@ import 'package:test/test.dart';
const
List
<
int
>
items
=
const
<
int
>[
0
,
1
,
2
,
3
,
4
,
5
];
Widget
buildFrame
(
)
{
Widget
buildFrame
(
ViewportAnchor
scrollAnchor
)
{
return
new
Center
(
child:
new
Container
(
height:
50.0
,
child:
new
ScrollableList
(
itemExtent:
290.0
,
scrollDirection:
Axis
.
horizontal
,
scrollAnchor:
scrollAnchor
,
children:
items
.
map
((
int
item
)
{
return
new
Container
(
child:
new
Text
(
'
$item
'
)
...
...
@@ -27,9 +28,9 @@ Widget buildFrame() {
}
void
main
(
)
{
test
(
'Drag horizontally'
,
()
{
test
(
'Drag horizontally
with scroll anchor at top
'
,
()
{
testWidgets
((
WidgetTester
tester
)
{
tester
.
pumpWidget
(
buildFrame
());
tester
.
pumpWidget
(
buildFrame
(
ViewportAnchor
.
start
));
tester
.
pump
(
const
Duration
(
seconds:
1
));
tester
.
scroll
(
tester
.
findText
(
'1'
),
const
Offset
(-
300.0
,
0.0
));
...
...
@@ -46,7 +47,7 @@ void main() {
expect
(
tester
.
findText
(
'5'
),
isNull
);
// the center of item 3 is visible, so this works;
// if item 3 was a bit wider, such that it
'
s center was past the 800px mark, this would fail,
// if item 3 was a bit wider, such that its center was past the 800px mark, this would fail,
// because it wouldn't be hit tested when scrolling from its center, as scroll() does.
tester
.
pump
(
const
Duration
(
seconds:
1
));
tester
.
scroll
(
tester
.
findText
(
'3'
),
const
Offset
(-
290.0
,
0.0
));
...
...
@@ -117,7 +118,7 @@ void main() {
expect
(
tester
.
findText
(
'5'
),
isNotNull
);
tester
.
pumpWidget
(
new
Container
());
tester
.
pumpWidget
(
buildFrame
(),
const
Duration
(
seconds:
1
));
tester
.
pumpWidget
(
buildFrame
(
ViewportAnchor
.
start
),
const
Duration
(
seconds:
1
));
tester
.
scroll
(
tester
.
findText
(
'2'
),
const
Offset
(-
280.0
,
0.0
));
tester
.
pump
(
const
Duration
(
seconds:
1
));
// screen is 800px wide, and has the following items:
...
...
@@ -147,4 +148,106 @@ void main() {
expect
(
tester
.
findText
(
'5'
),
isNull
);
});
});
test
(
'Drag horizontally with scroll anchor at end'
,
()
{
testWidgets
((
WidgetTester
tester
)
{
tester
.
pumpWidget
(
buildFrame
(
ViewportAnchor
.
end
));
tester
.
pump
(
const
Duration
(
seconds:
1
));
// screen is 800px wide, and has the following items:
// -70..220 = 3
// 220..510 = 4
// 510..800 = 5
expect
(
tester
.
findText
(
'0'
),
isNull
);
expect
(
tester
.
findText
(
'1'
),
isNull
);
expect
(
tester
.
findText
(
'2'
),
isNull
);
expect
(
tester
.
findText
(
'3'
),
isNotNull
);
expect
(
tester
.
findText
(
'4'
),
isNotNull
);
expect
(
tester
.
findText
(
'5'
),
isNotNull
);
tester
.
scroll
(
tester
.
findText
(
'5'
),
const
Offset
(
300.0
,
0.0
));
tester
.
pump
(
const
Duration
(
seconds:
1
));
// screen is 800px wide, and has the following items:
// -80..210 = 2
// 230..520 = 3
// 520..810 = 4
expect
(
tester
.
findText
(
'0'
),
isNull
);
expect
(
tester
.
findText
(
'1'
),
isNull
);
expect
(
tester
.
findText
(
'2'
),
isNotNull
);
expect
(
tester
.
findText
(
'3'
),
isNotNull
);
expect
(
tester
.
findText
(
'4'
),
isNotNull
);
expect
(
tester
.
findText
(
'5'
),
isNull
);
// the center of item 3 is visible, so this works;
// if item 3 was a bit wider, such that its center was past the 800px mark, this would fail,
// because it wouldn't be hit tested when scrolling from its center, as scroll() does.
tester
.
pump
(
const
Duration
(
seconds:
1
));
tester
.
scroll
(
tester
.
findText
(
'3'
),
const
Offset
(
290.0
,
0.0
));
tester
.
pump
(
const
Duration
(
seconds:
1
));
// screen is 800px wide, and has the following items:
// -10..280 = 1
// 280..570 = 2
// 570..860 = 3
expect
(
tester
.
findText
(
'0'
),
isNull
);
expect
(
tester
.
findText
(
'1'
),
isNotNull
);
expect
(
tester
.
findText
(
'2'
),
isNotNull
);
expect
(
tester
.
findText
(
'3'
),
isNotNull
);
expect
(
tester
.
findText
(
'4'
),
isNull
);
expect
(
tester
.
findText
(
'5'
),
isNull
);
tester
.
pump
(
const
Duration
(
seconds:
1
));
tester
.
scroll
(
tester
.
findText
(
'3'
),
const
Offset
(
0.0
,
290.0
));
tester
.
pump
(
const
Duration
(
seconds:
1
));
// unchanged
expect
(
tester
.
findText
(
'0'
),
isNull
);
expect
(
tester
.
findText
(
'1'
),
isNotNull
);
expect
(
tester
.
findText
(
'2'
),
isNotNull
);
expect
(
tester
.
findText
(
'3'
),
isNotNull
);
expect
(
tester
.
findText
(
'4'
),
isNull
);
expect
(
tester
.
findText
(
'5'
),
isNull
);
tester
.
pump
(
const
Duration
(
seconds:
1
));
tester
.
scroll
(
tester
.
findText
(
'2'
),
const
Offset
(
290.0
,
0.0
));
tester
.
pump
(
const
Duration
(
seconds:
1
));
// screen is 800px wide, and has the following items:
// -10..280 = 0
// 280..570 = 1
// 570..860 = 2
expect
(
tester
.
findText
(
'0'
),
isNotNull
);
expect
(
tester
.
findText
(
'1'
),
isNotNull
);
expect
(
tester
.
findText
(
'2'
),
isNotNull
);
expect
(
tester
.
findText
(
'3'
),
isNull
);
expect
(
tester
.
findText
(
'4'
),
isNull
);
expect
(
tester
.
findText
(
'5'
),
isNull
);
tester
.
pump
(
const
Duration
(
seconds:
1
));
// at this point we can drag 60 pixels further before we hit the friction zone
// then, every pixel we drag is equivalent to half a pixel of movement
// to move item 3 entirely off screen therefore takes:
// 60 + (290-60)*2 = 520 pixels
// plus a couple more to be sure
tester
.
scroll
(
tester
.
findText
(
'1'
),
const
Offset
(
522.0
,
0.0
));
tester
.
pump
();
// just after release
// screen is 800px wide, and has the following items:
// 280..570 = 0
// 570..860 = 1
expect
(
tester
.
findText
(
'0'
),
isNotNull
);
expect
(
tester
.
findText
(
'1'
),
isNotNull
);
expect
(
tester
.
findText
(
'2'
),
isNull
);
expect
(
tester
.
findText
(
'3'
),
isNull
);
expect
(
tester
.
findText
(
'4'
),
isNull
);
expect
(
tester
.
findText
(
'5'
),
isNull
);
tester
.
pump
(
const
Duration
(
seconds:
1
));
// a second after release
// screen is 800px wide, and has the following items:
// 0..290 = 0
// 290..580 = 1
// 580..870 = 2
expect
(
tester
.
findText
(
'0'
),
isNotNull
);
expect
(
tester
.
findText
(
'1'
),
isNotNull
);
expect
(
tester
.
findText
(
'2'
),
isNotNull
);
expect
(
tester
.
findText
(
'3'
),
isNull
);
expect
(
tester
.
findText
(
'4'
),
isNull
);
expect
(
tester
.
findText
(
'5'
),
isNull
);
});
});
}
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