12+ Jahre App-Entwicklung
Alles aus einer Hand
50+ Erfolgreiche App-Projekte

Blog

Lottie-Animationen mit Gesten und Scrollen kombinieren

In diesem Artikel lernst du, wie du Lottie-Animationen in .NET MAUI integrierst und sie mit Gesten, Scroll-Positionen und CarouselViews verknüpfst. Du erfährst, wie Animationen per Tap, durch Tippen & Halten, über Scroll-Interaktionen sowie beim Wechseln von CarouselView-Seiten gesteuert werden. Zusätzlich bekommst du komplette XAML- und C#-Beispiele, Best Practices und fertige Demo-Videos, um interaktive und moderne UI-Erlebnisse in deiner MAUI-App umzusetzen.

Artikelbild für Lottie-Animationen mit Gesten und Scrollen kombinieren

Was ist eine Lottie?

Eine Lottie ist ein JSON-basiertes Animationsformat, das es Designern ermöglicht, Animationen so einfach wie statische Assets auf jeder Plattform bereitzustellen. Sie sind kleine Dateien, funktionieren auf jedem Gerät und können ohne Pixelbildung hoch- oder herunterskaliert werden.

Viele von euch kennen Lottie-Animationen vermutlich bereits. Für alle anderen gibt es hier den offiziellen Leitfaden zu Lottie.

Aktueller Stand der Lottie-Unterstützung in .NET MAUI

Leider gibt es zum Zeitpunkt des Schreibens noch keinen vollständig funktionierenden Lottie-SDK-Wrapper für .NET MAUI. Es gab einige Versuche, LottieXamarin auf .NET MAUI zu migrieren, was sich jedoch als ziemlich mühsam erwiesen hat.

Eine Alternative könnte Skottie sein, das SkiaSharp zur Darstellung von Lottie-Animationen nutzt. Allerdings konnte ich die Vorschau nicht zum Laufen bringen, sodass ich eine andere Lösung finden musste.

Ich bin auf das Repository von Csaba8472 gestoßen, in dem er ein eigenes Lottie-Binding für MAUI mit dem „Slim Binding“-Ansatz erstellt. Als ich mit diesem Beitrag begonnen habe, war die Implementierung jedoch noch unvollständig, weshalb ich mich entschlossen habe, mein eigenes „Slim Binding“ zu implementieren. Mehr zum Thema Slim-Binding gibt es auf GitHub.

Genau einen Tag vor der Veröffentlichung dieses Blogposts hat Csaba8472 seinen Beitrag zu #MAUIUIJuly veröffentlicht, in dem er sein Slim Binding erklärt. Du solltest ihn dir unbedingt ansehen, denn ich werde hier nicht weiter auf das Binding im Detail eingehen.
Binding Lottie (oder jedes andere Swift-Framework mit UI) in MAUI

Da ich mich darauf konzentrieren wollte, wie man Lottie-Animationen mit Gesten und scrollbaren Containern kombiniert, gehe ich hier weder auf das Binding noch auf die Erstellung des Custom Handlers näher ein. Außerdem stelle ich nur ein funktionierendes Binding für Android bereit.
Sobald es einen stabilen, voll funktionsfähigen .NET-MAUI-Wrapper für Lottie gibt, werde ich meine Beispiele darauf migrieren und einen neuen Blogpost veröffentlichen. Folge also unbedingt @CayasSoftware und @f_goncalves_ auf Twitter.

Los geht’s!

Ein Klassiker – Animation per Tap

Das ist der Klassiker: Du tippst auf ein Steuerelement, das eine Lottie-Animation auslöst. Das ist in vielen Szenarien nützlich – von einfachen Buttons bis zu „Like“- oder „Bookmark“-Interaktionen.

Lottie-Animation, die durch einen Tap ausgelöst wird – in Aktion

Wir müssen lediglich die LottieView in unser XAML einfügen, ihr einen Namen geben und einen TapGestureRecognizer anhängen:

<controls:LottieView x:Name="HeartLottieView"
                     Grid.Column="2"
                     WidthRequest="100"
                     HeightRequest="100"
                     HorizontalOptions="Center"
                     VerticalOptions="Center"
                     RepeatCount="0"
                     MaxFrame="20"
                     Margin="8,8">
    <controls:LottieView.GestureRecognizers>
        <TapGestureRecognizer Tapped="TapGestureRecognizer_Tapped" />
    </controls:LottieView.GestureRecognizers>
</controls:LottieView>

Dann können wir das Tapped-Event im Code-Behind implementieren und die Animation abspielen:

void TapGestureRecognizer_Tapped(System.Object sender, System.EventArgs e)
{
    HeartLottieView.IsPlaying = true;
}

Da der RepeatCount auf 0 gesetzt ist, wird die Animation nur einmal abgespielt und bleibt auf ihrem letzten Frame stehen (in diesem Fall Frame 20, da die vollständige Animation am Ende mehrere ähnliche Frames enthält, die ich auslassen wollte).

Wenn Dir kurzfristig Expertise fehlt, unterstützen wir Dich flexibel und punktgenau.

Bevor Dein Projekt ins Stocken gerät, hole dir gezielte Unterstützung ins Team. Mit unserem Expert as a Service greifst du direkt auf erfahrene Experten zu – ohne lange Vorlaufzeiten, sofort einsatzbereit und mit dem Fokus, Herausforderungen schnell und sauber zu lösen.

Jetzt Experten anfordern

Tippen und Halten – Animation während der Interaktion

Es gibt Szenarien, in denen das Ausführen einer Animation nur für die Dauer, in der der Benutzer mit der App interagiert, das Nutzererlebnis verbessern kann.
Das kann ein „Aufladen“ durch Tippen und Halten sein (wie hier gezeigt), funktioniert aber auch für fortgeschrittene Szenarien, z.B. während eines Fingerabdruck- oder Gesichtsscans.
Allen gemeinsam ist, dass es einen „Play“-Trigger und einen „Pause“-Trigger gibt.

Lottie-Animation, die durch Tippen und Halten ausgelöst wird – in Aktion

<Grid ColumnDefinitions="*,1,Auto"
      RowDefinitions="Auto,Auto">

    <Button Grid.Column="0"
            Grid.Row="0"
            Grid.RowSpan="2"
            Text="Tap and hold here!"
            Pressed="Button_Pressed"
            Released="Button_Released"/>

    <BoxView Grid.Column="1"
             Grid.Row="0"
             Grid.RowSpan="2"
             WidthRequest="1"
             VerticalOptions="FillAndExpand"
             Color="DarkGray" />

    <controls:LottieView x:Name="AcceptLottieView"
                         Grid.Row="0"
                         Grid.Column="2"
                         WidthRequest="100"
                         HeightRequest="100"
                         HorizontalOptions="Center"
                         VerticalOptions="Center"
                         RepeatCount="0"
                         MinFrame="1"
                         MaxFrame="45"
                         Margin="8,8" />

    <Label x:Name="AcceptProgress"
           Grid.Row="1"
           Grid.Column="2"
           FontSize="Micro"
           Text="0%"
           HorizontalTextAlignment="Center"
           HorizontalOptions="Fill" />

</Grid>

Hier verwenden wir die Pressed- und Released-Events eines Buttons, um den aktuell gerenderten Frame unserer LottieView zu steuern und außerdem den Fortschritt der Aktion in einem Label anzuzeigen. Die Events werden im Code-Behind implementiert:

void InitAccept()
{
    _tapAndHoldTimer = new System.Timers.Timer
    {
        Interval = 1000 / 30 // Animation soll 30 FPS haben
    };
    _tapAndHoldTimer.Elapsed += _tapAndHoldTimer_Elapsed;
}

void Button_Pressed(System.Object sender, System.EventArgs e)
{
    if (AcceptLottieView.Progress >= 1)
        AcceptLottieView.Progress = 0;

    AcceptProgress.Text = $"{(int)(AcceptLottieView.Progress * 100)}%";
    _tapAndHoldTimer.Start();
}

void Button_Released(System.Object sender, System.EventArgs e)
{
    _tapAndHoldTimer.Stop();
}

