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
daa7860e
Commit
daa7860e
authored
Jul 19, 2017
by
Hans Muller
Committed by
GitHub
Jul 19, 2017
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add a ScrollController parameter to NestedScrollView (#11242)
parent
5f9e5605
Changes
3
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
211 additions
and
17 deletions
+211
-17
nested_scroll_view.dart
packages/flutter/lib/src/widgets/nested_scroll_view.dart
+12
-11
scroll_controller.dart
packages/flutter/lib/src/widgets/scroll_controller.dart
+91
-3
nested_scroll_view_test.dart
packages/flutter/test/widgets/nested_scroll_view_test.dart
+108
-3
No files found.
packages/flutter/lib/src/widgets/nested_scroll_view.dart
View file @
daa7860e
...
...
@@ -32,12 +32,10 @@ import 'ticker_provider.dart';
/// content ostensibly below it.
typedef
List
<
Widget
>
NestedScrollViewHeaderSliversBuilder
(
BuildContext
context
,
bool
innerBoxIsScrolled
);
// TODO(abarth): Make this configurable with a controller.
const
double
_kInitialScrollOffset
=
0.0
;
class
NestedScrollView
extends
StatefulWidget
{
const
NestedScrollView
({
Key
key
,
this
.
controller
,
this
.
scrollDirection
:
Axis
.
vertical
,
this
.
reverse
:
false
,
this
.
physics
,
...
...
@@ -49,7 +47,9 @@ class NestedScrollView extends StatefulWidget {
assert
(
body
!=
null
),
super
(
key:
key
);
// TODO(ianh): we should expose a controller so you can call animateTo, etc.
/// An object that can be used to control the position to which the outer
/// scroll view is scrolled.
final
ScrollController
controller
;
/// The axis along which the scroll view scrolls.
///
...
...
@@ -114,7 +114,7 @@ class _NestedScrollViewState extends State<NestedScrollView> {
@override
void
initState
()
{
super
.
initState
();
_coordinator
=
new
_NestedScrollCoordinator
(
context
,
_kInitialScrollOffset
);
_coordinator
=
new
_NestedScrollCoordinator
(
context
,
widget
.
controller
);
}
@override
...
...
@@ -170,12 +170,14 @@ class _NestedScrollMetrics extends FixedScrollMetrics {
typedef
ScrollActivity
_NestedScrollActivityGetter
(
_NestedScrollPosition
position
);
class
_NestedScrollCoordinator
implements
ScrollActivityDelegate
,
ScrollHoldController
{
_NestedScrollCoordinator
(
this
.
_context
,
double
initialScrollOffset
)
{
_NestedScrollCoordinator
(
this
.
_context
,
this
.
_parent
)
{
final
double
initialScrollOffset
=
_parent
?.
initialScrollOffset
??
0.0
;
_outerController
=
new
_NestedScrollController
(
this
,
initialScrollOffset:
initialScrollOffset
,
debugLabel:
'outer'
);
_innerController
=
new
_NestedScrollController
(
this
,
initialScrollOffset:
initialScrollOffset
,
debugLabel:
'inner'
);
_innerController
=
new
_NestedScrollController
(
this
,
initialScrollOffset:
0.0
,
debugLabel:
'inner'
);
}
final
BuildContext
_context
;
final
ScrollController
_parent
;
_NestedScrollController
_outerController
;
_NestedScrollController
_innerController
;
...
...
@@ -407,7 +409,7 @@ class _NestedScrollCoordinator implements ScrollActivityDelegate, ScrollHoldCont
Future
<
Null
>
animateTo
(
double
to
,
{
@required
Duration
duration
,
@required
Curve
curve
,
})
{
})
async
{
final
DrivenScrollActivity
outerActivity
=
_outerPosition
.
createDrivenScrollActivity
(
nestOffset
(
to
,
_outerPosition
),
duration
,
...
...
@@ -426,7 +428,7 @@ class _NestedScrollCoordinator implements ScrollActivityDelegate, ScrollHoldCont
return
innerActivity
;
},
);
return
Future
.
wait
<
Null
>(
resultFutures
);
await
Future
.
wait
<
Null
>(
resultFutures
);
}
void
jumpTo
(
double
to
)
{
...
...
@@ -513,7 +515,7 @@ class _NestedScrollCoordinator implements ScrollActivityDelegate, ScrollHoldCont
}
void
updateParent
()
{
_outerPosition
?.
setParent
(
PrimaryScrollController
.
of
(
_context
));
_outerPosition
?.
setParent
(
_parent
??
PrimaryScrollController
.
of
(
_context
));
}
@mustCallSuper
...
...
@@ -827,7 +829,6 @@ class _NestedOuterBallisticScrollActivity extends BallisticScrollActivity {
done
=
true
;
}
}
else
if
(
velocity
<
0.0
)
{
assert
(
velocity
<
0.0
);
if
(
value
>
metrics
.
maxRange
)
return
true
;
if
(
value
<
metrics
.
minRange
)
{
...
...
packages/flutter/lib/src/widgets/scroll_controller.dart
View file @
daa7860e
...
...
@@ -45,11 +45,12 @@ class ScrollController extends ChangeNotifier {
///
/// The values of `initialScrollOffset` and `keepScrollOffset` must not be null.
ScrollController
({
this
.
initialScrollOffset
:
0.0
,
double
initialScrollOffset:
0.0
,
this
.
keepScrollOffset
:
true
,
this
.
debugLabel
,
})
:
assert
(
initialScrollOffset
!=
null
),
assert
(
keepScrollOffset
!=
null
);
assert
(
keepScrollOffset
!=
null
),
_initialScrollOffset
=
initialScrollOffset
;
/// The initial value to use for [offset].
///
...
...
@@ -58,7 +59,8 @@ class ScrollController extends ChangeNotifier {
/// if [keepScrollOffset] is false or a scroll offset hasn't been saved yet.
///
/// Defaults to 0.0.
final
double
initialScrollOffset
;
final
double
_initialScrollOffset
;
double
get
initialScrollOffset
=>
_initialScrollOffset
;
/// Each time a scroll completes, save the current scroll [offset] with
/// [PageStorage] and restore it if this controller's scrollable is recreated.
...
...
@@ -266,3 +268,89 @@ class ScrollController extends ChangeNotifier {
}
}
}
// Examples can assume:
// TrackingScrollController _trackingScrollController;
/// A [ScrollController] whose `initialScrollOffset` tracks its most recently
/// updated [ScrollPosition].
///
/// This class can be used to synchronize the scroll offset of two or more
/// lazily created scroll views that share a single [TrackingScrollController].
/// It tracks the most recently updated scroll position and reports it as its
/// `initialScrollOffset`.
///
/// ## Sample code
///
/// In this example each [PageView] page contains a [ListView] and all three
/// [ListView]'s share a [TrackingController]. The scroll offsets of all three
/// list views will track each other, to the extent that's possible given the
/// different list lengths.
///
/// ```dart
/// new PageView(
/// children: <Widget>[
/// new ListView(
/// controller: _trackingScrollController,
/// children: new List<Widget>.generate(100, (int i) => new Text('page 0 item $i')).toList(),
/// ),
/// new ListView(
/// controller: _trackingScrollController,
/// children: new List<Widget>.generate(200, (int i) => new Text('page 1 item $i')).toList(),
/// ),
/// new ListView(
/// controller: _trackingScrollController,
/// children: new List<Widget>.generate(300, (int i) => new Text('page 2 item $i')).toList(),
/// ),
/// ],
/// )
/// ```
///
/// In this example the `_trackingController` would have been created by the
/// stateful widget that built the widget tree.
class
TrackingScrollController
extends
ScrollController
{
TrackingScrollController
({
double
initialScrollOffset:
0.0
,
bool
keepScrollOffset:
true
,
String
debugLabel
,
})
:
super
(
initialScrollOffset:
initialScrollOffset
,
keepScrollOffset:
keepScrollOffset
,
debugLabel:
debugLabel
);
Map
<
ScrollPosition
,
VoidCallback
>
_positionToListener
=
<
ScrollPosition
,
VoidCallback
>{};
ScrollPosition
_lastUpdated
;
/// The last [ScrollPosition] to change. Returns null if there aren't any
/// attached scroll positions or there hasn't been any scrolling yet.
ScrollPosition
get
mostRecentlyUpdatedPosition
=>
_lastUpdated
;
/// Returns the scroll offset of the [mostRecentlyUpdatedPosition] or 0.0.
@override
double
get
initialScrollOffset
=>
_lastUpdated
?.
pixels
??
super
.
initialScrollOffset
;
@override
void
attach
(
ScrollPosition
position
)
{
super
.
attach
(
position
);
assert
(!
_positionToListener
.
containsKey
(
position
));
_positionToListener
[
position
]
=
()
{
_lastUpdated
=
position
;
};
position
.
addListener
(
_positionToListener
[
position
]);
}
@override
void
detach
(
ScrollPosition
position
)
{
super
.
detach
(
position
);
assert
(
_positionToListener
.
containsKey
(
position
));
position
.
removeListener
(
_positionToListener
[
position
]);
_positionToListener
.
remove
(
position
);
}
@override
void
dispose
()
{
for
(
ScrollPosition
position
in
positions
)
{
assert
(
_positionToListener
.
containsKey
(
position
));
position
.
removeListener
(
_positionToListener
[
position
]);
}
_positionToListener
.
clear
();
super
.
dispose
();
}
}
packages/flutter/test/widgets/nested_scroll_view_test.dart
View file @
daa7860e
...
...
@@ -6,17 +6,18 @@ import 'package:flutter/foundation.dart';
import
'package:flutter/material.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
Widget
buildTest
(
)
{
Widget
buildTest
(
{
ScrollController
controller
,
String
title:
'TTTTTTTT'
}
)
{
return
new
MediaQuery
(
data:
const
MediaQueryData
(),
child:
new
Scaffold
(
body:
new
DefaultTabController
(
length:
4
,
child:
new
NestedScrollView
(
controller:
controller
,
headerSliverBuilder:
(
BuildContext
context
,
bool
innerBoxIsScrolled
)
{
return
<
Widget
>[
new
SliverAppBar
(
title:
const
Text
(
'TTTTTTTT'
),
title:
new
Text
(
title
),
pinned:
true
,
expandedHeight:
200.0
,
forceElevated:
innerBoxIsScrolled
,
...
...
@@ -183,4 +184,108 @@ void main() {
expect
(
find
.
text
(
'ccc1'
),
findsOneWidget
);
expect
(
tester
.
renderObject
<
RenderBox
>(
find
.
byType
(
AppBar
)).
size
.
height
,
200.0
);
});
testWidgets
(
'NestedScrollView with a ScrollController'
,
(
WidgetTester
tester
)
async
{
final
ScrollController
controller
=
new
ScrollController
(
initialScrollOffset:
50.0
);
double
scrollOffset
;
controller
.
addListener
(()
{
scrollOffset
=
controller
.
offset
;
});
await
tester
.
pumpWidget
(
buildTest
(
controller:
controller
));
expect
(
controller
.
position
.
minScrollExtent
,
0.0
);
expect
(
controller
.
position
.
pixels
,
50.0
);
expect
(
controller
.
position
.
maxScrollExtent
,
200.0
);
// The appbar's expandedHeight - initialScrollOffset = 150.
expect
(
tester
.
renderObject
<
RenderBox
>(
find
.
byType
(
AppBar
)).
size
.
height
,
150.0
);
// Fully expand the appbar by scrolling (no animation) to 0.0.
controller
.
jumpTo
(
0.0
);
await
(
tester
.
pumpAndSettle
());
expect
(
scrollOffset
,
0.0
);
expect
(
tester
.
renderObject
<
RenderBox
>(
find
.
byType
(
AppBar
)).
size
.
height
,
200.0
);
// Scroll back to 50.0 animating over 100ms.
controller
.
animateTo
(
50.0
,
duration:
const
Duration
(
milliseconds:
100
),
curve:
Curves
.
linear
);
await
tester
.
pump
();
await
tester
.
pump
();
expect
(
scrollOffset
,
0.0
);
expect
(
tester
.
renderObject
<
RenderBox
>(
find
.
byType
(
AppBar
)).
size
.
height
,
200.0
);
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
));
// 50ms - halfway to scroll offset = 50.0.
expect
(
scrollOffset
,
25.0
);
expect
(
tester
.
renderObject
<
RenderBox
>(
find
.
byType
(
AppBar
)).
size
.
height
,
175.0
);
await
tester
.
pump
(
const
Duration
(
milliseconds:
50
));
// 100ms - all the way to scroll offset = 50.0.
expect
(
scrollOffset
,
50.0
);
expect
(
tester
.
renderObject
<
RenderBox
>(
find
.
byType
(
AppBar
)).
size
.
height
,
150.0
);
// Scroll to the end, (we're not scrolling to the end of the list that contains aaa1,
// just to the end of the outer scrollview). Verify that the first item in each tab
// is still visible.
controller
.
jumpTo
(
controller
.
position
.
maxScrollExtent
);
await
tester
.
pumpAndSettle
();
expect
(
scrollOffset
,
200.0
);
expect
(
find
.
text
(
'aaa1'
),
findsOneWidget
);
await
tester
.
tap
(
find
.
text
(
'BB'
));
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
'bbb1'
),
findsOneWidget
);
await
tester
.
tap
(
find
.
text
(
'CC'
));
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
'ccc1'
),
findsOneWidget
);
await
tester
.
tap
(
find
.
text
(
'DD'
));
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
'ddd1'
),
findsOneWidget
);
});
testWidgets
(
'Three NestedScrollViews with one ScrollController'
,
(
WidgetTester
tester
)
async
{
final
TrackingScrollController
controller
=
new
TrackingScrollController
();
expect
(
controller
.
mostRecentlyUpdatedPosition
,
isNull
);
expect
(
controller
.
initialScrollOffset
,
0.0
);
await
tester
.
pumpWidget
(
new
PageView
(
children:
<
Widget
>[
buildTest
(
controller:
controller
,
title:
'Page0'
),
buildTest
(
controller:
controller
,
title:
'Page1'
),
buildTest
(
controller:
controller
,
title:
'Page2'
),
],
),
);
// Initially Page0 is visible and Page0's appbar is fully expanded (height = 200.0).
expect
(
find
.
text
(
'Page0'
),
findsOneWidget
);
expect
(
find
.
text
(
'Page1'
),
findsNothing
);
expect
(
find
.
text
(
'Page2'
),
findsNothing
);
expect
(
tester
.
renderObject
<
RenderBox
>(
find
.
byType
(
AppBar
)).
size
.
height
,
200.0
);
// A scroll collapses Page0's appbar to 150.0.
controller
.
jumpTo
(
50.0
);
await
(
tester
.
pumpAndSettle
());
expect
(
tester
.
renderObject
<
RenderBox
>(
find
.
byType
(
AppBar
)).
size
.
height
,
150.0
);
// Fling to Page1. Page1's appbar height is the same as the appbar for Page0.
await
tester
.
fling
(
find
.
text
(
'Page0'
),
const
Offset
(-
100.0
,
0.0
),
10000.0
);
await
(
tester
.
pumpAndSettle
());
expect
(
find
.
text
(
'Page0'
),
findsNothing
);
expect
(
find
.
text
(
'Page1'
),
findsOneWidget
);
expect
(
find
.
text
(
'Page2'
),
findsNothing
);
expect
(
tester
.
renderObject
<
RenderBox
>(
find
.
byType
(
AppBar
)).
size
.
height
,
150.0
);
// Expand Page1's appbar and then fling to Page2. Page2's appbar appears
// fully expanded.
controller
.
jumpTo
(
0.0
);
await
(
tester
.
pumpAndSettle
());
expect
(
tester
.
renderObject
<
RenderBox
>(
find
.
byType
(
AppBar
)).
size
.
height
,
200.0
);
await
tester
.
fling
(
find
.
text
(
'Page1'
),
const
Offset
(-
100.0
,
0.0
),
10000.0
);
await
(
tester
.
pumpAndSettle
());
expect
(
find
.
text
(
'Page0'
),
findsNothing
);
expect
(
find
.
text
(
'Page1'
),
findsNothing
);
expect
(
find
.
text
(
'Page2'
),
findsOneWidget
);
expect
(
tester
.
renderObject
<
RenderBox
>(
find
.
byType
(
AppBar
)).
size
.
height
,
200.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