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 Werte dazwischen werden angezeigt, damit das Widget flüssig animiert wird. Beispiele für implizite Animationen sind
AnimatedSize
,AnimatedScale
undAnimatedPositioned
. - Explizite Animationen sind ebenfalls vordefinierte Animationseffekte, die jedoch ein
Animation
-Objekt erfordern, um zu funktionieren. 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 angibt, 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 Glättungskurve 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 Multiple-Choice-Quiz mit verschiedenen Animationseffekten und ‑techniken.
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 intelligente „Zurück“-Touch-Geste hinzufügen, die in der neuesten Android-Version verfügbar ist
Aufgaben in diesem Lab
In diesem Codelab lernen Sie Folgendes:
- Mit implizit animierten Effekten lassen sich 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 du mit dem
animations
-Paket mit minimalem Aufwand ausgefallene Animationseffekte anzeigen kannst.
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).
- Der Android-Emulator (erfordert die Einrichtung in Android Studio).
- Einen Browser (Chrome ist für das Debuggen erforderlich)
- Einen Windows-, Linux- oder macOS-Desktopcomputer. 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
Starter-App herunterladen
Klonen Sie die Start-App mit git
aus dem Repository „flutter/samples“ 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 zum Ausführen der App den Befehl flutter run
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
so, dass Material 3 verwendet und der Startbildschirm angezeigt wird. - 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 über die
QuestionScreen
angezeigt wird.
Die App unterstützt noch keine animierten Effekte, mit Ausnahme des Standard-Bildschirmübergangs, der von der Navigator
-Klasse von Flutter angezeigt wird, wenn der Nutzer auf die Schaltfläche Neues Spiel drückt.
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 ein animiertes Punktesystem angezeigt wird. Informationen zu gängigen impliziten Animationseffekten finden Sie in der API-Dokumentation für ImplicitlyAnimatedWidget.
Widget für das unbewegliche Punktebrett erstellen
Erstellen Sie eine neue Datei 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 zu den untergeordneten Elementen des StatusBar
-Widgets hinzu und ersetzen Sie die Text
-Widgets, die zuvor die Punktzahl und die Gesamtzahl der Fragen angezeigt haben. Ihr Editor sollte die erforderliche import "scoreboard.dart"
automatisch oben in der Datei hinzufügen.
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
),
],
),
),
);
}
}
In diesem Widget wird für jede Frage ein Sternsymbol angezeigt. Wenn eine Frage richtig beantwortet wird, leuchtet sofort ohne Animation ein weiterer Stern auf. In den folgenden Schritten informieren Sie den Nutzer über eine Änderung seines Punktestands, 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
). Mit diesem können alle Attribute, einschließlich der Farbe, automatisch animiert werden. 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 Wert end
von ColorTween
je nach Wert des Parameters isActive
ändert. Das liegt daran, dass TweenAnimationBuilder
die Animation immer dann noch einmal ausführt, wenn sich der Wert von Tween.end
ändert. In diesem Fall wird die neue Animation vom aktuellen Animationswert 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 mit der 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
Mit den Flutter DevTools können Sie alle Animationen in Ihrer App verlangsamen, um Fehler zu beheben.
Damit Sie die DevTools öffnen können, 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 mit einem expliziten Animationseffekt beginnen 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. Dies ist eine gute Gelegenheit, eine explizite Animation zu verwenden.
In diesem Codelab verwenden wir als erste explizite Animation SlideTransition
. Diese 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 einem Tween<Offset>
die vom AnimatedSwitcher
bereitgestellten Animation<double>
in ein Animation<Offset>
umgewandelt werden können, das 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>
umgewandelt wird, die auf der X-Achse von -0,1 zu 0,0 übergeht.
Alternativ gibt es in der Animation-Klasse die Funktion drive()
, die eine beliebige Tween
(oder Animatable
) in eine neue 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, indem Sie das SlideTransition-Widget umschließen.
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 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 dies 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“, bei der jedoch Alignment.topCenter
anstelle von Alignment.center
verwendet wird.
Zusammenfassung
- Explizite Animationen sind Animationseffekte, die ein Animation-Objekt verwenden (im Gegensatz zu ImplicitlyAnimatedWidgets, die einen Zielwert und eine Dauer verwenden).
- Die Animation-Klasse stellt eine laufende Animation dar, definiert aber keinen bestimmten Effekt.
- Mit Tween().animate oder Animation.drive() können Sie einer Animation Tweens und Kurven (mit CurveTween) hinzufügen.
- Mit dem Parameter „layoutBuilder“ von AnimatedSwitcher können Sie festlegen, wie die untergeordneten Elemente angeordnet werden.
6. Status einer Animation steuern
Bisher wurde jede Animation 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 Tweens mit einer TweenSequence kombinieren.
Animation mit einem AnimationController ausführen
So erstellst du 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, wenn 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 Karten nacheinander umgedreht werden. Fügen Sie als Erstes ein neues Feld mit dem Namen 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 AnswerCards
-Build-Methode 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. Beachten Sie, dass hier keine Dienstprogramme aus der dart:async
-Bibliothek wie Future.delayed
verwendet werden. Das liegt daran, dass die Verzögerung Teil der Animation ist und nicht vom Widget explizit gesteuert wird, wenn es den AnimationController verwendet. Das erleichtert das Debuggen des Animationseffekts, wenn Sie in den Entwicklertools langsame Animationen aktivieren, da derselbe TickerProvider verwendet wird.
Wenn Sie eine TweenSequence
verwenden möchten, erstellen Sie zwei TweenSequenceItem
s, von denen eines eine ConstantTween
enthält, die 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 einen Hot Reload der App durch und sehen Sie sich an, wie sich die Karten nacheinander umdrehen. 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 PageRouteBuilder
-Klasse 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 ansprechende vorgefertigte Animationseffekte wie „FadeThroughTransition“. Importieren Sie das Animationspaket und ersetzen Sie das FadeTransition-Widget durch das FadeThroughTransition-Widget:
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 die intelligente „Zurück“-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 die Navigation starten. 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. Mit anderen Worten: 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 Funktion „Vorhersagende Navigation zurück“, wenn Sie zwischen Routen in einer Flutter-App wechseln. Ein spezieller PageTransitionsBuilder namens PredictiveBackPageTransitionsBuilder
überwacht die Gesten für die vorausschauende Navigation zurück 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 Übergangseffekt „Fade-Through“ 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 in seinem Builder-Callback nur eine Animation. 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 Container-Transformations-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
Herzlichen Glückwunsch! Sie haben einer Flutter-App erfolgreich Animationen hinzugefügt und sich mit den Hauptkomponenten des Flutter-Animationssystems vertraut gemacht. Im Detail haben Sie Folgendes gelernt:
- ImplicitlyAnimatedWidget verwenden
- ExplicitlyAnimatedWidget verwenden
- Kurven und Tweens auf eine Animation anwenden
- Vordefinierte Übergangs-Widgets wie „AnimatedSwitcher“ oder „PageRouteBuilder“ verwenden
- So verwenden Sie ausgefeilte vordefinierte Animationseffekte aus dem
animations
-Paket, 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 einige dieser 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)
- Implicit animations (tutorial)
- Eigenschaften eines Containers animieren (Rezeptbuch)
- Ein- und Ausblenden von Widgets (Rezeptesammlung)
- Hero-Animationen
- Seitenübergang animieren (Rezeptbuch)
- Widget mithilfe einer Physiksimulation animieren (Rezeptbuch)
- Gestaffelte Animationen
- Animationen 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?
- Bewegungsanimationen mit integrierten expliziten Animationen
- Grundlagen der Flutter-Animation mit impliziten Animationen
- Wann sollte ich AnimatedBuilder oder AnimatedWidget verwenden?