void _tapAndHoldTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
    if (AcceptLottieView?.Animation == null)
        return;

    if (AcceptLottieView.Progress + 100f / AcceptLottieView.MaxFrame / 100f > 1)
    {
        _tapAndHoldTimer.Stop();
        AcceptLottieView.Progress = 1;
    }
    else
    {
        AcceptLottieView.Progress += 100f / AcceptLottieView.MaxFrame / 100f;
    }

    AcceptProgress.Dispatcher.Dispatch(() =>
    {
        AcceptProgress.Text = $"{(int)(AcceptLottieView.Progress * 100)}%";
    });
}

Ich initialisiere einen Timer mit einem Intervall, das 30 Frames pro Sekunde entspricht. Der Timer wird gestartet, wenn der Button gedrückt wird, und gestoppt, wenn der Button losgelassen wird.
Wenn der Timer abläuft, berechne ich den neuen Fortschritt der Animation und aktualisiere das Label. Wenn der neue Fortschritt größer als 1 (= 100 %) wäre, wird der Timer gestoppt, der Fortschritt auf 1 gesetzt und die Animation beendet.

Animation auf LottieFiles ansehen

Du kannst auch IsPlaying auf der LottieView verwenden, um das Abspielen und Pausieren der Animation zu steuern, sodass du keinen Timer benötigst. Um den Fortschritt im Label anzuzeigen und eine alternative Möglichkeit zur Steuerung der Animation zu zeigen, habe ich mich hier für den Timer entschieden.

Schicke AGB-Seite – Animation basierend auf Scroll-Position aktualisieren

Jeder kennt die klassische AGB-Seite. Eine scrollbare Textwand, die du in wenigen Sekunden liest und akzeptierst ;-). Gelegentlich gibt es den Twist, dass du bis ganz nach unten scrollen musst, damit der „Akzeptieren“-Button aktiviert wird.
Ich nutze dieses Beispiel, um zu zeigen, wie du deine Lottie-Animationen basierend auf der aktuellen Scroll-Position einer ScrollView steuern kannst.

Lottie-Animation, die durch die Scroll-Position einer ScrollView gesteuert wird

