Flutter - 5 : 键盘遮挡输入框问题,以及阻止系统键盘弹出

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<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都不会弹出键盘了。
如果不弹系统键盘,那只能自定义一个,适用于某些特殊状况。

本集完!
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值