flutter/dev/manual_tests/lib/raw_keyboard.dart
Greg Spencer ef9c059ef2
Hook up character events and unmodified code points to Android raw key event handling. (#27853)
This makes Android raw key event handling use the "character" data coming from the engine properly, and gets rid of the "toLowerCase" hack that I was using to normalize logical key events, in favor of using the new "plainCodePoint" that has the unmodified code point (the code point as if no modifier keys were pressed).
2019-02-12 19:40:19 -08:00

122 lines
4.4 KiB
Dart

// Copyright 2016 The Chromium 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/material.dart';
import 'package:flutter/services.dart';
void main() {
runApp(MaterialApp(
title: 'Hardware Key Demo',
home: Scaffold(
appBar: AppBar(
title: const Text('Hardware Key Demo'),
),
body: const Center(
child: RawKeyboardDemo(),
),
),
));
}
class RawKeyboardDemo extends StatefulWidget {
const RawKeyboardDemo({Key key}) : super(key: key);
@override
_HardwareKeyDemoState createState() => _HardwareKeyDemoState();
}
class _HardwareKeyDemoState extends State<RawKeyboardDemo> {
final FocusNode _focusNode = FocusNode();
RawKeyEvent _event;
@override
void dispose() {
_focusNode.dispose();
super.dispose();
}
void _handleKeyEvent(RawKeyEvent event) {
setState(() {
_event = event;
});
}
String _asHex(int value) => value != null ? '0x${value.toRadixString(16)}' : 'null';
String _getEnumName(dynamic enumItem) {
final String name = '$enumItem';
final int index = name.indexOf('.');
return index == -1 ? name : name.substring(index + 1);
}
@override
Widget build(BuildContext context) {
final TextTheme textTheme = Theme.of(context).textTheme;
return RawKeyboardListener(
focusNode: _focusNode,
onKey: _handleKeyEvent,
child: AnimatedBuilder(
animation: _focusNode,
builder: (BuildContext context, Widget child) {
if (!_focusNode.hasFocus) {
return GestureDetector(
onTap: () {
FocusScope.of(context).requestFocus(_focusNode);
},
child: Text('Tap to focus', style: textTheme.display1),
);
}
if (_event == null) {
return Text('Press a key', style: textTheme.display1);
}
final RawKeyEventData data = _event.data;
final String modifierList = data.modifiersPressed.keys.map<String>(_getEnumName).join(', ').replaceAll('Modifier', '');
final List<Widget> dataText = <Widget>[
Text('${_event.runtimeType}'),
Text('modifiers set: $modifierList'),
];
if (data is RawKeyEventDataAndroid) {
const int combiningCharacterMask = 0x7fffffff;
final String codePointChar = String.fromCharCode(combiningCharacterMask & data.codePoint);
dataText.add(Text('codePoint: ${data.codePoint} (${_asHex(data.codePoint)}: $codePointChar)'));
final String plainCodePointChar = String.fromCharCode(combiningCharacterMask & data.plainCodePoint);
dataText.add(Text('plainCodePoint: ${data.plainCodePoint} (${_asHex(data.plainCodePoint)}: $plainCodePointChar)'));
dataText.add(Text('keyCode: ${data.keyCode} (${_asHex(data.keyCode)})'));
dataText.add(Text('scanCode: ${data.scanCode} (${_asHex(data.scanCode)})'));
dataText.add(Text('metaState: ${data.metaState} (${_asHex(data.metaState)})'));
dataText.add(Text('flags: ${data.flags} (${_asHex(data.flags)})'));
} else if (data is RawKeyEventDataFuchsia) {
dataText.add(Text('codePoint: ${data.codePoint} (${_asHex(data.codePoint)})'));
dataText.add(Text('hidUsage: ${data.hidUsage} (${_asHex(data.hidUsage)})'));
dataText.add(Text('modifiers: ${data.modifiers} (${_asHex(data.modifiers)})'));
}
dataText.add(Text('logical: ${_event.logicalKey}'));
dataText.add(Text('physical: ${_event.physicalKey}'));
if (_event.character != null) {
dataText.add(Text('character: ${_event.character}'));
}
for (ModifierKey modifier in data.modifiersPressed.keys) {
for (KeyboardSide side in KeyboardSide.values) {
if (data.isModifierPressed(modifier, side: side)) {
dataText.add(
Text('${_getEnumName(side)} ${_getEnumName(modifier).replaceAll('Modifier', '')} pressed'),
);
}
}
}
return DefaultTextStyle(
style: textTheme.headline,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: dataText,
),
);
},
),
);
}
}