In diesem Beispiel verwenden wir ein einfaches Label für den Text, eine LottieView für den Button, der sichtbarer wird, je näher wir dem unteren Ende kommen (https://lottiefiles.com/animations/button-reveal-light-theme-3FhFGoLZ3H) und zusätzlich eine LottieView, die den Benutzer zum Scrollen nach unten auffordert, sofern er das noch nicht getan hat (https://lottiefiles.com/animations/button-scroll-down-iva7BNRqxF).

<Grid RowDefinitions="*,Auto">

    <ScrollView x:Name="ToSScrollView"
                Grid.Row="0">

        <Label x:Name="TermsOfUseLabel"/>

    </ScrollView>

    <controls:LottieView x:Name="StartLottieView"
                         Grid.Row="1"
                         HorizontalOptions="Center"
                         HeightRequest="100"
                         RepeatCount="0"
                         MinFrame="5"
                         MaxFrame="30"
                         Margin="8,8"/>

    <controls:LottieView x:Name="ScrollDownLottieView"
                         Grid.Row="0"
                         HorizontalOptions="Center"
                         VerticalOptions="End"
                         RepeatCount="-1"
                         HeightRequest="50"
                         WidthRequest="50" />

</Grid>

Im Code-Behind der Seite abonnieren wir das Scrolled-Event unserer ScrollView und berechnen den aktuellen Frame der Animation basierend auf der aktuellen Scroll-Position.

private void ToSScrollView_Scrolled(object sender, ScrolledEventArgs e)
{
    if (_isScolledToBottom)
        return;

    var heightDifference = ToSScrollView.ContentSize.Height - ToSScrollView.Height;

    var tolerance = 50; // 50 Pixel Toleranz bis zum unteren Ende der ScrollView

    if (e.ScrollY <= heightDifference - tolerance)
    {
        ScrollDownLottieView.IsPlaying = true;
        ScrollDownLottieView.IsVisible = true;
    }
    else
    {
        ScrollDownLottieView.IsPlaying = false;
        ScrollDownLottieView.IsVisible = false;

        _isScolledToBottom = true;
    }

    var percentageScrolled = e.ScrollY / heightDifference;
    StartLottieView.Frame = (int)(StartLottieView.MinFrame + (StartLottieView.MaxFrame - StartLottieView.MinFrame) * percentageScrolled);
}

Wir berechnen die Distanz, um die gescrollt werden kann, indem wir die Höhe des Inhalts und die Höhe der ScrollView verwenden. Basierend darauf, wie viel dieser Distanz bereits gescrollt wurde, berechnen wir den Frame, der diesen Fortschritt repräsentiert.
Da das Scrolling scheinbar nicht pixelgenau ist, verwenden wir eine Toleranz, wenn wir prüfen, ob das untere Ende der ScrollView bereits erreicht wurde. Wenn das untere Ende erreicht ist, stoppen und verstecken wir die Animation des Pfeils, der den Benutzer zum Scrollen auffordert. Damit der Button nicht wieder verschwindet, wenn wir nach oben scrollen, merken wir uns, dass das Ende bereits erreicht wurde.

TimePunch-Mobile-UI nachbauen – CarouselView-Elemente für coole Scroll-Effekte animieren

In unserer Zeiterfassungs-App TimePunch Mobile, die mit Xamarin.iOS und Xamarin.Android erstellt wurde, haben wir beim Wechseln zwischen Seiten in der Übersicht folgende Animationen:

Lottie-Animationen in TimePunch Mobile

Da wir in Erwägung ziehen, TimePunch Mobile nach .NET MAUI zu migrieren bzw. mit .NET MAUI neu zu schreiben, wollte ich prüfen, ob wir die oben gezeigten Animationen auch in .NET MAUI implementieren können.

Warum sollte das nicht funktionieren? Alles, was wir brauchen, ist eine CarouselView, die Scroll-Events weitergibt, und eine funktionierende Lottie-Animation.
Als Bonus habe ich beschlossen, eine Lottie-Animation als Indikator hinzuzufügen, um zu zeigen, dass auch das möglich ist.

<Grid RowDefinitions="*,100">

        <CarouselView x:Name="Carousel"
                      PeekAreaInsets="200"
                      Loop="False"
                      ItemsSource="{Binding Items}"
                      ItemTemplate="{StaticResource AnimationItemTemplateSelector}"
                      Scrolled="CarouselView_Scrolled"/>

        <controls:LottieView x:Name="Indicator"
                             Grid.Row="1"
                             HeightRequest="100"
                             HorizontalOptions="Center" />

</Grid>

Im XAML-Code siehst du, dass ich die CarouselView hinzugefügt, die ItemsSource-Eigenschaft an die Items-Eigenschaft im Code-Behind gebunden und das Scrolled-Event abonniert habe.
Für die ItemTemplate-Eigenschaft verwende ich einen DataTemplateSelector – später wird klar, warum.
In der Zeile unter der CarouselView habe ich eine LottieView hinzugefügt, die als Indikator dient.

public ObservableCollection<CarouselAnimation> Items { get; set; } = new ObservableCollection<CarouselAnimation>();
public abstract class CarouselAnimation : INotifyPropertyChanged
{
    public abstract event PropertyChangedEventHandler PropertyChanged;

    public abstract void UpdateAnimation(double offsetFromCenter);
}

Im Code habe ich eine abstrakte Klasse CarouselAnimation definiert, die INotifyPropertyChanged implementiert und eine abstrakte Methode UpdateAnimation enthält, die einen double entgegennimmt, der den Abstand vom Zentrum für dieses Element angibt.
Die Items-Eigenschaft im Code-Behind ist eine ObservableCollection<CarouselAnimation>, sodass wir unterschiedliche Animationen mit unterschiedlichem Verhalten in einer einzelnen CarouselView darstellen können.

Sobald die Seite erscheint, wird Items mit Instanzen von Unterklassen von CarouselAnimation gefüllt und die Indikator-Animation geladen.

Im Event-Handler für das Scrolled-Event berechnen wir den Offset der Elemente vom Zentrum und geben diesen an die UpdateAnimation-Methoden der CarouselAnimation-Instanzen weiter.
Ich passe außerdem den Frame der Indicator-View in Abhängigkeit vom HorizontalScrollOffset an, um den aktuellen Index darzustellen.

void CarouselView_Scrolled(System.Object sender, Microsoft.Maui.Controls.ItemsViewScrolledEventArgs e)
{
    if (e.CenterItemIndex < 0 || e.CenterItemIndex >= Items.Count)
        return;

    var itemWidth = 200d;
    var centerItemOffset = e.HorizontalOffset - e.CenterItemIndex * itemWidth;

    // centerItemOffset -> centerItemOffsetFactor Funktion
    // 0 - 100   -> -1 -> 0
    // 100 - 200 ->  0 -> 1
    var centerItemOffsetFactor = (itemWidth / 2 - centerItemOffset) / (itemWidth / 2);
    Items[e.CenterItemIndex].UpdateAnimation(centerItemOffsetFactor);

    const int indicatorStopFrameDistance = 35; // Frames zwischen den vollständig ausgefüllten Bubbles im Indikator
    Indicator.Frame = (int)((e.HorizontalOffset - itemWidth/2) / itemWidth * indicatorStopFrameDistance);
}

Die Variable centerItemOffsetFactor ist

  • -1, wenn das Element ganz links vom Zentrum ist
  • 0, wenn das Element im Zentrum ist
  • 1, wenn das Element ganz rechts vom Zentrum ist

Natürlich nimmt sie auch Zwischenwerte an, sodass wir eine flüssige Animation erhalten.

Warum habe ich Unterklassen von CarouselAnimation?
Eine der in TimePunch gezeigten Animationen unterscheidet sich dadurch, dass sie aus zwei übereinanderliegenden Animationen besteht, die sich in entgegengesetzte Richtungen bewegen. Daher muss ich die UpdateAnimation-Methode dort anders behandeln.
Kurz gesagt gibt es die Klassen SingleLayerCarouselAnimation und DoubleLayerCarouselAnimation (Standard-Boilerplate-Code habe ich ausgelassen. Den vollständigen Code findest du im Repository.)

public class SingleLayerCarouselAnimation : CarouselAnimation
{
    public string Animation { get; set; }
    public int MinFrame { get; set; }
    public int MaxFrame { get; set; }
    public int Frame { get; set; }

    private readonly bool _inverted;

    public SingleLayerCarouselAnimation(bool inverted = false)
    {
        _inverted = inverted;
    }

    // offsetFromCenter ist
    // -1 wenn vollständig links vom Zentrum
    //  0 wenn im Zentrum
    //  1 wenn vollständig rechts vom Zentrum
    public override void UpdateAnimation(double offsetFromCenter)
    {
        // In diesem Beispiel nehmen wir an, dass das Zentrum genau zwischen Min- und MaxFrame liegt.
        // Die Berechnungen würden aber auch für ungleichmäßige Animationen funktionieren.
        var centerFrame = (MaxFrame - MinFrame) / 2;

        if (_inverted)
            offsetFromCenter *= -1;

        if (offsetFromCenter >= 0)
            Frame = (int)(centerFrame + (MaxFrame - centerFrame) * offsetFromCenter);
        else
            Frame = (int)(centerFrame + (centerFrame - MinFrame) * offsetFromCenter);
    }
}

Die Hauptlogik steckt in UpdateAnimation, wo der aktuelle Frame der Animation auf Basis von offsetFromCenter berechnet wird. Die Eigenschaften werden im DataTemplate gebunden.

<DataTemplate x:DataType="demo:SingleLayerCarouselAnimation">
    <controls:LottieView WidthRequest="200"
                         HeightRequest="200"
                         HorizontalOptions="Center"
                         VerticalOptions="Center"
                         Animation="{Binding Animation}"
                         MinFrame="{Binding MinFrame}"
                         MaxFrame="{Binding MaxFrame}"
                         Frame="{Binding Frame}"/>
</DataTemplate>

Für die DoubleLayerCarouselAnimation kann ich einfach zwei SingleLayerCarouselAnimation-Instanzen kombinieren und die Animationen im DataTemplate übereinander legen.

public class DoubleLayerCarouselAnimation : CarouselAnimation
{
    public SingleLayerCarouselAnimation Layer1 { get; set; }
    public SingleLayerCarouselAnimation Layer2 { get; set; }

    public override void UpdateAnimation(double offsetFromCenter)
    {
        Layer1.UpdateAnimation(offsetFromCenter);
        Layer2.UpdateAnimation(offsetFromCenter);
    }
}

Das Endergebnis:

Lottie-Animationen, die durch die Scroll-Position in der CarouselView gesteuert werden

Leider gibt es zum Zeitpunkt des Schreibens offenbar einen Bug mit der CarouselView, bei dem das erste und letzte Element nicht korrekt einrasten, wenn PeekAreaInsets gesetzt sind.

Planst du ein neues App-Projekt mit .NET MAUI?

Wenn du eine Idee für eine neue App hast oder eine bestehende Xamarin- oder .NET-MAUI-Anwendung weiterentwickeln möchtest, unterstützen wir dich von Architektur und UX-Konzept bis hin zur Umsetzung und Wartung. Gemeinsam bringen wir deine Anwendung stabil, performant und nutzerfreundlich in Produktion.

Projektanfrage stellen

Fazit

Wie du siehst, sind die Möglichkeiten nahezu endlos. Animationen und Interaktivität sind das, was deine App zum Leben erweckt und das Nutzererlebnis verbessert.

Bleib dran – es wird einen aktualisierten Blogpost geben, sobald es einen voll funktionsfähigen Lottie-Wrapper mit stabiler API für .NET MAUI gibt.

Du findest den Quellcode in unserem öffentlichen Bitbucket-Repository: Cayas Bitbucket.

Flavio Goncalves

Flavio Goncalves

Als Head of Technology unterstütze ich mein Team und unsere Kunden hinsichtlich neuester Technologien und Trends in der Android und iOS-Entwicklung. Unsere Projekte profitieren von meiner Xamarin und .NET MAUI-Erfahrung, sowie von meinem Faible für sauberen Code, attraktive UIs und intuitive UX. Du kannst mich gerne für einen Workshop buchen.

Verwandte Artikel

Bidirektionale Kommunikation mit MQTT in .NET MAUI
Bidirektionale Kommunikation mit MQTT in .NET MAUI

Als Mobile-App-Entwickler:innen müssen wir ständig Informationen zwischen App und Backend austauschen. In den meisten Fällen ist eine RESTful-API die Lösung. Aber was, wenn ein konstanter Datenfluss in beide Richtungen benötigt wird? In diesem Beitrag schauen wir uns MQTT an und wie man eine einfache Chat-App in .NET MAUI erstellt.

Individuelle Karten in .NET MAUI: Custom Map-Handler einfach erklärt
Individuelle Karten in .NET MAUI: Custom Map-Handler einfach erklärt

Ich arbeite derzeit an der Portierung einer Xamarin Forms App zu .NET MAUI. Die App verwendet auch Karten von Apple oder Google Maps, um Standorte anzuzeigen. Obwohl es bis zur Veröffentlichung von .NET 7 keine offizielle Unterstützung in MAUI gab, möchte ich Ihnen eine Möglichkeit zeigen, Karten über einen benutzerdefinierten Handler anzuzeigen.

Responsive Layouts in .NET MAUI
Responsive Layouts in .NET MAUI

.NET MAUI ermöglicht es uns, plattform- und geräteunabhängige Anwendungen zu schreiben, was eine dynamische Anpassung an die Bildschirmgröße und -form des Benutzers erforderlich macht. In diesem Blog-Beitrag erfahren Sie, wie Sie Ihre XAML-Layouts an unterschiedliche Geräteausrichtungen anpassen können. Dabei verwenden Sie eine ähnliche Syntax wie OnIdiom und OnPlatform, die Ihnen vielleicht schon bekannt ist.