Flutter SK/CZ – #1.11 – HERO Animácia
Úvod
Vitajte v Himdeve development, kde pre Vás pripravujeme tie najlepšie tutoriály, ktoré Vám uľahčia a zefektívnia vývoj mobilných aplikácií.
Cieľ
- Vytvoríme Hero animáciu na prechod medzi stránkami
- Pridáme špeciálne animačné efekty pre Hero animáciu
- Naučíme sa nastaviť Ripple Efekt cez celý widget vrátane jeho potomkov
- Ukážeme si ako zadefinovať vlastný Builder pomocou Typedefu
- Pridáme cachovanie obrázkov načítavaných z internetu
Postup
Najskôr otvoríme našu existujúcu aplikáciu z predchádzajúceho tutoriálu a otvoríme portfolio_tutorials_sub_page.dart súbor. Toto bude náš vstupný súbor pre tento tutoriál.
Máme tu definovaný list tutoriálov pomocou Tuple2 triedy.
Kde prvá položka predstavuje url adresu k obrázku tutoriálu a druhá názov alebo teda popis tutoriálu.
Hero animácia
Hero označuje widget, ktorý letí medzi obrazovkami. Hero animácia je teda animácia widgetu, najčastejšie obrázku, z jednej obrazovky na druhú počas prechodu medzi stránkami pomocou Page Route navigácie.
V našom prípade chceme použiť Hero animáciu medzi položkou z listu našich tutoriálov a prislúchajúcim detailom k tejto položke.
hero_widget.dart
Začneme hneď vytvorením Hero Widget triedy. V priečinku components vytvoríme nový súbor hero_widget.dart. Bude to Stateless Widget – HeroWidget.
Typedef
Zadefinujeme Typdef HeroBuilder.
Typedefy sú aliasy pre funkcie, ktoré sú objektami. Obvyklím použitím Typedef aliasov je callback rozhranie. V našom prípade HeroBuilder je alias pre funkciu, ktorá berie BuildContext ako parameter a vracia Widget.
Zadefinujeme premennú heroBuilder do HeroWidget triedy.
Do konštruktora tejto triedy pridáme heroBuilder ako povinný atribút, avšak keďže builder callback funkcie, sa zvyknú nazývať len builder, tak aj my nastavíme meno tohto parametra na builder.
Zadefinujeme aj ostatné parametre pre túto triedu. Aby bolo možné identifikovať a spárovať 2 Hero widgety, tak im musíme nastaviť rovnaký Hero Tag. Čiže pridáme parameter heroTag. Taktiež je vhodné umožniť nastavovať veľkosť Hero widgetu pomocou definovania jeho šírky. Na to využijeme parameter width. A ako posledný parameter nastavíme onTap callback metódu, ktorá bude reagovať na ťuknutie na daný widget.
HeroWidget – build metóda
Následne zadefinujeme build metódu tohto widgetu.
Aby sme mohli meniť veľkosť Hero widgetu, tak ho musíme obaliť SizedBox widgetom, ktorému môžeme nastavovať nami definovanú šírku pomocou atribútu width.
SizedBox má ďalší atribút child, do ktorého vložíme Hero widget.
Hero
Hero má atribút tag, ktorý ako bolo spomenuté, slúži na identifikáciu a spárovanie dvoch Hero widgetov. Nastavíme sem našu premennú heroTag.
Do child atribútu Hero widgetu vložíme InkWell widget, ktorý nám umožní definovať Ripple efekt. Ripple efekt sme si vysvetlili detailne v predchádzajúcom tutoriále. InkWell má atribút child, do ktorého vložíme práve našu callback funkciu heroBuilder s contextom danej build metódy. Do atribútu onTap vložíme void callback metódu onTap.
InkWell widget zaobalíme Material widgetom s transparentnou farbou aby mal Ripple Efekt miesto, kde sa môže prejaviť.
RotationTransition
Ďalším atribútom Hero widgetu je flightShuttleBuilder, ktorý slúži práve na zmenu vrstvy prechodu medzi dvomi destináciami Hero widgetu.
Vrátime sem RotationTransition, čo je prechod, ktorý animuje rotáciu daného widgetu.
RotationTranstion má atribúty turns, ktorý slúži na kontrolovanie rotácie svojho potomka. Vložíme sem Tween. Tween predstavuje lineárnu interpoláciu medzi začiatočnou a koncovou hodnotou. V našom prípade to bude predstavovať koľko krát sa daný widget bude rotovať v priebehu prechodu medzi stránkami.
Výhodou Tweenov je, že sa dajú spájať dokopy pomocou chain príkazu. Môžeme preto pridať k rotácií objektu aj zrýchlenie widgetu na začiatku animácie a spomalenie na konci pomocou Curves.ease volania. Toto volanie nastavíme do atribútu curve pre triedu CurveTween.
A na záver zavoláme metódu animate, ktorá vytvorí z Tweenov, animation objekt.
Ďalším atribútom RotationTransition widgetu je child, do ktorého vložíme obsah Hero widgetu, konkrétne celý jeho podstrom, pomocou volania widget.child. Zo záverečného contextu vyberieme Hero widget, precastujeme ho aby bol rozpoznateľný ako Hero a následne pomocou kaskádového operátora alebo symbolu (..) z neho vyberieme jeho podstrom. Dve bodky umožňujú urobiť sekvenciu operácií na jeden objekt.
Tým sme si prešli celý hero_widget.dart súbor.
Vrátime sa do portfolio_tutorials_sub_page.dart súboru.
Ripple Efekt pre celý widget
Na to aby sme dosiahli Ripple efekt pre celý widget tak, aby prekrýval aj obrázky, ktoré sú v ňom vykreslené, tak potrebujeme použiť Stack widget.
Preto v metóde _buildListItem vrátime Stack widget s dvomi potomkami. Prvého potomka bude tvoriť metóda _buildCardView, ktorá vráti Card widget zabalený do Positioned widgetu. A druhého potomka bude tvoriť metóda _buildRippleEffectNavigation, ktorá vytvorí požadovaný Ripple Efekt cez celú položku list widgetu.
V _buildCardView metóde vrátime existujúci Card widget zaobalený do Positioned widgetu pomocou konštruktora Positioned.fill, ktorý zabezpečí natiahnutie daného widgetu na šírku aj výšku rodiča. A to tak, že defaultne má nastavené atribúty top, bottom, left a right na 0.
Card widget má v atribúte child vložený Row widget, ktorým sa náš HeroWidget (obrázok tutoriálu) nastaví na ľavú stranu riadku pomocou metódy _buildHeroWidget a popis tutoriálu na pravú stranu riadku pomocou metódy _buildDesc.
V _buildHeroWidget metóde vrátime HeroWidget, ktorému nastavíme šírku na 150 pomocou atribútu width. Do atribútu heroTag nastavíme url adresu obrázku. Url adresa by mala reprezentovať unikátny kľúč. A v našom builder atribúte, ktorý reprezentuje callback funkciu definovanú pomocou Typdef aliasu v hero_widget.dart súbore, vrátime obsah pre HeroWidget pomocou metódy _buildHeroWidgetContent.
V metóde _buildHeroWidgetContent vrátime CachedNetworkImage widget reprezentujúci ukážku daného tutoriálu.
Aby sme mohli použiť CachedNetworkImage widget, tak musíme do pubspec.yaml súboru zadefinovať knižnicu cached_network_image.
Druhou metódou v Row widgete bude, vedľa HeroWidgetu, popis k danému tutoriálu, vytvorený pomocou metódy _buildDesc.
V _buildRippleEffectNavigation metóde vrátime Positioned widget pomocou Positioned konštruktora Positioned.fill. Do child atribútu vložíme Material widget s transparentnou farbou aby sa mal kde Ripple Efekt vykresliť a do Material widget atribútu child vložíme InkWell widget. InkWell widget má atribút onTap, na ktorý zavoláme Navigator push metódu na zmenu stránky.
Novú stránku zadefinujeme v metóde _createTutorialDetailRoute.
V prípade, že by sme nechceli meniť rýchlosť animácie prechodu medzi stránkami, tak môžeme použiť klasický MaterialPageRoute konštruktor na vytvorenie cesty k detailu Tutoriálu. Avšak my chceme zmeniť trvanie animácie na 1 sekundu. Z toho dôvodu využijeme na vytvorenie cesty k detailu Tutoriálu PageRouteBuilder konštruktor.
transitionDuration
PageRouteBuilder konštrutor má atribúty transitionDuration, ktorým nastavíme dĺžku prechodu na 1 sekundu pomocou Duration objektu.
transitionsBuilder
Pri MaterialPageRoute konštruktore máme automaticky definovaný FadeTransition (spriesvitnenie) pri prechode z jednej stránky na druhú. Avšak použitím PageRouteBuilder musíme na to nastaviť atribút transitionsBuilder, v ktorom vytvoríme FadeTransition.
V prípade, že by sme chceli do FadeTransition pridať aj iný Tween, tak môžeme takisto použiť reťazenie ako pri Hero widgete.
A v prípade, že by sme chceli spojiť FadeTransition s ďalšiou Transition (prechod), napríklad SlideTranstion, tak to je možné tak, že ďalšiu Transition vložíme do child atribútu predchádzajúcej Transition.
pageBuilder
Posledný atribút, ktorý pre PageRouteBuilder zadefinujeme, bude pageBuilder, v ktorom práve zavoláme triedu reprezentujúcu novú stránku, čo je v našom prípade detail tutoriálu – PortfolioTutorialDetailPage.
PortfolioTutorialDetailPage
V priečinku pages/portfolio/ vytvoríme súbor portfolio_tutorial_detail_page.dart. V ňom vytvoríme Stateless Widget PortfolioTutorialDetailPage.
Zadefinujeme mu povinné atribúty heroTag, desc, a imageUrl.
PortfolioTutorialDetailPage – build metóda
A v build metóde tohto widgetu vrátime Scaffold widget, ktorému zadefinujeme AppBar s nadpisom ‘Tutorial Detail‘, aby sme mali tlačítko späť, ktorým sa vrátime na predchádzajúcu stránku.
Do body atribútu zadefinujeme metódu na vytvorenie obsahu widgetu – _buildContent.
V _buildContent metóde chceme zobraziť v hornej časti obrazovky HeroWidget, ktorý obsahuje obrázok reprezentujci tutoriál a pod ním jeho text s popisom tutoriálu. Na to použijeme Column widget, ktorý má atribút children, kde najskôr zadefinujeme metódu _buildHeroWidget na vytvorenie HeroWidgetu a následne metódu _buildDesc na vytvorenie popisu k tutoriálu. Aby obsah nášho widgetu bol skrolovateľný, tak Column widget zaobalíme SingleChildScrollView widgetom.
V _buildHeroWidget metóde vrátime HeroWidget, ktorému nastavíme heroTag atribút. Šírku mu nastavíme na celú šírku obrazovky pomocou príkazu MediaQuery.of(context).size.width. A v našom builder atribúte vrátime obsah pre HeroWidget pomocou metódy _buildHeroWidgetContent.
V _buildHeroWidgetContent metóde znovu využijeme CachedNetworkImage widget, reprezentujúci ukážku daného tutoriálu z internetu.
V _buildDesc metóde vrátime nadpis tutoriálu pomocou widgetu Text. Tento nadpis zacentrujeme na stred pomocou atribútu textAlign. A nastavíme veľkosť písma na 30. Následne obalíme tento widget Padding widgetom o veľkosti 8.
V prípade, že by sme chceli spomaliť beh aplikácie, aby sme videli jednotlivé animácie a efekty detailnejšie, nastavíme na začiatok build metódy timeDilation setter metódu.
Záver
A tým je naša jedenásta časť tejto prvej série Flutter SK/CZ Tutoriálov ukončená a kompletný zdrojový kód môžete samozrejme nájsť na githube.