paint-brush
Erstellen von gestaffelten Animationen in Flutter: Ein Leitfaden für Mikrointeraktionenvon@nikkieke
387 Lesungen
387 Lesungen

Erstellen von gestaffelten Animationen in Flutter: Ein Leitfaden für Mikrointeraktionen

von 19m2024/06/04
Read on Terminal Reader

Zu lang; Lesen

Das Konzept der Mikrointeraktionen basiert auf der Idee, dass kleine Details einen großen Einfluss auf das allgemeine Benutzererlebnis haben können. In Flutter können Sie Mikrointeraktionen erstellen, indem Sie subtile Animationen erstellen, indem Sie entweder die implizite oder explizite Animation verwenden. In diesem Artikel erstellen wir eine gestaffelte Animation, die die untergeordneten Elemente in einem Spalten-Widget animiert, wenn diese Seite gewischt wird.
featured image - Erstellen von gestaffelten Animationen in Flutter: Ein Leitfaden für Mikrointeraktionen
undefined HackerNoon profile picture
0-item

Eine gestaffelte Animation besteht aus aufeinanderfolgenden oder sich überlappenden Animationen. Die Animation kann rein sequentiell sein, wobei eine Änderung nach der anderen erfolgt, oder sie kann sich teilweise oder vollständig überlappen. Bei einer sequentiellen Animation werden die Elemente nacheinander mit einer leichten Verzögerung zwischen den Startzeiten der einzelnen Elemente animiert. Dadurch entsteht ein Kaskaden- oder Welleneffekt, bei dem die Animation die Elemente scheinbar schrittweise und nicht auf einmal durchläuft.


Versetzte Animationen gelten als eine Art Mikrointeraktion, da sie das Benutzererlebnis verbessern, indem sie subtiles, interaktives Feedback liefern, das den Benutzer durch eine Benutzeroberfläche führt. In Flutter können Sie Mikrointeraktionen erstellen, indem Sie subtile Animationen erstellen, indem Sie entweder die implizite oder explizite Animation verwenden.


Implizite Animationen sind so konzipiert, dass sie einfach und benutzerfreundlich sind, da die Animationsdetails abstrahiert werden, während explizite Animationen besser für komplexe Animationen geeignet sind, da sie eine vollständige Kontrolle über den Animationsprozess bieten. Dies wird insbesondere dann verwendet, wenn Sie eine feinere Kontrolle über die Animation benötigen.


In diesem Artikel werden wir uns eingehend mit dem Konzept der Mikrointeraktionen befassen. Anschließend werden wir für einen Anwendungsfall von Mikrointeraktionen mithilfe expliziter Animationen eine gestaffelte Animation erstellen, die die in einem Spalten-Widget enthaltenen untergeordneten Elemente animiert.

Was ist das Konzept der Mikrointeraktionen?

Tolle Produkte sind Produkte, die sowohl hinsichtlich der Funktionen als auch der Details überzeugen. Funktionen ziehen Menschen zu Ihren Produkten, aber Details halten sie. Diese Details sind Dinge, die Ihre App von anderen abheben. Mit Mikrointeraktionen können Sie diese Details schaffen, indem Sie Ihren Benutzern erfreuliches Feedback geben.


Das Konzept der Mikrointeraktionen basiert auf der Idee, dass kleine Details einen großen Einfluss auf das gesamte Benutzererlebnis haben können. Mikrointeraktionen können für wichtige Funktionen wie die Übermittlung von Feedback oder das Ergebnis einer Aktion verwendet werden. Beispiele für Mikrointeraktionen sind:


  • Schaltflächenanimationen: Die Schaltfläche ändert ihre Farbe oder Größe, wenn Sie mit der Maus darüber fahren oder darauf drücken.


  • Ladeindikatoren: Animationen, die einem Benutzer anzeigen, dass ein Vorgang ausgeführt wird.


  • Wischbewegungen: Animationen, die auf Wischbewegungen reagieren.


  • Navigationsübergänge: Flüssige Animationen beim Übergang zwischen Bildschirmen.

