Я хочу отключить кнопку, пока поле текстовой формы не станет действительным. И затем, как только данные действительны, кнопка должна быть включена. Я получил помощь по SO ранее с аналогичным вопросом, но, похоже, не могу применить то, что я узнал, к этой проблеме. Данные действительны, когда пользователь добавляет более 3 символов и менее 20. Я создал логическое значение (_isValidated) и добавил его в метод validateUserName, вызывающий setState после того, как пользователь ввел действительные данные, но это определенно неправильно и генерирует сообщение об ошибке. . Сообщение об ошибке:
setState() или markNeedsBuild(), вызываемые во время сборки.
Я не могу понять, что я делаю неправильно. Любая помощь будет оценена по достоинству. Спасибо.
class CreateUserNamePage extends StatefulWidget {
const CreateUserNamePage({
Key? key,
}) : super(key: key);
@override
_CreateUserNamePageState createState() => _CreateUserNamePageState();
}
class _CreateUserNamePageState extends State<CreateUserNamePage> {
final _formKey = GlobalKey<FormState>();
bool _isValidated = false;
late String userName;
final TextEditingController _userNameController = TextEditingController();
@override
void initState() {
super.initState();
_userNameController.addListener(() {
setState(() {});
});
}
void _clearUserNameTextField() {
setState(() {
_userNameController.clear();
});
}
String? _validateUserName(value) {
if (value!.isEmpty) {
return ValidatorString.userNameRequired;
} else if (value.trim().length < 3) {
return ValidatorString.userNameTooShort;
} else if (value.trim().length > 20) {
return ValidatorString.userNameTooLong;
} else {
setState(() {
_isValidated = true;
});
return null;
}
}
void _createNewUserName() {
final form = _formKey.currentState;
if (form!.validate()) {
form.save();
}
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Welcome $userName'),
),
);
Timer(const Duration(seconds: 2), () {
Navigator.pop(context, userName);
});
}
@override
void dispose() {
_userNameController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final isPortrait =
MediaQuery.of(context).orientation == Orientation.portrait;
final screenHeight = MediaQuery.of(context).size.height;
return WillPopScope(
onWillPop: () async => false,
child: Scaffold(
appBar: CreateUserNameAppBar(
preferredAppBarSize:
isPortrait ? screenHeight / 15.07 : screenHeight / 6.96,
),
body: ListView(
children: [
Column(
children: [
const CreateUserNamePageHeading(),
CreateUserNameTextFieldTwo(
userNameController: _userNameController,
createUserFormKey: _formKey,
onSaved: (value) => userName = value as String,
suffixIcon: _userNameController.text.isEmpty
? const EmptyContainer()
: ClearTextFieldIconButton(
onPressed: _clearUserNameTextField,
),
validator: _validateUserName,
),
CreateUserNameButton(
buttonText: ButtonString.createUserName,
onPressed: _isValidated ? _createNewUserName : null,
),
],
),
],
),
),
);
}
}
Я думаю, что лучший способ — присвоить нулевое значение параметру onPressed кнопки. Пожалуйста, проверьте ссылку ниже. https://www.flutterbeads.com/disable-button-in-flutter/
Спасибо за ссылку @Archer. Я просмотрю его сейчас и посмотрю, поможет ли он мне в моей проблеме.
У вас есть пользовательские виджеты, поэтому я не знаю, как работают ваши виджеты, но здесь вы можете использовать AbsorbPointer, чтобы отключить кнопку и проверить текст поля текстовой формы в параметре onChange, как здесь;
bool isDisabled = true;
String _validateUserName(value) {
if (value!.isEmpty) {
return ValidatorString.userNameRequired;
} else if (value.trim().length < 3) {
return ValidatorString.userNameTooShort;
} else if (value.trim().length > 20) {
return ValidatorString.userNameTooLong;
} else {
setState(() {
isDisabled = false;
});
return null;
}
}
@override
Widget build(BuildContext context) {
final ButtonStyle style =
ElevatedButton.styleFrom(textStyle: const TextStyle(fontSize: 20));
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ElevatedButton(
style: style,
onPressed: null,
child: const Text('Disabled'),
),
const SizedBox(height: 30),
TextFormField(
decoration: const InputDecoration(
labelText: 'Label',
),
onChanged: (String value) {
_validateUserName(value);
},
),
AbsorbPointer(
absorbing: isDisabled, // by default is true
child: TextButton(
onPressed: () {},
child: Text("Button Click!!!"),
),
),
],
),
);
}
Я буду читать об AbsorbPointer, так как никогда о нем не слышал. Спасибо за ответ. Это может занять некоторое время, так как я все еще учусь.
Просто используйте проверку формы, внутри TextFormField
отредактируйте функцию валидатора, добавьте функцию onChange и вызовите setState
, чтобы получить inputtedValue, который также может держать кнопку отключения, если форма не проверена.
Ключевые моменты, на которые следует обратить внимание:
final _formKey = GlobalKey<FormState>();
String? inputtedValue;
и !userInteracts()
— это трюки, вы можете обратиться к коду;!userInteracts() || _formKey.currentState == null || !_formKey.currentState!.validate()
Код здесь:
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
@override
Widget build(BuildContext context) {
return const MaterialApp(
title: _title,
home: MyCustomForm(),
);
}
}
class MyCustomForm extends StatefulWidget {
const MyCustomForm({Key? key}) : super(key: key);
@override
MyCustomFormState createState() {
return MyCustomFormState();
}
}
// Create a corresponding State class.
// This class holds data related to the form.
class MyCustomFormState extends State<MyCustomForm> {
// Create a global key that uniquely identifies the Form widget
// and allows validation of the form.
//
// Note: This is a GlobalKey<FormState>,
// not a GlobalKey<MyCustomFormState>.
final _formKey = GlobalKey<FormState>();
// recording fieldInput
String? inputtedValue;
// you can add more fields if needed
bool userInteracts() => inputtedValue != null;
@override
Widget build(BuildContext context) {
// Build a Form widget using the _formKey created above.
return Scaffold(
appBar: AppBar(
title: const Text('Form Disable Button Demo'),
),
body: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextFormField(
// The validator receives the text that the user has entered.
validator: (value) {
if (inputtedValue != null && (value == null || value.isEmpty)) {
return 'Please enter some text';
}
return null;
},
onChanged: (value) => setState(() => inputtedValue = value),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: ElevatedButton(
// return null will disable the button
// Validate returns true if the form is valid, or false otherwise.
onPressed: !userInteracts() || _formKey.currentState == null || !_formKey.currentState!.validate() ? null :() {
// If the form is valid, display a snackbar. In the real world,
// you'd often call a server or save the information in a database.
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Processing Data: ' + inputtedValue!)),
);
},
child: const Text('Submit'),
),
),
],
),
),
);
}
}
Вы можете использовать виджет, называемый поглощающим указателем, который указывает на функцию bool