SkiaSharp.Extended icon indicating copy to clipboard operation
SkiaSharp.Extended copied to clipboard

Programmatically Create SKLottieView in MAUI

Open heiser opened this issue 2 years ago • 10 comments

All of the examples I have seen assign the source in XAML; I have tried to do it in code without success:

            var source = new SKFileLottieImageSource()
            { 
                File = String.Format("{0}.json", Name)
            };
            
            var view = new SKLottieView()
            {
                Source = source,
                WidthRequest = 400,
                HeightRequest = 400,
                BackgroundColor = Colors.Red,
                RepeatMode = SKLottieRepeatMode.Restart,
                RepeatCount = Loop ? -1 : 0,
                //Scale = Scale,
                StyleId = ID
            };

The view renders to the screen but the animation does not appear.

I see in the source that the file is ultimately loaded using FileSystem.OpenAppPackageFileAsync and so I tested that with the file path I used in SKFileLottieImageSource, and it worked.

Also, I know the Lottie file I am using works, as it did fine in the Xamarin Forms app that we are attempting to migrate to MAUI.

heiser avatar Dec 09 '22 19:12 heiser

Similar to this but slightly different. I cannot play animation on demand from code behind. In Xamarin.Forms there was a method available PlayAnimation() but it it is missing in MAUI version. Can somebody provide some work around on how to play an animation from code behind for the LottieView created in XAML?

dpjha84 avatar Jan 17 '23 12:01 dpjha84

Yes we need an example or guidance to set image source using C#

Strypper avatar Feb 10 '23 18:02 Strypper

I was trying to get this working with Maui.Markup. The AnimationLoaded event fires, but the animation is never displayed. 😢

mrlacey avatar May 29 '23 09:05 mrlacey

Animations work perfectly fine when I assign them in Xaml. However, when I try to setup a derived control, that would set some predefined animations, the animations won't show up.

using System.Diagnostics;
using SkiaSharp.Extended.UI.Controls;

namespace MSOISales.Maui.Legacy.Controls;

public enum LoadingIndicatorType
{
    Circular,
    Horizontal
}

public class LoadingIndicator : SKLottieView
{
    public LoadingIndicator()
    {
        AnimationLoaded += OnAnimationLoaded;
        AnimationFailed += OnAnimationFailed;
        UpdateIndicatorType();
        UpdateIndicatorState();
    }

    public static readonly BindableProperty IndicatorTypeProperty = BindableProperty.Create(nameof(IndicatorType),
                                                                                            typeof(LoadingIndicatorType),
                                                                                            typeof(LoadingIndicator),
                                                                                            defaultValue:LoadingIndicatorType.Circular,
                                                                                            propertyChanged:OnIndicatorTypeChanged);

    public LoadingIndicatorType IndicatorType
    {
        get => (LoadingIndicatorType)GetValue(IndicatorTypeProperty);
        set => SetValue(IndicatorTypeProperty, value);
    }

    private static void OnIndicatorTypeChanged(BindableObject bindable, object oldValue, object newValue)
    {
        var loadingIndicator = (LoadingIndicator)bindable;
        loadingIndicator.UpdateIndicatorType();
    }

    public static readonly BindableProperty IsRunningProperty = BindableProperty.Create(nameof(IsRunning),
                                                                                        typeof(bool),
                                                                                        typeof(LoadingIndicator),
                                                                                        defaultValue:false,
                                                                                        propertyChanged:OnIsRunningChanged);

    public bool IsRunning
    {
        get => (bool)GetValue(IsRunningProperty);
        set => SetValue(IsRunningProperty, value);
    }

    private static void OnIsRunningChanged(BindableObject bindable, object oldValue, object newValue)
    {
        var loadingIndicator = (LoadingIndicator)bindable;
        loadingIndicator.UpdateIndicatorState();
    }

    private void UpdateIndicatorType()
    {
        Source = IndicatorType switch
        {
            LoadingIndicatorType.Circular => new SKFileLottieImageSource { File = "animation_circular_loading.json" },
            LoadingIndicatorType.Horizontal => new SKFileLottieImageSource { File = "animation_horizontal_loading.json" },
            _ => throw new ArgumentOutOfRangeException()
        };
    }

    private void UpdateIndicatorState()
    {
        IsAnimationEnabled = IsRunning;
        IsVisible = IsRunning;
    }

    private void OnAnimationLoaded(object sender, EventArgs e)
    {
        Debug.WriteLine($"Animation {IndicatorType} loaded");
    }

    private void OnAnimationFailed(object sender, EventArgs e)
    {
        Debug.WriteLine($"Animation {IndicatorType} failed");
    }
}

Later, I use it in Xaml like this

     <Style
        x:Key="DefaultLoadingIndicatorStyle"
        TargetType="controls:LoadingIndicator">
        <Setter Property="WidthRequest" Value="{StaticResource LoadingIndicatorWidth}" />
        <Setter Property="HeightRequest" Value="{StaticResource LoadingIndicatorHeight}" />
        <Setter Property="VerticalOptions" Value="Center" />
        <Setter Property="HorizontalOptions" Value="Center" />
        <Setter Property="BackgroundColor" Value="Red" />
    </Style>

    <Style
        BasedOn="{StaticResource DefaultLoadingIndicatorStyle}"
        TargetType="controls:LoadingIndicator" />

    <controls:LoadingIndicator
        IsRunning="{Binding SyncCommand.IsBusy}"
        IndicatorType="Horizontal" />

