您好,登錄后才能下訂單哦!
這篇文章主要介紹“Flutter如何自定義應用程序內鍵盤”的相關知識,小編通過實際案例向大家展示操作過程,操作方法簡單快捷,實用性強,希望這篇“Flutter如何自定義應用程序內鍵盤”文章能幫助大家解決問題。
效果:
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回調的形式將其值傳遞給鍵盤。
您還需要一個與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
方法。)然而,這仍然不是一個完美的解決方案,因為它不適用于像????????或????‍????‍????這樣的字素簇,它們由多個代理對組成。不過,至少它不會
以下是象形文字和表情符號鍵盤作為演示:
????????????‍????‍
如果您對此有意見,請參閱此堆棧溢出問題。
如果您想將自定義鍵盤與aTextField一起使用,但系統鍵盤不斷彈出,那會有點煩人。這畢竟是默認行為。
防止系統鍵盤顯示的方法是將TextField
的readOnly
屬性設置為true
。
TextField( ... showCursor: true, readOnly: true, ),
此外,將showCursor
設置為true
,使光標在您使用自定義鍵盤時仍然可以工作。
如果您想讓用戶選擇使用系統鍵盤或自定義鍵盤,您只需為readOnly
使用不同的值進行重建。
以下是演示應用程序中TextField的設置方式:
class _KeyboardDemoState extends State<KeyboardDemo> { TextEditingController _controller = TextEditingController(); bool _readOnly = true; @override Widget build(BuildContext context) { return Scaffold( resizeToAvoidBottomInset: false, body: Column( children: [ ... TextField( controller: _controller, decoration: ..., style: TextStyle(fontSize: 24), autofocus: true, showCursor: true, readOnly: _readOnly, ), IconButton( icon: Icon(Icons.keyboard), onPressed: () { setState(() { _readOnly = !_readOnly; }); }, ),
有趣的部分:
當按下鍵盤IconButton
時,更改_readOnly
的值,然后重建布局。這會導致系統鍵盤隱藏或顯示。
將Scaffold
上的resizeToAvoidBottomInset
設置為false
,允許系統鍵盤覆蓋自定義鍵盤。另一個選項是在顯示系統鍵盤時隱藏自定義鍵盤。然而,當我在實驗中這樣做時,我發現我必須使用單獨的布爾值來隱藏自定義鍵盤,這樣我就可以延遲顯示它,直到系統鍵盤消失。否則,它會跳到系統鍵盤頂部一秒鐘。
就這樣!如您所見,制作自己的應用程序內鍵盤并不難。
以下是我在本文中使用的演示應用程序的完整代碼:
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: KeyboardDemo(), ); } } class KeyboardDemo extends StatefulWidget { @override _KeyboardDemoState createState() => _KeyboardDemoState(); } class _KeyboardDemoState extends State<KeyboardDemo> { TextEditingController _controller = TextEditingController(); bool _readOnly = true; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("大前端之旅的自定義鍵盤"), ), resizeToAvoidBottomInset: false, body: Column( children: [ Text("微信:xjg13690"), SizedBox(height: 50), TextField( controller: _controller, decoration: InputDecoration( border: OutlineInputBorder( borderRadius: BorderRadius.circular(3), ), ), style: TextStyle(fontSize: 24), autofocus: true, showCursor: true, readOnly: _readOnly, ), IconButton( icon: Icon(Icons.keyboard), onPressed: () { setState(() { _readOnly = !_readOnly; }); }, ), Spacer(), CustomKeyboard( onTextInput: (myText) { _insertText(myText); }, onBackspace: () { _backspace(); }, ), ], ), ); } 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, ); } 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; } @override void dispose() { _controller.dispose(); super.dispose(); } } 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, ), ], ), ); } } 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!)), ), ), ), ), ); } } 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), ), ), ), ), ), ); } }
關于“Flutter如何自定義應用程序內鍵盤”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識,可以關注億速云行業資訊頻道,小編每天都會為大家更新不同的知識點。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。