In a mobile application, we often face the challenge of fitting a large picture in a limited, fixed-size space. One of the common solutions is to generate a thumbnail, but depending on the ratio of the picture the result will not necessarily meet the quality standards. In my case, I decided to try an alternative approach and mimic the built-in effect when pinning a picture as a secondary tile on Windows Phone, where the picture slowly scrolls from top to bottom (Ken Burns effect).
There is no control on Windows Phone to produce this kind of effect, so I had to put together my own.
First step, creating the files. For that, I chose to use a usercontrol, as the templating is far easier. Inside, we use a Canvas as container, and put the Image control inside:
Nothing too fancy here. Note that I give a name to the UserControl (“Root”), to make things easier when referencing it in bindings. The “Width” property of the image is bound to the “ActualWidth” property of the UserControl. This way, the picture will fill the width of the container. The “Source” property is bound to a dependency property with the same name, declared in the code-behind of the UserControl:
Finally, we create a test page for our control, and explicitly set its height and width to 200 pixels:
When starting the application, the picture is correctly displayed, but we notice right away that the height of the picture is wrong, and is bigger than the requested 200 pixels:
The height of the control is actually good, but per default no clipping is applied and the picture is rendered outside of its bonds. To fix this, we need to use the “Clip” property, to explicitly set the bounds. We want the size of the clipping zone to change according to the size of the control (to handle orientation changes), so it’s easier to set it directly from the code-behind. We subscribe to the “SizeChanged” event, and set the clipping to the full size of the control:
When testing it, the image is clipped as expected:
Now all that’s left is the sliding animation.
This part is trickier than it sounds. Making a sliding animation isn’t all that difficult, however we have two more constraints to take care of:
- The pictures are of variable size. When the height of the picture is smaller than the height of the control, no animation must be done
- The size of the image isn’t known at compilation time, therefore we’re forced to decide whether to start the animation or not at runtime.
We start by adding a TranslateTransform to the Image control, then a Storyboard targeting the “Y” property of the transform:
The speed of an animation is calculated from the duration. I want the translation effect to go at the same speed no matter the size of the picture, so the duration must be set dynamically. Therefore, we’ll create the animations directly from the code-behind. To create the animation, we write a “SetStoryboard” method. First, we retrieve the storyboard from the control’s resources, and we make sure it’s stopped as it may be called multiple times (we’ll elaborate on this later). If we need to stop it, we also make sure to rewind it.
Then we make sure that the height of the picture is greater than the size of the control, in which case we populate the key frames of the animation and start the storyboard. The animation will have four phases:
- First, we display the top of the picture for three seconds
- Then we slide to the bottom, at a peak speed of 10 pixels per second, using an easing function to have an acceleration and deceleration
- We display the bottom of the picture for two seconds
- We quickly slide back up to the top of the picture in 500 milliseconds, and repeat the animation
The final method looks like:
Now when to call it? First, in the “ImageOpened” event of the Image control, that’s the moment we’ll know the height of the picture. Since a change in the width of the picture will impact the height of the picture (to maintain the aspect ratio), we must also change the timing of the animation in the “SizeChanged” event.
At this point, we have a working animation:
Last but not least, what happens if I want the height to be set automatically? For instance, I want my control to use 200 pixels on the screen. If the picture is bigger, only 200 pixels should be displayed, and the animation is used to show the hidden parts of the picture. If the picture is smaller, the control is resized to fit it.
The perfect candidate is the “MaxHeight” property. Let’s change our test page to set the maximum height of the control, and display some text underneath to test the bounds:
When testing it, the image disappears completely. Why?
The problem is: we’ve set no default height and the control has no logic to resize itself. To fix this, we need to override the “MeasureOverride” method. It is called by the parent container (in our test page, the StackPanel) to tell the control how much space is available, and the control must answer with the amount of space it would like to use. In our case, we’ll fill all the width. For the height, we’ll take the minimum between the height of the picture and the available height:
Now, since the picture’s size is only known after loading, we need to manually call the “InvalidateMeasure” method. It’ll tell the container that the control’s size should be re-computed. We call it at two places:
- In the “ImageOpened” event, since that’s the moment when we know the size of the picture
- When the “Source” dependency property is set, to reset the size of the control if the source (and therefore, the displayed picture) is changed at some point
The final code looks like: