序言
小編在項(xiàng)目中有遇到使用 flutter 實(shí)現(xiàn)掃碼槍接入的需求。為方便使用,小編把能力封裝成 package 并發(fā)布。好記性不如爛筆頭,下面是該插件的使用方式,以及途中遇到的坑和處理想法。

使用方式:
- 在pubspec.yaml文件中進(jìn)行引用:
dependencies:
scan_gun: ^1.0.0
- 提供
ScanMonitorWidget作為父節(jié)點(diǎn),嵌套使用:
ScanMonitorWidget({
Key? key,
required ChildBuilder childBuilder,
FocusNode? scanNode,
FocusNode? textFiledNode,
required void Function(String) onSubmit,
})
參數(shù)說明:
childBuilder :
typedef ChildBuilder = Widget Function(BuildContext context),使用者自己UI作為子節(jié)點(diǎn)scanNode:
- 非必傳,如果傳,可通過
scanNode監(jiān)聽獲取當(dāng)前掃碼可用狀態(tài),hasFocus時(shí)為可用 - 也可通過
scanNoderequestFocus 方法,強(qiáng)制掃碼獲取焦點(diǎn),保證掃碼能力
textFiledNode:
提供外部存在輸入框鍵盤輸入與掃碼輸入同時(shí)存在的場(chǎng)景。內(nèi)部做了焦點(diǎn)切換能力,保證輸入框焦點(diǎn)取消后,能馬上切換成掃碼槍的焦點(diǎn)onSubmit:
接收掃碼槍返回的結(jié)果
兩種場(chǎng)景能力支持
- 無輸入框交互,獲取掃碼結(jié)果:
@override
Widget build(BuildContext context) {
return ScanMonitorWidget(
childBuilder: (context) {
return body();
},
onSubmit: (String result) {
print(result); //接收到掃碼結(jié)果
},
);
}
- 帶輸入框交互,獲取掃碼結(jié)果:
FocusNode textFiledNode = FocusNode();
TextEditingController controller = TextEditingController();
Widget body() {
return TextField(
focusNode: textFiledNode,
controller: controller,
);
}
@override
Widget build(BuildContext context) {
return ScanMonitorWidget(
textFiledNode: textFiledNode,
childBuilder: (context) {
return body();
},
onSubmit: (String result) {
print(result); //接收到掃碼結(jié)果
},
);
}
github 源碼已上傳 :傳送門
目前該方案為非通用方案,依賴 flutter 版本進(jìn)行定制,小編使用的是
Flutter 2.8.1,后續(xù)更新通用方案。
通用方案已更新:新方案?jìng)魉烷T
技術(shù)點(diǎn)分析
1. 如何獲取掃碼槍輸入內(nèi)容
使用過 flutter 編寫輸入框的同學(xué)都用過 TextField,通過源碼我們可以看到 TextField 的功能實(shí)現(xiàn)者是它的子節(jié)點(diǎn):EditableText。

掃碼槍本質(zhì)上是一個(gè)外接的輸入設(shè)備。將 EditableText 封裝,控制隱藏??赏ㄟ^獲取 EditableText 的內(nèi)容來獲取掃碼槍的輸入內(nèi)容。
控制隱藏可使用 Offstage 標(biāo)簽:
return Stack(
children: [
//讓輸入框保持隱藏
Offstage(child: edtWidget, offstage: true),
child,
],
);
2. 鍵盤彈出問題
使用 EditableText 的過程中遇到了系統(tǒng)鍵盤彈出的問題。我們通過 Edit 的焦點(diǎn)來獲取掃碼槍的輸入。但 EditableText 一旦獲取了焦點(diǎn),內(nèi)部會(huì)調(diào)用原生層喚起鍵盤。這個(gè)問題怎么處理呢?
首先,我們來看看源碼中 EditableText 是如何喚起鍵盤的。
省略非關(guān)鍵代碼,直接定位到 EditableTextState

當(dāng)焦點(diǎn)變化時(shí),調(diào)用了
_openOrCloseInputConnectionIfNeeded()

在 _openInputConnection() 方法中通過 TextInput 喚起系統(tǒng)鍵盤


既然了解到了EditableText喚起鍵盤的邏輯,通過自定義 EditableText,將 TextInput.show 步驟過濾掉,只保留單純的通過焦點(diǎn)獲取輸入源內(nèi)容的能力。
3. 擴(kuò)展,如何自定義監(jiān)聽數(shù)據(jù)源輸入
在 TextInput 源碼中,可以發(fā)現(xiàn)鍵盤等輸入的數(shù)據(jù)通過 MessageChannel 的方式進(jìn)行數(shù)據(jù)流轉(zhuǎn):

由于篇幅原因,這里小編只做拋磚引玉。下面列出核心代碼部分:
void listenKeyboard() {
SystemChannels.textInput.setMethodCallHandler((call) => _handleTextInputInvocation(call));
}
String scanData = '';
void _update(){
setState(() {});
}
Future<dynamic> _handleTextInputInvocation(MethodCall methodCall) async {
final String method = methodCall.method;
final List<dynamic> args = methodCall.arguments as List<dynamic>;
switch (method) {
case 'TextInputClient.updateEditingState': //每次的內(nèi)容變化會(huì)進(jìn)來這里
final data = TextEditingValue.fromJSON(args[1] as Map<String, dynamic>);
final text = data.text;
scanData += text;
dev.log('rex: -updateEditingState - $text');
break;
case 'TextInputClient.performAction':
final action = args[1] as String;
dev.log('rex: -performAction - $action');
if(action == 'TextInputAction.none'){ //點(diǎn)擊確定
_update();
}
break;
default:
throw MissingPluginException();
}
}
通用方案已更新:新方案?jìng)魉烷T