The AnimationLoaded is fired, and I never receive an error on the AnimationFailed; however, nothing happens, I see a blank view.

kyurkchyan avatar Jun 19 '23 13:06 kyurkchyan

I think I found a workaround for this. We create a library where we have a Content Page that will animate using SKLottieView when consumers set a property for the page IsFinishedSaving.

The SKLottieView is created programatically, and we also noticed that the first time the app loads and the first time we display the animation, it does not appear. The second time we try to display the animation it does appear. I went through this repository, and found this line in the constructor of SKLottieView:

ResourceLoader<SKLottieViewResources>.EnsureRegistered((VisualElement) this);

From my reading, the ResourcesLoader basically makes sure that the SKLottieViewResources is added to the apps resources. I somehow figured out that this has to be some kind of race condition where it might not have been loaded successfully when we try to display the animation the first time you try to create it. The second time you display it its already merged and it works as expected.

The workaround is to merge this resource to your resources manually in your App.xaml:

<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:themes="clr-namespace:SkiaSharp.Extended.UI.Controls.Themes;assembly=SkiaSharp.Extended.UI"
    ...>
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <themes:SKLottieViewResources/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

Let me know if this did not work, we did not inherit directly from SKLottieView in our component. So there might be differences in how we programatically use SKLottieView if it does not work for you.

haavamoa avatar Sep 19 '23 18:09 haavamoa

Hello @haavamoa I have the same problem. Unfortunately, your workaround doesn't work for me. If I load the animation for the second time, it works as expected.

naaeef avatar Nov 23 '23 15:11 naaeef

If I add the following to the constructor of the page containing the animation, it works for me: Application.Current?.Resources?.MergedDictionaries.Add(new SKLottieViewResources());

naaeef avatar Nov 29 '23 11:11 naaeef

Looks like binding the source does not work either. When using binding, nothing displays within the SKLottieView. Not even after loading a second or third time. Only when hardcoding the name of the JSON file within the source attribute, the animation displays.

Elmigo avatar Feb 10 '24 12:02 Elmigo

I think I found a workaround for this. We create a library where we have a Content Page that will animate using SKLottieView when consumers set a property for the page IsFinishedSaving.

The SKLottieView is created programatically, and we also noticed that the first time the app loads and the first time we display the animation, it does not appear. The second time we try to display the animation it does appear. I went through this repository, and found this line in the constructor of SKLottieView:

ResourceLoader<SKLottieViewResources>.EnsureRegistered((VisualElement) this);

From my reading, the ResourcesLoader basically makes sure that the SKLottieViewResources is added to the apps resources. I somehow figured out that this has to be some kind of race condition where it might not have been loaded successfully when we try to display the animation the first time you try to create it. The second time you display it its already merged and it works as expected.

The workaround is to merge this resource to your resources manually in your App.xaml:

<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:themes="clr-namespace:SkiaSharp.Extended.UI.Controls.Themes;assembly=SkiaSharp.Extended.UI"
    ...>
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <themes:SKLottieViewResources/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

Let me know if this did not work, we did not inherit directly from SKLottieView in our component. So there might be differences in how we programatically use SKLottieView if it does not work for you.

Thanks, the workaround worked for me :)

I think this should be easy to fix in library though since this clearly shows where the issue is :)

awasilik avatar May 19 '24 12:05 awasilik

I think I found a workaround for this. We create a library where we have a Content Page that will animate using SKLottieView when consumers set a property for the page IsFinishedSaving.

The SKLottieView is created programatically, and we also noticed that the first time the app loads and the first time we display the animation, it does not appear. The second time we try to display the animation it does appear. I went through this repository, and found this line in the constructor of SKLottieView:

ResourceLoader<SKLottieViewResources>.EnsureRegistered((VisualElement) this);

From my reading, the ResourcesLoader basically makes sure that the SKLottieViewResources is added to the apps resources. I somehow figured out that this has to be some kind of race condition where it might not have been loaded successfully when we try to display the animation the first time you try to create it. The second time you display it its already merged and it works as expected.

The workaround is to merge this resource to your resources manually in your App.xaml:

<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:themes="clr-namespace:SkiaSharp.Extended.UI.Controls.Themes;assembly=SkiaSharp.Extended.UI"
    ...>
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <themes:SKLottieViewResources/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

Let me know if this did not work, we did not inherit directly from SKLottieView in our component. So there might be differences in how we programatically use SKLottieView if it does not work for you.

Great workaround, thanks so much.

In our case, we were programmatically creating the SKLottieView before the Window was set, causing that EnsureRegistered to fail.

Thanks again!

bradencohen avatar Aug 02 '24 18:08 bradencohen