RawAutocomplete flutter примеры

Обзоры > Flutter > RawAutocomplete flutter примеры

Был у нас в свое время написан компонет DropDown с выпадающим списком, поиском, выделением и прочими фичами. Но что-то он мне все время не нравился, да и за новыми хотелками дизайнеров не успевал — костыль на костыль )) пришлось бы делать. Поэтому было принято решение написать новый компонент, только теперь не писать полностью с нуля, а использовать RawAutocomplete, чтобы он взял на себя подкапотную работу с Overlay. (По сравнению с Autocomplete RawAutocomplete позволяет дополнительно еще прокидывать focusNode и textEditingController, что поможет нам написать более гибкий виджет.)

Свои изыскания я и попытаюсь изложить здесь для себя (чтоб потом не вспоминать, что там и как) и для коллег.

Задача

Некоторые требования, на которых заострим внимание:

  • Выпадающий список должен сам «понимать», куда ему расположиться, вниз от поля ввода или наверх. Очень желательно, чтобы при изменении размеров экрана (браузера в вебе) он перестраивался. Факультативно, чтобы и при скролле, если компонент находится в списке, тоже мог бы перестроиться.
  • У компонента должен быть режим «выбор одного значения», когда после тапа на элемент, список закрывается, а в поле ввода отображается его представление.
  • У компонента должен быть режим «выбор нескольких значений», когда после тапа на элемент, список не закрывается, а позволяет выбирать другие элементы. В поле ввода ничего не отображается, поле ввода используется исключительно в качестве «строки поиска».
  • У компонента будет возможность как загрузить сразу полный список, так и подгружать по мере пролистывания (подразумевается, что подтягиваем таким образом с бэка большие списки).
к содержанию ↑

Параметры

optionsBuilder

Обязательный параметр. Функция, асинхронная или нет, возвращающая список для отображения, в зависимости от изменения текстового поля. Срабатывает, когда пользователь вводит (меняет данные) или мы напрямую в textEditingController корректируем value.

  // Called when _textEditingController changes.
  Future<void> _onChangedField() async {
    final TextEditingValue value = _textEditingController.value;
    final Iterable<T> options = await widget.optionsBuilder(value);
    _options = options;

Мы можем напрямую передавать свой список, но его изменение не вызовет перестройку виджетов, если не перестроить весь виджет, конечно. Что-то типа: optionsBuilder: (_) => _ourOptions,

Простейший вариант использования (из официальной документации):

      optionsBuilder: (TextEditingValue textEditingValue) {
        return _options.where((String option) {
          return option.contains(textEditingValue.text.toLowerCase());
        });
      },
к содержанию ↑

optionsViewBuilder

Обязательный параметр. Функция, которая возвращает виджет. В нее передаются context, коллбек выбора элемента onSelected и «список» объектов, которые нужно отобразить (сформированный в optionsBuilder). Чтобы внутри можно было определить индекс выбранного элемента, билдер оборачивается в AutocompleteHighlightedOption, из которого мы и можем получать нужную информацию: int highlightedIndex = AutocompleteHighlightedOption.of(context);

Список опять же можем «брать» свой из «замыкания», а не из параметров функции.

к содержанию ↑

focusNode

Передаваемый внутрь фокус. Слушатель на наш фокус вешается в initState.

  FocusNode? _internalFocusNode;
  FocusNode get _focusNode {
    return widget.focusNode ?? (_internalFocusNode ??= FocusNode()..addListener(_updateOptionsViewVisibility));
  }

textEditingController

Передаваемый внутрь контроллер поля ввода.

  TextEditingController? _internalTextEditingController;
  TextEditingController get _textEditingController {
    return widget.textEditingController ?? (_internalTextEditingController ??= TextEditingController()..addListener(_onChangedField));
  }

optionsViewOpenDirection

Это просто направление, в котором открывается список: вниз или наверх.

displayStringForOption

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

который представляет из себя ToString для нашего выбранного элемента.

Используется в хэндлере изменений текстового поля для сравнения текущего текста и представления для выбранного элемента. а так же для формирования текста при выборе элемента в списке.

к содержанию ↑

onSelected

Это просто коллбек, который вызывается при выборе элемента в списке. И он же передается в optionsViewBuilder.

fieldViewBuilder

Здесь мы просто «рисуем» наш текстовый инпут. На вход этой функции передается context, контроллер (наш или созданный при инициализации), фокус и функция, которая отрабатывает завершение ввода в поле, как будто тапнули на текущем элементе.

Если ничего не передаем в fieldViewBuilder, то обязаны через контроллер связать наш RawAutocompleter с «внешним» полем ввода.

к содержанию ↑

initialValue

Стартовое значение, которое «раньше выбрано». Игнорируется, если мы передали контроллер, потому что участвует только в создании внутреннего:

    final TextEditingController initialController =
        widget.textEditingController ?? (_internalTextEditingController = TextEditingController.fromValue(widget.initialValue));
к содержанию ↑

Реализация

Общие установки

В отличии от собственного компонента, мы не можем напрямую управлять показом списка через OverlayPortalController. За отображение отвечает вот этот вот код:

Фокус в поле, список не пустой… По факту, мы сможем только занулять _selection… А зануляется оно только в случае:

    final T? selection = _selection;
    if (selection != null && value.text != widget.displayStringForOption(selection)) {
      _selection = null;
    }

в _onChangedField, т.е. при изменении текста в контроллере. А при выборе элемента из null «превращается» в выбранный элемент. Таким образом, чтобы показывать список при помещении фокуса в поле ввода или при вводе текста, мы используем «костыль», а именно будем выдавать в displayStringForOption всегда уникальный текст, а потом через контроллер заменять его на нужный нам:

displayStringForOption: (_) => 'Уникальный текст RawAutocomplete',
к содержанию ↑

Выбор одного элемента

Выстраиваем следующий процесс.

Первый клик по пустому полю ввода. Фокус есть, _selection = null, список какой-то есть. А точнее, полный. Значит список открывается. Начинаем вводить текст в поле. Отрабатывает _onChangedField, который вызывает optionsBuilder, список уменьшается. Выбираем один из элементов, отрабатывает _select, который помещает в _selection выбранный объект и в текст контроллера записывает наш уникальный текст из displayStringForOption. Вызывается наш onSelected, в котором мы в текст подставляем уже наше представление элемента. Снова отрабатывает _onChangedField и _selection зануляется, потому что наше представление не совпадает с уникальной строкой. Но мы убираем фокус из поля ввода и из-за этого список скрывается.

Новый клик по полю ввода, в нем текстовое представление нашего элемента. Оно используется для фильтрации списка, а так как мы выбирали элемент, то он, как минимум, и покажется, потому что _selection уже null и условия достаточны.

Все ок…

к содержанию ↑

Выбор нескольких элементов

Первый клик по пустому полю ввода, все как и в предыдущем случае — список отображается. Только вместо представления нашего элемента подменяем на пустое поле и ничего не выводим. (Набранный таким образом список отображается в другом месте).

Новый клик, отображается весь список, потому что фильтровать не почему, _selection был сброшен ранее.

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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *