【问题标题】:Flutter: Dynamically resize widgets when keyboard is opened when using TextFormFieldFlutter:使用TextFormField时打开键盘时动态调整小部件的大小
【发布时间】:2021-10-11 04:52:46
【问题描述】:

我在打开键盘时遇到 Flutter 报告溢出问题。这是打开键盘或验证表单或字段时我的视图的外观和行为:

有没有办法让顶部(“大标题”和“lorem ipsum”)小部件在发生这种情况时自动调整大小,以便表单字段和提交按钮可见?

这发生在元素位于Column 小部件中的位置。 我已经知道resizeToAvoidBottomInset ,但我不想使用它,因为我不想挡住键盘下方的任何小部件。 我也知道我可以使用SingleChildScrollView,但我也不想使用它,因为我希望“提交”按钮在键盘启动时可见。

您可以查看gist 以查看此确切代码:

import 'package:flutter/material.dart';
import 'dart:math';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key}) : super(key: key);
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  bool _hasError = false;
  bool _usernameHasError = false;
  bool _passwordHasError = false;

  _handleFormSubmission() {
    final rand = Random();

    setState(() {
      _hasError = rand.nextBool();
    });

    print('Sumbmitted');
  }

  _handleUsernameChange(String value) {
    setState(() {
      _usernameHasError = value.isEmpty;
    });
  }

  _handlePasswordChange(String value) {
    setState(() {
      _passwordHasError = value.isEmpty;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Container(
      padding: const EdgeInsets.all(10.0).add(const EdgeInsets.only(top: 30.0)),
      child: Column(
          mainAxisSize: MainAxisSize.max,
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            Container(
              child: Column(children: [
                Text('Large title',
                    style: Theme.of(context).textTheme.headline2),
                Text(
                    'Lorem ipsum dolor sit amet, consectetur adipiscing elit, '
                    'sed do eiusmod tempor incididunt ut labore et dolore magna'
                    ' aliqua. Ut enim ad minim veniam, quis nostrud exercitation '
                    'ullamco laboris nisi ut aliquip ex ea commodo consequat.',
                    style: Theme.of(context).textTheme.headline5),
              ]),
            ),
            Container(
              padding: const EdgeInsets.only(top: 10.0),
              child: Column(children: [
                Container(
                    alignment: Alignment.centerLeft,
                    padding: const EdgeInsets.only(bottom: 20.0),
                    child: Text('Additional information is placed here')),
                TextFormField(
                  decoration: InputDecoration(
                      labelText: 'Username',
                      errorText: _usernameHasError ? 'Invalid username' : null),
                  onChanged: _handleUsernameChange,
                ),
                TextFormField(
                  obscureText: true,
                  decoration: InputDecoration(
                      labelText: 'Password',
                      errorText: _passwordHasError ? 'Invalid password' : null),
                  onChanged: _handlePasswordChange,
                ),
                _hasError
                    ? Container(
                        padding: const EdgeInsets.symmetric(vertical: 20.0),
                        child: Row(
                            mainAxisAlignment: MainAxisAlignment.center,
                            children: [
                              Icon(Icons.error,
                                  color: Theme.of(context).errorColor),
                              Text('An error occurred!',
                                  style: TextStyle(
                                      color: Theme.of(context).errorColor))
                            ]))
                    : Container()
              ]),
            ),
            Container(
                padding: const EdgeInsets.only(top: 10.0),
                child: Column(children: [
                  Container(
                      width: double.infinity,
                      child: ElevatedButton(
                        child: Text('Submit'),
                        onPressed: _handleFormSubmission,
                      )),
                ])),
          ]),
    ));
  }
}

当您查看我的 GIF 时,您还可以看到键盘向上并不是唯一可以调整视图大小的东西,还会弹出错误消息。

我认为一个好的解决方案是动态调整顶部的文本“Large title”和“Lorem ipsum”文本的大小,以便为表单和按钮腾出空间。

我不确定是否有可靠的方法来检测键盘何时启动,但我已经阅读了有关在 this question 中检查 MediaQuery.of(context).viewInsets.bottom 的信息。当键盘打开时,我可以调整文本大小(或隐藏它)。这是可行的解决方案吗?

