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
56a7c97f
Unverified
Commit
56a7c97f
authored
Jan 10, 2022
by
Viren Khatri
Committed by
GitHub
Jan 10, 2022
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Adds ability to mark a subtree as not traversable (#94626)
parent
23e7449a
Changes
8
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
479 additions
and
12 deletions
+479
-12
actions.dart
packages/flutter/lib/src/widgets/actions.dart
+5
-0
focus_manager.dart
packages/flutter/lib/src/widgets/focus_manager.dart
+56
-9
focus_scope.dart
packages/flutter/lib/src/widgets/focus_scope.dart
+49
-0
focus_traversal.dart
packages/flutter/lib/src/widgets/focus_traversal.dart
+73
-3
actions_test.dart
packages/flutter/test/widgets/actions_test.dart
+69
-0
focus_manager_test.dart
packages/flutter/test/widgets/focus_manager_test.dart
+35
-0
focus_scope_test.dart
packages/flutter/test/widgets/focus_scope_test.dart
+59
-0
focus_traversal_test.dart
packages/flutter/test/widgets/focus_traversal_test.dart
+133
-0
No files found.
packages/flutter/lib/src/widgets/actions.dart
View file @
56a7c97f
...
...
@@ -1064,6 +1064,7 @@ class FocusableActionDetector extends StatefulWidget {
this
.
focusNode
,
this
.
autofocus
=
false
,
this
.
descendantsAreFocusable
=
true
,
this
.
descendantsAreTraversable
=
true
,
this
.
shortcuts
,
this
.
actions
,
this
.
onShowFocusHighlight
,
...
...
@@ -1095,6 +1096,9 @@ class FocusableActionDetector extends StatefulWidget {
/// {@macro flutter.widgets.Focus.descendantsAreFocusable}
final
bool
descendantsAreFocusable
;
/// {@macro flutter.widgets.Focus.descendantsAreTraversable}
final
bool
descendantsAreTraversable
;
/// {@macro flutter.widgets.actions.actions}
final
Map
<
Type
,
Action
<
Intent
>>?
actions
;
...
...
@@ -1281,6 +1285,7 @@ class _FocusableActionDetectorState extends State<FocusableActionDetector> {
focusNode:
widget
.
focusNode
,
autofocus:
widget
.
autofocus
,
descendantsAreFocusable:
widget
.
descendantsAreFocusable
,
descendantsAreTraversable:
widget
.
descendantsAreTraversable
,
canRequestFocus:
_canRequestFocus
,
onFocusChange:
_handleFocusChange
,
child:
widget
.
child
,
...
...
packages/flutter/lib/src/widgets/focus_manager.dart
View file @
56a7c97f
...
...
@@ -409,12 +409,14 @@ class FocusNode with DiagnosticableTreeMixin, ChangeNotifier {
bool
skipTraversal
=
false
,
bool
canRequestFocus
=
true
,
bool
descendantsAreFocusable
=
true
,
bool
descendantsAreTraversable
=
true
,
})
:
assert
(
skipTraversal
!=
null
),
assert
(
canRequestFocus
!=
null
),
assert
(
descendantsAreFocusable
!=
null
),
_skipTraversal
=
skipTraversal
,
_canRequestFocus
=
canRequestFocus
,
_descendantsAreFocusable
=
descendantsAreFocusable
{
_descendantsAreFocusable
=
descendantsAreFocusable
,
_descendantsAreTraversable
=
descendantsAreTraversable
{
// Set it via the setter so that it does nothing on release builds.
this
.
debugLabel
=
debugLabel
;
}
...
...
@@ -429,7 +431,17 @@ class FocusNode with DiagnosticableTreeMixin, ChangeNotifier {
/// This is different from [canRequestFocus] because it only implies that the
/// node can't be reached via traversal, not that it can't be focused. It may
/// still be focused explicitly.
bool
get
skipTraversal
=>
_skipTraversal
;
bool
get
skipTraversal
{
if
(
_skipTraversal
)
{
return
true
;
}
for
(
final
FocusNode
ancestor
in
ancestors
)
{
if
(!
ancestor
.
descendantsAreTraversable
)
{
return
true
;
}
}
return
false
;
}
bool
_skipTraversal
;
set
skipTraversal
(
bool
value
)
{
if
(
value
!=
_skipTraversal
)
{
...
...
@@ -511,13 +523,17 @@ class FocusNode with DiagnosticableTreeMixin, ChangeNotifier {
///
/// See also:
///
/// * [ExcludeFocus], a widget that uses this property to conditionally
/// exclude focus for a subtree.
/// * [Focus], a widget that exposes this setting as a parameter.
/// * [FocusTraversalGroup], a widget used to group together and configure
/// the focus traversal policy for a widget subtree that also has an
/// `descendantsAreFocusable` parameter that prevents its children from
/// being focused.
/// * [ExcludeFocus], a widget that uses this property to conditionally
/// exclude focus for a subtree.
/// * [descendantsAreTraversable], which makes this widget's descendants
/// untraversable.
/// * [ExcludeFocusTraversal], a widget that conditionally excludes focus
/// traversal for a subtree.
/// * [Focus], a widget that exposes this setting as a parameter.
/// * [FocusTraversalGroup], a widget used to group together and configure
/// the focus traversal policy for a widget subtree that also has an
/// `descendantsAreFocusable` parameter that prevents its children from
/// being focused.
bool
get
descendantsAreFocusable
=>
_descendantsAreFocusable
;
bool
_descendantsAreFocusable
;
@mustCallSuper
...
...
@@ -534,6 +550,36 @@ class FocusNode with DiagnosticableTreeMixin, ChangeNotifier {
_manager
?.
_markPropertiesChanged
(
this
);
}
/// If false, tells the focus traversal policy to skip over for all of this
/// node's descendants for purposes of the traversal algorithm.
///
/// Defaults to true. Does not affect the focus traversal of this node: for
/// that, use [skipTraversal].
///
/// Does not affect the value of [FocusNode.skipTraversal] on the
/// descendants. Does not affect focusability of the descendants.
///
/// See also:
///
/// * [ExcludeFocusTraversal], a widget that uses this property to conditionally
/// exclude focus traversal for a subtree.
/// * [descendantsAreFocusable], which makes this widget's descendants
/// unfocusable.
/// * [ExcludeFocus], a widget that conditionally excludes focus for a subtree.
/// * [FocusTraversalGroup], a widget used to group together and configure
/// the focus traversal policy for a widget subtree that also has an
/// `descendantsAreFocusable` parameter that prevents its children from
/// being focused.
bool
get
descendantsAreTraversable
=>
_descendantsAreTraversable
;
bool
_descendantsAreTraversable
;
@mustCallSuper
set
descendantsAreTraversable
(
bool
value
)
{
if
(
value
!=
_descendantsAreTraversable
)
{
_descendantsAreTraversable
=
value
;
_manager
?.
_markPropertiesChanged
(
this
);
}
}
/// The context that was supplied to [attach].
///
/// This is typically the context for the widget that is being focused, as it
...
...
@@ -1105,6 +1151,7 @@ class FocusNode with DiagnosticableTreeMixin, ChangeNotifier {
super
.
debugFillProperties
(
properties
);
properties
.
add
(
DiagnosticsProperty
<
BuildContext
>(
'context'
,
context
,
defaultValue:
null
));
properties
.
add
(
FlagProperty
(
'descendantsAreFocusable'
,
value:
descendantsAreFocusable
,
ifFalse:
'DESCENDANTS UNFOCUSABLE'
,
defaultValue:
true
));
properties
.
add
(
FlagProperty
(
'descendantsAreTraversable'
,
value:
descendantsAreTraversable
,
ifFalse:
'DESCENDANTS UNTRAVERSABLE'
,
defaultValue:
true
));
properties
.
add
(
FlagProperty
(
'canRequestFocus'
,
value:
canRequestFocus
,
ifFalse:
'NOT FOCUSABLE'
,
defaultValue:
true
));
properties
.
add
(
FlagProperty
(
'hasFocus'
,
value:
hasFocus
&&
!
hasPrimaryFocus
,
ifTrue:
'IN FOCUS PATH'
,
defaultValue:
false
));
properties
.
add
(
FlagProperty
(
'hasPrimaryFocus'
,
value:
hasPrimaryFocus
,
ifTrue:
'PRIMARY FOCUS'
,
defaultValue:
false
));
...
...
packages/flutter/lib/src/widgets/focus_scope.dart
View file @
56a7c97f
...
...
@@ -124,6 +124,7 @@ class Focus extends StatefulWidget {
bool
?
canRequestFocus
,
bool
?
skipTraversal
,
bool
?
descendantsAreFocusable
,
bool
?
descendantsAreTraversable
,
this
.
includeSemantics
=
true
,
String
?
debugLabel
,
})
:
_onKeyEvent
=
onKeyEvent
,
...
...
@@ -131,6 +132,7 @@ class Focus extends StatefulWidget {
_canRequestFocus
=
canRequestFocus
,
_skipTraversal
=
skipTraversal
,
_descendantsAreFocusable
=
descendantsAreFocusable
,
_descendantsAreTraversable
=
descendantsAreTraversable
,
_debugLabel
=
debugLabel
,
assert
(
child
!=
null
),
assert
(
autofocus
!=
null
),
...
...
@@ -279,10 +281,17 @@ class Focus extends StatefulWidget {
/// Does not affect the value of [FocusNode.canRequestFocus] on the
/// descendants.
///
/// If a descendant node loses focus when this value is changed, the focus
/// will move to the scope enclosing this node.
///
/// See also:
///
/// * [ExcludeFocus], a widget that uses this property to conditionally
/// exclude focus for a subtree.
/// * [descendantsAreTraversable], which makes this widget's descendants
/// untraversable.
/// * [ExcludeFocusTraversal], a widget that conditionally excludes focus
/// traversal for a subtree.
/// * [FocusTraversalGroup], a widget used to group together and configure the
/// focus traversal policy for a widget subtree that has a
/// `descendantsAreFocusable` parameter to conditionally block focus for a
...
...
@@ -291,6 +300,30 @@ class Focus extends StatefulWidget {
bool
get
descendantsAreFocusable
=>
_descendantsAreFocusable
??
focusNode
?.
descendantsAreFocusable
??
true
;
final
bool
?
_descendantsAreFocusable
;
/// {@template flutter.widgets.Focus.descendantsAreTraversable}
/// If false, will make this widget's descendants untraversable.
///
/// Defaults to true. Does not affect traversablility of this node (just its
/// descendants): for that, use [FocusNode.skipTraversal].
///
/// Does not affect the value of [FocusNode.skipTraversal] on the
/// descendants. Does not affect focusability of the descendants.
///
/// See also:
///
/// * [ExcludeFocusTraversal], a widget that uses this property to
/// conditionally exclude focus traversal for a subtree.
/// * [descendantsAreFocusable], which makes this widget's descendants
/// unfocusable.
/// * [ExcludeFocus], a widget that conditionally excludes focus for a subtree.
/// * [FocusTraversalGroup], a widget used to group together and configure the
/// focus traversal policy for a widget subtree that has a
/// `descendantsAreFocusable` parameter to conditionally block focus for a
/// subtree.
/// {@endtemplate}
bool
get
descendantsAreTraversable
=>
_descendantsAreTraversable
??
focusNode
?.
descendantsAreTraversable
??
true
;
final
bool
?
_descendantsAreTraversable
;
/// {@template flutter.widgets.Focus.includeSemantics}
/// Include semantics information in this widget.
///
...
...
@@ -420,6 +453,7 @@ class Focus extends StatefulWidget {
properties
.
add
(
FlagProperty
(
'autofocus'
,
value:
autofocus
,
ifTrue:
'AUTOFOCUS'
,
defaultValue:
false
));
properties
.
add
(
FlagProperty
(
'canRequestFocus'
,
value:
canRequestFocus
,
ifFalse:
'NOT FOCUSABLE'
,
defaultValue:
false
));
properties
.
add
(
FlagProperty
(
'descendantsAreFocusable'
,
value:
descendantsAreFocusable
,
ifFalse:
'DESCENDANTS UNFOCUSABLE'
,
defaultValue:
true
));
properties
.
add
(
FlagProperty
(
'descendantsAreTraversable'
,
value:
descendantsAreTraversable
,
ifFalse:
'DESCENDANTS UNTRAVERSABLE'
,
defaultValue:
true
));
properties
.
add
(
DiagnosticsProperty
<
FocusNode
>(
'focusNode'
,
focusNode
,
defaultValue:
null
));
}
...
...
@@ -459,6 +493,8 @@ class _FocusWithExternalFocusNode extends Focus {
@override
bool
get
descendantsAreFocusable
=>
focusNode
!.
descendantsAreFocusable
;
@override
bool
?
get
_descendantsAreTraversable
=>
focusNode
!.
descendantsAreTraversable
;
@override
String
?
get
debugLabel
=>
focusNode
!.
debugLabel
;
}
...
...
@@ -468,6 +504,7 @@ class _FocusState extends State<Focus> {
late
bool
_hadPrimaryFocus
;
late
bool
_couldRequestFocus
;
late
bool
_descendantsWereFocusable
;
late
bool
_descendantsWereTraversable
;
bool
_didAutofocus
=
false
;
FocusAttachment
?
_focusAttachment
;
...
...
@@ -485,6 +522,7 @@ class _FocusState extends State<Focus> {
_internalNode
??=
_createNode
();
}
focusNode
.
descendantsAreFocusable
=
widget
.
descendantsAreFocusable
;
focusNode
.
descendantsAreTraversable
=
widget
.
descendantsAreTraversable
;
if
(
widget
.
skipTraversal
!=
null
)
{
focusNode
.
skipTraversal
=
widget
.
skipTraversal
;
}
...
...
@@ -493,6 +531,7 @@ class _FocusState extends State<Focus> {
}
_couldRequestFocus
=
focusNode
.
canRequestFocus
;
_descendantsWereFocusable
=
focusNode
.
descendantsAreFocusable
;
_descendantsWereTraversable
=
focusNode
.
descendantsAreTraversable
;
_hadPrimaryFocus
=
focusNode
.
hasPrimaryFocus
;
_focusAttachment
=
focusNode
.
attach
(
context
,
onKeyEvent:
widget
.
onKeyEvent
,
onKey:
widget
.
onKey
);
...
...
@@ -507,6 +546,7 @@ class _FocusState extends State<Focus> {
debugLabel:
widget
.
debugLabel
,
canRequestFocus:
widget
.
canRequestFocus
,
descendantsAreFocusable:
widget
.
descendantsAreFocusable
,
descendantsAreTraversable:
widget
.
descendantsAreTraversable
,
skipTraversal:
widget
.
skipTraversal
,
);
}
...
...
@@ -579,6 +619,7 @@ class _FocusState extends State<Focus> {
focusNode
.
canRequestFocus
=
widget
.
_canRequestFocus
!;
}
focusNode
.
descendantsAreFocusable
=
widget
.
descendantsAreFocusable
;
focusNode
.
descendantsAreTraversable
=
widget
.
descendantsAreTraversable
;
}
}
else
{
_focusAttachment
!.
detach
();
...
...
@@ -595,6 +636,7 @@ class _FocusState extends State<Focus> {
final
bool
hasPrimaryFocus
=
focusNode
.
hasPrimaryFocus
;
final
bool
canRequestFocus
=
focusNode
.
canRequestFocus
;
final
bool
descendantsAreFocusable
=
focusNode
.
descendantsAreFocusable
;
final
bool
descendantsAreTraversable
=
focusNode
.
descendantsAreTraversable
;
widget
.
onFocusChange
?.
call
(
focusNode
.
hasFocus
);
// Check the cached states that matter here, and call setState if they have
// changed.
...
...
@@ -613,6 +655,11 @@ class _FocusState extends State<Focus> {
_descendantsWereFocusable
=
descendantsAreFocusable
;
});
}
if
(
_descendantsWereTraversable
!=
descendantsAreTraversable
)
{
setState
(()
{
_descendantsWereTraversable
=
descendantsAreTraversable
;
});
}
}
@override
...
...
@@ -784,6 +831,8 @@ class _FocusScopeWithExternalFocusNode extends FocusScope {
@override
bool
get
descendantsAreFocusable
=>
focusNode
!.
descendantsAreFocusable
;
@override
bool
get
descendantsAreTraversable
=>
focusNode
!.
descendantsAreTraversable
;
@override
String
?
get
debugLabel
=>
focusNode
!.
debugLabel
;
}
...
...
packages/flutter/lib/src/widgets/focus_traversal.dart
View file @
56a7c97f
...
...
@@ -388,6 +388,11 @@ abstract class FocusTraversalPolicy with Diagnosticable {
}
}
final
List
<
FocusNode
>
sortedNodes
=
_sortAllDescendants
(
nearestScope
,
currentNode
);
if
(
sortedNodes
.
isEmpty
)
{
// If there are no nodes to traverse to, like when descendantsAreTraversable
// is false or skipTraversal for all the nodes is true.
return
false
;
}
if
(
forward
&&
focusedChild
==
sortedNodes
.
last
)
{
_focusAndEnsureVisible
(
sortedNodes
.
first
,
alignmentPolicy:
ScrollPositionAlignmentPolicy
.
keepVisibleAtEnd
);
return
true
;
...
...
@@ -1446,10 +1451,12 @@ class FocusTraversalGroup extends StatefulWidget {
Key
?
key
,
FocusTraversalPolicy
?
policy
,
this
.
descendantsAreFocusable
=
true
,
this
.
descendantsAreTraversable
=
true
,
required
this
.
child
,
})
:
assert
(
descendantsAreFocusable
!=
null
),
policy
=
policy
??
ReadingOrderTraversalPolicy
(),
super
(
key:
key
);
})
:
assert
(
descendantsAreFocusable
!=
null
),
assert
(
descendantsAreTraversable
!=
null
),
policy
=
policy
??
ReadingOrderTraversalPolicy
(),
super
(
key:
key
);
/// The policy used to move the focus from one focus node to another when
/// traversing them using a keyboard.
...
...
@@ -1471,6 +1478,9 @@ class FocusTraversalGroup extends StatefulWidget {
/// {@macro flutter.widgets.Focus.descendantsAreFocusable}
final
bool
descendantsAreFocusable
;
/// {@macro flutter.widgets.Focus.descendantsAreTraversable}
final
bool
descendantsAreTraversable
;
/// The child widget of this [FocusTraversalGroup].
///
/// {@macro flutter.widgets.ProxyWidget.child}
...
...
@@ -1573,6 +1583,7 @@ class _FocusTraversalGroupState extends State<FocusTraversalGroup> {
skipTraversal:
true
,
includeSemantics:
false
,
descendantsAreFocusable:
widget
.
descendantsAreFocusable
,
descendantsAreTraversable:
widget
.
descendantsAreTraversable
,
child:
widget
.
child
,
),
);
...
...
@@ -1737,3 +1748,62 @@ class DirectionalFocusAction extends Action<DirectionalFocusIntent> {
}
}
}
/// A widget that controls whether or not the descendants of this widget are
/// traversable.
///
/// Does not affect the value of [FocusNode.skipTraversal] of the descendants.
///
/// See also:
///
/// * [Focus], a widget for adding and managing a [FocusNode] in the widget tree.
/// * [ExcludeFocus], a widget that excludes its descendants from focusability.
/// * [FocusTraversalGroup], a widget that groups widgets for focus traversal,
/// and can also be used in the same way as this widget by setting its
/// `descendantsAreFocusable` attribute.
class
ExcludeFocusTraversal
extends
StatelessWidget
{
/// Const constructor for [ExcludeFocusTraversal] widget.
///
/// The [excluding] argument must not be null.
///
/// The [child] argument is required, and must not be null.
const
ExcludeFocusTraversal
({
Key
?
key
,
this
.
excluding
=
true
,
required
this
.
child
,
})
:
assert
(
excluding
!=
null
),
assert
(
child
!=
null
),
super
(
key:
key
);
/// If true, will make this widget's descendants untraversable.
///
/// Defaults to true.
///
/// Does not affect the value of [FocusNode.skipTraversal] on the descendants.
///
/// See also:
///
/// * [Focus.descendantsAreTraversable], the attribute of a [Focus] widget that
/// controls this same property for focus widgets.
/// * [FocusTraversalGroup], a widget used to group together and configure the
/// focus traversal policy for a widget subtree that has a
/// `descendantsAreFocusable` parameter to conditionally block focus for a
/// subtree.
final
bool
excluding
;
/// The child widget of this [ExcludeFocusTraversal].
///
/// {@macro flutter.widgets.ProxyWidget.child}
final
Widget
child
;
@override
Widget
build
(
BuildContext
context
)
{
return
Focus
(
canRequestFocus:
false
,
skipTraversal:
true
,
includeSemantics:
false
,
descendantsAreTraversable:
!
excluding
,
child:
child
,
);
}
}
packages/flutter/test/widgets/actions_test.dart
View file @
56a7c97f
...
...
@@ -962,6 +962,75 @@ void main() {
expect
(
buttonNode
.
hasFocus
,
isFalse
);
},
);
testWidgets
(
'FocusableActionDetector can prevent its descendants from being traversable'
,
(
WidgetTester
tester
)
async
{
final
FocusNode
buttonNode1
=
FocusNode
(
debugLabel:
'Button Node 1'
);
final
FocusNode
buttonNode2
=
FocusNode
(
debugLabel:
'Button Node 2'
);
await
tester
.
pumpWidget
(
MaterialApp
(
home:
FocusableActionDetector
(
child:
Column
(
children:
<
Widget
>[
MaterialButton
(
focusNode:
buttonNode1
,
child:
const
Text
(
'Node 1'
),
onPressed:
()
{},
),
MaterialButton
(
focusNode:
buttonNode2
,
child:
const
Text
(
'Node 2'
),
onPressed:
()
{},
),
],
),
),
),
);
buttonNode1
.
requestFocus
();
await
tester
.
pump
();
expect
(
buttonNode1
.
hasFocus
,
isTrue
);
expect
(
buttonNode2
.
hasFocus
,
isFalse
);
primaryFocus
!.
nextFocus
();
await
tester
.
pump
();
expect
(
buttonNode1
.
hasFocus
,
isFalse
);
expect
(
buttonNode2
.
hasFocus
,
isTrue
);
await
tester
.
pumpWidget
(
MaterialApp
(
home:
FocusableActionDetector
(
descendantsAreTraversable:
false
,
child:
Column
(
children:
<
Widget
>[
MaterialButton
(
focusNode:
buttonNode1
,
child:
const
Text
(
'Node 1'
),
onPressed:
()
{},
),
MaterialButton
(
focusNode:
buttonNode2
,
child:
const
Text
(
'Node 2'
),
onPressed:
()
{},
),
],
),
),
),
);
buttonNode1
.
requestFocus
();
await
tester
.
pump
();
expect
(
buttonNode1
.
hasFocus
,
isTrue
);
expect
(
buttonNode2
.
hasFocus
,
isFalse
);
primaryFocus
!.
nextFocus
();
await
tester
.
pump
();
expect
(
buttonNode1
.
hasFocus
,
isTrue
);
expect
(
buttonNode2
.
hasFocus
,
isFalse
);
},
);
});
group
(
'Diagnostics'
,
()
{
...
...
packages/flutter/test/widgets/focus_manager_test.dart
View file @
56a7c97f
...
...
@@ -151,6 +151,39 @@ void main() {
expect
(
scope
.
traversalDescendants
.
contains
(
child2
),
isFalse
);
});
testWidgets
(
'descendantsAreTraversable disables traversal for descendants.'
,
(
WidgetTester
tester
)
async
{
final
BuildContext
context
=
await
setupWidget
(
tester
);
final
FocusScopeNode
scope
=
FocusScopeNode
(
debugLabel:
'Scope'
);
final
FocusAttachment
scopeAttachment
=
scope
.
attach
(
context
);
final
FocusNode
parent1
=
FocusNode
(
debugLabel:
'Parent 1'
);
final
FocusAttachment
parent1Attachment
=
parent1
.
attach
(
context
);
final
FocusNode
parent2
=
FocusNode
(
debugLabel:
'Parent 2'
);
final
FocusAttachment
parent2Attachment
=
parent2
.
attach
(
context
);
final
FocusNode
child1
=
FocusNode
(
debugLabel:
'Child 1'
);
final
FocusAttachment
child1Attachment
=
child1
.
attach
(
context
);
final
FocusNode
child2
=
FocusNode
(
debugLabel:
'Child 2'
);
final
FocusAttachment
child2Attachment
=
child2
.
attach
(
context
);
scopeAttachment
.
reparent
(
parent:
tester
.
binding
.
focusManager
.
rootScope
);
parent1Attachment
.
reparent
(
parent:
scope
);
parent2Attachment
.
reparent
(
parent:
scope
);
child1Attachment
.
reparent
(
parent:
parent1
);
child2Attachment
.
reparent
(
parent:
parent2
);
expect
(
scope
.
traversalDescendants
,
equals
(<
FocusNode
>[
child1
,
parent1
,
child2
,
parent2
]));
parent2
.
descendantsAreTraversable
=
false
;
expect
(
scope
.
traversalDescendants
,
equals
(<
FocusNode
>[
child1
,
parent1
,
parent2
]));
parent1
.
descendantsAreTraversable
=
false
;
expect
(
scope
.
traversalDescendants
,
equals
(<
FocusNode
>[
parent1
,
parent2
]));
parent1
.
descendantsAreTraversable
=
true
;
parent2
.
descendantsAreTraversable
=
true
;
scope
.
descendantsAreTraversable
=
false
;
expect
(
scope
.
traversalDescendants
,
equals
(<
FocusNode
>[]));
});
testWidgets
(
"canRequestFocus doesn't affect traversalChildren"
,
(
WidgetTester
tester
)
async
{
final
BuildContext
context
=
await
setupWidget
(
tester
);
final
FocusScopeNode
scope
=
FocusScopeNode
(
debugLabel:
'Scope'
);
...
...
@@ -191,6 +224,7 @@ void main() {
expect
(
description
,
<
String
>[
'context: null'
,
'descendantsAreFocusable: true'
,
'descendantsAreTraversable: true'
,
'canRequestFocus: true'
,
'hasFocus: false'
,
'hasPrimaryFocus: false'
,
...
...
@@ -1156,6 +1190,7 @@ void main() {
expect
(
description
,
<
String
>[
'context: null'
,
'descendantsAreFocusable: true'
,
'descendantsAreTraversable: true'
,
'canRequestFocus: true'
,
'hasFocus: false'
,
'hasPrimaryFocus: false'
,
...
...
packages/flutter/test/widgets/focus_scope_test.dart
View file @
56a7c97f
...
...
@@ -1085,6 +1085,7 @@ void main() {
focusScopeNode
.
onKey
=
ignoreCallback
;
focusScopeNode
.
onKeyEvent
=
ignoreEventCallback
;
focusScopeNode
.
descendantsAreFocusable
=
false
;
focusScopeNode
.
descendantsAreTraversable
=
false
;
focusScopeNode
.
skipTraversal
=
false
;
focusScopeNode
.
canRequestFocus
=
true
;
FocusScope
focusScopeWidget
=
FocusScope
.
withExternalFocusNode
(
...
...
@@ -1095,11 +1096,13 @@ void main() {
expect
(
focusScopeNode
.
onKey
,
equals
(
ignoreCallback
));
expect
(
focusScopeNode
.
onKeyEvent
,
equals
(
ignoreEventCallback
));
expect
(
focusScopeNode
.
descendantsAreFocusable
,
isFalse
);
expect
(
focusScopeNode
.
descendantsAreTraversable
,
isFalse
);
expect
(
focusScopeNode
.
skipTraversal
,
isFalse
);
expect
(
focusScopeNode
.
canRequestFocus
,
isTrue
);
expect
(
focusScopeWidget
.
onKey
,
equals
(
focusScopeNode
.
onKey
));
expect
(
focusScopeWidget
.
onKeyEvent
,
equals
(
focusScopeNode
.
onKeyEvent
));
expect
(
focusScopeWidget
.
descendantsAreFocusable
,
equals
(
focusScopeNode
.
descendantsAreFocusable
));
expect
(
focusScopeWidget
.
descendantsAreTraversable
,
equals
(
focusScopeNode
.
descendantsAreTraversable
));
expect
(
focusScopeWidget
.
skipTraversal
,
equals
(
focusScopeNode
.
skipTraversal
));
expect
(
focusScopeWidget
.
canRequestFocus
,
equals
(
focusScopeNode
.
canRequestFocus
));
...
...
@@ -1111,6 +1114,7 @@ void main() {
focusScopeNode
.
onKey
=
handleCallback
;
focusScopeNode
.
onKeyEvent
=
handleEventCallback
;
focusScopeNode
.
descendantsAreFocusable
=
true
;
focusScopeNode
.
descendantsAreTraversable
=
true
;
focusScopeWidget
=
FocusScope
.
withExternalFocusNode
(
focusScopeNode:
focusScopeNode
,
child:
Container
(
key:
key1
),
...
...
@@ -1119,11 +1123,13 @@ void main() {
expect
(
focusScopeNode
.
onKey
,
equals
(
handleCallback
));
expect
(
focusScopeNode
.
onKeyEvent
,
equals
(
handleEventCallback
));
expect
(
focusScopeNode
.
descendantsAreFocusable
,
isTrue
);
expect
(
focusScopeNode
.
descendantsAreTraversable
,
isTrue
);
expect
(
focusScopeNode
.
skipTraversal
,
isFalse
);
expect
(
focusScopeNode
.
canRequestFocus
,
isTrue
);
expect
(
focusScopeWidget
.
onKey
,
equals
(
focusScopeNode
.
onKey
));
expect
(
focusScopeWidget
.
onKeyEvent
,
equals
(
focusScopeNode
.
onKeyEvent
));
expect
(
focusScopeWidget
.
descendantsAreFocusable
,
equals
(
focusScopeNode
.
descendantsAreFocusable
));
expect
(
focusScopeWidget
.
descendantsAreTraversable
,
equals
(
focusScopeNode
.
descendantsAreTraversable
));
expect
(
focusScopeWidget
.
skipTraversal
,
equals
(
focusScopeNode
.
skipTraversal
));
expect
(
focusScopeWidget
.
canRequestFocus
,
equals
(
focusScopeNode
.
canRequestFocus
));
...
...
@@ -1639,12 +1645,47 @@ void main() {
expect
(
containerNode
.
hasFocus
,
isFalse
);
expect
(
unfocusableNode
.
hasFocus
,
isFalse
);
});
testWidgets
(
'descendantsAreTraversable works as expected.'
,
(
WidgetTester
tester
)
async
{
final
FocusScopeNode
scopeNode
=
FocusScopeNode
(
debugLabel:
'scope'
);
final
FocusNode
node1
=
FocusNode
(
debugLabel:
'node 1'
);
final
FocusNode
node2
=
FocusNode
(
debugLabel:
'node 2'
);
final
FocusNode
node3
=
FocusNode
(
debugLabel:
'node 3'
);
await
tester
.
pumpWidget
(
FocusScope
(
node:
scopeNode
,
child:
Column
(
children:
<
Widget
>[
Focus
(
focusNode:
node1
,
child:
Container
(),
),
Focus
(
focusNode:
node2
,
descendantsAreTraversable:
false
,
child:
Focus
(
focusNode:
node3
,
child:
Container
(),
)
),
],
),
),
);
await
tester
.
pump
();
expect
(
scopeNode
.
traversalDescendants
,
equals
(<
FocusNode
>[
node1
,
node2
]));
expect
(
node2
.
traversalDescendants
,
equals
(<
FocusNode
>[]));
});
testWidgets
(
"Focus doesn't introduce a Semantics node when includeSemantics is false"
,
(
WidgetTester
tester
)
async
{
final
SemanticsTester
semantics
=
SemanticsTester
(
tester
);
await
tester
.
pumpWidget
(
Focus
(
includeSemantics:
false
,
child:
Container
()));
final
TestSemantics
expectedSemantics
=
TestSemantics
.
root
();
expect
(
semantics
,
hasSemantics
(
expectedSemantics
));
});
testWidgets
(
'Focus updates the onKey handler when the widget updates'
,
(
WidgetTester
tester
)
async
{
final
GlobalKey
key1
=
GlobalKey
(
debugLabel:
'1'
);
final
FocusNode
focusNode
=
FocusNode
();
...
...
@@ -1695,6 +1736,7 @@ void main() {
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
enter
);
expect
(
keyEventHandled
,
isTrue
);
});
testWidgets
(
'Focus updates the onKeyEvent handler when the widget updates'
,
(
WidgetTester
tester
)
async
{
final
GlobalKey
key1
=
GlobalKey
(
debugLabel:
'1'
);
final
FocusNode
focusNode
=
FocusNode
();
...
...
@@ -1745,6 +1787,7 @@ void main() {
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
enter
);
expect
(
keyEventHandled
,
isTrue
);
});
testWidgets
(
"Focus doesn't update the focusNode attributes when the widget updates if withExternalFocusNode is used"
,
(
WidgetTester
tester
)
async
{
final
GlobalKey
key1
=
GlobalKey
(
debugLabel:
'1'
);
final
FocusNode
focusNode
=
FocusNode
();
...
...
@@ -1766,6 +1809,7 @@ void main() {
focusNode
.
onKey
=
ignoreCallback
;
focusNode
.
onKeyEvent
=
ignoreEventCallback
;
focusNode
.
descendantsAreFocusable
=
false
;
focusNode
.
descendantsAreTraversable
=
false
;
focusNode
.
skipTraversal
=
false
;
focusNode
.
canRequestFocus
=
true
;
Focus
focusWidget
=
Focus
.
withExternalFocusNode
(
...
...
@@ -1776,11 +1820,13 @@ void main() {
expect
(
focusNode
.
onKey
,
equals
(
ignoreCallback
));
expect
(
focusNode
.
onKeyEvent
,
equals
(
ignoreEventCallback
));
expect
(
focusNode
.
descendantsAreFocusable
,
isFalse
);
expect
(
focusNode
.
descendantsAreTraversable
,
isFalse
);
expect
(
focusNode
.
skipTraversal
,
isFalse
);
expect
(
focusNode
.
canRequestFocus
,
isTrue
);
expect
(
focusWidget
.
onKey
,
equals
(
focusNode
.
onKey
));
expect
(
focusWidget
.
onKeyEvent
,
equals
(
focusNode
.
onKeyEvent
));
expect
(
focusWidget
.
descendantsAreFocusable
,
equals
(
focusNode
.
descendantsAreFocusable
));
expect
(
focusWidget
.
descendantsAreTraversable
,
equals
(
focusNode
.
descendantsAreTraversable
));
expect
(
focusWidget
.
skipTraversal
,
equals
(
focusNode
.
skipTraversal
));
expect
(
focusWidget
.
canRequestFocus
,
equals
(
focusNode
.
canRequestFocus
));
...
...
@@ -1792,6 +1838,7 @@ void main() {
focusNode
.
onKey
=
handleCallback
;
focusNode
.
onKeyEvent
=
handleEventCallback
;
focusNode
.
descendantsAreFocusable
=
true
;
focusNode
.
descendantsAreTraversable
=
true
;
focusWidget
=
Focus
.
withExternalFocusNode
(
focusNode:
focusNode
,
child:
Container
(
key:
key1
),
...
...
@@ -1800,18 +1847,29 @@ void main() {
expect
(
focusNode
.
onKey
,
equals
(
handleCallback
));
expect
(
focusNode
.
onKeyEvent
,
equals
(
handleEventCallback
));
expect
(
focusNode
.
descendantsAreFocusable
,
isTrue
);
expect
(
focusNode
.
descendantsAreTraversable
,
isTrue
);
expect
(
focusNode
.
skipTraversal
,
isFalse
);
expect
(
focusNode
.
canRequestFocus
,
isTrue
);
expect
(
focusWidget
.
onKey
,
equals
(
focusNode
.
onKey
));
expect
(
focusWidget
.
onKeyEvent
,
equals
(
focusNode
.
onKeyEvent
));
expect
(
focusWidget
.
descendantsAreFocusable
,
equals
(
focusNode
.
descendantsAreFocusable
));
expect
(
focusWidget
.
descendantsAreTraversable
,
equals
(
focusNode
.
descendantsAreTraversable
));
expect
(
focusWidget
.
skipTraversal
,
equals
(
focusNode
.
skipTraversal
));
expect
(
focusWidget
.
canRequestFocus
,
equals
(
focusNode
.
canRequestFocus
));
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
enter
);
expect
(
keyEventHandled
,
isTrue
);
});
testWidgets
(
'Focus passes changes in attribute values to its focus node'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
Focus
(
child:
Container
(),
),
);
});
});
group
(
'ExcludeFocus'
,
()
{
testWidgets
(
"Descendants of ExcludeFocus aren't focusable."
,
(
WidgetTester
tester
)
async
{
final
GlobalKey
key1
=
GlobalKey
(
debugLabel:
'1'
);
...
...
@@ -1919,6 +1977,7 @@ void main() {
expect
(
parentFocusNode
.
hasFocus
,
isFalse
);
expect
(
parentFocusNode
.
enclosingScope
!.
hasPrimaryFocus
,
isTrue
);
});
testWidgets
(
"ExcludeFocus doesn't introduce a Semantics node"
,
(
WidgetTester
tester
)
async
{
final
SemanticsTester
semantics
=
SemanticsTester
(
tester
);
await
tester
.
pumpWidget
(
ExcludeFocus
(
child:
Container
()));
...
...
packages/flutter/test/widgets/focus_traversal_test.dart
View file @
56a7c97f
...
...
@@ -2054,6 +2054,7 @@ void main() {
final
TestSemantics
expectedSemantics
=
TestSemantics
.
root
();
expect
(
semantics
,
hasSemantics
(
expectedSemantics
));
});
testWidgets
(
"Descendants of FocusTraversalGroup aren't focusable if descendantsAreFocusable is false."
,
(
WidgetTester
tester
)
async
{
final
GlobalKey
key1
=
GlobalKey
(
debugLabel:
'1'
);
final
GlobalKey
key2
=
GlobalKey
(
debugLabel:
'2'
);
...
...
@@ -2092,6 +2093,78 @@ void main() {
expect
(
containerNode
.
hasFocus
,
isFalse
);
expect
(
unfocusableNode
.
hasFocus
,
isFalse
);
});
testWidgets
(
"Descendants of FocusTraversalGroup aren't traversable if descendantsAreTraversable is false."
,
(
WidgetTester
tester
)
async
{
final
FocusNode
node1
=
FocusNode
();
final
FocusNode
node2
=
FocusNode
();
await
tester
.
pumpWidget
(
FocusTraversalGroup
(
descendantsAreTraversable:
false
,
child:
Column
(
children:
<
Widget
>[
Focus
(
focusNode:
node1
,
child:
Container
(),
),
Focus
(
focusNode:
node2
,
child:
Container
(),
),
],
),
),
);
node1
.
requestFocus
();
await
tester
.
pump
();
expect
(
node1
.
hasPrimaryFocus
,
isTrue
);
expect
(
node2
.
hasPrimaryFocus
,
isFalse
);
expect
(
primaryFocus
!.
nextFocus
(),
isFalse
);
await
tester
.
pump
();
expect
(
node1
.
hasPrimaryFocus
,
isTrue
);
expect
(
node2
.
hasPrimaryFocus
,
isFalse
);
});
testWidgets
(
"FocusTraversalGroup with skipTraversal for all descendents set to true doesn't cause an exception."
,
(
WidgetTester
tester
)
async
{
final
FocusNode
node1
=
FocusNode
();
final
FocusNode
node2
=
FocusNode
();
await
tester
.
pumpWidget
(
FocusTraversalGroup
(
child:
Column
(
children:
<
Widget
>[
Focus
(
skipTraversal:
true
,
focusNode:
node1
,
child:
Container
(),
),
Focus
(
skipTraversal:
true
,
focusNode:
node2
,
child:
Container
(),
),
],
),
),
);
node1
.
requestFocus
();
await
tester
.
pump
();
expect
(
node1
.
hasPrimaryFocus
,
isTrue
);
expect
(
node2
.
hasPrimaryFocus
,
isFalse
);
expect
(
primaryFocus
!.
nextFocus
(),
isFalse
);
await
tester
.
pump
();
expect
(
node1
.
hasPrimaryFocus
,
isTrue
);
expect
(
node2
.
hasPrimaryFocus
,
isFalse
);
});
testWidgets
(
"Nested FocusTraversalGroup with unfocusable children doesn't assert."
,
(
WidgetTester
tester
)
async
{
final
GlobalKey
key1
=
GlobalKey
(
debugLabel:
'1'
);
final
GlobalKey
key2
=
GlobalKey
(
debugLabel:
'2'
);
...
...
@@ -2140,6 +2213,7 @@ void main() {
expect
(
containerNode
.
hasFocus
,
isFalse
);
expect
(
unfocusableNode
.
hasFocus
,
isFalse
);
});
testWidgets
(
"Empty FocusTraversalGroup doesn't cause an exception."
,
(
WidgetTester
tester
)
async
{
final
GlobalKey
key
=
GlobalKey
(
debugLabel:
'Test Key'
);
final
FocusNode
focusNode
=
FocusNode
(
debugLabel:
'Test Node'
);
...
...
@@ -2169,6 +2243,7 @@ void main() {
expect
(
primaryFocus
,
equals
(
focusNode
));
});
});
group
(
RawKeyboardListener
,
()
{
testWidgets
(
'Raw keyboard listener introduces a Semantics node by default'
,
(
WidgetTester
tester
)
async
{
final
SemanticsTester
semantics
=
SemanticsTester
(
tester
);
...
...
@@ -2195,6 +2270,7 @@ void main() {
ignoreTransform:
true
,
));
});
testWidgets
(
"Raw keyboard listener doesn't introduce a Semantics node when specified"
,
(
WidgetTester
tester
)
async
{
final
SemanticsTester
semantics
=
SemanticsTester
(
tester
);
final
FocusNode
focusNode
=
FocusNode
();
...
...
@@ -2209,6 +2285,63 @@ void main() {
expect
(
semantics
,
hasSemantics
(
expectedSemantics
));
});
});
group
(
ExcludeFocusTraversal
,
()
{
testWidgets
(
"Descendants aren't traversable"
,
(
WidgetTester
tester
)
async
{
final
FocusNode
node1
=
FocusNode
(
debugLabel:
'node 1'
);
final
FocusNode
node2
=
FocusNode
(
debugLabel:
'node 2'
);
final
FocusNode
node3
=
FocusNode
(
debugLabel:
'node 3'
);
final
FocusNode
node4
=
FocusNode
(
debugLabel:
'node 4'
);
await
tester
.
pumpWidget
(
FocusTraversalGroup
(
child:
Column
(
children:
<
Widget
>[
Focus
(
autofocus:
true
,
focusNode:
node1
,
child:
Container
(),
),
ExcludeFocusTraversal
(
child:
Focus
(
focusNode:
node2
,
child:
Focus
(
focusNode:
node3
,
child:
Container
(),
),
),
),
Focus
(
focusNode:
node4
,
child:
Container
(),
),
],
),
),
);
await
tester
.
pump
();
expect
(
node1
.
hasPrimaryFocus
,
isTrue
);
expect
(
node2
.
hasPrimaryFocus
,
isFalse
);
expect
(
node3
.
hasPrimaryFocus
,
isFalse
);
expect
(
node4
.
hasPrimaryFocus
,
isFalse
);
node1
.
nextFocus
();
await
tester
.
pump
();
expect
(
node1
.
hasPrimaryFocus
,
isFalse
);
expect
(
node2
.
hasPrimaryFocus
,
isFalse
);
expect
(
node3
.
hasPrimaryFocus
,
isFalse
);
expect
(
node4
.
hasPrimaryFocus
,
isTrue
);
});
testWidgets
(
"Doesn't introduce a Semantics node"
,
(
WidgetTester
tester
)
async
{
final
SemanticsTester
semantics
=
SemanticsTester
(
tester
);
await
tester
.
pumpWidget
(
ExcludeFocusTraversal
(
child:
Container
()));
final
TestSemantics
expectedSemantics
=
TestSemantics
.
root
();
expect
(
semantics
,
hasSemantics
(
expectedSemantics
));
});
});
}
class
TestRoute
extends
PageRouteBuilder
<
void
>
{
...
...
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