Animering af apps med Flutter

Shubham

Om forfatteren

En full stack softwareudvikler om dagen og udvikler af mobile apps om natten. Når jeg ikke arbejder, elsker jeg at lave mad og spille pc-spil.Mere omShubham↬

  • 14 min læsning
  • Apps,Animation
  • Sparet til offline læsning
  • Del på Twitter, LinkedIn
Flutter giver god understøttelse af animation til apps på tværs af platforme. Denne artikel udforsker den nye ukonventionelle og en nemmere måde at tilføje animationer til apps på. Hvad er disse nye “Animation and Motion”-widgets helt præcist, og hvordan kan vi bruge dem til at tilføje enkle og komplekse animationer?

Apps til enhver platform bliver rost, når de er intuitive, ser godt ud og giver behagelig feedback på brugerinteraktioner. Animation er en af måderne at gøre netop dette på.

Flutter, en ramme på tværs af platforme, er i de seneste to år modnet til at omfatte understøttelse af web og desktop. Det har fået et ry for, at apps, der er udviklet med det, er smidige og flotte. Med sin rige animationsunderstøttelse, deklarative måde at skrive brugergrænseflader på, “Hot Reload” og andre funktioner er det nu en komplet ramme på tværs af platforme.

Hvis du starter med Flutter og ønsker at lære en utraditionel måde at tilføje animation på, er du kommet til det rette sted: Vi vil udforske området animation og motion widgets, en implicit måde at tilføje animationer på.

Flutter er baseret på konceptet widgets. Hver visuel komponent i en app er en widget – tænk på dem som visninger i Android. Flutter giver understøttelse af animation ved hjælp af en Animation-klasse, et “AnimationController”-objekt til styring og “Tween” til at interpolere dataområdet. Disse tre komponenter arbejder sammen for at give en jævn animation. Da dette kræver manuel oprettelse og forvaltning af animation, er det kendt som en eksplicit måde at animere på.

Lad mig nu introducere dig til animation og bevægelseswidgets. Flutter tilbyder adskillige widgets, som i sagens natur understøtter animation. Der er ikke behov for at oprette et animationsobjekt eller en controller, da al animation håndteres af denne kategori af widgets. Du skal blot vælge den passende widget til den ønskede animation og sende widgets egenskabsværdier for at animere. Denne teknik er en implicit måde at animere på.

Animationshierarki i Flutter. (Stor forhåndsvisning)

Diagrammet ovenfor beskriver groft sagt animationshierarkiet i Flutter, hvordan både eksplicit og implicit animation understøttes.

Nogle af de animerede widgets, der er omfattet af denne artikel, er:

  • AnimatedOpacity
  • AnimatedCrossFade
  • AnimatedAlign
  • AnimatedPadding
  • AnimatedSize
  • AnimatedPositioned.

Flutter indeholder ikke kun foruddefinerede animerede widgets, men også en generisk widget kaldet AnimatedWidget, som kan bruges til at oprette brugerdefinerede implicit animerede widgets. Som det fremgår af navnet, tilhører disse widgets kategorien animerede widgets og bevægelseswidgets, og de har derfor nogle fælles egenskaber, som gør det muligt for os at lave animationer meget glattere og flottere.

Lad mig forklare disse fælles egenskaber nu, da de vil blive brugt senere i alle eksempler.

  • duration
    Den varighed, som parametrene skal animeres over.
  • reverseDuration
    Den omvendte animations varighed.
  • curve
    Den kurve, der skal anvendes ved animationen af parametrene. De interpolerede værdier kan tages fra en lineær fordeling eller, hvis og når det er angivet, kan de tages fra en kurve.

Lad os begynde rejsen ved at oprette en simpel app, som vi kalder “Citeret”. Den vil vise et tilfældigt citat, hver gang appen starter. To ting skal bemærkes: For det første vil alle disse citater blive hardcodet i applikationen, og for det andet vil der ikke blive gemt nogen brugerdata.

Notat: Alle filer til disse eksempler kan findes på GitHub.

Gå i gang

Flutter bør være installeret, og du skal have et vist kendskab til det grundlæggende flow, før du går videre. Et godt sted at starte er: “Using Google’s Flutter For Truly Cross-Platform Mobile Development”.

Opret et nyt Flutter-projekt i Android Studio.

New flutter project menu i Android Studio. (Stort preview)

Dette åbner en ny projektguide, hvor du kan konfigurere projektets grundelementer.

Skærm til valg af Flutter-projekttype. (Stort eksempel)

I skærmbilledet til valg af projekttype er der forskellige typer Flutter-projekter, som hver især henvender sig til et specifikt scenarie… I denne vejledning skal du vælge Flutter Application og trykke på Next.

Du skal nu indtaste nogle projektspecifikke oplysninger: projektets navn og sti, virksomhedsdomæne osv. Tag et kig på billedet nedenfor.

Skærm til konfiguration af Flutter-applikation. (Stort preview)

Føj projektnavn, Flutter SDK-sti, projektplacering og en valgfri projektbeskrivelse. Tryk på Næste.

Skærmen med navnet på Flutter-programpakke. (Stort eksempel)

Alle applikationer (uanset om det er Android eller iOS) kræver et unikt pakkenavn. Typisk bruger du det omvendte af dit websteds domæne; f.eks. com.google eller com.yahoo. Tryk på Afslut for at generere en fungerende Flutter-applikation.

Det genererede eksempelprojekt. (Stor forhåndsvisning)

Når projektet er genereret, bør du se den ovenfor viste skærm. Åbn filen main.dart (fremhævet i skærmbilledet). Dette er den vigtigste programfil. Eksempelprojektet er komplet i sig selv og kan køres direkte på en emulator eller en fysisk enhed uden nogen ændring.

Udskift indholdet af filen main.dart med følgende kodestump:

import 'package:animated_widgets/FirstPage.dart';import 'package:flutter/material.dart';void main() => runApp(MyApp());class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Animated Widgets', debugShowCheckedModeBanner: false, theme: ThemeData( primarySwatch: Colors.blue, accentColor: Colors.redAccent, ), home: FirstPage(), ); }}

Denne kode rydder op i filen main.dart ved blot at tilføje simple oplysninger, der er relevante for oprettelse af en ny app. Klassen MyApp returnerer et objekt: en MaterialApp widget, som giver den grundlæggende struktur til oprettelse af apps, der er i overensstemmelse med Material Design. For at gøre koden mere struktureret skal du oprette to nye dart-filer i lib-mappen: FirstPage.dart og Quotes.dart.

FirstPage.dart-filen. (Stor forhåndsvisning)

FirstPage.dart vil indeholde al den kode, der er ansvarlig for alle de visuelle elementer (widgets), der er nødvendige for vores Citat-app. Al animation håndteres i denne fil.

Bemærk: Senere i artiklen tilføjes alle kodestumperne for hver animeret widget til denne fil som børn af Scaffold-widget’en. Hvis du vil have flere oplysninger, kan dette eksempel på GitHub være nyttigt.

Start med at tilføje følgende kode til FirstPage.dart. Dette er den delvise kode, hvor andre ting vil blive tilføjet senere.

import 'dart:math';import 'package:animated_widgets/Quotes.dart';import 'package:flutter/material.dart';class FirstPage extends StatefulWidget { @override State createState() { return FirstPageState(); }}class FirstPageState extends State with TickerProviderStateMixin { bool showNextButton = false; bool showNameLabel = false; bool alignTop = false; bool increaseLeftPadding = false; bool showGreetings = false; bool showQuoteCard = false; String name = ''; double screenWidth; double screenHeight; String quote; @override void initState() { super.initState(); Random random = new Random(); int quoteIndex = random.nextInt(Quotes.quotesArray.length); quote = Quotes.quotesArray; } @override Widget build(BuildContext context) { screenWidth = MediaQuery.of(context).size.width; screenHeight = MediaQuery.of(context).size.height; return Scaffold( appBar: _getAppBar(), body: Stack( children: , ), ); }}

Den Quotes.dart-fil. (Stor forhåndsvisning)

Filen Quotes.dart indeholder en liste over alle de hårdkodede citater. Et punkt, der skal bemærkes her, er, at listen er et statisk objekt. Det betyder, at den kan bruges andre steder uden at oprette et nyt objekt af klassen Cotes. Dette er valgt med vilje, da ovenstående liste blot fungerer som et hjælpeværktøj.

Føj følgende kode til denne fil:

class Quotes { static const quotesArray = ;}

Projektskelettet er nu klar, så lad os konkretisere Citat lidt mere.

AnimatedOpacity

For at give appen et personligt præg ville det være rart at kende brugerens navn, så lad os spørge om det og vise en næste knap. Indtil brugeren indtaster sit navn, er denne knap skjult, og den vil elegant blive vist, når et navn er angivet. Vi har brug for en form for synlighedsanimation for knappen, men er der en widget til det? Ja, det er der.

Indtast AnimatedOpacity. Denne widget bygger videre på widgetten Opacity ved at tilføje understøttelse af implicit animation. Hvordan kan vi bruge den? Husk vores scenarie: Vi har brug for at vise en næste knap med animeret synlighed. Vi pakker knapwidgetten ind i AnimatedOpacity-widgetten, indfører nogle ordentlige værdier og tilføjer en betingelse for at udløse animationen – og Flutter kan klare resten.

_getAnimatedOpacityButton() { return AnimatedOpacity( duration: Duration(seconds: 1), reverseDuration: Duration(seconds: 1), curve: Curves.easeInOut, opacity: showNextButton ? 1 : 0, child: _getButton(), );}
Opacity-animation af næste knap. (Stor forhåndsvisning)

Den AnimatedOpacity widget har to obligatoriske egenskaber:

  • opacity
    En værdi på 1 betyder helt synlig; 0 (nul) betyder skjult. Under animationen interpolerer Flutter værdier mellem disse to ekstremer. Du kan se, hvordan en betingelse er placeret for at ændre synligheden og dermed udløse animationen.
  • child
    Den underordnede widget, hvis synlighed vil blive animeret.

Du burde nu forstå, hvor virkelig enkelt det er at tilføje synlighedsanimation med den implicitte widget. Og alle sådanne widgets følger de samme retningslinjer og er nemme at bruge. Lad os gå videre til den næste.

AnimatedCrossFade

Vi har brugerens navn, men widget’en venter stadig på input. I det foregående trin, når brugeren indtaster sit navn, viser vi den næste knap. Nu, når brugeren trykker på knappen, ønsker jeg at stoppe med at acceptere input og vise det indtastede navn. Der er naturligvis mange måder at gøre det på, men måske kan vi skjule input-widget’en og vise en widget med tekst, der ikke kan redigeres. Lad os prøve det med widget AnimatedCrossFade.

Denne widget kræver to børn, da widget’en krydspasser mellem dem baseret på en eller anden betingelse. En vigtig ting at huske på, mens du bruger denne widget, er, at begge børn skal have samme bredde. Hvis højden er forskellig, vil den højere widget blive klippet fra bunden. I dette scenarie vil der blive brugt to widgets som børn: input og label.

_getAnimatedCrossfade() { return AnimatedCrossFade( duration: Duration(seconds: 1), alignment: Alignment.center, reverseDuration: Duration(seconds: 1), firstChild: _getNameInputWidget(), firstCurve: Curves.easeInOut, secondChild: _getNameLabelWidget(), secondCurve: Curves.easeInOut, crossFadeState: showNameLabel ? CrossFadeState.showSecond : CrossFadeState.showFirst, );}
Krydsblænding mellem input-widget og navn-widget. (Stor forhåndsvisning)

Denne widget kræver et andet sæt obligatoriske parametre:

  • crossFadeState
    Denne tilstand finder ud af, hvilket barn der skal vises.
  • firstChild
    Angiver det første barn for denne widget.
  • secondChild
    Angiver det andet barn.

AnimatedAlign

På dette tidspunkt er navneetiketten placeret i midten af skærmen. Det vil se meget bedre ud i toppen, da vi har brug for midten af skærmen til at vise citater. Kort sagt skal justeringen af widgetten til navneskiltet ændres fra midten til toppen. Og ville det ikke være rart at animere denne justering sammen med den tidligere cross-fade-animation? Lad os gøre det.

Som altid kan der bruges flere teknikker til at opnå dette. Da widgeten med navneskiltet allerede er centerjusteret, ville det være meget enklere at animere dens justering end at manipulere widgets værdier for top og venstre. Widgeten AnimatedAlign er perfekt til denne opgave.

For at igangsætte denne animation er der brug for en trigger. Det eneste formål med denne widget er at animere ændring af justeringen, så den har kun få egenskaber: Tilføj et barn, indstil dets justering, udløs ændringen af justeringen, og det er det hele.

_getAnimatedAlignWidget() { return AnimatedAlign(duration: Duration(seconds: 1),curve: Curves.easeInOut,alignment: alignTop ? Alignment.topLeft : Alignment.center,child: _getAnimatedCrossfade(), );}
Animation af justeringen af widget’en navn. (Stor forhåndsvisning)

Den har kun to obligatoriske egenskaber:

  • child:
    Det barn, hvis justering vil blive ændret.
  • alignment:
    Nødvendig justeringsværdi.

Denne widget er virkelig enkel, men resultaterne er elegante. Desuden så vi, hvor nemt vi kan bruge to forskellige animerede widgets til at skabe en mere kompleks animation. Det er det smukke ved animerede widgets.

AnimatedPadding

Nu har vi brugerens navn øverst, der er flydende animeret uden større anstrengelser ved hjælp af forskellige typer animerede widgets. Lad os tilføje en hilsen, “Hej”, før navnet. Hvis vi tilføjer en tekstwidget med værdien “Hej,” øverst, vil den overlappe hilsen-tekstwidgetten, så den ser ud som på billedet nedenfor.

Hilsen- og navne-widgetterne overlapper hinanden. (Stort eksempel)

Hvad nu hvis widgeten med navnetekst havde noget padding til venstre? En forøgelse af padding til venstre vil helt sikkert virke, men vent: Kan vi øge padding med noget animation? Ja, og det er det, som AnimatedPadding gør. For at få det hele til at se meget bedre ud, kan vi lade hilsen-tekst-widgetten fade ind og lade navnetekst-widgettens padding stige på samme tid.

_getAnimatedPaddingWidget() { return AnimatedPadding( duration: Duration(seconds: 1), curve: Curves.fastOutSlowIn, padding: increaseLeftPadding ? EdgeInsets.only(left: 28.0) : EdgeInsets.only(left: 0), child: _getAnimatedCrossfade(), );}

Da animationen ovenfor først skal ske, når den foregående animerede tilpasning er færdig, skal vi forsinke udløsningen af denne animation. Hvis vi kort afviger fra emnet, er dette et godt tidspunkt til at tale om en populær mekanisme til at tilføje forsinkelse. Flutter tilbyder flere sådanne teknikker, men konstruktøren Future.delayed er en af de enklere, renere og mere læsevenlige tilgange. For eksempel for at udføre et stykke kode efter 1 sekund:

Future.delayed(Duration(seconds: 1), (){ sum = a + b; // This sum will be calculated after 1 second. print(sum);});

Da forsinkelsesvarigheden allerede er kendt (beregnet ud fra tidligere animationsvarigheder), kan animationen udløses efter dette interval.

// Showing "Hi" after 1 second - greetings visibility trigger._showGreetings() { Future.delayed(Duration(seconds: 1), () { setState(() { showGreetings = true; }); });}// Increasing the padding for name label widget after 1 second - increase padding trigger._increaseLeftPadding() { Future.delayed(Duration(seconds: 1), () { setState(() { increaseLeftPadding = true; }); });}

Padding animation af navnet widget. (Stor forhåndsvisning)

Denne widget har kun to obligatoriske egenskaber:

  • child
    Det barn inden for denne widget, som padding vil blive anvendt på.
  • padding
    Mængden af plads, der skal tilføjes.

AnimatedSize

I dag vil enhver app, der har en eller anden form for animation, omfatte zoomning ind eller ud af visuelle komponenter for at fange brugerens opmærksomhed (almindeligvis kaldet skaleringsanimation). Hvorfor ikke bruge den samme teknik her? Vi kan vise brugeren et motiverende citat, som zoomes ind fra midten af skærmen. Lad mig præsentere dig for AnimatedSize-widget’en, som aktiverer zoom ind- og zoom ud-effekter, der styres ved at ændre størrelsen på dens underordnede element.

Denne widget er lidt anderledes end de andre, når det gælder de nødvendige parametre. Vi har brug for det, som Flutter kalder en “Ticker”. Flutter har en metode til at lade objekter vide, når der udløses en ny rammehændelse. Man kan tænke på det som noget, der sender et signal, der siger: “Gør det nu! … Gør det nu! … Gør det nu! …”

AnimatedSize Widgeten AnimatedSize kræver en egenskab – vsync – som accepterer en ticker-provider. Den nemmeste måde at få en ticker-provider på er at tilføje en Mixin til klassen. Der er to grundlæggende ticker provider-implementeringer: SingleTickerProviderStateMixin, som giver en enkelt ticker, og TickerProviderStateMixin, som giver flere.

Den standardimplementering af en ticker bruges til at markere rammerne i en animation. I dette tilfælde er sidstnævnte anvendt. Mere om mixins.

// Helper method to create quotes card widget._getQuoteCardWidget() { return Card( color: Colors.green, elevation: 8.0, child: _getAnimatedSizeWidget(), );}// Helper method to create animated size widget and set its properties._getAnimatedSizeWidget() { return AnimatedSize( duration: Duration(seconds: 1), curve: Curves.easeInOut, vsync: this, child: _getQuoteContainer(), );}// Helper method to create the quotes container widget with different sizes._getQuoteContainer() { return Container( height: showQuoteCard ? 100 : 0, width: showQuoteCard ? screenWidth - 32 : 0, child: Center( child: Padding( padding: EdgeInsets.symmetric(horizontal: 16), child: Text(quote, style: TextStyle(color: Colors.white, fontWeight: FontWeight.w400, fontSize: 14),), ), ), );}// Trigger used to show the quote card widget._showQuote() { Future.delayed(Duration(seconds: 2), () { setState(() { showQuoteCard = true; }); });}
Skalering af animationen af citaternes widget. (Stor forhåndsvisning)

Obligatoriske egenskaber for denne widget:

  • vsync
    Den nødvendige ticker-provider til koordinering af animation og rammeændringer.<
  • child
    Det barn, hvis størrelsesændringer vil blive animeret.

Det er nu nemt at styre animationen til at zoome ind og zoome ud.

AnimatedPositioned

Glimrende! Citaterne zoomer ind fra midten for at fange brugerens opmærksomhed. Hvad nu, hvis det gled op fra bunden, mens der zoomes ind? Lad os prøve det. Denne bevægelse indebærer, at vi leger med citatwidgets position og animerer ændringerne i positionsegenskaberne. AnimatedPositioned er den perfekte kandidat.

Denne widget overgår automatisk barnets position over en given varighed, hver gang den angivne position ændres. Et punkt at bemærke: Den fungerer kun, hvis dens overordnet widget er en “Stack”. Denne widget er ret enkel og ligetil at bruge. Lad os se.

// Helper method to create the animated positioned widget.// With position changes based on "showQuoteCard" flag._getAnimatedPositionWidget() { return AnimatedPositioned( duration: Duration(seconds: 1), curve: Curves.easeInOut, child: _getQuoteCardWidget(), top: showQuoteCard ? screenHeight/2 - 100 : screenHeight, left: !showQuoteCard ? screenWidth/2 : 12, );}
Position med skaleringsanimation af citater. (Stort preview)

Denne widget har kun én obligatorisk egenskab:

  • child
    Den widget, hvis position vil blive ændret.

Hvis størrelsen af barnet ikke forventes at ændre sig sammen med dets position, ville et mere performativt alternativ til denne widget være SlideTransition.

Her er vores komplette animation:

Alle de animerede widgets tilsammen. (Stort preview)

Konklusion

Animationer er en integreret del af brugeroplevelsen. Statiske apps eller apps med skramlede animationer sænker ikke kun brugerfastholdelsen, men også udviklerens ry for at levere resultater.

I dag har de fleste populære apps en eller anden form for subtil animation for at glæde brugerne. Animeret feedback på brugernes anmodninger kan også få dem til at udforske mere. Flutter tilbyder mange funktioner til udvikling på tværs af platforme, herunder rig understøttelse af glidende og responsive animationer.

Flutter har stor plug-in-understøttelse, som gør det muligt for os at bruge animationer fra andre udviklere. Nu hvor det er modnet til version 1.9, med så meget kærlighed fra fællesskabet, vil Flutter helt sikkert blive bedre i fremtiden. Jeg vil sige, at nu er et godt tidspunkt at lære Flutter!

Leave a Reply