在 Flutter 中只让一个 widget 悬浮在键盘上方

Make only one widget float above the keyboard in Flutter

我想在键盘上方显示一个 "Close keyboard" 按钮。

我知道 resizeToAvoidBottomInset 会影响键盘与应用程序其余部分的交互方式,但它并不完全符合我的要求。

我有一个背景图片和其他小部件(未在下面的示例中显示),它们不应在显示键盘时调整大小和移动。当 resizeToAvoidBottomInset 属性设置为 false 时,这是一个正常的行为。

不过,我想添加一个应该出现在键盘上方的按钮。

我该怎么做?我只想要一个小部件浮动在键盘上方,而不是所有应用程序。

这是一个示例代码:

import 'dart:async';

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var home = MyHomePage(title: 'Flutter Demo Home Page');
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: home,
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      resizeToAvoidBottomInset: false,
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: _getBody(),
      floatingActionButton: FloatingActionButton(
        onPressed: () {},
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }

  Widget _getBody() {
    return Stack(children: <Widget>[
      Container(
        decoration: BoxDecoration(
            image: DecorationImage(
                image: AssetImage("assets/sample.jpg"), fit: BoxFit.fitWidth)),
        // color: Color.fromARGB(50, 200, 50, 20),
        child: Column(
          children: <Widget>[TextField()],
        ),
      ),
      Positioned(
        bottom: 0,
        left: 0,
        right: 0,
        child: Container(
          height: 50,
          child: Text("Aboveeeeee"),
          decoration: BoxDecoration(color: Colors.pink),
        ),
      ),
    ]);
  }
}

检查这个 package, 它可以在键盘上方显示一个关闭按钮。

您的 Positioned 小部件的 bottom 为 0,替换为适当的值应该可以完成工作。

MediaQuery.of(context).viewInsets.bottom 将为您提供系统覆盖的高度值 UI(在本例中为键盘)。

import 'dart:async';

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var home = MyHomePage(title: 'Flutter Demo Home Page');
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: home,
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      resizeToAvoidBottomInset: false,
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: _getBody(),
      floatingActionButton: FloatingActionButton(
        onPressed: () {},
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }

  Widget _getBody() {
    return Stack(children: <Widget>[
      Container(
        decoration: BoxDecoration(
            image: DecorationImage(
                image: AssetImage("assets/sample.jpg"), fit: BoxFit.fitWidth)),
        // color: Color.fromARGB(50, 200, 50, 20),
        child: Column(
          children: <Widget>[TextField()],
        ),
      ),
      Positioned(
        bottom: MediaQuery.of(context).viewInsets.bottom,
        left: 0,
        right: 0,
        child: Container(
          height: 50,
          child: Text("Aboveeeeee"),
          decoration: BoxDecoration(color: Colors.pink),
        ),
      ),
    ]);
  }
}

2022 年更新

合并了一个为 closing/opening 键盘提供平台同步动画的 PR。见 PR in effect here.

详细解答

要实现基于键盘可见性的动画填充,这里是对@10101010 的出色回答的一些修改:

如果您希望 bottom 在键盘更改可见性时更改为动画 并且 您希望在浮动子项下有额外的填充,则:

1- 使用keyboard_visibility flutter pub

当键盘为 appearing/disappearing 时收听,像这样:

  bool isKeyboardVisible = false;
  
  @override
  void initState() {
    super.initState();

    KeyboardVisibilityNotification().addNewListener(
      onChange: (bool visible) {
        isKeyboardVisible = visible;
      },
    );
  }

您可以选择编写自己的本机插件,但它已经存在,您可以查看发布的 git 存储库。

2- 在你的 AnimatedPostioned:

中消耗可见性标志

微调动画填充,像这样:

Widget _getBody() {
    double bottomPadding = 0;
    if (isKeyboardVisible) {
      // when keyboard is shown, our floating widget is above the keyboard and its accessories by `16`
      bottomPadding = MediaQuery.of(context).viewInsets.bottom + 16;
    } else {
      // when keyboard is hidden, we should have default spacing
      bottomPadding = 48; // MediaQuery.of(context).size.height * 0.15;
    }

    return Stack(children: <Widget>[
      Container(
        decoration: BoxDecoration(
            image: DecorationImage(
                image: AssetImage("assets/sample.jpg"), fit: BoxFit.fitWidth)),
        // color: Color.fromARGB(50, 200, 50, 20),
        child: Column(
          children: <Widget>[TextField()],
        ),
      ),
      AnimatedPositioned(
        duration: Duration(milliseconds: 500),
        bottom: bottomPadding,
        left: 0,
        right: 0,
        child: Container(
          height: 50,
          child: Text("Aboveeeeee"),
          decoration: BoxDecoration(color: Colors.pink),
        ),
      ),
    ]);
}

3- 特定于键盘的动画曲线和同步动画的持续时间

目前这仍然是 known ongoing issue

您可以使用 Scaffold 的 bottomSheet 参数,它会保持一个持久的底部 sheet。请参阅下面的代码。

class InputScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Close')),
      bottomSheet: Container(
          padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 16),
          color: Colors.black,
          child: const SizedBox(width: double.infinity, height: 10)),
      body: Column(
        children: [
          const TextField(
            decoration: InputDecoration(
              border: OutlineInputBorder(),
              hintText: 'Enter your input here',
            ),
          ),
          ElevatedButton(
            onPressed: () {},
            child: const Text('Submit'),
          ),
        ],
      ),
    );
  }
}