1. Einführung
Animationen sind eine gute Möglichkeit, die Nutzerfreundlichkeit Ihrer App zu verbessern, wichtige Informationen zu vermitteln und Ihre App ansprechender und nutzerfreundlicher zu gestalten.
Übersicht über das Animationsframework von Flutter
Flutter zeigt Animationseffekte an, indem in jedem Frame ein Teil des Widget-Baums neu erstellt wird. Es bietet vorgefertigte Animationseffekte und andere APIs, die das Erstellen und Komponieren von Animationen erleichtern.
- Implizite Animationen sind vordefinierte Animationseffekte, bei denen die gesamte Animation automatisch ausgeführt wird. Wenn sich der Zielwert der Animation ändert, wird die Animation vom aktuellen Wert zum Zielwert ausgeführt und alle dazwischen liegenden Werte werden angezeigt, damit das Widget flüssig animiert wird. Beispiele für implizite Animationen sind
AnimatedSize
,AnimatedScale
undAnimatedPositioned
. - Explizite Animationen sind ebenfalls vordefinierte Animationen, die jedoch ein
Animation
-Objekt erfordern. Beispiele:SizeTransition
,ScaleTransition
oderPositionedTransition
. - Animation ist eine Klasse, die eine laufende oder angehaltene Animation darstellt. Sie besteht aus einem Wert, der den Zielwert der Animation darstellt, und dem Status, der den aktuellen Wert darstellt, der von der Animation zu einem bestimmten Zeitpunkt auf dem Bildschirm angezeigt wird. Es ist eine Unterklasse von
Listenable
und benachrichtigt seine Listener, wenn sich der Status während der Ausführung der Animation ändert. - Mit AnimationController können Sie eine Animation erstellen und ihren Status steuern. Mithilfe von Methoden wie
forward()
,reset()
,stop()
undrepeat()
können Sie die Animation steuern, ohne den angezeigten Animationseffekt wie Skalierung, Größe oder Position definieren zu müssen. - Mit Tweens werden Werte zwischen einem Anfangs- und einem Endwert interpoliert. Sie können jeden Typ haben, z. B. „double“,
Offset
oderColor
. - Mit Kurven lässt sich die Änderungsrate eines Parameters im Zeitverlauf anpassen. Wenn eine Animation ausgeführt wird, wird häufig eine Easing-Kurve angewendet, um die Änderungsrate zu Beginn oder am Ende der Animation schneller oder langsamer zu gestalten. Kurven nehmen einen Eingabewert zwischen 0,0 und 1,0 an und geben einen Ausgabewert zwischen 0,0 und 1,0 zurück.
Umfang
In diesem Codelab erstellen Sie ein Quizspiel mit Multiple-Choice-Fragen, das verschiedene Animationseffekte und ‑techniken enthält.
Sie erfahren, wie Sie…
- Widget erstellen, das Größe und Farbe animiert
- 3D-Kartenumdrehen-Effekt erstellen
- Ausgefallene vorgefertigte Animationseffekte aus dem Animationspaket verwenden
- Unterstützung für die vorhersagende Zurück-Geste hinzufügen, die in der neuesten Android-Version verfügbar ist
Aufgaben in diesem Lab
In diesem Codelab lernen Sie Folgendes:
- Wie Sie mit implizit animierten Effekten ansprechende Animationen erstellen, ohne viel Code schreiben zu müssen.
- Wie Sie mit ausdrücklich animierten Effekten eigene Effekte mit vorgefertigten animierten Widgets wie
AnimatedSwitcher
oderAnimationController
konfigurieren. - So definieren Sie mit
AnimationController
ein eigenes Widget mit einem 3D-Effekt. - Wie Sie mit dem
animations
-Paket mit minimaler Einrichtung ausgefallene Animationseffekte anzeigen.
Voraussetzungen
- Das Flutter SDK
- Eine IDE wie VSCode oder Android Studio / IntelliJ
2. Flutter-Entwicklungsumgebung einrichten
Für dieses Lab benötigen Sie zwei Softwareprogramme: das Flutter SDK und einen Editor.
Sie können das Codelab auf einem der folgenden Geräte ausführen:
- Ein physisches Android (für die Implementierung der Vorhersagefunktion in Schritt 7 empfohlen) oder iOSGerät, das mit Ihrem Computer verbunden und auf den Entwicklermodus gesetzt ist.
- Der iOS-Simulator (erfordert die Installation von Xcode-Tools).
- Android-Emulator (erfordert Einrichtung in Android Studio)
- Einen Browser (Chrome ist für die Fehlerbehebung erforderlich)
- Sie haben einen Windows-, Linux- oder macOS-Computer. Sie müssen die Entwicklung auf der Plattform durchführen, auf der Sie die Bereitstellung planen. Wenn Sie also eine Windows-Desktopanwendung entwickeln möchten, müssen Sie die Entwicklung unter Windows durchführen, um auf die entsprechende Build-Kette zugreifen zu können. Es gibt betriebssystemspezifische Anforderungen, die unter docs.flutter.dev/desktop ausführlich beschrieben werden.
Installation prüfen
Mit dem Flutter Doctor-Tool können Sie prüfen, ob Ihr Flutter SDK richtig konfiguriert ist und mindestens eine der oben genannten Zielplattformen installiert ist:
$ flutter doctor Doctor summary (to see all details, run flutter doctor -v): [✓] Flutter (Channel stable, 3.24.2, on macOS 14.6.1 23G93 darwin-arm64, locale en) [✓] Android toolchain - develop for Android devices [✓] Xcode - develop for iOS and macOS [✓] Chrome - develop for the web [✓] Android Studio [✓] IntelliJ IDEA Ultimate Edition [✓] VS Code [✓] Connected device (4 available) [✓] Network resources • No issues found!
3. Start-App ausführen
Auslöser-App herunterladen
Klonen Sie die Start-App mit git
aus dem flutter/samples-Repository auf GitHub.
$ git clone https://github.com/flutter/codelabs.git $ cd codelabs/animations/step_01/
Alternativ können Sie den Quellcode als ZIP-Datei herunterladen.
App ausführen
Verwenden Sie den Befehl flutter run
, um die App auszuführen, und geben Sie ein Zielgerät wie android
, ios
oder chrome
an. Eine vollständige Liste der unterstützten Plattformen finden Sie auf der Seite Unterstützte Plattformen.
$ flutter run -d android
Sie können die App auch mit der IDE Ihrer Wahl ausführen und beheben. Weitere Informationen finden Sie in der offiziellen Flutter-Dokumentation.
Code ansehen
Die Starter-App ist ein Multiple-Choice-Quizspiel mit zwei Bildschirmen, das dem Designmuster „Model View View Model“ (MVVM) folgt. Die QuestionScreen
(Ansicht) verwendet die Klasse QuizViewModel
(Ansichtsmodell), um dem Nutzer Multiple-Choice-Fragen aus der Klasse QuestionBank
(Modell) zu stellen.
- home_screen.dart: Zeigt einen Bildschirm mit der Schaltfläche Neues Spiel an.
- main.dart: Konfiguriert die
MaterialApp
für die Verwendung von Material 3 und zeigt den Startbildschirm an. - model.dart: Hier werden die Hauptklassen definiert, die in der gesamten App verwendet werden.
- question_screen.dart: Zeigt die Benutzeroberfläche für das Quiz an
- view_model.dart: Speichert den Status und die Logik für das Quizspiel, die von der
QuestionScreen
angezeigt wird.
Die App unterstützt noch keine animierten Effekte, mit Ausnahme des Standard-Ansichtsübergangs, der von der Navigator
-Klasse von Flutter angezeigt wird, wenn der Nutzer auf die Schaltfläche Neues Spiel klickt.
4. Implizite Animationseffekte verwenden
Implizite Animationen sind in vielen Situationen eine gute Wahl, da sie keine spezielle Konfiguration erfordern. In diesem Abschnitt aktualisieren Sie das StatusBar
-Widget, damit eine animierte Anzeigetafel angezeigt wird. Informationen zu gängigen impliziten Animationseffekten finden Sie in der API-Dokumentation für ImplicitlyAnimatedWidget.
Widget für das unbewegliche Punkteboard erstellen
Erstellen Sie eine neue Datei vom Typ lib/scoreboard.dart
mit dem folgenden Code:
lib/scoreboard.dart
import 'package:flutter/material.dart';
class Scoreboard extends StatelessWidget {
final int score;
final int totalQuestions;
const Scoreboard({
super.key,
required this.score,
required this.totalQuestions,
});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
for (var i = 0; i < totalQuestions; i++)
Icon(
Icons.star,
size: 50,
color:
score < i + 1 ? Colors.grey.shade400 : Colors.yellow.shade700,
)
],
),
);
}
}
Fügen Sie dann das Scoreboard
-Widget als untergeordnetes Element des StatusBar
-Widgets hinzu und ersetzen Sie damit die Text
-Widgets, in denen zuvor die Punktzahl und die Gesamtzahl der Fragen angezeigt wurden. Der erforderliche import "scoreboard.dart"
-Befehl sollte oben in der Datei automatisch hinzugefügt werden.
lib/question_screen.dart
class StatusBar extends StatelessWidget {
final QuizViewModel viewModel;
const StatusBar({required this.viewModel, super.key});
@override
Widget build(BuildContext context) {
return Card(
elevation: 4,
child: Padding(
padding: EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Scoreboard( // NEW
score: viewModel.score, // NEW
totalQuestions: viewModel.totalQuestions, // NEW
),
],
),
),
);
}
}
Dieses Widget zeigt für jede Frage ein Sternsymbol an. Wenn eine Frage richtig beantwortet wird, leuchtet sofort ohne Animation ein weiterer Stern auf. In den folgenden Schritten informieren Sie den Nutzer über die Änderung seiner Punktzahl, indem Sie Größe und Farbe animieren.
Einen impliziten Animationseffekt verwenden
Erstellen Sie ein neues Widget namens AnimatedStar
, das ein AnimatedScale
-Widget verwendet, um den scale
-Wert von 0.5
in 1.0
zu ändern, wenn der Stern aktiv wird:
lib/scoreboard.dart
import 'package:flutter/material.dart';
class Scoreboard extends StatelessWidget {
final int score;
final int totalQuestions;
const Scoreboard({
super.key,
required this.score,
required this.totalQuestions,
});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
for (var i = 0; i < totalQuestions; i++)
AnimatedStar( // NEW
isActive: score > i, // NEW
) // NEW
],
),
);
}
}
class AnimatedStar extends StatelessWidget { // Add from here...
final bool isActive;
final Duration _duration = const Duration(milliseconds: 1000);
final Color _deactivatedColor = Colors.grey.shade400;
final Color _activatedColor = Colors.yellow.shade700;
AnimatedStar({super.key, required this.isActive});
@override
Widget build(BuildContext context) {
return AnimatedScale(
scale: isActive ? 1.0 : 0.5,
duration: _duration,
child: Icon(
Icons.star,
size: 50,
color: isActive ? _activatedColor : _deactivatedColor,
),
);
}
} // To here.
Wenn der Nutzer eine Frage richtig beantwortet, ändert das AnimatedStar
-Widget seine Größe jetzt mithilfe einer impliziten Animation. Der color
von Icon
ist hier nicht animiert, nur der scale
, was vom AnimatedScale
-Widget übernommen wird.
Mit einem Tween zwischen zwei Werten interpolieren
Die Farbe des AnimatedStar
-Widgets ändert sich sofort, nachdem das Feld isActive
auf „wahr“ gesetzt wurde.
Wenn Sie einen animierten Farbeffekt erzielen möchten, können Sie ein AnimatedContainer
-Widget verwenden (eine weitere Unterklasse von ImplicitlyAnimatedWidget
). Damit lassen sich alle Attribute, einschließlich der Farbe, automatisch animieren. Leider muss unser Widget ein Symbol und keinen Container anzeigen.
Sie können auch AnimatedIcon
ausprobieren, mit dem Übergangseffekte zwischen den Formen der Symbole implementiert werden. Es gibt jedoch keine Standardimplementierung eines Sternsymbols in der Klasse AnimatedIcons
.
Stattdessen verwenden wir eine andere Unterklasse von ImplicitlyAnimatedWidget
namens TweenAnimationBuilder
, die einen Tween
als Parameter annimmt. Ein Tween ist eine Klasse, die zwei Werte (begin
und end
) annimmt und die Zwischenwerte berechnet, damit sie in einer Animation dargestellt werden können. In diesem Beispiel verwenden wir ein ColorTween
, das die Tween<Color>
-Schnittstelle erfüllt, die für die Erstellung des Animationseffekts erforderlich ist.
Wählen Sie das Icon
-Widget aus und verwenden Sie die Schnellaktion „Mit Builder umschließen“ in Ihrer IDE. Ändern Sie den Namen in TweenAnimationBuilder
. Geben Sie dann die Dauer und ColorTween
an.
lib/scoreboard.dart
class AnimatedStar extends StatelessWidget {
final bool isActive;
final Duration _duration = const Duration(milliseconds: 1000);
final Color _deactivatedColor = Colors.grey.shade400;
final Color _activatedColor = Colors.yellow.shade700;
AnimatedStar({super.key, required this.isActive});
@override
Widget build(BuildContext context) {
return AnimatedScale(
scale: isActive ? 1.0 : 0.5,
duration: _duration,
child: TweenAnimationBuilder( // Add from here...
duration: _duration,
tween: ColorTween(
begin: _deactivatedColor,
end: isActive ? _activatedColor : _deactivatedColor,
),
builder: (context, value, child) { // To here.
return Icon(
Icons.star,
size: 50,
color: value, // Modify from here...
);
}, // To here.
),
);
}
}
Führen Sie jetzt einen Hot-Reload der App durch, um die neue Animation zu sehen.
Beachten Sie, dass sich der end
-Wert von ColorTween
je nach Wert des Parameters isActive
ändert. Das liegt daran, dass TweenAnimationBuilder
die Animation jedes Mal neu ausführt, wenn sich der Wert von Tween.end
ändert. In diesem Fall wird die neue Animation vom aktuellen Animationswert bis zum neuen Endwert ausgeführt. So können Sie die Farbe jederzeit ändern (auch während der Animation) und einen flüssigen Animationseffekt mit den richtigen Zwischenwerten anzeigen.
Kurve anwenden
Beide Animationseffekte laufen mit einer konstanten Geschwindigkeit ab. Animationen sind jedoch oft visuell interessanter und informativer, wenn sie beschleunigt oder verlangsamt werden.
Bei einem Curve
wird eine Ausblendungsfunktion angewendet, die die Änderungsrate eines Parameters im Zeitverlauf definiert. Flutter enthält eine Sammlung vordefinierter Glättungskurven in der Klasse Curves
, z. B. easeIn
oder easeOut
.
Diese Diagramme (auf der Seite Curves
API-Dokumentation verfügbar) geben einen Hinweis darauf, wie Kurven funktionieren. Mithilfe von Kurven wird ein Eingabewert zwischen 0,0 und 1,0 (auf der X-Achse) in einen Ausgabewert zwischen 0,0 und 1,0 (auf der Y-Achse) umgewandelt. Diese Diagramme zeigen auch eine Vorschau, wie verschiedene Animationseffekte aussehen, wenn eine Ease-Kurve verwendet wird.
Erstellen Sie in „AnimatedStar“ ein neues Feld namens _curve
und übergeben Sie es als Parameter an die Widgets AnimatedScale
und TweenAnimationBuilder
.
lib/scoreboard.dart
class AnimatedStar extends StatelessWidget {
final bool isActive;
final Duration _duration = const Duration(milliseconds: 1000);
final Color _deactivatedColor = Colors.grey.shade400;
final Color _activatedColor = Colors.yellow.shade700;
final Curve _curve = Curves.elasticOut; // NEW
AnimatedStar({super.key, required this.isActive});
@override
Widget build(BuildContext context) {
return AnimatedScale(
scale: isActive ? 1.0 : 0.5,
curve: _curve, // NEW
duration: _duration,
child: TweenAnimationBuilder(
curve: _curve, // NEW
duration: _duration,
tween: ColorTween(
begin: _deactivatedColor,
end: isActive ? _activatedColor : _deactivatedColor,
),
builder: (context, value, child) {
return Icon(
Icons.star,
size: 50,
color: value,
);
},
),
);
}
}
In diesem Beispiel sorgt die elasticOut
-Kurve für einen übertriebenen Federeffekt, der mit einer Federbewegung beginnt und sich gegen Ende ausgleicht.
Führen Sie einen Hot-Reload der App durch, um zu sehen, wie diese Kurve auf AnimatedSize
und TweenAnimationBuilder
angewendet wird.
Langsame Animationen mit den DevTools aktivieren
Zum Beheben von Animationseffekten können Sie mit den Flutter DevTools alle Animationen in Ihrer App verlangsamen, damit Sie sie besser sehen können.
Damit die DevTools geöffnet werden, muss die App im Debug-Modus ausgeführt werden. Öffnen Sie dann den Widget-Inspektor, indem Sie ihn in der Debug-Symbolleiste in VSCode auswählen oder die Schaltfläche Flutter DevTools öffnen im Debug-Toolfenster in IntelliJ / Android Studio anklicken.
Klicken Sie in der Symbolleiste des Widget-Inspektors auf die Schaltfläche Animationen verlangsamen.
5. Explizite Animationseffekte verwenden
Wie implizite Animationen sind auch explizite Animationen vordefinierte Animationseffekte. Statt eines Zielwerts wird jedoch ein Animation
-Objekt als Parameter verwendet. Das ist in Situationen hilfreich, in denen die Animation bereits durch einen Navigationsübergang wie AnimatedSwitcher
oder AnimationController
definiert ist.
Einen expliziten Animationseffekt verwenden
Wenn Sie einen expliziten Animationseffekt verwenden möchten, schließen Sie das Card
-Widget in ein AnimatedSwitcher
ein.
lib/question_screen.dart
class QuestionCard extends StatelessWidget {
final String? question;
const QuestionCard({
required this.question,
super.key,
});
@override
Widget build(BuildContext context) {
return AnimatedSwitcher( // NEW
duration: const Duration(milliseconds: 300), // NEW
child: Card(
key: ValueKey(question),
elevation: 4,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
question ?? '',
style: Theme.of(context).textTheme.displaySmall,
),
),
), // NEW
);
}
}
Für AnimatedSwitcher
wird standardmäßig ein Überblendungseffekt verwendet. Du kannst das aber mit dem Parameter transitionBuilder
überschreiben. Der Übergangs-Builder stellt das untergeordnete Widget bereit, das an AnimatedSwitcher
übergeben wurde, und ein Animation
-Objekt. Hier bietet sich eine gute Gelegenheit, eine explizite Animation zu verwenden.
In diesem Codelab verwenden wir als erste explizite Animation SlideTransition
. Sie nimmt ein Animation<Offset>
an, das den Anfangs- und Endoffset definiert, zwischen dem sich die ein- und ausgehenden Widgets bewegen.
Tweens haben eine Hilfsfunktion, animate()
, die jeden Animation
in einen anderen Animation
mit dem angewendeten Tween konvertiert. Das bedeutet, dass mit einer Tween<Offset>
die vom AnimatedSwitcher
bereitgestellten Animation<double>
in eine Animation<Offset>
umgewandelt werden können, die an das SlideTransition
-Widget übergeben wird.
lib/question_screen.dart
class QuestionCard extends StatelessWidget {
final String? question;
const QuestionCard({
required this.question,
super.key,
});
@override
Widget build(BuildContext context) {
return AnimatedSwitcher(
transitionBuilder: (child, animation) { // Add from here...
final curveAnimation =
CurveTween(curve: Curves.easeInCubic).animate(animation);
final offsetAnimation =
Tween<Offset>(begin: Offset(-0.1, 0.0), end: Offset.zero)
.animate(curveAnimation);
return SlideTransition(position: offsetAnimation, child: child);
}, // To here.
duration: const Duration(milliseconds: 300),
child: Card(
key: ValueKey(question),
elevation: 4,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
question ?? '',
style: Theme.of(context).textTheme.displaySmall,
),
),
),
);
}
}
Beachten Sie, dass hier mit Tween.animate
eine Curve
auf die Animation
angewendet und dann von einer Tween<double>
, die von 0,0 bis 1,0 reicht, in eine Tween<Offset>
konvertiert wird, die auf der X-Achse von -0,1 zu 0,0 übergeht.
Alternativ gibt es in der Animation-Klasse die Funktion drive()
, die jeden Tween
(oder Animatable
) in einen neuen Animation
umwandelt. So können Tweens „verkettet“ werden, was den resultierenden Code prägnanter macht:
lib/question_screen.dart
transitionBuilder: (child, animation) {
var offsetAnimation = animation
.drive(CurveTween(curve: Curves.easeInCubic))
.drive(Tween<Offset>(begin: Offset(-0.1, 0.0), end: Offset.zero));
return SlideTransition(position: offsetAnimation, child: child);
},
Ein weiterer Vorteil von expliziten Animationen ist, dass sie sich leicht zusammenstellen lassen. Fügen Sie eine weitere explizite Animation hinzu, die die gleiche gekrümmte Animation verwendet. Hierzu fügen Sie das Widget „Wechsel zur nächsten Folie“ ein.
lib/question_screen.dart
return AnimatedSwitcher(
transitionBuilder: (child, animation) {
final curveAnimation =
CurveTween(curve: Curves.easeInCubic).animate(animation);
final offsetAnimation =
Tween<Offset>(begin: Offset(-0.1, 0.0), end: Offset.zero)
.animate(curveAnimation);
final fadeInAnimation = curveAnimation; // NEW
return FadeTransition( // NEW
opacity: fadeInAnimation, // NEW
child: SlideTransition(position: offsetAnimation, child: child), // NEW
); // NEW
},
layoutBuilder anpassen
Möglicherweise fällt Ihnen ein kleines Problem mit dem AnimationSwitcher auf. Wenn eine Fragekarte zu einer neuen Frage wechselt, wird sie während der laufenden Animation in der Mitte des verfügbaren Bereichs angezeigt. Wenn die Animation jedoch angehalten wird, springt das Widget an den oberen Bildschirmrand. Das führt zu einer ruckeligen Animation, da die endgültige Position der Fragekarte nicht mit der Position übereinstimmt, die sie während der Animation hat.
Um das zu beheben, hat der AnimatedSwitcher auch einen Parameter „layoutBuilder“, mit dem das Layout definiert werden kann. Mit dieser Funktion können Sie den Layout-Builder so konfigurieren, dass die Karte oben auf dem Bildschirm ausgerichtet wird:
lib/question_screen.dart
@override
Widget build(BuildContext context) {
return AnimatedSwitcher(
layoutBuilder: (currentChild, previousChildren) {
return Stack(
alignment: Alignment.topCenter,
children: <Widget>[
...previousChildren,
if (currentChild != null) currentChild,
],
);
},
Dieser Code ist eine modifizierte Version des defaultLayoutBuilder aus der Klasse „AnimatedSwitcher“, verwendet aber Alignment.topCenter
anstelle von Alignment.center
.
Zusammenfassung
- Explizite Animationen sind Animationseffekte, die ein Animationsobjekt verwenden (im Gegensatz zu ImplicitlyAnimatedWidgets, die einen Zielwert und eine Dauer verwenden).
- Die Klasse „Animation“ stellt eine laufende Animation dar, definiert aber keinen bestimmten Effekt.
- Mit Tween().animate oder Animation.drive() kannst du einer Animation Tweens und Kurven (mit CurveTween) hinzufügen.
- Mit dem Parameter „layoutBuilder“ von AnimatedSwitcher können Sie die Anordnung der untergeordneten Elemente anpassen.
6. Status einer Animation steuern
Bisher wurden alle Animationen automatisch vom Framework ausgeführt. Implizite Animationen werden automatisch ausgeführt. Für explizite Animationen ist eine Animation erforderlich, damit sie richtig funktionieren. In diesem Abschnitt erfahren Sie, wie Sie mit einem AnimationController eigene Animationen erstellen und mit einer TweenSequence Tweens kombinieren.
Animation mit einem AnimationController ausführen
So erstellen Sie eine Animation mit einem AnimationController:
- StatefulWidget erstellen
- Verwenden Sie den Mixin „SingleTickerProviderStateMixin“ in Ihrer State-Klasse, um Ihrem AnimationController einen Ticker zur Verfügung zu stellen.
- Initialisieren Sie den AnimationController in der Lebenszyklusmethode „initState“ und geben Sie dem Parameter
vsync
(TickerProvider) das aktuelle State-Objekt an. - Achten Sie darauf, dass Ihr Widget neu erstellt wird, sobald der AnimationController seine Listener benachrichtigt. Verwenden Sie dazu entweder AnimatedBuilder oder rufen Sie listen() und setState manuell auf.
Erstellen Sie eine neue Datei mit dem Namen „flip_effect.dart“ und fügen Sie den folgenden Code ein:
lib/flip_effect.dart
import 'dart:math' as math;
import 'package:flutter/widgets.dart';
class CardFlipEffect extends StatefulWidget {
final Widget child;
final Duration duration;
const CardFlipEffect({
super.key,
required this.child,
required this.duration,
});
@override
State<CardFlipEffect> createState() => _CardFlipEffectState();
}
class _CardFlipEffectState extends State<CardFlipEffect>
with SingleTickerProviderStateMixin {
late final AnimationController _animationController;
Widget? _previousChild;
@override
void initState() {
super.initState();
_animationController =
AnimationController(vsync: this, duration: widget.duration);
_animationController.addListener(() {
if (_animationController.value == 1) {
_animationController.reset();
}
});
}
@override
void didUpdateWidget(covariant CardFlipEffect oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.child.key != oldWidget.child.key) {
_handleChildChanged(widget.child, oldWidget.child);
}
}
void _handleChildChanged(Widget newChild, Widget previousChild) {
_previousChild = previousChild;
_animationController.forward();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
return Transform(
alignment: Alignment.center,
transform: Matrix4.identity()
..rotateX(_animationController.value * math.pi),
child: _animationController.isAnimating
? _animationController.value < 0.5
? _previousChild
: Transform.flip(flipY: true, child: child)
: child,
);
},
child: widget.child,
);
}
}
Diese Klasse richtet einen AnimationController ein und führt die Animation jedes Mal neu aus, wenn das Framework „didUpdateWidget“ aufruft, um es darüber zu informieren, dass sich die Widget-Konfiguration geändert hat und es möglicherweise ein neues untergeordnetes Widget gibt.
Der AnimatedBuilder sorgt dafür, dass der Widget-Baum neu erstellt wird, sobald der AnimationController seine Listener benachrichtigt. Mit dem Transform-Widget wird ein 3D-Dreheffekt angewendet, um das Umblättern einer Karte zu simulieren.
Wenn Sie dieses Widget verwenden möchten, müssen Sie jede Antwortkarte in ein CardFlipEffect-Widget einbetten. Geben Sie für das Karten-Widget ein key
an:
lib/question_screen.dart
@override
Widget build(BuildContext context) {
return GridView.count(
shrinkWrap: true,
crossAxisCount: 2,
childAspectRatio: 5 / 2,
children: List.generate(answers.length, (index) {
var color = Theme.of(context).colorScheme.primaryContainer;
if (correctAnswer == index) {
color = Theme.of(context).colorScheme.tertiaryContainer;
}
return CardFlipEffect( // NEW
duration: const Duration(milliseconds: 300), // NEW
child: Card.filled( // NEW
key: ValueKey(answers[index]), // NEW
color: color,
elevation: 2,
margin: EdgeInsets.all(8),
clipBehavior: Clip.hardEdge,
child: InkWell(
onTap: () => onTapped(index),
child: Padding(
padding: EdgeInsets.all(16.0),
child: Center(
child: Text(
answers.length > index ? answers[index] : '',
style: Theme.of(context).textTheme.titleMedium,
overflow: TextOverflow.clip,
),
),
),
),
), // NEW
);
}),
);
}
Führen Sie jetzt einen Hot-Reload der App durch, damit die Antwortkarten mit dem CardFlipEffect-Widget umgedreht werden.
Diese Klasse ähnelt einem expliziten Animationseffekt. Es ist oft sinnvoll, die Klasse „AnimatedWidget“ direkt zu erweitern, um eine eigene Version zu implementieren. Da diese Klasse das vorherige Widget in ihrem Status speichern muss, muss sie ein StatefulWidget verwenden. Weitere Informationen zum Erstellen eigener expliziter Animationseffekte finden Sie in der API-Dokumentation für AnimatedWidget.
Mit TweenSequence eine Verzögerung hinzufügen
In diesem Abschnitt fügen Sie dem CardFlipEffect-Widget eine Verzögerung hinzu, damit die einzelnen Karten nacheinander umgedreht werden. Fügen Sie zuerst ein neues Feld namens delayAmount
hinzu.
lib/flip_effect.dart
class CardFlipEffect extends StatefulWidget {
final Widget child;
final Duration duration;
final double delayAmount; // NEW
const CardFlipEffect({
super.key,
required this.child,
required this.duration,
required this.delayAmount, // NEW
});
@override
State<CardFlipEffect> createState() => _CardFlipEffectState();
}
Fügen Sie dann die delayAmount
der Build-Methode AnswerCards
hinzu.
lib/question_screen.dart
@override
Widget build(BuildContext context) {
return GridView.count(
shrinkWrap: true,
crossAxisCount: 2,
childAspectRatio: 5 / 2,
children: List.generate(answers.length, (index) {
var color = Theme.of(context).colorScheme.primaryContainer;
if (correctAnswer == index) {
color = Theme.of(context).colorScheme.tertiaryContainer;
}
return CardFlipEffect(
delayAmount: index.toDouble() / 2, // NEW
duration: const Duration(milliseconds: 300),
child: Card.filled(
key: ValueKey(answers[index]),
Erstellen Sie dann in _CardFlipEffectState
eine neue Animation, bei der die Verzögerung mithilfe eines TweenSequence
angewendet wird. Hinweis: Hier werden keine Dienstprogramme aus der dart:async
-Bibliothek wie Future.delayed
verwendet. Das liegt daran, dass die Verzögerung Teil der Animation ist und nicht vom Widget explizit gesteuert wird, wenn es den AnimationController verwendet. So lässt sich der Animationseffekt leichter beheben, wenn Sie in den DevTools langsame Animationen aktivieren, da derselbe TickerProvider verwendet wird.
Wenn Sie ein TweenSequence
verwenden möchten, erstellen Sie zwei TweenSequenceItem
s. Eines davon enthält ein ConstantTween
, das die Animation für eine relative Dauer auf 0 hält, und eine normale Tween
, die von 0.0
bis 1.0
reicht.
lib/flip_effect.dart
class _CardFlipEffectState extends State<CardFlipEffect>
with SingleTickerProviderStateMixin {
late final AnimationController _animationController;
Widget? _previousChild;
late final Animation<double> _animationWithDelay; // NEW
@override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this, duration: widget.duration * (widget.delayAmount + 1));
_animationController.addListener(() {
if (_animationController.value == 1) {
_animationController.reset();
}
});
_animationWithDelay = TweenSequence<double>([ // NEW
if (widget.delayAmount > 0) // NEW
TweenSequenceItem( // NEW
tween: ConstantTween<double>(0.0), // NEW
weight: widget.delayAmount, // NEW
), // NEW
TweenSequenceItem( // NEW
tween: Tween(begin: 0.0, end: 1.0), // NEW
weight: 1.0, // NEW
), // NEW
]).animate(_animationController); // NEW
}
Ersetzen Sie abschließend in der Build-Methode die Animation des AnimationControllers durch die neue verzögerte Animation.
lib/flip_effect.dart
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animationWithDelay, // Modify this line
builder: (context, child) {
return Transform(
alignment: Alignment.center,
transform: Matrix4.identity()
..rotateX(_animationWithDelay.value * math.pi), // And this line
child: _animationController.isAnimating
? _animationWithDelay.value < 0.5 // And this one.
? _previousChild
: Transform.flip(flipY: true, child: child)
: child,
);
},
child: widget.child,
);
}
Führen Sie jetzt ein Hot Reload der App durch und sehen Sie sich an, wie die Karten nacheinander umgedreht werden. Sie können auch versuchen, die Perspektive des 3D-Effekts des Transform
-Widgets zu ändern.
7. Benutzerdefinierte Navigationsübergänge verwenden
Bisher haben wir gesehen, wie Sie Effekte auf einem einzelnen Bildschirm anpassen. Sie können Animationen aber auch für den Übergang zwischen Bildschirmen verwenden. In diesem Abschnitt erfahren Sie, wie Sie Bildschirmübergänge mithilfe von integrierten und vorgefertigten Animationseffekten aus dem offiziellen Animationspaket auf pub.dev animieren.
Navigationsübergang animieren
Die Klasse PageRouteBuilder
ist eine Route
, mit der Sie die Übergangsanimation anpassen können. Sie können den transitionBuilder
-Callback überschreiben, der zwei Animationsobjekte bereitstellt, die die eingehende und ausgehende Animation darstellen, die vom Navigator ausgeführt wird.
Ersetzen Sie MaterialPageRoute
durch PageRouteBuilder
, um die Übergangsanimation anzupassen, wenn der Nutzer von HomeScreen
zu QuestionScreen
wechselt. Verwenden Sie einen FadeTransition (ein explizit animiertes Widget), um den neuen Bildschirm über dem vorherigen Bildschirm einzublenden.
lib/home_screen.dart
ElevatedButton(
onPressed: () {
// Show the question screen to start the game
Navigator.push(
context,
PageRouteBuilder( // NEW
pageBuilder: (context, animation, secondaryAnimation) { // NEW
return QuestionScreen(); // NEW
}, // NEW
transitionsBuilder: // NEW
(context, animation, secondaryAnimation, child) { // NEW
return FadeTransition( // NEW
opacity: animation, // NEW
child: child, // NEW
); // NEW
}, // NEW
), // NEW
);
},
child: Text('New Game'),
),
Das Animationspaket bietet praktische vorgefertigte Animationseffekte wie „FadeThroughTransition“. Importieren Sie das Animationspaket und ersetzen Sie das Widget „FadeTransition“ durch das Widget „FadeThroughTransition“:
lib/home_screen.dart
import 'package;animations/animations.dart';
ElevatedButton(
onPressed: () {
// Show the question screen to start the game
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) {
return const QuestionScreen();
},
transitionsBuilder:
(context, animation, secondaryAnimation, child) {
return FadeThroughTransition( // NEW
animation: animation, // NEW
secondaryAnimation: secondaryAnimation, // NEW
child: child, // NEW
); // NEW
},
),
);
},
child: Text('New Game'),
),
Animation für intelligente „Zurück“-Touch-Geste anpassen
Die Funktion „Vorhersage für Zurück“ ist eine neue Android-Funktion, mit der Nutzer einen Blick auf die aktuelle Route oder App werfen können, bevor sie weitergeleitet werden. Die Peek-Animation wird durch die Position des Fingers des Nutzers gesteuert, während er den Finger über das Display zieht.
Flutter unterstützt die vorausschauende Navigation zurück, indem die Funktion auf Systemebene aktiviert wird, wenn Flutter keine Routen im Navigationsstack hat, die eingeblendet werden können, oder wenn durch die Schaltfläche „Zurück“ die App geschlossen wird. Diese Animation wird vom System und nicht von Flutter selbst verwaltet.
Flutter unterstützt auch die Vorhersage von „Zurück“-Gesten beim Wechseln zwischen Routen innerhalb einer Flutter-App. Ein spezieller PageTransitionsBuilder namens PredictiveBackPageTransitionsBuilder
überwacht die Vorhersage von „Zurück“-Gesten des Systems und steuert den Seitenübergang anhand des Fortschritts der Geste.
Die Vorhersage der Rückwärtsnavigation wird nur in Android U und höher unterstützt. Flutter wechselt aber nahtlos zum ursprünglichen Verhalten der Rückwärtsgeste und zum ZoomPageTransitionBuilder. Weitere Informationen finden Sie in unserem Blogpost, einschließlich eines Abschnitts zur Einrichtung in Ihrer eigenen App.
Konfigurieren Sie in der ThemeData-Konfiguration Ihrer App das PageTransitionsTheme so, dass auf Android-Geräten „PredictiveBack“ und auf anderen Plattformen der Überblendungseffekt aus dem Animationspaket verwendet wird:
lib/main.dart
import 'package:animations/animations.dart'; // NEW
import 'package:flutter/material.dart';
import 'home_screen.dart';
void main() {
runApp(MainApp());
}
class MainApp extends StatelessWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
useMaterial3: true,
pageTransitionsTheme: PageTransitionsTheme(
builders: {
TargetPlatform.android: PredictiveBackPageTransitionsBuilder(), // NEW
TargetPlatform.iOS: FadeThroughPageTransitionsBuilder(), // NEW
TargetPlatform.macOS: FadeThroughPageTransitionsBuilder(), // NEW
TargetPlatform.windows: FadeThroughPageTransitionsBuilder(), // NEW
TargetPlatform.linux: FadeThroughPageTransitionsBuilder(), // NEW
},
),
),
home: HomeScreen(),
);
}
}
Jetzt können Sie den Rückruf von Navigator.push() in eine MaterialPageRoute ändern.
lib/home_screen.dart
ElevatedButton(
onPressed: () {
// Show the question screen to start the game
Navigator.push(
context,
MaterialPageRoute(builder: (context) { // NEW
return const QuestionScreen(); // NEW
}), // NEW
);
},
child: Text('New Game'),
),
Mit FadeThroughTransition die aktuelle Frage ändern
Das AnimatedSwitcher-Widget bietet nur eine Animation in seinem Builder-Callback. Um dies zu ermöglichen, bietet das Paket animations
einen PageTransitionSwitcher.
lib/question_screen.dart
class QuestionCard extends StatelessWidget {
final String? question;
const QuestionCard({
required this.question,
super.key,
});
@override
Widget build(BuildContext context) {
return PageTransitionSwitcher( // NEW
layoutBuilder: (entries) { // NEW
return Stack( // NEW
alignment: Alignment.topCenter, // NEW
children: entries, // NEW
); // NEW
}, // NEW
transitionBuilder: (child, animation, secondaryAnimation) { // NEW
return FadeThroughTransition( // NEW
animation: animation, // NEW
secondaryAnimation: secondaryAnimation, // NEW
child: child, // NEW
); // NEW
}, // NEW
duration: const Duration(milliseconds: 300),
child: Card(
key: ValueKey(question),
elevation: 4,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
question ?? '',
style: Theme.of(context).textTheme.displaySmall,
),
),
),
);
}
}
OpenContainer verwenden
Das Widget OpenContainer aus dem Paket animations
bietet einen Containertransformations-Animationseffekt, der sich ausweitet, um eine visuelle Verbindung zwischen zwei Widgets herzustellen.
Das von closedBuilder
zurückgegebene Widget wird zuerst angezeigt und maximiert sich zu dem von openBuilder
zurückgegebenen Widget, wenn auf den Container getippt oder der openContainer
-Callback aufgerufen wird.
Um den openContainer
-Callback mit dem View-Modell zu verknüpfen, fügen Sie dem QuestionCard-Widget ein neues View-Modell hinzu und speichern Sie einen Callback, mit dem der Bildschirm „Game Over“ angezeigt wird:
lib/question_screen.dart
class QuestionScreen extends StatefulWidget {
const QuestionScreen({super.key});
@override
State<QuestionScreen> createState() => _QuestionScreenState();
}
class _QuestionScreenState extends State<QuestionScreen> {
late final QuizViewModel viewModel =
QuizViewModel(onGameOver: _handleGameOver);
VoidCallback? _showGameOverScreen; // NEW
@override
Widget build(BuildContext context) {
return ListenableBuilder(
listenable: viewModel,
builder: (context, child) {
return Scaffold(
appBar: AppBar(
actions: [
TextButton(
onPressed:
viewModel.hasNextQuestion && viewModel.didAnswerQuestion
? () {
viewModel.getNextQuestion();
}
: null,
child: const Text('Next'),
)
],
),
body: Center(
child: Column(
children: [
QuestionCard( // NEW
onChangeOpenContainer: _handleChangeOpenContainer, // NEW
question: viewModel.currentQuestion?.question, // NEW
viewModel: viewModel, // NEW
), // NEW
Spacer(),
AnswerCards(
onTapped: (index) {
viewModel.checkAnswer(index);
},
answers: viewModel.currentQuestion?.possibleAnswers ?? [],
correctAnswer: viewModel.didAnswerQuestion
? viewModel.currentQuestion?.correctAnswer
: null,
),
StatusBar(viewModel: viewModel),
],
),
),
);
},
);
}
void _handleChangeOpenContainer(VoidCallback openContainer) { // NEW
_showGameOverScreen = openContainer; // NEW
} // NEW
void _handleGameOver() { // NEW
if (_showGameOverScreen != null) { // NEW
_showGameOverScreen!(); // NEW
} // NEW
} // NEW
}
Fügen Sie ein neues Widget hinzu, „GameOverScreen“:
lib/question_screen.dart
class GameOverScreen extends StatelessWidget {
final QuizViewModel viewModel;
const GameOverScreen({required this.viewModel, super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Scoreboard(
score: viewModel.score,
totalQuestions: viewModel.totalQuestions,
),
Text(
'You Win!',
style: Theme.of(context).textTheme.displayLarge,
),
Text(
'Score: ${viewModel.score} / ${viewModel.totalQuestions}',
style: Theme.of(context).textTheme.displaySmall,
),
ElevatedButton(
child: Text('OK'),
onPressed: () {
Navigator.popUntil(context, (route) => route.isFirst);
},
),
],
),
),
);
}
}
Ersetzen Sie im Widget „QuestionCard“ die Karte durch ein „OpenContainer“-Widget aus dem Animationspaket und fügen Sie zwei neue Felder für das View-Modell und den Open-Container-Callback hinzu:
lib/question_screen.dart
class QuestionCard extends StatelessWidget {
final String? question;
const QuestionCard({
required this.onChangeOpenContainer,
required this.question,
required this.viewModel,
super.key,
});
final ValueChanged<VoidCallback> onChangeOpenContainer;
final QuizViewModel viewModel;
static const _backgroundColor = Color(0xfff2f3fa);
@override
Widget build(BuildContext context) {
return PageTransitionSwitcher(
duration: const Duration(milliseconds: 200),
transitionBuilder: (child, animation, secondaryAnimation) {
return FadeThroughTransition(
animation: animation,
secondaryAnimation: secondaryAnimation,
child: child,
);
},
child: OpenContainer( // NEW
key: ValueKey(question), // NEW
tappable: false, // NEW
closedColor: _backgroundColor, // NEW
closedShape: const RoundedRectangleBorder( // NEW
borderRadius: BorderRadius.all(Radius.circular(12.0)), // NEW
), // NEW
closedElevation: 4, // NEW
closedBuilder: (context, openContainer) { // NEW
onChangeOpenContainer(openContainer); // NEW
return ColoredBox( // NEW
color: _backgroundColor, // NEW
child: Padding( // NEW
padding: const EdgeInsets.all(16.0), // NEW
child: Text(
question ?? '',
style: Theme.of(context).textTheme.displaySmall,
),
),
);
},
openBuilder: (context, closeContainer) { // NEW
return GameOverScreen(viewModel: viewModel); // NEW
}, // NEW
),
);
}
}
8. Glückwunsch
Glückwunsch, Sie haben einer Flutter-App Animationseffekte hinzugefügt und sich mit den Hauptkomponenten des Flutter-Animationssystems vertraut gemacht. Sie haben unter anderem Folgendes gelernt:
- ImplicitlyAnimatedWidget verwenden
- ExplicitlyAnimatedWidget verwenden
- Kurven und Tweens auf eine Animation anwenden
- Vordefinierte Übergangs-Widgets wie AnimatedSwitcher oder PageRouteBuilder verwenden
- Vordefinierte Animationseffekte aus dem
animations
-Paket verwenden, z. B. „FadeThroughTransition“ und „OpenContainer“ - Informationen zum Anpassen der Standardübergangsanimation, einschließlich der Unterstützung der Funktion „Vorhersagen für Zurück“ auf Android-Geräten
Was liegt als Nächstes an?
Sehen Sie sich diese Codelabs an:
- Ein animiertes responsives App-Layout mit Material 3 erstellen
- Schöne Übergänge mit Material Motion für Flutter erstellen
- Ihre Flutter-App von langweilig zu schön machen
Sie können auch die Beispielanwendung für Animationen herunterladen, in der verschiedene Animationstechniken gezeigt werden.
Weitere Informationen
Weitere Ressourcen zu Animationen finden Sie auf flutter.dev:
- Einführung in Animationen
- Anleitung zu Animationen (Anleitung)
- Implizite Animationen (Anleitung)
- Eigenschaften eines Containers animieren (Rezeptbuch)
- Ein- und Ausblenden von Widgets (Rezeptbuch)
- Hero-Animationen
- Seitenübergang animieren (Rezeptbuch)
- Widget mithilfe einer Physiksimulation animieren (Rezeptbuch)
- Gestaffelte Animationen
- Animation- und Bewegungs-Widgets (Widget-Katalog)
Oder lesen Sie diese Artikel auf Medium:
- Animationen im Detail
- Benutzerdefinierte implizite Animationen in Flutter
- Animationen mit Flutter und Flux / Redux verwalten
- Welches Flutter-Animations-Widget ist das richtige für Sie?
- Richtungsanimationen mit integrierten expliziten Animationen
- Grundlagen der Flutter-Animation mit impliziten Animationen
- Wann sollte ich AnimatedBuilder oder AnimatedWidget verwenden?