详解Flutter自定义应用程序内键盘的实现方法
目录
- 创建关键小部件
- 文本键
- Backspace键
- 将按键组成键盘
- 在应用程序中使用键盘
- 处理文本输入
- 处理退格
- 防止系统键盘显示
- 在系统键盘和自定义键盘之间切换
- 完整代码
本文将向您展示如何创建自定义键盘小部件,用于在您自己的应用程序中的Flutter TextField中输入文本。使用案例包括特殊字符或语言的文本输入,其中系统键盘可能不存在或用户可能没有安装正确的键盘。
我们今天将制作一个更简单的版本:
注意 :本文不会告诉您如何构建用户在任何应用程序中安装和使用的系统键盘。这只是一种基于小部件的方法,可以在您自己的应用程序中使用。
完整的代码在文章的底部。
创建关键小部件
Flutter的优点是,通过组合更简单的小部件,可以轻松构建键盘等复杂布局。首先,您将创建几个简单的按键小部件。
文本键
我已经圈出了由您首先制作的TextKey
小部件制作的键。
显示文本键(包括空格键)的自定义写意红色圆圈
将以下TextKey
小部件添加到您的项目中:
class TextKey extends StatelessWidget { const TextKey({ Key key, @required this.text, this.onTextInput, this.flex = 1, }) : super(key: key); final String text; final ValueSetter<String> onTextInput; final int flex; @override Widget build(BuildContext context) { return Expanded( flex: flex, child: Padding( padding: const EdgeInsets.all(1.0), child: Material( color: Colors.blue.shade300, child: InkWell( onTap: () { onTextInput?.call(text); }, child: Container( child: Center(child: Text(text)), ), ), ), ), ); } }
以下是有趣的部分:
flex
属性允许您的按键均匀分布在一行之间,甚至占据行的更大比例(如上图中的空格键)。- 按下按键后,它将以anonTextInput回调的形式将其值传递给键盘。
Backspace键
您还需要一个与TextKey
小部件具有不同外观和功能的退格键。
退格键
将以下小部件添加到您的项目中:
class BackspaceKey extends StatelessWidget { const BackspaceKey({ Key? key, this.onBackspace, this.flex = 1, }) : super(key: key); final VoidCallback? onBackspace; final int flex; @override Widget build(BuildContext context) { return Expanded( flex: flex, child: Padding( padding: const EdgeInsets.all(1.0), child: Material( color: Colors.blue.shade300, child: InkWell( onTap: () { onBackspace?.call(); }, child: Container( child: Center( child: Icon(Icons.backspace), ), ), ), ), ), ); }
备注:
TextKey
代码有点重复,因此一些重构是为了使其更加简介。onBackspace
是VoidCallback
,因为不需要将任何文本传递回键盘。
将按键组成键盘
一旦有了按键,键盘就很容易布局,因为它们只是列中的行。
包含三行的列
这是代码。我省略了一些重复的部分,以便简洁。不过,你可以在文章的末尾找到它。
class CustomKeyboard extends StatelessWidget { CustomKeyboard({ Key? key, this.onTextInput, this.onBackspace, }) : super(key: key); final ValueSetter<String>? onTextInput; final VoidCallback? onBackspace; void _textInputHandler(String text) => onTextInput?.call(text); void _backspaceHandler() => onBackspace?.call(); @override Widget build(BuildContext context) { return Container( height: 160, color: Colors.blue, child: Column( children: [ buildRowOne(), buildRowTwo(), buildRowThree(), buildRowFour(), buildRowFive() ], ), ); } Expanded buildRowOne() { return Expanded( child: Row( children: [ TextKey( text: '坚', onTextInput: _textInputHandler, ), TextKey( text: '果', onTextInput: _textInputHandler, ), TextKey( text: '祝', onTextInput: _textInputHandler, ), ], ), ); } Expanded buildRowTwo() { return Expanded( child: Row( children: [ TextKey( text: 'I', onTextInput: _textInputHandler, ), TextKey( text: 'n', onTextInput: _textInputHandler, ), TextKey( text: 'f', onTextInput: _textInputHandler, ), TextKey( text: 'o', onTextInput: _textInputHandler, ), TextKey( text: 'Q', onTextInput: _textInputHandler, ), ], ), ); } Expanded buildRowThree() { return Expanded( child: Row( children: [ TextKey( text: '十', onTextInput: _textInputHandler, ), TextKey( text: '五', onTextInput: _textInputHandler, ), TextKey( text: '周', onTextInput: _textInputHandler, ), TextKey( text: '年', onTextInput: _textInputHandler, ), ], ), ); } Expanded buildRowFour() { return Expanded( child: Row( children: [ TextKey( text: '生', onTextInput: _textInputHandler, ), TextKey( text: '日', onTextInput: _textInputHandler, ), TextKey( text: '快', onTextInput: _textInputHandler, ), TextKey( text: '乐', onTextInput: _textInputHandler, ), TextKey( text: '!', onTextInput: _textInputHandler, ), ], ), ); } Expanded buildRowFive() { return Expanded( child: Row( children: [ TextKey( text: ' ', flex: 2, onTextInput: _textInputHandler, ), TextKey( text: ' ', flex: 2, onTextInput: _textInputHandler, ), TextKey( text: '', flex: 2, onTextInput: _textInputHandler, ), TextKey( text: '', flex: 2, onTextInput: _textInputHandler, ), BackspaceKey( onBackspace: _backspaceHandler, ), ], ), ); } }
有趣的部分:
- 键盘收集按键的回调并传递它们。这样,任何使用
CustomKeyboard
的人都会收到回调。 - 您可以看到第三行如何使用
flex
。空格键的弯曲为4
,而退格的默认弯曲为1。这使得空格键占用了后空键宽度的四倍。
在应用程序中使用键盘
现在,您可以像这样使用自定义键盘小部件:
代码看起来是这样的:
CustomKeyboard( onTextInput: (myText) { _insertText(myText); }, onBackspace: () { _backspace(); }, ),
处理文本输入
以下是_insertText
方法的样子:
void _insertText(String myText) { final text = _controller.text; final textSelection = _controller.selection; final newText = text.replaceRange( textSelection.start, textSelection.end, myText, ); final myTextLength = myText.length; _controller.text = newText; _controller.selection = textSelection.copyWith( baseOffset: textSelection.start + myTextLength, extentOffset: textSelection.start + myTextLength, ); }
_controller
是TextField
的TextEditingController
。你必须记住,可能有一个选择,所以如果有的话,请用密钥传递的文本替换它。
感谢这个,以提供帮助。*
处理退格
您会认为退格很简单,但有一些不同的情况需要考虑:
- 有一个选择(删除选择)
- 光标在开头(什么都不要做)
- 其他任何事情(删除之前的角色)
以下是_backspace
方法的实现:
void _backspace() { final text = _controller.text; final textSelection = _controller.selection; final selectionLength = textSelection.end - textSelection.start; // There is a selection. if (selectionLength > 0) { final newText = text.replaceRange( textSelection.start, textSelection.end, '', ); _controller.text = newText; _controller.selection = textSelection.copyWith( baseOffset: textSelection.start, extentOffset: textSelection.start, ); return; } // The cursor is at the beginning. if (textSelection.start == 0) { return; } // Delete the previous character final previousCodeUnit = text.codeUnitAt(textSelection.start - 1); final offset = _isUtf16Surrogate(previousCodeUnit) ? 2 : 1; final newStart = textSelection.start - offset; final newEnd = textSelection.start; final newText = text.replaceRange( newStart, newEnd, '', ); _controller.text = newText; _controller.selection = textSelection.copyWith( baseOffset: newStart, extentOffset: newStart, ); } bool _isUtf16Surrogate(int value) { return value & 0xF800 == 0xD800; }
即使删除之前的角色也有点棘手。如果您在有表情符号或其他代理对时只回退单个代码单元这将导致崩溃。作为上述代码中的变通办法,我检查了上一个字符是否是UFT-16代理,如果是,则后退了两个字符。(我从Flutter TextPainter
源代码中获得了_isUtf16Surrogate
方法。)然而,这仍然不是一个完美的解决方案,因为它不适用于像