mirror of
https://github.com/flutter/flutter
synced 2024-10-13 19:52:53 +00:00
Implement delayed key event synthesis support for Android (#59358)
This commit is contained in:
parent
91bdf15858
commit
c68758fab1
|
@ -467,6 +467,13 @@ class RawKeyUpEvent extends RawKeyEvent {
|
|||
}) : super(data: data, character: character);
|
||||
}
|
||||
|
||||
/// A callback type used by [RawKeyboard.keyEventHandler] to send key events to
|
||||
/// a handler that can determine if the key has been handled or not.
|
||||
///
|
||||
/// The handler should return true if the key has been handled, and false if the
|
||||
/// key was not handled. It must not return null.
|
||||
typedef RawKeyEventHandler = bool Function(RawKeyEvent event);
|
||||
|
||||
/// An interface for listening to raw key events.
|
||||
///
|
||||
/// Raw key events pass through as much information as possible from the
|
||||
|
@ -477,6 +484,9 @@ class RawKeyUpEvent extends RawKeyEvent {
|
|||
/// buttons that are represented as keys. Typically used by games and other apps
|
||||
/// that use keyboards for purposes other than text entry.
|
||||
///
|
||||
/// These key events are typically only key events generated by a hardware
|
||||
/// keyboard, and not those from software keyboards or input method editors.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [RawKeyDownEvent] and [RawKeyUpEvent], the classes used to describe
|
||||
|
@ -494,20 +504,61 @@ class RawKeyboard {
|
|||
|
||||
final List<ValueChanged<RawKeyEvent>> _listeners = <ValueChanged<RawKeyEvent>>[];
|
||||
|
||||
/// Calls the listener every time the user presses or releases a key.
|
||||
/// Register a listener that is called every time the user presses or releases
|
||||
/// a hardware keyboard key.
|
||||
///
|
||||
/// Since the listeners have no way to indicate what they did with the event,
|
||||
/// listeners are assumed to not handle the key event. These events will also
|
||||
/// be distributed to other listeners, and to the [keyEventHandler].
|
||||
///
|
||||
/// Most applications prefer to use the focus system (see [Focus] and
|
||||
/// [FocusManager]) to receive key events to the focused control instead of
|
||||
/// this kind of passive listener.
|
||||
///
|
||||
/// Listeners can be removed with [removeListener].
|
||||
void addListener(ValueChanged<RawKeyEvent> listener) {
|
||||
_listeners.add(listener);
|
||||
}
|
||||
|
||||
/// Stop calling the listener every time the user presses or releases a key.
|
||||
/// Stop calling the given listener every time the user presses or releases a
|
||||
/// hardware keyboard key.
|
||||
///
|
||||
/// Listeners can be added with [addListener].
|
||||
void removeListener(ValueChanged<RawKeyEvent> listener) {
|
||||
_listeners.remove(listener);
|
||||
}
|
||||
|
||||
/// A handler for hardware keyboard events that will stop propagation if the
|
||||
/// handler returns true.
|
||||
///
|
||||
/// Key events on the platform are given to Flutter to be handled by the
|
||||
/// engine. If they are not handled, then the platform will continue to
|
||||
/// distribute the keys (i.e. propagate them) to other (possibly non-Flutter)
|
||||
/// components in the application. The return value from this handler tells
|
||||
/// the platform to either stop propagation (by returning true: "event
|
||||
/// handled"), or pass the event on to other controls (false: "event not
|
||||
/// handled").
|
||||
///
|
||||
/// This handler is normally set by the [FocusManager] so that it can control
|
||||
/// the key event propagation to focused widgets.
|
||||
///
|
||||
/// Most applications can use the focus system (see [Focus] and
|
||||
/// [FocusManager]) to receive key events. If you are not using the
|
||||
/// [FocusManager] to manage focus, then to be able to stop propagation of the
|
||||
/// event by indicating that the event was handled, set this attribute to a
|
||||
/// [RawKeyEventHandler]. Otherwise, key events will be assumed to not have
|
||||
/// been handled by Flutter, and will also be sent to other (possibly
|
||||
/// non-Flutter) controls in the application.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [Focus.onKey], a [Focus] callback attribute that will be given key
|
||||
/// events distributed by the [FocusManager] based on the current primary
|
||||
/// focus.
|
||||
/// * [addListener], to add passive key event listeners that do not stop event
|
||||
/// propagation.
|
||||
RawKeyEventHandler keyEventHandler;
|
||||
|
||||
Future<dynamic> _handleKeyEvent(dynamic message) async {
|
||||
final RawKeyEvent event = RawKeyEvent.fromMessage(message as Map<String, dynamic>);
|
||||
if (event == null) {
|
||||
|
@ -534,14 +585,19 @@ class RawKeyboard {
|
|||
// Make sure that the modifiers reflect reality, in case a modifier key was
|
||||
// pressed/released while the app didn't have focus.
|
||||
_synchronizeModifiers(event);
|
||||
if (_listeners.isEmpty) {
|
||||
return;
|
||||
}
|
||||
// Send the event to passive listeners.
|
||||
for (final ValueChanged<RawKeyEvent> listener in List<ValueChanged<RawKeyEvent>>.from(_listeners)) {
|
||||
if (_listeners.contains(listener)) {
|
||||
listener(event);
|
||||
}
|
||||
}
|
||||
|
||||
// Send the key event to the keyEventHandler, then send the appropriate
|
||||
// response to the platform so that it can resolve the event's handling.
|
||||
// Defaults to false if keyEventHandler is null.
|
||||
final bool handled = keyEventHandler != null && keyEventHandler(event);
|
||||
assert(handled != null, 'keyEventHandler returned null, which is not allowed');
|
||||
return <String, dynamic>{ 'handled': handled };
|
||||
}
|
||||
|
||||
static final Map<_ModifierSidePair, Set<PhysicalKeyboardKey>> _modifierKeyMap = <_ModifierSidePair, Set<PhysicalKeyboardKey>>{
|
||||
|
|
|
@ -1416,7 +1416,7 @@ class FocusManager with DiagnosticableTreeMixin, ChangeNotifier {
|
|||
/// from the [WidgetsBinding] singleton).
|
||||
FocusManager() {
|
||||
rootScope._manager = this;
|
||||
RawKeyboard.instance.addListener(_handleRawKeyEvent);
|
||||
RawKeyboard.instance.keyEventHandler = _handleRawKeyEvent;
|
||||
GestureBinding.instance.pointerRouter.addGlobalRoute(_handlePointerEvent);
|
||||
}
|
||||
|
||||
|
@ -1605,7 +1605,7 @@ class FocusManager with DiagnosticableTreeMixin, ChangeNotifier {
|
|||
}
|
||||
}
|
||||
|
||||
void _handleRawKeyEvent(RawKeyEvent event) {
|
||||
bool _handleRawKeyEvent(RawKeyEvent event) {
|
||||
// Update highlightMode first, since things responding to the keys might
|
||||
// look at the highlight mode, and it should be accurate.
|
||||
_lastInteractionWasTouch = false;
|
||||
|
@ -1616,7 +1616,7 @@ class FocusManager with DiagnosticableTreeMixin, ChangeNotifier {
|
|||
// onKey on the way up, and if one responds that they handled it, stop.
|
||||
if (_primaryFocus == null) {
|
||||
assert(_focusDebug('No primary focus for key event, ignored: $event'));
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
bool handled = false;
|
||||
for (final FocusNode node in <FocusNode>[_primaryFocus, ..._primaryFocus.ancestors]) {
|
||||
|
@ -1629,6 +1629,7 @@ class FocusManager with DiagnosticableTreeMixin, ChangeNotifier {
|
|||
if (!handled) {
|
||||
assert(_focusDebug('Key event not handled by anyone: $event.'));
|
||||
}
|
||||
return handled;
|
||||
}
|
||||
|
||||
/// The node that currently has the primary focus.
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
class _ModifierCheck {
|
||||
|
@ -508,6 +509,48 @@ void main() {
|
|||
final RawKeyEventDataAndroid data = repeatCountEvent.data as RawKeyEventDataAndroid;
|
||||
expect(data.repeatCount, equals(42));
|
||||
});
|
||||
testWidgets('Key events are responded to correctly.', (WidgetTester tester) async {
|
||||
expect(RawKeyboard.instance.keysPressed, isEmpty);
|
||||
// Generate the data for a regular key down event.
|
||||
final Map<String, dynamic> data = KeyEventSimulator.getKeyData(
|
||||
LogicalKeyboardKey.keyA,
|
||||
platform: 'android',
|
||||
isDown: true,
|
||||
);
|
||||
Map<String, dynamic> message;
|
||||
await ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage(
|
||||
SystemChannels.keyEvent.name,
|
||||
SystemChannels.keyEvent.codec.encodeMessage(data),
|
||||
(ByteData data) {
|
||||
message = SystemChannels.keyEvent.codec.decodeMessage(data) as Map<String, dynamic>;
|
||||
},
|
||||
);
|
||||
expect(message, equals(<String, dynamic>{ 'handled': false }));
|
||||
|
||||
// Set up a widget that will receive focused text events.
|
||||
final FocusNode focusNode = FocusNode(debugLabel: 'Test Node');
|
||||
await tester.pumpWidget(
|
||||
Focus(
|
||||
focusNode: focusNode,
|
||||
onKey: (FocusNode node, RawKeyEvent event) {
|
||||
return true; // handle all events.
|
||||
},
|
||||
child: const SizedBox(),
|
||||
),
|
||||
);
|
||||
focusNode.requestFocus();
|
||||
await tester.pump();
|
||||
|
||||
await ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage(
|
||||
SystemChannels.keyEvent.name,
|
||||
SystemChannels.keyEvent.codec.encodeMessage(data),
|
||||
(ByteData data) {
|
||||
message = SystemChannels.keyEvent.codec.decodeMessage(data) as Map<String, dynamic>;
|
||||
},
|
||||
);
|
||||
expect(message, equals(<String, dynamic>{ 'handled': true }));
|
||||
ServicesBinding.instance.defaultBinaryMessenger.setMockMessageHandler(SystemChannels.keyEvent.name, null);
|
||||
});
|
||||
});
|
||||
group('RawKeyEventDataFuchsia', () {
|
||||
const Map<int, _ModifierCheck> modifierTests = <int, _ModifierCheck>{
|
||||
|
|
Loading…
Reference in a new issue