在 Flutter 中目前官方?jīng)]有提供快速自定義鍵盤的解決方案侦高。
但在項(xiàng)目中以及 ICP 測評需要用到安全鍵盤停巷,比如金額輸入师坎、安全密碼輸入恕酸、各種自定義快速輸入鍵盤。
這里以Packages的形式實(shí)現(xiàn)了一個(gè)自定義安全鍵盤胯陋,已上傳 Pub蕊温。
實(shí)現(xiàn)功能
- 適用原生輸入框控件
- 支持一個(gè)頁面多個(gè)安全鍵盤輸入框
- 支持系統(tǒng)鍵盤與安全鍵盤混合使用
- 支持自動定位到輸入框位置
實(shí)現(xiàn)思路
主要是通過攔截PlatformChannel實(shí)現(xiàn)的,可以無縫對接TextFiled等Flutter自帶的輸入框,監(jiān)聽輸入框各種狀態(tài)遏乔,以及內(nèi)容變化义矛。
關(guān)鍵代碼講解
- KeyboardManager
自定義鍵盤管理類,包括輸入類型與鍵盤的管理盟萨,以及輸入框攔截凉翻、懸浮自定義鍵盤及生命周期管理等。
主要功能方法 init捻激。
///初始化鍵盤監(jiān)聽并且傳遞當(dāng)前頁面的context
static init(BuildContext context) {
_context = context;
interceptorInput();
}
///攔截鍵盤交互
static interceptorInput() {
if (isInterceptor) return;
isInterceptor = true;
BinaryMessages.setMockMessageHandler("flutter/textinput",
(ByteData data) async {
var methodCall = _codec.decodeMethodCall(data);
switch (methodCall.method) {
...
return response;
});
}
在init 方法中對輸入框交互進(jìn)行攔截制轰。
當(dāng)輸入框獲焦時(shí)會調(diào)用TextInput.setClient、TextInput.show铺罢。
case 'TextInput.show':
if (_currentKeyboard != null) {
openKeyboard();
return _codec.encodeSuccessEnvelope(null);
} else {
return await _sendPlatformMessage("flutter/textinput", data);
}
break
case 'TextInput.setClient':
...
brea
在TextInput.setClient中根據(jù)輸入框定義的鍵盤類型找出對應(yīng)鍵盤對應(yīng)的鍵盤配置艇挨、InputClient残炮,以及初始輸入變更監(jiān)聽KeyboardController韭赘。
在TextInput.show根據(jù)配置的鍵盤類型,調(diào)用 openKeyboard 生成鍵盤控件势就。
///顯示鍵盤
static openKeyboard() {
///鍵盤已經(jīng)打開
if (_keyboardEntry != null) return;
_pageKey = GlobalKey<KeyboardPageState>();
_keyboardHeight = _currentKeyboard.getHeight();
///根據(jù)鍵盤高度泉瞻,使鍵盤滾動到輸入框位置
KeyboardMediaQueryState queryState = _context
.ancestorStateOfType(const TypeMatcher<KeyboardMediaQueryState>())
as KeyboardMediaQueryState;
queryState.update();
...
///往Overlay中插入插入OverlayEntry
Overlay.of(_context).insert(_keyboardEntry);
}
這里通過 Overlay顯示一個(gè)懸浮窗脉漏,并鍵盤彈出后,滾動到輸入框位置袖牙。
當(dāng)輸入框輸入時(shí)侧巨,會調(diào)用TextInput.setEditingState。
case 'TextInput.setEditingState':
var editingState = TextEditingValue.fromJSON(methodCall.arguments);
if (editingState != null && _keyboardController != null) {
_keyboardController.value = editingState;
return _codec.encodeSuccessEnvelope(null);
}
break
這里設(shè)置 KeyboardController 的TextEditingValue鞭达,就可以監(jiān)聽輸入框的輸入變化司忱。
當(dāng)切換焦點(diǎn)、或自定義鍵盤輸入完成關(guān)閉時(shí)畴蹭,會調(diào)用TextInput.clearClient坦仍、TextInput.hide。
case 'TextInput.hide':
if (_currentKeyboard != null) {
hideKeyboard();
return _codec.encodeSuccessEnvelope(null);
} else {
return await _sendPlatformMessage("flutter/textinput", data);
}
break;
///切換輸入框時(shí)叨襟,會調(diào)用該回調(diào),切換時(shí)鍵盤會隱藏繁扎。
case 'TextInput.clearClient':
hideKeyboard(animation: false);
clearKeyboard();
break
在 clearClient 中清除 InputClient,且隱藏鍵盤糊闽。
- KeyboardController
鍵盤輸入變更監(jiān)聽,可以參考系統(tǒng)[TextEditingController]梳玫,主要通過ValueNotifier監(jiān)聽TextEditingValue的值變化,ValueNotifier是一個(gè)包含單個(gè)值的變更通知器右犹,當(dāng)它的值改變的時(shí)候提澎,會通知它的監(jiān)聽。
擴(kuò)展了 addText念链,deleteText虱朵,doneAction
///刪除一個(gè)字符,一般用于鍵盤的刪除鍵
deleteOne() {
if (selection.baseOffset == 0) return;
String newText = '';
if (selection.baseOffset != selection.extentOffset) {
newText = selection.textBefore(text) + selection.textAfter(text);
value = TextEditingValue(
text: newText,
selection: selection.copyWith(
baseOffset: selection.baseOffset,
extentOffset: selection.baseOffset));
} else {
newText = text.substring(0, selection.baseOffset - 1) +
selection.textAfter(text);
value = TextEditingValue(
text: newText,
selection: selection.copyWith(
baseOffset: selection.baseOffset - 1,
extentOffset: selection.baseOffset - 1));
}
}
/// 在光標(biāo)位置添加文字,一般用于鍵盤輸入
addText(String insertText) {
String newText =
selection.textBefore(text) + insertText + selection.textAfter(text);
value = TextEditingValue(
text: newText,
selection: selection.copyWith(
baseOffset: selection.baseOffset + insertText.length,
extentOffset: selection.baseOffset + insertText.length));
}
/// 完成
doneAction() {
KeyboardManager.sendPerformAction(TextInputAction.done);
}
- KeyboardMediaQuery
用于鍵盤彈出的時(shí)候控制頁面邊間,使輸入框不被擋住钓账,自動定位到輸入框位置碴犬。
class KeyboardMediaQuery extends StatefulWidget {
final Widget child;
KeyboardMediaQuery({this.child}) : assert(child != null);
@override
State<StatefulWidget> createState() => KeyboardMediaQueryState();
}
class KeyboardMediaQueryState extends State<KeyboardMediaQuery> {
@override
Widget build(BuildContext context) {
var data = MediaQuery.of(context);
///消息傳遞,更新控件邊距
return MediaQuery(
child: widget.child,
data: data.copyWith(
viewInsets: data.viewInsets
.copyWith(bottom: KeyboardManager.keyboardHeight)));
}
///通知更新
void update() {
setState(() => {});
}
}
使用方法
- Step1
在項(xiàng)目pubspec.yaml添加安全鍵盤依賴
dependencies:
security_keyboard: ^1.0.2
- Step2
根據(jù)項(xiàng)目需求編寫個(gè)性化鍵盤
typedef KeyboardSwitch = Function(SecurityKeyboardType type);
enum SecurityKeyboardType {
text,
textUpperCase,
number,
numberOnly,
numberSimple,
symbol
}
class SecurityKeyboard extends StatefulWidget {
///用于控制鍵盤輸出的Controller
final KeyboardController controller;
///鍵盤類型,默認(rèn)文本
final SecurityKeyboardType keyboardType;
///定義InputType類型
static const SecurityTextInputType inputType =
const SecurityTextInputType(name: 'SecurityKeyboardInputType');
SecurityKeyboard({this.controller, this.keyboardType});
///文本輸入類型
static SecurityTextInputType text =
SecurityKeyboard._inputKeyboard(SecurityKeyboardType.text);
///數(shù)字輸入類型
static SecurityTextInputType number =
SecurityKeyboard._inputKeyboard(SecurityKeyboardType.number);
///僅數(shù)字輸入類型
static SecurityTextInputType numberOnly =
SecurityKeyboard._inputKeyboard(SecurityKeyboardType.numberOnly);
///僅數(shù)字輸入類型,且沒有鍵盤提示
static SecurityTextInputType numberSimple =
SecurityKeyboard._inputKeyboard(SecurityKeyboardType.numberSimple);
///初始化鍵盤類型梆暮,返回輸入框類型
static SecurityTextInputType _inputKeyboard(
SecurityKeyboardType securityKeyboardType) {
///注冊鍵盤的方法
String inputType = securityKeyboardType.toString();
SecurityTextInputType securityTextInputType =
SecurityTextInputType(name: inputType);
KeyboardManager.addKeyboard(
securityTextInputType,
KeyboardConfig(
builder: (context, controller) {
return SecurityKeyboard(
controller: controller,
keyboardType: securityKeyboardType,
);
},
getHeight: () {
return SecurityKeyboard.getHeight(securityKeyboardType);
},
),
);
return securityTextInputType;
}
///鍵盤類型
SecurityKeyboardType get _keyboardType => keyboardType;
///編寫獲取高度的方法
static double getHeight(SecurityKeyboardType securityKeyboardType) {
return securityKeyboardType == SecurityKeyboardType.numberSimple
? LcfarmSize.dp(192)
: LcfarmSize.dp(232);
}
@override
_SecurityKeyboardState createState() => _SecurityKeyboardState();
}
class _SecurityKeyboardState extends State<SecurityKeyboard> {
///當(dāng)前鍵盤類型
SecurityKeyboardType currentKeyboardType;
@override
void initState() {
super.initState();
currentKeyboardType = widget._keyboardType;
}
@override
Widget build(BuildContext context) {
Widget keyboard;
switch (currentKeyboardType) {
case SecurityKeyboardType.number:
keyboard = NumberKeyboard(
widget.controller,
currentKeyboardType,
keyboardSwitch: (SecurityKeyboardType keyboardType) {
setState(() {
currentKeyboardType = keyboardType;
});
},
);
break;
case SecurityKeyboardType.numberOnly:
case SecurityKeyboardType.numberSimple:
keyboard = NumberKeyboard(widget.controller, currentKeyboardType);
break;
case SecurityKeyboardType.symbol:
keyboard = SymbolKeyboard(widget.controller,
(SecurityKeyboardType keyboardType) {
setState(() {
currentKeyboardType = keyboardType;
});
});
break;
case SecurityKeyboardType.text:
case SecurityKeyboardType.textUpperCase:
keyboard = AlphabetKeyboard(widget.controller, currentKeyboardType,
(SecurityKeyboardType keyboardType) {
setState(() {
currentKeyboardType = keyboardType;
});
});
break;
}
return keyboard;
}
}
將以下代碼添加到要使用安全鍵盤的頁面:
class PasswordVerify extends StatelessWidget {
@override
Widget buildScaffold(BuildContext context) {
//構(gòu)建包含安全鍵盤視圖,用于鍵盤彈出的時(shí)候頁面可以滾動到輸入框的位置
return KeyboardMediaQuery(
child: Builder(builder: (ctx) {
//初始化鍵盤監(jiān)聽并且傳遞當(dāng)前頁面的context
KeyboardManager.init(ctx);
return super.buildScaffold(context);
}),
);
}
- Step4
在TextField keyboardType中設(shè)置自定義安全性鍵盤類型服协。
只需傳遞Step1中編寫的inputType,就像通常設(shè)置鍵盤輸入類型一樣啦粹。
TextField(
...
keyboardType: SecurityKeyboard.text,
...
)
最后
??如果在使用過程遇到問題偿荷,歡迎下方留言交流。
??Pub類庫地址