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
25de9419
Unverified
Commit
25de9419
authored
Aug 11, 2020
by
Michael Goderbauer
Committed by
GitHub
Aug 11, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Make Scrollables restorable (#63131)
parent
a84e3902
Changes
11
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
840 additions
and
6 deletions
+840
-6
restoration.dart
packages/flutter/lib/src/services/restoration.dart
+33
-4
list_wheel_scroll_view.dart
packages/flutter/lib/src/widgets/list_wheel_scroll_view.dart
+8
-0
nested_scroll_view.dart
packages/flutter/lib/src/widgets/nested_scroll_view.dart
+7
-0
page_view.dart
packages/flutter/lib/src/widgets/page_view.dart
+23
-0
scroll_context.dart
packages/flutter/lib/src/widgets/scroll_context.dart
+9
-0
scroll_position.dart
packages/flutter/lib/src/widgets/scroll_position.dart
+40
-0
scroll_view.dart
packages/flutter/lib/src/widgets/scroll_view.dart
+29
-0
scrollable.dart
packages/flutter/lib/src/widgets/scrollable.dart
+72
-2
single_child_scroll_view.dart
...ges/flutter/lib/src/widgets/single_child_scroll_view.dart
+5
-0
restoration_test.dart
packages/flutter/test/services/restoration_test.dart
+37
-0
scrollable_restoration_test.dart
...ges/flutter/test/widgets/scrollable_restoration_test.dart
+577
-0
No files found.
packages/flutter/lib/src/services/restoration.dart
View file @
25de9419
...
...
@@ -250,7 +250,7 @@ class RestorationManager extends ChangeNotifier {
}
bool
_debugDoingUpdate
=
false
;
bool
_
postFrame
Scheduled
=
false
;
bool
_
serialization
Scheduled
=
false
;
final
Set
<
RestorationBucket
>
_bucketsNeedingSerialization
=
<
RestorationBucket
>{};
...
...
@@ -270,8 +270,8 @@ class RestorationManager extends ChangeNotifier {
assert
(
bucket
.
_manager
==
this
);
assert
(!
_debugDoingUpdate
);
_bucketsNeedingSerialization
.
add
(
bucket
);
if
(!
_
postFrame
Scheduled
)
{
_
postFrame
Scheduled
=
true
;
if
(!
_
serialization
Scheduled
)
{
_
serialization
Scheduled
=
true
;
SchedulerBinding
.
instance
!.
addPostFrameCallback
((
Duration
_
)
=>
_doSerialization
());
}
}
...
...
@@ -295,11 +295,14 @@ class RestorationManager extends ChangeNotifier {
}
void
_doSerialization
()
{
if
(!
_serializationScheduled
)
{
return
;
}
assert
(()
{
_debugDoingUpdate
=
true
;
return
true
;
}());
_
postFrame
Scheduled
=
false
;
_
serialization
Scheduled
=
false
;
for
(
final
RestorationBucket
bucket
in
_bucketsNeedingSerialization
)
{
bucket
.
finalize
();
...
...
@@ -312,6 +315,32 @@ class RestorationManager extends ChangeNotifier {
return
true
;
}());
}
/// Called to manually flush the restoration data to the engine.
///
/// A change in restoration data is usually accompanied by scheduling a frame
/// (because the restoration data is modified inside a [State.setState] call,
/// because it is usually something that affects the interface). Restoration
/// data is automatically flushed to the engine at the end of a frame. As a
/// result, it is uncommon to need to call this method directly. However, if
/// restoration data is changed without triggering a frame, this method must
/// be called to ensure that the updated restoration data is sent to the
/// engine in a timely manner. An example of such a use case is the
/// [Scrollable], where the final scroll offset after a scroll activity
/// finishes is determined between frames without scheduling a new frame.
///
/// Calling this method is a no-op if a frame is already scheduled. In that
/// case, the restoration data will be flushed to the engine at the end of
/// that frame. If this method is called and no frame is scheduled, the
/// current restoration data is directly sent to the engine.
void
flushData
()
{
assert
(!
_debugDoingUpdate
);
if
(
SchedulerBinding
.
instance
!.
hasScheduledFrame
)
{
return
;
}
_doSerialization
();
assert
(!
_serializationScheduled
);
}
}
/// A [RestorationBucket] holds pieces of the restoration data that a part of
...
...
packages/flutter/lib/src/widgets/list_wheel_scroll_view.dart
View file @
25de9419
...
...
@@ -434,12 +434,14 @@ class _FixedExtentScrollable extends Scrollable {
ScrollPhysics
physics
,
@required
this
.
itemExtent
,
@required
ViewportBuilder
viewportBuilder
,
String
restorationId
,
})
:
super
(
key:
key
,
axisDirection:
axisDirection
,
controller:
controller
,
physics:
physics
,
viewportBuilder:
viewportBuilder
,
restorationId:
restorationId
,
);
final
double
itemExtent
;
...
...
@@ -585,6 +587,7 @@ class ListWheelScrollView extends StatefulWidget {
this
.
onSelectedItemChanged
,
this
.
renderChildrenOutsideViewport
=
false
,
this
.
clipBehavior
=
Clip
.
hardEdge
,
this
.
restorationId
,
@required
List
<
Widget
>
children
,
})
:
assert
(
children
!=
null
),
assert
(
diameterRatio
!=
null
),
...
...
@@ -625,6 +628,7 @@ class ListWheelScrollView extends StatefulWidget {
this
.
onSelectedItemChanged
,
this
.
renderChildrenOutsideViewport
=
false
,
this
.
clipBehavior
=
Clip
.
hardEdge
,
this
.
restorationId
,
@required
this
.
childDelegate
,
})
:
assert
(
childDelegate
!=
null
),
assert
(
diameterRatio
!=
null
),
...
...
@@ -713,6 +717,9 @@ class ListWheelScrollView extends StatefulWidget {
/// Defaults to [Clip.hardEdge].
final
Clip
clipBehavior
;
/// {@macro flutter.widgets.scrollable.restorationId}
final
String
restorationId
;
@override
_ListWheelScrollViewState
createState
()
=>
_ListWheelScrollViewState
();
}
...
...
@@ -765,6 +772,7 @@ class _ListWheelScrollViewState extends State<ListWheelScrollView> {
controller:
scrollController
,
physics:
widget
.
physics
,
itemExtent:
widget
.
itemExtent
,
restorationId:
widget
.
restorationId
,
viewportBuilder:
(
BuildContext
context
,
ViewportOffset
offset
)
{
return
ListWheelViewport
(
diameterRatio:
widget
.
diameterRatio
,
...
...
packages/flutter/lib/src/widgets/nested_scroll_view.dart
View file @
25de9419
...
...
@@ -376,6 +376,7 @@ class NestedScrollView extends StatefulWidget {
this
.
dragStartBehavior
=
DragStartBehavior
.
start
,
this
.
floatHeaderSlivers
=
false
,
this
.
clipBehavior
=
Clip
.
hardEdge
,
this
.
restorationId
,
})
:
assert
(
scrollDirection
!=
null
),
assert
(
reverse
!=
null
),
assert
(
headerSliverBuilder
!=
null
),
...
...
@@ -457,6 +458,9 @@ class NestedScrollView extends StatefulWidget {
/// Defaults to [Clip.hardEdge].
final
Clip
clipBehavior
;
/// {@macro flutter.widgets.scrollable.restorationId}
final
String
restorationId
;
/// Returns the [SliverOverlapAbsorberHandle] of the nearest ancestor
/// [NestedScrollView].
///
...
...
@@ -636,6 +640,7 @@ class NestedScrollViewState extends State<NestedScrollView> {
),
handle:
_absorberHandle
,
clipBehavior:
widget
.
clipBehavior
,
restorationId:
widget
.
restorationId
,
);
},
),
...
...
@@ -653,6 +658,7 @@ class _NestedScrollViewCustomScrollView extends CustomScrollView {
@required
this
.
handle
,
@required
this
.
clipBehavior
,
DragStartBehavior
dragStartBehavior
=
DragStartBehavior
.
start
,
String
restorationId
,
})
:
super
(
scrollDirection:
scrollDirection
,
reverse:
reverse
,
...
...
@@ -660,6 +666,7 @@ class _NestedScrollViewCustomScrollView extends CustomScrollView {
controller:
controller
,
slivers:
slivers
,
dragStartBehavior:
dragStartBehavior
,
restorationId:
restorationId
,
);
final
SliverOverlapAbsorberHandle
handle
;
...
...
packages/flutter/lib/src/widgets/page_view.dart
View file @
25de9419
...
...
@@ -390,6 +390,22 @@ class _PagePosition extends ScrollPositionWithSingleContext implements PageMetri
}
}
@override
void
saveOffset
()
{
context
.
saveOffset
(
getPageFromPixels
(
pixels
,
viewportDimension
));
}
@override
void
restoreOffset
(
double
offset
,
{
bool
initialRestore
=
false
})
{
assert
(
initialRestore
!=
null
);
assert
(
offset
!=
null
);
if
(
initialRestore
)
{
_pageToUseOnStartup
=
offset
;
}
else
{
jumpTo
(
getPixelsFromPage
(
offset
));
}
}
@override
bool
applyViewportDimension
(
double
viewportDimension
)
{
final
double
oldViewportDimensions
=
this
.
viewportDimension
;
...
...
@@ -570,6 +586,7 @@ class PageView extends StatefulWidget {
List
<
Widget
>
children
=
const
<
Widget
>[],
this
.
dragStartBehavior
=
DragStartBehavior
.
start
,
this
.
allowImplicitScrolling
=
false
,
this
.
restorationId
,
})
:
assert
(
allowImplicitScrolling
!=
null
),
controller
=
controller
??
_defaultPageController
,
childrenDelegate
=
SliverChildListDelegate
(
children
),
...
...
@@ -605,6 +622,7 @@ class PageView extends StatefulWidget {
int
itemCount
,
this
.
dragStartBehavior
=
DragStartBehavior
.
start
,
this
.
allowImplicitScrolling
=
false
,
this
.
restorationId
,
})
:
assert
(
allowImplicitScrolling
!=
null
),
controller
=
controller
??
_defaultPageController
,
childrenDelegate
=
SliverChildBuilderDelegate
(
itemBuilder
,
childCount:
itemCount
),
...
...
@@ -703,6 +721,7 @@ class PageView extends StatefulWidget {
@required
this
.
childrenDelegate
,
this
.
dragStartBehavior
=
DragStartBehavior
.
start
,
this
.
allowImplicitScrolling
=
false
,
this
.
restorationId
,
})
:
assert
(
childrenDelegate
!=
null
),
assert
(
allowImplicitScrolling
!=
null
),
controller
=
controller
??
_defaultPageController
,
...
...
@@ -721,6 +740,9 @@ class PageView extends StatefulWidget {
/// will traverse to the next page in the page view.
final
bool
allowImplicitScrolling
;
/// {@macro flutter.widgets.scrollable.restorationId}
final
String
restorationId
;
/// The axis along which the page view scrolls.
///
/// Defaults to [Axis.horizontal].
...
...
@@ -824,6 +846,7 @@ class _PageViewState extends State<PageView> {
axisDirection:
axisDirection
,
controller:
widget
.
controller
,
physics:
physics
,
restorationId:
widget
.
restorationId
,
viewportBuilder:
(
BuildContext
context
,
ViewportOffset
position
)
{
return
Viewport
(
// TODO(dnfield): we should provide a way to set cacheExtent
...
...
packages/flutter/lib/src/widgets/scroll_context.dart
View file @
25de9419
...
...
@@ -35,6 +35,7 @@ abstract class ScrollContext {
/// particular, it should involve any [GlobalKey]s that are dynamically
/// created as part of creating the scrolling widget, since those would be
/// different each time the widget is created.
// TODO(goderbauer): Deprecate this when state restoration supports all features of PageStorage.
BuildContext
get
storageContext
;
/// A [TickerProvider] to use when animating the scroll position.
...
...
@@ -61,4 +62,12 @@ abstract class ScrollContext {
/// Set the [SemanticsAction]s that should be expose to the semantics tree.
void
setSemanticsActions
(
Set
<
SemanticsAction
>
actions
);
/// Called by the [ScrollPosition] whenever scrolling ends to persist the
/// provided scroll `offset` for state restoration purposes.
///
/// The [ScrollContext] may pass the value back to a [ScrollPosition] by
/// calling [ScrollPosition.restoreOffset] at a later point in time or after
/// the application has restarted to restore the scroll offset.
void
saveOffset
(
double
offset
);
}
packages/flutter/lib/src/widgets/scroll_position.dart
View file @
25de9419
...
...
@@ -128,6 +128,7 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics {
///
/// * [ScrollController.keepScrollOffset] and [PageController.keepPage], which
/// create scroll positions and initialize this property.
// TODO(goderbauer): Deprecate this when state restoration supports all features of PageStorage.
final
bool
keepScrollOffset
;
/// A label that is used in the [toString] output.
...
...
@@ -358,6 +359,7 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics {
/// The default implementation writes the [pixels] using the nearest
/// [PageStorage] found from the [context]'s [ScrollContext.storageContext]
/// property.
// TODO(goderbauer): Deprecate this when state restoration supports all features of PageStorage.
@protected
void
saveScrollOffset
()
{
PageStorage
.
of
(
context
.
storageContext
)?.
writeState
(
context
.
storageContext
,
pixels
);
...
...
@@ -378,6 +380,7 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics {
///
/// This method is called from the constructor, so layout has not yet
/// occurred, and the viewport dimensions aren't yet known when it is called.
// TODO(goderbauer): Deprecate this when state restoration supports all features of PageStorage.
@protected
void
restoreScrollOffset
()
{
if
(
pixels
==
null
)
{
...
...
@@ -387,6 +390,42 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics {
}
}
/// Called by [context] to restore the scroll offset to the provided value.
///
/// The provided value has previously been provided to the [context] by
/// calling [ScrollContext.saveOffset], e.g. from [saveOffset].
///
/// This method may be called right after the scroll position is created
/// before layout has occurred. In that case, `initialRestore` is set to true
/// and the viewport dimensions will not be known yet. If the [context]
/// doesn't have any information to restore the scroll offset this method is
/// not called.
///
/// The method may be called multiple times in the lifecycle of a
/// [ScrollPosition] to restore it to different scroll offsets.
void
restoreOffset
(
double
offset
,
{
bool
initialRestore
=
false
})
{
assert
(
initialRestore
!=
null
);
assert
(
offset
!=
null
);
if
(
initialRestore
)
{
correctPixels
(
offset
);
}
else
{
jumpTo
(
offset
);
}
}
/// Called whenever scrolling ends, to persist the current scroll offset for
/// state restoration purposes.
///
/// The default implementation stores the current value of [pixels] on the
/// [context] by calling [ScrollContext.saveOffset]. At a later point in time
/// or after the application restarts, the [context] may restore the scroll
/// position to the persisted offset by calling [restoreOffset].
@protected
void
saveOffset
()
{
assert
(
pixels
!=
null
);
context
.
saveOffset
(
pixels
);
}
/// Returns the overscroll by applying the boundary conditions.
///
/// If the given value is in bounds, returns 0.0. Otherwise, returns the
...
...
@@ -761,6 +800,7 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics {
/// This also saves the scroll offset using [saveScrollOffset].
void
didEndScroll
()
{
activity
.
dispatchScrollEndNotification
(
copyWith
(),
context
.
notificationContext
);
saveOffset
();
if
(
keepScrollOffset
)
saveScrollOffset
();
}
...
...
packages/flutter/lib/src/widgets/scroll_view.dart
View file @
25de9419
...
...
@@ -91,6 +91,7 @@ abstract class ScrollView extends StatelessWidget {
this
.
semanticChildCount
,
this
.
dragStartBehavior
=
DragStartBehavior
.
start
,
this
.
keyboardDismissBehavior
=
ScrollViewKeyboardDismissBehavior
.
manual
,
this
.
restorationId
,
})
:
assert
(
scrollDirection
!=
null
),
assert
(
reverse
!=
null
),
assert
(
shrinkWrap
!=
null
),
...
...
@@ -260,6 +261,9 @@ abstract class ScrollView extends StatelessWidget {
/// dismiss the keyboard automatically.
final
ScrollViewKeyboardDismissBehavior
keyboardDismissBehavior
;
/// {@macro flutter.widgets.scrollable.restorationId}
final
String
restorationId
;
/// Returns the [AxisDirection] in which the scroll view scrolls.
///
/// Combines the [scrollDirection] with the [reverse] boolean to obtain the
...
...
@@ -333,6 +337,7 @@ abstract class ScrollView extends StatelessWidget {
controller:
scrollController
,
physics:
physics
,
semanticChildCount:
semanticChildCount
,
restorationId:
restorationId
,
viewportBuilder:
(
BuildContext
context
,
ViewportOffset
offset
)
{
return
buildViewport
(
context
,
offset
,
axisDirection
,
slivers
);
},
...
...
@@ -568,6 +573,7 @@ class CustomScrollView extends ScrollView {
this
.
slivers
=
const
<
Widget
>[],
int
semanticChildCount
,
DragStartBehavior
dragStartBehavior
=
DragStartBehavior
.
start
,
String
restorationId
,
})
:
super
(
key:
key
,
scrollDirection:
scrollDirection
,
...
...
@@ -581,6 +587,7 @@ class CustomScrollView extends ScrollView {
cacheExtent:
cacheExtent
,
semanticChildCount:
semanticChildCount
,
dragStartBehavior:
dragStartBehavior
,
restorationId:
restorationId
,
);
/// The slivers to place inside the viewport.
...
...
@@ -615,6 +622,7 @@ abstract class BoxScrollView extends ScrollView {
int
semanticChildCount
,
DragStartBehavior
dragStartBehavior
=
DragStartBehavior
.
start
,
ScrollViewKeyboardDismissBehavior
keyboardDismissBehavior
=
ScrollViewKeyboardDismissBehavior
.
manual
,
String
restorationId
,
})
:
super
(
key:
key
,
scrollDirection:
scrollDirection
,
...
...
@@ -627,6 +635,7 @@ abstract class BoxScrollView extends ScrollView {
semanticChildCount:
semanticChildCount
,
dragStartBehavior:
dragStartBehavior
,
keyboardDismissBehavior:
keyboardDismissBehavior
,
restorationId:
restorationId
,
);
/// The amount of space by which to inset the children.
...
...
@@ -1027,6 +1036,7 @@ class ListView extends BoxScrollView {
int
semanticChildCount
,
DragStartBehavior
dragStartBehavior
=
DragStartBehavior
.
start
,
ScrollViewKeyboardDismissBehavior
keyboardDismissBehavior
=
ScrollViewKeyboardDismissBehavior
.
manual
,
String
restorationId
,
})
:
childrenDelegate
=
SliverChildListDelegate
(
children
,
addAutomaticKeepAlives:
addAutomaticKeepAlives
,
...
...
@@ -1046,6 +1056,7 @@ class ListView extends BoxScrollView {
semanticChildCount:
semanticChildCount
??
children
.
length
,
dragStartBehavior:
dragStartBehavior
,
keyboardDismissBehavior:
keyboardDismissBehavior
,
restorationId:
restorationId
,
);
/// Creates a scrollable, linear array of widgets that are created on demand.
...
...
@@ -1098,6 +1109,7 @@ class ListView extends BoxScrollView {
int
semanticChildCount
,
DragStartBehavior
dragStartBehavior
=
DragStartBehavior
.
start
,
ScrollViewKeyboardDismissBehavior
keyboardDismissBehavior
=
ScrollViewKeyboardDismissBehavior
.
manual
,
String
restorationId
,
})
:
assert
(
itemCount
==
null
||
itemCount
>=
0
),
assert
(
semanticChildCount
==
null
||
semanticChildCount
<=
itemCount
),
childrenDelegate
=
SliverChildBuilderDelegate
(
...
...
@@ -1120,6 +1132,7 @@ class ListView extends BoxScrollView {
semanticChildCount:
semanticChildCount
??
itemCount
,
dragStartBehavior:
dragStartBehavior
,
keyboardDismissBehavior:
keyboardDismissBehavior
,
restorationId:
restorationId
,
);
/// Creates a fixed-length scrollable linear array of list "items" separated
...
...
@@ -1187,6 +1200,7 @@ class ListView extends BoxScrollView {
double
cacheExtent
,
DragStartBehavior
dragStartBehavior
=
DragStartBehavior
.
start
,
ScrollViewKeyboardDismissBehavior
keyboardDismissBehavior
=
ScrollViewKeyboardDismissBehavior
.
manual
,
String
restorationId
,
})
:
assert
(
itemBuilder
!=
null
),
assert
(
separatorBuilder
!=
null
),
assert
(
itemCount
!=
null
&&
itemCount
>=
0
),
...
...
@@ -1229,6 +1243,7 @@ class ListView extends BoxScrollView {
semanticChildCount:
itemCount
,
dragStartBehavior:
dragStartBehavior
,
keyboardDismissBehavior:
keyboardDismissBehavior
,
restorationId:
restorationId
,
);
/// Creates a scrollable, linear array of widgets with a custom child model.
...
...
@@ -1328,6 +1343,7 @@ class ListView extends BoxScrollView {
int
semanticChildCount
,
DragStartBehavior
dragStartBehavior
=
DragStartBehavior
.
start
,
ScrollViewKeyboardDismissBehavior
keyboardDismissBehavior
=
ScrollViewKeyboardDismissBehavior
.
manual
,
String
restorationId
,
})
:
assert
(
childrenDelegate
!=
null
),
super
(
key:
key
,
...
...
@@ -1342,6 +1358,7 @@ class ListView extends BoxScrollView {
semanticChildCount:
semanticChildCount
,
dragStartBehavior:
dragStartBehavior
,
keyboardDismissBehavior:
keyboardDismissBehavior
,
restorationId:
restorationId
,
);
/// If non-null, forces the children to have the given extent in the scroll
...
...
@@ -1621,6 +1638,7 @@ class GridView extends BoxScrollView {
int
semanticChildCount
,
DragStartBehavior
dragStartBehavior
=
DragStartBehavior
.
start
,
ScrollViewKeyboardDismissBehavior
keyboardDismissBehavior
=
ScrollViewKeyboardDismissBehavior
.
manual
,
String
restorationId
,
})
:
assert
(
gridDelegate
!=
null
),
childrenDelegate
=
SliverChildListDelegate
(
children
,
...
...
@@ -1641,6 +1659,7 @@ class GridView extends BoxScrollView {
semanticChildCount:
semanticChildCount
??
children
.
length
,
dragStartBehavior:
dragStartBehavior
,
keyboardDismissBehavior:
keyboardDismissBehavior
,
restorationId:
restorationId
,
);
/// Creates a scrollable, 2D array of widgets that are created on demand.
...
...
@@ -1681,6 +1700,7 @@ class GridView extends BoxScrollView {
int
semanticChildCount
,
DragStartBehavior
dragStartBehavior
=
DragStartBehavior
.
start
,
ScrollViewKeyboardDismissBehavior
keyboardDismissBehavior
=
ScrollViewKeyboardDismissBehavior
.
manual
,
String
restorationId
,
})
:
assert
(
gridDelegate
!=
null
),
childrenDelegate
=
SliverChildBuilderDelegate
(
itemBuilder
,
...
...
@@ -1702,6 +1722,7 @@ class GridView extends BoxScrollView {
semanticChildCount:
semanticChildCount
??
itemCount
,
dragStartBehavior:
dragStartBehavior
,
keyboardDismissBehavior:
keyboardDismissBehavior
,
restorationId:
restorationId
,
);
/// Creates a scrollable, 2D array of widgets with both a custom
...
...
@@ -1726,6 +1747,7 @@ class GridView extends BoxScrollView {
int
semanticChildCount
,
DragStartBehavior
dragStartBehavior
=
DragStartBehavior
.
start
,
ScrollViewKeyboardDismissBehavior
keyboardDismissBehavior
=
ScrollViewKeyboardDismissBehavior
.
manual
,
String
restorationId
,
})
:
assert
(
gridDelegate
!=
null
),
assert
(
childrenDelegate
!=
null
),
super
(
...
...
@@ -1741,6 +1763,7 @@ class GridView extends BoxScrollView {
semanticChildCount:
semanticChildCount
,
dragStartBehavior:
dragStartBehavior
,
keyboardDismissBehavior:
keyboardDismissBehavior
,
restorationId:
restorationId
,
);
/// Creates a scrollable, 2D array of widgets with a fixed number of tiles in
...
...
@@ -1778,6 +1801,7 @@ class GridView extends BoxScrollView {
int
semanticChildCount
,
DragStartBehavior
dragStartBehavior
=
DragStartBehavior
.
start
,
ScrollViewKeyboardDismissBehavior
keyboardDismissBehavior
=
ScrollViewKeyboardDismissBehavior
.
manual
,
String
restorationId
,
})
:
gridDelegate
=
SliverGridDelegateWithFixedCrossAxisCount
(
crossAxisCount:
crossAxisCount
,
mainAxisSpacing:
mainAxisSpacing
,
...
...
@@ -1803,6 +1827,7 @@ class GridView extends BoxScrollView {
semanticChildCount:
semanticChildCount
??
children
.
length
,
dragStartBehavior:
dragStartBehavior
,
keyboardDismissBehavior:
keyboardDismissBehavior
,
restorationId:
restorationId
,
);
/// Creates a scrollable, 2D array of widgets with tiles that each have a
...
...
@@ -1835,10 +1860,12 @@ class GridView extends BoxScrollView {
bool
addAutomaticKeepAlives
=
true
,
bool
addRepaintBoundaries
=
true
,
bool
addSemanticIndexes
=
true
,
double
cacheExtent
,
List
<
Widget
>
children
=
const
<
Widget
>[],
int
semanticChildCount
,
DragStartBehavior
dragStartBehavior
=
DragStartBehavior
.
start
,
ScrollViewKeyboardDismissBehavior
keyboardDismissBehavior
=
ScrollViewKeyboardDismissBehavior
.
manual
,
String
restorationId
,
})
:
gridDelegate
=
SliverGridDelegateWithMaxCrossAxisExtent
(
maxCrossAxisExtent:
maxCrossAxisExtent
,
mainAxisSpacing:
mainAxisSpacing
,
...
...
@@ -1860,9 +1887,11 @@ class GridView extends BoxScrollView {
physics:
physics
,
shrinkWrap:
shrinkWrap
,
padding:
padding
,
cacheExtent:
cacheExtent
,
semanticChildCount:
semanticChildCount
??
children
.
length
,
dragStartBehavior:
dragStartBehavior
,
keyboardDismissBehavior:
keyboardDismissBehavior
,
restorationId:
restorationId
,
);
/// A delegate that controls the layout of the children within the [GridView].
...
...
packages/flutter/lib/src/widgets/scrollable.dart
View file @
25de9419
...
...
@@ -12,6 +12,7 @@ import 'package:flutter/gestures.dart';
import
'package:flutter/rendering.dart'
;
import
'package:flutter/scheduler.dart'
;
import
'package:flutter/painting.dart'
;
import
'package:flutter/services.dart'
;
import
'actions.dart'
;
import
'basic.dart'
;
...
...
@@ -19,6 +20,8 @@ import 'focus_manager.dart';
import
'framework.dart'
;
import
'gesture_detector.dart'
;
import
'notification_listener.dart'
;
import
'restoration.dart'
;
import
'restoration_properties.dart'
;
import
'scroll_configuration.dart'
;
import
'scroll_context.dart'
;
import
'scroll_controller.dart'
;
...
...
@@ -90,6 +93,7 @@ class Scrollable extends StatefulWidget {
this
.
excludeFromSemantics
=
false
,
this
.
semanticChildCount
,
this
.
dragStartBehavior
=
DragStartBehavior
.
start
,
this
.
restorationId
,
})
:
assert
(
axisDirection
!=
null
),
assert
(
dragStartBehavior
!=
null
),
assert
(
viewportBuilder
!=
null
),
...
...
@@ -226,6 +230,22 @@ class Scrollable extends StatefulWidget {
/// {@endtemplate}
final
DragStartBehavior
dragStartBehavior
;
/// {@template flutter.widgets.scrollable.restorationId}
/// Restoration ID to save and restore the scroll offset of the scrollable.
///
/// If a restoration id is provided, the scrollable will persist its current
/// scroll offset and restore it during state restoration.
///
/// The scroll offset is persisted in a [RestorationBucket] claimed from
/// the surrounding [RestorationScope] using the provided restoration ID.
///
/// See also:
///
/// * [RestorationManager], which explains how state restoration works in
/// Flutter.
/// {@endtemplate}
final
String
restorationId
;
/// The axis along which the scroll view scrolls.
///
/// Determined by the [axisDirection].
...
...
@@ -239,6 +259,7 @@ class Scrollable extends StatefulWidget {
super
.
debugFillProperties
(
properties
);
properties
.
add
(
EnumProperty
<
AxisDirection
>(
'axisDirection'
,
axisDirection
));
properties
.
add
(
DiagnosticsProperty
<
ScrollPhysics
>(
'physics'
,
physics
));
properties
.
add
(
StringProperty
(
'restorationId'
,
restorationId
));
}
/// The state from the closest instance of this class that encloses the given context.
...
...
@@ -340,7 +361,7 @@ class _ScrollableScope extends InheritedWidget {
///
/// This class is not intended to be subclassed. To specialize the behavior of a
/// [Scrollable], provide it with a [ScrollPhysics].
class
ScrollableState
extends
State
<
Scrollable
>
with
TickerProviderStateMixin
class
ScrollableState
extends
State
<
Scrollable
>
with
TickerProviderStateMixin
,
RestorationMixin
implements
ScrollContext
{
/// The manager for this [Scrollable] widget's viewport position.
///
...
...
@@ -350,6 +371,8 @@ class ScrollableState extends State<Scrollable> with TickerProviderStateMixin
ScrollPosition
get
position
=>
_position
;
ScrollPosition
_position
;
final
_RestorableScrollOffset
_persistedScrollOffset
=
_RestorableScrollOffset
();
@override
AxisDirection
get
axisDirection
=>
widget
.
axisDirection
;
...
...
@@ -378,10 +401,28 @@ class ScrollableState extends State<Scrollable> with TickerProviderStateMixin
controller
?.
attach
(
position
);
}
@override
void
restoreState
(
RestorationBucket
oldBucket
)
{
registerForRestoration
(
_persistedScrollOffset
,
'offset'
);
assert
(
position
!=
null
);
if
(
_persistedScrollOffset
.
value
!=
null
)
{
position
.
restoreOffset
(
_persistedScrollOffset
.
value
,
initialRestore:
oldBucket
==
null
);
}
}
@override
void
saveOffset
(
double
offset
)
{
assert
(
debugIsSerializableForRestoration
(
offset
));
_persistedScrollOffset
.
value
=
offset
;
// [saveOffset] is called after a scrolling ends and it is usually not
// followed by a frame. Therefore, manually flush restoration data.
ServicesBinding
.
instance
.
restorationManager
.
flushData
();
}
@override
void
didChangeDependencies
()
{
super
.
didChangeDependencies
();
_updatePosition
();
super
.
didChangeDependencies
();
}
bool
_shouldUpdatePosition
(
Scrollable
oldWidget
)
{
...
...
@@ -414,6 +455,7 @@ class ScrollableState extends State<Scrollable> with TickerProviderStateMixin
void
dispose
()
{
widget
.
controller
?.
detach
(
position
);
position
.
dispose
();
_persistedScrollOffset
.
dispose
();
super
.
dispose
();
}
...
...
@@ -663,6 +705,9 @@ class ScrollableState extends State<Scrollable> with TickerProviderStateMixin
super
.
debugFillProperties
(
properties
);
properties
.
add
(
DiagnosticsProperty
<
ScrollPosition
>(
'position'
,
position
));
}
@override
String
get
restorationId
=>
widget
.
restorationId
;
}
/// With [_ScrollSemantics] certain child [SemanticsNode]s can be
...
...
@@ -1029,3 +1074,28 @@ class ScrollAction extends Action<ScrollIntent> {
);
}
}
// Not using a RestorableDouble because we want to allow null values and override
// [enabled].
class
_RestorableScrollOffset
extends
RestorableValue
<
double
>
{
@override
double
createDefaultValue
()
=>
null
;
@override
void
didUpdateValue
(
double
oldValue
)
{
notifyListeners
();
}
@override
double
fromPrimitives
(
Object
data
)
{
return
data
as
double
;
}
@override
Object
toPrimitives
()
{
return
value
;
}
@override
bool
get
enabled
=>
value
!=
null
;
}
packages/flutter/lib/src/widgets/single_child_scroll_view.dart
View file @
25de9419
...
...
@@ -222,6 +222,7 @@ class SingleChildScrollView extends StatelessWidget {
this
.
child
,
this
.
dragStartBehavior
=
DragStartBehavior
.
start
,
this
.
clipBehavior
=
Clip
.
hardEdge
,
this
.
restorationId
,
})
:
assert
(
scrollDirection
!=
null
),
assert
(
dragStartBehavior
!=
null
),
assert
(
clipBehavior
!=
null
),
...
...
@@ -299,6 +300,9 @@ class SingleChildScrollView extends StatelessWidget {
/// Defaults to [Clip.hardEdge].
final
Clip
clipBehavior
;
/// {@macro flutter.widgets.scrollable.restorationId}
final
String
restorationId
;
AxisDirection
_getDirection
(
BuildContext
context
)
{
return
getAxisDirectionFromAxisReverseAndDirectionality
(
context
,
scrollDirection
,
reverse
);
}
...
...
@@ -317,6 +321,7 @@ class SingleChildScrollView extends StatelessWidget {
axisDirection:
axisDirection
,
controller:
scrollController
,
physics:
physics
,
restorationId:
restorationId
,
viewportBuilder:
(
BuildContext
context
,
ViewportOffset
offset
)
{
return
_SingleChildViewport
(
axisDirection:
axisDirection
,
...
...
packages/flutter/test/services/restoration_test.dart
View file @
25de9419
...
...
@@ -7,6 +7,7 @@
import
'dart:async'
;
import
'dart:typed_data'
;
import
'package:flutter/scheduler.dart'
;
import
'package:flutter/services.dart'
;
import
'package:flutter/widgets.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
...
...
@@ -197,6 +198,42 @@ void main() {
});
expect
(
rootBucket
,
isNull
);
});
testWidgets
(
'flushData'
,
(
WidgetTester
tester
)
async
{
final
List
<
MethodCall
>
callsToEngine
=
<
MethodCall
>[];
final
Completer
<
Map
<
dynamic
,
dynamic
>>
result
=
Completer
<
Map
<
dynamic
,
dynamic
>>();
SystemChannels
.
restoration
.
setMockMethodCallHandler
((
MethodCall
call
)
{
callsToEngine
.
add
(
call
);
return
result
.
future
;
});
final
RestorationManager
manager
=
RestorationManager
();
final
Future
<
RestorationBucket
>
rootBucketFuture
=
manager
.
rootBucket
;
RestorationBucket
rootBucket
;
rootBucketFuture
.
then
((
RestorationBucket
bucket
)
{
rootBucket
=
bucket
;
});
result
.
complete
(
_createEncodedRestorationData1
());
await
tester
.
pump
();
expect
(
rootBucket
,
isNotNull
);
callsToEngine
.
clear
();
// Schedule a frame.
SchedulerBinding
.
instance
.
ensureVisualUpdate
();
rootBucket
.
write
(
'foo'
,
1
);
// flushData is no-op because frame is scheduled.
manager
.
flushData
();
expect
(
callsToEngine
,
isEmpty
);
// Data is flushed at the end of the frame.
await
tester
.
pump
();
expect
(
callsToEngine
,
hasLength
(
1
));
callsToEngine
.
clear
();
// flushData without frame sends data directly.
rootBucket
.
write
(
'foo'
,
2
);
manager
.
flushData
();
expect
(
callsToEngine
,
hasLength
(
1
));
});
});
test
(
'debugIsSerializableForRestoration'
,
()
{
...
...
packages/flutter/test/widgets/scrollable_restoration_test.dart
0 → 100644
View file @
25de9419
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