How to create a table with vertically sticky header and horizontally sticky first column?

kapselikapseli Member ✭✭

Hi! When displaying tabular data, I think that in some cases having an always visible header row and an always visible first column can really improve the readability and the overall usability of a table, especially if there is a lot of data in the table. The problem occurs when the table has to support both horizontal and vertical scrolling. A good example of such a table can be found from the NBA application when viewing box score of a past game. Here's an example image from the NBA mobile application:

As you can clearly see from the image the header row is horizontally aligned with the actual table data and the first column is vertically aligned with the table data. I don't know whether or not it's an involuntary or a voluntary decision to prevent scrolling both horizontally and vertically with the same touch motion but that's a minor detail I don't care about.
I don't know how to implement this using Xamarin Forms. I do realize that I most likely have to use custom renderers for both Android and IOS. My current idea is that I have an absolute layout where I have the following elements:

  • The first cell (it's stationary and the only stationary thing)
  • Rest of the header row inside a horizontal scrollview
  • First column inside a listview/stacklayout + vertical scrollview
  • The actual table data inside a listview + horizontal scrollview / stacklayout + horizontal and vertical scrollview

With this setup I would capture the touch event and send it to the other listviews/scrollviews, thus synchronizing the scrolling. In fact I can easily achieve the synchronized scrolling with the first column and the actual table data by setting the table data inside the same vertical scrollview as the first column. But I don't know how to synchronize the horizontal scrolling to the header row and I do believe that this can't be accomplished by clever component structure. I have tested only on Android so far that I can capture the touch event in a scrollview custom renderer's OnTouchEvent -method but I don't know how I could send this to the header row scrollview from the custom renderer.

Here is a draft XAML illustrating my approach.

<AbsoluteLayout xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             HorizontalOptions="FillAndExpand">
    <ScrollView
        Orientation="Horizontal"
        x:Name="HeaderScrollView"
        AbsoluteLayout.LayoutBounds="0,0,1,1"
        AbsoluteLayout.LayoutFlags="All">
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="200" />
                <ColumnDefinition Width="100" />
                <ColumnDefinition Width="100" />
                <ColumnDefinition Width="100" />
                <ColumnDefinition Width="100" />
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="50" />
            </Grid.RowDefinitions>
            <!-- Skip first column, leave it empty for stationary cell -->
            <Label Text="Column 1" Grid.Row="0" Grid.Column="1" />
            <Label Text="Column 2" Grid.Row="0" Grid.Column="2" />
            <Label Text="Column 3" Grid.Row="0" Grid.Column="3" />
            <Label Text="Column 4" Grid.Row="0" Grid.Column="4" />
        </Grid>
    </ScrollView>
    <ScrollView
        x:Name="FirstColumnScrollView"
        Orientation="Vertical"
        AbsoluteLayout.LayoutBounds="0,50,1,1"
        AbsoluteLayout.LayoutFlags="SizeProportional"
        BackgroundColor="Aqua">
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="200" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="*" />
            </Grid.RowDefinitions>
            <StackLayout
                Grid.Column="0"
                Grid.Row="0"
                BindableLayout.ItemsSource="{Binding DataSource}">
                <BindableLayout.ItemTemplate>
                    <DataTemplate>
                        <Grid>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="50" />
                            </Grid.RowDefinitions>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="150" />
                            </Grid.ColumnDefinitions>
                            <Label Text="{Binding Column1}" Grid.Row="0" Grid.Column="0" />
                        </Grid>
                    </DataTemplate>
                </BindableLayout.ItemTemplate>
            </StackLayout>
            <ScrollView
                x:Name="TableDataScrollView"
                Grid.Column="1"
                Grid.Row="0"
                Orientation="Horizontal">
                <StackLayout
                    BindableLayout.ItemsSource="{Binding DataSource}">
                    <BindableLayout.ItemTemplate>
                        <DataTemplate>
                            <Grid>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="100" />
                                    <ColumnDefinition Width="100" />
                                    <ColumnDefinition Width="100" />
                                    <ColumnDefinition Width="100" />
                                </Grid.ColumnDefinitions>
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="50" />
                                </Grid.RowDefinitions>
                                <Label Text="{Binding Column2}" Grid.Row="0" Grid.Column="0" />
                                <Label Text="{Binding Column3}" Grid.Row="0" Grid.Column="1" />
                                <Label Text="{Binding Column4}" Grid.Row="0" Grid.Column="2" />
                                <Label Text="{Binding Column5}" Grid.Row="0" Grid.Column="3" />
                            </Grid>
                        </DataTemplate>
                    </BindableLayout.ItemTemplate>
                </StackLayout>
            </ScrollView>
        </Grid>
    </ScrollView>
    <Label Text="First Column" BackgroundColor="White" AbsoluteLayout.LayoutBounds="0,0,200,50" />
</AbsoluteLayout>

As you can see the problem is that horizontal scrolling events between HeaderScrollView and TableDataScrollView are not shared and I don't know how to accomplish this in the best way possible or at all.
I do appreciate all the help and feedback with this!

Answers

  • JarvanJarvan Member, Xamarin Team Xamurai

    Try to use Syncfusion.Xamarin.SfDataGrid plugin to achieve the feature. The SfDataGrid provides extensive support to freeze the rows at the top and freeze the columns at the left. to You can add the effect by setting SfDataGrid.FrozenRowsCount and SfDataGrid.FrozenColumnsCount propertys.

    //Setting number of rows to freeze in SfDataGrid
    dataGrid.FrozenRowsCount = 2;
    
    //Setting number of columns to freeze in SfDataGrid
    dataGrid.FrozenColumnsCount = 2; 
    

    Tutorial: https://help.syncfusion.com/xamarin/datagrid/freeze-panes#freeze-columns

  • kapselikapseli Member ✭✭
    edited December 2

    @Jarvan Thanks for the help and I'm sorry that I forgot to specify that I am not looking for a closed source solution. I was aware that this can be achieved with these paid UI components but I want to learn how this can be accomplished and a paid component doesn't help with that. I'll edit the question accordingly. (Apparently you can't edit the question)

  • JarvanJarvan Member, Xamarin Team Xamurai

    Try to create four Grids to set the layout and use ScrollView to achieve scrolling. A Grid wraps the three others like:

    • One for the fixed row, with only horizontal scroll
    • One for the fixed column, with only vertical scroll
    • One for the other cell, with horizontal and vertical scroll
  • kapselikapseli Member ✭✭

    I don't quite understand what you are saying. I don't see how that is going to help with syncing the scroll events between different scrollviews. The table cells should be children of both the fixed row with only horizontal scroll and the fixed column with only vertical scroll and that seems to be impossible. I understood that your suggestion simply gives 3 scrollviews that don't work together at all.

Sign In or Register to comment.