在Flutter中使一个小部件浮动在键盘上方

15

当键盘显示时,我想显示一个“关闭键盘”按钮在键盘上方。

我知道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),
        ),
      ),
    ]);
  }
}
6个回答

26

您的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),
        ),
      ),
    ]);
  }
}

gif


这个怎么运作的?它会在每次键盘弹出后自动重建吗? - axunic
我知道很长时间过去了,但基本上在您的小部件中任何地方使用MediaQuery.of(context)意味着当手机状态更改时,例如旋转、键盘弹出等,它将重新构建。 - FLjubic

5

2022年更新

合并了一个PR,提供了针对关闭/打开键盘的平台同步动画。查看此处的PR

详细回答

为了实现基于键盘可见性的动画填充效果,在@10101010的优秀回答上进行以下几个修改:

如果你想让bottom随着键盘可见性的变化而带有动画效果,并且想给浮动子元素添加额外的内边距,则需要:

1- 使用keyboard_visibility flutter pub

监听键盘出现/消失事件,如下所示:

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

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

你可以选择编写自己的原生插件,但 pub 的 git 仓库已经有了,可以查看。

2- 在 AnimatedPositioned 中使用可见性标志:

用于精细调整动画填充效果,示例如下:

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- 同步动画的键盘专用动画曲线和持续时间

目前这仍然是一个已知的正在进行的问题


4
您可以使用 Scaffold 组件的 bottomSheet 属性。
例如:
    return Scaffold(
      appBar: AppBar(
        title: const Text("New Game"),
      ),
      bottomSheet: Container(
          padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 16),
          color: Colors.blue,
          child: const SizedBox(
            width: double.infinity,
            height: 20,
            child: Text("Above Keyboard"),
          ))
...
)


1

您可以使用Scaffold的bottomSheet参数,它可以保持一个持久的底部抽屉。请参见以下代码。

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'),
          ),
        ],
      ),
    );
  }
}

0

请查看包,,它可以在键盘上方显示一个取消按钮。


那个包很有趣,也符合我想做的事情的想法,但是@10101010提到的解决方案对于我的情况更容易实现。 - leb1755

0

如果堆栈方法失败??
那么请尝试使用 bottomsheet 和 Positioned.bottom: MediaQuery.of(context).viewInsets.bottom!! 方法

将您的按钮小部件放在 bottomsheet 中

如果需要滚动主体部分,请使用 SingleChildScrollView 进行包装

示例代码和附件见下方

Widget build(BuildContext context) {
  return Scaffold(
      appBar: AppBar(),
      body: const SingleChildScrollView(
          child: Column(
        children: [
          // your Widgets here
          //Textfield 1
          //Textfield 2
          //Textfield 3
        ],
      )),
      bottomSheet: SizedBox(
        height: 70,
        child: ButtonWidget(
          buttonName: "Continue",
          onpressed: () {
            //Button press code here
            context.pushRoute(const SignUpStepsPageRoute());
          },
        ),
      ));
}

输出将会是

Image that keyboard open / close state


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接