Flutter:在输入时提交 TextFormField
Flutter: Submit TextFormField on Enter
我正在使用 Flutter 为 Windows 开发桌面应用程序。我想听键盘按键。我制作了一个登录页面,它有两个 TextFormFields(一个用于用户名,另一个用于密码)。
当我按下键盘上的 'enter' 键时,我希望在按下 'Login' 按钮时提交表单。因此,如果我在用户名或密码文本字段中按下 'enter' 键,我希望应用程序的行为就像我按下 'Login' 按钮一样。
这是登录页面的图片:
--> Login Page
登录页面代码如下:
import 'package:flutter/material.dart';
import '../../api/services/login_services.dart';
import '../../api/models/login_models.dart';
import '../../constants/constant_methods.dart';
import '../main_page/main_screen.dart';
import '../../constants/constant_variables.dart';
class LoginPage extends StatefulWidget {
const LoginPage({Key? key}) : super(key: key);
@override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
TextEditingController usernameController = TextEditingController();
TextEditingController passwordController = TextEditingController();
late LoginRequestModel loginRequestModel;
bool isUsernameEmpty = false;
bool isPasswordEmpty = false;
bool secure = true;
bool notSecure = false;
bool isLoading = false;
@override
void initState() {
super.initState();
loginRequestModel = LoginRequestModel(
usernameController.text,
passwordController.text,
);
}
@override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
decoration: const BoxDecoration(
gradient: loginPageGradient,
),
child: Center(
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
'assets/images/cloud_soft_logo.png',
height: 90,
),
const SizedBox(height: 15),
Text(
'Hoomy\'s Real Estate',
style: Theme.of(context).textTheme.headline4,
),
const SizedBox(height: 20),
Container(
width: 310,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: borderRadius(10.0),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const SizedBox(height: 20),
Text(
'Welcome',
style: Theme.of(context)
.textTheme
.headline6!
.copyWith(
fontWeight: FontWeight.w600,
foreground: Paint()..shader = linearGradient),
),
Text(
'Please Log in to Your Account',
style: Theme.of(context).textTheme.headline2,
),
const SizedBox(height: 10),
loginTextField("Username", "Username can't be empty",
usernameController),
loginTextField("Password", "Password can't be empty",
passwordController),
forgetPasswordButton(),
loginButton(context),
const SizedBox(height: 40),
],
),
),
],
),
),
),
),
),
);
}
Widget loginTextField(
String labelText,
String errorText,
TextEditingController controller,
) {
return SizedBox(
width: 250,
child: TextFormField(
decoration: InputDecoration(
labelText: labelText,
errorText: labelText == "Username"
? isUsernameEmpty
? errorText
: null
: isPasswordEmpty
? errorText
: null,
suffixIcon: labelText == "Username"
? IconButton(
onPressed: () {}, icon: const Icon(Icons.person, size: 20))
: IconButton(
onPressed: () {
setState(() {
secure = !secure;
});
},
icon: Icon(
secure ? Icons.visibility_off : Icons.visibility,
size: 20,
),
)),
obscureText: labelText == "Username" ? notSecure : secure,
controller: controller,
),
);
}
Padding forgetPasswordButton() {
return Padding(
padding: const EdgeInsets.all(20).copyWith(right: 30),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Theme(
data: ThemeData(splashColor: Colors.transparent),
child: TextButton(
child: const Text('Forget Password'),
onPressed: () {},
),
),
],
),
);
}
DecoratedBox loginButton(BuildContext context) {
return DecoratedBox(
decoration: BoxDecoration(
borderRadius: borderRadius(50.0),
gradient: loginButtonGradient,
),
child: ElevatedButton(
style: ElevatedButton.styleFrom(
primary: Colors.transparent,
fixedSize: const Size(250, 50),
shape: RoundedRectangleBorder(
borderRadius: borderRadius(50.0),
)),
child: isLoading
? CircularProgressIndicator(
color: Colors.blue[800],
backgroundColor: Colors.grey,
)
: const Text('Login'),
onPressed: () {
setState(() {
usernameController.text.isEmpty
? isUsernameEmpty = true
: isUsernameEmpty = false;
passwordController.text.isEmpty
? isPasswordEmpty = true
: isPasswordEmpty = false;
});
setState(() {
isLoading = true;
});
LoginService.login(usernameController.text, passwordController.text)
.then((response) {
setState(() {
isLoading = false;
});
if (response) {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const MainScreen(),
),
);
} else if (!response &&
usernameController.text.isNotEmpty &&
passwordController.text.isNotEmpty) {
showErrorDialog(context, "Invalid Username or Password!");
}
});
}),
);
}
Future<dynamic> showErrorDialog(BuildContext context, String dialogContent) {
return showDialog(
context: context,
builder: (BuildContext context) {
return Center(
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: SizedBox(
width: 350,
child: AlertDialog(
backgroundColor: Colors.white,
shape: RoundedRectangleBorder(borderRadius: borderRadius(10.0)),
title: Text("Error",
style: Theme.of(context)
.textTheme
.bodyText1!
.copyWith(color: Colors.red)),
content: Text(dialogContent,
style: Theme.of(context)
.textTheme
.headline2!
.copyWith(color: Colors.black87)),
actions: <Widget>[
TextButton(
child: Text("OK",
style: Theme.of(context)
.textTheme
.bodyText1!
.copyWith(color: Colors.blue)),
onPressed: () {
Navigator.of(context).pop();
},
),
],
),
),
),
);
},
);
}
}
添加字段 onFieldSubmitted
和 FormKey
:
// Widget attribute
final _formKey = GlobalKey<FormState>();
// Widget method: build()
Form(
key: _formKey,
child:
TextFormField(
onFieldSubmitted: (value) {
print('ENTER pressed');
// Will trigger validation for ALL fields in Form.
// All your TextFormFields (email, password) share
// the SAME Form and thus the SAME _formKey.
if (_formKey.currentState!.validate()) {
print('ALL FIELDS ARE VALID, GO ON ...');
}
}
)
)
文档
https://api.flutter.dev/flutter/material/TextFormField-class.html
我正在使用 Flutter 为 Windows 开发桌面应用程序。我想听键盘按键。我制作了一个登录页面,它有两个 TextFormFields(一个用于用户名,另一个用于密码)。 当我按下键盘上的 'enter' 键时,我希望在按下 'Login' 按钮时提交表单。因此,如果我在用户名或密码文本字段中按下 'enter' 键,我希望应用程序的行为就像我按下 'Login' 按钮一样。
这是登录页面的图片: --> Login Page
登录页面代码如下:
import 'package:flutter/material.dart';
import '../../api/services/login_services.dart';
import '../../api/models/login_models.dart';
import '../../constants/constant_methods.dart';
import '../main_page/main_screen.dart';
import '../../constants/constant_variables.dart';
class LoginPage extends StatefulWidget {
const LoginPage({Key? key}) : super(key: key);
@override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
TextEditingController usernameController = TextEditingController();
TextEditingController passwordController = TextEditingController();
late LoginRequestModel loginRequestModel;
bool isUsernameEmpty = false;
bool isPasswordEmpty = false;
bool secure = true;
bool notSecure = false;
bool isLoading = false;
@override
void initState() {
super.initState();
loginRequestModel = LoginRequestModel(
usernameController.text,
passwordController.text,
);
}
@override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
decoration: const BoxDecoration(
gradient: loginPageGradient,
),
child: Center(
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
'assets/images/cloud_soft_logo.png',
height: 90,
),
const SizedBox(height: 15),
Text(
'Hoomy\'s Real Estate',
style: Theme.of(context).textTheme.headline4,
),
const SizedBox(height: 20),
Container(
width: 310,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: borderRadius(10.0),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const SizedBox(height: 20),
Text(
'Welcome',
style: Theme.of(context)
.textTheme
.headline6!
.copyWith(
fontWeight: FontWeight.w600,
foreground: Paint()..shader = linearGradient),
),
Text(
'Please Log in to Your Account',
style: Theme.of(context).textTheme.headline2,
),
const SizedBox(height: 10),
loginTextField("Username", "Username can't be empty",
usernameController),
loginTextField("Password", "Password can't be empty",
passwordController),
forgetPasswordButton(),
loginButton(context),
const SizedBox(height: 40),
],
),
),
],
),
),
),
),
),
);
}
Widget loginTextField(
String labelText,
String errorText,
TextEditingController controller,
) {
return SizedBox(
width: 250,
child: TextFormField(
decoration: InputDecoration(
labelText: labelText,
errorText: labelText == "Username"
? isUsernameEmpty
? errorText
: null
: isPasswordEmpty
? errorText
: null,
suffixIcon: labelText == "Username"
? IconButton(
onPressed: () {}, icon: const Icon(Icons.person, size: 20))
: IconButton(
onPressed: () {
setState(() {
secure = !secure;
});
},
icon: Icon(
secure ? Icons.visibility_off : Icons.visibility,
size: 20,
),
)),
obscureText: labelText == "Username" ? notSecure : secure,
controller: controller,
),
);
}
Padding forgetPasswordButton() {
return Padding(
padding: const EdgeInsets.all(20).copyWith(right: 30),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Theme(
data: ThemeData(splashColor: Colors.transparent),
child: TextButton(
child: const Text('Forget Password'),
onPressed: () {},
),
),
],
),
);
}
DecoratedBox loginButton(BuildContext context) {
return DecoratedBox(
decoration: BoxDecoration(
borderRadius: borderRadius(50.0),
gradient: loginButtonGradient,
),
child: ElevatedButton(
style: ElevatedButton.styleFrom(
primary: Colors.transparent,
fixedSize: const Size(250, 50),
shape: RoundedRectangleBorder(
borderRadius: borderRadius(50.0),
)),
child: isLoading
? CircularProgressIndicator(
color: Colors.blue[800],
backgroundColor: Colors.grey,
)
: const Text('Login'),
onPressed: () {
setState(() {
usernameController.text.isEmpty
? isUsernameEmpty = true
: isUsernameEmpty = false;
passwordController.text.isEmpty
? isPasswordEmpty = true
: isPasswordEmpty = false;
});
setState(() {
isLoading = true;
});
LoginService.login(usernameController.text, passwordController.text)
.then((response) {
setState(() {
isLoading = false;
});
if (response) {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const MainScreen(),
),
);
} else if (!response &&
usernameController.text.isNotEmpty &&
passwordController.text.isNotEmpty) {
showErrorDialog(context, "Invalid Username or Password!");
}
});
}),
);
}
Future<dynamic> showErrorDialog(BuildContext context, String dialogContent) {
return showDialog(
context: context,
builder: (BuildContext context) {
return Center(
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: SizedBox(
width: 350,
child: AlertDialog(
backgroundColor: Colors.white,
shape: RoundedRectangleBorder(borderRadius: borderRadius(10.0)),
title: Text("Error",
style: Theme.of(context)
.textTheme
.bodyText1!
.copyWith(color: Colors.red)),
content: Text(dialogContent,
style: Theme.of(context)
.textTheme
.headline2!
.copyWith(color: Colors.black87)),
actions: <Widget>[
TextButton(
child: Text("OK",
style: Theme.of(context)
.textTheme
.bodyText1!
.copyWith(color: Colors.blue)),
onPressed: () {
Navigator.of(context).pop();
},
),
],
),
),
),
);
},
);
}
}
添加字段 onFieldSubmitted
和 FormKey
:
// Widget attribute
final _formKey = GlobalKey<FormState>();
// Widget method: build()
Form(
key: _formKey,
child:
TextFormField(
onFieldSubmitted: (value) {
print('ENTER pressed');
// Will trigger validation for ALL fields in Form.
// All your TextFormFields (email, password) share
// the SAME Form and thus the SAME _formKey.
if (_formKey.currentState!.validate()) {
print('ALL FIELDS ARE VALID, GO ON ...');
}
}
)
)
文档
https://api.flutter.dev/flutter/material/TextFormField-class.html