Flutter - 5 : 鍵盤遮擋輸入框問題,以及阻止系統鍵盤彈出
Flutter中的輸入框控件TextField竟然在被鍵盤遮擋的時候沒有上移的行為,真是坑爹。
中間參考過某位大神的解決辦法,然而沒成功,可能是我看的不夠仔細,用的方法不對。
連結如下:點選跳轉某位大神的解決辦法
沒辦法,隻能自己解決,暴力的解決。效果如下圖:
解決辦法:
如果系統沒給向上滑動,那就自己控制它向上滑動,SingleChildScrollView可以随意控制想要滑動到的位置。
缺點就是麻煩。
1.
最好用的辦法是寫一個插件,在原生部分通過布局的變化來确定鍵盤的目前狀态,然後在狀态變化時通過插件通知flutter,這個辦法一般不會出錯。目前我也是在用這種辦法,雖然麻煩,不出錯就好,例子就不寫了,網上在原生端判斷鍵盤狀态的代碼很多,結合插件使用就好了。
2.
次級辦法是在flutter中判斷,然而壞處是,隻能通過監聽螢幕的矩陣回調來判斷,有些時候,會失效,甚至誤判,是以,不是很推薦這種方法。下面的例子是矩陣回調的例子,已确認過有些時候會發生誤判。
第一步:
Flutter中,焦點的擷取依賴于FocusNode這個類,這個類中提供了一系列與焦點相關的方法,尤其是其中的consumeKeyboardToken方法,傳回了目前系統鍵盤的狀态,如果想要不讓系統鍵盤彈出來,可以重寫這個方法,當然,裡面還是有坑。
是以首先需要寫一個繼承類:
class ScrollFocusNode extends FocusNode {
final bool _useSystemKeyBoard; // 是否使用系統鍵盤
final double _moveValue; // 上移距離
ScrollFocusNode(this._useSystemKeyBoard, this._moveValue);
@override
bool consumeKeyboardToken() {
if (_useSystemKeyBoard) {
return super.consumeKeyboardToken();
}
return false;
}
double get moveValue => _moveValue;
bool get useSystemKeyBoard => _useSystemKeyBoard;
}
第二步:
WidgetsBindingObserver 這個類,提供了很多回調的方法,這裡使用到的是對螢幕矩陣的回調(開始提到的參考文章中對此有相關介紹以及文檔),當然如果沒有用系統的鍵盤,也就沒有必要加它了。滾動的位置由傳入的focusNode所帶的參數來确定,直接繼承之後實作相關方法就行了。
abstract class BoardWidget extends State
with WidgetsBindingObserver {
final ScrollController _controller = ScrollController();
ScrollFocusNode _focusNode;
double _currentPosition = 0.0;
List initChild();
void bindNewInputer(ScrollFocusNode focusNode) {
_focusNode = focusNode;
_animateUp();
}
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
@override
void dispose() {
super.dispose();
_controller.dispose();
WidgetsBinding.instance.removeObserver(this);
}
// 向上滾動
void _animateUp() {
_controller
.animateTo(_focusNode.moveValue,
duration: Duration(milliseconds: 250), curve: Curves.easeOut)
.then((Null) {
_currentPosition = _controller.offset;
});
}
// 向下滾動
void _animateDown() {
_controller
.animateTo(0.0,
duration: Duration(milliseconds: 250), curve: Curves.easeOut)
.then((Null) {
_currentPosition = 0.0;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: SingleChildScrollView(
controller: _controller,
physics: NeverScrollableScrollPhysics(),
child: Column(
children: initChild()..add(SizedBox(height: 400.0)),
),
),
);
}
// 使用系統鍵盤 ---> 矩陣變換 ---> 傳回原位置
@override
void didChangeMetrics() {
if (_currentPosition != 0.0) {
_focusNode.unfocus(); // 如果不加,收起鍵盤再點選會預設鍵盤還在。
_animateDown();
}
}
}
第三步:
使用的例子,在initChild()中傳回想要實作的布局,在TextField的點選事件onTap中傳入目前TextField綁定的ScrollFocusNode就能實作效果了,不過需要先确定滾動的距離。
class TestPage extends StatefulWidget {
@override
State createState() {
return _TestPageState();
}
}
class _TestPageState extends BoardWidget {
final bool _useSystemKeyBoard = true;
final TextStyle textStyle = TextStyle(
fontFamily: "hwxw",
fontSize: 20.0,
letterSpacing: 1.0,
fontWeight: FontWeight.bold,
fontStyle: FontStyle.normal,
color: Colors.black87);
final TextStyle lableStyle = TextStyle(
fontFamily: "hwxw",
fontSize: 20.0,
letterSpacing: 16.0,
fontWeight: FontWeight.bold,
fontStyle: FontStyle.normal);
final TextStyle helperStyle = TextStyle(
fontFamily: "hwxw", fontSize: 12.0, fontStyle: FontStyle.normal);
ScrollFocusNode _userNameFocusNode;
ScrollFocusNode _passWordFocusNode;
@override
void initState() {
super.initState();
_userNameFocusNode = ScrollFocusNode(_useSystemKeyBoard, 120.0); // 第二個參數是向上滾動的距離
_passWordFocusNode = ScrollFocusNode(_useSystemKeyBoard, 180.0); // 第二個參數是向上滾動的距離
}
@override
List initChild() {
return [
Padding(
padding: EdgeInsets.only(top: 350.0, left: 50.0, right: 50.0),
child: TextField(
focusNode: _userNameFocusNode,
autofocus: false,
maxLength: 12,
maxLines: 1,
style: textStyle,
decoration: InputDecoration(
contentPadding: EdgeInsets.only(top: 16.0, bottom: 16.0),
border: OutlineInputBorder(),
labelText: "賬号",
labelStyle: lableStyle,
helperStyle: helperStyle,
prefixIcon: Icon(Icons.account_box, size: 24.0),
),
onTap: () {
// 點選時綁定目前focusNode
bindNewInputer(_userNameFocusNode);
},
),
),
Padding(
padding: EdgeInsets.only(top: 20.0, left: 50.0, right: 50.0),
child: TextField(
obscureText: true,
focusNode: _passWordFocusNode,
autofocus: false,
maxLength: 12,
maxLines: 1,
style: textStyle,
decoration: InputDecoration(
contentPadding: EdgeInsets.only(top: 16.0, bottom: 16.0),
border: OutlineInputBorder(),
labelText: "密碼",
labelStyle: lableStyle,
helperStyle: helperStyle,
prefixIcon: Icon(Icons.https, size: 24.0),
),
onTap: () {
// 點選時綁定目前focusNode
bindNewInputer(_passWordFocusNode);
},
),
),
];
}
}
最後,阻止系統鍵盤彈出:
flutter的鍵盤是通過系統插件與原生通信,然後調起的,是以組織系統鍵盤的最簡單辦法就是,注釋掉調用代碼,如下:
修改flutter\packages\flutter\lib\src\services\text_input.dart路徑下的這個檔案的TextInputConnection方法下的show方法,注掉SystemChannels.textInput.invokeMethod(‘TextInput.show’);這一行就行了,這是調起系統鍵盤的系統通信頻道調用方法。
當然,這樣直接修改源代碼的後果就是,所有的textfield都不會彈出鍵盤了。
如果不彈系統鍵盤,那隻能自定義一個,适用于某些特殊狀況。
本集完!