或者也许有一些我不知道的布局?最好在顶部为两个小部件设置动画,这样它们会逐渐消失和/或减小尺寸(可能是AnimatedContainer)?这仍然需要以编程方式检查打开的键盘。

感谢任何建议!

【问题讨论】:

    标签: flutter dart user-interface mobile


    【解决方案1】:

    我考虑了 Neela 的回答,然后开始尝试一些不同的方法。 我发现足够的一个方法是隐藏 lorem ipsum 文本,因为在我的上下文中,关注表单字段时并不重要。

    我使用flutter_keyboard_visibility 插件来检测键盘何时启动,然后有条件地删除了文本小部件。

    完整代码(也在这个gist):

    import 'package:flutter/material.dart';
    import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart';
    import 'dart:math';
    import 'dart:async';
    
    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
      // This widget is the root of your application.
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: MyHomePage(),
        );
      }
    }
    
    class MyHomePage extends StatefulWidget {
      MyHomePage({Key? key}) : super(key: key);
      @override
      _MyHomePageState createState() => _MyHomePageState();
    }
    
    class _MyHomePageState extends State<MyHomePage> {
      bool _hasError = false;
      bool _usernameHasError = false;
      bool _passwordHasError = false;
      bool _keyboardVisible = false;
    
      final KeyboardVisibilityController _keyboardVisibilityController =
          KeyboardVisibilityController();
      StreamSubscription<bool>? _keyboardVisibilitySubscription;
    
      @override
      void initState() {
        super.initState();
        _keyboardVisibilitySubscription = _keyboardVisibilityController.onChange
            .listen(_handleKeyboardVisibilityChange);
      }
    
      @override
      void dispose() {
        _keyboardVisibilitySubscription?.pause();
        _keyboardVisibilitySubscription?.cancel();
        super.dispose();
      }
    
      void _handleKeyboardVisibilityChange(bool visible) {
        setState(() {
          _keyboardVisible = visible;
        });
      }
    
    
      _handleFormSubmission() {
        final rand = Random();
    
        setState(() {
          _hasError = rand.nextBool();
        });
    
        print('Sumbmitted');
      }
    
      _handleUsernameChange(String value) {
        setState(() {
          _usernameHasError = value.isEmpty;
        });
      }
    
      _handlePasswordChange(String value) {
        setState(() {
          _passwordHasError = value.isEmpty;
        });
      }
    
      @override
      Widget build(BuildContext context) {
        print('\nkeyboard is visible $_keyboardVisible');
        return Scaffold(
            body: Container(
          padding: const EdgeInsets.all(10.0).add(const EdgeInsets.only(top: 30.0)),
          child: Column(
              mainAxisSize: MainAxisSize.max,
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                Container(
                  child: Column(children: [
                    Text('Large title',
                        style: Theme.of(context).textTheme.headline2),
                    _keyboardVisible ? Container() : Text(
                        'Lorem ipsum dolor sit amet, consectetur adipiscing elit, '
                        'sed do eiusmod tempor incididunt ut labore et dolore magna'
                        ' aliqua. Ut enim ad minim veniam, quis nostrud exercitation '
                        'ullamco laboris nisi ut aliquip ex ea commodo consequat.',
                        style: Theme.of(context).textTheme.headline5),
                  ]),
                ),
                Container(
                  padding: const EdgeInsets.only(top: 10.0),
                  child: Column(children: [
                    Container(
                        alignment: Alignment.centerLeft,
                        padding: const EdgeInsets.only(bottom: 20.0),
                        child: Text('Additional information is placed here')),
                    TextFormField(
                      decoration: InputDecoration(
                          labelText: 'Username',
                          errorText: _usernameHasError ? 'Invalid username' : null),
                      onChanged: _handleUsernameChange,
                    ),
                    TextFormField(
                      obscureText: true,
                      decoration: InputDecoration(
                          labelText: 'Password',
                          errorText: _passwordHasError ? 'Invalid password' : null),
                      onChanged: _handlePasswordChange,
                    ),
                    _hasError
                        ? Container(
                            padding: const EdgeInsets.symmetric(vertical: 20.0),
                            child: Row(
                                mainAxisAlignment: MainAxisAlignment.center,
                                children: [
                                  Icon(Icons.error,
                                      color: Theme.of(context).errorColor),
                                  Text('An error occurred!',
                                      style: TextStyle(
                                          color: Theme.of(context).errorColor))
                                ]))
                        : Container()
                  ]),
                ),
                Container(
                    padding: const EdgeInsets.only(top: 10.0),
                    child: Column(children: [
                      Container(
                          width: double.infinity,
                          child: ElevatedButton(
                            child: Text('Submit'),
                            onPressed: _handleFormSubmission,
                          )),
                    ])),
              ]),
        ));
      }
    }
    

    我相信这可以做得更好,例如,当它从小部件树中删除时,将小部件设置为动画。这是基本的并且有效。

    正如你所见,溢出不再发生,因为现在有更多的垂直空间:

    【讨论】:

      【解决方案2】:

      由于您想让提交按钮在打开键盘时可见,您可以像下面那样执行此操作。我对您的代码本身进行了一些更改

          return Scaffold(
          body: Column(
        children: [
          Expanded(
            child: SingleChildScrollView(
              child: Container(
                padding: const EdgeInsets.all(10.0)
                    .add(const EdgeInsets.only(top: 30.0)),
                child: Column(
                    mainAxisSize: MainAxisSize.max,
                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
                    children: [
                      Container(
                        child: Column(children: [
                          Text('Large title',
                              style: Theme.of(context).textTheme.headline2),
                          Text(
                              'Lorem ipsum dolor sit amet, consectetur adipiscing elit, '
                              'sed do eiusmod tempor incididunt ut labore et dolore magna'
                              ' aliqua. Ut enim ad minim veniam, quis nostrud exercitation '
                              'ullamco laboris nisi ut aliquip ex ea commodo consequat.',
                              style: Theme.of(context).textTheme.headline5),
                        ]),
                      ),
                      Container(
                        padding: const EdgeInsets.only(top: 10.0),
                        child: Column(children: [
                          Container(
                              alignment: Alignment.centerLeft,
                              padding: const EdgeInsets.only(bottom: 20.0),
                              child:
                                  Text('Additional information is placed here')),
                          TextFormField(
                            decoration: InputDecoration(
                                labelText: 'Username',
                                errorText: _usernameHasError
                                    ? 'Invalid username'
                                    : null),
                            onChanged: _handleUsernameChange,
                          ),
                          TextFormField(
                            obscureText: true,
                            decoration: InputDecoration(
                                labelText: 'Password',
                                errorText: _passwordHasError
                                    ? 'Invalid password'
                                    : null),
                            onChanged: _handlePasswordChange,
                          ),
                          _hasError
                              ? Container(
                                  padding:
                                      const EdgeInsets.symmetric(vertical: 20.0),
                                  child: Row(
                                      mainAxisAlignment: MainAxisAlignment.center,
                                      children: [
                                        Icon(Icons.error,
                                            color: Theme.of(context).errorColor),
                                        Text('An error occurred!',
                                            style: TextStyle(
                                                color:
                                                    Theme.of(context).errorColor))
                                      ]))
                              : Container()
                        ]),
                      ),
                    ]),
              ),
            ),
          ),
          Container(
              padding: const EdgeInsets.only(top: 10.0),
              child: Column(children: [
                Container(
                    width: double.infinity,
                    child: ElevatedButton(
                      child: Text('Submit'),
                      onPressed: _handleFormSubmission,
                    )),
              ])),
        ],
      ));
      

      希望这会对你有所帮助。

      【讨论】:

      • 感谢您的回答。是的,这解决了问题,所以我最终可能会使用它。但是我仍然认为以编程方式减小顶部标题的字体大小在视觉上会更好,以便为表单留出足够的空间。我的想法是让提交按钮可见但不与其他字段重叠,但这可能从我的问题中不清楚。再次感谢您的帮助,我认为在功能上这是一个很好的解决方案。
      猜你喜欢
      • 2013-02-14
      • 2020-08-22
      • 1970-01-01
      • 2016-10-11
      • 2019-01-17
      • 2017-02-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多