Xamarin.Forms Layout Challenges – Timeline
A layout I’m seeing more and more these days is a timeline of activities. This is useful for things like transportation schedules or class times. So let’s put together a simple layout for a timeline using a ListView with headers and footers and a custom ViewCell.
Page Structure
This page is just a simple ListView, nothing really more complex than that. I turned the Separators off. Set the RowHeight to something that feels nice.
<ListView x:Name="timelineListView" ItemTapped="timelineListView_ItemTapped" ItemsSource="{Binding .}" RowHeight="75" SeparatorVisibility="None">
Also, you may notice I have an ItemTapped hooked up that doesn’t do much:
private void timelineListView_ItemTapped(object sender, ItemTappedEventArgs e) { timelineListView.SelectedItem = null; }
It just disables a row from being selected, mainly because it looks rubbish when one of the rows is selected… but of course depending on your layout, you might actually want to do Master / Detail style navigation.
Header
If you want something to appear above your ListView and have it scroll with the ListView use a header.
Pro Tip: Whatever you do, do NOT put a ScrollView around the entire page. Having nested scrolling containers (eg. ScrollView with ListView inside) is just going end with tears. Use a Header instead.
For our header, it’s just a simple stack layout with some labels and a bit of padding
<ListView.Header> <StackLayout Padding="20,40,0,30"> <Label Style="{StaticResource PageHeaderLabel}" Text="Class Schedule" /> <Label Style="{StaticResource SubHeaderLabel}" Text="8 Mar 2017" /> </StackLayout> </ListView.Header>
Footer
Down the bottom of the list, we have just put an image. Not for any particularly good reason, just to jazz up the page a bit and show how footers work. You could easily get rid of it and have a nice layout, I figured I’d just include it to complete the header / footer idea.
In our case the footer contains a Grid with two rows. The actual background image “Footer.png” occupies both the rows. Then we have a transparent-to-white gradient image that is overlaid in the first row. Basically just creating a fade in effect for the image.
<ListView.Footer> <Grid RowSpacing="0"> <Grid.RowDefinitions> <RowDefinition Height="64" /> <RowDefinition Height="100" /> </Grid.RowDefinitions> <Image Grid.RowSpan="2" Aspect="AspectFill" HorizontalOptions="Fill" VerticalOptions="Start" Source="YogaImage.png" /> <Image Aspect="Fill" Grid.RowSpan="2" HorizontalOptions="Fill" Source="FadeToWhite.png" /> </Grid> </ListView.Footer>
ViewCell
All the real magic happens in the ViewCell which defines what each row is going to look like.
At it’s core it’s just a simple Grid with 3 columns and two rows.
The only really interesting bit of this is the actual lines and circles that form the timeline. This is achieved by a thin vertical BoxView that runs the height of the Viewcell. Overlayed in the first row is our circle image. Nothing magical here.
Now you might notice that it uses a ValueConverter for the IsVisible property, this is kind of a hack to make the line not appear in the last row. Our model object (which would actually probably be a ViewModel in a real app) has a property called IsLast which is set to true for the last row. And then we have a NotBooleanConverter assigned to the IsVisible of the line, so basically the line isn’t rendered on the last row. It feels a bit awkward, but off the top of my head, given the time, I couldn’t think of a better option.
<ListView.ItemTemplate> <DataTemplate> <ViewCell> <Grid ColumnSpacing="0" RowSpacing="0"> <Grid.ColumnDefinitions> <ColumnDefinition Width="100" /> <ColumnDefinition Width="30" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <Label HorizontalOptions="Center" Style="{StaticResource ClassTimeLabel}" Text="{Binding ClassTime, StringFormat='{0:H:mm}'}" /> <Label Grid.Column="2" Margin="20,0" Style="{StaticResource ClassNameLabel}" Text="{Binding ClassName}" /> <Label Grid.Row="1" Grid.Column="2" Margin="20,0" Style="{StaticResource ClassInstructorLabel}" Text="{Binding Instructor}" /> <BoxView Grid.RowSpan="2" Grid.Column="1" BackgroundColor="{StaticResource TimelineColor}" HorizontalOptions="Center" IsVisible="{Binding IsLast, Converter={local:NotBooleanConverter}}" VerticalOptions="Fill" WidthRequest="3" /> <Image Grid.Column="1" Source="Bullet.png" /> </Grid> </ViewCell> </DataTemplate> </ListView.ItemTemplate>
Wrap Up
So that is it, a simple timeline using a ListView. It works pretty nicely.
Here are some links for further information about some of the techniques:
As YouTubers would say: “let me know in the comments if you liked this” 🙂 Also, If you have any layouts that you thing would be interesting to cover, just let me know.
Oh yeah, and you can grab the project over at https://github.com/kphillpotts/XamarinFormsLayoutChallenges
Also, make sure you check out some of the other layouts in Xamarin.Forms Layout Challenges.
Happy Layouts!