Flutter - 5 : 键盘遮挡输入框问题,以及阻止系统键盘弹出
Flutter中的输入框控件TextField
竟然在被键盘遮挡的时候没有上移的行为,真是坑爹。
中间参考过某位大神的解决办法,然而没成功,可能是我看的不够仔细,用的方法不对。
链接如下:点击跳转某位大神的解决办法
没办法,只能自己解决,暴力的解决。效果如下图:
![](https://i-blog.csdnimg.cn/blog_migrate/35c3e8db2424dc398240ced758c4e276.gif)
解决办法:
如果系统没给向上滑动,那就自己控制它向上滑动,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<StatefulWidget>
with WidgetsBindingObserver {
final ScrollController _controller = ScrollController();
ScrollFocusNode _focusNode;
double _currentPosition = 0.0;
List<Widget> 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<StatefulWidget> 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<Widget> initChild() {
return <Widget>[
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都不会弹出键盘了。
如果不弹系统键盘,那只能自定义一个,适用于某些特殊状况。