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
81baee43
Unverified
Commit
81baee43
authored
Apr 06, 2022
by
Dwayne Slater
Committed by
GitHub
Apr 06, 2022
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Configurable padding around FocusNodes in Scrollables (#96815)
parent
82d4dbb6
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
482 additions
and
2 deletions
+482
-2
focus_manager.dart
packages/flutter/lib/src/widgets/focus_manager.dart
+24
-0
focus_traversal.dart
packages/flutter/lib/src/widgets/focus_traversal.dart
+1
-1
page_view.dart
packages/flutter/lib/src/widgets/page_view.dart
+1
-0
scroll_position.dart
packages/flutter/lib/src/widgets/scroll_position.dart
+12
-1
scrollable.dart
packages/flutter/lib/src/widgets/scrollable.dart
+11
-0
focus_traversal_test.dart
packages/flutter/test/widgets/focus_traversal_test.dart
+433
-0
No files found.
packages/flutter/lib/src/widgets/focus_manager.dart
View file @
81baee43
...
...
@@ -759,6 +759,16 @@ class FocusNode with DiagnosticableTreeMixin, ChangeNotifier {
return
null
;
}
/// Returns the amount of additional space to reveal around the attached widget
/// when focused inside a scrolling container via [Scrollable.ensureVisible].
///
/// For example, a value of `EdgeInsets.all(16.0)` ensures 16 pixels of
/// the adjacent widget are visible when this node receives focus.
///
/// By default, this returns [FocusManager.defaultEnsureVisiblePadding] from the
/// associated [FocusManager], or [EdgeInsets.zero].
EdgeInsets
get
ensureVisiblePadding
=>
_manager
?.
defaultEnsureVisiblePadding
??
EdgeInsets
.
zero
;
/// Returns the size of the attached widget's [RenderObject], in logical
/// units.
///
...
...
@@ -1710,6 +1720,20 @@ class FocusManager with DiagnosticableTreeMixin, ChangeNotifier {
return
handled
;
}
/// The default amount of additonal space to reveal when a widget is focused
/// inside a scrolling container via [Scrollable.ensureVisible].
///
/// Defaults to [EdgeInsets.zero], which does not add any additional space
/// when widgets are revealed.
///
/// For example, a value of `EdgeInsets.all(16.0)` ensures 16 pixels of
/// the adjacent widget are visible when focusing a widget inside of a
/// scrolling container.
///
/// Individual [FocusNode]s may increase or decrease this padding, use
/// [FocusNode.ensureVisiblePadding] to obtain a node's desired padding.
EdgeInsets
defaultEnsureVisiblePadding
=
EdgeInsets
.
zero
;
/// The node that currently has the primary focus.
FocusNode
?
get
primaryFocus
=>
_primaryFocus
;
FocusNode
?
_primaryFocus
;
...
...
packages/flutter/lib/src/widgets/focus_traversal.dart
View file @
81baee43
...
...
@@ -36,7 +36,7 @@ void _focusAndEnsureVisible(
ScrollPositionAlignmentPolicy
alignmentPolicy
=
ScrollPositionAlignmentPolicy
.
explicit
,
})
{
node
.
requestFocus
();
Scrollable
.
ensureVisible
(
node
.
context
!,
alignment:
1.0
,
alignmentPolicy:
alignmentPolicy
);
Scrollable
.
ensureVisible
(
node
.
context
!,
alignment:
1.0
,
padding:
node
.
ensureVisiblePadding
,
alignmentPolicy:
alignmentPolicy
);
}
// A class to temporarily hold information about FocusTraversalGroups when
...
...
packages/flutter/lib/src/widgets/page_view.dart
View file @
81baee43
...
...
@@ -346,6 +346,7 @@ class _PagePosition extends ScrollPositionWithSingleContext implements PageMetri
Future
<
void
>
ensureVisible
(
RenderObject
object
,
{
double
alignment
=
0.0
,
EdgeInsets
padding
=
EdgeInsets
.
zero
,
Duration
duration
=
Duration
.
zero
,
Curve
curve
=
Curves
.
ease
,
ScrollPositionAlignmentPolicy
alignmentPolicy
=
ScrollPositionAlignmentPolicy
.
explicit
,
...
...
packages/flutter/lib/src/widgets/scroll_position.dart
View file @
81baee43
...
...
@@ -676,6 +676,10 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics {
/// Animates the position such that the given object is as visible as possible
/// by just scrolling this position.
///
/// The [padding] is used to add extra space around the [object] when revealing it.
/// For example, `EdgeInsets.only(bottom: 16.0)` will ensure an additional 16 pixels
/// of space are visible below the [object].
///
/// The optional `targetRenderObject` parameter is used to determine which area
/// of that object should be as visible as possible. If `targetRenderObject`
/// is null, the entire [RenderObject] (as defined by its
...
...
@@ -686,9 +690,12 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics {
///
/// * [ScrollPositionAlignmentPolicy] for the way in which `alignment` is
/// applied, and the way the given `object` is aligned.
/// * [FocusNode.ensureVisiblePadding] which specifies the [padding] used when
/// a widget is focused via focus traversal.
Future
<
void
>
ensureVisible
(
RenderObject
object
,
{
double
alignment
=
0.0
,
EdgeInsets
padding
=
EdgeInsets
.
zero
,
Duration
duration
=
Duration
.
zero
,
Curve
curve
=
Curves
.
ease
,
ScrollPositionAlignmentPolicy
alignmentPolicy
=
ScrollPositionAlignmentPolicy
.
explicit
,
...
...
@@ -699,14 +706,18 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics {
final
RenderAbstractViewport
viewport
=
RenderAbstractViewport
.
of
(
object
)!;
assert
(
viewport
!=
null
);
Rect
?
targetRect
;
Rect
targetRect
;
if
(
targetRenderObject
!=
null
&&
targetRenderObject
!=
object
)
{
targetRect
=
MatrixUtils
.
transformRect
(
targetRenderObject
.
getTransformTo
(
object
),
object
.
paintBounds
.
intersect
(
targetRenderObject
.
paintBounds
),
);
}
else
{
targetRect
=
object
.
paintBounds
;
}
targetRect
=
padding
.
inflateRect
(
targetRect
);
double
target
;
switch
(
alignmentPolicy
)
{
case
ScrollPositionAlignmentPolicy
.
explicit
:
...
...
packages/flutter/lib/src/widgets/scrollable.dart
View file @
81baee43
...
...
@@ -311,9 +311,19 @@ class Scrollable extends StatefulWidget {
/// Scrolls the scrollables that enclose the given context so as to make the
/// given context visible.
///
/// The [padding] is used to add extra space around the [context]'s
/// associated widget when revealing it. For example, `EdgeInsets.only(bottom: 16.0)`
/// will ensure an additional 16 pixels of space are visible below the widget.
///
/// See also:
///
/// * [FocusNode.ensureVisiblePadding] which specifies the [padding] used when
/// a widget is focused via focus traversal.
static
Future
<
void
>
ensureVisible
(
BuildContext
context
,
{
double
alignment
=
0.0
,
EdgeInsets
padding
=
EdgeInsets
.
zero
,
Duration
duration
=
Duration
.
zero
,
Curve
curve
=
Curves
.
ease
,
ScrollPositionAlignmentPolicy
alignmentPolicy
=
ScrollPositionAlignmentPolicy
.
explicit
,
...
...
@@ -332,6 +342,7 @@ class Scrollable extends StatefulWidget {
futures
.
add
(
scrollable
.
position
.
ensureVisible
(
context
.
findRenderObject
()!,
alignment:
alignment
,
padding:
padding
,
duration:
duration
,
curve:
curve
,
alignmentPolicy:
alignmentPolicy
,
...
...
packages/flutter/test/widgets/focus_traversal_test.dart
View file @
81baee43
...
...
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'dart:math'
;
import
'dart:ui'
;
import
'package:flutter/material.dart'
;
...
...
@@ -1874,6 +1875,438 @@ void main() {
expect
(
controller
.
offset
,
equals
(
0.0
));
},
skip:
isBrowser
,
variant:
KeySimulatorTransitModeVariant
.
all
());
// https://github.com/flutter/flutter/issues/35347
testWidgets
(
'Focus traversal inside a vertical scrollable applies ensure visible padding.'
,
(
WidgetTester
tester
)
async
{
tester
.
binding
.
focusManager
.
defaultEnsureVisiblePadding
=
const
EdgeInsets
.
all
(
50.0
);
addTearDown
(()
{
tester
.
binding
.
focusManager
.
defaultEnsureVisiblePadding
=
EdgeInsets
.
zero
;
});
const
double
minScrollExtent
=
0.0
;
const
double
maxScrollExtent
=
700.0
;
final
List
<
int
>
items
=
List
<
int
>.
generate
(
11
,
(
int
index
)
=>
index
).
toList
();
final
List
<
FocusNode
>
nodes
=
List
<
FocusNode
>.
generate
(
11
,
(
int
index
)
=>
FocusNode
(
debugLabel:
'Item
${index + 1}
'
)).
toList
();
final
FocusNode
topNode
=
FocusNode
(
debugLabel:
'Header'
);
final
FocusNode
bottomNode
=
FocusNode
(
debugLabel:
'Footer'
);
final
ScrollController
controller
=
ScrollController
();
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Column
(
children:
<
Widget
>[
Focus
(
focusNode:
topNode
,
child:
Container
(
height:
100
)),
Expanded
(
child:
ListView
(
controller:
controller
,
children:
items
.
map
<
Widget
>((
int
item
)
{
return
Focus
(
focusNode:
nodes
[
item
],
child:
Container
(
height:
100
),
);
}).
toList
(),
),
),
Focus
(
focusNode:
bottomNode
,
child:
Container
(
height:
100
)),
],
),
),
);
// Start at the top
expect
(
controller
.
offset
,
equals
(
0.0
));
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowDown
);
await
tester
.
pump
();
expect
(
topNode
.
hasPrimaryFocus
,
isTrue
);
expect
(
controller
.
offset
,
equals
(
0.0
));
// Enter the list.
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowDown
);
await
tester
.
pump
();
expect
(
nodes
[
0
].
hasPrimaryFocus
,
isTrue
);
expect
(
controller
.
offset
,
equals
(
0.0
));
// Go down until we hit the bottom of the visible area, taking padding into account.
for
(
int
i
=
1
;
i
<=
2
;
++
i
)
{
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowDown
);
await
tester
.
pump
();
expect
(
controller
.
offset
,
equals
(
0.0
),
reason:
'Focusing item
$i
caused a scroll'
);
}
// Now keep going down, and the scrollable should scroll automatically.
for
(
int
i
=
3
;
i
<=
10
;
++
i
)
{
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowDown
);
await
tester
.
pump
();
final
double
expectedOffset
=
min
(
100.0
*
(
i
-
3
)
+
50.0
,
maxScrollExtent
);
expect
(
controller
.
offset
,
equals
(
expectedOffset
),
reason:
"Focusing item
$i
didn't cause a scroll to
$expectedOffset
"
);
}
// Now go one more, and see that the footer gets focused.
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowDown
);
await
tester
.
pump
();
expect
(
bottomNode
.
hasPrimaryFocus
,
isTrue
);
expect
(
controller
.
offset
,
equals
(
maxScrollExtent
));
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowUp
);
await
tester
.
pump
();
expect
(
nodes
[
10
].
hasPrimaryFocus
,
isTrue
);
expect
(
controller
.
offset
,
equals
(
maxScrollExtent
));
// Now reverse directions and go back to the top.
// These should not cause a scroll.
final
double
lowestOffset
=
controller
.
offset
;
for
(
int
i
=
10
;
i
>=
9
;
--
i
)
{
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowUp
);
await
tester
.
pump
();
expect
(
controller
.
offset
,
equals
(
lowestOffset
),
reason:
'Focusing item
$i
caused a scroll'
);
}
// These should all cause a scroll.
for
(
int
i
=
8
;
i
>=
1
;
--
i
)
{
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowUp
);
await
tester
.
pump
();
final
double
expectedOffset
=
max
(
100.0
*
(
i
-
1
)
-
50.0
,
minScrollExtent
);
expect
(
controller
.
offset
,
equals
(
expectedOffset
),
reason:
"Focusing item
$i
didn't cause a scroll"
);
}
// Back at the top.
expect
(
nodes
[
0
].
hasPrimaryFocus
,
isTrue
);
expect
(
controller
.
offset
,
equals
(
0.0
));
// Now we jump to the header.
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowUp
);
await
tester
.
pump
();
expect
(
topNode
.
hasPrimaryFocus
,
isTrue
);
expect
(
controller
.
offset
,
equals
(
0.0
));
},
skip:
isBrowser
,
variant:
KeySimulatorTransitModeVariant
.
all
());
// https://github.com/flutter/flutter/issues/35347
testWidgets
(
'Focus traversal inside a horizontal scrollable applies ensure visible padding.'
,
(
WidgetTester
tester
)
async
{
tester
.
binding
.
focusManager
.
defaultEnsureVisiblePadding
=
const
EdgeInsets
.
all
(
50.0
);
addTearDown
(()
{
tester
.
binding
.
focusManager
.
defaultEnsureVisiblePadding
=
EdgeInsets
.
zero
;
});
const
double
minScrollExtent
=
0.0
;
const
double
maxScrollExtent
=
500.0
;
final
List
<
int
>
items
=
List
<
int
>.
generate
(
11
,
(
int
index
)
=>
index
).
toList
();
final
List
<
FocusNode
>
nodes
=
List
<
FocusNode
>.
generate
(
11
,
(
int
index
)
=>
FocusNode
(
debugLabel:
'Item
${index + 1}
'
)).
toList
();
final
FocusNode
leftNode
=
FocusNode
(
debugLabel:
'Left Side'
);
final
FocusNode
rightNode
=
FocusNode
(
debugLabel:
'Right Side'
);
final
ScrollController
controller
=
ScrollController
();
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Row
(
children:
<
Widget
>[
Focus
(
focusNode:
leftNode
,
child:
Container
(
width:
100
)),
Expanded
(
child:
ListView
(
scrollDirection:
Axis
.
horizontal
,
controller:
controller
,
children:
items
.
map
<
Widget
>((
int
item
)
{
return
Focus
(
focusNode:
nodes
[
item
],
child:
Container
(
width:
100
),
);
}).
toList
(),
),
),
Focus
(
focusNode:
rightNode
,
child:
Container
(
width:
100
)),
],
),
),
);
// Start at the right
expect
(
controller
.
offset
,
equals
(
0.0
));
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowRight
);
await
tester
.
pump
();
expect
(
leftNode
.
hasPrimaryFocus
,
isTrue
);
expect
(
controller
.
offset
,
equals
(
0.0
));
// Enter the list.
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowRight
);
await
tester
.
pump
();
expect
(
nodes
[
0
].
hasPrimaryFocus
,
isTrue
);
expect
(
controller
.
offset
,
equals
(
0.0
));
// Go right until we hit the right of the visible area, taking padding into account.
for
(
int
i
=
1
;
i
<=
4
;
++
i
)
{
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowRight
);
await
tester
.
pump
();
expect
(
controller
.
offset
,
equals
(
0.0
),
reason:
'Focusing item
$i
caused a scroll'
);
}
// Now keep going right, and the scrollable should scroll automatically.
for
(
int
i
=
5
;
i
<=
10
;
++
i
)
{
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowRight
);
await
tester
.
pump
();
final
double
expectedOffset
=
min
(
100.0
*
(
i
-
5
)
+
50.0
,
maxScrollExtent
);
expect
(
controller
.
offset
,
equals
(
expectedOffset
),
reason:
"Focusing item
$i
didn't cause a scroll to
$expectedOffset
"
);
}
// Now go one more, and see that the right edge gets focused.
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowRight
);
await
tester
.
pump
();
expect
(
rightNode
.
hasPrimaryFocus
,
isTrue
);
expect
(
controller
.
offset
,
equals
(
maxScrollExtent
));
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowLeft
);
await
tester
.
pump
();
expect
(
nodes
[
10
].
hasPrimaryFocus
,
isTrue
);
expect
(
controller
.
offset
,
equals
(
maxScrollExtent
));
// Now reverse directions and go back to the left.
// These should not cause a scroll.
final
double
lowestOffset
=
controller
.
offset
;
for
(
int
i
=
10
;
i
>=
7
;
--
i
)
{
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowLeft
);
await
tester
.
pump
();
expect
(
controller
.
offset
,
equals
(
lowestOffset
),
reason:
'Focusing item
$i
caused a scroll'
);
}
// These should all cause a scroll.
for
(
int
i
=
6
;
i
>=
1
;
--
i
)
{
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowLeft
);
await
tester
.
pump
();
final
double
expectedOffset
=
max
(
100.0
*
(
i
-
1
)
-
50.0
,
minScrollExtent
);
expect
(
controller
.
offset
,
equals
(
expectedOffset
),
reason:
"Focusing item
$i
didn't cause a scroll"
);
}
// Back at the left side of the scrollable.
expect
(
nodes
[
0
].
hasPrimaryFocus
,
isTrue
);
expect
(
controller
.
offset
,
equals
(
0.0
));
// Now we jump to the left edge of the app.
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowLeft
);
await
tester
.
pump
();
expect
(
leftNode
.
hasPrimaryFocus
,
isTrue
);
expect
(
controller
.
offset
,
equals
(
0.0
));
},
skip:
isBrowser
,
variant:
KeySimulatorTransitModeVariant
.
all
());
// https://github.com/flutter/flutter/issues/35347
testWidgets
(
'Focus traversal inside a vertical scrollable applies asymmetric ensure visible padding.'
,
(
WidgetTester
tester
)
async
{
const
double
leadingPadding
=
25.0
;
const
double
trailingPadding
=
50.0
;
tester
.
binding
.
focusManager
.
defaultEnsureVisiblePadding
=
const
EdgeInsets
.
only
(
top:
leadingPadding
,
bottom:
trailingPadding
);
addTearDown
(()
{
tester
.
binding
.
focusManager
.
defaultEnsureVisiblePadding
=
EdgeInsets
.
zero
;
});
const
double
minScrollExtent
=
0.0
;
const
double
maxScrollExtent
=
700.0
;
final
List
<
int
>
items
=
List
<
int
>.
generate
(
11
,
(
int
index
)
=>
index
).
toList
();
final
List
<
FocusNode
>
nodes
=
List
<
FocusNode
>.
generate
(
11
,
(
int
index
)
=>
FocusNode
(
debugLabel:
'Item
${index + 1}
'
)).
toList
();
final
FocusNode
topNode
=
FocusNode
(
debugLabel:
'Header'
);
final
FocusNode
bottomNode
=
FocusNode
(
debugLabel:
'Footer'
);
final
ScrollController
controller
=
ScrollController
();
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Column
(
children:
<
Widget
>[
Focus
(
focusNode:
topNode
,
child:
Container
(
height:
100
)),
Expanded
(
child:
ListView
(
controller:
controller
,
children:
items
.
map
<
Widget
>((
int
item
)
{
return
Focus
(
focusNode:
nodes
[
item
],
child:
Container
(
height:
100
),
);
}).
toList
(),
),
),
Focus
(
focusNode:
bottomNode
,
child:
Container
(
height:
100
)),
],
),
),
);
// Start at the top
expect
(
controller
.
offset
,
equals
(
0.0
));
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowDown
);
await
tester
.
pump
();
expect
(
topNode
.
hasPrimaryFocus
,
isTrue
);
expect
(
controller
.
offset
,
equals
(
0.0
));
// Enter the list.
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowDown
);
await
tester
.
pump
();
expect
(
nodes
[
0
].
hasPrimaryFocus
,
isTrue
);
expect
(
controller
.
offset
,
equals
(
0.0
));
// Go down until we hit the bottom of the visible area, taking padding into account.
for
(
int
i
=
1
;
i
<=
2
;
++
i
)
{
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowDown
);
await
tester
.
pump
();
expect
(
controller
.
offset
,
equals
(
0.0
),
reason:
'Focusing item
$i
caused a scroll'
);
}
// Now keep going down, and the scrollable should scroll automatically.
for
(
int
i
=
3
;
i
<=
10
;
++
i
)
{
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowDown
);
await
tester
.
pump
();
final
double
expectedOffset
=
min
(
100.0
*
(
i
-
3
)
+
trailingPadding
,
maxScrollExtent
);
expect
(
controller
.
offset
,
equals
(
expectedOffset
),
reason:
"Focusing item
$i
didn't cause a scroll to
$expectedOffset
"
);
}
// Now go one more, and see that the footer gets focused.
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowDown
);
await
tester
.
pump
();
expect
(
bottomNode
.
hasPrimaryFocus
,
isTrue
);
expect
(
controller
.
offset
,
equals
(
maxScrollExtent
));
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowUp
);
await
tester
.
pump
();
expect
(
nodes
[
10
].
hasPrimaryFocus
,
isTrue
);
expect
(
controller
.
offset
,
equals
(
maxScrollExtent
));
// Now reverse directions and go back to the top.
// These should not cause a scroll.
final
double
lowestOffset
=
controller
.
offset
;
for
(
int
i
=
10
;
i
>=
9
;
--
i
)
{
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowUp
);
await
tester
.
pump
();
expect
(
controller
.
offset
,
equals
(
lowestOffset
),
reason:
'Focusing item
$i
caused a scroll'
);
}
// These should all cause a scroll.
for
(
int
i
=
8
;
i
>=
1
;
--
i
)
{
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowUp
);
await
tester
.
pump
();
final
double
expectedOffset
=
max
(
100.0
*
(
i
-
1
)
-
leadingPadding
,
minScrollExtent
);
expect
(
controller
.
offset
,
equals
(
expectedOffset
),
reason:
"Focusing item
$i
didn't cause a scroll"
);
}
// Back at the top.
expect
(
nodes
[
0
].
hasPrimaryFocus
,
isTrue
);
expect
(
controller
.
offset
,
equals
(
0.0
));
// Now we jump to the header.
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowUp
);
await
tester
.
pump
();
expect
(
topNode
.
hasPrimaryFocus
,
isTrue
);
expect
(
controller
.
offset
,
equals
(
0.0
));
},
skip:
isBrowser
,
variant:
KeySimulatorTransitModeVariant
.
all
());
// https://github.com/flutter/flutter/issues/35347
testWidgets
(
'Focus traversal inside a horizontal scrollable applies asymmetric ensure visible padding.'
,
(
WidgetTester
tester
)
async
{
const
double
leadingPadding
=
25.0
;
const
double
trailingPadding
=
50.0
;
tester
.
binding
.
focusManager
.
defaultEnsureVisiblePadding
=
const
EdgeInsets
.
only
(
left:
leadingPadding
,
right:
trailingPadding
);
addTearDown
(()
{
tester
.
binding
.
focusManager
.
defaultEnsureVisiblePadding
=
EdgeInsets
.
zero
;
});
const
double
minScrollExtent
=
0.0
;
const
double
maxScrollExtent
=
500.0
;
final
List
<
int
>
items
=
List
<
int
>.
generate
(
11
,
(
int
index
)
=>
index
).
toList
();
final
List
<
FocusNode
>
nodes
=
List
<
FocusNode
>.
generate
(
11
,
(
int
index
)
=>
FocusNode
(
debugLabel:
'Item
${index + 1}
'
)).
toList
();
final
FocusNode
leftNode
=
FocusNode
(
debugLabel:
'Left Side'
);
final
FocusNode
rightNode
=
FocusNode
(
debugLabel:
'Right Side'
);
final
ScrollController
controller
=
ScrollController
();
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Row
(
children:
<
Widget
>[
Focus
(
focusNode:
leftNode
,
child:
Container
(
width:
100
)),
Expanded
(
child:
ListView
(
scrollDirection:
Axis
.
horizontal
,
controller:
controller
,
children:
items
.
map
<
Widget
>((
int
item
)
{
return
Focus
(
focusNode:
nodes
[
item
],
child:
Container
(
width:
100
),
);
}).
toList
(),
),
),
Focus
(
focusNode:
rightNode
,
child:
Container
(
width:
100
)),
],
),
),
);
// Start at the right
expect
(
controller
.
offset
,
equals
(
0.0
));
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowRight
);
await
tester
.
pump
();
expect
(
leftNode
.
hasPrimaryFocus
,
isTrue
);
expect
(
controller
.
offset
,
equals
(
0.0
));
// Enter the list.
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowRight
);
await
tester
.
pump
();
expect
(
nodes
[
0
].
hasPrimaryFocus
,
isTrue
);
expect
(
controller
.
offset
,
equals
(
0.0
));
// Go right until we hit the right of the visible area, taking padding into account.
for
(
int
i
=
1
;
i
<=
4
;
++
i
)
{
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowRight
);
await
tester
.
pump
();
expect
(
controller
.
offset
,
equals
(
0.0
),
reason:
'Focusing item
$i
caused a scroll'
);
}
// Now keep going right, and the scrollable should scroll automatically.
for
(
int
i
=
5
;
i
<=
10
;
++
i
)
{
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowRight
);
await
tester
.
pump
();
final
double
expectedOffset
=
min
(
100.0
*
(
i
-
5
)
+
trailingPadding
,
maxScrollExtent
);
expect
(
controller
.
offset
,
equals
(
expectedOffset
),
reason:
"Focusing item
$i
didn't cause a scroll to
$expectedOffset
"
);
}
// Now go one more, and see that the right edge gets focused.
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowRight
);
await
tester
.
pump
();
expect
(
rightNode
.
hasPrimaryFocus
,
isTrue
);
expect
(
controller
.
offset
,
equals
(
maxScrollExtent
));
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowLeft
);
await
tester
.
pump
();
expect
(
nodes
[
10
].
hasPrimaryFocus
,
isTrue
);
expect
(
controller
.
offset
,
equals
(
maxScrollExtent
));
// Now reverse directions and go back to the left.
// These should not cause a scroll.
final
double
lowestOffset
=
controller
.
offset
;
for
(
int
i
=
10
;
i
>=
7
;
--
i
)
{
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowLeft
);
await
tester
.
pump
();
expect
(
controller
.
offset
,
equals
(
lowestOffset
),
reason:
'Focusing item
$i
caused a scroll'
);
}
// These should all cause a scroll.
for
(
int
i
=
6
;
i
>=
1
;
--
i
)
{
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowLeft
);
await
tester
.
pump
();
final
double
expectedOffset
=
max
(
100.0
*
(
i
-
1
)
-
leadingPadding
,
minScrollExtent
);
expect
(
controller
.
offset
,
equals
(
expectedOffset
),
reason:
"Focusing item
$i
didn't cause a scroll"
);
}
// Back at the left side of the scrollable.
expect
(
nodes
[
0
].
hasPrimaryFocus
,
isTrue
);
expect
(
controller
.
offset
,
equals
(
0.0
));
// Now we jump to the left edge of the app.
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowLeft
);
await
tester
.
pump
();
expect
(
leftNode
.
hasPrimaryFocus
,
isTrue
);
expect
(
controller
.
offset
,
equals
(
0.0
));
},
skip:
isBrowser
,
variant:
KeySimulatorTransitModeVariant
.
all
());
// https://github.com/flutter/flutter/issues/35347
testWidgets
(
'Arrow focus traversal actions can be re-enabled for text fields.'
,
(
WidgetTester
tester
)
async
{
final
GlobalKey
upperLeftKey
=
GlobalKey
(
debugLabel:
'upperLeftKey'
);
final
GlobalKey
upperRightKey
=
GlobalKey
(
debugLabel:
'upperRightKey'
);
...
...
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