Part 32: Animating Image Search Results
In this lesson we'll refine our search results page by animating the images as they are loaded from Flickr. We want the images to ease in, fade in to view. We also want to provide feedback to the user of the app for long running operations like retrieving these photos over a slow internet connection by adding a progress indicator. And finally, I want to add some feedback in case the user searches their current location and topics and Flickr has no results to return ... we don't want to leave the user in the dark waiting for photos to appear that never come.
So, our game plan in this lesson:
- We'll modify our LongListMultiSelector's DataTemplate -- we'll prepare the Image control for the fade in effect we'll implement
- We'll create an animation and storyboard targeting the image control to enable the fade in effect
- We'll make provisions for the possibility that there are no photos on Flickr that match our search criteria by creating and hiding a Text block that we'll un-hide when there are no photos to show
- We'll add a progress indicator that will run while we're performing the web service call to Flickr and loading the images
1. Prepare the Image control for a fade-in animation
The first step we'll take is to set the initial Opacity attribute of the Image to 0, indicating that we want to hide the contents of the image. 0 is completely transparent and 1 is completely opaque. 0.5 would be partially transparent / opaque.
Then, for each image in our DataTemplate, as that particular image has downloaded from Flickr, the ImageOpened event will fire. We'll handle that event and write code to animate the change of the Opacity attribute.
So, we'll make the following changes to the Image control:
- Add an Opacity attribute, set it to 0
- Add an ImageOpened event handler attribute and wire it up to a new method called "Image_ImageOpenend", which we'll implement in the next step.
Right-click anywhere on that line of code with the ImageOpened attribute or it's setting, select "Navigate to Event Handler" from the context menu.
2. Implement the Image_ImageOpened Event Handler
Here we'll write C# code to animate the fade in effect. Many examples of animation you'll see use XAML to define both the Storyboard and Animations associated with the storyboard, then use C# code to kick off the animation when a certain event is triggered. We could do it that way, but we've chosen to do it all with C# because we're dealing with a special situation (more about that in a moment).
Animations are made up of a Storyboard object and at least one Animation object.
Let's start with the Animation. First, there are several different types of animation data types. We're using a DoubleAnimation data type because we want to move a property from 0.0 to 1.0. There's also a ColorAnimation class for animating between two colors, and a PointAnimation for modifying an objects X Y coordinate or it's size.
An Animation is basically a timeline combined with a result. The results are determined by the specific Animation class you pick like we just talked about. It's important to know that all Animation classes inherit from a Timeline class which confers properties related to timing of the animation ... the Begin time, allowing you to delay the start of animation, perhaps waiting for other animations on the storyboard to begin or complete, a Duration property that effects how long the animation should take before delivering the desired result -- how long should it take for our fade in effect, in this case. There's also AutoReverse and RepeatBehavior properties which do what they suggest.
A Storyboard is a collection of one or more Animations. You group the Animations you want triggered by a specific event, like a button click, a loaded event and so on. The Storyboard allows you to pair an Animation with a target object. The animation is just a definition of what property should be affected, when it should be affected and how long. We have to APPLY that Animation to a target object. In our case, that target object will be an Image control.
In our case, our Storyboard is simple ... we just want one thing to happen. We could add multiple Animations targeting multiple properties of multiple objects, and could even create child Storyboards to better refine how our user's experience will play out. Once we're ready to allow the Storyboard to play, we call its Begin() method. It will in turn kick off all it's child Animations and child Storyboards.
For a more complete explanation of animations, I'd recommend you start here:
Back to our situation ... we need to animate each image as it's opened. We'll create an Animation to move the Opacity property from 0 to 1 thereby making it transform from completely transparent into completely opaque. We'll add the Animation to a Storyboard and then call Begin() on the Storyboard.
Sometimes the animation is subtle to the untrained eye. To make the effect more dramatic, you can stretch the time from 500 milliseconds to something like 2000 or 3000 milliseconds, or rather, 2 or 3 seconds just for testing purposes.
Lines 74 and 75 use a SetTarget and SetTargetProperty syntax to pair up the animation and its target. You see a lot of Set___ and Get___ styled methods in the Windows Phone API, and that's due to the Windows Phone property system we talked about at the very outset of this series.
The Windows Phone property system allows us to use attached properties. If lines 68 through 75 were in XAML, it might look something like this:
To="1.0" Duration="0:0:0.5" />
(Now, this approach is actually flawed because we would be targeting a single Image control. And as far as I know, you can't use a binding expression inside of the Storyboard.TargetName to make it dynamically apply to every image control in our data template, so this is one reason why a C# approach works better.)
I wanted to show the XAML version (flawed though it is) to illustrate what we're doing in our C# code ... in lines 74 and 75 we're ATTACHING the Storyboard.SetTarget and Storyboard.SetTargetProperty attached properties TO the DoubleAnimation object doubleAni, and setting their values appropriately. It looks like we're setting attributes of the Storyboard, but we're ATTACHING attached properties TO the DoubleAnimation.
One more curiosity about line 75. We're setting the Storyboard.TargetProperty to a new PropertyPath(OpacityProperty) ... what does this mean?
The rules of the Windows Phone property system require the target property of an animation must be set to a Dependency Property. Remember: that's one of the special powers of a Dependency Property -- that it can be animated, unlike regular old CLR properties. Fine, then why do we have to wrap it with the new PropertyPath object. That's difficult for me to explain, and for the sake of brevity let me point you to another article that can explain it with a good example:
PropertyPath XAML Syntax
Specifically, this link points to the anchor "PropertyPath for Animation Targets"
In a nutshell, the PropertyPath is used to specify the property that is the target of an animation. We have a simple case, and so for now, I think it's easier just to understand that the PropertyPath object provides a map to find the dependency property we want to animate.
Let's make sure our images fade in by debugging the app:
Mine works, and did you notice the half-second fade in? Very nice.
We've satisfied the topic of this lesson, but I wanted to add a few more features to our app while we're here. We'll provide feedback to the user to let them know the status of their searches against the Flickr API.
3. Add a TextBlock for "No Photos Found", a Progress Bar and TextBlock for "Loading"
Now, lets edit the MainPage.xaml again, sandwiching the LongListMultiSelector with two new passages of XAML:
- We create a TextBlock element. A TextBlock is a simple XAML element for displaying small amounts of flow content. By "flow content" I mean textual content that dynamically adjusts and reflows the text content based on run-time variables such as window size, device resolution and other user preferences. There's quite a bit to Flow Documents in XAML ... I would point you to this resource for more information: https://msdn.microsoft.com/en-us/library/aa970909.aspx
At any rate, the TextBlock is just a way to put some text on our page.
We style it using a built-in Text Style: https://msdn.microsoft.com/en-us/library/windowsphone/develop/ff769552(v=vs.105).aspx#BKMK_TextStyles and most importantly: we set it's Visibility attribute to Collapsed meaning we want it hidden by default. We'll write some logic that will potentially change that in a moment.
- Add a StackPanel that contains another TextBlock. It also has a ProgressBar element with an IsIndeterminate="True" set, meaning we just want it to keep animating until we're finished with it. We'll toggle this property on and off as we need it. Most importantly, we set the Visibility of the StackPanel to "Collapsed" ... here again, we'll write some logic that will toggle the Visibility as we're performing the web service call in the background.
Next, in the SearchResults_Loaded event handler, we'll write the logic for both code passages (above) that I just alluded to.
We added code before and after our web services call to Flickr to show and hide the StackPanel's contents, as well as toggle the ProgressBar's animation.
- Here we show the StackPanel we added (named Overlay). Also, we turn on the ProgressBar's animation by setting the IsIndeterminate = true.
- In lines 47 through 50, we decide to toggle the Visibility of the TextBlock to reveal the run stating "No Photos found " in, in fact, no photos were returned from the Flickr web service call.
In lines 52 and 53, we reverse what we did in lines 34 and 35 ... we toggle the Visibility back to Collapsed to hide it, then set IsIndeterminate = false to stop the animation.
I'll test both scenarios. First, I'll search for some non-sensical term:
Since it takes a moment to run the query, depending on the speed of your internet connection, you should briefly see the "Loading ..." text and the animated progress bar. Ultimately, we want to see the results:
And it works!
Just to recap, the big take away from this lesson is animation in the Windows Phone API. We create an Animation object to control the change of a dependency property over time, and pair that Animation object with a dependency object and add that pairing to a Storyboard. These little touches, almost imperceptible, make our apps stand out from the competition and earn the respect of our users.
We also added feedback to the user about the state of the app while it's making that long call to the Flickr API with a Progress Bar and a message, and let the user know when there were no Flickr images that matched their criteria. Again, we're trying to consider this from a user's perspective ... I've used plenty of apps where I wasn't sure if the app was still working or it had locked up. These little touches help the user gain confidence in the app and enhance their emotional ties to it.