Ilustrație de Virginia Poltrack

Animarea pe o programare

Animații în aplicația Google I / O

Am făcut parte recent dintr-o echipă grozavă care lucrează la aplicația Android I / O 2018 Android. Aceasta este o aplicație de însoțire a conferinței, care permite participanților și persoanelor îndepărtate să găsească sesiuni, să construiască un program personalizat și să rezerve locuri la locul de desfășurare (dacă aveți norocul să fiți acolo!). Am creat o serie de funcții animate interesante în aplicație, care cred că a îmbunătățit mult experiența. Codul acestei aplicații tocmai a fost deschis și am vrut să evidențiez câteva dintre aceste cazuri și câteva detalii interesante de implementare.

Câteva elemente animate din aplicația I / O

În general, există 3 tipuri de animații pe care le-am folosit în aplicație:

  1. Animațiile Hero - folosite pentru a consolida brandingul și a aduce momente de încântare
  2. Tranziții pe ecran
  3. Schimbări de stat

Aș dori să intru în detalii despre câteva dintre acestea.

Numărătoarea inversă

O parte din rolul aplicației este de a crea emoție și anticipare pentru conferință. În acest fel, am inclus o numărătoare numărătoare animată pentru începutul conferinței, afișat atât pe ecranul de bord cât și în secțiunea Informații. Aceasta a fost, de asemenea, o oportunitate excelentă de a încorpora brandul evenimentului în aplicație, aducând mult caracter.

Numărătoarea inversă la începutul conferinței

Această animație a fost proiectată de un designer de mișcare și distribuită ca o serie de fișiere json Lottie: fiecare o secundă lungă care arată un număr care animă „în” apoi „afară”. Formatul Lottie a făcut ușor să aruncați fișierele în active și chiar a oferit metode de comoditate precum setMinAndMaxProgress, ceea ce ne-a permis să jucăm doar prima sau ultima jumătate a unei animații (pentru a arăta un număr care se animă sau iese).

Partea interesantă a fost de fapt orchestrarea acestor animații multiple în numărătoarea inversă. Pentru a face acest lucru, am creat un CountdownView personalizat, care este destul de complex ConstraintLayout care deține un număr de LottieAnimationViews. În acest sens, am creat un delegat Kotlin pentru a încapsula pornirea animației corespunzătoare. Acest lucru ne-a permis să atribuim o intrare simplă fiecărui delegat al cifrei pe care ar trebui să o afișeze și delegatul va configura și va începe animația (animările). Am extins delegatul ObservableProperty care ne asigură că am rulat o animație doar atunci când cifra se schimbă. Bucla noastră de animație a postat pur și simplu un runnable în fiecare secundă (când vizualizarea este atașată), care a calculat ce cifră ar trebui să afișeze fiecare vizualizare și a actualizat delegații.

Rezervare

Una dintre acțiunile cheie ale aplicației este de a permite participanților să își rezerve locurile. Ca atare, am afișat această acțiune în mod vizibil într-un FAB pe ecranul de detalii al sesiunii. Am considerat că este important să raportăm doar că sesiunea a fost rezervată o dată ce a fost finalizată cu succes pe backend (spre deosebire de acțiuni mai puțin importante, cum ar fi să joci o sesiune în care actualizăm optimist UI imediat). Este posibil să dureze puțin timp în timp ce așteptăm un răspuns din partea backend-ului, astfel încât să răspundem mai mult, am folosit pictograma animată pentru a oferi feedback pe care îl lucrăm și pentru a trece cu ușurință la noul stat.

Feedback în timp ce rezervați un loc la o sesiune

Acest lucru este complicat de faptul că există o serie de state pe care această pictogramă trebuie să le reflecte: sesiunea ar putea fi rezervată, ei ar fi deja rezervat un loc, dacă sesiunea este completă, atunci o listă de așteptare ar putea fi disponibilă sau pot fi lista de așteptare sau aproape de sesiune încep rezervările sunt dezactivate Acest lucru a dus la multe permutări ale diferitelor state pentru a anima între. Pentru a simplifica aceste tranziții, am decis să trecem întotdeauna printr-o stare „funcțională”; clepsidra animată de mai sus. Prin urmare, fiecare tranziție este de fapt o pereche de: stat 1 → de lucru și de lucru → starea 2. Acest lucru a simplificat foarte mult lucrurile. Am creat fiecare din aceste animații folosind forma de schimbare; consultați fișierele avd_state_to_state aici.

Pentru a afișa acest lucru, am folosit o vizualizare personalizată și o AnimatedStateListDrawable (ASLD). Dacă nu ați mai folosit ASLD înainte, este (așa cum îi spune numele) o versiune animată a StateListDrawable pe care ați întâlnit-o probabil - permițându-vă să furnizați nu numai desene diferite pentru fiecare stat, ci și tranziții între state (sub forma unui AnimatedVectorDrawable) sau un AnimationDrawable). Iată desenul care definește imaginile statice și tranzițiile către și din starea de lucru pentru pictograma rezervării.

