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

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

Flutter中的输入框控件TextField竟然在被键盘遮挡的时候没有上移的行为,真是坑爹。

中间参考过某位大神的解决办法,然而没成功,可能是我看的不够仔细,用的方法不对。
链接如下:点击跳转某位大神的解决办法

没办法,只能自己解决,暴力的解决。效果如下图:

解决办法:

如果系统没给向上滑动,那就自己控制它向上滑动,SingleChildScrollView可以随意控制想要滑动到的位置。
缺点就是麻烦。

第一步:

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);
          },
        ),
      ),
    ];
  }
}
最后,阻止系统键盘弹出:

如果只是重写FocusNode,那么确实能阻止键盘弹出,然而如果再点一次,键盘就又弹出来了,因为Flutter中这部分的代码,是像下面一样写的:

  void _openOrCloseInputConnectionIfNeeded() {
    if (_hasFocus && widget.focusNode.consumeKeyboardToken() ) {
      _openInputConnection();
    } else if (!_hasFocus) {
      _closeInputConnectionIfNeeded();
      widget.controller.clearComposing();
    }
  }

  /// Express interest in interacting with the keyboard.
  ///
  /// If this control is already attached to the keyboard, this function will
  /// request that the keyboard become visible. Otherwise, this function will
  /// ask the focus system that it become focused. If successful in acquiring
  /// focus, the control will then attach to the keyboard and request that the
  /// keyboard become visible.
  ///
void requestKeyboard() {
    if (_hasFocus)
      _openInputConnection();
    else
      FocusScope.of(context).requestFocus(widget.focusNode);
  }

可以看到,_openOrCloseInputConnectionIfNeeded方法中,分别判定了当前是否拥有焦点以及键盘状态,而第二个,只判定了是否有焦点,所以,连续点击的时候,只重写FocusNode是没用的,只能在第二个判定里面加一条了。。。改成 :
if (_hasFocus && widget.focusNode.consumeKeyboardToken() )

这部分代码在editable_text文件的第707行。

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

本集完!

猜你喜欢

转载自blog.csdn.net/weixin_42572156/article/details/85107017
今日推荐