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 'basic.dart';
|
||||
import 'binding.dart';
|
||||
import 'focus_manager.dart';
|
||||
import 'framework.dart';
|
||||
import 'localizations.dart';
|
||||
import 'media_query.dart';
|
||||
@ -730,7 +731,33 @@ class _WidgetsAppState extends State<WidgetsApp> implements WidgetsBindingObserv
|
||||
}
|
||||
|
||||
@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
|
||||
void didHaveMemoryPressure() { }
|
||||
|
@ -417,6 +417,45 @@ class FocusManager {
|
||||
final FocusScopeNode rootScope = FocusScopeNode();
|
||||
|
||||
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) {
|
||||
assert(node != null);
|
||||
@ -434,16 +473,18 @@ class FocusManager {
|
||||
|
||||
FocusNode _findNextFocus() {
|
||||
FocusScopeNode scope = rootScope;
|
||||
while (scope._firstChild != null)
|
||||
while (scope._firstChild != null) {
|
||||
scope = scope._firstChild;
|
||||
}
|
||||
return scope._focus;
|
||||
}
|
||||
|
||||
void _update() {
|
||||
_haveScheduledUpdate = false;
|
||||
final FocusNode nextFocus = _findNextFocus();
|
||||
if (_currentFocus == nextFocus)
|
||||
if (_currentFocus == nextFocus) {
|
||||
return;
|
||||
}
|
||||
final FocusNode previousFocus = _currentFocus;
|
||||
_currentFocus = nextFocus;
|
||||
previousFocus?._notify();
|
||||
|
@ -2,6 +2,9 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// 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/widgets.dart';
|
||||
|
||||
@ -243,4 +246,29 @@ void main() {
|
||||
|
||||
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