Am creat o vizualizare personalizată pentru a susține propriile state personalizate. Vizualizările oferă unele stări standard, cum ar fi apăsat sau selectat. În mod similar, puteți să vă definiți propriul și să aveți ruta Vizualizare pe care o afișează. Am definit propriile state_reservable, state_reserved etc. Am creat apoi o enumerare a acestor stări diferite, încapsulând starea de vizualizare, plus toate atributele conexe, cum ar fi o descriere a conținutului asociat. Logica noastră de afaceri ar putea apoi să stabilească pur și simplu valoarea corespunzătoare din această enumerare (prin legarea datelor), care ar actualiza starea desenului, care a lansat o animație prin ASLD. Combinația de stări personalizate și AnimatedStateListDrawable a fost o modalitate corectă de a implementa acest lucru, păstrând multitudinea de stări în straturile declarative, rezultând un cod de vizualizare minim.

Tranziția de boxe

Multe dintre tranzițiile ecranului au funcționat bine cu animațiile standard pentru ferestre. Un loc în care ne-am abătut de la acesta este tranziția în ecranul detaliilor difuzoarelor. Aceasta a afișat imaginea difuzoarelor de o parte și de alta a tranziției și a fost un candidat perfect pentru o tranziție cu element comun. Acest lucru ajută la ușurarea schimbării contextului între ecrane.

O tranziție de element comun

Aceasta este o tranziție de element partajat destul de standard, folosind clasele de platformă ChangeBounds și ArcMotion de pe ImageView.

Ceea ce a fost mai interesant este modul în care inițierea acestei tranziții s-a încadrat în modelul de eveniment pe care l-am folosit pentru navigare. În esență, acest model decuplează evenimentele de intrare (cum ar fi apăsarea unui difuzor) de la evenimentele de navigație, punând ViewModel responsabil de modul în care să răspundă la intrare. În acest caz, această decuplare înseamnă că ViewModel a expus un LiveData de evenimente, care nu știa decât ID-ul difuzorului la care să navigheze. Inițierea unei tranziții de element partajat necesită Vizualizare comună, pe care nu am avut-o în acest moment. Am rezolvat acest lucru stocând ID-ul difuzorului ca o etichetă pe vizualizare atunci când este legat, astfel încât vizualizarea poate fi ulterior preluată atunci când trebuie să navigăm la un anumit ecran cu detalii despre difuzor.

Filtre

O parte esențială a aplicației de conferință este filtrarea multor evenimente până la cele care vă interesează. Fiecare subiect a avut o culoare asociată pentru o recunoaștere ușoară și am primit un design excelent pentru un „cip” personalizat pe care să-l utilizăm la selectarea filtrelor:

Jetoane de filtru animate

Ne-am uitat la Chip de la Componentele Materialelor, dar am optat pentru a implementa propria vizualizare personalizată pentru un control mai mare asupra afișajului și animației dintre stările „verificate”. Aceasta este implementată folosind desenul pe pânză și un StaticLayout pentru afișarea textului. Vizualizarea are o singură proprietate de progres [0–1] de modelare necontrolată - bifată. Pentru a comuta starea, pur și simplu animăm această valoare și invalidăm vizualizarea și codul de redare interpolează liniar pozițiile și dimensiunile elementelor bazate pe aceasta.

Inițial când am implementat acest lucru, am făcut vizualizarea să implementeze interfața verificabilă și am dat start animației când metoda setChecked a setat o nouă stare. Pe măsură ce afișăm mai multe filtre într-un RecyclerView, acest lucru a avut efectul nefericit de a rula animația dacă un filtru selectat a defilat și vederea a fost redusă la un filtru neselecționat. Whoops. Prin urmare, am adăugat o metodă separată pentru a lansa animația, care ne permite să diferențiem între a fi comisată de un clic și o actualizare imediată atunci când se leagă date noi la vizualizare.

În plus, atunci când am introdus această animație de comutare, am descoperit că se arunca, adică pică cadre. A fost vina codul meu de animație? Aceste filtre sunt afișate într-o Foaie de fund în fața ecranului principal al programului conferinței. Când un filtru este comutat, inițiem logica de filtrare care trebuie aplicată la program (și actualizăm numărul de evenimente potrivite din titlul foii de filtru). Câteva operațiuni de spelunking de mai târziu, am stabilit că problema a fost că atunci când s-au aplicat filtrele, ViewPager de RecyclerViews care afișează programul a fost oprit și actualizat la datele recent livrate. Acest lucru a făcut ca o serie de vizualizări să fie umflate și legate. Toate aceste lucrări au fost bugetul nostru cadru ... dar programul de actualizare nu a fost vizibil, întrucât se afla în spatele foii de filtru. Am luat decizia de a întârzia efectuarea filtrării efective până când animarea s-a difuzat, am făcut schimb de o mai mare complexitate de implementare pentru o experiență mai ușoară pentru utilizatori. Inițial am implementat acest lucru folosind postDelayed, dar asta a provocat probleme pentru testele UI. În schimb, am schimbat metoda noastră care a pornit animația pentru a accepta o lambda care să fie rulată la sfârșit. Acest lucru ne-a permis să respectăm mai bine setările de animație ale utilizatorului și să testăm corect execuția.

Fii animat

În general, simt că animațiile au contribuit cu adevărat la experiența, caracterul, brandingul și receptivitatea aplicației. Sperăm că această postare a ajutat să explice atât de ce, cât și cum au fost utilizate și v-a oferit indicatoare pentru implementarea lor.