Home > database >  How to run control animation inside Template by ViewModel property change
How to run control animation inside Template by ViewModel property change

Time:10-27

.Net Maui page with CarouselView and list of cards created via data binding to the collection of items in ViewModel (VM). I'm looking for ways to animate control inside CarouselView by some property in VM set to a particular value. Animation should be done in c# code (code-behind, trigger action, behavior etc.), not by xaml. Not sure how to properly implement that. This is what I considered as possible solutions:

  • Declare event in VM and subscribe for it in code-behind. Works very well for non-template controls, but with CarouselView which consists of collection Card controls described inside DataTemplate I need to find that particular active control only, let's say Label that I want to animate. Not sure how to find it, there are one instance of it per each item in VM collection, but even if I do it does not look to be a good MVVM oriented design.

  • My big hope was on TriggerAction<Label> (given I want to animate Label), but then problem is TriggerAction seems to only work inside EventTrigger which only catches xaml control events, not VM events. What can catch VM events and property changes is DataTrigger, but it does not allow to have TriggerAction<T> declared inside on the other hand. Not sure why there is such limitation in .Net Maui, I wish I had some mix of both.

  • Behaviors, - as with triggers not sure how to run them by any property change or event declared in VM

// ViewModel (mvvm community toolkit is used):
    public partial class CollectionOfItems : ObservableObject
    {
       public Item[] Items {get; set;}
    }
    
    public partial class Item : ObservableObject
    {
       [ObservableProperty]
       public string _name;
    
       // Setting this to false should trigger Label animation
       [ObservableProperty]
       public bool _isInvalid;
    }
    ...
    <CarouselView ItemsSource="{Binding Items}">
       <CarouselView.ItemTemplate>
         <DataTemplate>
            <Label Text="{Binding Name}">
                   <Label.Triggers>
                      <DataTrigger TargetType="Label" Binding="{Binding IsInvalid}" Value="True">
<!-- I wish I was able to trigger InvalidDataTriggerAction from here, but it's not compatible with DataTrigger -->
                      </DataTrigger>
                   </Label.Triggers>
            </Label>
         </DataTemplate>
       </CarouselView.ItemTemplate>
    </CarouselView>
    ...
    // Animation should be implemented in c#, not XAML
    // Should be triggered by IsInvalid=false, but I do not know how:   
    public class InvalidDataTriggerAction : TriggerAction<Label>
    {
        protected override void Invoke(Label label)
        {
            label.TranslateTo(-30, 0, 100);
            label.TranslateTo(60, 0, 100);
            label.TranslateTo(-30, 0, 100);
        }
    }

CodePudding user response:

Just like you said, the control in the datatemplate is hard to access. So I have done a sample to test the TranslateX property of the Label. And I found that label.TranslateTo(60, 0, 100); had the same effect as the label.TranslateX = 60.

According to this, you can create a variable in the Item and binding it to the label in the DataTemplate. And change the item's value in the page.cs. And you can also use the DataTrigger to set the value of label.TranslateX.

CodePudding user response:

Alright, I've found a way to do it through bindable properties inside behaviors. Happened to be bit more complicated than I expected, but it works. Unfortunately .NET Maui does not provide a better more intuitive way to do that, I guess it's the area for improvement.

Here's the code:

namespace View.Behaviors;

public class AnimateWrongAnswerBehavior : BaseBehavior<VisualElement>
{
    public static readonly BindableProperty ShouldAnimateProperty =
        BindableProperty.CreateAttached(
            "ShouldAnimate",
            typeof(bool),
            typeof(AnimateWrongAnswerBehavior),
            false,
            propertyChanged: OnShouldAnimateChanged);

    public static void SetShouldAnimate(BindableObject view, VisualElement value) =>
        view.SetValue(ShouldAnimateProperty, value);

    static async void OnShouldAnimateChanged(BindableObject bindable, object oldValue, object newValue)
    {
        if ((bool)newValue)
        {
            await Animate((VisualElement)bindable);
        }
    }

    /// <summary>
    /// Implemenation of Animation logic
    /// </summary>  
    static private async Task Animate(VisualElement elementToAnimate)
    {
        await elementToAnimate.TranslateTo(-30, 0, 100);
        await elementToAnimate.TranslateTo(60, 0, 100);
        await elementToAnimate.TranslateTo(-30, 0, 100);
    }
}

Then you can bind animation to any visual element like Frame:

<ContentPage 
   xmlns:bh="clr-namespace:View.Behaviors" ...>

    <Frame bh:AnimateWrongAnswerBehavior.ShouldAnimate="{Binding IsInvalid}">

</ContentPage>
  • Related