/ XAMARIN

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.

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>

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!

 

 

 

kphillpotts

Kym Phillpotts

Geek, Parent, Human, Senior Content Developer at Microsoft. Co-curator of http://weeklyxamarin.com and twitch streaming live coding at http://twitch.tv/kymphillpotts.

Read More