Thanks to Kévin Gosse @KooKiz for tweaking a part of the code to get it right 🙂
So, sometimes you want to try something different on Windows Phone and this time I wanted to tweak the scroll animation of a page.
The setup I was looking for visually: You have a content page ( for example in a news app ) that shows a nice article with an image and a title caption on top. But as you can see the text of the article is too large, so when the user scrolls down to view the text, we want to keep the header title in view! ( or something else depending on your taste )
You would think this would be easy, but it alas! Again we are faced with a stripped down XAML version in Windows Phone, so we don’t have the same ‘control’ over these events as in WPF.
Now with some small tricks, it’s possible to get a decent working version, so let’s get to it!
Just create a new Windows Phone app in Visual Studio, when doing so you’ll get a MainPage.xaml right away.
We are going to change the page, so delete everything inside the LayoutRoot grid and replace it with following xaml
1 |
<span id="lnum1" style="color: #606060"> 1:</span> <Grid.RowDefinitions> |
1 |
<span id="lnum2" style="color: #606060"> 2:</span> <RowDefinition Height=<span style="color: #006080">"Auto"</span>/> |
1 |
<span id="lnum3" style="color: #606060"> 3:</span> <RowDefinition Height=<span style="color: #006080">"*"</span>/> |
1 |
<span id="lnum4" style="color: #606060"> 4:</span> </Grid.RowDefinitions> |
1 |
<span id="lnum5" style="color: #606060"> 5:</span>  |
1 |
<span id="lnum6" style="color: #606060"> 6:</span> <!--TitlePanel contains the name of the application and page title--> |
1 |
<span id="lnum7" style="color: #606060"> 7:</span> <StackPanel x:Name=<span style="color: #006080">"TitlePanel"</span> Grid.Row=<span style="color: #006080">"0"</span> Margin=<span style="color: #006080">"12,17,0,12"</span>> |
1 |
<span id="lnum8" style="color: #606060"> 8:</span> <TextBlock Text=<span style="color: #006080">"FIXED TITLE DEMO"</span> Style=<span style="color: #006080">"{StaticResource PhoneTextNormalStyle}"</span> Margin=<span style="color: #006080">"12,0"</span>/> |
1 |
<span id="lnum9" style="color: #606060"> 9:</span> <TextBlock Name=<span style="color: #006080">"Values"</span> /> |
1 |
<span id="lnum10" style="color: #606060"> 10:</span> </StackPanel> |
1 |
<span id="lnum11" style="color: #606060"> 11:</span>  |
1 |
<span id="lnum12" style="color: #606060"> 12:</span> <!--ContentPanel - place additional content here--> |
1 |
<span id="lnum13" style="color: #606060"> 13:</span> <Grid x:Name=<span style="color: #006080">"ContentPanel"</span> Grid.Row=<span style="color: #006080">"1"</span> Margin=<span style="color: #006080">"0,0,0,0"</span>> |
1 |
<span id="lnum14" style="color: #606060"> 14:</span> <ScrollViewer Name=<span style="color: #006080">"ScrollViewer"</span> |
1 |
<span id="lnum15" style="color: #606060"> 15:</span> Margin=<span style="color: #006080">"0,0,0,12"</span> |
1 |
<span id="lnum16" style="color: #606060"> 16:</span> VerticalAlignment=<span style="color: #006080">"Top"</span> |
1 |
<span id="lnum17" style="color: #606060"> 17:</span> VerticalScrollBarVisibility=<span style="color: #006080">"Visible"</span> |
1 |
<span id="lnum18" style="color: #606060"> 18:</span> ManipulationMode=<span style="color: #006080">"Control"</span> |
1 |
<span id="lnum19" style="color: #606060"> 19:</span> MouseMove=<span style="color: #006080">"ScrollViewer_MouseMove"</span> |
1 |
<span id="lnum20" style="color: #606060"> 20:</span> MouseLeftButtonUp=<span style="color: #006080">"ScrollViewer_MouseLeftButtonUp"</span>> |
1 |
<span id="lnum21" style="color: #606060"> 21:</span> <Grid> |
1 |
<span id="lnum22" style="color: #606060"> 22:</span> <Grid.RowDefinitions> |
1 |
<span id="lnum23" style="color: #606060"> 23:</span> <RowDefinition Height=<span style="color: #006080">"Auto"</span> /> |
1 |
<span id="lnum24" style="color: #606060"> 24:</span> <RowDefinition Height=<span style="color: #006080">"*"</span> /> |
1 |
<span id="lnum25" style="color: #606060"> 25:</span> </Grid.RowDefinitions> |
1 |
<span id="lnum26" style="color: #606060"> 26:</span> |
1 |
<span id="lnum27" style="color: #606060"> 27:</span> <Image Name=<span style="color: #006080">"Image"</span> |
1 |
<span id="lnum28" style="color: #606060"> 28:</span> Source=<span style="color: #006080">"/Assets/Images/bear.jpg"</span> |
1 |
<span id="lnum29" style="color: #606060"> 29:</span> Grid.Row=<span style="color: #006080">"0"</span> |
1 |
<span id="lnum30" style="color: #606060"> 30:</span> /> |
1 |
<span id="lnum31" style="color: #606060"> 31:</span>  |
1 |
<span id="lnum32" style="color: #606060"> 32:</span> <RichTextBox Name=<span style="color: #006080">"Article"</span> |
1 |
<span id="lnum33" style="color: #606060"> 33:</span> Grid.Row=<span style="color: #006080">"1"</span> |
1 |
<span id="lnum34" style="color: #606060"> 34:</span> Margin=<span style="color: #006080">"12,12,12,0"</span>> |
1 |
<span id="lnum35" style="color: #606060"> 35:</span> <Paragraph> |
1 |
<span id="lnum36" style="color: #606060"> 36:</span> <Run FontWeight=<span style="color: #006080">"Bold"</span> FontSize=<span style="color: #006080">"22"</span>>Never heard before, but a bear was found <span style="color: #0000ff">in</span> the wild!</Run> |
1 |
<span id="lnum37" style="color: #606060"> 37:</span> </Paragraph> |
1 |
<span id="lnum38" style="color: #606060"> 38:</span> <Paragraph> |
1 |
<span id="lnum39" style="color: #606060"> 39:</span> <LineBreak /> |
1 |
<span id="lnum40" style="color: #606060"> 40:</span> <Run> |
1 |
<span id="lnum41" style="color: #606060"> 41:</span> Bears are mammals of the family Ursidae. Bears are classified <span style="color: #0000ff">as</span> caniforms, or doglike carnivorans, with the pinnipeds being their closest living relatives. Although only eight species of bears are extant, they are widespread, appearing <span style="color: #0000ff">in</span> a wide variety of habitats throughout the Northern Hemisphere and partially <span style="color: #0000ff">in</span> the Southern Hemisphere. Bears are found on the continents of North America, Central America, South America, Europe, and Asia. |
1 |
<span id="lnum42" style="color: #606060"> 42:</span> Common characteristics of modern bears include large bodies with stocky legs, <span style="color: #0000ff">long</span> snouts, shaggy hair, plantigrade paws with five nonretractile claws, and <span style="color: #0000ff">short</span> tails. While the polar bear <span style="color: #0000ff">is</span> mostly carnivorous and the giant panda feeds almost entirely on bamboo, the remaining six species are omnivorous, with varied diets. |
1 |
<span id="lnum43" style="color: #606060"> 43:</span> With the exceptions of courting individuals and mothers with their young, bears are typically solitary animals. They are generally diurnal, but may be active during the night (nocturnal) or twilight (crepuscular), particularly around humans. Bears are aided by an excellent sense of smell, and despite their heavy build and awkward gait, they can run quickly and are adept climbers and swimmers. In autumn, some bear species forage large amounts of fermented fruits, which affects their behaviour.[1] Bears use shelters, such <span style="color: #0000ff">as</span> caves and burrows, <span style="color: #0000ff">as</span> their dens; most species occupy their dens during the winter <span style="color: #0000ff">for</span> a <span style="color: #0000ff">long</span> period (up to 100 days) of sleep similar to hibernation.[2] |
1 |
<span id="lnum44" style="color: #606060"> 44:</span> Bears have been hunted since prehistoric times <span style="color: #0000ff">for</span> their meat and fur. With their tremendous physical presence and charisma, they play a prominent role <span style="color: #0000ff">in</span> the arts, mythology, and other cultural aspects of various human societies. In modern times, the bears' existence has been pressured through the encroachment on their habitats and the illegal trade of bears and bear parts, including the Asian bile bear market. The IUCN lists six bear species <span style="color: #0000ff">as</span> vulnerable or endangered, and even least concern species, such <span style="color: #0000ff">as</span> the brown bear, are at risk of extirpation <span style="color: #0000ff">in</span> certain countries. The poaching and international trade of these most threatened populations are prohibited, but still ongoing. |
1 |
<span id="lnum45" style="color: #606060"> 45:</span> </Run> |
1 |
<span id="lnum46" style="color: #606060"> 46:</span> </Paragraph> |
1 |
<span id="lnum47" style="color: #606060"> 47:</span> </RichTextBox> |
1 |
<span id="lnum48" style="color: #606060"> 48:</span> </Grid> |
1 |
<span id="lnum49" style="color: #606060"> 49:</span> </ScrollViewer> |
1 |
<span id="lnum50" style="color: #606060"> 50:</span>  |
1 |
<span id="lnum51" style="color: #606060"> 51:</span> <Border Name=<span style="color: #006080">"TitleBorder"</span> |
1 |
<span id="lnum52" style="color: #606060"> 52:</span> Background=<span style="color: #006080">"#FF264778"</span> |
1 |
<span id="lnum53" style="color: #606060"> 53:</span> Margin=<span style="color: #006080">"0,216,0,0"</span> |
1 |
<span id="lnum54" style="color: #606060"> 54:</span> Height=<span style="color: #006080">"{Binding ElementName=TitleText, Path=Height}"</span> |
1 |
<span id="lnum55" style="color: #606060"> 55:</span> VerticalAlignment=<span style="color: #006080">"Top"</span>> |
1 |
<span id="lnum56" style="color: #606060"> 56:</span> <TextBlock Name=<span style="color: #006080">"TitleText"</span> |
1 |
<span id="lnum57" style="color: #606060"> 57:</span> Text=<span style="color: #006080">"Bear found in the wild!"</span> |
1 |
<span id="lnum58" style="color: #606060"> 58:</span> Foreground=<span style="color: #006080">"White"</span> |
1 |
<span id="lnum59" style="color: #606060"> 59:</span> Margin=<span style="color: #006080">"12"</span> |
1 |
<span id="lnum60" style="color: #606060"> 60:</span> /> |
1 |
<span id="lnum61" style="color: #606060"> 61:</span> </Border> |
1 |
<span id="lnum62" style="color: #606060"> 62:</span> </Grid> |
If you did this correctly ( also there should be a bear.jpg image inside the Assets/Images folder 😉 ) you’ll get a nice page representing the article!
You’ll notice the page has a scrollviewer that contains everything except the header title! The trick we are going to use, is manipulating the location of the header title ourselves.
Do note that we need to se the ManipulationMode of the ScrollViewer to Control to get a smooth UI effect! ( see xaml above ) Otherwise the UI won’t be notified enough with the scrollviewer scroll values – is a performance optimization that we now need to bypass.
To manipulate the location of the header title, we need to keep track of the scroll offset of our scrollbar. But the scrollviewer itself doesn’t give use enough info, the way to get this info is by hooking into the ValueChanged event of the VerticalScrollBar that is inside the ScrollViewer !
So first we need to get hold of this VerticalScrollBar, this is done by using the VisualTreeHelper on our ScrollViewer and hook onto the ValueChanged event.
1 |
<span id="lnum1" style="color: #606060"> 1:</span> _vBar = ((FrameworkElement)VisualTreeHelper.GetChild(ScrollViewer, 0)).FindName(<span style="color: #006080">"VerticalScrollBar"</span>) <span style="color: #0000ff">as</span> ScrollBar; |
1 |
<span id="lnum2" style="color: #606060"> 2:</span> _vBar.ValueChanged += _vBar_ValueChangedHandler; |
Inside our ValueChangedHandler we will animate our header title to the corresponding vertical offset of our ScrollBar, but we will stop animating when the vertical offset is higher than the top position of our header title when the page was launched!! Because if our vertical offset has reached that value this means that the header title will be positioned at the top of our page!
The top value of the header title is called _borderTop and is calculated at page load.
1 |
<span id="lnum1" style="color: #606060"> 1:</span> <span style="color: #0000ff">private</span> <span style="color: #0000ff">void</span> _vBar_ValueChangedHandler(<span style="color: #0000ff">object</span> sender, RoutedPropertyChangedEventArgs<<span style="color: #0000ff">double</span>> e) |
1 |
<span id="lnum2" style="color: #606060"> 2:</span> { |
1 |
<span id="lnum3" style="color: #606060"> 3:</span> <span style="color: #0000ff">if</span> (e.NewValue < _borderTop) |
1 |
<span id="lnum4" style="color: #606060"> 4:</span> { |
1 |
<span id="lnum5" style="color: #606060"> 5:</span> <span style="color: #0000ff">if</span> (e.NewValue >= 0) |
1 |
<span id="lnum6" style="color: #606060"> 6:</span> <span style="color: #0000ff">this</span>.TitleBorder.SetVerticalOffset(0 - e.NewValue); |
1 |
<span id="lnum7" style="color: #606060"> 7:</span> } |
1 |
<span id="lnum8" style="color: #606060"> 8:</span> <span style="color: #0000ff">else</span> |
1 |
<span id="lnum9" style="color: #606060"> 9:</span> <span style="color: #0000ff">this</span>.TitleBorder.SetVerticalOffset(0 - _borderTop); |
1 |
<span id="lnum10" style="color: #606060"> 10:</span> } |
With that in place all is looking great, except for one thing: what to do when the user compresses the ScrollViewer by dragging the content down when the page is at it’s initial position. This behaviour is sometimes used in listboxes to get that ‘pull to refresh effect’. When someone does this on our page, the header title will stay at it’s position while the rest is dragged down… not a nice effect indeed. Let’s fix this.
This can be solved by hooking into 2 other events of the ScrollViewer: MouseMove and MouseLeftButtonUp. MouseMove to detect the compression and MouseLeftButtonUp to know when the user stops the compression.
When the compression occurs, you’ll notice a positive TranslateY value on the CompositeTransform of the ScrollViewer content, we’ll use this to also animate our header title. When the compression is stopper, we check if we had a positive TranslateY value and if so, we reset the position of the header title.
1 |
<span id="lnum1" style="color: #606060"> 1:</span> <span style="color: #0000ff">private</span> <span style="color: #0000ff">void</span> ScrollViewer_MouseMove(<span style="color: #0000ff">object</span> sender, MouseEventArgs e) |
1 |
<span id="lnum2" style="color: #606060"> 2:</span> { |
1 |
<span id="lnum3" style="color: #606060"> 3:</span> UIElement scrollContent = (UIElement)<span style="color: #0000ff">this</span>.ScrollViewer.Content; |
1 |
<span id="lnum4" style="color: #606060"> 4:</span> CompositeTransform ct = scrollContent.RenderTransform <span style="color: #0000ff">as</span> CompositeTransform; |
1 |
<span id="lnum5" style="color: #606060"> 5:</span> <span style="color: #0000ff">if</span> (ct != <span style="color: #0000ff">null</span> && ct.TranslateY > 0) |
1 |
<span id="lnum6" style="color: #606060"> 6:</span> <span style="color: #0000ff">this</span>.TitleBorder.SetVerticalOffset(ct.TranslateY); |
1 |
<span id="lnum7" style="color: #606060"> 7:</span> } |
1 |
<span id="lnum8" style="color: #606060"> 8:</span>  |
1 |
<span id="lnum9" style="color: #606060"> 9:</span> <span style="color: #0000ff">private</span> <span style="color: #0000ff">void</span> ScrollViewer_MouseLeftButtonUp(<span style="color: #0000ff">object</span> sender, MouseButtonEventArgs e) |
1 |
<span id="lnum10" style="color: #606060"> 10:</span> { |
1 |
<span id="lnum11" style="color: #606060"> 11:</span> UIElement scrollContent = (UIElement)<span style="color: #0000ff">this</span>.ScrollViewer.Content; |
1 |
<span id="lnum12" style="color: #606060"> 12:</span> CompositeTransform ct = scrollContent.RenderTransform <span style="color: #0000ff">as</span> CompositeTransform; |
1 |
<span id="lnum13" style="color: #606060"> 13:</span> <span style="color: #0000ff">if</span> (ct != <span style="color: #0000ff">null</span>) |
1 |
<span id="lnum14" style="color: #606060"> 14:</span> { |
1 |
<span id="lnum15" style="color: #606060"> 15:</span> <span style="color: #0000ff">if</span>(ct.TranslateY > 0) |
1 |
<span id="lnum16" style="color: #606060"> 16:</span> <span style="color: #0000ff">this</span>.TitleBorder.SetVerticalOffset(0); |
1 |
<span id="lnum17" style="color: #606060"> 17:</span> } |
1 |
<span id="lnum18" style="color: #606060"> 18:</span> } |
If all goes well, the end result should look like this
To not mis anything, the demo project is up on my github here https://github.com/Depechie/FixedTextScrollDemo
Great Thanks for You!
msdn sucks