Создание интерактивного экрана входа в систему с помощью Flare & Flutter

Наша команда в 2Dimensions недавно столкнулась с взаимодействием формы входа Remembear: мы подумали, что это прекрасный пример, который мы могли бы создать во Flare и поделиться с сообществом!

Исходный код доступен на GitHub, а файл Flare можно найти в 2Dimensions.

обзор

Во-первых, нам нужно импортировать библиотеку flare_flutter в pubspec.yaml (примечание. Мы используем относительный путь, поскольку мы находимся в репозитории библиотеки, но пакет также доступен в DartPub). Мы также добавили папку assets в pubspec.yaml, чтобы ее содержимое было доступно во Flutter.

Все соответствующие файлы находятся в папке / lib, а файл Flare - в папке assets:

/ Lib
  - input_helper.dart
  - main.dart
  - signin_button.dart
  - teddy_controller.dart
  - tracking_text_input.dart
/ активы
  - Teddy.flr

Как это работает

Давайте сначала посмотрим на Тедди в Flare: у этого персонажа есть узел с именем ctrl_face, который является целью для ограничения перевода элементов лица. Это означает, что перемещение узла также приведет к перемещению всех его зависимых элементов.

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

В код

В main.dart MyHomePage создает макет для приложения.
Мы используем виджет FlareActor из библиотеки flare_flutter для размещения анимации в представлении:

[...]
FlareActor (
  "Активы / Teddy.flr",
  // Привязать FlareController
  контроллер: _teddyController
  [...]
)

Поскольку мы хотим манипулировать положением узла ctrl_face, мы связываем _teddyController с нашим FlareActor. Контроллер - это конкретная реализация FlareController, интерфейса, предоставляемого flare_flutter, и он дает нам возможность запрашивать и манипулировать иерархией Flare.

Пользовательские элементы управления

Давайте взглянем на классTeddyController: вы заметите, что TeddyController расширяет FlareControls, а не FlareController!
FlareControls - это конкретная реализация FlareController, которую flare_flutter уже предоставляет, и она имеет некоторые базовые функции воспроизведения / микширования.

TeddyController имеет несколько полей:

// Матрица для преобразования глобальных координат Flutter
// в мировые координаты вспышки.
Mat2D _globalToFlareWorld = Mat2D ();
// Ссылка на узел `ctrl_look`.
ActorNode _faceControl;
// Сохраняем начало узла в мировых и локальных пространствах преобразования.
Vec2D _faceOrigin = Vec2D ();
Vec2D _faceOriginLocal = Vec2D ();
// Каретка в глобальных координатах Flutter и в мировых координатах Flare.
Vec2D _caretGlobal = Vec2D ();
Vec2D _caretWorld = Vec2D ()

Этот класс затем должен будет переопределить три метода: initialize (), advance () и setViewTransform ().
вызывается initialize () - вы уже догадались! - во время инициализации, когда создается виджет FlareActor. Это где наша ссылка на узел сначала выбирается, снова с библиотечным вызовом:

_faceControl = artboard.getNode ("ctrl_face");
if (_faceControl! = null) {
  _faceControl.getWorldTranslation (_faceOrigin);
  Vec2D.copy (_faceOriginLocal, _faceControl.translation);
}
играть ( "холостой ход");

Артборды в Flare - это контейнеры верхнего уровня для узлов, форм и анимации. artboard.getNode (String name) возвращает ссылку на ActorNode с указанным именем.

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

Два других переопределения называются каждым кадром: setViewTransform () используется здесь для построения _globalToFlareWorld - это матрица для преобразования глобальных координат экрана Flutter в мировые координаты Flare.

В методе advance () все вышеперечисленное объединяется!
Когда пользователь начинает печатать, TrackingTextInput будет переводить позицию экрана каретки в _caretGlobal. С помощью этой координаты контроллер может вычислить новую позицию ctrl_face, тем самым сместив свой взгляд.

// Проецируем взгляд на столько пикселей.
static const double _projectGaze = 60.0;
[...]
// Получить карету в космическом мире Flare.
Vec2D.transformMat2D (
  _caretWorld, _caretGlobal, _globalToFlareWorld);
[...]
// Вычислить вектор направления.
Vec2D toCaret = Vec2D.subtract (Vec2D (), _caretWorld, _faceOrigin);
Vec2D.normalize (toCaret, toCaret);
// Масштабировать направление с постоянным значением.
Vec2D.scale (toCaret, toCaret, _projectGaze);
// Вычисляем преобразование, которое приводит нас в пространство лица ctrl_face.
Mat2D toFaceTransform = Mat2D ();
if (Mat2D.invert (toFaceTransform,
        _faceControl.parent.worldTransform)) {
  // Поместить toCaret в локальное пространство.
  // N.B. мы используем вектор направления, а не перевод,
  // поэтому используем transformMat2 () для преобразования без перевода
  Vec2D.transformMat2 (toCaret, toCaret, toFaceTransform);
  // Конечная позиция ctrl_face - это исходный перевод лица
  // плюс этот вектор направления
  targetTranslation = Vec2D.add (Vec2D (), toCaret, _faceOriginLocal);
}

Поскольку картинка стоит тысячи слов - или в данном случае строк кода - ниже, мы можем видеть, как вычисляется направление: вектор разности сохраняется в toCaret.

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

Наконец, мы преобразуем toCaret в собственное пространство узла, чтобы мы могли добавить его к исходному переводу узла.

Позиция каретки

Последний кусочек головоломки - как вычислить положение каретки на экране.

Это делается в виджете TrackingTextInput. Этот виджет хранит ссылку на GlobalKey для создания его TextFormFields. С помощью этого ключа Flutter позволяет нам получить RenderObject, который включает в себя этот TextFormField:

RenderObject fieldBox = _fieldKey.currentContext.findRenderObject ();

С помощью трех вспомогательных функций, доступных в lib / input_helper.dart, мы можем использовать RenderBox для вычисления фактической позиции каретки в экранных координатах, просматривая иерархию виджетов из этого RenderBox и ища RenderEditable. Этот класс Flutter предоставляет метод getEndpointsForSelection (), который используется для вычисления локальных координат, которые могут быть преобразованы в глобальные координаты с помощью originalRenderBox.

Вот и все!

Еще раз, не забудьте проверить источники на GitHub и Flare, и присоединяйтесь к нам на 2Dimensions.com!