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都不会弹出键盘了。

如果不弹系统键盘,那只能自定义一个,适用于某些特殊状况。

本集完!

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