[Windows (Phone)8] How to animate the apparition of ListBoxItems ?

January 6th, 2014 | Posted by Tom in .NET | Article | Windows 8 | Windows Phone | Read: 2,085

On Silverlight/Windows Phone, the FluidMoveBehavior can be used to animate the items of a ListBox when user adds or removes an item. But the behavior has no effect when the items are loaded and bound to the Listbox.

Imagine the following C# code corresponding to our ViewModel:

private ObservableCollection<Category> _categories;

public ObservableCollection<Category> Categories
{
    get
    {
        return this._categories;
    }
    set
    {
        this.Set(() => this.Categories, ref this._categories, value);
    }
}

public override async Task OnNavigatedTo(NavigationEventArgs navigationEventArgs, NavigationContext navigationContext)
{
    this.IsBusy = true;

    if (App.Categories == null)
    {
        App.Categories = await ClientServices.ClientServicesLocator.CategoriesService.GetAllCategoriesAsync();
    }

    this.Categories = new ObservableCollection<Category>(App.Categories);

    this.IsBusy = false;
}

And the associated XAML code:

<ListBox x:Name="CategoriesListBox" ItemsSource="{Binding Categories}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Name}"
                       Style="{StaticResource PhoneTextTitle2Style}"
                       FontSize="40"
                       controls:TiltEffect.IsTiltEnabled="True"
                       x:Name="CategoryNameTextBlock" />
        </DataTemplate>
    </ListBox.ItemTemplate>
    <ListBox.ItemsPanel>
        <ItemsPanelTemplate>
            <StackPanel Orientation="Vertical" />
        </ItemsPanelTemplate>
    </ListBox.ItemsPanel>
</ListBox>

If you apply the FluidMoveBehavior to the StackPanel of the ListBox and go to the page, you’ll see that the items are all loaded on the same time, with no effect.

To change this, we can replace the ViewModel code with this one:

public Action RefreshCategories { get; set; }

public override async Task OnNavigatedTo(NavigationEventArgs navigationEventArgs, NavigationContext navigationContext)
{
    this.IsBusy = true;

    if (App.Categories == null)
    {
        App.Categories = await ClientServices.ClientServicesLocator.CategoriesService.GetAllCategoriesAsync();
    }

    this.RefreshCategories();
}

As you can see, we no longer use a property of the ViewModel to bind it to the view: we just have an Action property that is raised once the data are loaded. In the code-behind of the page, here is how we use the Action property:

public CategoriesPage()
{
    InitializeComponent();

    this._viewModel = this.DataContext as CategoriesPageViewModel;
    if (this._viewModel != null)
    {
        this._viewModel.RefreshCategories = async () =>
        {
            this.CategoriesListBox.Items.Clear();

            foreach (var category in App.Categories)
            {
                this.CategoriesListBox.Items.Add(category);

                await Task.Delay(100);
            }

            this._viewModel.IsBusy = false;
        };
    }
}

So once the action is raised, we first starts by clearing the items on the ListBox then, for each items we want to add, we add it to the Items property of the control. Then, we make a small pause using Task.Delay, to give some time to the UI to be refreshed!

To get a more clean result, it’s also possible to modify the DataTemplate:

<DataTemplate>
    <TextBlock Text="{Binding Name}"
               Style="{StaticResource PhoneTextTitle2Style}"
               FontSize="40"
               controls:TiltEffect.IsTiltEnabled="True"
               x:Name="CategoryNameTextBlock">
        <TextBlock.Resources>
            <EventTrigger x:Name="LoadEventTrigger"
                          RoutedEvent="Canvas.Loaded">
                <BeginStoryboard>
                    <Storyboard x:Name="FadeIn">
                        <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)"
                                                       Storyboard.TargetName="CategoryNameTextBlock">
                            <EasingDoubleKeyFrame KeyTime="0" Value="0.01"/>
                            <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="1"/>
                        </DoubleAnimationUsingKeyFrames>
                    </Storyboard>
                </BeginStoryboard>
            </EventTrigger>
        </TextBlock.Resources>
    </TextBlock>
</DataTemplate>

When the item is loaded, an animation is raised to change its opacity from 0 to 1. The result of the code can be seen on the first page of the following video (the animation on the second page will be covered in another post):

 

Happy coding!

You can follow any responses to this entry through the RSS 2.0 You can leave a response, or trackback.

One Response

Add Comment Register



Leave a Reply