← Назад к вопросам

Какие знаешь способы ввода текста?

2.0 Middle🔥 161 комментариев
#Flutter виджеты

Комментарии (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();
  }
}

Лучшие практики

  1. Всегда используйте TextEditingController в StatefulWidget — управляйте жизненным циклом
  2. Вызывайте dispose() — освобождайте ресурсы
  3. Используйте TextFormField в Form — для валидации
  4. Применяйте InputFormatters — для контроля ввода
  5. Обрабатывайте фокус — улучшайте UX
  6. Валидируйте данные — показывайте ошибки пользователю
  7. Используйте правильный keyboardType — для лучшего UX

Текстовый ввод — критическая часть мобильного интерфейса, и правильное его использование значительно улучшает пользовательский опыт.