Mikrointeraktionen in realen Anwendungen

Unten sehen wir reale Anwendungen von Mikrointeraktionen. Diese subtilen Animationen werden in Flutter erstellt, um das Benutzererlebnis zu verbessern. 👇🏽

Designreferenzen für diese Apps wurden von Dribbble bezogen.

So erstellen Sie gestaffelte Animationen in Flutter

In diesem Beispiel erstellen wir eine gestaffelte Animation, die die untergeordneten Elemente in einem Spalten-Widget animiert, wenn über diese Seite gewischt wird. Dies wird mit dem expliziten Animationsansatz erstellt, da wir in diesem Fall die volle Kontrolle darüber haben müssen, wie die Animation ablaufen soll.


So wird die Animation aussehen, wenn wir fertig sind 👇🏽

Voraussetzung

Um dieses Tutorial optimal nutzen zu können, sollten Sie über Folgendes verfügen:


  • Ein grundlegendes Verständnis davon, wie Animationen in Flutter erstellt werden
  • Gute Kenntnisse der Flutter- und Dart-Grundlagen
  • Ein Code-Editor, entweder VScode oder Android Studio
  • Ein Emulator oder Gerät zum Aufbauen


Nachdem Sie alle Voraussetzungen des Projekts überprüft haben, können wir loslegen.


Zuerst erstellen wir den Hauptbildschirm, der diese beiden Seiten enthält. Diese beiden Seiten werden in ein Seitenansichts-Widget eingebettet, das steuert, welche Seite beim Wischen angezeigt wird. Außerdem haben wir unten auf dem Hauptbildschirm einen Indikator, der uns die Seite anzeigt, auf der wir uns gerade befinden.

 class MainScreen extends StatefulWidget { const MainScreen({super.key}); @override State<MainScreen> createState() => _MainScreenState(); } class _MainScreenState extends State<MainScreen>{ final controller = PageController(keepPage: true); @override Widget build(BuildContext context) { return Scaffold( body: SafeArea( child: PageView( controller: controller, children: [ Page1(pageController: controller,), Page2(pageController: controller,), ], ), ), bottomNavigationBar: Container( alignment: Alignment.topCenter, padding: const EdgeInsets.only(top: 10), height: 30, child: SmoothPageIndicator( controller: controller, count: 2, effect: const JumpingDotEffect( dotHeight: 10, dotWidth: 10, activeDotColor: Colors.grey, dotColor: Colors.black12, ), ), ) ); } }


Der im obigen Codeausschnitt verwendete SmoothPageIndicator ist auf pub.dev zu finden. Hier wird der SmoothPageIndicator verwendet, um die aktuelle Seite in der Ansicht anzuzeigen. Sie können das Paket wie folgt zu Ihrer pubspec.yaml hinzufügen 👇🏽

 dependencies: flutter: sdk: flutter smooth_page_indicator: ^1.1.0


Auf den beiden Seiten haben wir ein Spalten-Widget mit mehreren leeren Karten-Widgets. Diese leeren Karten-Widgets werden verwendet, um die Spalte zu füllen, damit wir die gestaffelte Animation vollständig sehen und würdigen können. Wir erstellen das leere Karten-Widget als wiederverwendbare Komponente, damit wir es nicht an jeder Stelle, an der wir es verwenden, von Grund auf neu erstellen müssen.

 class EmptyCard extends StatelessWidget { const EmptyCard({super.key, required this.width, required this.height}); final double width; final double height; @override Widget build(BuildContext context) { return Container( height: height, width: width, decoration: BoxDecoration( borderRadius: const BorderRadius.all(Radius.circular(10)), color: Colors.blue.shade200, boxShadow: const [ BoxShadow( color: Colors.black12, blurRadius: 4, offset: Offset(0,4), ) ] ), ); } }


