drop/restore focus when app becomes invisible/visible (#24744)
This commit is contained in:
parent
ea7d086e11
commit
ff97e255b5
@ -12,6 +12,7 @@ import 'package:flutter/rendering.dart';
|
|||||||
import 'banner.dart';
|
import 'banner.dart';
|
||||||
import 'basic.dart';
|
import 'basic.dart';
|
||||||
import 'binding.dart';
|
import 'binding.dart';
|
||||||
|
import 'focus_manager.dart';
|
||||||
import 'framework.dart';
|
import 'framework.dart';
|
||||||
import 'localizations.dart';
|
import 'localizations.dart';
|
||||||
import 'media_query.dart';
|
import 'media_query.dart';
|
||||||
@ -730,7 +731,33 @@ class _WidgetsAppState extends State<WidgetsApp> implements WidgetsBindingObserv
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didChangeAppLifecycleState(AppLifecycleState state) { }
|
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||||
|
switch (defaultTargetPlatform) {
|
||||||
|
case TargetPlatform.iOS:
|
||||||
|
return;
|
||||||
|
case TargetPlatform.android:
|
||||||
|
case TargetPlatform.fuchsia:
|
||||||
|
// When the application moves to the background, any focus nodes
|
||||||
|
// need to lose focus. When the application is restored to the
|
||||||
|
// foreground state, any focused node should regain focus (including)
|
||||||
|
// restoring the keyboard. This is only important in cases where
|
||||||
|
// applications in the background are partially visible.
|
||||||
|
switch (state) {
|
||||||
|
case AppLifecycleState.paused:
|
||||||
|
case AppLifecycleState.suspending:
|
||||||
|
case AppLifecycleState.inactive: {
|
||||||
|
final FocusManager focusManager = WidgetsBinding.instance.focusManager;
|
||||||
|
focusManager.windowDidLoseFocus();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case AppLifecycleState.resumed: {
|
||||||
|
final FocusManager focusManager = WidgetsBinding.instance.focusManager;
|
||||||
|
focusManager.windowDidGainFocus();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didHaveMemoryPressure() { }
|
void didHaveMemoryPressure() { }
|
||||||
|
@ -417,6 +417,45 @@ class FocusManager {
|
|||||||
final FocusScopeNode rootScope = FocusScopeNode();
|
final FocusScopeNode rootScope = FocusScopeNode();
|
||||||
|
|
||||||
FocusNode _currentFocus;
|
FocusNode _currentFocus;
|
||||||
|
bool _windowHasFocus = true;
|
||||||
|
FocusNode _cachedFocus;
|
||||||
|
FocusScopeNode _cachedScope;
|
||||||
|
|
||||||
|
/// Call when the window loses focus.
|
||||||
|
///
|
||||||
|
/// The currently focused node and containg scope is cached, then the current
|
||||||
|
/// focus is removed. For example, this ensures that cursors do not blink
|
||||||
|
/// while in the background.
|
||||||
|
///
|
||||||
|
/// This method is safe to call multiple times.
|
||||||
|
void windowDidLoseFocus() {
|
||||||
|
if (!_windowHasFocus) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_windowHasFocus = false;
|
||||||
|
_cachedFocus = _currentFocus;
|
||||||
|
_cachedScope = _cachedFocus._parent;
|
||||||
|
_currentFocus?.unfocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Call when the window regains focus.
|
||||||
|
///
|
||||||
|
/// If there is a cached focus node from when the window lost focus, this
|
||||||
|
/// method will restore focus to that node. For example, this ensures that
|
||||||
|
/// a focused text node will re-request the keyboard.
|
||||||
|
///
|
||||||
|
/// This method is safe to call multiple times.
|
||||||
|
void windowDidGainFocus() {
|
||||||
|
if (_windowHasFocus) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_windowHasFocus = true;
|
||||||
|
if (_cachedFocus != null) {
|
||||||
|
_cachedScope._setFocus(_cachedFocus);
|
||||||
|
_cachedFocus = null;
|
||||||
|
_cachedScope = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _willDisposeFocusNode(FocusNode node) {
|
void _willDisposeFocusNode(FocusNode node) {
|
||||||
assert(node != null);
|
assert(node != null);
|
||||||
@ -434,16 +473,18 @@ class FocusManager {
|
|||||||
|
|
||||||
FocusNode _findNextFocus() {
|
FocusNode _findNextFocus() {
|
||||||
FocusScopeNode scope = rootScope;
|
FocusScopeNode scope = rootScope;
|
||||||
while (scope._firstChild != null)
|
while (scope._firstChild != null) {
|
||||||
scope = scope._firstChild;
|
scope = scope._firstChild;
|
||||||
|
}
|
||||||
return scope._focus;
|
return scope._focus;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _update() {
|
void _update() {
|
||||||
_haveScheduledUpdate = false;
|
_haveScheduledUpdate = false;
|
||||||
final FocusNode nextFocus = _findNextFocus();
|
final FocusNode nextFocus = _findNextFocus();
|
||||||
if (_currentFocus == nextFocus)
|
if (_currentFocus == nextFocus) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
final FocusNode previousFocus = _currentFocus;
|
final FocusNode previousFocus = _currentFocus;
|
||||||
_currentFocus = nextFocus;
|
_currentFocus = nextFocus;
|
||||||
previousFocus?._notify();
|
previousFocus?._notify();
|
||||||
|
@ -2,6 +2,9 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
@ -243,4 +246,29 @@ void main() {
|
|||||||
|
|
||||||
parentFocusScope.detach();
|
parentFocusScope.detach();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('Focus is lost/restored when window focus is lost/restored on Fuchsia', (WidgetTester tester) async {
|
||||||
|
debugDefaultTargetPlatformOverride = TargetPlatform.fuchsia;
|
||||||
|
final FocusNode node = FocusNode();
|
||||||
|
await tester.pumpWidget(MaterialApp(
|
||||||
|
home: Scaffold(
|
||||||
|
body: TextField(
|
||||||
|
focusNode: node,
|
||||||
|
autofocus: true,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
));
|
||||||
|
expect(node.hasFocus, true);
|
||||||
|
ByteData message = const StringCodec().encodeMessage('AppLifecycleState.inactive');
|
||||||
|
await BinaryMessages.handlePlatformMessage('flutter/lifecycle', message, (_) {});
|
||||||
|
await tester.pump();
|
||||||
|
// Focus is lost when app goes to background.
|
||||||
|
expect(node.hasFocus, false);
|
||||||
|
message = const StringCodec().encodeMessage('AppLifecycleState.resumed');
|
||||||
|
await BinaryMessages.handlePlatformMessage('flutter/lifecycle', message, (_) {});
|
||||||
|
await tester.pump();
|
||||||
|
// Focus is restored.
|
||||||
|
expect(node.hasFocus, true);
|
||||||
|
debugDefaultTargetPlatformOverride = null;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user