Assume you have some news page that contains a header with the actual news title, some highlight image and a lot of body text.
If this is represented on a mobile device, depending on the screen resolution, this can result in the user needing to scroll the text content to be able to read everything.
Now if you just put the whole page in a ScrollView, the user will loose the content of the article he is reading, because most likely the title will also scroll out of view.
So let me show you what you can do to keep the title fixed as soon as it reaches a certain anchor point on screen, while the user is scrolling the text content.
The starting view looks like this
You will notice the title “Bear found in the wild” is fixed to the bottom of the highlight picture and as soon as the user scrolls, this will also scroll to the top of the screen. But we want to keep it in view when it reaches the top of the image ( it will be fixed below the “Fixed header demo page title ). even if the user keeps scrolling down after we reached our anchor point.
First the design of the page in XAML:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
<?xml version="1.0" encoding="utf-8"?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:FixedHeader" x:Class="FixedHeader.MainPage"> <Grid RowSpacing="0" ColumnSpacing="0"> <Grid.Margin> <OnPlatform x:TypeArguments="Thickness"> <On Platform="iOS" Value="0,20,0,0" /> </OnPlatform> </Grid.Margin> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <Label Text="FIXED HEADER DEMO" Margin="12" Grid.Row="0" FontSize="14" /> <Grid x:Name="ContentGrid" RowSpacing="0" ColumnSpacing="0" Grid.Row="1"> <ScrollView x:Name="TheScroll"> <Grid RowSpacing="0" ColumnSpacing="0"> <Grid.RowDefinitions> <RowDefinition x:Name="ImageRow" Height="Auto" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <Image x:Name="BearImage" Source="bear.jpg" Aspect="AspectFill" Grid.Row="0" /> <Label LineBreakMode="WordWrap" Margin="12,5,12,5" Grid.Row="1"> <Label.FormattedText> <FormattedString> <Span Text="Never heard before, but a bear was found in the wild!" FontAttributes="Bold" FontSize="18" /> <Span Text=" Bears are mammals of the family Ursidae. Bears are classified as caniforms, or doglike carnivorans, with the pinnipeds being their closest living relatives. Although only eight species of bears are extant, they are widespread, appearing in a wide variety of habitats throughout the Northern Hemisphere and partially in the Southern Hemisphere. Bears are found on the continents of North America, Central America, South America, Europe, and Asia. Common characteristics of modern bears include large bodies with stocky legs, long snouts, shaggy hair, plantigrade paws with five nonretractile claws, and short tails. While the polar bear is mostly carnivorous and the giant panda feeds almost entirely on bamboo, the remaining six species are omnivorous, with varied diets. 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 as caves and burrows, as their dens; most species occupy their dens during the winter for a long period (up to 100 days) of sleep similar to hibernation.[2] Bears have been hunted since prehistoric times for their meat and fur. With their tremendous physical presence and charisma, they play a prominent role in 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 as vulnerable or endangered, and even least concern species, such as the brown bear, are at risk of extirpation in certain countries. The poaching and international trade of these most threatened populations are prohibited, but still ongoing." FontSize="14" /> </FormattedString> </Label.FormattedText> </Label> </Grid> </ScrollView> <Label x:Name="TitleText" Text="Bear found in the wild!" TextColor="White" BackgroundColor="#FF264778" HeightRequest="40" VerticalOptions="Start" VerticalTextAlignment="Center" HorizontalTextAlignment="Center" /> </Grid> </Grid> </ContentPage> |
Import thing to note here, is that the actual TitleText label is positioned outside of the ScrollView! This is needed, because we will animate it ourselves depending on the scroll values of the ScrollView.
So how do we position this TitleText to the bottom of the bear image? This is done in the code behind of the page! In the constructor we hookup to the SizeChanged event of the bear image and in that event we will set the margin of the TitleText to the Height of the bear image minus the height of the TitleText itself.
1 2 3 4 5 6 7 8 9 |
BearImage.SizeChanged += OnBearImageSizeChanged; private void OnBearImageSizeChanged(object sender, System.EventArgs e) { BearImage.SizeChanged -= OnBearImageSizeChanged; //When the bear image has been loaded, reposition the news header to the bottom of this image TitleText.Margin = new Thickness(0, BearImage.Height - 40, 0, 0); } |
Now when the TitleText has been fixed to it’s starting position, we need to take hold of the top Y coördinate. This is needed to calculate the anchor point while scrolling.
We also hookup to the SizeChanged event of the TitleText and when it changes, we can grab the Y point.
1 2 3 4 5 6 7 8 9 10 11 12 |
TitleText.SizeChanged += OnTitleTextSizeChanged; private void OnTitleTextSizeChanged(object sender, System.EventArgs e) { TitleText.SizeChanged -= OnTitleTextSizeChanged; //As soon as the news header has been repositioned, we can grab the actual screen top position _titleTextTop = TitleText.Y; //Remark: GetScreenCoordinates will get the actual position on screen instead of the actual position inside the parent //_titleTextTop = GetScreenCoordinates(TitleText).Y; } |
Only one thing left to do, we now have to keep hold of the scroll values of the ScrollView, so we can animate the TitleText in sync with the scroll position and direction.
So we subscribe to the PropertyChanged event of the ScrollView and look for Y value changes.
When we get a change, we validate if it is still lower than our anchor point, if so, animate the TitleText Y coördinate to the same value. If it’s passed the anchor point, we will just keep the TitleText coördinate the same. This will result in the title being kept in view!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
TheScroll.PropertyChanged += OnScrollViewPropertyChanged; private void OnScrollViewPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) { if(e.PropertyName.Equals(ScrollView.ScrollYProperty.PropertyName)) { var scrolled = ((ScrollView)sender).ScrollY; System.Diagnostics.Debug.WriteLine($"Y position: {scrolled.ToString()}"); if (scrolled < _titleTextTop) TitleText.TranslationY = (0 - scrolled); else TitleText.TranslationY = (0 - _titleTextTop); } } |
And that’s it!
The end result will look like this:
As always a complete demo can be found on my GitHub here…
Thanks mate you are pro….
I have tried your sample for a filter with horizontal orientation and by
clicking on the values in the filter I am changing the data source of a list view in My scenario I have user information stack followed by list view in a grid and filter in the main grid and I am changing filter Y in the scrolled event of listview and margin for listview and it’s causing glitch in the screen, can you suggest any solution for my scenario
Sorry, I have not yet tested this scenario with a horizontal list… so no idea how to fix this.