Les animacions són una part essencial de les aplicacions modernes, ja que milloren l'experiència de l'usuari fent que les interfícies siguin més atractives i intuïtives. En aquest tema, aprendrem com crear animacions en Flutter utilitzant diverses tècniques i widgets.
Continguts
Introducció a les Animacions
Les animacions en Flutter es poden dividir en dues categories principals:
- Animacions Implicites: Són fàcils d'implementar i no requereixen gaire codi.
- Animacions Explícites: Ofereixen més control i flexibilitat, però requereixen més codi i configuració.
Animacions Implicites
Les animacions implicites són fàcils d'utilitzar i es basen en widgets que canvien automàticament les seves propietats amb el temps. Alguns dels widgets més comuns són:
AnimatedContainer
AnimatedOpacity
AnimatedAlign
AnimatedPositioned
Exemple: AnimatedContainer
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar(title: Text('Animacions Implicites')), body: Center(child: AnimatedContainerExample()), ), ); } } class AnimatedContainerExample extends StatefulWidget { @override _AnimatedContainerExampleState createState() => _AnimatedContainerExampleState(); } class _AnimatedContainerExampleState extends State<AnimatedContainerExample> { bool _isExpanded = false; @override Widget build(BuildContext context) { return GestureDetector( onTap: () { setState(() { _isExpanded = !_isExpanded; }); }, child: AnimatedContainer( duration: Duration(seconds: 1), width: _isExpanded ? 200 : 100, height: _isExpanded ? 200 : 100, color: _isExpanded ? Colors.blue : Colors.red, alignment: _isExpanded ? Alignment.center : AlignmentDirectional.topCenter, child: FlutterLogo(size: 75), ), ); } }
Explicació del Codi
AnimatedContainer
: Aquest widget canvia les seves propietats (com ara la mida, el color i l'alineació) de manera animada.GestureDetector
: Detecta els tocs de l'usuari per canviar l'estat de_isExpanded
.setState
: Actualitza l'estat de_isExpanded
i redibuixa el widget amb les noves propietats.
Animacions Explícites
Les animacions explícites ofereixen més control sobre el procés d'animació. Utilitzen classes com AnimationController
i Tween
.
Exemple: AnimationController
i Tween
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar(title: Text('Animacions Explícites')), body: Center(child: ExplicitAnimationExample()), ), ); } } class ExplicitAnimationExample extends StatefulWidget { @override _ExplicitAnimationExampleState createState() => _ExplicitAnimationExampleState(); } class _ExplicitAnimationExampleState extends State<ExplicitAnimationExample> with SingleTickerProviderStateMixin { AnimationController _controller; Animation<double> _animation; @override void initState() { super.initState(); _controller = AnimationController( duration: const Duration(seconds: 2), vsync: this, ); _animation = Tween<double>(begin: 0, end: 300).animate(_controller) ..addListener(() { setState(() {}); }); _controller.forward(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Container( width: _animation.value, height: _animation.value, color: Colors.blue, ); } }
Explicació del Codi
AnimationController
: Controla la durada i l'estat de l'animació.Tween
: Defineix els valors inicials i finals de l'animació.addListener
: Escolta els canvis en l'animació i actualitza l'estat del widget.
Animacions amb Hero
El widget Hero
permet crear animacions de transició entre pantalles.
Exemple: Hero
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: FirstScreen(), ); } } class FirstScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Primera Pantalla')), body: Center( child: GestureDetector( onTap: () { Navigator.push(context, MaterialPageRoute(builder: (_) => SecondScreen())); }, child: Hero( tag: 'hero-logo', child: FlutterLogo(size: 100), ), ), ), ); } } class SecondScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Segona Pantalla')), body: Center( child: Hero( tag: 'hero-logo', child: FlutterLogo(size: 200), ), ), ); } }
Explicació del Codi
Hero
: El widgetHero
crea una animació de transició entre dues pantalles.tag
: Identifica els widgets que participen en l'animació de transició.
Animacions Personalitzades
Les animacions personalitzades permeten crear efectes únics utilitzant CustomPainter
i altres tècniques avançades.
Exemple: CustomPainter
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar(title: Text('Animacions Personalitzades')), body: Center(child: CustomAnimationExample()), ), ); } } class CustomAnimationExample extends StatefulWidget { @override _CustomAnimationExampleState createState() => _CustomAnimationExampleState(); } class _CustomAnimationExampleState extends State<CustomAnimationExample> with SingleTickerProviderStateMixin { AnimationController _controller; @override void initState() { super.initState(); _controller = AnimationController( duration: const Duration(seconds: 2), vsync: this, )..repeat(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return CustomPaint( painter: CirclePainter(_controller), child: Container(), ); } } class CirclePainter extends CustomPainter { final Animation<double> animation; CirclePainter(this.animation) : super(repaint: animation); @override void paint(Canvas canvas, Size size) { final paint = Paint() ..color = Colors.blue ..style = PaintingStyle.fill; final radius = 50.0 + 50.0 * animation.value; canvas.drawCircle(Offset(size.width / 2, size.height / 2), radius, paint); } @override bool shouldRepaint(CustomPainter oldDelegate) => true; }
Explicació del Codi
CustomPainter
: Permet dibuixar formes personalitzades en unCanvas
.AnimationController
: Controla l'animació delCustomPainter
.
Exercicis Pràctics
- Animació de Color: Crea una animació que canviï el color d'un
Container
de vermell a verd. - Animació de Posició: Crea una animació que mogui un
Container
d'una posició a una altra. - Animació de Forma: Crea una animació que canviï la forma d'un
Container
de quadrat a cercle.
Solucions
- Animació de Color
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar(title: Text('Animació de Color')), body: Center(child: ColorAnimationExample()), ), ); } } class ColorAnimationExample extends StatefulWidget { @override _ColorAnimationExampleState createState() => _ColorAnimationExampleState(); } class _ColorAnimationExampleState extends State<ColorAnimationExample> with SingleTickerProviderStateMixin { AnimationController _controller; Animation<Color> _animation; @override void initState() { super.initState(); _controller = AnimationController( duration: const Duration(seconds: 2), vsync: this, ); _animation = ColorTween(begin: Colors.red, end: Colors.green).animate(_controller) ..addListener(() { setState(() {}); }); _controller.forward(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Container( width: 100, height: 100, color: _animation.value, ); } }
- Animació de Posició
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar(title: Text('Animació de Posició')), body: Center(child: PositionAnimationExample()), ), ); } } class PositionAnimationExample extends StatefulWidget { @override _PositionAnimationExampleState createState() => _PositionAnimationExampleState(); } class _PositionAnimationExampleState extends State<PositionAnimationExample> with SingleTickerProviderStateMixin { AnimationController _controller; Animation<Offset> _animation; @override void initState() { super.initState(); _controller = AnimationController( duration: const Duration(seconds: 2), vsync: this, ); _animation = Tween<Offset>(begin: Offset(0, 0), end: Offset(100, 100)).animate(_controller) ..addListener(() { setState(() {}); }); _controller.forward(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Transform.translate( offset: _animation.value, child: Container( width: 100, height: 100, color: Colors.blue, ), ); } }
- Animació de Forma
import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar(title: Text('Animació de Forma')), body: Center(child: ShapeAnimationExample()), ), ); } } class ShapeAnimationExample extends StatefulWidget { @override _ShapeAnimationExampleState createState() => _ShapeAnimationExampleState(); } class _ShapeAnimationExampleState extends State<ShapeAnimationExample> with SingleTickerProviderStateMixin { AnimationController _controller; Animation<double> _animation; @override void initState() { super.initState(); _controller = AnimationController( duration: const Duration(seconds: 2), vsync: this, ); _animation = Tween<double>(begin: 0, end: 1).animate(_controller) ..addListener(() { setState(() {}); }); _controller.forward(); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Container( width: 100, height: 100, decoration: BoxDecoration( color: Colors.blue, borderRadius: BorderRadius.circular(_animation.value * 50), ), ); } }
Conclusió
En aquest tema, hem après com crear animacions en Flutter utilitzant tant tècniques implicites com explícites. També hem vist com utilitzar el widget Hero
per a animacions de transició entre pantalles i com crear animacions personalitzades amb CustomPainter
. Les animacions són una eina poderosa per millorar l'experiència de l'usuari i fer que les aplicacions siguin més atractives i intuïtives.
En el següent tema, explorarem la pintura personalitzada i l'ús de Canvas
per crear gràfics i efectes visuals únics en les nostres aplicacions Flutter.
Curs de Desenvolupament Flutter
Mòdul 1: Introducció a Flutter
- Què és Flutter?
- Configuració de l'Entorn de Desenvolupament
- Comprensió de l'Arquitectura de Flutter
- Creació de la Teva Primera Aplicació Flutter
Mòdul 2: Conceptes Bàsics de Programació en Dart
- Introducció a Dart
- Variables i Tipus de Dades
- Sentències de Flux de Control
- Funcions i Mètodes
- Programació Orientada a Objectes en Dart
Mòdul 3: Widgets de Flutter
- Introducció als Widgets
- Widgets Stateless vs Stateful
- Widgets Bàsics
- Widgets de Disseny
- Widgets d'Entrada i Formulari
Mòdul 4: Gestió de l'Estat
Mòdul 5: Navegació i Enrutament
- Introducció a la Navegació
- Navegació Bàsica
- Rutes Nomenades
- Passar Dades Entre Pantalles
- Deep Linking
Mòdul 6: Xarxes i APIs
- Obtenir Dades d'Internet
- Analitzar Dades JSON
- Gestió d'Errors de Xarxa
- Ús d'APIs REST
- Integració de GraphQL
Mòdul 7: Persistència i Emmagatzematge
- Introducció a la Persistència
- Preferències Compartides
- Emmagatzematge de Fitxers
- Base de Dades SQLite
- Ús de Hive per a l'Emmagatzematge Local
Mòdul 8: Conceptes Avançats de Flutter
- Animacions en Flutter
- Pintura Personalitzada i Canvas
- Canals de Plataforma
- Isolates i Concurrència
- Optimització del Rendiment
Mòdul 9: Proves i Depuració
- Introducció a les Proves
- Proves Unitàries
- Proves de Widgets
- Proves d'Integració
- Tècniques de Depuració
Mòdul 10: Desplegament i Manteniment
- Preparació per al Llançament
- Construcció per a iOS
- Construcció per a Android
- Integració i Desplegament Continu (CI/CD)
- Manteniment i Actualització de la Teva Aplicació