Nachdem wir alle diese Boilerplate-Codes aus dem Weg geräumt haben, beginnen wir nun mit dem Erstellen der Animation. Zuerst erstellen wir ein neues Stateful-Widget, das wir AnimateWidget nennen. Es verfügt über mehrere Parameter, mit denen sich steuern lässt, wie die Animation ablaufen soll.

 class AnimateWidget extends StatefulWidget { const AnimateWidget({super.key, required this.duration, required this.position, required this.horizontalOffset, required this.child, required this.controller, }); final Duration duration; final int position; final double? horizontalOffset; final Widget child; final PageController controller; @override State<AnimateWidget> createState() => _AnimateWidgetState(); }


Mit den Parametern von AnimateWidget , wie sie im obigen Snippet zu sehen sind, können wir Folgendes erfolgreich steuern:

  • Dauer der Animation.
  • Der pageController sorgt dafür, dass die Animation beim Wischen über die Seite ausgelöst wird.
  • Der Betrag des horizontalen Versatzes des animierten Elements.


Als nächstes definieren wir in AnimateWiget Folgendes:

  • AnimationController : Wird zur Steuerung der Animationssequenz verwendet.

  • Aktuelle Seite: wird verwendet, um die aktuellen Seitendaten zu speichern.

  • Timer: Wird verwendet, um die Animation nach einer gewissen Verzögerung auszulösen. Dadurch entsteht der gestaffelte Kaskadeneffekt der Animation.


     class _AnimateWidgetState extends State<AnimateWidget> with AutomaticKeepAliveClientMixin, SingleTickerProviderStateMixin{ @override bool get wantKeepAlive => true; late AnimationController animationController; int currentPage = 0; Timer? _timer; @override void initState() { super.initState(); animationController = AnimationController(vsync: this, duration: widget.duration); _timer = Timer(getDelay(), animationController.forward); widget.controller.addListener(() { currentPage = widget.controller.page!.round(); if(currentPage == widget.controller.page){ _timer = Timer(getDelay(), animationController.forward); } }); } @override void dispose() { _timer?.cancel(); animationController.dispose(); super.dispose(); } Duration getDelay(){ var delayInMilliSec = widget.duration.inMilliseconds ~/ 6; int getStaggeredListDuration(){ return widget.position * delayInMilliSec; } return Duration(milliseconds: getStaggeredListDuration()); }

Wenn Sie den obigen Codeausschnitt analysieren, werden Sie Folgendes feststellen:

  • Wir haben AnimationController verwendet, um die Animationssequenz zu steuern, deshalb haben wir SingleTickerProviderStateMixin eingeführt.


  • In der Methode init state haben wir den AnimationController initialisiert und dann die Animation mit animationController.forward “ beim Seitenaufruf und beim Seitenwischen ausgelöst.


  • Wir haben den Timer verwendet, um das Auslösen der Animation basierend auf der Verzögerung zu steuern.


  • In der dispose -Methode haben wir Ressourcen bereinigt.


  • Wir haben AutomaticKeepAliveClientMixin verwendet, um den Status des Widgets beizubehalten. Dies soll verhindern, dass der AnimationController gelöscht wird, wenn eine Seite durchgewischt wird und nicht mehr sichtbar ist.


  • In der Funktion getDelay haben wir die Verzögerung berechnet, bevor die Animation für jedes Element ausgelöst wurde. Dazu haben wir die Dauer in Millisekunden durch 6 geteilt, die Ergebnisse angenähert und sie dann mit der Position multipliziert. Dies ist die Position (in diesem Fall der Index) des Elements.


In der Build-Methode geben wir einen AnimatedBuilder zurück. In diesem animierten Builder geben wir eine Widget-Funktion namens _slideAnimation zurück, die ein Transform.translate -Widget zurückgibt. In der _slideAnimation Funktion haben wir die offsetAnimation Funktion.


Die Funktion offsetAnimation gibt die Animationseigenschaft zurück, die im Widget Transform.translate verwendet wird. Das Widget Transform.translate animiert das untergeordnete Widget unter Verwendung des Werts aus der Animation.

 @override Widget build(BuildContext context) { super.build(context); return AnimatedBuilder( animation: animationController, builder: (context, child){ return _slideAnimation(animationController); } ); } Widget _slideAnimation(Animation<double> animationController){ Animation<double> offsetAnimation(double offset, Animation<double> animationController) { return Tween<double>(begin: offset, end: 0.0).animate( CurvedAnimation( parent: animationController, curve: const Interval(0.0, 1.0, curve: Curves.ease), ), ); } return Transform.translate( offset: Offset( widget.horizontalOffset == 0.0 ? 0.0 : offsetAnimation(widget.horizontalOffset!, animationController).value, 0.0, ), child: widget.child, ); }


Dies ist der vollständige Code für die AnimateWidget Klasse 👇🏽

 class AnimateWidget extends StatefulWidget { const AnimateWidget({super.key, required this.duration, required this.position, required this.horizontalOffset, required this.child, required this.controller, }); final Duration duration; final int position; final double? horizontalOffset; final Widget child; final PageController controller; @override State<AnimateWidget> createState() => _AnimateWidgetState(); } class _AnimateWidgetState extends State<AnimateWidget> with AutomaticKeepAliveClientMixin, SingleTickerProviderStateMixin{ @override bool get wantKeepAlive => true; late AnimationController animationController; int currentPage = 0; Timer? _timer; @override void initState() { super.initState(); animationController = AnimationController(vsync: this, duration: widget.duration); _timer = Timer(getDelay(), animationController.forward); widget.controller.addListener(() { currentPage = widget.controller.page!.round(); if(currentPage == widget.controller.page){ _timer = Timer(getDelay(), animationController.forward); } }); } @override void dispose() { _timer?.cancel(); animationController.dispose(); super.dispose(); } Duration getDelay(){ var delayInMilliSec = widget.duration.inMilliseconds ~/ 6; int getStaggeredListDuration(){ return widget.position * delayInMilliSec; } return Duration(milliseconds: getStaggeredListDuration()); } @override Widget build(BuildContext context) { super.build(context); return AnimatedBuilder( animation: animationController, builder: (context, child){ return _slideAnimation(animationController); } ); } Widget _slideAnimation(Animation<double> animationController){ Animation<double> offsetAnimation(double offset, Animation<double> animationController) { return Tween<double>(begin: offset, end: 0.0).animate( CurvedAnimation( parent: animationController, curve: const Interval(0.0, 1.0, curve: Curves.ease), ), ); } return Transform.translate( offset: Offset( widget.horizontalOffset == 0.0 ? 0.0 : offsetAnimation(widget.horizontalOffset!, animationController).value, 0.0, ), child: widget.child, ); } }

Um diese AnimateWidget Klasse als Nächstes in einem Spalten-Widget zu verwenden, erstellen wir eine Klasse mit einer statischen Methode namens toStaggeredList , die eine Liste zurückgibt. In dieser Methode übergeben wir alle erforderlichen Parameter, einschließlich einer Liste children . An den Parameter children übergeben wir die Liste der Elemente, die wir animieren werden.


Als Nächstes ordnen wir untergeordnete Elemente zu und umschließen jedes untergeordnete Element mit dem AnimateWidget .

 class AnimateList{ static List<Widget>toStaggeredList({ required Duration duration, double? horizontalOffset, required PageController controller, required List<Widget>children, })=> children .asMap() .map((index, widget){ return MapEntry( index, AnimateWidget( duration: duration, position: index, horizontalOffset: horizontalOffset, controller: controller, child: widget, ) ); }) .values .toList(); }


Im AnimateWidget übergeben wir die erforderlichen Parameter, um jedes untergeordnete Element in der Liste erfolgreich zu animieren. Mit der Methode AnimateList.toStaggeredList können wir es jetzt auf den beiden Seiten implementieren, an denen wir arbeiten.

 class Page1 extends StatelessWidget { const Page1({super.key, required this.pageController}); final PageController pageController; @override Widget build(BuildContext context) { return SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( children: AnimateList.toStaggeredList( duration: const Duration(milliseconds: 375), controller: pageController, horizontalOffset: MediaQuery.of(context).size.width / 2, children: [ const EmptyCard(width: 250, height: 50,), const Padding( padding: EdgeInsets.only(top: 20), child: EmptyCard(width: 180, height: 80,), ), const Padding( padding: EdgeInsets.only(top: 20), child: EmptyCard(width: 270, height: 50,), ), const Padding( padding: EdgeInsets.symmetric(vertical: 20), child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ EmptyCard(height: 50, width: 70), EmptyCard(height: 50, width: 70), EmptyCard(height: 50, width: 70), ], ), ), const EmptyCard(width: 250, height: 50,), const Padding( padding: EdgeInsets.only(top: 20), child: EmptyCard(width: 180, height: 80,), ), const Padding( padding: EdgeInsets.only(top: 20), child: EmptyCard(width: 270, height: 50,), ), const Padding( padding: EdgeInsets.symmetric(vertical: 20), child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ EmptyCard(height: 50, width: 70), EmptyCard(height: 50, width: 70), EmptyCard(height: 50, width: 70), ], ), ), ], ), ), ); } } class Page2 extends StatelessWidget { const Page2({super.key, required this.pageController}); final PageController pageController; @override Widget build(BuildContext context) { return SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( children: AnimateList.toStaggeredList( duration: const Duration(milliseconds: 375), controller: pageController, horizontalOffset: MediaQuery.of(context).size.width / 2, children: [ const EmptyCard(width: 220, height: 70,), const Padding( padding: EdgeInsets.only(top: 20), child: EmptyCard(width: 300, height: 70,), ), const Padding( padding: EdgeInsets.only(top: 20), child: EmptyCard(width: 200, height: 50,), ), const Padding( padding: EdgeInsets.symmetric(vertical: 20), child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ EmptyCard(height: 70, width: 70), EmptyCard(height: 70, width: 70), ], ), ), const EmptyCard(width: 220, height: 70,), const Padding( padding: EdgeInsets.only(top: 20), child: EmptyCard(width: 300, height: 70,), ), const Padding( padding: EdgeInsets.symmetric(vertical: 20), child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ EmptyCard(height: 70, width: 70), EmptyCard(height: 70, width: 70), ], ), ), ], ), ), ); } }

