Sticky headers
This example shows how we can create sticky headers that hover at the top of the screen as we scroll the content. Pictures for this example are taken from unsplash.com. Design inspiration: Read App Interface by Kun-Li.
The desired effect can be implemented with the use of the x()
and y()
UX expression functions. Since in this case we only need a vertical translation, we will use the y()
function, which tells us the value of an elements’ vertical position.
The main structure of our app is a ScrollView
that holds a list of cards.
<ScrollView Background="#f3f4f6">
<StackPanel ItemSpacing="8">
<Each Items="{list}">
<ListItem />
</Each>
</StackPanel>
</ScrollView>
The cards are defined as components using ux:Class
. We have a separate content
element, which holds the large image, and on top of it - our slightly transparent header
Panel
with author details. Below that, we have the footer section that shows the image title and some icons.
<StackPanel ux:Class="ListItem" Background="#fff">
<StackPanel ux:Name="content">
<Panel ux:Name="header" Height="68" Padding="24,0" Background="#fffffff8">
<DockPanel Alignment="VerticalCenter">
<Icon File="Assets/Icons/more.png" Size="24" Dock="Right" />
<Circle Dock="Left" Width="40" Height="40">
<ImageFill File="{authorFile}" />
</Circle>
<StackPanel Padding="16,0">
<Text Value="{authorName}" Color="#8e979e" FontSize="16" Alignment="Left" />
<Text Value="{authorPlace}" Color="#8e979e" FontSize="12" Alignment="Left" />
</StackPanel>
</DockPanel>
</Panel>
<Image Height="256" File="{imageFile}" StretchMode="UniformToFill" />
</StackPanel>
<Panel Height="56" Padding="24,0">
<DockPanel Alignment="VerticalCenter">
<StackPanel Dock="Right" Orientation="Horizontal" ItemSpacing="16">
<Icon File="Assets/Icons/favorite.png" Size="18" />
<Icon File="Assets/Icons/chat.png" Size="18" />
</StackPanel>
<Text Value="{imageTitle}" Color="#8e979e" FontSize="20" Alignment="CenterLeft" ClipToBounds="true" Margin="0,0,16,0" />
</DockPanel>
</Panel>
</StackPanel>
To make the header
items position react to our ScrollView
being scrolled, we use a ScrollingAnimation
trigger together with a Move
animator.
Since we put the ScrollingAnimation
triggers inside of our card class, the position values we get in our UX expression functions are relative to the particular instance of that class.
The first ScrollingAnimation
trigger sticks the header
to the top of the ScrollView
. As we scroll, the Move
animator moves the header
vertically, relative to the size of the content
area. Since the ScrollingAnimation
trigger length matches the height of the content
node, it makes the header
appear like it’s stuck to the top.
<ScrollingAnimation From="y(this)" To="y(this) + height(content)">
<Move Target="header" Y="1" RelativeTo="Size" RelativeNode="content" />
</ScrollingAnimation>
If we do the math for the first ListItem
, the ScrollingAnimation
is starting from 0
and ending with 0 + (68 + 256) = 324
. For every other ListItem
, the y(this)
expression translates to the vertical offset of that item relative to the StackPanel
that holds the list.
The second ScrollingAnimation
trigger is responsible for moving the header
out of the view when it hits the bottom of the card. As we scroll, the Move
animator moves the header
upwards relative to its size. Since in this case the ScrollingAnimation
trigger length matches the height of the header
element, it gets smoothly scrolled out of the view.
<ScrollingAnimation From="y(this) + height(content) - height(header)" To="y(this) + height(content)">
<Move Target="header" Y="-1" RelativeTo="Size" />
</ScrollingAnimation>
The expression for the starting point takes into account the position of the card, adds the height of the content area and subtracts the height of the header
. The expression for the end point only needs to take into account the position of the card and add the height of the content area.
Now we have a simple starting point for any sticky header situation. We can’t wait to see what you will come up with!