Flutter-Animationsgrundlagen
Dieser Abschnitt führt in die Implementierung von Animationen in Flutter ein, einschließlich impliziter und expliziter Animationen.
Implizite Animation
Implizite Animationen sind Animations-Widgets, bei denen Flutter die Übergangseffekte automatisch übernimmt.
Beispiel: AnimatedContainer
class AnimatedContainerExample extends StatefulWidget {
const AnimatedContainerExample({super.key});
@override
State<AnimatedContainerExample> createState() =>
_AnimatedContainerExampleState();
}
class _AnimatedContainerExampleState
extends State<AnimatedContainerExample> {
bool _isLarge = false;
@override
Widget build(BuildContext context) {
return Column(
children: [
// Klicken, um die Größe umzuschalten
GestureDetector(
onTap: () => setState(() => _isLarge = !_isLarge),
child: AnimatedContainer(
duration: const Duration(milliseconds: 500), // Animationsdauer
curve: Curves.easeInOut, // Animationskurve
width: _isLarge ? 200 : 100,
height: _isLarge ? 200 : 100,
color: _isLarge ? Colors.blue : Colors.red,
child: Center(
child: Text(
_isLarge ? 'large' : 'small',
style: const TextStyle(color: Colors.white, fontSize: 24),
),
),
),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () => setState(() => _isLarge = !_isLarge),
child: const Text('Switch'),
),
],
);
}
}
const AnimatedContainerExample({super.key});
@override
State<AnimatedContainerExample> createState() =>
_AnimatedContainerExampleState();
}
class _AnimatedContainerExampleState
extends State<AnimatedContainerExample> {
bool _isLarge = false;
@override
Widget build(BuildContext context) {
return Column(
children: [
// Klicken, um die Größe umzuschalten
GestureDetector(
onTap: () => setState(() => _isLarge = !_isLarge),
child: AnimatedContainer(
duration: const Duration(milliseconds: 500), // Animationsdauer
curve: Curves.easeInOut, // Animationskurve
width: _isLarge ? 200 : 100,
height: _isLarge ? 200 : 100,
color: _isLarge ? Colors.blue : Colors.red,
child: Center(
child: Text(
_isLarge ? 'large' : 'small',
style: const TextStyle(color: Colors.white, fontSize: 24),
),
),
),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () => setState(() => _isLarge = !_isLarge),
child: const Text('Switch'),
),
],
);
}
}
Häufige implizite Animations-Widgets
| Widget | Beschreibung |
|---|---|
| AnimatedContainer | Animationen, wenn sich Eigenschaften wie Containergröße, Farbe usw. ändern |
| AnimatedOpacity | Deckkraft-Übergangsanimation |
| AnimatedPadding | Padding-Übergangsanimation |
| AnimatedAlign | Animation bei Ausrichtungsänderung |
| AnimatedSwitcher | Animation beim Wechseln untergeordneter Widgets |
| AnimatedDefaultTextStyle | Animation bei Textstiländerung |
Explizite Animation
Explizite Animationen verwenden AnimationController, um die Animation vollständig zu steuern.
Beispiel: Rotationsanimation
class RotationAnimation extends StatefulWidget {
const RotationAnimation({super.key});
@override
State<RotationAnimation> createState() => _RotationAnimationState();
}
class _RotationAnimationState extends State<RotationAnimation>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
@override
void initState() {
super.initState();
// Animationscontroller erstellen
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
);
// Rotationsanimation erstellen (0 bis 1 entspricht 0 bis 2π)
_animation = Tween<double>(begin: 0, end: 6.28).animate(
CurvedAnimation(parent: _controller, curve: Curves.linear),
);
// Animation starten
_controller.repeat();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Center(
child: AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return Transform.rotate(
angle: _animation.value, // Rotationswinkel
child: const Icon(Icons.refresh, size: 100),
);
},
),
);
}
}
const RotationAnimation({super.key});
@override
State<RotationAnimation> createState() => _RotationAnimationState();
}
class _RotationAnimationState extends State<RotationAnimation>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
@override
void initState() {
super.initState();
// Animationscontroller erstellen
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
);
// Rotationsanimation erstellen (0 bis 1 entspricht 0 bis 2π)
_animation = Tween<double>(begin: 0, end: 6.28).animate(
CurvedAnimation(parent: _controller, curve: Curves.linear),
);
// Animation starten
_controller.repeat();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Center(
child: AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return Transform.rotate(
angle: _animation.value, // Rotationswinkel
child: const Icon(Icons.refresh, size: 100),
);
},
),
);
}
}
Gestaffelte Animation
Beispiel: Gestaffelte Animation
class StaggeredAnimation extends StatefulWidget {
const StaggeredAnimation({super.key});
@override
State<StaggeredAnimation> createState() => _StaggeredAnimationState();
}
class _StaggeredAnimationState extends State<StaggeredAnimation>
with TickerProviderStateMixin {
late AnimationController _controller;
// Einzelne Animationen
late Animation<double> _fadeAnimation;
late Animation<double> _scaleAnimation;
late Animation<Offset> _slideAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 1500),
vsync: this,
);
// Einblendanimation (0-0.5)
_fadeAnimation = Tween<double>(begin: 0, end: 1).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(0, 0.5, curve: Curves.easeOut),
),
);
// Skalierungsanimation (0.3-0.7)
_scaleAnimation = Tween<double>(begin: 0.5, end: 1).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(0.3, 0.7, curve: Curves.elasticOut),
),
);
// Einschubanimation (0.5-1.0)
_slideAnimation = Tween<Offset>(
begin: const Offset(0, 0.5),
end: Offset.zero,
).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(0.5, 1, curve: Curves.easeOutCubic),
),
);
_controller.forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return FadeTransition(
opacity: _fadeAnimation,
child: ScaleTransition(
scale: _scaleAnimation,
child: SlideTransition(
position: _slideAnimation,
child: const Card(
child: Padding(
padding: EdgeInsets.all(24),
child: Text('Welcome'),
),
),
),
),
);
},
);
}
}
const StaggeredAnimation({super.key});
@override
State<StaggeredAnimation> createState() => _StaggeredAnimationState();
}
class _StaggeredAnimationState extends State<StaggeredAnimation>
with TickerProviderStateMixin {
late AnimationController _controller;
// Einzelne Animationen
late Animation<double> _fadeAnimation;
late Animation<double> _scaleAnimation;
late Animation<Offset> _slideAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 1500),
vsync: this,
);
// Einblendanimation (0-0.5)
_fadeAnimation = Tween<double>(begin: 0, end: 1).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(0, 0.5, curve: Curves.easeOut),
),
);
// Skalierungsanimation (0.3-0.7)
_scaleAnimation = Tween<double>(begin: 0.5, end: 1).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(0.3, 0.7, curve: Curves.elasticOut),
),
);
// Einschubanimation (0.5-1.0)
_slideAnimation = Tween<Offset>(
begin: const Offset(0, 0.5),
end: Offset.zero,
).animate(
CurvedAnimation(
parent: _controller,
curve: const Interval(0.5, 1, curve: Curves.easeOutCubic),
),
);
_controller.forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return FadeTransition(
opacity: _fadeAnimation,
child: ScaleTransition(
scale: _scaleAnimation,
child: SlideTransition(
position: _slideAnimation,
child: const Card(
child: Padding(
padding: EdgeInsets.all(24),
child: Text('Welcome'),
),
),
),
),
);
},
);
}
}
Hero-Animation
Hero-Animation wird verwendet, um Übergangseffekte für gemeinsam genutzte Elemente beim Wechsel zwischen Seiten zu erzeugen.
Beispiel: Hero-Animation
// Startseite - Bild mit Hero
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Home')),
body: Center(
child: GestureDetector(
onTap: () => Navigator.push(
context,
MaterialPageRoute(builder: (_) => const DetailPage()),
),
child: Hero(
tag: 'myHero', // Gleicher Tag
child: Image.network(
'https://picsum.photos/200',
width: 200,
height: 200,
),
),
),
),
);
}
}
// Detailseite - Bild mit Hero
class DetailPage extends StatelessWidget {
const DetailPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Details')),
body: Center(
child: Hero(
tag: 'myHero', // Gleicher Tag
child: Image.network(
'https://picsum.photos/200',
width: 300,
height: 300,
),
),
),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Home')),
body: Center(
child: GestureDetector(
onTap: () => Navigator.push(
context,
MaterialPageRoute(builder: (_) => const DetailPage()),
),
child: Hero(
tag: 'myHero', // Gleicher Tag
child: Image.network(
'https://picsum.photos/200',
width: 200,
height: 200,
),
),
),
),
);
}
}
// Detailseite - Bild mit Hero
class DetailPage extends StatelessWidget {
const DetailPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Details')),
body: Center(
child: Hero(
tag: 'myHero', // Gleicher Tag
child: Image.network(
'https://picsum.photos/200',
width: 300,
height: 300,
),
),
),
);
}
}
Weitere ErweiterungenImplizite Animationen eignen sich für einfache Szenarien, während explizite Animationen für komplexe Steuerungen geeignet sind.
Die Hero-Animation ist die beste Wahl für Seitenübergänge.