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:
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
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
andSfDataGrid.FrozenColumnsCount
propertys.Tutorial: https://help.syncfusion.com/xamarin/datagrid/freeze-panes#freeze-columns
@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)
Try to create four
Grid
s to set the layout and use ScrollView to achieve scrolling. A Grid wraps the three others like: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.