Informationen zu diesem Codelab
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 sindSizeTransition
,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.
Aufgaben
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
Lerninhalte
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 ausgefallene Animationseffekte mit minimalem Aufwand 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 der Xcode-Tools).
- Der Android-Emulator (erfordert die 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
Start-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.
Anwendung ausführen
Verwenden Sie den Befehl flutter run
, um die App auszuführen, und geben Sie ein Zielgerät an, z. B. android
, ios
oder chrome
. 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 das Anzeigen des Startbildschirms - 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 Punktebrett 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.
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(isActive: score > i), // Edit this line.
],
),
);
}
}
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. Das color
von Icon
ist hier nicht animiert, nur das 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
-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); // And modify this line.
},
),
);
}
}
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 mithilfe von 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.
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 dies jedoch 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
. Diese Animation 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. Mit einer Tween
kann also der von der AnimatedSwitcher
bereitgestellte Animation
in einen Animation
umgewandelt werden, der 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 Tween.animate
verwendet wird, um eine Curve
auf die Animation
anzuwenden und sie dann von einer Tween
, die von 0,0 bis 1,0 reicht, in eine Tween
umzuwandeln, 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 kombiniert werden können. Fügen Sie eine weitere explizite Animation hinzu, FadeTransition
, die dieselbe 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 der AnimationSwitcher
auf. Wenn ein QuestionCard
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 dieses Problem zu beheben, gibt es für AnimatedSwitcher
auch den 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 zuImplicitlyAnimatedWidgets
, die ein Zielvalue
undduration
verwenden). - Die Klasse
Animation
stellt eine laufende Animation dar, definiert aber keinen bestimmten Effekt. - Verwenden Sie
Tween().animate
oderAnimation.drive()
, umTweens
undCurves
(mitCurveTween
) auf eine Animation anzuwenden. - Mit dem Parameter
layoutBuilder
vonAnimatedSwitcher
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 ein Animation
erforderlich, damit sie ordnungsgemäß funktionieren. In diesem Abschnitt erfahren Sie, wie Sie mithilfe eines AnimationController
eigene Animation
-Objekte erstellen und Tween
s mit einem TweenSequence
kombinieren.
Animation mit einem AnimationController ausführen
So erstellen Sie eine Animation mit einem AnimationController:
StatefulWidget
erstellen- Verwenden Sie den
SingleTickerProviderStateMixin
-Mixin in IhrerState
-Klasse, um IhremAnimationController
eineTicker
zur Verfügung zu stellen. - Initialisieren Sie die
AnimationController
in der LebenszyklusmethodeinitState
und geben Sie das aktuelleState
-Objekt für den Parametervsync
(TickerProvider
) an. - Achten Sie darauf, dass Ihr Widget neu erstellt wird, wenn
AnimationController
seine Listener benachrichtigt. Verwenden Sie dazu entwederAnimatedBuilder
oder rufen Sielisten()
undsetState
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 ein 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, wenn 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 einfügen. Geben Sie für das Card
-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 über das CardFlipEffect
-Widget umgedreht werden.
Diese Klasse ähnelt einem expliziten Animationseffekt. Es ist oft sinnvoll, die AnimatedWidget
-Klasse direkt zu erweitern, um eine eigene Version zu implementieren. Da diese Klasse das vorherige Widget in ihrer State
speichern muss, muss sie eine 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
, die die Verzögerung mithilfe einer TweenSequence
anwendet. 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 die AnimationController
verwendet. So lässt sich der Animationseffekt leichter beheben, wenn Sie in den DevTools langsame Animationen aktivieren, da dieselbe TickerProvider
verwendet wird.
Wenn Sie ein TweenSequence
verwenden möchten, erstellen Sie zwei TweenSequenceItem
s, von denen eines ein ConstantTween
enthält, 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>([ // Add from here...
if (widget.delayAmount > 0)
TweenSequenceItem(
tween: ConstantTween<double>(0.0),
weight: widget.delayAmount,
),
TweenSequenceItem(tween: Tween(begin: 0.0, end: 1.0), weight: 1.0),
]).animate(_animationController); // To here.
}
Ersetzen Sie abschließend die Animation von AnimationController
durch die neue verzögerte Animation in der build
-Methode.
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 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 ein FadeTransition
(ein explizit animiertes Widget), damit der neue Bildschirm über dem vorherigen Bildschirm eingeblendet wird.
lib/home_screen.dart
ElevatedButton(
onPressed: () {
// Show the question screen to start the game
Navigator.push(
context,
PageRouteBuilder( // Add from here...
pageBuilder: (context, animation, secondaryAnimation) {
return const QuestionScreen();
},
transitionsBuilder:
(context, animation, secondaryAnimation, child) {
return FadeTransition(
opacity: animation,
child: child,
);
},
), // To here.
);
},
child: Text('New Game'),
),
Das Animationspaket bietet ausgefeilte vordefinierte Animationseffekte wie FadeThroughTransition
. Importieren Sie das Animationspaket und ersetzen Sie das FadeTransition
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( // Add from here...
animation: animation,
secondaryAnimation: secondaryAnimation,
child: child,
); // To here.
},
),
);
},
child: Text('New Game'),
),
Animation für intelligente „Zurück“-Touch-Geste anpassen
Die Funktion „Vorausschauendes 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 den Bildschirm 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 in einer Flutter-App. Eine spezielle 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 für Ihre App die PageTransitionsTheme
so, dass auf Android-Geräten PredictiveBack
verwendet wird, und auf anderen Plattformen der Übergangseffekt „Weichzeichner“ aus dem Animationspaket:
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),
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 Navigator.push()
-Rückruf in einen MaterialPageRoute
ändern.
lib/home_screen.dart
ElevatedButton(
onPressed: () {
// Show the question screen to start the game
Navigator.push(
context,
MaterialPageRoute( // Add from here...
builder: (context) {
return const QuestionScreen();
},
), // To here.
);
},
child: Text('New Game'),
),
Mit FadeThroughTransition die aktuelle Frage ändern
Das AnimatedSwitcher
-Widget stellt in seinem Builder-Callback nur einen Animation
bereit. Um dies zu beheben, enthält das Paket animations
eine 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( // Add from here...
layoutBuilder: (entries) {
return Stack(alignment: Alignment.topCenter, children: entries);
},
transitionBuilder: (child, animation, secondaryAnimation) {
return FadeThroughTransition(
animation: animation,
secondaryAnimation: secondaryAnimation,
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,
),
),
),
);
}
}
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 Ansichtsmodell zu verknüpfen, fügen Sie dem QuestionCard
-Widget ein neues viewModel
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 QuestionCard
-Widget das Card
durch ein OpenContainer
-Widget aus dem animations
-Paket und fügen Sie zwei neue Felder für den viewModel
- 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:
- So verwendest du
ImplicitlyAnimatedWidget
- So verwendest du
ExplicitlyAnimatedWidget
Curves
undTweens
auf eine Animation anwenden- Vordefinierte Übergangs-Widgets wie
AnimatedSwitcher
oderPageRouteBuilder
verwenden - So verwenden Sie ausgefeilte vordefinierte Animationseffekte aus dem
animations
-Paket, z. B.FadeThroughTransition
undOpenContainer
- Informationen zum Anpassen der Standardübergangsanimation, einschließlich der Unterstützung der Funktion „Vorhersagen für Zurück“ auf Android-Geräten
Nächste Schritte
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 mit 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?