次の方法で共有


Animating using the DispatcherTimer class

[This post is a part of a series of post about the Social Media Dashboard Sample. This post was written by Simon Jäger. For an introductory blog post, click here]

Users love animations - it’s a great and a not very time consuming way of introducing more personality into your projects! Using C#/XAML we have been provided with a bunch of different ways of creating liveliness in our apps and games.

Blend for Visual Studio and XAML Storyboards (https://msdn.microsoft.com/en-us/library/ms742868.aspx) are powerful and probably the most well-known approaches for this task.

In this post we’ll be taking a look at using the DispatcherTimer class to produce animations.

The DispatcherTimer class is ideal for our purposes as it is built into the Dispatcher queue with the other work items and operations. Obviously , if there are operations taking too long in the queue – the timer will also have to hold resulting in the animation taking longer than expected.

The great upside with the DispatcherTimer class is that we can run our animation logic directly in the DispatcherTimer.Tick event (https://msdn.microsoft.com/en-us/library/windows/apps/windows.ui.xaml.dispatchertimer.tick.aspx) instead of having to put the logic into the Dispatcher of the UI thread in order to invoke it.

In the Social Media Dashboard Sample we want to have the existing tiles in the main page to fade into plain colors with a label on it. To achieve this, the first thing that we need to do is to dig into our existing XAML data templates and add the controls that we wish to animate.

 <DataTemplate x:Key="BigSpotlightTileItemTemplate">
    <Grid HorizontalAlignment="Left" Width="360" Height="360">
        <Border Background="{StaticResource ListViewItemPlaceholderBackgroundThemeBrush}">
            <Image Source="{Binding Path=HQImage}" 
                    Stretch="UniformToFill"
                    VerticalAlignment="Center"
                    HorizontalAlignment="Center"
                    AutomationProperties.Name="{Binding Title}"
                    Margin="0,-64,0,0"/>
        </Border>
        <Border Background="{StaticResource AppDarkColor}"
                VerticalAlignment="Stretch"
                HorizontalAlignment="Stretch"
                Opacity="{Binding OverlayOpacity}"/>
        <TextBlock Text="{Binding TileLabel}"
                    FontFamily="{StaticResource AppFontFamily}"
                    Margin="5,5,40,5"
                    TextWrapping="Wrap"
                    Foreground="White"
                    FontWeight="Bold"
                    VerticalAlignment="Top"
                    HorizontalAlignment="Stretch"
                    Opacity="{Binding LabelOpacity}" />
    </Grid>
</DataTemplate>

The new border element will be overlaying the existing content with its background – which is what we want. The TextBlock element will be on top of the new border control displaying a few lines of wrapped text.

We also need to make sure that the bound XAML properties for the new elements have the corresponding properties in the existing view model, SpotlightFlickrImageDataItem.cs.

 private string _tileLabel = string.Empty;
public string TileLabel
{
    get { return this._tileLabel; }
    set { this.SetProperty(ref this._tileLabel, value); }
}

private float overlayOpacity;
public float OverlayOpacity
{
    get { return this.overlayOpacity; }
    set { this.SetProperty(ref this.overlayOpacity, value); }
}

private float labelOpacity;
public float LabelOpacity
{
    get { return this.labelOpacity; }
    set { this.SetProperty(ref this.labelOpacity, value); }
}

Next up we need to add the DispatcherTimer object itself and add some additional properties to the view model class.

 public int Index { get; set; }
public DispatcherTimer DispatcherTimer { get; set; }
public TimeSpan Timer { get; set; }
public TileStates TileState { get; set; }

The enumeration is built up according to the following:

 public enum TileStates
{ 
    OverlayFadeIn, 
    Idle, 
    OverlayFadeOut
}

To continue on, we have to initialize the default values for the properties and hook up the Tick event on the DispatcherTimer. The constructor will be a good place for this task.

The DispatcherTimer is given an interval of 16 milliseconds, meaning the DispatcherTimer will try (potential delays described earlier in this post) to fire the Tick event approximately 60 times per second. Finally we tell the DispatcherTimer to start by calling the Start method.

 public SpotlightFlickrImageDataItem(int index)
{
    Index = index;
    TileState = TileStates.OverlayFadeIn;
    OverlayOpacity = 0f;
    LabelOpacity = 0f;
    Timer = new TimeSpan(0, 0, 0, 0, (new Random((int)DateTime.Now.Ticks * index).Next(0, 20000)));
    DispatcherTimer = new DispatcherTimer { Interval = new TimeSpan(0, 0, 0, 0, 16) };
    DispatcherTimer.Tick += DispatcherTimerTick;
    DispatcherTimer.Start();
}

In the Tick event handler we build our animation logic and modify the appropriate properties to make the XAML elements react as we desire.

 private void DispatcherTimerTick(object sender, object e)
{
    if (Timer.TotalMilliseconds > 0)
        Timer -= DispatcherTimer.Interval;
    else
    {
        if (string.IsNullOrEmpty(TileLabel))
            DispatcherTimer.Stop();
        if (TileState == TileStates.OverlayFadeIn)
        {
            if (LabelOpacity <= 1f)
            {
                LabelOpacity += 0.05f;
                if (LabelOpacity >= 1f)
                {
                    Timer = new TimeSpan(0, 0, 0, 0, (new Random((int)DateTime.Now.Ticks * Index).Next(1000, 3000)));
                    TileState = TileStates.Idle;
                }
            }
        }
        else if (TileState == TileStates.Idle)
            TileState = TileStates.OverlayFadeOut;
        else if (TileState == TileStates.OverlayFadeOut)
        {
            if (LabelOpacity >= 0f)
            {
                LabelOpacity -= 0.05f;
                if (LabelOpacity <= 0f)
                {
                    Timer = new TimeSpan(0, 0, 0, 0, (new Random((int)DateTime.Now.Ticks * Index).Next(0, 20000)));
                    TileState = TileStates.OverlayFadeIn;
                }
            }
        }
    }
    OverlayOpacity = LabelOpacity * 0.85f;
}

Voila! By using timers to build our animations, we get a better understanding of how animations can be built up from the core – this approach is what the XAML Storyboards is using more or less under the hood.

The XAML Storyboards are powerful objects, but as you increase the complexity of your animations you might want to consider using this technique. As it provides a large amount of flexibility and freedom to make finely tuned adjustments down to the core.

image