blob: c3cb7d2351dc0507f3bc0c47d70de6b0e84d1604 [file] [log] [blame]
// Copyright 2014 The Flutter Authors. All rights reserved.
// 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/services.dart';
import 'focus_manager.dart';
import 'focus_scope.dart';
import 'framework.dart';
export 'package:flutter/services.dart' show RawKeyEvent;
/// A widget that calls a callback whenever the user presses or releases a key
/// on a keyboard.
///
/// A [RawKeyboardListener] is useful for listening to raw key events and
/// hardware buttons that are represented as keys. Typically used by games and
/// other apps that use keyboards for purposes other than text entry.
///
/// For text entry, consider using a [EditableText], which integrates with
/// on-screen keyboards and input method editors (IMEs).
///
/// The [RawKeyboardListener] is different from [KeyboardListener] in that
/// [RawKeyboardListener] uses the legacy [RawKeyboard] API. Use
/// [KeyboardListener] if possible.
///
/// See also:
///
/// * [EditableText], which should be used instead of this widget for text
/// entry.
/// * [KeyboardListener], a similar widget based on the newer
/// [HardwareKeyboard] API.
class RawKeyboardListener extends StatefulWidget {
/// Creates a widget that receives raw keyboard events.
///
/// For text entry, consider using a [EditableText], which integrates with
/// on-screen keyboards and input method editors (IMEs).
///
/// The [focusNode] and [child] arguments are required and must not be null.
///
/// The [autofocus] argument must not be null.
const RawKeyboardListener({
super.key,
required this.focusNode,
this.autofocus = false,
this.includeSemantics = true,
this.onKey,
required this.child,
}) : assert(focusNode != null),
assert(autofocus != null),
assert(includeSemantics != null),
assert(child != null);
/// Controls whether this widget has keyboard focus.
final FocusNode focusNode;
/// {@macro flutter.widgets.Focus.autofocus}
final bool autofocus;
/// {@macro flutter.widgets.Focus.includeSemantics}
final bool includeSemantics;
/// Called whenever this widget receives a raw keyboard event.
final ValueChanged<RawKeyEvent>? onKey;
/// The widget below this widget in the tree.
///
/// {@macro flutter.widgets.ProxyWidget.child}
final Widget child;
@override
State<RawKeyboardListener> createState() => _RawKeyboardListenerState();
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<FocusNode>('focusNode', focusNode));
}
}
class _RawKeyboardListenerState extends State<RawKeyboardListener> {
@override
void initState() {
super.initState();
widget.focusNode.addListener(_handleFocusChanged);
}
@override
void didUpdateWidget(RawKeyboardListener oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.focusNode != oldWidget.focusNode) {
oldWidget.focusNode.removeListener(_handleFocusChanged);
widget.focusNode.addListener(_handleFocusChanged);
}
}
@override
void dispose() {
widget.focusNode.removeListener(_handleFocusChanged);
_detachKeyboardIfAttached();
super.dispose();
}
void _handleFocusChanged() {
if (widget.focusNode.hasFocus) {
_attachKeyboardIfDetached();
} else {
_detachKeyboardIfAttached();
}
}
bool _listening = false;
void _attachKeyboardIfDetached() {
if (_listening) {
return;
}
RawKeyboard.instance.addListener(_handleRawKeyEvent);
_listening = true;
}
void _detachKeyboardIfAttached() {
if (!_listening) {
return;
}
RawKeyboard.instance.removeListener(_handleRawKeyEvent);
_listening = false;
}
void _handleRawKeyEvent(RawKeyEvent event) {
widget.onKey?.call(event);
}
@override
Widget build(BuildContext context) {
return Focus(
focusNode: widget.focusNode,
autofocus: widget.autofocus,
includeSemantics: widget.includeSemantics,
child: widget.child,
);
}
}