Building cool animated tiles with Rx

Posted on Nov 15, 2011 in and tagged ,

I have a small WP7 project called Summarize. It is a fun math-based game (check it out for free).

It has a nice effect of loading tiles. Several people has asked me how I have done it, so in this post I will cover how to build something similar on your own.

Let’s start with building the UI. We have the toolkit’s WrapPanel inside the ItemsControl and a simple TileControl with TextBlock displaying the tile number.

TileControl.xaml markup:

<UserControl 
    x:Name="UserControl"
    x:Class="AnimatedTilesSample.TileControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    Loaded="OnLoaded"
    Width="76" Height="76">
    
    <UserControl.Projection>
        <PlaneProjection RotationY="-180"/>
    </UserControl.Projection>
    
    <UserControl.Resources>
        <Storyboard x:Key="LoadedStoryboard">
            <DoubleAnimation 
                Storyboard.TargetProperty="(UIElement.Projection).(PlaneProjection.RotationY)"
                Storyboard.TargetName="UserControl"
                To="0"
                Duration="0:0:0.2"/>
            <ObjectAnimationUsingKeyFrames
                Storyboard.TargetProperty="UIElement.Visibility"
                Storyboard.TargetName="Text">
                <DiscreteObjectKeyFrame 
                    KeyTime="0:0:0.1">
                    <DiscreteObjectKeyFrame.Value>
                        <Visibility>Visible</Visibility>
                    </DiscreteObjectKeyFrame.Value>
                </DiscreteObjectKeyFrame>
            </ObjectAnimationUsingKeyFrames>
        </Storyboard>
    </UserControl.Resources>
    
    <Grid 
        x:Name="LayoutRoot"
        Background="{StaticResource PhoneChromeBrush}">
        <TextBlock 
            x:Name="Text"
            HorizontalAlignment="Center"
            VerticalAlignment="Center"
            Visibility="Collapsed"
            Style="{StaticResource PhoneTextTitle2Style}"
            Text="{Binding ., Mode=OneTime}"/>
    </Grid>
</UserControl>

In the code-behind we start the animation when the tile is loaded:

private void OnLoaded(object sender, RoutedEventArgs e)
{
    var sb = Resources["LoadedStoryboard"] as Storyboard;
    if (sb != null)
    {
        sb.Begin();
    }
}

MainPage.xaml markup:

<phone:PhoneApplicationPage 
    x:Class="AnimatedTilesSample.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit"
    xmlns:local="clr-namespace:AnimatedTilesSample"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait" Orientation="Portrait" >
     <Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid x:Name="ContentPanel" Margin="12,0,12,0">
            <ItemsControl
                x:Name="Cells">
                <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <toolkit:WrapPanel
                            Orientation="Horizontal"/>
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <local:TileControl Margin="2"/>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
            
            <Button
                HorizontalAlignment="Left"
                VerticalAlignment="Bottom"
                x:Name="PlayAnimationButton"
                Content="Play animation"
                Click="PlayAnimationHandler"/>
        </Grid>
    </Grid>
 
</phone:PhoneApplicationPage>

Clicking the button generates the observable sequence producing values every 80 milliseconds. We use only the first 25 values and when the every next value is available, the appropriate tile is created and added to the list. Adding the tile starts its ‘loaded’ animation.

private void PlayAnimationHandler(object sender, RoutedEventArgs e)
{
    Cells.Items.Clear();
    PlayAnimationButton.IsEnabled = false;


    Observable.Interval(TimeSpan.FromMilliseconds(80))
        .Take(25)
        .ObserveOnDispatcher()
        .Finally(() =>
        {
            PlayAnimationButton.IsEnabled = true;
        })
        .Subscribe(x => Cells.Items.Add(x));
}

Because changes to ObservableCollection should occur only on the thread that it was created on, I used DispatcherObservable.ObserveOnDispatcher extension method that schedules all observer actions on the runtime dispatcher.

You can download sample app with sources here.

Leave a Reply

Your email address will not be published. Required fields are marked *