In den untergeordneten Elementen des Spalten-Widgets übergeben wir die AnimateList.toStaggeredList und die erforderlichen Parameter, einschließlich der Widgets, die in der Spalte angezeigt werden sollen. Damit haben wir erfolgreich eine gestaffelte Animation erstellt, die beim Wischen ausgelöst wird. Den vollständigen Code können Sie hier einsehen.


Dies ist unser Endergebnis:

Abschluss

Wir sind am Ende dieses Tutorials angelangt. An diesem Punkt haben wir das Konzept der Mikrointeraktionen und ihre Auswirkungen auf die Benutzererfahrung behandelt. Wir haben auch den Prozess der Erstellung einer gestaffelten Animation von Elementen in einem Spalten-Widget durchlaufen, das beim Wischen auf der Seite ausgelöst wird.


Es gibt so viele Arten von Mikrointeraktionen, die Sie Ihrer App hinzufügen können, um die Benutzererfahrung Ihres Projekts zu verbessern; Sie können experimentieren, indem Sie in Flutter interaktivere Animationen erstellen.


Wenn Sie diesen Artikel hilfreich fanden, können Sie ihn unterstützen, indem Sie ein „Gefällt mir“ oder einen Kommentar hinterlassen. Sie können mir auch folgen, um weitere verwandte Artikel zu erhalten.

Verweise

Flutter Staggered Animation-Paket