在 Flutter 中,如何在表单外(即提交按钮下方)输出 TextFormField 验证器文本?
In Flutter, how do you output TextFormField validator text outside the form (i.e. below submit button)?
我编写了示例代码,当 TextFormField 中没有字符串时,如果用户单击登录按钮会显示一条消息。它将在 TextFormField 下方显示一条消息,提醒用户输入电子邮件。我想要做的是在其他地方显示该消息,也许在登录按钮下方。
理想情况下,我有一个登录名和密码文本字段,而不是在每个文本字段下方显示消息,我想在登录按钮下方显示消息。任何帮助,将不胜感激!我研究了文本控制器,但不知道如何实现,如果验证器通过但没有消息,它会保持小部件相同的高度,但仅在验证器失败时才扩展高度显示消息。
import 'package:flutter/material.dart';
final Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: TextFieldExample(),
),
),
);
}
}
class TextFieldExample extends StatefulWidget {
@override
_TextFieldExampleState createState() => _TextFieldExampleState();
}
class _TextFieldExampleState extends State<TextFieldExample> {
final _formKey = GlobalKey<FormState>();
String email = '';
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.symmetric(vertical: 5),
padding: EdgeInsets.symmetric(horizontal: 15),
decoration: BoxDecoration(borderRadius: BorderRadius.circular(29)),
child: Form(
key: _formKey,
child: Column(children: <Widget>[
TextFormField(
validator: (value) => value.isEmpty ? 'Enter an email' : null,
onChanged: (value) {
setState(() => email = value);
},
decoration:
InputDecoration(icon: Icon(Icons.person), hintText: "Email"),
),
Container(
margin: EdgeInsets.symmetric(vertical: 5),
child: ClipRRect(
borderRadius: BorderRadius.circular(29),
child: RaisedButton(
padding: EdgeInsets.symmetric(vertical: 10),
onPressed: () async {
if (_formKey.currentState.validate()) {
//sign in;
}
},
child: Text("Login")))),
]),
),
);
}
}
考虑使用 Row
而不是 Column
import 'package:flutter/material.dart';
final Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: TextFieldExample(),
),
),
);
}
}
class TextFieldExample extends StatefulWidget {
@override
_TextFieldExampleState createState() => _TextFieldExampleState();
}
class _TextFieldExampleState extends State<TextFieldExample> {
final _formKey = GlobalKey<FormState>();
String email = '';
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.symmetric(vertical: 5),
padding: EdgeInsets.symmetric(horizontal: 15),
decoration: BoxDecoration(borderRadius: BorderRadius.circular(29)),
child: Form(
key: _formKey,
child: Row(children: <Widget>[
Flexible(
child: TextFormField(
validator: (value) => value.isEmpty ? 'Enter an email' : null,
onChanged: (value) {
setState(() => email = value);
},
decoration:
InputDecoration(icon: Icon(Icons.person), hintText: "Email"),
),
),
Expanded(
flex: 0,
child: Container(
margin: EdgeInsets.symmetric(vertical: 5),
child: ClipRRect(
borderRadius: BorderRadius.circular(29),
child: RaisedButton(
padding: EdgeInsets.symmetric(vertical: 10),
onPressed: () async {
if (_formKey.currentState.validate()) {
//sign in;
}
},
child: Text("Login")))),)
]),
),
);
}
}
多亏LOfG()的帮助,我得到了答案
以下代码执行以下操作:
- 使用 StreamBuilder 重建每个新事件
- 仅当验证失败时才在登录按钮下方显示消息,否则 return 空容器
- 当用户点击用户名或密码时,消息不为空则消失
- 使用snapshot.data登录用户(这里可以使用Provider with FirebaseAuth)
我想要这个功能的原因是在特定位置显示错误消息,而不是在文本字段中。
import 'package:flutter/material.dart';
import 'dart:async';
final Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: TextFieldExample(),
),
),
);
}
}
class TextFieldExample extends StatefulWidget {
@override
_TextFieldExampleState createState() => _TextFieldExampleState();
}
class _TextFieldExampleState extends State<TextFieldExample> {
@override
void dispose() {
_username.close();
_password.close();
super.dispose();
}
String validatorMessage;
bool validate = false; //will be true if the user clicked in the login button
final _username = StreamController<String>(); //stream to validate the text
final _password = StreamController<String>();
Widget build(BuildContext context) {
return Scaffold(
body: Container(
margin: EdgeInsets.symmetric(vertical: 5),
padding: EdgeInsets.symmetric(horizontal: 15),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(29),
),
//expose streambuilder to the column widget to use on multiple widgets
child: StreamBuilder<String>(
initialData: '',
stream: _username.stream,
builder: (context, usernameSnapshot) {
return StreamBuilder<String>(
initialData: '',
stream: _password.stream,
builder: (context, passwordSnapshot) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextField(
onChanged: _username.add, //everytime the text changes a new value will be added to the stream
decoration: InputDecoration(
icon: Icon(Icons.person),
hintText: "Email",
),
),
TextField(
obscureText: true,
onChanged: _password.add, //everytime the text changes a new value will be added to the stream
decoration: InputDecoration(
icon: Icon(Icons.visibility),
hintText: "Password",
),
),
Container(
margin: EdgeInsets.symmetric(vertical: 5),
child: ClipRRect(
borderRadius: BorderRadius.circular(29),
child: RaisedButton(
padding: EdgeInsets.symmetric(vertical: 10),
//when user presses button, validate turns to true and we check snapshots to get the data for the entries
onPressed: () async {
if (usernameSnapshot.data.isNotEmpty && passwordSnapshot.data.isNotEmpty) {
try {
//sign in with usernameSnapshot.data and passwordSnapshot.data
} catch (e) {
print(e);
}
validate = true;
} else {
setState(() {
validate = true;
});
}
},
child: Text("Login"),
),
),
),
if (usernameSnapshot.data.isEmpty &&validate == true) //checking the stream and if the user clicked in the button
Container(
alignment: Alignment.center,
child: Text(
'Check your e-mail and password.',
style: TextStyle(
color: Colors.red,
),
),
)
else
Container()
],
);
});
})),
);
}
}
我编写了示例代码,当 TextFormField 中没有字符串时,如果用户单击登录按钮会显示一条消息。它将在 TextFormField 下方显示一条消息,提醒用户输入电子邮件。我想要做的是在其他地方显示该消息,也许在登录按钮下方。
理想情况下,我有一个登录名和密码文本字段,而不是在每个文本字段下方显示消息,我想在登录按钮下方显示消息。任何帮助,将不胜感激!我研究了文本控制器,但不知道如何实现,如果验证器通过但没有消息,它会保持小部件相同的高度,但仅在验证器失败时才扩展高度显示消息。
import 'package:flutter/material.dart';
final Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: TextFieldExample(),
),
),
);
}
}
class TextFieldExample extends StatefulWidget {
@override
_TextFieldExampleState createState() => _TextFieldExampleState();
}
class _TextFieldExampleState extends State<TextFieldExample> {
final _formKey = GlobalKey<FormState>();
String email = '';
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.symmetric(vertical: 5),
padding: EdgeInsets.symmetric(horizontal: 15),
decoration: BoxDecoration(borderRadius: BorderRadius.circular(29)),
child: Form(
key: _formKey,
child: Column(children: <Widget>[
TextFormField(
validator: (value) => value.isEmpty ? 'Enter an email' : null,
onChanged: (value) {
setState(() => email = value);
},
decoration:
InputDecoration(icon: Icon(Icons.person), hintText: "Email"),
),
Container(
margin: EdgeInsets.symmetric(vertical: 5),
child: ClipRRect(
borderRadius: BorderRadius.circular(29),
child: RaisedButton(
padding: EdgeInsets.symmetric(vertical: 10),
onPressed: () async {
if (_formKey.currentState.validate()) {
//sign in;
}
},
child: Text("Login")))),
]),
),
);
}
}
考虑使用 Row
而不是 Column
import 'package:flutter/material.dart';
final Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: TextFieldExample(),
),
),
);
}
}
class TextFieldExample extends StatefulWidget {
@override
_TextFieldExampleState createState() => _TextFieldExampleState();
}
class _TextFieldExampleState extends State<TextFieldExample> {
final _formKey = GlobalKey<FormState>();
String email = '';
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.symmetric(vertical: 5),
padding: EdgeInsets.symmetric(horizontal: 15),
decoration: BoxDecoration(borderRadius: BorderRadius.circular(29)),
child: Form(
key: _formKey,
child: Row(children: <Widget>[
Flexible(
child: TextFormField(
validator: (value) => value.isEmpty ? 'Enter an email' : null,
onChanged: (value) {
setState(() => email = value);
},
decoration:
InputDecoration(icon: Icon(Icons.person), hintText: "Email"),
),
),
Expanded(
flex: 0,
child: Container(
margin: EdgeInsets.symmetric(vertical: 5),
child: ClipRRect(
borderRadius: BorderRadius.circular(29),
child: RaisedButton(
padding: EdgeInsets.symmetric(vertical: 10),
onPressed: () async {
if (_formKey.currentState.validate()) {
//sign in;
}
},
child: Text("Login")))),)
]),
),
);
}
}
多亏LOfG(
以下代码执行以下操作:
- 使用 StreamBuilder 重建每个新事件
- 仅当验证失败时才在登录按钮下方显示消息,否则 return 空容器
- 当用户点击用户名或密码时,消息不为空则消失
- 使用snapshot.data登录用户(这里可以使用Provider with FirebaseAuth)
我想要这个功能的原因是在特定位置显示错误消息,而不是在文本字段中。
import 'package:flutter/material.dart';
import 'dart:async';
final Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: TextFieldExample(),
),
),
);
}
}
class TextFieldExample extends StatefulWidget {
@override
_TextFieldExampleState createState() => _TextFieldExampleState();
}
class _TextFieldExampleState extends State<TextFieldExample> {
@override
void dispose() {
_username.close();
_password.close();
super.dispose();
}
String validatorMessage;
bool validate = false; //will be true if the user clicked in the login button
final _username = StreamController<String>(); //stream to validate the text
final _password = StreamController<String>();
Widget build(BuildContext context) {
return Scaffold(
body: Container(
margin: EdgeInsets.symmetric(vertical: 5),
padding: EdgeInsets.symmetric(horizontal: 15),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(29),
),
//expose streambuilder to the column widget to use on multiple widgets
child: StreamBuilder<String>(
initialData: '',
stream: _username.stream,
builder: (context, usernameSnapshot) {
return StreamBuilder<String>(
initialData: '',
stream: _password.stream,
builder: (context, passwordSnapshot) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextField(
onChanged: _username.add, //everytime the text changes a new value will be added to the stream
decoration: InputDecoration(
icon: Icon(Icons.person),
hintText: "Email",
),
),
TextField(
obscureText: true,
onChanged: _password.add, //everytime the text changes a new value will be added to the stream
decoration: InputDecoration(
icon: Icon(Icons.visibility),
hintText: "Password",
),
),
Container(
margin: EdgeInsets.symmetric(vertical: 5),
child: ClipRRect(
borderRadius: BorderRadius.circular(29),
child: RaisedButton(
padding: EdgeInsets.symmetric(vertical: 10),
//when user presses button, validate turns to true and we check snapshots to get the data for the entries
onPressed: () async {
if (usernameSnapshot.data.isNotEmpty && passwordSnapshot.data.isNotEmpty) {
try {
//sign in with usernameSnapshot.data and passwordSnapshot.data
} catch (e) {
print(e);
}
validate = true;
} else {
setState(() {
validate = true;
});
}
},
child: Text("Login"),
),
),
),
if (usernameSnapshot.data.isEmpty &&validate == true) //checking the stream and if the user clicked in the button
Container(
alignment: Alignment.center,
child: Text(
'Check your e-mail and password.',
style: TextStyle(
color: Colors.red,
),
),
)
else
Container()
],
);
});
})),
);
}
}