Creación de una pantalla de inicio de sesión interactiva con Flare & Flutter

Nuestro equipo de 2Dimensions recientemente se encontró con la interacción del formulario de inicio de sesión de Remembear: ¡pensamos que este era un ejemplo perfecto que podríamos construir en Flare y compartir con la comunidad!

El código fuente está disponible en GitHub, y el archivo Flare se puede encontrar en dimensiones 2D.

Visión general

Primero, necesitamos importar la biblioteca flare_flutter en pubspec.yaml (N.B. Utilizamos una ruta relativa ya que estamos en el repositorio de la biblioteca, pero el paquete también está disponible en DartPub). También agregamos la carpeta de activos a pubspec.yaml para que su contenido sea accesible en Flutter.

Todos los archivos relevantes están en la carpeta / lib, mientras que el archivo Flare está en la carpeta de activos:

/ lib
  - input_helper.dart
  - main.dart
  - signin_button.dart
  - teddy_controller.dart
  - tracking_text_input.dart
/bienes
  - Teddy.flr

Cómo funciona esto

Primero echemos un vistazo a Teddy in Flare: este personaje tiene un nodo llamado ctrl_face, que es el objetivo de la restricción de traducción de los elementos de la cara. Esto significa que mover el nodo hará que todos sus dependientes se muevan también.

Al tomar la referencia al nodo ctrl_face, podemos mover la cara de Teddy y ajustar la dirección de su mirada. Solo necesitaremos encontrar la posición del campo de texto debajo de Teddy y ajustar la posición del nodo ctrl_face según corresponda.

En el código

En main.dart, MyHomePage crea el diseño de la aplicación.
Usamos el widget FlareActor de la biblioteca flare_flutter para colocar la animación en la vista:

[...]
FlareActor (
  "activos / Teddy.flr",
  // Vincula un FlareController
  controlador: _teddyController
  [...]
)

Como queremos manipular la posición del nodo ctrl_face, vinculamos _teddyController a nuestro FlareActor. Un controlador es una implementación concreta de FlareController, una interfaz proporcionada por flare_flutter, y nos brinda la capacidad de consultar y manipular la jerarquía de Flare.

Controles personalizados

Echemos un vistazo a la clase TeddyController: ¡notará que TeddyController extiende FlareControls y no FlareController!
FlareControls es una implementación concreta de FlareController que flare_flutter ya proporciona, y tiene algunas funciones básicas de reproducción / mezcla.

TeddyController tiene algunos campos:

// Matriz para transformar las coordenadas globales de Flutter
// en las coordenadas del mundo Flare.
Mat2D _globalToFlareWorld = Mat2D ();
// Una referencia al nodo `ctrl_look`.
ActorNode _faceControl;
// Almacene el origen del nodo en espacios de transformación mundiales y locales.
Vec2D _faceOrigin = Vec2D ();
Vec2D _faceOriginLocal = Vec2D ();
// Caret en las coordenadas globales de Flutter y en las coordenadas del mundo Flare.
Vec2D _caretGlobal = Vec2D ();
Vec2D _caretWorld = Vec2D ()

Esta clase necesitará anular tres métodos: initialize (), advance () y setViewTransform ().
Se llama initialize (), ¡lo has adivinado! - en el momento de la inicialización, cuando se construye el widget FlareActor. Aquí es donde primero se obtiene nuestra referencia de nodo, nuevamente con una llamada a la biblioteca:

_faceControl = artboard.getNode ("ctrl_face");
if (_faceControl! = null) {
  _faceControl.getWorldTranslation (_faceOrigin);
  Vec2D.copy (_faceOriginLocal, _faceControl.translation);
}
jugar ("inactivo");

Las mesas de trabajo en Flare son los contenedores de nivel superior para nodos, formas y animaciones. artboard.getNode (String name) devuelve la referencia de ActorNode con el nombre dado.

Después de haber almacenado la referencia del nodo, también guardamos su traducción original, para poder restaurarla cuando el campo de texto pierde el foco y comenzamos a reproducir la animación inactiva.

Las otras dos anulaciones se denominan cada cuadro: setViewTransform () se usa aquí para construir _globalToFlareWorld, que es la matriz para transformar las coordenadas globales de la pantalla Flutter en coordenadas del mundo Flare.

¡El método advance () es donde todo lo anterior se une!
Cuando el usuario comienza a escribir, TrackingTextInput transmitirá la posición de la pantalla del cursor en _caretGlobal. Con esta coordenada, el controlador puede calcular la nueva posición de ctrl_face, cambiando así su mirada.

// Proyecta la mirada hacia adelante por esta cantidad de píxeles.
const estático doble _projectGaze = 60.0;
[...]
// Preocúpate en el espacio del mundo Flare.
Vec2D.transformMat2D (
  _caretWorld, _caretGlobal, _globalToFlareWorld);
[...]
// Calcular el vector de dirección.
Vec2D toCaret = Vec2D.subtract (Vec2D (), _caretWorld, _faceOrigin);
Vec2D.normalize (toCaret, toCaret);
// Escala la dirección con un valor constante.
Vec2D.scale (toCaret, toCaret, _projectGaze);
// Calcule la transformación que nos pone en el espacio face ctrl_face.
Mat2D toFaceTransform = Mat2D ();
if (Mat2D.invert (toFaceTransform,
        _faceControl.parent.worldTransform)) {
  // Poner toCaret en el espacio local.
  // N.B. estamos usando un vector de dirección, no una traducción,
  // entonces usa transformMat2 () para transformar sin traducción
  Vec2D.transformMat2 (toCaret, toCaret, toFaceTransform);
  // La posición final de ctrl_face es la traducción facial original
  // más este vector de dirección
  targetTranslation = Vec2D.add (Vec2D (), toCaret, _faceOriginLocal);
}

Como una imagen vale más que mil palabras, o en este caso, líneas de código, a continuación podemos ver cómo se calcula la dirección: el vector de diferencia se almacena en toCaret.

Como esta es una dirección, se normaliza y luego se amplía según la cantidad de píxeles que la mirada debe proyectar desde su posición original.

Por último, transformamos toCaret en el propio espacio del nodo para poder agregarlo a la traducción original del nodo.

Posición de cuidado

La última pieza del rompecabezas es cómo calcular la posición de la pantalla del cursor.

Esto se hace en el widget TrackingTextInput. Este widget almacena una referencia a una GlobalKey para construir sus TextFormFields. A través de esta clave, Flutter nos permite obtener el RenderObject que abarca este TextFormField:

RenderObject fieldBox = _fieldKey.currentContext.findRenderObject ();

Con las tres funciones de ayuda disponibles en lib / input_helper.dart, podemos usar RenderBox para calcular la posición real de intercalación en las coordenadas de la pantalla atravesando la jerarquía de widgets desde ese RenderBox y buscando un RenderEditable. Esta clase Flutter proporciona el método getEndpointsForSelection () que se usa para calcular coordenadas locales, que el originalRenderBox puede transformar en coordenadas globales.

¡Y eso es!

Una vez más, asegúrese de consultar las fuentes en GitHub y Flare, y ¡únase a nosotros en 2Dimensions.com!