Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Какие знаешь способы ввода текста?
Ввод текста — один из самых важных элементов пользовательского интерфейса в мобильных приложениях. Flutter предоставляет несколько способов организации и обработки текстового ввода, каждый со своими преимуществами и случаями использования.
1. TextField — стандартный виджет ввода
Базовое использование:
class BasicTextInput extends StatefulWidget {
@override
State<BasicTextInput> createState() => _BasicTextInputState();
}
class _BasicTextInputState extends State<BasicTextInput> {
late TextEditingController _controller;
@override
void initState() {
super.initState();
_controller = TextEditingController();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return TextField(
controller: _controller,
decoration: InputDecoration(
hintText: 'Enter your name',
labelText: 'Name',
border: OutlineInputBorder(),
),
onChanged: (value) {
print('User typed: $value');
},
onSubmitted: (value) {
print('Submitted: $value');
},
);
}
}
Расширенные возможности:
TextField(
controller: _controller,
keyboardType: TextInputType.emailAddress, // Тип клавиатуры
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'[0-9]')), // Только цифры
],
obscureText: true, // Для паролей
maxLength: 20, // Максимум символов
maxLines: 3, // Многострочный
textInputAction: TextInputAction.done, // Кнопка на клавиатуре
decoration: InputDecoration(
prefixIcon: Icon(Icons.email), // Иконка слева
suffixIcon: IconButton(
icon: Icon(Icons.clear),
onPressed: () => _controller.clear(),
),
errorText: _validateEmail(_controller.text),
),
)
2. TextEditingController — управление состоянием
Мониторинг изменений:
class ControllerExample extends StatefulWidget {
@override
State<ControllerExample> createState() => _ControllerExampleState();
}
class _ControllerExampleState extends State<ControllerExample> {
late TextEditingController _controller;
String _displayText = '';
@override
void initState() {
super.initState();
_controller = TextEditingController();
// Слушаем изменения
_controller.addListener(_onTextChanged);
}
void _onTextChanged() {
setState(() {
_displayText = _controller.text;
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
TextField(controller: _controller),
Text('Typed: $_displayText'),
],
);
}
@override
void dispose() {
_controller.removeListener(_onTextChanged);
_controller.dispose();
super.dispose();
}
}
Программное управление:
void _handleTextField() {
// Получить текст
String text = _controller.text;
// Установить текст
_controller.text = 'New value';
// Переместить курсор
_controller.selection = TextSelection.fromPosition(
TextPosition(offset: _controller.text.length),
);
// Выделить всё
_controller.selection = TextSelection(
baseOffset: 0,
extentOffset: _controller.text.length,
);
// Очистить
_controller.clear();
}
3. Валидация текста
Встроенная валидация:
class ValidatedTextField extends StatefulWidget {
@override
State<ValidatedTextField> createState() => _ValidatedTextFieldState();
}
class _ValidatedTextFieldState extends State<ValidatedTextField> {
final _formKey = GlobalKey<FormState>();
late TextEditingController _emailController;
@override
void initState() {
super.initState();
_emailController = TextEditingController();
}
String? _validateEmail(String value) {
if (value.isEmpty) return 'Email cannot be empty';
if (!RegExp(r'^[^@]+@[^@]+\.[^@]+').hasMatch(value)) {
return 'Enter a valid email';
}
return null; // Валиден
}
@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
TextFormField(
controller: _emailController,
decoration: InputDecoration(labelText: 'Email'),
validator: (value) => _validateEmail(value ?? ''),
),
ElevatedButton(
onPressed: () {
if (_formKey.currentState?.validate() ?? false) {
print('Form is valid');
}
},
child: Text('Submit'),
),
],
),
);
}
@override
void dispose() {
_emailController.dispose();
super.dispose();
}
}
4. Различные типы клавиатур
TextField(
keyboardType: TextInputType.text, // Обычный текст
),
TextField(
keyboardType: TextInputType.number, // Цифры
),
TextField(
keyboardType: TextInputType.emailAddress, // Email
),
TextField(
keyboardType: TextInputType.phone, // Телефон
),
TextField(
keyboardType: TextInputType.url, // URL
),
TextField(
keyboardType: TextInputType.multiline, // Многострочный
),
TextField(
keyboardType: TextInputType.datetime, // Дата/время
),
5. Форматирование ввода (Input Formatters)
Маски для ввода:
import 'package:mask_text_input_formatter/mask_text_input_formatter.dart';
class FormattedInput extends StatelessWidget {
final maskFormatter = MaskTextInputFormatter(
mask: '+# (###) ###-##-##',
filter: {'#': RegExp(r'[0-9]')},
);
@override
Widget build(BuildContext context) {
return TextField(
inputFormatters: [maskFormatter],
decoration: InputDecoration(hintText: '+1 (234) 567-89-01'),
);
}
}
Встроенные фильтры:
TextField(
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'[0-9]')), // Только цифры
FilteringTextInputFormatter.deny(RegExp(r'[^a-zA-Z]')), // Без спецсимволов
LengthLimitingTextInputFormatter(10), // Макс 10 символов
UpperCaseTextFormatter(), // Верхний регистр
],
)
Кастомный форматер:
class CreditCardFormatter extends TextInputFormatter {
@override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue,
TextEditingValue newValue,
) {
final text = newValue.text;
if (text.isEmpty) return newValue;
// Форматируем: 1234 5678 9012 3456
final buffer = StringBuffer();
for (int i = 0; i < text.length; i++) {
if (i > 0 && i % 4 == 0) buffer.write(' ');
buffer.write(text[i]);
}
return TextEditingValue(
text: buffer.toString(),
selection: TextSelection.collapsed(offset: buffer.length),
);
}
}
TextField(
inputFormatters: [CreditCardFormatter()],
)
6. Фокус и клавиатура
class FocusExample extends StatefulWidget {
@override
State<FocusExample> createState() => _FocusExampleState();
}
class _FocusExampleState extends State<FocusExample> {
late FocusNode _focusNode;
@override
void initState() {
super.initState();
_focusNode = FocusNode();
// Слушаем изменения фокуса
_focusNode.addListener(_onFocusChange);
}
void _onFocusChange() {
if (_focusNode.hasFocus) {
print('TextField focused');
} else {
print('TextField unfocused');
}
}
@override
Widget build(BuildContext context) {
return Column(
children: [
TextField(
focusNode: _focusNode,
decoration: InputDecoration(labelText: 'Username'),
),
ElevatedButton(
onPressed: () {
if (_focusNode.hasFocus) {
// Закрыть клавиатуру
FocusScope.of(context).unfocus();
} else {
// Открыть клавиатуру
FocusScope.of(context).requestFocus(_focusNode);
}
},
child: Text('Toggle Focus'),
),
],
);
}
@override
void dispose() {
_focusNode.dispose();
super.dispose();
}
}
7. Поиск с автодополнением
class SearchableTextField extends StatefulWidget {
@override
State<SearchableTextField> createState() => _SearchableTextFieldState();
}
class _SearchableTextFieldState extends State<SearchableTextField> {
late TextEditingController _controller;
List<String> _suggestions = [];
final List<String> _allItems = ['Apple', 'Apricot', 'Banana', 'Cherry'];
@override
void initState() {
super.initState();
_controller = TextEditingController();
_controller.addListener(_updateSuggestions);
}
void _updateSuggestions() {
setState(() {
_suggestions = _allItems
.where((item) => item.toLowerCase().contains(
_controller.text.toLowerCase(),
))
.toList();
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
TextField(
controller: _controller,
decoration: InputDecoration(labelText: 'Search'),
),
Expanded(
child: ListView.builder(
itemCount: _suggestions.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(_suggestions[index]),
onTap: () {
_controller.text = _suggestions[index];
},
);
},
),
),
],
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
8. Специальные виджеты ввода
Числовой ввод:
TextField(
keyboardType: TextInputType.number,
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
decoration: InputDecoration(labelText: 'Age'),
)
Дата/время:
ElevatedButton(
onPressed: () async {
final DateTime? date = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(1900),
lastDate: DateTime.now(),
);
if (date != null) {
print('Selected date: $date');
}
},
child: Text('Pick Date'),
)
Пароль с toggle видимости:
class PasswordField extends StatefulWidget {
@override
State<PasswordField> createState() => _PasswordFieldState();
}
class _PasswordFieldState extends State<PasswordField> {
bool _obscured = true;
late TextEditingController _controller;
@override
void initState() {
super.initState();
_controller = TextEditingController();
}
@override
Widget build(BuildContext context) {
return TextField(
controller: _controller,
obscureText: _obscured,
decoration: InputDecoration(
labelText: 'Password',
suffixIcon: IconButton(
icon: Icon(_obscured ? Icons.visibility_off : Icons.visibility),
onPressed: () => setState(() => _obscured = !_obscured),
),
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
Лучшие практики
- Всегда используйте TextEditingController в StatefulWidget — управляйте жизненным циклом
- Вызывайте dispose() — освобождайте ресурсы
- Используйте TextFormField в Form — для валидации
- Применяйте InputFormatters — для контроля ввода
- Обрабатывайте фокус — улучшайте UX
- Валидируйте данные — показывайте ошибки пользователю
- Используйте правильный keyboardType — для лучшего UX
Текстовый ввод — критическая часть мобильного интерфейса, и правильное его использование значительно улучшает пользовательский опыт.