参考代码:
Flutter - Create Custom Keyboard Examples
本文贴出的代码实现了一个输入十六进制数据的键盘:
(1)支持长按退格键连续删除字符;
(2)可通过退格键删除选中的文字;
(3)监听文本变化(包括粘贴剪切导致的变化)。
hex_keyboard.dart
import 'dart:async';
import 'package:flutter/material.dart';
class HexKeyboard extends StatefulWidget {
final TextEditingController controller;
final void Function() onDone;
final void Function(String) onTextChanged;
const HexKeyboard({
super.key,
required this.controller,
required this.onDone,
required this.onTextChanged,
});
@override
State<HexKeyboard> createState() => _HexKeyboardState();
}
class _HexKeyboardState extends State<HexKeyboard> {
late TextEditingController _controller;
final Widget _horizontalPadding = const SizedBox(width: 1.0);
final Widget _verticalPadding = const SizedBox(height: 1.0);
Timer? _timer;
@override
void initState() {
super.initState();
_controller = widget.controller;
}
void _handleType(String text) {
int position = _controller.selection.base.offset;
var value = _controller.text;
if (value.isEmpty) {
_controller.text = text;
} else {
_controller.text = value.substring(0, position) +
text +
value.substring(position, value.length);
}
_controller.selection =
TextSelection.fromPosition(TextPosition(offset: position + 1));
widget.onTextChanged(_controller.text);
}
void _handleBackspace() {
final value = _controller.text;
if (value.isNotEmpty) {
final start = _controller.selection.start;
final end = _controller.selection.end;
print("selection.start=$start, selection.end=$end");
final int offset;
if(end > 0) {
if(start == end) {
_controller.text = value.substring(0, start - 1) +
value.substring(start, value.length);
offset = start - 1;
} else {
_controller.text = value.substring(0, start) +
value.substring(end, value.length);
offset = start;
}
_controller.selection =
TextSelection.fromPosition(TextPosition(offset: offset));
widget.onTextChanged(_controller.text);
}
}
}
Widget _buildButton(String text,
{VoidCallback? onPressed,
VoidCallback? onLongPressStart,
VoidCallback? onLongPressUp}) {
return Expanded(
child: Container(
color: Colors.white,
child: GestureDetector(
onLongPressStart: (e) {
onLongPressStart?.call();
},
onLongPressUp: onLongPressUp,
child: TextButton(
onPressed: onPressed ?? () => _handleType(text),
child: Text(
text,
style: const TextStyle(color: Colors.black, fontSize: 16),
),
),
),
),
);
}
@override
Widget build(BuildContext context) {
return _buildButtonKeyboard();
}
Widget _buildButtonRow(String key1, String key2, String key3) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_horizontalPadding,
_buildButton(key1),
_horizontalPadding,
_buildButton(key2),
_horizontalPadding,
_buildButton(key3),
_horizontalPadding,
],
);
}
Widget _buildButtonKeyboard() {
return Container(
color: const Color(0xffdddddd),
child: Column(
children: [
_verticalPadding,
_buildButtonRow('A', 'B', 'C'),
_verticalPadding,
_buildButtonRow('D', 'E', 'F'),
_verticalPadding,
_buildButtonRow('1', '2', '3'),
_verticalPadding,
_buildButtonRow('4', '5', '6'),
_verticalPadding,
_buildButtonRow('7', '8', '9'),
_verticalPadding,
Row(
children: [
_horizontalPadding,
_buildButton(
'⌫',
onPressed: _handleBackspace,
onLongPressStart: () {
_timer =
Timer.periodic(const Duration(milliseconds: 50), (timer) {
_handleBackspace();
});
},
onLongPressUp: () {
_timer?.cancel();
},
),
_horizontalPadding,
_buildButton('0'),
_horizontalPadding,
_buildButton(
'Done',
onPressed: widget.onDone,
),
_horizontalPadding,
],
),
_verticalPadding,
],
),
);
}
}
hex_input_screen.dart
import 'package:flutter/material.dart';
class HexInputScreen extends StatefulWidget {
final String text;
const HexInputScreen({super.key, required this.text});
@override
State<HexInputScreen> createState() => _HexInputScreenState();
}
class _HexInputScreenState extends State<HexInputScreen> {
late TextEditingController _controller;
final FocusNode _focus = FocusNode();
late ValueNotifier<bool> _focusValueNotifier;
int _byteCount = 0;
int _toByteCount(String hex) {
return hex.length % 2 == 0 ? hex.length ~/ 2 : hex.length ~/ 2 + 1;
}
void _onTextChanged(String text) {
//更新字节数
setState(() {
_byteCount = _toByteCount(text);
});
}
@override
void initState() {
_controller = TextEditingController(text: widget.text);
_focus.addListener(_handleFocusChange);
_focusValueNotifier = ValueNotifier<bool>(_focus.hasFocus);
_focus.requestFocus();
setState(() {
_byteCount = widget.text.length;
});
super.initState();
}
@override
void dispose() {
super.dispose();
_focus.removeListener(_handleFocusChange);
_focus.dispose();
}
void _handleFocusChange() {
_focusValueNotifier.value = _focus.hasFocus;
}
void _onDone() {
print(_controller.text);
Navigator.pop(context, _controller.text);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('HEX' /*, style: TextStyle(color: Colors.white)*/),
// backgroundColor: Colors.black,
),
body: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
const SizedBox(height: 10),
Text('已输入 $_byteCount 字节'),
Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
decoration: const InputDecoration(
border: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.grey,
width: 1,
),
),
),
controller: _controller,
keyboardType: TextInputType.none,
focusNode: _focus,
maxLines: 12,
maxLength: 1024,
onChanged: _onTextChanged,//这里监听粘贴剪切导致的变化
),
),
const Spacer(),
ListenableBuilder(
listenable: _focusValueNotifier,
builder: (BuildContext context, Widget? child) {
return _focus.hasFocus
? HexKeyboard(
controller: _controller,
onDone: _onDone,
onTextChanged: _onTextChanged,//这里监听自定义键盘导致的变化
)
: Container();
},
),
],
),
);
}
}