← Назад к вопросам
Как обрабатывать пользовательский ввод во Flutter?
1.2 Junior🔥 192 комментариев
#Flutter виджеты
Комментарии (2)
🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Обработка пользовательского ввода во Flutter
Обработка ввода — это фундаментальная часть любого приложения. Рассмотрю полный спектр: от текстовых полей до жестов.
1. TextField — текстовое поле
class TextInputExample extends StatefulWidget {
@override
State<TextInputExample> createState() => _TextInputExampleState();
}
class _TextInputExampleState extends State<TextInputExample> {
late TextEditingController controller;
String? _error;
@override
void initState() {
super.initState();
controller = TextEditingController();
// Слушать изменения
controller.addListener(() {
setState(() {
_error = _validateEmail(controller.text);
});
});
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
String? _validateEmail(String email) {
if (email.isEmpty) return 'Email обязателен';
if (!email.contains('@')) return 'Неверный email';
return null;
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: TextField(
controller: controller,
decoration: InputDecoration(
labelText: 'Email',
hintText: 'введите email',
errorText: _error,
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.email),
suffixIcon: controller.text.isNotEmpty
? IconButton(
icon: Icon(Icons.clear),
onPressed: () => controller.clear(),
)
: null,
),
keyboardType: TextInputType.emailAddress,
textInputAction: TextInputAction.next,
),
);
}
}
2. Обработка различных типов ввода
class FormInputsExample extends StatefulWidget {
@override
State<FormInputsExample> createState() => _FormInputsExampleState();
}
class _FormInputsExampleState extends State<FormInputsExample> {
final formKey = GlobalKey<FormState>();
final nameController = TextEditingController();
final emailController = TextEditingController();
final passwordController = TextEditingController();
DateTime? selectedDate;
String? selectedOption;
bool agreedToTerms = false;
@override
void dispose() {
nameController.dispose();
emailController.dispose();
passwordController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Форма')),
body: Form(
key: formKey,
child: ListView(
padding: EdgeInsets.all(16),
children: [
// Текстовое поле
TextFormField(
controller: nameController,
decoration: InputDecoration(labelText: 'Имя'),
validator: (value) {
if (value?.isEmpty ?? true) return 'Введите имя';
return null;
},
),
SizedBox(height: 16),
// Email
TextFormField(
controller: emailController,
decoration: InputDecoration(labelText: 'Email'),
keyboardType: TextInputType.emailAddress,
validator: (value) {
if (value?.isEmpty ?? true) return 'Введите email';
if (!value!.contains('@')) return 'Неверный email';
return null;
},
),
SizedBox(height: 16),
// Пароль
TextFormField(
controller: passwordController,
decoration: InputDecoration(
labelText: 'Пароль',
suffixIcon: IconButton(
icon: Icon(Icons.visibility),
onPressed: () => setState(() {}),
),
),
obscureText: true,
validator: (value) {
if (value?.isEmpty ?? true) return 'Введите пароль';
if ((value?.length ?? 0) < 6) return 'Минимум 6 символов';
return null;
},
),
SizedBox(height: 16),
// Выпадающий список
DropdownButtonFormField<String>(
value: selectedOption,
items: ['Option 1', 'Option 2', 'Option 3']
.map((e) => DropdownMenuItem(value: e, child: Text(e)))
.toList(),
onChanged: (value) => setState(() => selectedOption = value),
decoration: InputDecoration(labelText: 'Выберите опцию'),
validator: (value) {
if (value == null) return 'Выберите опцию';
return null;
},
),
SizedBox(height: 16),
// Дата
GestureDetector(
onTap: () async {
final date = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(1900),
lastDate: DateTime.now(),
);
if (date != null) {
setState(() => selectedDate = date);
}
},
child: InputDecorator(
decoration: InputDecoration(labelText: 'Дата рождения'),
child: Text(
selectedDate?.toString().split(' ')[0] ?? 'Выберите дату',
),
),
),
SizedBox(height: 16),
// Checkbox
CheckboxListTile(
title: Text('Согласен с условиями'),
value: agreedToTerms,
onChanged: (value) => setState(() => agreedToTerms = value ?? false),
),
SizedBox(height: 24),
// Кнопка отправки
ElevatedButton(
onPressed: () {
if (formKey.currentState!.validate() && agreedToTerms) {
print('Form submitted');
print('Name: ${nameController.text}');
print('Email: ${emailController.text}');
}
},
child: Text('Отправить'),
),
],
),
),
);
}
}
3. Обработка жестов (Gesture)
class GestureInputExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Tap
GestureDetector(
onTap: () => print('Тап!'),
onDoubleTap: () => print('Двойной тап!'),
onLongPress: () => print('Длинный нажим!'),
child: Container(
width: 200,
height: 200,
color: Colors.blue,
child: Center(child: Text('Тап меня')),
),
),
SizedBox(height: 32),
// Drag
DraggableExample(),
SizedBox(height: 32),
// Swipe
SwipeDetectorExample(),
],
),
),
);
}
}
// Перетаскивание
class DraggableExample extends StatefulWidget {
@override
State<DraggableExample> createState() => _DraggableExampleState();
}
class _DraggableExampleState extends State<DraggableExample> {
Offset position = Offset.zero;
@override
Widget build(BuildContext context) {
return Positioned(
left: position.dx,
top: position.dy,
child: GestureDetector(
onPanUpdate: (details) {
setState(() {
position += details.delta;
});
},
child: Container(
width: 100,
height: 100,
color: Colors.red,
child: Center(child: Text('Тащи меня')),
),
),
);
}
}
// Свайпы
class SwipeDetectorExample extends StatefulWidget {
@override
State<SwipeDetectorExample> createState() => _SwipeDetectorExampleState();
}
class _SwipeDetectorExampleState extends State<SwipeDetectorExample> {
String direction = '';
void _handleSwipe(DragEndDetails details) {
const double swipeThreshold = 200.0;
if (details.velocity.pixelsPerSecond.dx.abs() > swipeThreshold) {
if (details.velocity.pixelsPerSecond.dx > 0) {
setState(() => direction = 'Свайп вправо');
} else {
setState(() => direction = 'Свайп влево');
}
} else if (details.velocity.pixelsPerSecond.dy.abs() > swipeThreshold) {
if (details.velocity.pixelsPerSecond.dy > 0) {
setState(() => direction = 'Свайп вниз');
} else {
setState(() => direction = 'Свайп вверх');
}
}
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onPanEnd: _handleSwipe,
child: Container(
width: 200,
height: 100,
color: Colors.green,
child: Center(child: Text(direction.isNotEmpty ? direction : 'Свайпай')),
),
);
}
}
4. Обработка клавиатуры
class KeyboardInputExample extends StatefulWidget {
@override
State<KeyboardInputExample> createState() => _KeyboardInputExampleState();
}
class _KeyboardInputExampleState extends State<KeyboardInputExample> {
final controller = TextEditingController();
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
onTap: () => FocusScope.of(context).unfocus(), // Скрыть клавиатуру
child: Column(
children: [
TextField(
controller: controller,
decoration: InputDecoration(labelText: 'Введите текст'),
onSubmitted: (value) {
print('Отправлено: $value');
FocusScope.of(context).unfocus();
},
textInputAction: TextInputAction.send,
),
SizedBox(height: 16),
// Кнопка для скрытия клавиатуры
ElevatedButton(
onPressed: () {
FocusScope.of(context).unfocus();
},
child: Text('Скрыть клавиатуру'),
),
],
),
),
);
}
}
5. Валидация формы
class FormValidationExample extends StatefulWidget {
@override
State<FormValidationExample> createState() => _FormValidationExampleState();
}
class _FormValidationExampleState extends State<FormValidationExample> {
final formKey = GlobalKey<FormState>();
String? validateUsername(String? value) {
if (value?.isEmpty ?? true) return 'Username обязателен';
if (value!.length < 3) return 'Минимум 3 символа';
if (!RegExp(r'^[a-zA-Z0-9_]+$').hasMatch(value)) {
return 'Только буквы, цифры и подчёркивание';
}
return null;
}
String? validatePassword(String? value) {
if (value?.isEmpty ?? true) return 'Пароль обязателен';
if (value!.length < 8) return 'Минимум 8 символов';
if (!RegExp(r'[A-Z]').hasMatch(value)) return 'Требуется заглавная буква';
if (!RegExp(r'[0-9]').hasMatch(value)) return 'Требуется цифра';
return null;
}
String? validateEmail(String? value) {
if (value?.isEmpty ?? true) return 'Email обязателен';
const emailRegex = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$';
if (!RegExp(emailRegex).hasMatch(value!)) return 'Неверный email';
return null;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Валидация')),
body: Form(
key: formKey,
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
children: [
TextFormField(
decoration: InputDecoration(labelText: 'Username'),
validator: validateUsername,
),
SizedBox(height: 16),
TextFormField(
decoration: InputDecoration(labelText: 'Email'),
validator: validateEmail,
),
SizedBox(height: 16),
TextFormField(
decoration: InputDecoration(labelText: 'Пароль'),
obscureText: true,
validator: validatePassword,
),
SizedBox(height: 32),
ElevatedButton(
onPressed: () {
if (formKey.currentState!.validate()) {
print('Форма валидна!');
}
},
child: Text('Отправить'),
),
],
),
),
),
);
}
}
Чеклист для production
- Используй
TextEditingControllerи не забывайdispose() - Валидируй ввод на клиенте
- Показывай ошибки пользователю
- Обработай всё вручную введённые данные на сервере
- Используй
TextFormFieldсGlobalKey<FormState>для форм - Скрывай клавиатуру когда она не нужна
- Тестируй с разными длинами текста
- Используй правильный
keyboardType(email, number и т.д.) - Обработай пустой ввод
- Покажи feedback (снэки, диалоги) о результатах