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
784520bd
Unverified
Commit
784520bd
authored
Jun 09, 2022
by
Kate Lovett
Committed by
GitHub
Jun 09, 2022
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Updating PrimaryScrollController for Desktop (#102099)
parent
b73be72d
Changes
31
Hide whitespace changes
Inline
Side-by-side
Showing
31 changed files
with
497 additions
and
87 deletions
+497
-87
colors_demo.dart
...tegration_tests/flutter_gallery/lib/demo/colors_demo.dart
+1
-0
cupertino_alert_demo.dart
...tter_gallery/lib/demo/cupertino/cupertino_alert_demo.dart
+1
-0
cupertino_navigation_demo.dart
...gallery/lib/demo/cupertino/cupertino_navigation_demo.dart
+1
-0
cupertino_text_field_demo.dart
...gallery/lib/demo/cupertino/cupertino_text_field_demo.dart
+1
-0
backdrop_demo.dart
...ests/flutter_gallery/lib/demo/material/backdrop_demo.dart
+1
-0
bottom_app_bar_demo.dart
...lutter_gallery/lib/demo/material/bottom_app_bar_demo.dart
+1
-0
cards_demo.dart
...n_tests/flutter_gallery/lib/demo/material/cards_demo.dart
+1
-0
chip_demo.dart
...on_tests/flutter_gallery/lib/demo/material/chip_demo.dart
+6
-1
data_table_demo.dart
...ts/flutter_gallery/lib/demo/material/data_table_demo.dart
+1
-0
elevation_demo.dart
...sts/flutter_gallery/lib/demo/material/elevation_demo.dart
+6
-1
expansion_tile_list_demo.dart
...r_gallery/lib/demo/material/expansion_tile_list_demo.dart
+1
-0
full_screen_dialog_demo.dart
...er_gallery/lib/demo/material/full_screen_dialog_demo.dart
+1
-0
icons_demo.dart
...n_tests/flutter_gallery/lib/demo/material/icons_demo.dart
+1
-0
leave_behind_demo.dart
.../flutter_gallery/lib/demo/material/leave_behind_demo.dart
+1
-0
list_demo.dart
...on_tests/flutter_gallery/lib/demo/material/list_demo.dart
+1
-0
overscroll_demo.dart
...ts/flutter_gallery/lib/demo/material/overscroll_demo.dart
+1
-0
reorderable_list_demo.dart
...tter_gallery/lib/demo/material/reorderable_list_demo.dart
+1
-0
text_form_field_demo.dart
...utter_gallery/lib/demo/material/text_form_field_demo.dart
+1
-0
typography_demo.dart
...ation_tests/flutter_gallery/lib/demo/typography_demo.dart
+6
-1
video_demo.dart
...ntegration_tests/flutter_gallery/lib/demo/video_demo.dart
+1
-0
raw_scrollbar.0.dart
examples/api/lib/widgets/scrollbar/raw_scrollbar.0.dart
+9
-5
raw_scrollbar.shape.0.dart
...ples/api/lib/widgets/scrollbar/raw_scrollbar.shape.0.dart
+5
-0
dropdown.dart
packages/flutter/lib/src/material/dropdown.dart
+2
-0
nested_scroll_view.dart
packages/flutter/lib/src/widgets/nested_scroll_view.dart
+12
-0
primary_scroll_controller.dart
...es/flutter/lib/src/widgets/primary_scroll_controller.dart
+76
-7
scroll_view.dart
packages/flutter/lib/src/widgets/scroll_view.dart
+33
-12
scrollable.dart
packages/flutter/lib/src/widgets/scrollable.dart
+30
-8
scrollbar.dart
packages/flutter/lib/src/widgets/scrollbar.dart
+22
-18
range_maintaining_scroll_physics_test.dart
...r/test/widgets/range_maintaining_scroll_physics_test.dart
+2
-1
scroll_view_test.dart
packages/flutter/test/widgets/scroll_view_test.dart
+187
-31
scrollbar_test.dart
packages/flutter/test/widgets/scrollbar_test.dart
+84
-2
No files found.
dev/integration_tests/flutter_gallery/lib/demo/colors_demo.dart
View file @
784520bd
...
...
@@ -96,6 +96,7 @@ class PaletteTabView extends StatelessWidget {
final
TextStyle
blackTextStyle
=
textTheme
.
bodyText2
!.
copyWith
(
color:
Colors
.
black
);
return
Scrollbar
(
child:
ListView
(
primary:
true
,
itemExtent:
kColorItemHeight
,
children:
<
Widget
>[
...
primaryKeys
.
map
<
Widget
>((
int
index
)
{
...
...
dev/integration_tests/flutter_gallery/lib/demo/cupertino/cupertino_alert_demo.dart
View file @
784520bd
...
...
@@ -60,6 +60,7 @@ class _CupertinoAlertDemoState extends State<CupertinoAlertDemo> {
children:
<
Widget
>[
CupertinoScrollbar
(
child:
ListView
(
primary:
true
,
// Add more padding to the normal safe area.
padding:
const
EdgeInsets
.
symmetric
(
vertical:
24.0
,
horizontal:
72.0
)
+
MediaQuery
.
of
(
context
).
padding
,
...
...
dev/integration_tests/flutter_gallery/lib/demo/cupertino/cupertino_navigation_demo.dart
View file @
784520bd
...
...
@@ -446,6 +446,7 @@ class CupertinoDemoTab2 extends StatelessWidget {
),
child:
CupertinoScrollbar
(
child:
ListView
(
primary:
true
,
children:
<
Widget
>[
const
CupertinoUserInterfaceLevel
(
data:
CupertinoUserInterfaceLevelData
.
elevated
,
...
...
dev/integration_tests/flutter_gallery/lib/demo/cupertino/cupertino_text_field_demo.dart
View file @
784520bd
...
...
@@ -168,6 +168,7 @@ class _CupertinoTextFieldDemoState extends State<CupertinoTextFieldDemo> {
),
child:
CupertinoScrollbar
(
child:
ListView
(
primary:
true
,
children:
<
Widget
>[
Padding
(
padding:
const
EdgeInsets
.
symmetric
(
vertical:
32.0
,
horizontal:
16.0
),
...
...
dev/integration_tests/flutter_gallery/lib/demo/material/backdrop_demo.dart
View file @
784520bd
...
...
@@ -104,6 +104,7 @@ class CategoryView extends StatelessWidget {
final
ThemeData
theme
=
Theme
.
of
(
context
);
return
Scrollbar
(
child:
ListView
(
primary:
true
,
key:
PageStorageKey
<
Category
?>(
category
),
padding:
const
EdgeInsets
.
symmetric
(
vertical:
16.0
,
...
...
dev/integration_tests/flutter_gallery/lib/demo/material/bottom_app_bar_demo.dart
View file @
784520bd
...
...
@@ -161,6 +161,7 @@ class _BottomAppBarDemoState extends State<BottomAppBarDemo> {
),
body:
Scrollbar
(
child:
ListView
(
primary:
true
,
padding:
const
EdgeInsets
.
only
(
bottom:
88.0
),
children:
<
Widget
>[
const
_Heading
(
'FAB Shape'
),
...
...
dev/integration_tests/flutter_gallery/lib/demo/material/cards_demo.dart
View file @
784520bd
...
...
@@ -375,6 +375,7 @@ class _CardsDemoState extends State<CardsDemo> {
),
body:
Scrollbar
(
child:
ListView
(
primary:
true
,
padding:
const
EdgeInsets
.
only
(
top:
8.0
,
left:
8.0
,
right:
8.0
),
children:
destinations
.
map
<
Widget
>((
TravelDestination
destination
)
{
Widget
?
child
;
...
...
dev/integration_tests/flutter_gallery/lib/demo/material/chip_demo.dart
View file @
784520bd
...
...
@@ -347,7 +347,12 @@ class _ChipDemoState extends State<ChipDemo> {
borderRadius:
BorderRadius
.
circular
(
10.0
),
))
:
theme
.
chipTheme
,
child:
Scrollbar
(
child:
ListView
(
children:
tiles
)),
child:
Scrollbar
(
child:
ListView
(
primary:
true
,
children:
tiles
,
)
),
),
floatingActionButton:
FloatingActionButton
(
onPressed:
()
=>
setState
(
_reset
),
...
...
dev/integration_tests/flutter_gallery/lib/demo/material/data_table_demo.dart
View file @
784520bd
...
...
@@ -175,6 +175,7 @@ class _DataTableDemoState extends State<DataTableDemo> {
),
body:
Scrollbar
(
child:
ListView
(
primary:
true
,
padding:
const
EdgeInsets
.
all
(
20.0
),
children:
<
Widget
>[
PaginatedDataTable
(
...
...
dev/integration_tests/flutter_gallery/lib/demo/material/elevation_demo.dart
View file @
784520bd
...
...
@@ -64,7 +64,12 @@ class _ElevationDemoState extends State<ElevationDemo> {
),
],
),
body:
Scrollbar
(
child:
ListView
(
children:
buildCards
())),
body:
Scrollbar
(
child:
ListView
(
primary:
true
,
children:
buildCards
(),
),
),
);
}
}
dev/integration_tests/flutter_gallery/lib/demo/material/expansion_tile_list_demo.dart
View file @
784520bd
...
...
@@ -20,6 +20,7 @@ class ExpansionTileListDemo extends StatelessWidget {
),
body:
Scrollbar
(
child:
ListView
(
primary:
true
,
children:
<
Widget
>[
const
ListTile
(
title:
Text
(
'Top'
)),
ExpansionTile
(
...
...
dev/integration_tests/flutter_gallery/lib/demo/material/full_screen_dialog_demo.dart
View file @
784520bd
...
...
@@ -162,6 +162,7 @@ class FullScreenDialogDemoState extends State<FullScreenDialogDemo> {
onWillPop:
_onWillPop
,
child:
Scrollbar
(
child:
ListView
(
primary:
true
,
padding:
const
EdgeInsets
.
all
(
16.0
),
children:
<
Widget
>[
Container
(
...
...
dev/integration_tests/flutter_gallery/lib/demo/material/icons_demo.dart
View file @
784520bd
...
...
@@ -62,6 +62,7 @@ class IconsDemoState extends State<IconsDemo> {
bottom:
false
,
child:
Scrollbar
(
child:
ListView
(
primary:
true
,
padding:
const
EdgeInsets
.
all
(
24.0
),
children:
<
Widget
>[
_IconsDemoCard
(
handleIconButtonPress
,
Icons
.
face
),
// direction-agnostic icon
...
...
dev/integration_tests/flutter_gallery/lib/demo/material/leave_behind_demo.dart
View file @
784520bd
...
...
@@ -131,6 +131,7 @@ class LeaveBehindDemoState extends State<LeaveBehindDemo> {
}
else
{
body
=
Scrollbar
(
child:
ListView
(
primary:
true
,
children:
leaveBehindItems
.
map
<
Widget
>((
LeaveBehindItem
item
)
{
return
_LeaveBehindListItem
(
confirmDismiss:
_confirmDismiss
,
...
...
dev/integration_tests/flutter_gallery/lib/demo/material/list_demo.dart
View file @
784520bd
...
...
@@ -256,6 +256,7 @@ class _ListDemoState extends State<ListDemo> {
),
body:
Scrollbar
(
child:
ListView
(
primary:
true
,
padding:
EdgeInsets
.
symmetric
(
vertical:
_dense
!=
null
?
4.0
:
8.0
),
children:
listTiles
.
toList
(),
),
...
...
dev/integration_tests/flutter_gallery/lib/demo/material/overscroll_demo.dart
View file @
784520bd
...
...
@@ -64,6 +64,7 @@ class OverscrollDemoState extends State<OverscrollDemo> {
onRefresh:
_handleRefresh
,
child:
Scrollbar
(
child:
ListView
.
builder
(
primary:
true
,
padding:
kMaterialListPadding
,
itemCount:
_items
.
length
,
itemBuilder:
(
BuildContext
context
,
int
index
)
{
...
...
dev/integration_tests/flutter_gallery/lib/demo/material/reorderable_list_demo.dart
View file @
784520bd
...
...
@@ -208,6 +208,7 @@ class _ListDemoState extends State<ReorderableListDemo> {
),
body:
Scrollbar
(
child:
ReorderableListView
(
primary:
true
,
header:
_itemType
!=
_ReorderableListType
.
threeLine
?
Padding
(
padding:
const
EdgeInsets
.
all
(
8.0
),
...
...
dev/integration_tests/flutter_gallery/lib/demo/material/text_form_field_demo.dart
View file @
784520bd
...
...
@@ -182,6 +182,7 @@ class TextFormFieldDemoState extends State<TextFormFieldDemo> {
onWillPop:
_warnUserAboutInvalidData
,
child:
Scrollbar
(
child:
SingleChildScrollView
(
primary:
true
,
dragStartBehavior:
DragStartBehavior
.
down
,
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
16.0
),
child:
Column
(
...
...
dev/integration_tests/flutter_gallery/lib/demo/typography_demo.dart
View file @
784520bd
...
...
@@ -66,7 +66,12 @@ class TypographyDemo extends StatelessWidget {
body:
SafeArea
(
top:
false
,
bottom:
false
,
child:
Scrollbar
(
child:
ListView
(
children:
styleItems
)),
child:
Scrollbar
(
child:
ListView
(
primary:
true
,
children:
styleItems
,
),
),
),
);
}
...
...
dev/integration_tests/flutter_gallery/lib/demo/video_demo.dart
View file @
784520bd
...
...
@@ -418,6 +418,7 @@ class _VideoDemoState extends State<VideoDemo> with SingleTickerProviderStateMix
connectedCompleter:
connectedCompleter
,
child:
Scrollbar
(
child:
ListView
(
primary:
true
,
children:
<
Widget
>[
VideoCard
(
title:
'Butterfly'
,
...
...
examples/api/lib/widgets/scrollbar/raw_scrollbar.0.dart
View file @
784520bd
...
...
@@ -45,8 +45,9 @@ class _MyStatefulWidgetState extends State<MyStatefulWidget> {
children:
<
Widget
>[
SizedBox
(
width:
constraints
.
maxWidth
/
2
,
// Only one scroll position can be attached to the
// PrimaryScrollController if using Scrollbars. Providing a
// When using the PrimaryScrollController and a Scrollbar
// together, only one ScrollPosition can be attached to the
// PrimaryScrollController at a time. Providing a
// unique scroll controller to this scroll view prevents it
// from attaching to the PrimaryScrollController.
child:
Scrollbar
(
...
...
@@ -64,12 +65,15 @@ class _MyStatefulWidgetState extends State<MyStatefulWidget> {
)),
SizedBox
(
width:
constraints
.
maxWidth
/
2
,
// This vertical scroll view has not been provided a
// ScrollController, so it is using the
// PrimaryScrollController.
// This vertical scroll view has primary set to true, so it is
// using the PrimaryScrollController. On mobile platforms, the
// PrimaryScrollController automatically attaches to vertical
// ScrollViews, unlike on Desktop platforms, where the primary
// parameter is required.
child:
Scrollbar
(
thumbVisibility:
true
,
child:
ListView
.
builder
(
primary:
true
,
itemCount:
100
,
itemBuilder:
(
BuildContext
context
,
int
index
)
{
return
Container
(
...
...
examples/api/lib/widgets/scrollbar/raw_scrollbar.shape.0.dart
View file @
784520bd
...
...
@@ -35,6 +35,11 @@ class MyStatelessWidget extends StatelessWidget {
thumbColor:
Colors
.
blue
,
thumbVisibility:
true
,
child:
ListView
(
// On mobile platforms, setting primary to true is not required, as
// the PrimaryScrollController automatically attaches to vertical
// ScrollPositions. On desktop platforms however, using the
// PrimaryScrollController requires ScrollView.primary be set.
primary:
true
,
physics:
const
BouncingScrollPhysics
(),
children:
List
<
Text
>.
generate
(
100
,
(
int
index
)
=>
Text
((
index
*
index
).
toString
())),
...
...
packages/flutter/lib/src/material/dropdown.dart
View file @
784520bd
...
...
@@ -309,6 +309,8 @@ class _DropdownMenuState<T> extends State<_DropdownMenu<T>> {
child:
Scrollbar
(
thumbVisibility:
true
,
child:
ListView
(
// Ensure this always inherits the PrimaryScrollController
primary:
true
,
padding:
kMaterialListPadding
,
shrinkWrap:
true
,
children:
children
,
...
...
packages/flutter/lib/src/widgets/nested_scroll_view.dart
View file @
784520bd
...
...
@@ -312,7 +312,19 @@ class NestedScrollView extends StatefulWidget {
return
<
Widget
>[
...
headerSliverBuilder
(
context
,
bodyIsScrolled
),
SliverFillRemaining
(
// The inner (body) scroll view must use this scroll controller so that
// the independent scroll positions can be kept in sync.
child:
PrimaryScrollController
(
// The inner scroll view should always inherit this
// PrimaryScrollController, on every platform.
automaticallyInheritForPlatforms:
TargetPlatform
.
values
.
toSet
(),
// `PrimaryScrollController.scrollDirection` is not set, and so it is
// restricted to the default Axis.vertical.
// Ideally the inner and outer views would have the same
// scroll direction, and so we could assume
// `NestedScrollView.scrollDirection` for the PrimaryScrollController,
// but use cases already exist where the axes are mismatched.
// https://github.com/flutter/flutter/issues/102001
controller:
innerController
,
child:
body
,
),
...
...
packages/flutter/lib/src/widgets/primary_scroll_controller.dart
View file @
784520bd
...
...
@@ -3,19 +3,33 @@
// found in the LICENSE file.
import
'package:flutter/foundation.dart'
;
import
'package:flutter/painting.dart'
;
import
'framework.dart'
;
import
'scroll_configuration.dart'
;
import
'scroll_controller.dart'
;
const
Set
<
TargetPlatform
>
_kMobilePlatforms
=
<
TargetPlatform
>{
TargetPlatform
.
android
,
TargetPlatform
.
iOS
,
TargetPlatform
.
fuchsia
,
};
/// Associates a [ScrollController] with a subtree.
///
/// When a [ScrollView] has [ScrollView.primary] set to true and is not given
/// an explicit [ScrollController], the [ScrollView] uses [of] to find the
/// [ScrollController] associated with its subtree.
/// When a [ScrollView] has [ScrollView.primary] set to true, the [ScrollView]
/// uses [of] to inherit the [PrimaryScrollController] associated with its
/// subtree.
///
/// A ScrollView that doesn't have a controller or the primary flag set will
/// inherit the PrimarySCrollController, if [shouldInherit] allows it. By
/// default [shouldInherit] is true for mobile platforms when the ScrollView has
/// a scroll direction of [Axis.vertical]. This automatic inheritance can be
/// configured with [automaticallyInheritForPlatforms] and [scrollDirection].
///
///
This mechanism can be used to provide default behavior for scroll views in a
///
subtree. For example, the [Scaffold] uses this mechanism to implement the
/// scroll-to-top gesture on iOS.
///
Inheriting this ScrollController can provide default behavior for scroll
///
views in a subtree. For example, the [Scaffold] uses this mechanism to
///
implement the
scroll-to-top gesture on iOS.
///
/// Another default behavior handled by the PrimaryScrollController is default
/// [ScrollAction]s. If a ScrollAction is not handled by an otherwise focused
...
...
@@ -34,6 +48,8 @@ class PrimaryScrollController extends InheritedWidget {
const
PrimaryScrollController
({
super
.
key
,
required
ScrollController
this
.
controller
,
this
.
automaticallyInheritForPlatforms
=
_kMobilePlatforms
,
this
.
scrollDirection
=
Axis
.
vertical
,
required
super
.
child
,
})
:
assert
(
controller
!=
null
);
...
...
@@ -41,7 +57,9 @@ class PrimaryScrollController extends InheritedWidget {
const
PrimaryScrollController
.
none
({
super
.
key
,
required
super
.
child
,
})
:
controller
=
null
;
})
:
automaticallyInheritForPlatforms
=
const
<
TargetPlatform
>{},
scrollDirection
=
null
,
controller
=
null
;
/// The [ScrollController] associated with the subtree.
///
...
...
@@ -51,6 +69,57 @@ class PrimaryScrollController extends InheritedWidget {
/// scroll controller.
final
ScrollController
?
controller
;
/// The [Axis] this controller is configured for [ScrollView]s to
/// automatically inherit.
///
/// Used in conjunction with [automaticallyInheritForPlatforms]. If the
/// current [TargetPlatform] is not included in
/// [automaticallyInheritForPlatforms], this is ignored.
///
/// When null, no [ScrollView] in any Axis will automatically inherit this
/// controller. This is dissimilar to [PrimaryScrollController.none]. When a
/// PrimaryScrollController is inherited, ScrollView will insert
/// PrimaryScrollController.none into the tree to prevent further descendant
/// ScrollViews from inheriting the current PrimaryScrollController.
///
/// Defaults to [Axis.vertical].
final
Axis
?
scrollDirection
;
/// The [TargetPlatform]s this controller is configured for [ScrollView]s to
/// automatically inherit.
///
/// Used in conjunction with [scrollDirection]. If the [Axis] provided to
/// [shouldInherit] is not [scrollDirection], this is ignored.
///
/// When empty, no ScrollView in any Axis will automatically inherit this
/// controller. Defaults to [TargetPlatformVariant.mobile].
final
Set
<
TargetPlatform
>
automaticallyInheritForPlatforms
;
/// Returns true if this PrimaryScrollController is configured to be
/// automatically inherited for the current [TargetPlatform] and the given
/// [Axis].
///
/// This method is typically not called directly. [ScrollView] will call this
/// method if it has not been provided a [ScrollController] and
/// [ScrollView.primary] is unset.
///
/// If a ScrollController has already been provided to
/// [ScrollView.controller], or [ScrollView.primary] is set, this is method is
/// not called by ScrollView as it will have determined whether or not to
/// inherit the PrimaryScrollController.
static
bool
shouldInherit
(
BuildContext
context
,
Axis
scrollDirection
)
{
final
PrimaryScrollController
?
result
=
context
.
findAncestorWidgetOfExactType
<
PrimaryScrollController
>();
if
(
result
==
null
)
{
return
false
;
}
final
TargetPlatform
platform
=
ScrollConfiguration
.
of
(
context
).
getPlatform
(
context
);
if
(
result
.
automaticallyInheritForPlatforms
.
contains
(
platform
))
{
return
result
.
scrollDirection
==
scrollDirection
;
}
return
false
;
}
/// Returns the [ScrollController] most closely associated with the given
/// context.
///
...
...
packages/flutter/lib/src/widgets/scroll_view.dart
View file @
784520bd
...
...
@@ -87,7 +87,7 @@ abstract class ScrollView extends StatelessWidget {
this
.
scrollDirection
=
Axis
.
vertical
,
this
.
reverse
=
false
,
this
.
controller
,
bool
?
primary
,
this
.
primary
,
ScrollPhysics
?
physics
,
this
.
scrollBehavior
,
this
.
shrinkWrap
=
false
,
...
...
@@ -104,15 +104,16 @@ abstract class ScrollView extends StatelessWidget {
assert
(
shrinkWrap
!=
null
),
assert
(
dragStartBehavior
!=
null
),
assert
(
clipBehavior
!=
null
),
assert
(!(
controller
!=
null
&&
(
primary
??
false
)),
'Primary ScrollViews obtain their ScrollController via inheritance from a PrimaryScrollController widget. '
'You cannot both set primary to true and pass an explicit controller.'
,
assert
(
!(
controller
!=
null
&&
(
primary
??
false
)),
'Primary ScrollViews obtain their ScrollController via inheritance '
'from a PrimaryScrollController widget. You cannot both set primary to '
'true and pass an explicit controller.'
,
),
assert
(!
shrinkWrap
||
center
==
null
),
assert
(
anchor
!=
null
),
assert
(
anchor
>=
0.0
&&
anchor
<=
1.0
),
assert
(
semanticChildCount
==
null
||
semanticChildCount
>=
0
),
primary
=
primary
??
controller
==
null
&&
identical
(
scrollDirection
,
Axis
.
vertical
),
physics
=
physics
??
((
primary
??
false
)
||
(
primary
==
null
&&
controller
==
null
&&
identical
(
scrollDirection
,
Axis
.
vertical
))
?
const
AlwaysScrollableScrollPhysics
()
:
null
);
/// {@template flutter.widgets.scroll_view.scrollDirection}
...
...
@@ -169,11 +170,24 @@ abstract class ScrollView extends StatelessWidget {
///
/// On iOS, this also identifies the scroll view that will scroll to top in
/// response to a tap in the status bar.
/// {@endtemplate}
///
/// Defaults to true when [scrollDirection] is [Axis.vertical] and
/// [controller] is null.
final
bool
primary
;
/// Cannot be true while a [ScrollController] is provided to `controller`,
/// only one ScrollController can be associated with a ScrollView.
///
/// Setting to false will explicitly prevent inheriting any
/// [PrimaryScrollController].
///
/// Defaults to null. When null, and a controller is not provided,
/// [PrimaryScrollController.shouldInherit] is used to decide automatic
/// inheritance.
///
/// By default, the [PrimaryScrollController] that is injected by each
/// [ModalRoute] is configured to automatically be inherited on
/// [TargetPlatformVariant.mobile] for ScrollViews in the [Axis.vertical]
/// scroll direction. Adding another to your app will override the
/// PrimaryScrollController above it.
/// {@endtemplate}
final
bool
?
primary
;
/// {@template flutter.widgets.scroll_view.physics}
/// How the scroll view should respond to user input.
...
...
@@ -393,8 +407,13 @@ abstract class ScrollView extends StatelessWidget {
final
List
<
Widget
>
slivers
=
buildSlivers
(
context
);
final
AxisDirection
axisDirection
=
getDirection
(
context
);
final
ScrollController
?
scrollController
=
primary
?
PrimaryScrollController
.
of
(
context
)
:
controller
;
final
bool
effectivePrimary
=
primary
??
controller
==
null
&&
PrimaryScrollController
.
shouldInherit
(
context
,
scrollDirection
);
final
ScrollController
?
scrollController
=
effectivePrimary
?
PrimaryScrollController
.
of
(
context
)
:
controller
;
final
Scrollable
scrollable
=
Scrollable
(
dragStartBehavior:
dragStartBehavior
,
axisDirection:
axisDirection
,
...
...
@@ -407,7 +426,9 @@ abstract class ScrollView extends StatelessWidget {
return
buildViewport
(
context
,
offset
,
axisDirection
,
slivers
);
},
);
final
Widget
scrollableResult
=
primary
&&
scrollController
!=
null
final
Widget
scrollableResult
=
effectivePrimary
&&
scrollController
!=
null
// Further descendant ScrollViews will not inherit the same PrimaryScrollController
?
PrimaryScrollController
.
none
(
child:
scrollable
)
:
scrollable
;
...
...
packages/flutter/lib/src/widgets/scrollable.dart
View file @
784520bd
...
...
@@ -1586,13 +1586,8 @@ class ScrollAction extends Action<ScrollIntent> {
return
true
;
}
// Check for fallback scrollable with context from PrimaryScrollController
if
(
PrimaryScrollController
.
of
(
focus
.
context
!)
!=
null
)
{
final
ScrollController
?
primaryScrollController
=
PrimaryScrollController
.
of
(
focus
.
context
!);
return
primaryScrollController
!=
null
&&
primaryScrollController
.
hasClients
&&
primaryScrollController
.
position
.
context
.
notificationContext
!=
null
&&
Scrollable
.
of
(
primaryScrollController
.
position
.
context
.
notificationContext
!)
!=
null
;
}
final
ScrollController
?
primaryScrollController
=
PrimaryScrollController
.
of
(
focus
.
context
!);
return
primaryScrollController
!=
null
&&
primaryScrollController
.
hasClients
;
}
return
false
;
}
...
...
@@ -1681,7 +1676,34 @@ class ScrollAction extends Action<ScrollIntent> {
ScrollableState
?
state
=
Scrollable
.
of
(
primaryFocus
!.
context
!);
if
(
state
==
null
)
{
final
ScrollController
?
primaryScrollController
=
PrimaryScrollController
.
of
(
primaryFocus
!.
context
!);
state
=
Scrollable
.
of
(
primaryScrollController
!.
position
.
context
.
notificationContext
!);
assert
(()
{
if
(
primaryScrollController
!.
positions
.
length
!=
1
)
{
throw
FlutterError
.
fromParts
(<
DiagnosticsNode
>[
ErrorSummary
(
'A ScrollAction was invoked with the PrimaryScrollController, but '
'more than one ScrollPosition is attached.'
,
),
ErrorDescription
(
'Only one ScrollPosition can be manipulated by a ScrollAction at '
'a time.'
,
),
ErrorHint
(
'The PrimaryScrollController can be inherited automatically by '
'descendant ScrollViews based on the TargetPlatform and scroll '
'direction. By default, the PrimaryScrollController is '
'automatically inherited on mobile platforms for vertical '
'ScrollViews. ScrollView.primary can also override this behavior.'
,
),
]);
}
return
true
;
}());
if
(
primaryScrollController
!.
position
.
context
.
notificationContext
==
null
&&
Scrollable
.
of
(
primaryScrollController
.
position
.
context
.
notificationContext
!)
==
null
)
{
return
;
}
state
=
Scrollable
.
of
(
primaryScrollController
.
position
.
context
.
notificationContext
!);
}
assert
(
state
!=
null
,
'
$ScrollAction
was invoked on a context that has no scrollable parent'
);
assert
(
state
!.
position
.
hasPixels
,
'Scrollable must be laid out before it can be scrolled via a ScrollAction'
);
...
...
packages/flutter/lib/src/widgets/scrollbar.dart
View file @
784520bd
...
...
@@ -849,9 +849,12 @@ class ScrollbarPainter extends ChangeNotifier implements CustomPainter {
/// This sample shows an app with two scrollables in the same route. Since by
/// default, there is one [PrimaryScrollController] per route, and they both have a
/// scroll direction of [Axis.vertical], they would both try to attach to that
/// controller. The [Scrollbar] cannot support multiple positions attached to
/// the same controller, so one [ListView], and its [Scrollbar] have been
/// provided a unique [ScrollController].
/// controller on mobile platforms. The [Scrollbar] cannot support multiple
/// positions attached to the same controller, so one [ListView], and its
/// [Scrollbar] have been provided a unique [ScrollController]. Desktop
/// platforms do not automatically attach to the PrimaryScrollController,
/// requiring [ScrollView.primary] to be true instead in order to use the
/// PrimaryScrollController.
///
/// Alternatively, a new PrimaryScrollController could be created above one of
/// the [ListView]s.
...
...
@@ -1507,14 +1510,14 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv
ErrorHint
(
'The Scrollbar attempted to use the
$controllerForError
. This '
'ScrollController should be associated with the ScrollView that '
'the Scrollbar is being applied to.
'
'the Scrollbar is being applied to.'
'
${tryPrimary
? 'A ScrollView with an Axis.vertical '
'
ScrollDirection
will automatically use the '
? 'A ScrollView with an Axis.vertical
ScrollDirection on mobile
'
'
platforms
will automatically use the '
'PrimaryScrollController if the user has not provided a '
'ScrollController
, but a ScrollDirection of Axis.horizontal will
'
'
not. To use the PrimaryScrollController explicitly, set ScrollView.primary
'
'
to true for the Scrollable
widget.'
'ScrollController
. To use the PrimaryScrollController
'
'
explicitly, set ScrollView.primary to true for the Scrollable
'
'widget.'
: 'When providing your own ScrollController, ensure both the '
'Scrollbar and the Scrollable widget use the same one.'
}
'
,
...
...
@@ -1539,16 +1542,17 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv
'The Scrollbar requires a single ScrollPosition in order to be painted.'
,
),
ErrorHint
(
'When
$when
, the associated Scroll
abl
e '
'
widgets must have unique ScrollControllers.
'
'When
$when
, the associated Scroll
Controller must only have on
e '
'
ScrollPosition attached.
'
'
${tryPrimary
? 'The PrimaryScrollController is used by default for '
'ScrollViews with an Axis.vertical ScrollDirection, '
'unless the ScrollView has been provided its own '
'ScrollController. More than one Scrollable may have tried '
'to use the PrimaryScrollController of the current context.'
: 'The provided ScrollController must be unique to a '
'Scrollable widget.'
? 'If a ScrollController has not been provided, the '
'PrimaryScrollController is used by default on mobile platforms '
'for ScrollViews with an Axis.vertical scroll direction. More '
'than one ScrollView may have tried to use the '
'PrimaryScrollController of the current context. '
'ScrollView.primary can override this behavior.'
: 'The provided ScrollController must be unique to one '
'ScrollView widget.'
}
'
,
),
]);
...
...
packages/flutter/test/widgets/range_maintaining_scroll_physics_test.dart
View file @
784520bd
...
...
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'package:flutter/foundation.dart'
;
import
'package:flutter/gestures.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
...
...
@@ -377,7 +378,7 @@ class RangeMaintainingTestScrollBehavior extends ScrollBehavior {
const
RangeMaintainingTestScrollBehavior
();
@override
TargetPlatform
getPlatform
(
BuildContext
context
)
=>
throw
'should not be called'
;
TargetPlatform
getPlatform
(
BuildContext
context
)
=>
defaultTargetPlatform
;
@override
Widget
buildOverscrollIndicator
(
BuildContext
context
,
Widget
child
,
ScrollableDetails
details
)
{
...
...
packages/flutter/test/widgets/scroll_view_test.dart
View file @
784520bd
...
...
@@ -54,6 +54,19 @@ Widget textFieldBoilerplate({ required Widget child }) {
);
}
Widget
primaryScrollControllerBoilerplate
(
{
required
Widget
child
,
required
ScrollController
controller
})
{
return
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
MediaQuery
(
data:
const
MediaQueryData
(),
child:
PrimaryScrollController
(
controller:
controller
,
child:
child
,
),
),
);
}
void
main
(
)
{
testWidgets
(
'ListView control test'
,
(
WidgetTester
tester
)
async
{
final
List
<
String
>
log
=
<
String
>[];
...
...
@@ -887,61 +900,155 @@ void main() {
expect
(
log
,
isEmpty
);
});
testWidgets
(
'Vertical CustomScrollViews are primary by default'
,
(
WidgetTester
tester
)
async
{
test
(
'PrimaryScrollController.automaticallyInheritOnPlatforms defaults to all mobile platforms'
,
(){
final
PrimaryScrollController
primaryScrollController
=
PrimaryScrollController
(
controller:
ScrollController
(),
child:
const
SizedBox
(),
);
expect
(
primaryScrollController
.
automaticallyInheritForPlatforms
,
TargetPlatformVariant
.
mobile
().
values
,
);
});
testWidgets
(
'Vertical CustomScrollViews are not primary by default'
,
(
WidgetTester
tester
)
async
{
const
CustomScrollView
view
=
CustomScrollView
();
expect
(
view
.
primary
,
is
True
);
expect
(
view
.
primary
,
is
Null
);
});
testWidgets
(
'Vertical ListViews are primary by default'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'Vertical CustomScrollViews use PrimaryScrollController by default on mobile'
,
(
WidgetTester
tester
)
async
{
final
ScrollController
controller
=
ScrollController
();
await
tester
.
pumpWidget
(
primaryScrollControllerBoilerplate
(
child:
const
CustomScrollView
(),
controller:
controller
,
));
expect
(
controller
.
hasClients
,
isTrue
);
},
variant:
TargetPlatformVariant
.
mobile
());
testWidgets
(
"Vertical CustomScrollViews don't use PrimaryScrollController by default on desktop"
,
(
WidgetTester
tester
)
async
{
final
ScrollController
controller
=
ScrollController
();
await
tester
.
pumpWidget
(
primaryScrollControllerBoilerplate
(
child:
const
CustomScrollView
(),
controller:
controller
,
));
expect
(
controller
.
hasClients
,
isFalse
);
},
variant:
TargetPlatformVariant
.
desktop
());
testWidgets
(
'Vertical ListViews are not primary by default'
,
(
WidgetTester
tester
)
async
{
final
ListView
view
=
ListView
();
expect
(
view
.
primary
,
is
True
);
expect
(
view
.
primary
,
is
Null
);
});
testWidgets
(
'Vertical GridViews are primary by default'
,
(
WidgetTester
tester
)
async
{
final
GridView
view
=
GridView
.
count
(
crossAxisCount:
1
,
);
expect
(
view
.
primary
,
isTrue
);
testWidgets
(
'Vertical ListViews use PrimaryScrollController by default on mobile'
,
(
WidgetTester
tester
)
async
{
final
ScrollController
controller
=
ScrollController
();
await
tester
.
pumpWidget
(
primaryScrollControllerBoilerplate
(
child:
ListView
(),
controller:
controller
,
));
expect
(
controller
.
hasClients
,
isTrue
);
},
variant:
TargetPlatformVariant
.
mobile
());
testWidgets
(
"Vertical ListViews don't use PrimaryScrollController by default on desktop"
,
(
WidgetTester
tester
)
async
{
final
ScrollController
controller
=
ScrollController
();
await
tester
.
pumpWidget
(
primaryScrollControllerBoilerplate
(
child:
ListView
(),
controller:
controller
,
));
expect
(
controller
.
hasClients
,
isFalse
);
},
variant:
TargetPlatformVariant
.
desktop
());
testWidgets
(
'Vertical GridViews are not primary by default'
,
(
WidgetTester
tester
)
async
{
final
GridView
view
=
GridView
.
count
(
crossAxisCount:
1
);
expect
(
view
.
primary
,
isNull
);
});
testWidgets
(
'Vertical GridViews use PrimaryScrollController by default on mobile'
,
(
WidgetTester
tester
)
async
{
final
ScrollController
controller
=
ScrollController
();
await
tester
.
pumpWidget
(
primaryScrollControllerBoilerplate
(
child:
GridView
.
count
(
crossAxisCount:
1
),
controller:
controller
,
));
expect
(
controller
.
hasClients
,
isTrue
);
},
variant:
TargetPlatformVariant
.
mobile
());
testWidgets
(
"Vertical GridViews don't use PrimaryScrollController by default on desktop"
,
(
WidgetTester
tester
)
async
{
final
ScrollController
controller
=
ScrollController
();
await
tester
.
pumpWidget
(
primaryScrollControllerBoilerplate
(
child:
GridView
.
count
(
crossAxisCount:
1
),
controller:
controller
,
));
expect
(
controller
.
hasClients
,
isFalse
);
},
variant:
TargetPlatformVariant
.
desktop
());
testWidgets
(
'Horizontal CustomScrollViews are non-primary by default'
,
(
WidgetTester
tester
)
async
{
const
CustomScrollView
view
=
CustomScrollView
(
scrollDirection:
Axis
.
horizontal
);
expect
(
view
.
primary
,
isFalse
);
final
ScrollController
controller
=
ScrollController
();
await
tester
.
pumpWidget
(
primaryScrollControllerBoilerplate
(
child:
CustomScrollView
(
scrollDirection:
Axis
.
horizontal
,
controller:
ScrollController
(),
),
controller:
controller
,
));
expect
(
controller
.
hasClients
,
isFalse
);
});
testWidgets
(
'Horizontal ListViews are non-primary by default'
,
(
WidgetTester
tester
)
async
{
final
ListView
view
=
ListView
(
scrollDirection:
Axis
.
horizontal
);
expect
(
view
.
primary
,
isFalse
);
final
ScrollController
controller
=
ScrollController
();
await
tester
.
pumpWidget
(
primaryScrollControllerBoilerplate
(
child:
ListView
(
scrollDirection:
Axis
.
horizontal
,
controller:
ScrollController
(),
),
controller:
controller
,
));
expect
(
controller
.
hasClients
,
isFalse
);
});
testWidgets
(
'Horizontal GridViews are non-primary by default'
,
(
WidgetTester
tester
)
async
{
final
GridView
view
=
GridView
.
count
(
scrollDirection:
Axis
.
horizontal
,
crossAxisCount:
1
,
);
expect
(
view
.
primary
,
isFalse
);
final
ScrollController
controller
=
ScrollController
();
await
tester
.
pumpWidget
(
primaryScrollControllerBoilerplate
(
child:
GridView
.
count
(
scrollDirection:
Axis
.
horizontal
,
controller:
ScrollController
(),
crossAxisCount:
1
,
),
controller:
controller
,
));
expect
(
controller
.
hasClients
,
isFalse
);
});
testWidgets
(
'CustomScrollViews with controllers are non-primary by default'
,
(
WidgetTester
tester
)
async
{
final
CustomScrollView
view
=
CustomScrollView
(
controller:
ScrollController
(),
);
expect
(
view
.
primary
,
isFalse
);
final
ScrollController
controller
=
ScrollController
();
await
tester
.
pumpWidget
(
primaryScrollControllerBoilerplate
(
child:
CustomScrollView
(
controller:
ScrollController
(),
),
controller:
controller
,
));
expect
(
controller
.
hasClients
,
isFalse
);
});
testWidgets
(
'ListViews with controllers are non-primary by default'
,
(
WidgetTester
tester
)
async
{
final
ListView
view
=
ListView
(
controller:
ScrollController
(),
);
expect
(
view
.
primary
,
isFalse
);
final
ScrollController
controller
=
ScrollController
();
await
tester
.
pumpWidget
(
primaryScrollControllerBoilerplate
(
child:
ListView
(
controller:
ScrollController
(),
),
controller:
controller
,
));
expect
(
controller
.
hasClients
,
isFalse
);
});
testWidgets
(
'GridViews with controllers are non-primary by default'
,
(
WidgetTester
tester
)
async
{
final
GridView
view
=
GridView
.
count
(
controller:
ScrollController
(),
crossAxisCount:
1
,
);
expect
(
view
.
primary
,
isFalse
);
final
ScrollController
controller
=
ScrollController
();
await
tester
.
pumpWidget
(
primaryScrollControllerBoilerplate
(
child:
GridView
.
count
(
controller:
ScrollController
(),
crossAxisCount:
1
,
),
controller:
controller
,
));
expect
(
controller
.
hasClients
,
isFalse
);
});
testWidgets
(
'CustomScrollView sets PrimaryScrollController when primary'
,
(
WidgetTester
tester
)
async
{
...
...
@@ -1318,6 +1425,55 @@ void main() {
);
});
testWidgets
(
'Fallback ScrollActions handle too many positions with error message'
,
(
WidgetTester
tester
)
async
{
Widget
getScrollView
()
{
return
SizedBox
(
width:
400.0
,
child:
CustomScrollView
(
primary:
true
,
slivers:
List
<
Widget
>.
generate
(
20
,
(
int
index
)
{
return
SliverToBoxAdapter
(
child:
Focus
(
child:
SizedBox
(
key:
ValueKey
<
String
>(
'Box
$index
'
),
height:
50.0
),
),
);
},
),
),
);
}
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Row
(
children:
<
Widget
>[
getScrollView
(),
getScrollView
(),
],
),
),
);
await
tester
.
pumpAndSettle
();
expect
(
tester
.
getRect
(
find
.
byKey
(
const
ValueKey
<
String
>(
'Box 0'
),
skipOffstage:
false
).
first
),
equals
(
const
Rect
.
fromLTRB
(
0.0
,
0.0
,
400.0
,
50.0
)),
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
pageDown
);
final
AssertionError
exception
=
tester
.
takeException
()
as
AssertionError
;
expect
(
exception
,
isAssertionError
);
expect
(
exception
.
message
,
contains
(
'A ScrollAction was invoked with the PrimaryScrollController, but '
'more than one ScrollPosition is attached.'
),
);
});
testWidgets
(
'if itemExtent is non-null, children have same extent in the scroll direction'
,
(
WidgetTester
tester
)
async
{
final
List
<
int
>
numbers
=
<
int
>[
0
,
1
,
2
];
...
...
packages/flutter/test/widgets/scrollbar_test.dart
View file @
784520bd
...
...
@@ -2203,7 +2203,7 @@ void main() {
await
tester
.
pumpAndSettle
();
});
testWidgets
(
'Scrollbar thumb can be dragged when the scrollable widget has a negative minScrollExtent'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'Scrollbar thumb can be dragged when the scrollable widget has a negative minScrollExtent
- desktop
'
,
(
WidgetTester
tester
)
async
{
// Regression test for https://github.com/flutter/flutter/issues/95840
final
ScrollController
scrollController
=
ScrollController
();
...
...
@@ -2223,6 +2223,7 @@ void main() {
isAlwaysShown:
true
,
controller:
scrollController
,
child:
CustomScrollView
(
primary:
true
,
center:
uniqueKey
,
slivers:
<
Widget
>[
SliverToBoxAdapter
(
...
...
@@ -2282,7 +2283,88 @@ void main() {
color:
const
Color
(
0x66BCBCBC
),
),
);
},
variant:
TargetPlatformVariant
.
all
());
},
variant:
TargetPlatformVariant
.
desktop
());
testWidgets
(
'Scrollbar thumb can be dragged when the scrollable widget has a negative minScrollExtent - mobile'
,
(
WidgetTester
tester
)
async
{
// Regression test for https://github.com/flutter/flutter/issues/95840
final
ScrollController
scrollController
=
ScrollController
();
final
UniqueKey
uniqueKey
=
UniqueKey
();
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
MediaQuery
(
data:
const
MediaQueryData
(),
child:
ScrollConfiguration
(
behavior:
const
ScrollBehavior
().
copyWith
(
scrollbars:
false
,
),
child:
PrimaryScrollController
(
controller:
scrollController
,
child:
RawScrollbar
(
isAlwaysShown:
true
,
controller:
scrollController
,
child:
CustomScrollView
(
center:
uniqueKey
,
slivers:
<
Widget
>[
SliverToBoxAdapter
(
child:
Container
(
height:
600.0
,
),
),
SliverToBoxAdapter
(
key:
uniqueKey
,
child:
Container
(
height:
600.0
,
),
),
SliverToBoxAdapter
(
child:
Container
(
height:
600.0
,
),
),
],
),
),
),
),
),
),
);
await
tester
.
pumpAndSettle
();
expect
(
scrollController
.
offset
,
0.0
);
expect
(
find
.
byType
(
RawScrollbar
),
paints
..
rect
(
rect:
const
Rect
.
fromLTRB
(
794.0
,
0.0
,
800.0
,
600.0
))
..
rect
(
rect:
const
Rect
.
fromLTRB
(
794.0
,
200.0
,
800.0
,
400.0
),
color:
const
Color
(
0x66BCBCBC
),
),
);
// Drag the thumb up to scroll up.
const
double
scrollAmount
=
-
10.0
;
final
TestGesture
dragScrollbarGesture
=
await
tester
.
startGesture
(
const
Offset
(
797.0
,
300.0
));
await
tester
.
pumpAndSettle
();
await
dragScrollbarGesture
.
moveBy
(
const
Offset
(
0.0
,
scrollAmount
));
await
tester
.
pumpAndSettle
();
await
dragScrollbarGesture
.
up
();
await
tester
.
pumpAndSettle
();
// The view has scrolled more than it would have by a swipe gesture of the
// same distance.
expect
(
scrollController
.
offset
,
lessThan
(
scrollAmount
*
2
));
expect
(
find
.
byType
(
RawScrollbar
),
paints
..
rect
(
rect:
const
Rect
.
fromLTRB
(
794.0
,
0.0
,
800.0
,
600.0
))
..
rect
(
rect:
const
Rect
.
fromLTRB
(
794.0
,
190.0
,
800.0
,
390.0
),
color:
const
Color
(
0x66BCBCBC
),
),
);
},
variant:
TargetPlatformVariant
.
mobile
());
test
(
'ScrollbarPainter.shouldRepaint returns true when any of the properties changes'
,
()
{
ScrollbarPainter
createPainter
({
...
...
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