Flutter Animacje: Kompleksowy poradnik
Mateusz Kędziora

Witaj programisto! W tym artykule zajmiemy się animacjami we Flutterze. Animacje są kluczowym elementem tworzenia angażujących i intuicyjnych interfejsów użytkownika. Dzięki nim aplikacja staje się bardziej responsywna i przyjemna w użyciu. Celem tego artykułu jest kompleksowe omówienie różnych technik animacji dostępnych w Flutterze, od prostych animacji domyślnych (implicit) po zaawansowane animacje wektorowe z wykorzystaniem Rive. Bez względu na Twój poziom zaawansowania, znajdziesz tutaj coś dla siebie. Przejdziemy przez przykłady kodu, wyjaśnienia i wskazówki, które pomogą Ci opanować sztukę animacji w Flutterze.
Co znajdziesz w tym artykule?
- Wprowadzenie do animacji w Flutterze: Dlaczego animacje są ważne i co zamierzamy osiągnąć.
- Animacje domyślne (Implicit Animations): Wykorzystanie
AnimatedContainer
,AnimatedOpacity
iTweenAnimationBuilder
. - Animacje jawne (Explicit Animations): Użycie
AnimationController
iTween
do precyzyjnej kontroli nad animacją. - Animacje z CustomPainter: Tworzenie własnych animacji za pomocą rysowania.
- Zaawansowane techniki animacji: Krzywe animacji, animacje kaskadowe (staggered animations) i optymalizacja wydajności.
- Animacje Rive: Wprowadzenie do Rive i integracja z Flutterem.
- Podsumowanie: Kluczowe wnioski i zachęta do dalszej nauki.
Zaczynajmy!
1. Wprowadzenie do animacji w Flutterze
Animacje w aplikacjach mobilnych i webowych odgrywają kluczową rolę w poprawie doświadczenia użytkownika (UX). Poprawnie zaimplementowane animacje nie tylko sprawiają, że aplikacja wygląda bardziej atrakcyjnie, ale również zwiększają jej intuicyjność. Animacje mogą:
- Wskazywać zmiany stanu: Np. animacja ładowania, przejście między ekranami.
- Dawać feedback użytkownikowi: Np. animacja przycisku po kliknięciu.
- Poprawiać percepcję: Np. płynne przejścia sprawiają, że aplikacja wydaje się szybsza.
- Angażować użytkownika: Np. interaktywne elementy animowane.
Flutter oferuje bogaty zestaw narzędzi do tworzenia różnorodnych animacji, od prostych przejść po zaawansowane interaktywne efekty. W tym artykule zapoznasz się z kilkoma z nich, zaczynając od najprostszych (animacje domyślne), przez bardziej zaawansowane (animacje jawne i CustomPainter
) aż po integrację z zewnętrzną biblioteką Rive.
2. Animacje domyślne (Implicit Animations)
Animacje domyślne, nazywane również animacjami niejawnymi, są najprostszym sposobem dodawania animacji w Flutterze. Wykorzystują one wbudowane widgety, które automatycznie animują zmiany swoich właściwości. Oznacza to, że zamiast ręcznie kontrolować proces animacji, po prostu zmieniasz wartość jakiejś właściwości widgetu, a Flutter zajmuje się resztą – animacją przejścia pomiędzy starą a nową wartością.
Najpopularniejsze widgety do animacji domyślnych:
AnimatedContainer
: Animuje zmiany w kontenerze, takie jak rozmiar, kolor, margines, padding, dekoracja (np. border radius).AnimatedOpacity
: Animuje zmiany przezroczystości (opacity).AnimatedPadding
: Animuje zmiany paddingu.AnimatedPositioned
: Animuje zmiany pozycji widgetu wStack
.AnimatedDefaultTextStyle
: Animuje zmiany stylu tekstu.AnimatedCrossFade
: Umożliwia płynne przechodzenie między dwoma widgetami.TweenAnimationBuilder
: Pozwala na tworzenie własnych animacji domyślnych dla dowolnych właściwości.
Przykład: AnimatedContainer
Poniższy przykład pokazuje, jak użyć AnimatedContainer
do zmiany koloru i rozmiaru kontenera po naciśnięciu przycisku.
import 'package:flutter/material.dart';
import 'dart:math';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Implicit Animation Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key}) : super(key: key);
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
double _width = 100;
double _height = 100;
Color _color = Colors.blue;
BorderRadiusGeometry _borderRadius = BorderRadius.circular(8);
void _animateContainer() {
setState(() {
// Generuj losowe wartości dla animacji
Random random = Random();
_width = random.nextInt(200).toDouble() + 50;
_height = random.nextInt(200).toDouble() + 50;
_color = Color.fromRGBO(
random.nextInt(256),
random.nextInt(256),
random.nextInt(256),
1,
);
_borderRadius = BorderRadius.circular(random.nextInt(50).toDouble());
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Implicit Animation Demo'),
),
body: Center(
child: AnimatedContainer(
width: _width,
height: _height,
decoration: BoxDecoration(
color: _color,
borderRadius: _borderRadius,
),
duration: Duration(milliseconds: 500), // Czas trwania animacji
curve: Curves.fastOutSlowIn, // Krzywa animacji
),
),
floatingActionButton: FloatingActionButton(
onPressed: _animateContainer,
tooltip: 'Animate',
child: Icon(Icons.play_arrow),
),
);
}
}
Wyjaśnienie kodu:
AnimatedContainer
przyjmuje parametrywidth
,height
,decoration
,duration
icurve
.duration
określa czas trwania animacji.curve
określa krzywą animacji (np.Curves.easeInOut
,Curves.bounceOut
). Dostępnych jest wiele predefiniowanych krzywych, które nadają animacji charakterystyczny wygląd.- Po naciśnięciu przycisku (
FloatingActionButton
), funkcja_animateContainer
zmienia wartości_width
,_height
,_color
i_borderRadius
. setState()
informuje Fluttera, że stan widgetu się zmienił, co powoduje przebudowanie widgetuAnimatedContainer
z nowymi wartościami.AnimatedContainer
automatycznie animuje przejście pomiędzy starymi a nowymi wartościami właściwościwidth
,height
,color
,borderRadius
w czasie określonym przezduration
i zgodnie z krzywą określoną przezcurve
.
Przykład: AnimatedOpacity
Poniższy przykład demonstruje jak zmieniać przezroczystość widgetu z użyciem AnimatedOpacity
.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter AnimatedOpacity Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key}) : super(key: key);
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
double _opacity = 1.0;
void _toggleOpacity() {
setState(() {
_opacity = _opacity == 1.0 ? 0.0 : 1.0;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('AnimatedOpacity Demo'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
AnimatedOpacity(
opacity: _opacity,
duration: Duration(seconds: 1),
child: Container(
width: 200,
height: 200,
color: Colors.red,
),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: _toggleOpacity,
child: Text('Toggle Opacity'),
),
],
),
),
);
}
}
Wyjaśnienie kodu:
AnimatedOpacity
przyjmuje parametryopacity
iduration
.opacity
określa stopień przezroczystości widgetu (0.0 - całkowicie przezroczysty, 1.0 - całkowicie widoczny).- Po naciśnięciu przycisku (
ElevatedButton
), funkcja_toggleOpacity
zmienia wartość_opacity
na przeciwną (1.0 na 0.0 lub 0.0 na 1.0). AnimatedOpacity
automatycznie animuje zmianę przezroczystości w czasie określonym przezduration
.
TweenAnimationBuilder
- Tworzenie własnych animacji domyślnych
TweenAnimationBuilder
jest bardziej zaawansowanym widgetem, który pozwala na tworzenie własnych animacji domyślnych dla dowolnych właściwości, które nie są bezpośrednio obsługiwane przez inne widgety Animated...
. Wykorzystuje on Tween
, który definiuje zakres wartości animacji.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter TweenAnimationBuilder Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key}) : super(key: key);
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
double _targetValue = 0.0;
void _animateValue() {
setState(() {
_targetValue = _targetValue == 0.0 ? 1.0 : 0.0;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('TweenAnimationBuilder Demo'),
),
body: Center(
child: TweenAnimationBuilder<double>(
tween: Tween<double>(begin: 0.0, end: _targetValue),
duration: Duration(seconds: 1),
builder: (BuildContext context, double value, Widget? child) {
return Transform.scale(
scale: 1 + value,
child: Container(
width: 100,
height: 100,
color: Colors.green,
),
);
},
),
),
floatingActionButton: FloatingActionButton(
onPressed: _animateValue,
tooltip: 'Animate',
child: Icon(Icons.play_arrow),
),
);
}
}
Wyjaśnienie kodu:
TweenAnimationBuilder
przyjmuje parametrytween
,duration
ibuilder
.tween
definiuje zakres wartości animacji (odbegin
doend
). W tym przykładzie animujemy wartość typudouble
od 0.0 do_targetValue
.builder
jest funkcją, która buduje widget na podstawie aktualnej wartości animacji. Przyjmuje trzy argumenty:context
,value
(aktualna wartość animacji) ichild
(opcjonalny widget, który można ponownie wykorzystać).- W tym przykładzie używamy
Transform.scale
do skalowania kontenera w oparciu o wartość animacji. - Po naciśnięciu przycisku (
FloatingActionButton
), funkcja_animateValue
zmienia wartość_targetValue
. TweenAnimationBuilder
automatycznie animuje wartość od 0.0 do_targetValue
(lub z powrotem) w czasie określonym przezduration
.
Podsumowanie Animacji Domyślnych:
Animacje domyślne są proste w użyciu i idealne do tworzenia podstawowych animacji, takich jak zmiany koloru, rozmiaru, przezroczystości czy paddingu. Są łatwe do implementacji i nie wymagają zaawansowanej wiedzy na temat animacji. Jednakże, mają pewne ograniczenia – nie dają pełnej kontroli nad procesem animacji i nie pozwalają na tworzenie złożonych sekwencji animacji. Do bardziej zaawansowanych animacji potrzebne są animacje jawne.
3. Animacje jawne (Explicit Animations)
Animacje jawne, w przeciwieństwie do animacji domyślnych, dają pełną kontrolę nad procesem animacji. Wykorzystują one AnimationController
i Tween
, aby precyzyjnie kontrolować każdą klatkę animacji. Dzięki temu można tworzyć złożone sekwencje animacji, zmieniać prędkość animacji w czasie, dodawać efekty specjalne i interakcje.
Kluczowe elementy animacji jawnych:
AnimationController
: Zarządza czasem trwania animacji, kierunkiem (do przodu, do tyłu) i stanem (rozpoczęta, zatrzymana, zakończona).Tween
: Definiuje zakres wartości animacji (odbegin
doend
).Animation
: Reprezentuje aktualną wartość animacji w danym momencie czasu.Animation
jest tworzona na podstawieAnimationController
iTween
.Listener
: Funkcja wywoływana przy każdej zmianie wartości animacji. WListener
aktualizujemy stan widgetu (np. używającsetState()
), aby wyświetlić aktualną klatkę animacji.
Przykład: Prosta animacja z AnimationController
i Tween
Poniższy przykład pokazuje, jak użyć AnimationController
i Tween
do animowania przesunięcia (offset) kontenera.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Explicit Animation Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key}) : super(key: key);
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<Offset> _offsetAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this, // Potrzebne do synchronizacji animacji z odświeżaniem ekranu
);
_offsetAnimation = Tween<Offset>(
begin: Offset.zero,
end: const Offset(1, 0), // Przesunięcie o 1 w prawo
).animate(CurvedAnimation(
parent: _controller,
curve: Curves.easeInOut,
));
_controller.repeat(reverse: true); // Powtarzaj animację w przód i w tył
}
@override
void dispose() {
_controller.dispose(); // Zwolnij zasoby
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Explicit Animation Demo'),
),
body: Center(
child: SlideTransition(
position: _offsetAnimation,
child: Container(
width: 100,
height: 100,
color: Colors.red,
),
),
),
);
}
}
Wyjaśnienie kodu:
SingleTickerProviderStateMixin
jest potrzebny do synchronizacji animacji z odświeżaniem ekranu.AnimationController
jest inicjalizowany winitState()
zduration
(czas trwania animacji) ivsync
(obiekt, który dostarcza sygnały synchronizacji ekranu).Tween<Offset>
definiuje zakres wartości animacji (odOffset.zero
doOffset(1, 0)
)._offsetAnimation
jest tworzona na podstawieAnimationController
iTween
. Dodatkowo używamyCurvedAnimation
aby dodać krzywą animacji (Curves.easeInOut
)._controller.repeat(reverse: true)
uruchamia animację i powtarza ją w nieskończoność, odwracając kierunek po osiągnięciu końca.SlideTransition
jest widgetem, który przesuwa swój child w oparciu o wartość_offsetAnimation
._controller.dispose()
zwalnia zasoby, gdy widget jest usuwany z drzewa widgetów.
Sterowanie cyklem życia animacji
AnimationController
oferuje metody do sterowania cyklem życia animacji:
forward()
: Rozpoczyna animację w kierunku do przodu.reverse()
: Rozpoczyna animację w kierunku do tyłu.stop()
: Zatrzymuje animację.reset()
: Resetuje animację do stanu początkowego.dispose()
: Zwalnia zasoby.
Można również monitorować stan animacji za pomocą AnimationStatusListener
:
_controller.addStatusListener((status) {
if (status == AnimationStatus.completed) {
// Animacja zakończyła się w kierunku do przodu
_controller.reverse();
} else if (status == AnimationStatus.dismissed) {
// Animacja zakończyła się w kierunku do tyłu
_controller.forward();
}
});
Łączenie animacji (Chaining Animations)
Można łączyć animacje, aby tworzyć złożone sekwencje. Można to zrobić na kilka sposobów:
- Użycie
Future.delayed
: Opóźnia rozpoczęcie kolejnej animacji.
_controller.forward().then((_) {
Future.delayed(Duration(seconds: 1), () {
_controller2.forward(); // Uruchom drugą animację po 1 sekundzie
});
});
- Użycie
AnimationStatusListener
: Uruchamia kolejną animację po zakończeniu poprzedniej.
_controller.addStatusListener((status) {
if (status == AnimationStatus.completed) {
_controller2.forward(); // Uruchom drugą animację po zakończeniu pierwszej
}
});
_controller.forward();
- Użycie
SequentialAnimation
(z pakietuanimations
): Pozwala na definiowanie sekwencji animacji. Niestety pakiet ten nie jest już aktywnie rozwijany (stan na 8 maja 2025), ale może stanowić inspirację. Zalecane jest napisanie własnego rozwiązania bazującego na powyższych przykładach lub poszukanie alternatywnych pakietów.
Krzywe animacji
Krzywe animacji (Curves
) definiują, jak wartość animacji zmienia się w czasie. Domyślnie animacja ma liniowy przebieg (wartość zmienia się równomiernie). Krzywe animacji pozwalają na tworzenie bardziej naturalnych i interesujących efektów.
Flutter oferuje wiele predefiniowanych krzywych:
Curves.linear
: Liniowy przebieg animacji.Curves.easeInOut
: Animacja rozpoczyna się i kończy wolno.Curves.easeIn
: Animacja rozpoczyna się wolno i przyspiesza.Curves.easeOut
: Animacja rozpoczyna się szybko i zwalnia.Curves.bounceIn
: Efekt odbicia na początku animacji.Curves.bounceOut
: Efekt odbicia na końcu animacji.Curves.elasticIn
: Efekt elastycznego rozciągania na początku animacji.Curves.elasticOut
: Efekt elastycznego rozciągania na końcu animacji.
Można również tworzyć własne krzywe animacji, implementując interfejs Curve
.
class MyCustomCurve extends Curve {
@override
double transformInternal(double t) {
// Implementacja własnej krzywej animacji
return t * t; // Prosty przykład - kwadratowa krzywa
}
}
Podsumowanie Animacji Jawnych:
Animacje jawne dają pełną kontrolę nad procesem animacji, pozwalając na tworzenie złożonych sekwencji animacji, zmiany prędkości animacji w czasie, dodawanie efektów specjalnych i interakcji. Wymagają one jednak więcej kodu i zrozumienia, niż animacje domyślne. Są idealne do tworzenia zaawansowanych interfejsów użytkownika i interaktywnych elementów.
4. Animacje z CustomPainter
CustomPainter
pozwala na rysowanie własnych kształtów i elementów graficznych na ekranie. Można go użyć do tworzenia niestandardowych animacji, które nie są możliwe do osiągnięcia za pomocą standardowych widgetów. Dzięki CustomPainter
możemy animować ścieżki (paths), gradienty, tekstury i inne elementy wizualne.
Kluczowe elementy animacji z CustomPainter
:
CustomPainter
: Klasa, którą rozszerzamy, aby zdefiniować, co ma być narysowane na ekranie.paint(Canvas canvas, Size size)
: Metoda, w której rysujemy elementy graficzne.shouldRepaint(CustomPainter oldDelegate)
: Metoda, która określa, czy widget powinien być przerysowany.Animation
: Wykorzystywana do animowania właściwości rysowanych elementów.RepaintBoundary
: Widget, który izoluje obszar rysowania, poprawiając wydajność.
Przykład: Animacja okręgu
Poniższy przykład pokazuje, jak użyć CustomPainter
do animowania promienia okręgu.
import 'package:flutter/material.dart';
import 'dart:math';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter CustomPainter Animation Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key}) : super(key: key);
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _radiusAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
);
_radiusAnimation = Tween<double>(
begin: 0,
end: 100,
).animate(_controller);
_controller.repeat(reverse: true);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('CustomPainter Animation Demo'),
),
body: Center(
child: RepaintBoundary( // Izolacja obszaru rysowania
child: AnimatedBuilder(
animation: _radiusAnimation,
builder: (context, child) {
return CustomPaint(
size: Size(200, 200),
painter: CirclePainter(radius: _radiusAnimation.value),
);
},
),
),
),
);
}
}
class CirclePainter extends CustomPainter {
final double radius;
CirclePainter({required this.radius});
@override
void paint(Canvas canvas, Size size) {
final center = size.center(Offset.zero);
final paint = Paint()
..color = Colors.red
..style = PaintingStyle.fill;
canvas.drawCircle(center, radius, paint);
}
@override
bool shouldRepaint(covariant CirclePainter oldDelegate) {
return oldDelegate.radius != radius; // Przerysuj tylko jeśli promień się zmienił
}
}
Wyjaśnienie kodu:
CirclePainter
rozszerza klasęCustomPainter
.- W metodzie
paint
rysujemy okrąg za pomocącanvas.drawCircle
. Promień okręgu jest określony przezradius
. - Metoda
shouldRepaint
zwracatrue
tylko wtedy, gdy promień się zmienił. Dzięki temu unikamy niepotrzebnego przerysowywania widgetu. AnimatedBuilder
odbudowuje widget za każdym razem, gdy wartość_radiusAnimation
się zmienia.RepaintBoundary
izoluje obszar rysowania, co poprawia wydajność animacji.
Animowanie ścieżek (Paths)
Można animować ścieżki (paths) za pomocą CustomPainter
, tworząc złożone i dynamiczne efekty. Aby to zrobić, należy zdefiniować ścieżkę w metodzie paint
i animować jej punkty kontrolne lub inne właściwości.
Animowanie gradientów
Można również animować gradienty, zmieniając ich kolory, położenie lub orientację. Aby to zrobić, należy zdefiniować Gradient
w metodzie paint
i animować jego właściwości.
Podsumowanie Animacji z CustomPainter
:
CustomPainter
daje ogromną elastyczność w tworzeniu niestandardowych animacji. Pozwala na rysowanie dowolnych kształtów i animowanie ich właściwości. Wymaga jednak dobrego zrozumienia rysowania w Flutterze i optymalizacji wydajności. Jest idealny do tworzenia unikalnych i wizualnie atrakcyjnych efektów.
5. Zaawansowane techniki animacji
Po opanowaniu podstawowych technik animacji w Flutterze, można zacząć eksperymentować z bardziej zaawansowanymi technikami, które pozwolą na tworzenie bardziej naturalnych, płynnych i angażujących animacji.
Krzywe animacji dla naturalnych animacji
Używanie odpowiednich krzywych animacji ma kluczowe znaczenie dla tworzenia naturalnych i realistycznych efektów. Eksperymentuj z różnymi krzywymi, aby znaleźć te, które najlepiej pasują do Twojej animacji.
Curves.easeInOut
: Idealna do większości animacji, tworzy płynne przejście z przyspieszeniem i zwalnianiem.Curves.elasticOut
: Daje efekt sprężystego odbicia na końcu animacji, świetny do animacji, które mają imitować fizyczne ruchy.Curves.bounceOut
: Podobnie jakelasticOut
, ale z efektem odbicia.Curves.fastOutSlowIn
: Szybki początek i powolne zakończenie, dobra do animacji, które mają być dynamiczne, ale eleganckie.
Można również tworzyć własne krzywe animacji, implementując interfejs Curve
, aby uzyskać jeszcze bardziej unikalne efekty.
Animacje kaskadowe (Staggered Animations)
Animacje kaskadowe to technika, w której animacje poszczególnych elementów uruchamiane są z opóźnieniem, tworząc efekt sekwencyjny. Dzięki temu animacja wydaje się bardziej dynamiczna i interesująca.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Staggered Animation Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key}) : super(key: key);
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
late AnimationController _controller;
late List<Animation<double>> _animations;
int numberOfItems = 5;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
);
_animations = List.generate(numberOfItems, (index) {
return Tween<double>(
begin: 0.0,
end: 1.0,
).animate(
CurvedAnimation(
parent: _controller,
curve: Interval(
index / numberOfItems, // Start animacji elementu
1.0, // Koniec animacji elementu
curve: Curves.easeOut,
),
),
);
});
_controller.forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Staggered Animation Demo'),
),
body: Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(numberOfItems, (index) {
return FadeTransition(
opacity: _animations[index],
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
width: 50,
height: 50,
color: Colors.blue,
),
),
);
}),
),
),
);
}
}
Wyjaśnienie kodu:
- Tworzymy listę animacji (
_animations
) dla każdego elementu. - Używamy
Interval
wCurvedAnimation
, aby opóźnić rozpoczęcie animacji każdego elementu. index / numberOfItems
określa moment rozpoczęcia animacji dla danego elementu w relacji do czasu trwania całej animacji.FadeTransition
animuje przezroczystość każdego elementu w oparciu o jego animację.
Budowanie złożonych sekwencji animacji
Do budowania złożonych sekwencji animacji można wykorzystać kombinację technik, takich jak:
Future.delayed
: Do opóźniania rozpoczęcia kolejnych animacji.AnimationStatusListener
: Do uruchamiania kolejnych animacji po zakończeniu poprzednich.TweenSequence
: Pozwala na zdefiniowanie sekwencji wartościTween
w jednej animacji. Uwaga: Dostępność i wsparcie dla tej klasy może być ograniczone w nowszych wersjach Fluttera. Zawsze sprawdź aktualną dokumentację.
Optymalizacja wydajności animacji
Animacje mogą być zasobożerne, zwłaszcza te bardziej złożone. Dlatego ważne jest, aby zoptymalizować wydajność animacji, aby uniknąć spadków płynności (tzw. “jank”).
Porady dotyczące optymalizacji wydajności animacji:
- Używaj
RepaintBoundary
: Izoluj obszary, które się zmieniają, aby Flutter nie musiał przerysowywać całego ekranu. - Unikaj przebudowywania widgetów w
build
: UżywajAnimatedBuilder
iValueListenableBuilder
do odbudowywania tylko tych widgetów, które rzeczywiście się zmieniły. - Używaj
Transform
zamiast zmiany właściwości: Zmiana właściwości widgetu często powoduje jego przebudowanie, podczas gdyTransform
pozwala na zmianę wyglądu widgetu bez jego przebudowywania. - Używaj
Opacity
zamiastVisibility
:Visibility
powoduje usunięcie widgetu z drzewa widgetów i ponowne jego dodanie, co jest kosztowne.Opacity
tylko zmienia przezroczystość, co jest znacznie szybsze. - Ogranicz liczbę animowanych elementów: Zbyt duża liczba animowanych elementów może obciążyć procesor.
- Monitoruj wydajność: Używaj narzędzi do profilowania wydajności (np. Flutter DevTools), aby zidentyfikować wąskie gardła.
6. Animacje Rive
Rive (dawniej Flare) to potężne narzędzie do tworzenia interaktywnych animacji wektorowych w czasie rzeczywistym. Pozwala na tworzenie skomplikowanych animacji, które można łatwo zintegrować z aplikacjami Flutter. Rive oferuje edytor online, w którym można tworzyć animacje, a następnie eksportować je do formatu .riv
, który można zaimportować do projektu Flutter.
Zalety Rive:
- Wektorowe animacje: Skalowalne bez utraty jakości.
- Interaktywność: Możliwość sterowania animacjami za pomocą kodu.
- Edytor wizualny: Łatwe tworzenie skomplikowanych animacji bez konieczności pisania kodu.
- Wydajność: Zoptymalizowane do renderowania w czasie rzeczywistym.
- Bogata biblioteka: Dostęp do wielu gotowych animacji i zasobów.
Integracja Rive z Flutterem:
Aby zintegrować animacje Rive z aplikacją Flutter, należy dodać pakiet rive
do pliku pubspec.yaml
:
dependencies:
flutter:
sdk: flutter
rive: ^0.12.0 # Sprawdź najnowszą wersję na pub.dev
Następnie można użyć widgetu RiveAnimation.asset
lub RiveAnimation.network
do wyświetlania animacji z pliku lokalnego lub z adresu URL.
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
class RiveAnimationScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Rive Animation Demo'),
),
body: Center(
child: SizedBox(
width: 200,
height: 200,
child: RiveAnimation.asset(
'assets/my_animation.riv', // Ścieżka do pliku .riv w assets
),
),
),
);
}
}
Sterowanie animacjami Rive:
Pakiet rive
udostępnia kontrolery animacji, które pozwalają na sterowanie stanami animacji, odtwarzaniem, pauzowaniem i przechodzeniem między różnymi animacjami z poziomu kodu Flutter. Można reagować na interakcje użytkownika, takie jak naciśnięcia przycisków, aby zmieniać stany animacji Rive.
Rive jest doskonałym wyborem do tworzenia złożonych, interaktywnych elementów wizualnych w aplikacjach Flutter, które wykraczają poza możliwości standardowych animacji opartych na widgetach.
7. Animacje Hero
Animacje Hero to rodzaj animacji przejścia między ekranami lub widgetami w aplikacji Flutter, w których wspólny element (“bohater”) płynnie przemieszcza się między widokami. Jest to popularna technika do tworzenia wizualnie atrakcyjnych i spójnych nawigacji.
Podstawowe użycie animacji Hero
Aby zaimplementować animację Hero, należy owinąć wspólny widget w obu ekranach (lub w różnych miejscach tego samego ekranu) widgetem Hero
i przypisać mu unikalny tag
. Flutter automatycznie zajmie się animacją przejścia tego widgetu.
Przykład
Ekran 1:
import 'package:flutter/material.dart';
class FirstScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('First Screen'),
),
body: Center(
child: InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondScreen()),
);
},
child: Hero(
tag: 'my-image',
child: Image.asset(
'assets/image.jpg',
width: 150,
height: 150,
),
),
),
),
);
}
}
Ekran 2:
import 'package:flutter/material.dart';
class SecondScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Second Screen'),
),
body: Center(
child: Hero(
tag: 'my-image',
child: Image.asset(
'assets/image.jpg',
width: 300,
height: 300,
),
),
),
);
}
}
W tym przykładzie, po naciśnięciu na obraz na pierwszym ekranie, obraz “przeleci” płynnie na drugi ekran, powiększając się. Widgety Hero
na obu ekranach mają ten sam tag
, co informuje Fluttera, który element ma zostać animowany.
Konfiguracja animacji Hero
Można dostosować wygląd animacji Hero za pomocą właściwości widgetu Hero
, takich jak createRectTween
do definiowania niestandardowej animacji prostokąta granicznego. Domyślnie Flutter używa MaterialRectArcTween
, który tworzy łukową animację.
Animacje Hero są kluczowym elementem w tworzeniu płynnych i intuicyjnych interfejsów użytkownika w aplikacjach Flutter, szczególnie podczas nawigacji między różnymi częściami aplikacji.
Podsumowanie
Podsumowując, ten artykuł to prawdziwa skarbnica wiedzy o animacjach we Flutterze, od prostych efektów po zaawansowane techniki. Mamy nadzieję, że zainspirował Was do eksperymentowania i dodania trochę życia do Waszych projektów!
Polecane artykuły
Flutter DevTools: Debugowanie UI
Debugowanie układu UI we Flutterze z Flutter DevTools. Praktyczne wskazówki i techniki dla developerów.
Mateusz Kędziora
Impeller Flutter: Nowa era mobilnej grafiki
Odkryj Impeller, nowy silnik renderujący Fluttera! Zwiększ wydajność, popraw grafikę i wykorzystaj nowoczesne API.
Mateusz Kędziora
Docker vs Kubernetes: Który dla Ciebie w 2025?
Docker i Kubernetes objaśnione! Która technologia lepsza dla początkujących w 2025? Porównanie, przykłady i przyszłość.
Mateusz Kędziora