Fix FocusTraversalPolicy makes focus lost (#34153) (#34712)

FocusTraversalPolicy keep the previously visited node to avoid hysteresis. But even if the visited focus has been disposed, FocusTraversalPolicy will still use it to requestFocus, which will cause FocusManger to get an abandoned node to get the focus.
This commit is contained in:
伯言 2019-07-18 01:00:06 +08:00 committed by Greg Spencer
parent 7992e3242d
commit b2cc97013d
2 changed files with 69 additions and 0 deletions

View File

@ -311,6 +311,15 @@ mixin DirectionalFocusTraversalPolicyMixin on FocusTraversalPolicy {
bool _popPolicyDataIfNeeded(TraversalDirection direction, FocusScopeNode nearestScope, FocusNode focusedChild) {
final _DirectionalPolicyData policyData = _policyData[nearestScope];
if (policyData != null && policyData.history.isNotEmpty && policyData.history.first.direction != direction) {
if (policyData.history.last.node.parent == null) {
// If a node has been removed from the tree, then we should stop
// referencing it and reset the scope data so that we don't try and
// request focus on it. This can happen in slivers where the rendered node
// has been unmounted. This has the side effect that hysteresis might not
// be avoided when items that go off screen get unmounted.
invalidateScopeData(nearestScope);
return false;
}
switch (direction) {
case TraversalDirection.down:
case TraversalDirection.up:

View File

@ -799,5 +799,65 @@ void main() {
expect(policy.findFirstFocusInDirection(scope, TraversalDirection.left), equals(upperRightNode));
expect(policy.findFirstFocusInDirection(scope, TraversalDirection.right), equals(upperLeftNode));
});
testWidgets('Can find focus when policy data dirty', (WidgetTester tester) async {
final FocusNode focusTop = FocusNode(debugLabel: 'top');
final FocusNode focusCenter = FocusNode(debugLabel: 'center');
final FocusNode focusBottom = FocusNode(debugLabel: 'bottom');
final FocusTraversalPolicy policy = ReadingOrderTraversalPolicy();
await tester.pumpWidget(DefaultFocusTraversal(
policy: policy,
child: FocusScope(
debugLabel: 'Scope',
child: Column(
children: <Widget>[
Focus(
focusNode: focusTop,
child: Container(width: 100, height: 100)),
Focus(
focusNode: focusCenter,
child: Container(width: 100, height: 100)),
Focus(
focusNode: focusBottom,
child: Container(width: 100, height: 100)),
],
),
),
));
focusTop.requestFocus();
final FocusNode scope = focusTop.enclosingScope;
scope.focusInDirection(TraversalDirection.down);
scope.focusInDirection(TraversalDirection.down);
await tester.pump();
expect(focusBottom.hasFocus, isTrue);
// Remove center focus node.
await tester.pumpWidget(DefaultFocusTraversal(
policy: policy,
child: FocusScope(
debugLabel: 'Scope',
child: Column(
children: <Widget>[
Focus(
focusNode: focusTop,
child: Container(width: 100, height: 100)),
Focus(
focusNode: focusBottom,
child: Container(width: 100, height: 100)),
],
),
),
));
expect(focusBottom.hasFocus, isTrue);
scope.focusInDirection(TraversalDirection.up);
await tester.pump();
expect(focusCenter.hasFocus, isFalse);
expect(focusTop.hasFocus, isTrue);
});
});
}