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
fb7a5937
Unverified
Commit
fb7a5937
authored
Oct 09, 2018
by
Jonah Williams
Committed by
GitHub
Oct 09, 2018
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Basic scroll semantics support (#21764)
parent
59f1f1b6
Changes
18
Show whitespace changes
Inline
Side-by-side
Showing
18 changed files
with
661 additions
and
62 deletions
+661
-62
cupertino_navigation_demo.dart
...gallery/lib/demo/cupertino/cupertino_navigation_demo.dart
+5
-2
pesto_demo.dart
examples/flutter_gallery/lib/demo/pesto_demo.dart
+1
-0
proxy_box.dart
packages/flutter/lib/src/rendering/proxy_box.dart
+43
-0
semantics.dart
packages/flutter/lib/src/semantics/semantics.dart
+98
-3
basic.dart
packages/flutter/lib/src/widgets/basic.dart
+58
-0
scroll_view.dart
packages/flutter/lib/src/widgets/scroll_view.dart
+102
-6
scrollable.dart
packages/flutter/lib/src/widgets/scrollable.dart
+44
-3
sliver.dart
packages/flutter/lib/src/widgets/sliver.dart
+185
-0
semantics_test.dart
packages/flutter/test/semantics/semantics_test.dart
+4
-0
automatic_keep_alive_test.dart
packages/flutter/test/widgets/automatic_keep_alive_test.dart
+9
-0
draggable_test.dart
packages/flutter/test/widgets/draggable_test.dart
+1
-0
ensure_visible_test.dart
packages/flutter/test/widgets/ensure_visible_test.dart
+1
-0
keep_alive_test.dart
packages/flutter/test/widgets/keep_alive_test.dart
+4
-0
list_view_viewporting_test.dart
...ages/flutter/test/widgets/list_view_viewporting_test.dart
+1
-0
scrollable_semantics_traversal_order_test.dart
...st/widgets/scrollable_semantics_traversal_order_test.dart
+82
-47
semantics_tester.dart
packages/flutter/test/widgets/semantics_tester.dart
+20
-0
sliver_fill_viewport_test.dart
packages/flutter/test/widgets/sliver_fill_viewport_test.dart
+1
-1
matchers_test.dart
packages/flutter_test/test/matchers_test.dart
+2
-0
No files found.
examples/flutter_gallery/lib/demo/cupertino/cupertino_navigation_demo.dart
View file @
fb7a5937
...
...
@@ -30,6 +30,8 @@ const List<String> coolColorNames = <String>[
'Pervenche'
,
'Sinoper'
,
'Verditer'
,
'Watchet'
,
'Zaffre'
,
];
const
int
_kChildCount
=
50
;
class
CupertinoNavigationDemo
extends
StatelessWidget
{
CupertinoNavigationDemo
()
:
colorItems
=
List
<
Color
>.
generate
(
50
,
(
int
index
)
{
...
...
@@ -146,6 +148,7 @@ class CupertinoDemoTab1 extends StatelessWidget {
Widget
build
(
BuildContext
context
)
{
return
CupertinoPageScaffold
(
child:
CustomScrollView
(
semanticChildCount:
_kChildCount
,
slivers:
<
Widget
>[
CupertinoSliverNavigationBar
(
trailing:
trailingButtons
,
...
...
@@ -163,12 +166,12 @@ class CupertinoDemoTab1 extends StatelessWidget {
(
BuildContext
context
,
int
index
)
{
return
Tab1RowItem
(
index:
index
,
lastItem:
index
==
49
,
lastItem:
index
==
_kChildCount
-
1
,
color:
colorItems
[
index
],
colorName:
colorNameItems
[
index
],
);
},
childCount:
50
,
childCount:
_kChildCount
,
),
),
),
...
...
examples/flutter_gallery/lib/demo/pesto_demo.dart
View file @
fb7a5937
...
...
@@ -91,6 +91,7 @@ class _RecipeGridPageState extends State<RecipeGridPage> {
},
),
body:
CustomScrollView
(
semanticChildCount:
widget
.
recipes
.
length
,
slivers:
<
Widget
>[
_buildAppBar
(
context
,
statusBarHeight
),
_buildBody
(
context
,
statusBarHeight
),
...
...
packages/flutter/lib/src/rendering/proxy_box.dart
View file @
fb7a5937
...
...
@@ -4330,6 +4330,49 @@ class RenderExcludeSemantics extends RenderProxyBox {
}
}
/// A render objects that annotates semantics with an index.
///
/// Certain widgets will automatically provide a child index for building
/// semantics. For example, the [ScrollView] uses the index of the first
/// visible child semantics node to determine the
/// [SemanticsConfiguration.scrollIndex].
///
/// See also:
///
/// * [CustomScrollView], for an explanation of scroll semantics.
class
RenderIndexedSemantics
extends
RenderProxyBox
{
/// Creates a render object that annotates the child semantics with an index.
RenderIndexedSemantics
({
RenderBox
child
,
@required
int
index
,
})
:
assert
(
index
!=
null
),
_index
=
index
,
super
(
child
);
/// The index used to annotated child semantics.
int
get
index
=>
_index
;
int
_index
;
set
index
(
int
value
)
{
if
(
value
==
index
)
return
;
_index
=
value
;
markNeedsSemanticsUpdate
();
}
@override
void
describeSemanticsConfiguration
(
SemanticsConfiguration
config
)
{
super
.
describeSemanticsConfiguration
(
config
);
config
.
isSemanticBoundary
=
true
;
config
.
indexInParent
=
index
;
}
@override
void
debugFillProperties
(
DiagnosticPropertiesBuilder
properties
)
{
super
.
debugFillProperties
(
properties
);
properties
.
add
(
DiagnosticsProperty
<
int
>(
'index'
,
index
));
}
}
/// Provides an anchor for a [RenderFollowerLayer].
///
/// See also:
...
...
packages/flutter/lib/src/semantics/semantics.dart
View file @
fb7a5937
...
...
@@ -188,6 +188,8 @@ class SemanticsData extends Diagnosticable {
@required
this
.
textDirection
,
@required
this
.
rect
,
@required
this
.
textSelection
,
@required
this
.
scrollIndex
,
@required
this
.
scrollChildCount
,
@required
this
.
scrollPosition
,
@required
this
.
scrollExtentMax
,
@required
this
.
scrollExtentMin
,
...
...
@@ -249,6 +251,15 @@ class SemanticsData extends Diagnosticable {
/// if this node represents a text field.
final
TextSelection
textSelection
;
/// The total number of scrollable children that contribute to semantics.
///
/// If the number of children are unknown or unbounded, this value will be
/// null.
final
int
scrollChildCount
;
/// The index of the first visible semantic child of a scroll node.
final
int
scrollIndex
;
/// Indicates the current scrolling position in logical pixels if the node is
/// scrollable.
///
...
...
@@ -343,6 +354,8 @@ class SemanticsData extends Diagnosticable {
properties
.
add
(
EnumProperty
<
TextDirection
>(
'textDirection'
,
textDirection
,
defaultValue:
null
));
if
(
textSelection
?.
isValid
==
true
)
properties
.
add
(
MessageProperty
(
'textSelection'
,
'[
${textSelection.start}
,
${textSelection.end}
]'
));
properties
.
add
(
IntProperty
(
'scrollChildren'
,
scrollChildCount
,
defaultValue:
null
));
properties
.
add
(
IntProperty
(
'scrollIndex'
,
scrollIndex
,
defaultValue:
null
));
properties
.
add
(
DoubleProperty
(
'scrollExtentMin'
,
scrollExtentMin
,
defaultValue:
null
));
properties
.
add
(
DoubleProperty
(
'scrollPosition'
,
scrollPosition
,
defaultValue:
null
));
properties
.
add
(
DoubleProperty
(
'scrollExtentMax'
,
scrollExtentMax
,
defaultValue:
null
));
...
...
@@ -363,6 +376,8 @@ class SemanticsData extends Diagnosticable {
&&
typedOther
.
textDirection
==
textDirection
&&
typedOther
.
rect
==
rect
&&
setEquals
(
typedOther
.
tags
,
tags
)
&&
typedOther
.
scrollChildCount
==
scrollChildCount
&&
typedOther
.
scrollIndex
==
scrollIndex
&&
typedOther
.
textSelection
==
textSelection
&&
typedOther
.
scrollPosition
==
scrollPosition
&&
typedOther
.
scrollExtentMax
==
scrollExtentMax
...
...
@@ -385,6 +400,8 @@ class SemanticsData extends Diagnosticable {
rect
,
tags
,
textSelection
,
scrollChildCount
,
scrollIndex
,
scrollPosition
,
scrollExtentMax
,
scrollExtentMin
,
...
...
@@ -1125,6 +1142,14 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
/// If this rect is null [parentSemanticsClipRect] also has to be null.
Rect
parentPaintClipRect
;
/// The index of this node within the parent's list of semantic children.
///
/// This includes all semantic nodes, not just those currently in the
/// child list. For example, if a scrollable has five children but the first
/// two are not visible (and thus not included in the list of children), then
/// the index of the last node will still be 4.
int
indexInParent
;
/// Whether the node is invisible.
///
/// A node whose [rect] is outside of the bounds of the screen and hence not
...
...
@@ -1394,6 +1419,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
_scrollExtentMax
!=
config
.
_scrollExtentMax
||
_scrollExtentMin
!=
config
.
_scrollExtentMin
||
_actionsAsBits
!=
config
.
_actionsAsBits
||
indexInParent
!=
config
.
indexInParent
||
_mergeAllDescendantsIntoThisNode
!=
config
.
isMergingSemanticsOfDescendants
;
}
...
...
@@ -1415,7 +1441,8 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
int
_flags
=
_kEmptyConfig
.
_flags
;
bool
_hasFlag
(
SemanticsFlag
flag
)
=>
_flags
&
flag
.
index
!=
0
;
/// Whether this node currently has a given [SemanticsFlag].
bool
hasFlag
(
SemanticsFlag
flag
)
=>
_flags
&
flag
.
index
!=
0
;
/// A textual description of this node.
///
...
...
@@ -1479,6 +1506,17 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
TextSelection
get
textSelection
=>
_textSelection
;
TextSelection
_textSelection
;
/// The total number of scrollable children that contribute to semantics.
///
/// If the number of children are unknown or unbounded, this value will be
/// null.
int
get
scrollChildCount
=>
_scrollChildCount
;
int
_scrollChildCount
;
/// The index of the first visible semantic child of a scroll node.
int
get
scrollIndex
=>
_scrollIndex
;
int
_scrollIndex
;
/// Indicates the current scrolling position in logical pixels if the node is
/// scrollable.
///
...
...
@@ -1553,6 +1591,9 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
_scrollExtentMax
=
config
.
_scrollExtentMax
;
_scrollExtentMin
=
config
.
_scrollExtentMin
;
_mergeAllDescendantsIntoThisNode
=
config
.
isMergingSemanticsOfDescendants
;
_scrollChildCount
=
config
.
scrollChildCount
;
_scrollIndex
=
config
.
scrollIndex
;
indexInParent
=
config
.
indexInParent
;
_replaceChildren
(
childrenInInversePaintOrder
??
const
<
SemanticsNode
>[]);
assert
(
...
...
@@ -1582,6 +1623,8 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
TextDirection
textDirection
=
_textDirection
;
Set
<
SemanticsTag
>
mergedTags
=
tags
==
null
?
null
:
Set
<
SemanticsTag
>.
from
(
tags
);
TextSelection
textSelection
=
_textSelection
;
int
scrollChildCount
=
_scrollChildCount
;
int
scrollIndex
=
_scrollIndex
;
double
scrollPosition
=
_scrollPosition
;
double
scrollExtentMax
=
_scrollExtentMax
;
double
scrollExtentMin
=
_scrollExtentMin
;
...
...
@@ -1612,6 +1655,8 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
actions
|=
node
.
_actionsAsBits
;
textDirection
??=
node
.
_textDirection
;
textSelection
??=
node
.
_textSelection
;
scrollChildCount
??=
node
.
_scrollChildCount
;
scrollIndex
??=
node
.
_scrollIndex
;
scrollPosition
??=
node
.
_scrollPosition
;
scrollExtentMax
??=
node
.
_scrollExtentMax
;
scrollExtentMin
??=
node
.
_scrollExtentMin
;
...
...
@@ -1674,6 +1719,8 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
transform:
transform
,
tags:
mergedTags
,
textSelection:
textSelection
,
scrollChildCount:
scrollChildCount
,
scrollIndex:
scrollIndex
,
scrollPosition:
scrollPosition
,
scrollExtentMax:
scrollExtentMax
,
scrollExtentMin:
scrollExtentMin
,
...
...
@@ -1732,6 +1779,8 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
textDirection:
data
.
textDirection
,
textSelectionBase:
data
.
textSelection
!=
null
?
data
.
textSelection
.
baseOffset
:
-
1
,
textSelectionExtent:
data
.
textSelection
!=
null
?
data
.
textSelection
.
extentOffset
:
-
1
,
scrollChildren:
data
.
scrollChildCount
!=
null
?
data
.
scrollChildCount
:
0
,
scrollIndex:
data
.
scrollIndex
!=
null
?
data
.
scrollIndex
:
0
,
scrollPosition:
data
.
scrollPosition
!=
null
?
data
.
scrollPosition
:
double
.
nan
,
scrollExtentMax:
data
.
scrollExtentMax
!=
null
?
data
.
scrollExtentMax
:
double
.
nan
,
scrollExtentMin:
data
.
scrollExtentMin
!=
null
?
data
.
scrollExtentMin
:
double
.
nan
,
...
...
@@ -1855,10 +1904,10 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
.
toList
();
properties
.
add
(
IterableProperty
<
String
>(
'actions'
,
actions
,
ifEmpty:
null
));
properties
.
add
(
IterableProperty
<
String
>(
'customActions'
,
customSemanticsActions
,
ifEmpty:
null
));
final
List
<
String
>
flags
=
SemanticsFlag
.
values
.
values
.
where
((
SemanticsFlag
flag
)
=>
_
hasFlag
(
flag
)).
map
((
SemanticsFlag
flag
)
=>
flag
.
toString
().
substring
(
'SemanticsFlag.'
.
length
)).
toList
();
final
List
<
String
>
flags
=
SemanticsFlag
.
values
.
values
.
where
((
SemanticsFlag
flag
)
=>
hasFlag
(
flag
)).
map
((
SemanticsFlag
flag
)
=>
flag
.
toString
().
substring
(
'SemanticsFlag.'
.
length
)).
toList
();
properties
.
add
(
IterableProperty
<
String
>(
'flags'
,
flags
,
ifEmpty:
null
));
properties
.
add
(
FlagProperty
(
'isInvisible'
,
value:
isInvisible
,
ifTrue:
'invisible'
));
properties
.
add
(
FlagProperty
(
'isHidden'
,
value:
_
hasFlag
(
SemanticsFlag
.
isHidden
),
ifTrue:
'HIDDEN'
));
properties
.
add
(
FlagProperty
(
'isHidden'
,
value:
hasFlag
(
SemanticsFlag
.
isHidden
),
ifTrue:
'HIDDEN'
));
properties
.
add
(
StringProperty
(
'label'
,
_label
,
defaultValue:
''
));
properties
.
add
(
StringProperty
(
'value'
,
_value
,
defaultValue:
''
));
properties
.
add
(
StringProperty
(
'increasedValue'
,
_increasedValue
,
defaultValue:
''
));
...
...
@@ -1868,6 +1917,8 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
properties
.
add
(
DiagnosticsProperty
<
SemanticsSortKey
>(
'sortKey'
,
sortKey
,
defaultValue:
null
));
if
(
_textSelection
?.
isValid
==
true
)
properties
.
add
(
MessageProperty
(
'text selection'
,
'[
${_textSelection.start}
,
${_textSelection.end}
]'
));
properties
.
add
(
IntProperty
(
'scrollChildren'
,
scrollChildCount
,
defaultValue:
null
));
properties
.
add
(
IntProperty
(
'scrollIndex'
,
scrollIndex
,
defaultValue:
null
));
properties
.
add
(
DoubleProperty
(
'scrollExtentMin'
,
scrollExtentMin
,
defaultValue:
null
));
properties
.
add
(
DoubleProperty
(
'scrollPosition'
,
scrollPosition
,
defaultValue:
null
));
properties
.
add
(
DoubleProperty
(
'scrollExtentMax'
,
scrollExtentMax
,
defaultValue:
null
));
...
...
@@ -2889,6 +2940,44 @@ class SemanticsConfiguration {
_hasBeenAnnotated
=
true
;
}
/// The index of this node within the parent's list of semantic children.
///
/// This includes all semantic nodes, not just those currently in the
/// child list. For example, if a scrollable has five children but the first
/// two are not visible (and thus not included in the list of children), then
/// the index of the last node will still be 4.
int
get
indexInParent
=>
_indexInParent
;
int
_indexInParent
;
set
indexInParent
(
int
value
)
{
_indexInParent
=
value
;
_hasBeenAnnotated
=
true
;
}
/// The total number of scrollable children that contribute to semantics.
///
/// If the number of children are unknown or unbounded, this value will be
/// null.
int
get
scrollChildCount
=>
_scrollChildCount
;
int
_scrollChildCount
;
set
scrollChildCount
(
int
value
)
{
if
(
value
==
scrollChildCount
)
return
;
_scrollChildCount
=
value
;
_hasBeenAnnotated
=
true
;
}
/// The index of the first visible scrollable child that contributes to
/// semantics.
int
get
scrollIndex
=>
_scrollIndex
;
int
_scrollIndex
;
set
scrollIndex
(
int
value
)
{
if
(
value
==
scrollIndex
)
return
;
_scrollIndex
=
value
;
_hasBeenAnnotated
=
true
;
}
/// Whether the semantic information provided by the owning [RenderObject] and
/// all of its descendants should be treated as one logical entity.
///
...
...
@@ -3358,6 +3447,9 @@ class SemanticsConfiguration {
_scrollExtentMax
??=
other
.
_scrollExtentMax
;
_scrollExtentMin
??=
other
.
_scrollExtentMin
;
_hintOverrides
??=
other
.
_hintOverrides
;
_indexInParent
??=
other
.
indexInParent
;
_scrollIndex
??=
other
.
_scrollIndex
;
_scrollChildCount
??=
other
.
_scrollChildCount
;
textDirection
??=
other
.
textDirection
;
_sortKey
??=
other
.
_sortKey
;
...
...
@@ -3406,6 +3498,9 @@ class SemanticsConfiguration {
..
_scrollExtentMax
=
_scrollExtentMax
..
_scrollExtentMin
=
_scrollExtentMin
..
_actionsAsBits
=
_actionsAsBits
..
_indexInParent
=
indexInParent
..
_scrollIndex
=
_scrollIndex
..
_scrollChildCount
=
_scrollChildCount
..
_actions
.
addAll
(
_actions
)
..
_customSemanticsActions
.
addAll
(
_customSemanticsActions
);
}
...
...
packages/flutter/lib/src/widgets/basic.dart
View file @
fb7a5937
...
...
@@ -5553,6 +5553,64 @@ class ExcludeSemantics extends SingleChildRenderObjectWidget {
}
}
/// A widget that annotates the child semantics with an index.
///
/// Semantic indexes are used by TalkBack/Voiceover to make announcements about
/// the current scroll state. Certain widgets like the [ListView] will
/// automatically provide a child index for building semantics. A user may wish
/// to manually provide semanitc indexes if not all child of the scrollable
/// contribute semantics.
///
/// ## Sample code
///
/// The example below handles spacers in a scrollable that don't contribute
/// semantics. The automatic indexes would give the spaces a semantic index,
/// causing scroll announcements to erroneously state that there are four items
/// visible.
///
/// ```dart
/// ListView(
/// addSemanticIndexes: false,
/// semanticChildCount: 2,
/// children: const <Widget>[
/// IndexedSemantics(index: 0, child: Text('First')),
/// Spacer(),
/// IndexedSemantics(index: 1, child: Text('Second')),
/// Spacer(),
/// ],
/// )
/// ```
///
/// See also:
/// * [CustomScrollView], for an explaination of index semantics.
class
IndexedSemantics
extends
SingleChildRenderObjectWidget
{
/// Creates a widget that annotated the first child semantics node with an index.
///
/// [index] must not be null.
const
IndexedSemantics
({
Key
key
,
@required
this
.
index
,
Widget
child
,
})
:
assert
(
index
!=
null
),
super
(
key:
key
,
child:
child
);
/// The index used to annotate the first child semantics node.
final
int
index
;
@override
RenderIndexedSemantics
createRenderObject
(
BuildContext
context
)
=>
RenderIndexedSemantics
(
index:
index
);
@override
void
updateRenderObject
(
BuildContext
context
,
RenderIndexedSemantics
renderObject
)
{
renderObject
.
index
=
index
;
}
@override
void
debugFillProperties
(
DiagnosticPropertiesBuilder
properties
)
{
super
.
debugFillProperties
(
properties
);
properties
.
add
(
DiagnosticsProperty
<
int
>(
'index'
,
index
));
}
}
/// A widget that builds its child.
///
/// Useful for attaching a key to an existing widget.
...
...
packages/flutter/lib/src/widgets/scroll_view.dart
View file @
fb7a5937
...
...
@@ -60,6 +60,7 @@ abstract class ScrollView extends StatelessWidget {
ScrollPhysics
physics
,
this
.
shrinkWrap
=
false
,
this
.
cacheExtent
,
this
.
semanticChildCount
,
})
:
assert
(
reverse
!=
null
),
assert
(
shrinkWrap
!=
null
),
assert
(!(
controller
!=
null
&&
primary
==
true
),
...
...
@@ -172,6 +173,21 @@ abstract class ScrollView extends StatelessWidget {
/// {@macro flutter.rendering.viewport.cacheExtent}
final
double
cacheExtent
;
/// The number of children that will contribute semantic information.
///
/// Some subtypes of [ScrollView] can infer this value automatically. For
/// example [ListView] will use the number of widgets in the child list,
/// while the [new ListView.separated] constructor will use half that amount.
///
/// For [CustomScrollView] and other types which do not receive a builder
/// or list of widgets, the child count must be explicitly provided. If the
/// number is unknown or unbounded this should be left unset or set to null.
///
/// See also:
///
/// * [SemanticsConfiguration.scrollChildCount], the corresponding semantics property.
final
int
semanticChildCount
;
/// Returns the [AxisDirection] in which the scroll view scrolls.
///
/// Combines the [scrollDirection] with the [reverse] boolean to obtain the
...
...
@@ -234,6 +250,7 @@ abstract class ScrollView extends StatelessWidget {
axisDirection:
axisDirection
,
controller:
scrollController
,
physics:
physics
,
semanticChildCount:
semanticChildCount
,
viewportBuilder:
(
BuildContext
context
,
ViewportOffset
offset
)
{
return
buildViewport
(
context
,
offset
,
axisDirection
,
slivers
);
},
...
...
@@ -317,6 +334,40 @@ abstract class ScrollView extends StatelessWidget {
/// )
/// ```
///
/// ## Accessibility
///
/// A [CustomScrollView] can allow Talkback/VoiceOver to make announcements
/// to the user when the scroll state changes. For example, on Android an
/// announcement might be read as "showing items 1 to 10 of 23". To produce
/// this announcment, the scroll view needs three pieces of information:
///
/// * The first visible child index.
/// * The total number of children.
/// * The total number of visible children.
///
/// The last value can be computed exactly by the framework, however the first
/// two must be provided. Most of the higher-level scrollable widgets provide
/// this information automatically. For example, [ListView] provides each child
/// widget with a semantic index automatically and sets the semantic child
/// count to the length of the list.
///
/// To determine visible indexes, the scroll view needs a way to associate the
/// generated semantics of each scrollable item with a semantic index. This can
/// be done by wrapping the child widgets in an [IndexedSemantics].
///
/// This semantic index is not necesarily the same as the index of the widget
/// in the scrollable, because some widgets may not contribute semantic
/// information. Consider a [new ListView.separated()], every other widget is a
/// divider with no semantic information. In this case, only odd numbered
/// widgets have a semantic index (equal to the index ~/ 2). Furthermore, the
/// total number of children in this example would be half the number of
/// widgets. Note that [new ListView.separated()] handles this automatically
/// and is only used here as an example.
///
/// The total number of visible children can be provided by the constructor
/// parameter `semanticChildCount`. This should always be the same as the
/// number of widgets wrapped in [IndexedSemantics].
///
/// See also:
///
/// * [SliverList], which is a sliver that displays linear list of children.
...
...
@@ -329,6 +380,8 @@ abstract class ScrollView extends StatelessWidget {
/// and float as the scroll view scrolls.
/// * [ScrollNotification] and [NotificationListener], which can be used to watch
/// the scroll position without using a [ScrollController].
/// * [IndexedSemantics], which allows annotating child lists with an index
/// for scroll announcements.
class
CustomScrollView
extends
ScrollView
{
/// Creates a [ScrollView] that creates custom scroll effects using slivers.
///
...
...
@@ -343,6 +396,7 @@ class CustomScrollView extends ScrollView {
bool
shrinkWrap
=
false
,
double
cacheExtent
,
this
.
slivers
=
const
<
Widget
>[],
int
semanticChildCount
,
})
:
super
(
key:
key
,
scrollDirection:
scrollDirection
,
...
...
@@ -352,6 +406,7 @@ class CustomScrollView extends ScrollView {
physics:
physics
,
shrinkWrap:
shrinkWrap
,
cacheExtent:
cacheExtent
,
semanticChildCount:
semanticChildCount
,
);
/// The slivers to place inside the viewport.
...
...
@@ -383,6 +438,7 @@ abstract class BoxScrollView extends ScrollView {
bool
shrinkWrap
=
false
,
this
.
padding
,
double
cacheExtent
,
int
semanticChildCount
,
})
:
super
(
key:
key
,
scrollDirection:
scrollDirection
,
...
...
@@ -392,6 +448,7 @@ abstract class BoxScrollView extends ScrollView {
physics:
physics
,
shrinkWrap:
shrinkWrap
,
cacheExtent:
cacheExtent
,
semanticChildCount:
semanticChildCount
,
);
/// The amount of space by which to inset the children.
...
...
@@ -672,12 +729,15 @@ class ListView extends BoxScrollView {
this
.
itemExtent
,
bool
addAutomaticKeepAlives
=
true
,
bool
addRepaintBoundaries
=
true
,
bool
addSemanticIndexes
=
true
,
double
cacheExtent
,
List
<
Widget
>
children
=
const
<
Widget
>[],
int
semanticChildCount
,
})
:
childrenDelegate
=
SliverChildListDelegate
(
children
,
addAutomaticKeepAlives:
addAutomaticKeepAlives
,
addRepaintBoundaries:
addRepaintBoundaries
,
addSemanticIndexes:
addSemanticIndexes
,
),
super
(
key:
key
,
scrollDirection:
scrollDirection
,
...
...
@@ -688,6 +748,7 @@ class ListView extends BoxScrollView {
shrinkWrap:
shrinkWrap
,
padding:
padding
,
cacheExtent:
cacheExtent
,
semanticChildCount:
semanticChildCount
??
children
.
length
,
);
/// Creates a scrollable, linear array of widgets that are created on demand.
...
...
@@ -728,12 +789,15 @@ class ListView extends BoxScrollView {
int
itemCount
,
bool
addAutomaticKeepAlives
=
true
,
bool
addRepaintBoundaries
=
true
,
bool
addSemanticIndexes
=
true
,
double
cacheExtent
,
int
semanticChildCount
,
})
:
childrenDelegate
=
SliverChildBuilderDelegate
(
itemBuilder
,
childCount:
itemCount
,
addAutomaticKeepAlives:
addAutomaticKeepAlives
,
addRepaintBoundaries:
addRepaintBoundaries
,
addSemanticIndexes:
addSemanticIndexes
,
),
super
(
key:
key
,
scrollDirection:
scrollDirection
,
...
...
@@ -743,7 +807,8 @@ class ListView extends BoxScrollView {
physics:
physics
,
shrinkWrap:
shrinkWrap
,
padding:
padding
,
cacheExtent:
cacheExtent
cacheExtent:
cacheExtent
,
semanticChildCount:
semanticChildCount
??
itemCount
,
);
/// Creates a fixed-length scrollable linear array of list "items" separated
...
...
@@ -804,6 +869,7 @@ class ListView extends BoxScrollView {
@required
int
itemCount
,
bool
addAutomaticKeepAlives
=
true
,
bool
addRepaintBoundaries
=
true
,
bool
addSemanticIndexes
=
true
,
double
cacheExtent
,
})
:
assert
(
itemBuilder
!=
null
),
assert
(
separatorBuilder
!=
null
),
...
...
@@ -816,9 +882,13 @@ class ListView extends BoxScrollView {
?
itemBuilder
(
context
,
itemIndex
)
:
separatorBuilder
(
context
,
itemIndex
);
},
childCount:
math
.
max
(
0
,
itemCount
*
2
-
1
),
childCount:
_computeSemanticChildCount
(
itemCount
),
addAutomaticKeepAlives:
addAutomaticKeepAlives
,
addRepaintBoundaries:
addRepaintBoundaries
,
addSemanticIndexes:
addSemanticIndexes
,
semanticIndexCallback:
(
Widget
_
,
int
index
)
{
return
index
.
isEven
?
index
~/
2
:
null
;
}
),
super
(
key:
key
,
scrollDirection:
scrollDirection
,
...
...
@@ -828,7 +898,8 @@ class ListView extends BoxScrollView {
physics:
physics
,
shrinkWrap:
shrinkWrap
,
padding:
padding
,
cacheExtent:
cacheExtent
cacheExtent:
cacheExtent
,
semanticChildCount:
_computeSemanticChildCount
(
itemCount
),
);
/// Creates a scrollable, linear array of widgets with a custom child model.
...
...
@@ -847,6 +918,7 @@ class ListView extends BoxScrollView {
this
.
itemExtent
,
@required
this
.
childrenDelegate
,
double
cacheExtent
,
int
semanticChildCount
,
})
:
assert
(
childrenDelegate
!=
null
),
super
(
key:
key
,
...
...
@@ -858,6 +930,7 @@ class ListView extends BoxScrollView {
shrinkWrap:
shrinkWrap
,
padding:
padding
,
cacheExtent:
cacheExtent
,
semanticChildCount:
semanticChildCount
,
);
/// If non-null, forces the children to have the given extent in the scroll
...
...
@@ -893,6 +966,11 @@ class ListView extends BoxScrollView {
super
.
debugFillProperties
(
properties
);
properties
.
add
(
DoubleProperty
(
'itemExtent'
,
itemExtent
,
defaultValue:
null
));
}
// Helper method to compute the semantic child count for the separated constructor.
static
int
_computeSemanticChildCount
(
int
itemCount
)
{
return
math
.
max
(
0
,
itemCount
*
2
-
1
);
}
}
/// A scrollable, 2D array of widgets.
...
...
@@ -1047,13 +1125,16 @@ class GridView extends BoxScrollView {
@required
this
.
gridDelegate
,
bool
addAutomaticKeepAlives
=
true
,
bool
addRepaintBoundaries
=
true
,
bool
addSemanticIndexes
=
true
,
double
cacheExtent
,
List
<
Widget
>
children
=
const
<
Widget
>[],
int
semanticChildCount
,
})
:
assert
(
gridDelegate
!=
null
),
childrenDelegate
=
SliverChildListDelegate
(
children
,
addAutomaticKeepAlives:
addAutomaticKeepAlives
,
addRepaintBoundaries:
addRepaintBoundaries
,
addSemanticIndexes:
addSemanticIndexes
,
),
super
(
key:
key
,
...
...
@@ -1065,6 +1146,7 @@ class GridView extends BoxScrollView {
shrinkWrap:
shrinkWrap
,
padding:
padding
,
cacheExtent:
cacheExtent
,
semanticChildCount:
semanticChildCount
??
children
.
length
,
);
/// Creates a scrollable, 2D array of widgets that are created on demand.
...
...
@@ -1100,13 +1182,16 @@ class GridView extends BoxScrollView {
int
itemCount
,
bool
addAutomaticKeepAlives
=
true
,
bool
addRepaintBoundaries
=
true
,
bool
addSemanticIndexes
=
true
,
double
cacheExtent
,
int
semanticChildCount
,
})
:
assert
(
gridDelegate
!=
null
),
childrenDelegate
=
SliverChildBuilderDelegate
(
itemBuilder
,
childCount:
itemCount
,
addAutomaticKeepAlives:
addAutomaticKeepAlives
,
addRepaintBoundaries:
addRepaintBoundaries
,
addSemanticIndexes:
addSemanticIndexes
,
),
super
(
key:
key
,
...
...
@@ -1118,6 +1203,7 @@ class GridView extends BoxScrollView {
shrinkWrap:
shrinkWrap
,
padding:
padding
,
cacheExtent:
cacheExtent
,
semanticChildCount:
semanticChildCount
??
itemCount
,
);
/// Creates a scrollable, 2D array of widgets with both a custom
...
...
@@ -1139,6 +1225,7 @@ class GridView extends BoxScrollView {
@required
this
.
gridDelegate
,
@required
this
.
childrenDelegate
,
double
cacheExtent
,
int
semanticChildCount
,
})
:
assert
(
gridDelegate
!=
null
),
assert
(
childrenDelegate
!=
null
),
super
(
...
...
@@ -1151,6 +1238,7 @@ class GridView extends BoxScrollView {
shrinkWrap:
shrinkWrap
,
padding:
padding
,
cacheExtent:
cacheExtent
,
semanticChildCount:
semanticChildCount
,
);
/// Creates a scrollable, 2D array of widgets with a fixed number of tiles in
...
...
@@ -1182,8 +1270,10 @@ class GridView extends BoxScrollView {
double
childAspectRatio
=
1.0
,
bool
addAutomaticKeepAlives
=
true
,
bool
addRepaintBoundaries
=
true
,
bool
addSemanticIndexes
=
true
,
double
cacheExtent
,
List
<
Widget
>
children
=
const
<
Widget
>[],
int
semanticChildCount
,
})
:
gridDelegate
=
SliverGridDelegateWithFixedCrossAxisCount
(
crossAxisCount:
crossAxisCount
,
mainAxisSpacing:
mainAxisSpacing
,
...
...
@@ -1194,6 +1284,7 @@ class GridView extends BoxScrollView {
children
,
addAutomaticKeepAlives:
addAutomaticKeepAlives
,
addRepaintBoundaries:
addRepaintBoundaries
,
addSemanticIndexes:
addSemanticIndexes
,
),
super
(
key:
key
,
scrollDirection:
scrollDirection
,
...
...
@@ -1204,6 +1295,7 @@ class GridView extends BoxScrollView {
shrinkWrap:
shrinkWrap
,
padding:
padding
,
cacheExtent:
cacheExtent
,
semanticChildCount:
semanticChildCount
??
children
.
length
,
);
/// Creates a scrollable, 2D array of widgets with tiles that each have a
...
...
@@ -1235,7 +1327,9 @@ class GridView extends BoxScrollView {
double
childAspectRatio
=
1.0
,
bool
addAutomaticKeepAlives
=
true
,
bool
addRepaintBoundaries
=
true
,
bool
addSemanticIndexes
=
true
,
List
<
Widget
>
children
=
const
<
Widget
>[],
int
semanticChildCount
,
})
:
gridDelegate
=
SliverGridDelegateWithMaxCrossAxisExtent
(
maxCrossAxisExtent:
maxCrossAxisExtent
,
mainAxisSpacing:
mainAxisSpacing
,
...
...
@@ -1246,6 +1340,7 @@ class GridView extends BoxScrollView {
children
,
addAutomaticKeepAlives:
addAutomaticKeepAlives
,
addRepaintBoundaries:
addRepaintBoundaries
,
addSemanticIndexes:
addSemanticIndexes
,
),
super
(
key:
key
,
scrollDirection:
scrollDirection
,
...
...
@@ -1255,6 +1350,7 @@ class GridView extends BoxScrollView {
physics:
physics
,
shrinkWrap:
shrinkWrap
,
padding:
padding
,
semanticChildCount:
semanticChildCount
??
children
.
length
,
);
/// A delegate that controls the layout of the children within the [GridView].
...
...
packages/flutter/lib/src/widgets/scrollable.dart
View file @
fb7a5937
...
...
@@ -3,6 +3,7 @@
// found in the LICENSE file.
import
'dart:async'
;
import
'dart:ui'
;
import
'package:flutter/gestures.dart'
;
import
'package:flutter/rendering.dart'
;
...
...
@@ -79,6 +80,7 @@ class Scrollable extends StatefulWidget {
this
.
physics
,
@required
this
.
viewportBuilder
,
this
.
excludeFromSemantics
=
false
,
this
.
semanticChildCount
,
})
:
assert
(
axisDirection
!=
null
),
assert
(
viewportBuilder
!=
null
),
assert
(
excludeFromSemantics
!=
null
),
...
...
@@ -161,6 +163,23 @@ class Scrollable extends StatefulWidget {
/// exclusion.
final
bool
excludeFromSemantics
;
/// The number of children that will contribute semantic information.
///
/// The value will be null if the number of children is unknown or unbounded.
///
/// Some subtypes of [ScrollView] can infer this value automatically. For
/// example [ListView] will use the number of widgets in the child list,
/// while the [new ListView.separated] constructor will use half that amount.
///
/// For [CustomScrollView] and other types which do not receive a builder
/// or list of widgets, the child count must be explicitly provided.
///
/// See also:
///
/// * [CustomScrollView], for an explanation of scroll semantics.
/// * [SemanticsConfiguration.scrollChildCount], the corresponding semantics property.
final
int
semanticChildCount
;
/// The axis along which the scroll view scrolls.
///
/// Determined by the [axisDirection].
...
...
@@ -509,6 +528,7 @@ class ScrollableState extends State<Scrollable> with TickerProviderStateMixin
child:
result
,
position:
position
,
allowImplicitScrolling:
widget
?.
physics
?.
allowImplicitScrolling
??
false
,
semanticChildCount:
widget
.
semanticChildCount
,
);
}
...
...
@@ -541,17 +561,20 @@ class _ScrollSemantics extends SingleChildRenderObjectWidget {
Key
key
,
@required
this
.
position
,
@required
this
.
allowImplicitScrolling
,
@required
this
.
semanticChildCount
,
Widget
child
})
:
assert
(
position
!=
null
),
super
(
key:
key
,
child:
child
);
final
ScrollPosition
position
;
final
bool
allowImplicitScrolling
;
final
int
semanticChildCount
;
@override
_RenderScrollSemantics
createRenderObject
(
BuildContext
context
)
{
return
_RenderScrollSemantics
(
position:
position
,
allowImplicitScrolling:
allowImplicitScrolling
,
semanticChildCount:
semanticChildCount
,
);
}
...
...
@@ -559,7 +582,8 @@ class _ScrollSemantics extends SingleChildRenderObjectWidget {
void
updateRenderObject
(
BuildContext
context
,
_RenderScrollSemantics
renderObject
)
{
renderObject
..
allowImplicitScrolling
=
allowImplicitScrolling
..
position
=
position
;
..
position
=
position
..
semanticChildCount
=
semanticChildCount
;
}
}
...
...
@@ -567,9 +591,11 @@ class _RenderScrollSemantics extends RenderProxyBox {
_RenderScrollSemantics
({
@required
ScrollPosition
position
,
@required
bool
allowImplicitScrolling
,
@required
int
semanticChildCount
,
RenderBox
child
,
})
:
_position
=
position
,
_allowImplicitScrolling
=
allowImplicitScrolling
,
_semanticChildCount
=
semanticChildCount
,
assert
(
position
!=
null
),
super
(
child
)
{
position
.
addListener
(
markNeedsSemanticsUpdate
);
}
...
...
@@ -597,6 +623,15 @@ class _RenderScrollSemantics extends RenderProxyBox {
markNeedsSemanticsUpdate
();
}
int
get
semanticChildCount
=>
_semanticChildCount
;
int
_semanticChildCount
;
set
semanticChildCount
(
int
value
)
{
if
(
value
==
semanticChildCount
)
return
;
_semanticChildCount
=
value
;
markNeedsSemanticsUpdate
();
}
@override
void
describeSemanticsConfiguration
(
SemanticsConfiguration
config
)
{
super
.
describeSemanticsConfiguration
(
config
);
...
...
@@ -606,7 +641,8 @@ class _RenderScrollSemantics extends RenderProxyBox {
..
hasImplicitScrolling
=
allowImplicitScrolling
..
scrollPosition
=
_position
.
pixels
..
scrollExtentMax
=
_position
.
maxScrollExtent
..
scrollExtentMin
=
_position
.
minScrollExtent
;
..
scrollExtentMin
=
_position
.
minScrollExtent
..
scrollChildCount
=
semanticChildCount
;
}
}
...
...
@@ -624,15 +660,20 @@ class _RenderScrollSemantics extends RenderProxyBox {
..
isMergedIntoParent
=
node
.
isPartOfNodeMerging
..
rect
=
Offset
.
zero
&
node
.
rect
.
size
;
int
firstVisibleIndex
;
final
List
<
SemanticsNode
>
excluded
=
<
SemanticsNode
>[
_innerNode
];
final
List
<
SemanticsNode
>
included
=
<
SemanticsNode
>[];
for
(
SemanticsNode
child
in
children
)
{
assert
(
child
.
isTagged
(
RenderViewport
.
useTwoPaneSemantics
));
if
(
child
.
isTagged
(
RenderViewport
.
excludeFromScrolling
))
excluded
.
add
(
child
);
else
else
{
if
(!
child
.
hasFlag
(
SemanticsFlag
.
isHidden
))
firstVisibleIndex
??=
child
.
indexInParent
;
included
.
add
(
child
);
}
}
config
.
scrollIndex
=
firstVisibleIndex
;
node
.
updateWith
(
config:
null
,
childrenInInversePaintOrder:
excluded
);
_innerNode
.
updateWith
(
config:
config
,
childrenInInversePaintOrder:
included
);
}
...
...
packages/flutter/lib/src/widgets/sliver.dart
View file @
fb7a5937
...
...
@@ -16,6 +16,25 @@ export 'package:flutter/rendering.dart' show
SliverGridDelegateWithFixedCrossAxisCount
,
SliverGridDelegateWithMaxCrossAxisExtent
;
// Examples can assume:
// SliverGridDelegateWithMaxCrossAxisExtent _gridDelegate;
/// A callback which produces a semantic index given a widget and the local index.
///
/// Return a null value to prevent a widget from receiving an index.
///
/// A semantic index is used to tag child semantic nodes for accessibility
/// announcements in scroll view.
///
/// See also:
///
/// * [CustomScrollView], for an explanation of scroll semantics.
/// * [SliverChildBuilderDelegate], for an explanation of how this is used to
/// generate indexes.
typedef
SemanticIndexCallback
=
int
Function
(
Widget
widget
,
int
localIndex
);
int
_kDefaultSemanticIndexCallback
(
Widget
_
,
int
localIndex
)
=>
localIndex
;
/// A delegate that supplies children for slivers.
///
/// Many slivers lazily construct their box children to avoid creating more
...
...
@@ -188,10 +207,96 @@ abstract class SliverChildDelegate {
/// default) and in [RepaintBoundary] widgets if [addRepaintBoundaries] is true
/// (also the default).
///
/// ## Accessibility
///
/// The [CustomScrollView] requires that its semantic children are annotated
/// using [IndexedSemantics]. This is done by default in the delegate with
/// the `addSemanticIndexes` parameter set to true.
///
/// If multiple delegates are used in a single scroll view, then the indexes
/// will not be correct by default. The `semanticIndexOffset` can be used to
/// offset the semantic indexes of each delegate so that the indexes are
/// monotonically increasing. For example, if a scroll view contains two
/// delegates where the first has 10 children contributing semantics, then the
/// second delegate should offset its children by 10.
///
/// ## Sample code
///
/// This sample code shows how to use `semanticIndexOffset` to handle multiple
/// delegates in a single scroll view.
///
/// ```dart
/// CustomScrollView(
/// semanticChildCount: 4,
/// slivers: <Widget>[
/// SliverGrid(
/// gridDelegate: _gridDelegate,
/// delegate: SliverChildBuilderDelegate(
/// (BuildContext context, int index) {
/// return Text('...');
/// },
/// childCount: 2,
/// ),
/// ),
/// SliverGrid(
/// gridDelegate: _gridDelegate,
/// delegate: SliverChildBuilderDelegate(
/// (BuildContext context, int index) {
/// return Text('...');
/// },
/// childCount: 2,
/// semanticIndexOffset: 2,
/// ),
/// ),
/// ],
/// )
/// ```
///
/// In certain cases, only a subset of child widgets should be annotated
/// with a semantic index. For example, in [new ListView.separated()] the
/// separators do not have an index assocaited with them. This is done by
/// providing a `semanticIndexCallback` which returns null for separators
/// indexes and rounds the non-separator indexes down by half.
///
/// ## Sample code
///
/// This sample code shows how to use `semanticIndexCallback` to handle
/// annotating a subset of child nodes with a semantic index. There is
/// a [Spacer] widget at odd indexes which should not have a semantic
/// index.
///
/// ```dart
/// CustomScrollView(
/// semanticChildCount: 5,
/// slivers: <Widget>[
/// SliverGrid(
/// gridDelegate: _gridDelegate,
/// delegate: SliverChildBuilderDelegate(
/// (BuildContext context, int index) {
/// if (index.isEven) {
/// return Text('...');
/// }
/// return Spacer();
/// },
/// semanticIndexCallback: (Widget widget, int localIndex) {
/// if (localIndex.isEven) {
/// return localIndex ~/ 2;
/// }
/// return null;
/// },
/// childCount: 10,
/// ),
/// ),
/// ],
/// )
/// ```
///
/// See also:
///
/// * [SliverChildListDelegate], which is a delegate that has an explicit list
/// of children.
/// * [IndexedSemantics], for an example of manually annotating child nodes
/// with semantic indexes.
class
SliverChildBuilderDelegate
extends
SliverChildDelegate
{
/// Creates a delegate that supplies children for slivers using the given
/// builder callback.
...
...
@@ -203,6 +308,9 @@ class SliverChildBuilderDelegate extends SliverChildDelegate {
this
.
childCount
,
this
.
addAutomaticKeepAlives
=
true
,
this
.
addRepaintBoundaries
=
true
,
this
.
addSemanticIndexes
=
true
,
this
.
semanticIndexCallback
=
_kDefaultSemanticIndexCallback
,
this
.
semanticIndexOffset
=
0
,
})
:
assert
(
builder
!=
null
),
assert
(
addAutomaticKeepAlives
!=
null
),
assert
(
addRepaintBoundaries
!=
null
);
...
...
@@ -250,6 +358,27 @@ class SliverChildBuilderDelegate extends SliverChildDelegate {
/// Defaults to true.
final
bool
addRepaintBoundaries
;
/// Whether to wrap each child in an [IndexedSemantics].
///
/// Typically, children in a scrolling container must be annotated with a
/// semantic index in order to generate the correct accessibility
/// announcements. This should only be set to false if the indexes have
/// already been provided by wrapping the correct child widgets in an
/// indexed child semantics widget.
///
/// Defaults to true.
final
bool
addSemanticIndexes
;
/// An initial offset to add to the semantic indexes generated by this widget.
///
/// Defaults to zero.
final
int
semanticIndexOffset
;
/// A [SemanticIndexCallback] which is used when [addSemanticIndexes] is true.
///
/// Defaults to providing an index for each widget.
final
SemanticIndexCallback
semanticIndexCallback
;
@override
Widget
build
(
BuildContext
context
,
int
index
)
{
assert
(
builder
!=
null
);
...
...
@@ -260,6 +389,11 @@ class SliverChildBuilderDelegate extends SliverChildDelegate {
return
null
;
if
(
addRepaintBoundaries
)
child
=
RepaintBoundary
.
wrap
(
child
,
index
);
if
(
addSemanticIndexes
)
{
final
int
semanticIndex
=
semanticIndexCallback
(
child
,
index
);
if
(
semanticIndex
!=
null
)
child
=
IndexedSemantics
(
index:
semanticIndex
+
semanticIndexOffset
,
child:
child
);
}
if
(
addAutomaticKeepAlives
)
child
=
AutomaticKeepAlive
(
child:
child
);
return
child
;
...
...
@@ -297,6 +431,28 @@ class SliverChildBuilderDelegate extends SliverChildDelegate {
/// default) and in [RepaintBoundary] widgets if [addRepaintBoundaries] is true
/// (also the default).
///
/// ## Accessibility
///
/// The [CustomScrollView] requires that its semantic children are annotated
/// using [IndexedSemantics]. This is done by default in the delegate with
/// the `addSemanticIndexes` parameter set to true.
///
/// If multiple delegates are used in a single scroll view, then the indexes
/// will not be correct by default. The `semanticIndexOffset` can be used to
/// offset the semantic indexes of each delegate so that the indexes are
/// monotonically increasing. For example, if a scroll view contains two
/// delegates where the first has 10 children contributing semantics, then the
/// second delegate should offset its children by 10.
///
/// In certain cases, only a subset of child widgets should be annotated
/// with a semantic index. For example, in [new ListView.separated()] the
/// separators do not have an index assocaited with them. This is done by
/// providing a `semanticIndexCallback` which returns null for separators
/// indexes and rounds the non-separator indexes down by half.
///
/// See [SliverChildBuilderDelegate] for sample code using
/// `semanticIndexOffset` and `semanticIndexCallback`.
///
/// See also:
///
/// * [SliverChildBuilderDelegate], which is a delegate that uses a builder
...
...
@@ -311,6 +467,9 @@ class SliverChildListDelegate extends SliverChildDelegate {
this
.
children
,
{
this
.
addAutomaticKeepAlives
=
true
,
this
.
addRepaintBoundaries
=
true
,
this
.
addSemanticIndexes
=
true
,
this
.
semanticIndexCallback
=
_kDefaultSemanticIndexCallback
,
this
.
semanticIndexOffset
=
0
,
})
:
assert
(
children
!=
null
),
assert
(
addAutomaticKeepAlives
!=
null
),
assert
(
addRepaintBoundaries
!=
null
);
...
...
@@ -340,6 +499,27 @@ class SliverChildListDelegate extends SliverChildDelegate {
/// Defaults to true.
final
bool
addRepaintBoundaries
;
/// Whether to wrap each child in an [IndexedSemantics].
///
/// Typically, children in a scrolling container must be annotated with a
/// semantic index in order to generate the correct accessibility
/// announcements. This should only be set to false if the indexes have
/// already been provided by wrapping the correct child widgets in an
/// indexed child semantics widget.
///
/// Defaults to true.
final
bool
addSemanticIndexes
;
/// An initial offset to add to the semantic indexes generated by this widget.
///
/// Defaults to zero.
final
int
semanticIndexOffset
;
/// A [SemanticIndexCallback] which is used when [addSemanticIndexes] is true.
///
/// Defaults to providing an index for each widget.
final
SemanticIndexCallback
semanticIndexCallback
;
/// The widgets to display.
final
List
<
Widget
>
children
;
...
...
@@ -352,6 +532,11 @@ class SliverChildListDelegate extends SliverChildDelegate {
assert
(
child
!=
null
);
if
(
addRepaintBoundaries
)
child
=
RepaintBoundary
.
wrap
(
child
,
index
);
if
(
addSemanticIndexes
)
{
final
int
semanticIndex
=
semanticIndexCallback
(
child
,
index
);
if
(
semanticIndex
!=
null
)
child
=
IndexedSemantics
(
index:
semanticIndex
+
semanticIndexOffset
,
child:
child
);
}
if
(
addAutomaticKeepAlives
)
child
=
AutomaticKeepAlive
(
child:
child
);
return
child
;
...
...
packages/flutter/test/semantics/semantics_test.dart
View file @
fb7a5937
...
...
@@ -348,6 +348,8 @@ void main() {
' hint: ""
\n
'
' textDirection: null
\n
'
' sortKey: null
\n
'
' scrollChildren: null
\n
'
' scrollIndex: null
\n
'
' scrollExtentMin: null
\n
'
' scrollPosition: null
\n
'
' scrollExtentMax: null
\n
'
...
...
@@ -438,6 +440,8 @@ void main() {
' hint: ""
\n
'
' textDirection: null
\n
'
' sortKey: null
\n
'
' scrollChildren: null
\n
'
' scrollIndex: null
\n
'
' scrollExtentMin: null
\n
'
' scrollPosition: null
\n
'
' scrollExtentMax: null
\n
'
...
...
packages/flutter/test/widgets/automatic_keep_alive_test.dart
View file @
fb7a5937
...
...
@@ -70,6 +70,7 @@ void tests({ @required bool impliedMode }) {
child:
ListView
(
addAutomaticKeepAlives:
impliedMode
,
addRepaintBoundaries:
impliedMode
,
addSemanticIndexes:
false
,
itemExtent:
12.3
,
// about 50 widgets visible
cacheExtent:
0.0
,
children:
generateList
(
const
Placeholder
(),
impliedMode:
impliedMode
),
...
...
@@ -117,6 +118,7 @@ void tests({ @required bool impliedMode }) {
child:
ListView
(
addAutomaticKeepAlives:
impliedMode
,
addRepaintBoundaries:
impliedMode
,
addSemanticIndexes:
false
,
cacheExtent:
0.0
,
children:
generateList
(
Container
(
height:
12.3
,
child:
const
Placeholder
()),
// about 50 widgets visible
...
...
@@ -166,6 +168,7 @@ void tests({ @required bool impliedMode }) {
child:
GridView
.
count
(
addAutomaticKeepAlives:
impliedMode
,
addRepaintBoundaries:
impliedMode
,
addSemanticIndexes:
false
,
crossAxisCount:
2
,
childAspectRatio:
400.0
/
24.6
,
// about 50 widgets visible
cacheExtent:
0.0
,
...
...
@@ -222,6 +225,7 @@ void main() {
child:
ListView
(
addAutomaticKeepAlives:
false
,
addRepaintBoundaries:
false
,
addSemanticIndexes:
false
,
cacheExtent:
0.0
,
children:
<
Widget
>[
AutomaticKeepAlive
(
...
...
@@ -305,6 +309,7 @@ void main() {
child:
ListView
(
addAutomaticKeepAlives:
false
,
addRepaintBoundaries:
false
,
addSemanticIndexes:
false
,
cacheExtent:
0.0
,
children:
<
Widget
>[
AutomaticKeepAlive
(
...
...
@@ -360,6 +365,7 @@ void main() {
child:
ListView
(
addAutomaticKeepAlives:
false
,
addRepaintBoundaries:
false
,
addSemanticIndexes:
false
,
cacheExtent:
0.0
,
children:
<
Widget
>[
AutomaticKeepAlive
(
...
...
@@ -423,6 +429,7 @@ void main() {
child:
ListView
(
addAutomaticKeepAlives:
false
,
addRepaintBoundaries:
false
,
addSemanticIndexes:
false
,
cacheExtent:
0.0
,
children:
<
Widget
>[
AutomaticKeepAlive
(
...
...
@@ -468,6 +475,7 @@ void main() {
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
ListView
.
builder
(
addSemanticIndexes:
false
,
itemCount:
50
,
itemBuilder:
(
BuildContext
context
,
int
index
){
if
(
index
==
0
){
...
...
@@ -501,6 +509,7 @@ void main() {
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
ListView
.
builder
(
addSemanticIndexes:
false
,
itemCount:
250
,
itemBuilder:
(
BuildContext
context
,
int
index
){
if
(
index
%
2
==
0
){
...
...
packages/flutter/test/widgets/draggable_test.dart
View file @
fb7a5937
...
...
@@ -1658,6 +1658,7 @@ void main() {
await
tester
.
pumpWidget
(
MaterialApp
(
home:
ListView
(
scrollDirection:
Axis
.
horizontal
,
addSemanticIndexes:
false
,
children:
<
Widget
>[
DragTarget
<
int
>(
builder:
(
BuildContext
context
,
List
<
int
>
data
,
List
<
dynamic
>
rejects
)
{
...
...
packages/flutter/test/widgets/ensure_visible_test.dart
View file @
fb7a5937
...
...
@@ -48,6 +48,7 @@ Widget buildListView(Axis scrollDirection, { bool reverse = false, bool shrinkWr
child:
ListView
(
scrollDirection:
scrollDirection
,
reverse:
reverse
,
addSemanticIndexes:
false
,
shrinkWrap:
shrinkWrap
,
children:
<
Widget
>[
Container
(
key:
const
ValueKey
<
int
>(
0
),
width:
200.0
,
height:
200.0
),
...
...
packages/flutter/test/widgets/keep_alive_test.dart
View file @
fb7a5937
...
...
@@ -50,6 +50,7 @@ void main() {
cacheExtent:
0.0
,
addAutomaticKeepAlives:
false
,
addRepaintBoundaries:
false
,
addSemanticIndexes:
false
,
itemExtent:
12.3
,
// about 50 widgets visible
children:
generateList
(
const
Placeholder
()),
),
...
...
@@ -97,6 +98,7 @@ void main() {
cacheExtent:
0.0
,
addAutomaticKeepAlives:
false
,
addRepaintBoundaries:
false
,
addSemanticIndexes:
false
,
children:
generateList
(
Container
(
height:
12.3
,
child:
const
Placeholder
())),
// about 50 widgets visible
),
),
...
...
@@ -143,6 +145,7 @@ void main() {
cacheExtent:
0.0
,
addAutomaticKeepAlives:
false
,
addRepaintBoundaries:
false
,
addSemanticIndexes:
false
,
crossAxisCount:
2
,
childAspectRatio:
400.0
/
24.6
,
// about 50 widgets visible
children:
generateList
(
Container
(
child:
const
Placeholder
())),
...
...
@@ -190,6 +193,7 @@ void main() {
child:
ListView
(
addAutomaticKeepAlives:
false
,
addRepaintBoundaries:
false
,
addSemanticIndexes:
false
,
itemExtent:
400.0
,
// 2 visible children
children:
generateList
(
const
Placeholder
()),
),
...
...
packages/flutter/test/widgets/list_view_viewporting_test.dart
View file @
fb7a5937
...
...
@@ -308,6 +308,7 @@ void main() {
textDirection:
TextDirection
.
ltr
,
child:
ListView
(
addAutomaticKeepAlives:
false
,
addSemanticIndexes:
false
,
children:
<
Widget
>[
Container
(
height:
100.0
),
Container
(
height:
100.0
),
...
...
packages/flutter/test/widgets/scrollable_semantics_traversal_order_test.dart
View file @
fb7a5937
...
...
@@ -42,6 +42,7 @@ void main() {
data:
const
MediaQueryData
(),
child:
CustomScrollView
(
controller:
ScrollController
(
initialScrollOffset:
3000.0
),
semanticChildCount:
30
,
slivers:
<
Widget
>[
SliverList
(
delegate:
SliverChildListDelegate
(
listChildren
),
...
...
@@ -62,10 +63,15 @@ void main() {
TestSemantics
(
children:
<
TestSemantics
>[
TestSemantics
(
scrollIndex:
15
,
scrollChildren:
30
,
actions:
<
SemanticsAction
>[
SemanticsAction
.
scrollUp
,
SemanticsAction
.
scrollDown
,
],
children:
<
TestSemantics
>[
TestSemantics
(
flags:
<
SemanticsFlag
>[
SemanticsFlag
.
isHidden
],
children:
<
TestSemantics
>[
TestSemantics
(
flags:
<
SemanticsFlag
>[
SemanticsFlag
.
isHidden
],
...
...
@@ -77,6 +83,11 @@ void main() {
label:
'item 13b'
,
textDirection:
TextDirection
.
ltr
,
),
],
),
TestSemantics
(
flags:
<
SemanticsFlag
>[
SemanticsFlag
.
isHidden
],
children:
<
TestSemantics
>[
TestSemantics
(
flags:
<
SemanticsFlag
>[
SemanticsFlag
.
isHidden
],
label:
'Item 14a'
,
...
...
@@ -87,6 +98,10 @@ void main() {
label:
'item 14b'
,
textDirection:
TextDirection
.
ltr
,
),
]
),
TestSemantics
(
children:
<
TestSemantics
>[
TestSemantics
(
label:
'Item 15a'
,
textDirection:
TextDirection
.
ltr
,
...
...
@@ -95,6 +110,10 @@ void main() {
label:
'item 15b'
,
textDirection:
TextDirection
.
ltr
,
),
],
),
TestSemantics
(
children:
<
TestSemantics
>[
TestSemantics
(
label:
'Item 16a'
,
textDirection:
TextDirection
.
ltr
,
...
...
@@ -103,6 +122,10 @@ void main() {
label:
'item 16b'
,
textDirection:
TextDirection
.
ltr
,
),
],
),
TestSemantics
(
children:
<
TestSemantics
>[
TestSemantics
(
label:
'Item 17a'
,
textDirection:
TextDirection
.
ltr
,
...
...
@@ -111,6 +134,11 @@ void main() {
label:
'item 17b'
,
textDirection:
TextDirection
.
ltr
,
),
],
),
TestSemantics
(
flags:
<
SemanticsFlag
>[
SemanticsFlag
.
isHidden
],
children:
<
TestSemantics
>[
TestSemantics
(
flags:
<
SemanticsFlag
>[
SemanticsFlag
.
isHidden
],
label:
'Item 18a'
,
...
...
@@ -121,6 +149,11 @@ void main() {
label:
'item 18b'
,
textDirection:
TextDirection
.
ltr
,
),
],
),
TestSemantics
(
flags:
<
SemanticsFlag
>[
SemanticsFlag
.
isHidden
],
children:
<
TestSemantics
>[
TestSemantics
(
flags:
<
SemanticsFlag
>[
SemanticsFlag
.
isHidden
],
label:
'Item 19a'
,
...
...
@@ -139,6 +172,8 @@ void main() {
),
],
),
],
),
childOrder:
DebugSemanticsDumpOrder
.
traversalOrder
,
ignoreId:
true
,
ignoreTransform:
true
,
...
...
@@ -181,7 +216,7 @@ void main() {
slivers:
<
Widget
>[
SliverFixedExtentList
(
itemExtent:
200.0
,
delegate:
SliverChildListDelegate
(
listChildren
),
delegate:
SliverChildListDelegate
(
listChildren
,
addSemanticIndexes:
false
),
),
],
),
...
...
packages/flutter/test/widgets/semantics_tester.dart
View file @
fb7a5937
...
...
@@ -48,6 +48,8 @@ class TestSemantics {
this
.
transform
,
this
.
textSelection
,
this
.
children
=
const
<
TestSemantics
>[],
this
.
scrollIndex
,
this
.
scrollChildren
,
Iterable
<
SemanticsTag
>
tags
,
})
:
assert
(
flags
is
int
||
flags
is
List
<
SemanticsFlag
>),
assert
(
actions
is
int
||
actions
is
List
<
SemanticsAction
>),
...
...
@@ -73,6 +75,8 @@ class TestSemantics {
this
.
transform
,
this
.
textSelection
,
this
.
children
=
const
<
TestSemantics
>[],
this
.
scrollIndex
,
this
.
scrollChildren
,
Iterable
<
SemanticsTag
>
tags
,
})
:
id
=
0
,
assert
(
flags
is
int
||
flags
is
List
<
SemanticsFlag
>),
...
...
@@ -109,6 +113,8 @@ class TestSemantics {
Matrix4
transform
,
this
.
textSelection
,
this
.
children
=
const
<
TestSemantics
>[],
this
.
scrollIndex
,
this
.
scrollChildren
,
Iterable
<
SemanticsTag
>
tags
,
})
:
assert
(
flags
is
int
||
flags
is
List
<
SemanticsFlag
>),
assert
(
actions
is
int
||
actions
is
List
<
SemanticsAction
>),
...
...
@@ -200,6 +206,12 @@ class TestSemantics {
/// parent).
final
Matrix4
transform
;
/// The index of the first visible semantic node within a scrollable.
final
int
scrollIndex
;
/// The total number of semantic nodes within a scrollable.
final
int
scrollChildren
;
final
TextSelection
textSelection
;
static
Matrix4
_applyRootChildScale
(
Matrix4
transform
)
{
...
...
@@ -270,6 +282,12 @@ class TestSemantics {
if
(
textSelection
?.
baseOffset
!=
nodeData
.
textSelection
?.
baseOffset
||
textSelection
?.
extentOffset
!=
nodeData
.
textSelection
?.
extentOffset
)
{
return
fail
(
'expected node id
$id
to have textSelection [
${textSelection?.baseOffset}
,
${textSelection?.end}
] but found: [
${nodeData.textSelection?.baseOffset}
,
${nodeData.textSelection?.extentOffset}
].'
);
}
if
(
scrollIndex
!=
null
&&
scrollIndex
!=
nodeData
.
scrollIndex
)
{
return
fail
(
'expected node id
$id
to have scrollIndex
$scrollIndex
but found scrollIndex
${nodeData.scrollIndex}
.'
);
}
if
(
scrollChildren
!=
null
&&
scrollChildren
!=
nodeData
.
scrollChildCount
)
{
return
fail
(
'expected node id
$id
to have scrollIndex
$scrollChildren
but found scrollIndex
${nodeData.scrollChildCount}
.'
);
}
final
int
childrenCount
=
node
.
mergeAllDescendantsIntoThisNode
?
0
:
node
.
childrenCount
;
if
(
children
.
length
!=
childrenCount
)
return
fail
(
'expected node id
$id
to have
${children.length}
child
${ children.length == 1 ? "" : "ren" }
but found
$childrenCount
.'
);
...
...
@@ -322,6 +340,8 @@ class TestSemantics {
buf
.
writeln
(
'
$indent
textDirection:
$textDirection
,'
);
if
(
textSelection
?.
isValid
==
true
)
buf
.
writeln
(
'
$indent
textSelection:
\n
[
${textSelection.start}
,
${textSelection.end}
],'
);
if
(
scrollIndex
!=
null
)
buf
.
writeln
(
'
$indent
scrollIndex:
$scrollIndex
,'
);
if
(
rect
!=
null
)
buf
.
writeln
(
'
$indent
rect:
$rect
,'
);
if
(
transform
!=
null
)
...
...
packages/flutter/test/widgets/sliver_fill_viewport_test.dart
View file @
fb7a5937
...
...
@@ -17,7 +17,7 @@ void main() {
child:
CustomScrollView
(
slivers:
<
Widget
>[
SliverFillViewport
(
delegate:
SliverChildListDelegate
(
children
,
addAutomaticKeepAlives:
false
),
delegate:
SliverChildListDelegate
(
children
,
addAutomaticKeepAlives:
false
,
addSemanticIndexes:
false
),
),
],
),
...
...
packages/flutter_test/test/matchers_test.dart
View file @
fb7a5937
...
...
@@ -495,6 +495,8 @@ void main() {
textDirection:
TextDirection
.
ltr
,
rect:
Rect
.
fromLTRB
(
0.0
,
0.0
,
10.0
,
10.0
),
textSelection:
null
,
scrollIndex:
null
,
scrollChildCount:
null
,
scrollPosition:
null
,
scrollExtentMax:
null
,
scrollExtentMin:
null
,
...
...
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