Flutter:在使用 TextFormField 时打开键盘时动态调整小部件的大小
Flutter: Dynamically resize widgets when keyboard is opened when using TextFormField
打开键盘时,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 时,您还可以看到键盘弹起并不是唯一可以调整视图大小的东西,还有可能弹出的错误消息。
我认为一个好的解决方案是动态调整顶部文本“大标题”和“Lorem ipsum”文本的大小,使表单和按钮 space。
我不确定是否有可靠的方法来检测键盘何时启动,但我在 this question 中阅读过有关检查 MediaQuery.of(context).viewInsets.bottom
的内容。当键盘打开时,我可以调整文本的大小(或隐藏它)。这是可行的解决方案吗?
或者可能有一些我不知道的布局?为顶部的两个小部件设置动画效果会很好,这样会逐渐消失 and/or 减小尺寸(也许 AnimatedContainer
)?这仍然需要以编程方式检查打开的键盘。
如有任何建议,我们将不胜感激!
因为你想让提交按钮在打开键盘时可见,你可以像下面那样做。我对您的代码本身进行了一些更改
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,
)),
])),
],
));
希望对您有所帮助。
我考虑了 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,
)),
])),
]),
));
}
}
我相信这可以做得更好,例如当小部件从小部件树中删除时将其动画化。这是基本的并且有效。
如您所见,溢出不再发生,因为现在有更多的垂直 space:
打开键盘时,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 时,您还可以看到键盘弹起并不是唯一可以调整视图大小的东西,还有可能弹出的错误消息。
我认为一个好的解决方案是动态调整顶部文本“大标题”和“Lorem ipsum”文本的大小,使表单和按钮 space。
我不确定是否有可靠的方法来检测键盘何时启动,但我在 this question 中阅读过有关检查 MediaQuery.of(context).viewInsets.bottom
的内容。当键盘打开时,我可以调整文本的大小(或隐藏它)。这是可行的解决方案吗?
或者可能有一些我不知道的布局?为顶部的两个小部件设置动画效果会很好,这样会逐渐消失 and/or 减小尺寸(也许 AnimatedContainer
)?这仍然需要以编程方式检查打开的键盘。
如有任何建议,我们将不胜感激!
因为你想让提交按钮在打开键盘时可见,你可以像下面那样做。我对您的代码本身进行了一些更改
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,
)),
])),
],
));
希望对您有所帮助。
我考虑了 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,
)),
])),
]),
));
}
}
我相信这可以做得更好,例如当小部件从小部件树中删除时将其动画化。这是基本的并且有效。
如您所见,溢出不再发生,因为现在有更多的垂直 space: