Animowanie aplikacji za pomocą Flutter
Opublikowany: 2022-03-10Aplikacje na dowolną platformę są chwalone, gdy są intuicyjne, dobrze wyglądają i zapewniają przyjemną reakcję na interakcje użytkowników. Jednym ze sposobów na to jest animacja.
Flutter, platforma wieloplatformowa, dojrzała w ciągu ostatnich dwóch lat, obejmując obsługę sieci i komputerów. Zyskał reputację, że opracowane z nim aplikacje są płynne i dobrze wyglądają. Dzięki bogatej obsłudze animacji, deklaratywnemu sposobowi pisania interfejsu użytkownika, „gorącemu przeładowywaniu” i innym funkcjom jest to teraz kompletny framework międzyplatformowy.
Jeśli zaczynasz z Flutterem i chcesz nauczyć się niekonwencjonalnego sposobu dodawania animacji, to jesteś we właściwym miejscu: odkryjemy królestwo animacji i widżetów ruchu, niejawny sposób dodawania animacji.
Flutter opiera się na koncepcji widżetów. Każdy składnik wizualny aplikacji jest widżetem — pomyśl o nich jako o widokach w systemie Android. Flutter zapewnia obsługę animacji przy użyciu klasy Animation, obiektu „AnimationController” do zarządzania i „Tween” do interpolacji zakresu danych. Te trzy komponenty współpracują ze sobą, aby zapewnić płynną animację. Ponieważ wymaga to ręcznego tworzenia i zarządzania animacją, jest znany jako wyraźny sposób animacji.
Teraz pozwól, że przedstawię Ci animacje i widżety ruchu. Flutter udostępnia liczne widżety, które z natury obsługują animację. Nie ma potrzeby tworzenia obiektu animacji ani żadnego kontrolera, ponieważ cała animacja jest obsługiwana przez tę kategorię widżetów. Wystarczy wybrać odpowiedni widżet dla wymaganej animacji i przekazać wartości właściwości widżetu do animacji. Ta technika jest ukrytym sposobem animacji.

Powyższy wykres z grubsza przedstawia hierarchię animacji w Flutter, w jaki sposób obsługiwane są animacje jawne i niejawne.
Niektóre z animowanych widżetów omówionych w tym artykule to:
-
AnimatedOpacity
-
AnimatedCrossFade
-
AnimatedAlign
-
AnimatedPadding
-
AnimatedSize
-
AnimatedPositioned
.
Flutter zapewnia nie tylko predefiniowane animowane widżety, ale także ogólny widżet o nazwie AnimatedWidget
, którego można używać do tworzenia niestandardowych niejawnie animowanych widżetów. Jak wynika z nazwy, widżety te należą do kategorii widżetów animowanych i ruchomych, a więc mają pewne wspólne właściwości, dzięki którym animacje są znacznie płynniejsze i lepiej wyglądają.
Pozwólcie, że wyjaśnię teraz te wspólne właściwości, ponieważ zostaną użyte później we wszystkich przykładach.
-
duration
Czas trwania animacji parametrów. -
reverseDuration
Czas trwania animacji odwrotnej. -
curve
Krzywa do zastosowania podczas animowania parametrów. Wartości interpolowane można pobrać z rozkładu liniowego lub, jeśli określono, z krzywej.
Zacznijmy podróż od stworzenia prostej aplikacji, którą nazwiemy „Cytowana”. Wyświetli losowy cytat przy każdym uruchomieniu aplikacji. Należy zwrócić uwagę na dwie rzeczy: po pierwsze, wszystkie te cytaty zostaną zakodowane w aplikacji; a po drugie, żadne dane użytkownika nie zostaną zapisane.
Uwaga : wszystkie pliki dla tych przykładów można znaleźć w serwisie GitHub.
Pierwsze kroki
Flutter powinien być zainstalowany, a przed przejściem będziesz potrzebować trochę znajomości podstawowego przepływu. Dobrym miejscem do rozpoczęcia jest „Korzystanie z narzędzia Google Flutter do prawdziwie wieloplatformowego rozwoju mobilnego”.
Utwórz nowy projekt Flutter w Android Studio.

Spowoduje to otwarcie nowego kreatora projektu, w którym możesz skonfigurować podstawy projektu.

Na ekranie wyboru typu projektu znajdują się różne typy projektów Flutter, każdy dostosowany do konkretnego scenariusza. W tym samouczku wybierz aplikację Flutter i naciśnij Dalej .
Musisz teraz wprowadzić pewne informacje dotyczące projektu: nazwę projektu i ścieżkę, domenę firmy i tak dalej. Spójrz na poniższy obrazek.

Dodaj nazwę projektu, ścieżkę Flutter SDK, lokalizację projektu i opcjonalny opis projektu. Naciśnij Dalej .

Każda aplikacja (czy to Android czy iOS) wymaga unikalnej nazwy pakietu. Zazwyczaj używasz odwrotnej strony domeny swojej witryny; na przykład com.google lub com.yahoo. Naciśnij Zakończ , aby wygenerować działającą aplikację Flutter.

Po wygenerowaniu projektu powinieneś zobaczyć ekran pokazany powyżej. Otwórz plik main.dart (podświetlony na zrzucie ekranu). To jest główny plik aplikacji. Przykładowy projekt jest sam w sobie kompletny i można go uruchomić bezpośrednio na emulatorze lub urządzeniu fizycznym bez żadnych modyfikacji.
Zastąp zawartość pliku main.dart następującym fragmentem kodu:
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(), ); } }
Ten kod czyści plik main.dart , dodając proste informacje istotne przy tworzeniu nowej aplikacji. Klasa MyApp zwraca obiekt: widżet MaterialApp
, który zapewnia podstawową strukturę tworzenia aplikacji zgodnych z Material Design. Aby kod był bardziej uporządkowany, utwórz dwa nowe pliki rzutek w folderze lib : FirstPage.dart i Quotes.dart .

FirstPage.dart będzie zawierał cały kod odpowiedzialny za wszystkie elementy wizualne (widgety) wymagane dla naszej aplikacji Quoted. Cała animacja jest obsługiwana w tym pliku.
Uwaga : W dalszej części artykułu wszystkie fragmenty kodu dla każdego animowanego widżetu są dodawane do tego pliku jako elementy podrzędne widżetu Scaffold. Aby uzyskać więcej informacji, przydatny może być ten przykład w serwisie GitHub.
Zacznij od dodania następującego kodu do FirstPage.dart . To jest częściowy kod, do którego później zostaną dodane inne rzeczy.
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[quoteIndex]; } @override Widget build(BuildContext context) { screenWidth = MediaQuery.of(context).size.width; screenHeight = MediaQuery.of(context).size.height; return Scaffold( appBar: _getAppBar(), body: Stack( children: [ // All other children will be added here. // In this article, all the children widgets are contained // in their own separate methods. // Just method calls should be added here for the respective child. ], ), ); } }
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[quoteIndex]; } @override Widget build(BuildContext context) { screenWidth = MediaQuery.of(context).size.width; screenHeight = MediaQuery.of(context).size.height; return Scaffold( appBar: _getAppBar(), body: Stack( children: [ // All other children will be added here. // In this article, all the children widgets are contained // in their own separate methods. // Just method calls should be added here for the respective child. ], ), ); } }
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[quoteIndex]; } @override Widget build(BuildContext context) { screenWidth = MediaQuery.of(context).size.width; screenHeight = MediaQuery.of(context).size.height; return Scaffold( appBar: _getAppBar(), body: Stack( children: [ // All other children will be added here. // In this article, all the children widgets are contained // in their own separate methods. // Just method calls should be added here for the respective child. ], ), ); } }
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[quoteIndex]; } @override Widget build(BuildContext context) { screenWidth = MediaQuery.of(context).size.width; screenHeight = MediaQuery.of(context).size.height; return Scaffold( appBar: _getAppBar(), body: Stack( children: [ // All other children will be added here. // In this article, all the children widgets are contained // in their own separate methods. // Just method calls should be added here for the respective child. ], ), ); } }

Plik Quotes.dart zawiera listę wszystkich zakodowanych na stałe cytatów. Należy zauważyć, że lista jest obiektem statycznym. Oznacza to, że można go używać w innych miejscach bez tworzenia nowego obiektu klasy Quotes. Jest to wybrane celowo, ponieważ powyższa lista działa po prostu jako narzędzie.

Dodaj następujący kod do tego pliku:
class Quotes { static const quotesArray = [ "Good, better, best. Never let it rest. 'Til your good is better and your better is best", "It does not matter how slowly you go as long as you do not stop.", "Only I can change my life. No one can do it for me." ]; }
Szkielet projektu jest już gotowy, więc rozwińmy nieco więcej.
AnimatedOpacity
Aby nadać aplikacji osobisty charakter, dobrze byłoby znać nazwę użytkownika, więc zapytajmy o to i pokażmy następny przycisk. Dopóki użytkownik nie wprowadzi swojego imienia, przycisk ten jest ukryty i będzie ładnie widoczny po podaniu nazwy. Potrzebujemy jakiejś animacji widoczności przycisku, ale czy jest do tego widżet? Tak jest.
Wprowadź AnimatedOpacity
. Ten widżet opiera się na widżecie Krycie przez dodanie obsługi animacji niejawnych. Jak tego używamy? Zapamiętaj nasz scenariusz: musimy pokazać kolejny przycisk z animowaną widocznością. Zawijamy widżet przycisku wewnątrz widżetu AnimatedOpacity
, wprowadzamy odpowiednie wartości i dodajemy warunek wyzwalający animację — a Flutter poradzi sobie z resztą.
_getAnimatedOpacityButton() { return AnimatedOpacity( duration: Duration(seconds: 1), reverseDuration: Duration(seconds: 1), curve: Curves.easeInOut, opacity: showNextButton ? 1 : 0, child: _getButton(), ); }

Widżet AnimatedOpacity
ma dwie obowiązkowe właściwości:
-
opacity
Wartość 1 oznacza całkowicie widoczne; 0 (zero) oznacza ukryte. Podczas animacji Flutter interpoluje wartości pomiędzy tymi dwoma ekstremami. Możesz zobaczyć, jak umieszczony jest warunek, aby zmienić widoczność, uruchamiając w ten sposób animację. -
child
Widżet podrzędny, który będzie miał animowaną widoczność.
Powinieneś teraz zrozumieć, jak naprawdę proste jest dodanie animacji widoczności za pomocą niejawnego widżetu. Wszystkie takie widżety są zgodne z tymi samymi wytycznymi i są łatwe w użyciu. Przejdźmy do następnego.
AnimatedCrossFade
Mamy nazwę użytkownika, ale widżet wciąż czeka na wprowadzenie. W poprzednim kroku, gdy użytkownik wprowadzi swoją nazwę, wyświetlamy następny przycisk. Teraz, gdy użytkownik naciśnie przycisk, chcę przestać akceptować dane wejściowe i pokazać wprowadzoną nazwę. Oczywiście można to zrobić na wiele sposobów, ale być może uda nam się ukryć widżet wejściowy i pokazać nieedytowalny widżet tekstowy. Wypróbujmy to za pomocą widżetu AnimatedCrossFade
.
Ten widżet wymaga dwojga elementów podrzędnych, ponieważ widżet przechodzi między nimi w zależności od pewnych warunków. Jedną ważną rzeczą, o której należy pamiętać podczas korzystania z tego widżetu, jest to, że oba elementy potomne powinny mieć tę samą szerokość. Jeśli wysokość jest inna, wyższy widżet zostanie przycięty od dołu. W tym scenariuszu jako elementy podrzędne zostaną użyte dwa widżety: input i 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, ); }

Ten widżet wymaga innego zestawu obowiązkowych parametrów:
-
crossFadeState
Ten stan określa, które dziecko pokazać. -
firstChild
Określa pierwsze dziecko dla tego widżetu. -
secondChild
Określa drugie dziecko.
AnimatedAlign
W tym momencie etykieta z nazwą znajduje się na środku ekranu. Na górze będzie wyglądać znacznie lepiej, ponieważ potrzebujemy środka ekranu, aby wyświetlić cytaty. Mówiąc najprościej, wyrównanie widżetu etykiety nazwy powinno zostać zmienione od środka do góry. Czy nie byłoby miło animować tę zmianę wyrównania wraz z poprzednią animacją przenikania? Zróbmy to.
Jak zwykle, aby to osiągnąć, można użyć kilku technik. Ponieważ widżet etykiety nazwy jest już wyrównany do środka, animowanie jego wyrównania byłoby znacznie prostsze niż manipulowanie górną i lewą wartością widżetu. Widżet AnimatedAlign
jest idealny do tego zadania.
Aby zainicjować tę animację, wymagany jest wyzwalacz. Jedynym celem tego widżetu jest animowanie zmiany wyrównania, więc ma tylko kilka właściwości: dodaj dziecko, ustaw jego wyrównanie, wywołaj zmianę wyrównania i to wszystko.
_getAnimatedAlignWidget() { return AnimatedAlign( duration: Duration(seconds: 1), curve: Curves.easeInOut, alignment: alignTop ? Alignment.topLeft : Alignment.center, child: _getAnimatedCrossfade(), ); }

Ma tylko dwie obowiązkowe właściwości:
- dziecko:
Dziecko, którego wyrównanie zostanie zmodyfikowane. - wyrównanie:
Wymagana wartość wyrównania.
Ten widget jest naprawdę prosty, ale wyniki są eleganckie. Co więcej, zobaczyliśmy, jak łatwo możemy użyć dwóch różnych animowanych widżetów do stworzenia bardziej złożonej animacji. Na tym polega piękno animowanych widżetów.
AnimatedPadding
Teraz na górze mamy nazwę użytkownika, płynnie animowaną bez większego wysiłku, przy użyciu różnego rodzaju animowanych widżetów. Dodajmy powitanie „Cześć” przed nazwą. Dodanie widżetu tekstowego z wartością „Cześć” u góry spowoduje, że będzie on nakładał się na widżet tekstowy powitania, tak jak na poniższym obrazku.

Co by było, gdyby widżet tekstowy nazwy miał trochę dopełnienia po lewej stronie? Zwiększenie lewego dopełnienia na pewno zadziała, ale poczekaj: czy możemy zwiększyć dopełnienie z jakąś animacją? Tak, i to właśnie robi AnimatedPadding
. Aby wszystko to wyglądało znacznie lepiej, pozwólmy, aby widżet z pozdrowieniami pojawił się w tym samym czasie, a dopełnienie widżetu z tekstem nazwy zwiększyło się w tym samym czasie.
_getAnimatedPaddingWidget() { return AnimatedPadding( duration: Duration(seconds: 1), curve: Curves.fastOutSlowIn, padding: increaseLeftPadding ? EdgeInsets.only(left: 28.0) : EdgeInsets.only(left: 0), child: _getAnimatedCrossfade(), ); }
Ponieważ powyższa animacja powinna pojawić się dopiero po zakończeniu poprzedniego animowanego wyrównania, musimy opóźnić uruchomienie tej animacji. Odchodząc pokrótce od tematu, to dobry moment, aby porozmawiać o popularnym mechanizmie dodawania opóźnień. Flutter udostępnia kilka takich technik, ale konstruktor Future.delayed jest jednym z prostszych, czystszych i bardziej czytelnych podejść. Na przykład, aby wykonać fragment kodu po 1 sekundzie:
Future.delayed(Duration(seconds: 1), (){ sum = a + b; // This sum will be calculated after 1 second. print(sum); });
Ponieważ czas trwania opóźnienia jest już znany (obliczony na podstawie poprzednich czasów trwania animacji), animacja może zostać wyzwolona po tym przedziale.
// 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; }); }); }

Ten widżet ma tylko dwie obowiązkowe właściwości:
-
child
Dziecko wewnątrz tego widżetu, do którego zostanie zastosowane dopełnienie. -
padding
Ilość miejsca do dodania.
AnimatedSize
Obecnie każda aplikacja posiadająca jakiś rodzaj animacji będzie obejmować powiększanie lub pomniejszanie komponentów wizualnych, aby przyciągnąć uwagę użytkownika (powszechnie nazywane animacją skalowania). Dlaczego nie zastosować tutaj tej samej techniki? Możemy pokazać użytkownikowi motywacyjny cytat, który zbliża się ze środka ekranu. Pozwólcie, że przedstawię wam widżet AnimatedSize
, który włącza efekty powiększania i pomniejszania, kontrolowane przez zmianę rozmiaru jego elementu podrzędnego.
Ten widżet różni się nieco od pozostałych, jeśli chodzi o wymagane parametry. Potrzebujemy tego, co Flutter nazywa „Tickerem”. Flutter ma metodę powiadamiania obiektów o każdym wyzwoleniu nowego zdarzenia klatki. Można to traktować jako coś, co wysyła sygnał mówiący: „Zrób to teraz! … Zrób to teraz! … Zrób to teraz! …”
Widżet AnimatedSize
wymaga właściwości — vsync — która akceptuje dostawcę symboli. Najłatwiejszym sposobem na zdobycie dostawcy tickerów jest dodanie Mixin do klasy. Istnieją dwie podstawowe implementacje dostawcy tickerów: SingleTickerProviderStateMixin
, który zapewnia pojedynczy ticker; i TickerProviderStateMixin
, który zapewnia kilka.
Domyślna implementacja Tickera służy do oznaczania klatek animacji. W tym przypadku stosuje się ten ostatni. Więcej o mixinach.
// 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; }); }); }

Obowiązkowe właściwości tego widżetu:
-
vsync
Wymagany dostawca pasków do koordynowania animacji i zmian klatek.< -
child
Dziecko, którego rozmiar się zmienia, będzie animowane.
Animację powiększania i pomniejszania można teraz łatwo okiełznać.
AnimatedPositioned
Świetnie! Cytaty powiększają się od środka, aby przyciągnąć uwagę użytkownika. Co się stanie, jeśli zsunie się od dołu podczas powiększania? Spróbujmy. Ten ruch polega na zabawie z pozycją widżetu cytatu i animowaniu zmian we właściwościach pozycji. AnimatedPositioned
to idealny kandydat.
Ten widżet automatycznie zmienia pozycję dziecka w określonym czasie za każdym razem, gdy zmieni się określona pozycja. Jedna uwaga: działa tylko wtedy, gdy jego widżetem nadrzędnym jest „Stos”. Ten widżet jest dość prosty i łatwy w użyciu. Zobaczmy.
// 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, ); }

Ten widżet ma tylko jedną obowiązkową właściwość:
-
child
Widget, którego pozycja zostanie zmieniona.
Jeśli rozmiar elementu podrzędnego nie zmieni się wraz z jego pozycją, bardziej wydajną alternatywą dla tego widżetu będzie SlideTransition
.
Oto nasza pełna animacja:

Wniosek
Animacje są integralną częścią doświadczenia użytkownika. Aplikacje statyczne lub aplikacje z niestabilną animacją nie tylko obniżają retencję użytkowników, ale także reputację programisty w zakresie dostarczania wyników.
Obecnie najpopularniejsze aplikacje mają jakąś subtelną animację, która zachwyca użytkowników. Animowane informacje zwrotne na prośby użytkowników mogą również zachęcić ich do dalszego odkrywania. Flutter oferuje wiele funkcji do rozwoju międzyplatformowego, w tym bogate wsparcie dla płynnych i responsywnych animacji.
Flutter ma świetną obsługę wtyczek, które pozwalają nam korzystać z animacji innych programistów. Teraz, gdy dojrzał do wersji 1.9, z tak wielką miłością społeczności, Flutter z pewnością stanie się lepszy w przyszłości. Powiedziałbym, że teraz jest świetny czas na naukę Fluttera!
Dalsze zasoby
- „Widżety animacji i ruchu”, Flutter Docs
- „Wprowadzenie do animacji”, Flutter Docs
- Flutter CodeLabs
Uwaga redaktora : Ogromne podziękowania dla Ahmada Awaisa za pomoc w zrecenzowaniu tego artykułu.