Flutter-teamet sendte nylig en ny stabil versjon av det fantastiske mobilrammeverket på tvers av plattformer. Denne nye versjonen inkluderer mange nye oppgraderinger, inkludert forbedret mobilytelse, reduserte appstørrelser, metallstøtte på iOS-enheter, nye materialmoduler og så videre.
Blant disse nye funksjonene var den nye animasjonspakken den som virkelig fanget meg. Basert på Googles nye Material motion-spesifikasjon, lar denne pakken utviklere implementere animasjonsmønstre i mobilapputvikling.
I følge dokumentasjonen, "Denne pakken inneholder forhåndsinnstilte animasjoner for vanlige ønskede effekter. Animasjonene kan tilpasses med innholdet ditt og legges inn i applikasjonen din for å glede brukerne dine.»
I denne artikkelen vil jeg diskutere hva som er i den nye animasjonspakken og hvordan du bruker den i appen din for å skape vakrere UI-interaksjoner. En grunnleggende kunnskap om Flutter og Dart bør være nok til å følge denne artikkelen – med alt det sagt, la oss komme i gang!
Hva er Material Designs bevegelsessystem?
I følge Material Design-nettstedet er "Bevegelsessystemet et sett med overgangsmønstre som kan hjelpe brukere med å forstå og navigere i en app." I utgangspunktet består Materials bevegelsesspesifikasjon av vanlige overgangsmønstre som gir meningsfulle og vakre brukergrensesnitt-interaksjoner.
På tidspunktet for skriving av denne artikkelen er Material motion-pakker/-biblioteker tilgjengelige for bruk i opprinnelig Android-utvikling og Flutter-utvikling. I Flutter kommer dette i form av animasjonspakken.
Det er for øyeblikket fire overgangsmønstre tilgjengelig i pakken:
- Beholdertransformasjon
- Delt akseovergang
- Fadese gjennom overgangen
- Fade overgang
Vi skal nå se på hvordan du implementerer disse overgangsmønstrene med Flutter og animasjonspakken.
Sett opp et nytt Flutter-prosjekt
Først må du lage en ny Flutter-app. Jeg pleier å gjøre dette med VSCode Flutter-utvidelsen. Når du har opprettet Flutter-prosjektet, legger du til animasjonspakken som en avhengighet i pubspec.yaml
fil:
dependencies: flutter: sdk: flutter animations: ^1.0.0+5
Kjør nå følgende kommando for å få de nødvendige pakkene:
flutter pub get
Med vår nye Flutter-app satt opp, la oss begynne å skrive litt kode.
Beholdertransformasjonen
I følge Material motion-spesifikasjonen, "Beholdertransformasjonsmønsteret er designet for overganger mellom UI-elementer som inkluderer en beholder. Dette mønsteret skaper en synlig forbindelse mellom to UI-elementer." Beholderen fungerer som et vedvarende element gjennom hele overgangen.
Du kan se noen eksempler på containertransformasjonen i aksjon i animasjonspakkens dokumenter. Som du kan se, under overgangen, er det et felles element:beholderen, som holder det utgående og innkommende elementet og hvis dimensjoner og posisjon endres.
For å implementere containertransformasjonen kan vi bruke OpenContainer
widget levert av animasjonspakken. OpenContainer
lar oss definere innholdet i beholderen når den er lukket (startinnholdet) og innholdet i beholderen når den åpnes. Vi kan også definere andre egenskaper, for eksempel farge og høyden på beholderen i både lukket og åpnet tilstand.
Koden for implementering av beholdertransformasjonen ser slik ut:
void main() { runApp( MaterialApp( home:TestingContainer(), ), ); } class TestingContainer extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( body: Container(), floatingActionButton: OpenContainer( closedBuilder: (_, openContainer){ return FloatingActionButton( elevation: 0.0, onPressed: openContainer, backgroundColor: Colors.blue, child: Icon(Icons.add, color: Colors.white), ); }, openColor: Colors.blue, closedElevation: 5.0, closedShape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(100) ), closedColor: Colors.blue, openBuilder: (_, closeContainer){ return Scaffold( appBar: AppBar( backgroundColor: Colors.blue, title: Text("Details"), leading: IconButton( onPressed: closeContainer, icon: Icon(Icons.arrow_back, color: Colors.white), ), ), body: ( ListView.builder( itemCount: 10, itemBuilder: (_,index){ return ListTile( title: Text(index.toString()), ); } ) ), ); } ), ); } }
Som du kan se, vår OpenContainer
har to navngitte parametere (blant andre) kalt closedBuilder
og openBuilder
. Begge disse parameterne tar en funksjon som returnerer en widget.
Funksjonen tar inn et objekt av typen BuildContext
og en funksjon som enten åpner beholderen (i tilfelle closedBuilder
) eller som lukker beholderen (i tilfelle openBuilder
). ). Widgeten returnerte i closedBuilder
er innholdet i beholderen i lukket tilstand, og widgeten returnert i openBuilder
er innholdet i åpnet tilstand. Resultatet skal være:
Flere flotte artikler fra LogRocket:
- Ikke gå glipp av et øyeblikk med The Replay, et kuratert nyhetsbrev fra LogRocket
- Bruk Reacts useEffect for å optimalisere applikasjonens ytelse
- Bytt mellom flere versjoner av Node
- Finn ut hvordan du animerer React-appen din med AnimXYZ
- Utforsk Tauri, et nytt rammeverk for å bygge binærfiler
- Sammenlign NestJS vs. Express.js
- Oppdag populære ORM-er som brukes i TypeScript-landskapet
Det delte akseovergangsmønsteret
I følge dokumentene, "Det delte aksemønsteret brukes for overganger mellom UI-elementer som har et romlig eller navigasjonsforhold. Dette mønsteret bruker en delt transformasjon på x-, y- eller z-aksen for å forsterke forholdet mellom elementene." Så hvis du trenger å animere navigasjonen langs en bestemt akse, er overgangsmønsteret for delt akse det for deg.
Du kan få en bedre ide om hva jeg mener ved å se animasjonen i aksjon på pakkens dokumentside. For implementering av overgangsmønsteret for delt akse gir animasjonspakken oss PageTransitionSwitcher
og SharedAxisTransition
widgets.
PageTransitionSwitcher
widgeten går ganske enkelt over fra et gammelt barn til et nytt barn når barnet endres. Du bør alltid gi hvert barn av PageTransitionSwitcher
en unik nøkkel slik at Flutter vet at widgeten nå har et nytt barn. Dette kan enkelt gjøres med en UniqueKey
objekt.
Bortsett fra underordnet parameter, PageTransitionSwitcher
har også andre navngitte parametere:duration
, for å angi varigheten av overgangen; reverse
, som tar en boolsk verdi og bestemmer om overgangen skal "spilles baklengs" eller ikke; og transitionBuilder
, som tar en funksjon som vil returnere en widget.
I vårt tilfelle returnerer vi en SharedAxisTransition
widget. I SharedAxisTransition
widget, kan vi angi transitionType
(om vi ønsker å gå langs x-aksen, y-aksen eller z-aksen). Vi har også animation
og secondaryAnimation
parametere, som definerer henholdsvis animasjonen som driver barnets inngang og utgang og animasjonen som driver overgangen til et nytt barn på toppen av det gamle.
Koden for implementering av SharedAxisTransition
ser slik ut:
void main() { runApp( MaterialApp( home: TestingSharedAxis(), ), ); } class TestingSharedAxis extends StatefulWidget { @override _TestingSharedAxisState createState() => _TestingSharedAxisState(); } class _TestingSharedAxisState extends State<TestingSharedAxis> { bool _onFirstPage = true; @override Widget build(BuildContext context) { return Scaffold( resizeToAvoidBottomInset: false, body: SafeArea( child: Column( children: <Widget>[ Padding( padding: const EdgeInsets.all(8.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[ FlatButton( onPressed: _onFirstPage == true ? null : () { setState(() { _onFirstPage = true; }); }, child: Text( "First Page", style: TextStyle( color: _onFirstPage == true ? Colors.blue.withOpacity(0.5) : Colors.blue), )), FlatButton( onPressed: _onFirstPage == false ? null : () { setState(() { _onFirstPage = false; }); }, child: Text( "Second Page", style: TextStyle( color: _onFirstPage == false ? Colors.red.withOpacity(0.5) : Colors.red), )) ], ), ), Expanded( child: PageTransitionSwitcher( duration: const Duration(milliseconds: 300), reverse: !_onFirstPage, transitionBuilder: (Widget child, Animation<double> animation, Animation<double> secondaryAnimation) { return SharedAxisTransition( child: child, animation: animation, secondaryAnimation: secondaryAnimation, transitionType: SharedAxisTransitionType.horizontal, ); }, child: _onFirstPage ? Container( key: UniqueKey(), color: Colors.blue, child: Align( alignment: Alignment.topCenter, child: Text("FIRST PAGE"), ), ) : Container( key: UniqueKey(), color: Colors.red, child: Align( alignment: Alignment.topCenter, child: Text("SECOND PAGE"), ), ), ), ), ], ), ), ); } }
I kodeblokken ovenfor definerte vi en privat boolsk variabel kalt _onFirstPage
, som er sant hvis vi er på første side og usann ellers. Vi brukte også verdien _onFirstPage
for å definere verdien for den omvendte parameteren til PageTransitionSwitcher
. Dette tillater PageTransitionSwitcher
for å "sprette" den andre siden av når du bytter tilbake til den første siden.
Resultatet skal se omtrent slik ut:
Fade-through-overgangsmønsteret
Fade through-overgangsmønsteret brukes til å gå mellom UI-elementer som ikke er sterkt relatert til hverandre. Ta en titt på dokumentsiden for å se hvordan dette overgangsmønsteret ser ut.
Implementeringen av overgangsmønsteret for gjennomtoning er veldig likt det for overgangsmønsteret for delt akse. Her, FadeThroughTransition
brukes i stedet for SharedAxisTransition
. Her er koden for en enkel implementering av fade through-mønsteret i Flutter med animasjonspakken:
void main() { runApp( MaterialApp( home: TestingFadeThrough(), ), ); } class TestingFadeThrough extends StatefulWidget { @override _TestingFadeThroughState createState() => _TestingFadeThroughState(); } class _TestingFadeThroughState extends State<TestingFadeThrough> { int pageIndex = 0; List<Widget> pageList = <Widget>[ Container(key: UniqueKey(),color:Colors.red), Container(key: UniqueKey(),color: Colors.blue), Container(key: UniqueKey(),color:Colors.green) ]; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Testing Fade Through')), body: PageTransitionSwitcher( transitionBuilder: ( Widget child, Animation<double> animation, Animation<double> secondaryAnimation ){ return FadeThroughTransition( animation: animation, secondaryAnimation: secondaryAnimation, child: child, ); }, child: pageList[pageIndex], ), bottomNavigationBar: BottomNavigationBar( currentIndex: pageIndex, onTap: (int newValue) { setState(() { pageIndex = newValue; }); }, items: const <BottomNavigationBarItem>[ BottomNavigationBarItem( icon: Icon(Icons.looks_one), title: Text('First Page'), ), BottomNavigationBarItem( icon: Icon(Icons.looks_two), title: Text('Second Page'), ), BottomNavigationBarItem( icon: Icon(Icons.looks_3), title: Text('Third Page'), ), ], ), ); } }
Det vi gjør her er ganske grunnleggende; vi gjengir et nytt barn avhengig av indeksen til BottomNavigationBarItem
som er valgt for øyeblikket. Legg merke til at hvert barn har en unik nøkkel. Som jeg sa tidligere, gjør dette at Flutter kan skille mellom de forskjellige barna. Slik skal resultatet se ut:
Fade-overgangsmønsteret
Dette overgangsmønsteret brukes når et element må gå inn (enter) eller gå ut (ut) av skjermen, for eksempel i tilfelle av en modal eller dialog.
For å implementere dette i Flutter, må vi bruke FadeScaleTransition
og en AnimationController
å kontrollere inngangen og utgangen til overgangens barn. Vi vil bruke vår AnimationController
status for å bestemme om den underordnede widgeten skal vises eller skjules.
Slik ser en implementering av fade-overgangen ut i kode:
void main() { runApp( MaterialApp( home: TestingFadeScale(), ), ); } class TestingFadeScale extends StatefulWidget { @override _TestingFadeScaleState createState() => _TestingFadeScaleState(); } class _TestingFadeScaleState extends State<TestingFadeScale> with SingleTickerProviderStateMixin { AnimationController _controller; @override void initState() { _controller = AnimationController( value: 0.0, duration: const Duration(milliseconds: 500), reverseDuration: const Duration(milliseconds: 250), vsync: this) ..addStatusListener((status) { setState(() {}); }); super.initState(); } @override void dispose() { _controller.dispose(); super.dispose(); } bool get _isAnimationRunningForwardsOrComplete { switch (_controller.status) { case AnimationStatus.forward: case AnimationStatus.completed: return true; case AnimationStatus.reverse: case AnimationStatus.dismissed: return false; } return null; } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Testing FadeScale Transition'), ), body: Column( children: <Widget>[ Padding( padding: const EdgeInsets.all(8.0), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ RaisedButton( onPressed: () { if (_isAnimationRunningForwardsOrComplete) { _controller.reverse(); } else { _controller.forward(); } }, color: Colors.blue, child: Text(_isAnimationRunningForwardsOrComplete ? 'Hide Box' : 'Show Box'), ) ], ), ), AnimatedBuilder( animation: _controller, builder: (context, child) { return FadeScaleTransition(animation: _controller, child: child); }, child: Container( height: 200, width: 200, color: Colors.blue, ), ), ], ), ); } }
Som du kan se, er FadeScaleTransition
widgeten har en navngitt parameter kalt animation
, som tar inn en AnimationController
. Resultatet skal se slik ut:
showModal
funksjon
Animasjonspakken kommer også med en passende navngitt funksjon kalt showModal
, som (som navnet antyder) brukes til å vise en modal.
showModal
tar inn ulike argumenter, hvorav noen inkluderer:context
, som brukes til å finne Navigator for modalen; builder
, som er en funksjon som returnerer innholdet i modalen; og configuration
.
configuration
parameteren tar inn en widget som utvider ModalConfiguration
klasse, og den brukes til å definere egenskapene til modalen, for eksempel fargen på barrieren (deler av skjermen som ikke dekkes av modalen), varighet, inn- og utgangsoverganger, og så videre.
Her er hva showModal
funksjonen ser ut som i kode:
void main() { runApp( MaterialApp( home: TestingShowModal(), ), ); } class TestingShowModal extends StatelessWidget { @override Widget build(BuildContext context) { timeDilation = 20; return Scaffold( body: Center( child: RaisedButton( color: Colors.blue, child: Text( "Show Modal", style: TextStyle( color: Colors.white ), ), onPressed: (){ showModal( context: context, configuration: FadeScaleTransitionConfiguration(), builder: (context){ return AlertDialog( title: Text("Modal title"), content: Text("This is the modal content"), ); } ); } ), ), ); } }
I kodeblokken ovenfor brukte vi FadeScaleTransitionConfiguration
som vårt konfigurasjonsargument. FadeScaleTransitionConfiguration
er en forhåndsdefinert klasse som utvider ModalConfiguration
og brukes til å legge til egenskapene til en fade-overgang til vår modal.
Overstyre standard sideruteovergang
Med SharedAxisPageTransitionsBuilder
, FadeThroughPageTransitionsBuilder
, og pageTransitionsTheme
parameteren til vår MaterialApp
tema, kan vi overstyre standard overgangsanimasjonen som oppstår når vi bytter fra én rute til en annen i Flutter-appen vår.
For å gjøre dette med SharedAxisPageTransitionsBuilder
:
void main() { runApp( MaterialApp( theme: ThemeData( pageTransitionsTheme: const PageTransitionsTheme( builders: <TargetPlatform, PageTransitionsBuilder>{ TargetPlatform.android: SharedAxisPageTransitionsBuilder( transitionType: SharedAxisTransitionType.horizontal), }, ), ), home: HomePage(), ), ); }
Og for å gjøre dette med FadeThroughPageTransitionsBuilder
:
void main() { runApp( MaterialApp( theme: ThemeData( pageTransitionsTheme: const PageTransitionsTheme( builders: <TargetPlatform, PageTransitionsBuilder>{ TargetPlatform.android: FadeThroughPageTransitionsBuilder() }, ), ), home: HomePage(), ), ); }
Konklusjon
Som jeg har vist deg, er animasjonspakken flott for å legge til nyttige UI-interaksjoner og overganger til Flutter-appen din. Du kan få hele kildekoden til eksemplene vist her.