Δημιουργία μιας οθόνης αλληλεπιδραστικής σύνδεσης με Flare & Flutter

Η ομάδα μας στο 2Dimensions συναντήθηκε πρόσφατα με τη φόρμα σύνδεσης Remembear: θεωρήσαμε ότι αυτό ήταν ένα τέλειο παράδειγμα που θα μπορούσαμε να χτίσουμε στο Flare και να το μοιραστούμε με την κοινότητα!

Ο πηγαίος κώδικας είναι διαθέσιμος στο GitHub και το αρχείο Flare μπορεί να βρεθεί στο 2Dimensions.

ΣΦΑΙΡΙΚΗ ΕΙΚΟΝΑ

Πρώτον, πρέπει να εισάγουμε τη βιβλιοθήκη flare_flutter στο pubspec.yaml (N.B. Χρησιμοποιούμε μια σχετική διαδρομή από τότε που βρισκόμαστε στο repo της βιβλιοθήκης, αλλά το πακέτο είναι επίσης διαθέσιμο στο DartPub). Προσθέσαμε επίσης το φάκελο στοιχείων στο pubspec.yaml έτσι ώστε το περιεχόμενό του να είναι προσβάσιμο στο Flutter.

Τα σχετικά αρχεία είναι όλα στο φάκελο / lib, ενώ το αρχείο Flare βρίσκεται στο φάκελο στοιχείων:

/ lib
  - input_helper.dart
  - main.dart
  - signin_button.dart
  - teddy_controller.dart
  - tracking_text_input.dart
/περιουσιακά στοιχεία
  - Teddy.flr

Πώς λειτουργεί αυτό

Ας ρίξουμε μια ματιά στο Teddy στο Flare: αυτός ο χαρακτήρας έχει έναν κόμβο που ονομάζεται ctrl_face ο οποίος είναι ο στόχος για τον μεταφραστικό περιορισμό των στοιχείων προσώπου. Αυτό σημαίνει ότι η μετακίνηση του κόμβου θα προκαλέσει την κίνηση όλων των εξαρτώμενων ατόμων.

Αν πάρουμε την αναφορά στον κόμβο ctrl_face, μπορούμε να μετακινήσουμε το πρόσωπο του Teddy και να προσαρμόσουμε την κατεύθυνση του βλέμματος του. Απλά θα πρέπει να βρούμε τη θέση του πεδίου κειμένου κάτω από το Teddy και να προσαρμόσουμε ανάλογα τη θέση του κόμβου ctrl_face.

Στον κώδικα

Στο main.dart, το MyHomePage δημιουργεί τη διάταξη για την εφαρμογή.
Χρησιμοποιούμε το γραφικό στοιχείο FlareActor από τη βιβλιοθήκη flare_flutter για να τοποθετήσετε την κινούμενη εικόνα στην προβολή:

[...]
FlareActor (
  "περιουσιακά στοιχεία / Teddy.flr",
  // δεσμεύστε ένα FlareController
  ελεγκτής: _teddyController
  [...]
)

Δεδομένου ότι θέλουμε να χειριστούμε τη θέση του κόμβου ctrl_face, δεσμευόμαστε _teddyController στον FlareActor μας. Ένας ελεγκτής είναι μια συγκεκριμένη εφαρμογή του FlareController, ενός interface που παρέχεται από το flare_flutter, και μας δίνει τη δυνατότητα να διερευνήσουμε και να χειριστούμε την ιεραρχία Flare.

Προσαρμοσμένοι έλεγχοι

Ας ρίξουμε μια ματιά στην κατηγορία TeddyController: θα παρατηρήσετε ότι το TeddyController επεκτείνει το FlareControls και όχι το FlareController!
Το FlareControls είναι μια συγκεκριμένη εφαρμογή του FlareController που παρέχει ήδη το flare_flutter και έχει κάποια βασική λειτουργία αναπαραγωγής / μίξης.

Το TeddyController έχει μερικά πεδία:

// Matrix για τη μετατροπή των συνολικών συντεταγμένων Flutter
// σε συντεταγμένες του κόσμου Flare.
Mat2D _globalToFlareWorld = Mat2D ();
// Αναφορά στον κόμβο `ctrl_look`.
ActorNode _faceControl;
// Αποθηκεύστε την προέλευση του κόμβου σε χώρους παγκόσμιου και τοπικού μετασχηματισμού.
Vec2D _faceOrigin = Vec2D ();
Vec2D _faceOriginLocal = Vec2D ();
// Caret σε παγκόσμιες συντεταγμένες του Flutter και σε συντεταγμένες του κόσμου Flare.
Vec2D _caretGlobal = Vec2D ();
Vec2D _caretWorld = Vec2D ()

Αυτή η κλάση θα πρέπει να παρακάμψει τρεις μεθόδους: initialize (), advance () και setViewTransform ().
initialize () καλείται - το μαντέψατε! - κατά το χρόνο αρχικοποίησης, όταν είναι κατασκευασμένο το widget FlareActor. Αυτό είναι όπου η αναφορά κόμβου μας αρχικά τραβιέται, πάλι με κλήση βιβλιοθήκης:

_faceControl = artboard.getNode ("ctrl_face");
αν (_faceControl! = null) {
  _faceControl.getWorldTranslation (_faceOrigin);
  Vec2D.copy (_faceOriginLocal, _faceControl.translation);
}}
παιχνίδι ("ρελαντί");

Τα artboards in Flare είναι τα δοχεία του ανώτατου επιπέδου για κόμβους, σχήματα και κινούμενα σχέδια. artboard.getNode (όνομα συμβολοσειράς) επιστρέφει την αναφορά ActorNode με το όνομα.

Αφού αποθηκεύσουμε την αναφορά του κόμβου, αποθηκεύουμε επίσης την αρχική μετάφραση, έτσι ώστε να μπορέσουμε να την επαναφέρουμε όταν το πεδίο κειμένου χάσει την εστίαση και αρχίζουμε να παίζουμε την κινούμενη εικόνα.

Οι άλλες δύο παρακάμψεις ονομάζονται κάθε καρέ: το setViewTransform () χρησιμοποιείται εδώ για την κατασκευή του _globalToFlareWorld, δηλαδή του πίνακα για τη μετατροπή των συντεταγμένων της παγκόσμιας οθόνης Flutter σε συντεταγμένες του κόσμου Flare.

Η μέθοδος advance () είναι όπου όλα τα παραπάνω έρχονται μαζί!
Όταν ο χρήστης ξεκινήσει να πληκτρολογεί, το TrackingTextInput θα μεταδώσει τη θέση της οθόνης της καρφίτσας στο _caretGlobal. Με αυτή τη συντεταγμένη, ο ελεγκτής μπορεί να υπολογίσει τη νέα θέση του ctrl_face, μεταβάλλοντας έτσι το βλέμμα του.

// Προβολή του έργου προς τα εμπρός από αυτό πολλούς εικονοστοιχεία.
static const διπλό _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 ();
εάν (Mat2D.invert (toFaceTransform,
        _faceControl.parent.worldTransform)) {
  // Βάλτε στοCare στο τοπικό χώρο.
  // Ν.Β. χρησιμοποιούμε ένα διάνυσμα κατεύθυνσης, όχι μια μετάφραση,
  // έτσι χρησιμοποιήστε το transformMat2 () για μετασχηματισμό χωρίς μετάφραση
  Vec2D.transformMat2 (toCaret, toCaret, toFaceTransform);
  // Η τελική θέση ctrl_face είναι η αρχική μετάφραση προσώπου
  // συν αυτό το διάνυσμα κατεύθυνσης
  targetTranslation = Vec2D.add (Vec2D (), toCaret, _faceOriginLocal).
}}

Δεδομένου ότι μια εικόνα αξίζει χίλιες λέξεις - ή στην περίπτωση αυτή, γραμμές κώδικα - κάτω μπορούμε να δούμε πώς υπολογίζεται η κατεύθυνση: ο διανυσματικός διαχωρισμός αποθηκεύεται στο toCaret.

Δεδομένου ότι πρόκειται για μια κατεύθυνση, είναι ομαλοποιημένη και στη συνέχεια κλιμακωθεί με τον αριθμό των pixel που το βλέμμα πρέπει να προεξέχει από την αρχική του θέση.

Τέλος, μετασχηματίζουμε το toCaret στον χώρο του κόμβου ώστε να μπορέσουμε να το προσθέσουμε στην αρχική μετάφραση του κόμβου.

Θέση Caret

Το τελευταίο κομμάτι του παζλ είναι ο τρόπος υπολογισμού της θέσης της οθόνης της καρφίτσας.

Αυτό γίνεται στο γραφικό στοιχείο TrackingTextInput. Αυτό το γραφικό στοιχείο αποθηκεύει μια αναφορά σε ένα GlobalKey για την κατασκευή του TextFormFields. Μέσω αυτού του κλειδιού, το Flutter μας επιτρέπει να έχουμε το RenderObject που περιλαμβάνει αυτό το TextFormField:

RenderObject fieldBox = _fieldKey.currentContext.findRenderObject ();

Με τις τρεις βοηθητικές λειτουργίες που είναι διαθέσιμες στο lib / input_helper.dart, μπορούμε να χρησιμοποιήσουμε το RenderBox για να υπολογίσουμε την πραγματική θέση caret στις συντεταγμένες της οθόνης, διαβάζοντας την ιεραρχία widget από το RenderBox και αναζητώντας RenderEditable. Αυτή η κλάση Flutter παρέχει τη μέθοδο getEndpointsForSelection () που χρησιμοποιείται για τον υπολογισμό τοπικών συντεταγμένων, οι οποίες μπορούν να μετατραπούν σε παγκόσμιες συντεταγμένες από το πρωτότυποRenderBox.

Και αυτό είναι!

Για άλλη μια φορά, φροντίστε να ελέγξετε τις πηγές στο GitHub και το Flare και έρθετε μαζί μας στο 2Dimensions.com!