o_w
o_w

Reputation: 683

Abysmal list performance

An item template that includes a SkiaSharp control displaying svg, a SwipeView and some labels,

is taking forever to load when navigating to the page.

I am using StackLayout with BindableLayout.ItemSource and ItemTemplate.

Using CollectionView will let the page load much faster, but then every attempt to scroll down will slow the app for a moment, while the CollectionView generate the next batch of items.

I have moved all the code generating the items source to background, so the only thing happening on the ui thread is the binding to the ObservableCollection from the viewmodel, and drawing it.

I have also tried reducing the layouts in the item template, but it did not improve loading speed.

Xaml for page:

<DataTemplate x:Key="DataTemplateReportsItemAction">
        <StackLayout Orientation="Horizontal">
            <effectsView:SfEffectsView Style="{StaticResource StyleRippleEffectReportAction}">
                <effectsView:SfEffectsView.GestureRecognizers>
                    <TapGestureRecognizer Command="{Binding ActionCommand}" />
                </effectsView:SfEffectsView.GestureRecognizers>
                <image:SVGImage ImageSource="{Binding IconName}"
                                Style="{StaticResource StyleSVGImageReportRowAction}" />
            </effectsView:SfEffectsView>
            <BoxView Style="{StaticResource StyleBoxViewReportItemAction}"
                     IsVisible="{Binding IsLast, Converter={StaticResource BoolToReverseBoolConverter}}" />
        </StackLayout>
    </DataTemplate>
    <ControlTemplate x:Key="DataTemplateReportsItemCellDefault">
        <StackLayout Orientation="Horizontal"
                     HorizontalOptions="FillAndExpand"
                     BindingContext="{TemplateBinding BindingContext}">
            <StackLayout Orientation="Vertical"
                         HorizontalOptions="FillAndExpand"
                         VerticalOptions="Center">
                <Label Style="{StaticResource StyleLabelReportCellTop}"
                       Text="{Binding DisplayValue}"
                       TextColor="{Binding ColorName, TargetNullValue={StaticResource ColorLabelReportCell}, FallbackValue={StaticResource ColorLabelReportCell}}" />
                <Label Style="{StaticResource StyleLabelReportCellBottom}"
                       Text="{Binding DisplayTitle}"
                       TextColor="{Binding ColorName, TargetNullValue={StaticResource ColorLabelReportCell}, FallbackValue={StaticResource ColorLabelReportCell}}" />
            </StackLayout>
            <BoxView Style="{StaticResource StyleBoxViewReportItemCell}"
                     IsVisible="{Binding IsLast, Converter={StaticResource BoolToReverseBoolConverter}}" />
        </StackLayout>
    </ControlTemplate>
    <ControlTemplate x:Key="DataTemplateReportsItemCellTrend">
        <StackLayout Orientation="Horizontal"
                     HorizontalOptions="FillAndExpand"
                     BindingContext="{TemplateBinding BindingContext}">
            <StackLayout Orientation="Vertical"
                         HorizontalOptions="FillAndExpand"
                         VerticalOptions="Center">
                <image:SVGImage ImageSource="{Binding IconName}"
                                HorizontalOptions="Center" />
                <Label Style="{StaticResource StyleLabelReportCellTrend}"
                       Text="{Binding DisplayTitle}"
                       TextColor="{Binding ColorName, TargetNullValue={StaticResource ColorLabelReportCell}, FallbackValue={StaticResource ColorLabelReportCell}}" />
            </StackLayout>
            <BoxView Style="{StaticResource StyleBoxViewReportItemCell}"
                     IsVisible="{Binding IsLast, Converter={StaticResource BoolToReverseBoolConverter}}" />
        </StackLayout>
    </ControlTemplate>
    <converters:ValueToValueConverter x:Key="CellTemplateSelector"
                                      DefaultValue="{StaticResource DataTemplateReportsItemCellDefault}">
        <converters:ValueToValueList>
            <converters:ValueToValueItem OnValue="{x:Static reportenums:MobileColumnMetaType.Trend}"
                                         ToValue="{StaticResource DataTemplateReportsItemCellTrend}" />
        </converters:ValueToValueList>
    </converters:ValueToValueConverter>
    <DataTemplate x:Key="DataTemplateReportsItemCell">
        <ContentView ControlTemplate="{Binding MobileMetaType, Converter={StaticResource CellTemplateSelector}}"
                     HorizontalOptions="FillAndExpand" />
    </DataTemplate>
    <DataTemplate x:Key="DataTemplateReportsItem">
        <Grid HeightRequest="{StaticResource DoubleReportRowTotalHeight}">
            <Grid.RowDefinitions>
                <RowDefinition Height="{StaticResource DoubleReportRowTitleHeight}" />
                <RowDefinition Height="{StaticResource DoubleReportRowSwipeHeight}" />
            </Grid.RowDefinitions>
            <StackLayout Margin="0,5"
                         Orientation="Horizontal"
                         HorizontalOptions="FillAndExpand"
                         Spacing="10">
                <image:SVGImage ImageSource="{Binding LactationIcon}"
                                IsVisible="{Binding LactationIcon, Converter={StaticResource StringToIsVisibleConverter}}"
                                Style="{StaticResource StyleSVGImageReportRowLactation}" />
                <Label Style="{StaticResource StyleLabelReportRowTitle}"
                       FontSize="{Binding UseSmallTitle, Converter={StaticResource RowFontSizeConverter}}"
                       Text="{Binding DisplayTitle}" />
                <Label Style="{StaticResource StyleLabelReportRowTitleGroup}"
                       IsVisible="{Binding GroupName, Converter={StaticResource StringToIsVisibleConverter}}"
                       Text="{Binding GroupName}" />
                <Frame Style="{StaticResource StyleFrameReportRowBadge}"
                       BackgroundColor="{Binding Badge.BadgeType, Converter={StaticResource BadgeColorConverter}}"
                       IsVisible="{Binding Badge.HasBadge}">
                    <Label Style="{StaticResource StyleLabelReportRowTitleBadge}"
                           Text="{Binding Badge.BadgeTitle}" />
                </Frame>
            </StackLayout>
            <Grid Grid.Row="1">
                <SwipeView BackgroundColor="{StaticResource ColorReportViewRowBackground}"
                           IsEnabled="{Binding IsEditable}"
                           x:Name="swipey">
                    <SwipeView.RightItems>
                        <SwipeItems Mode="Reveal"
                                    SwipeBehaviorOnInvoked="RemainOpen">
                            <SwipeItemView WidthRequest="{Binding Width, Source={Reference swipey}}">
                                <Grid BackgroundColor="{StaticResource ColorLabelReportRowActionsBackground}">
                                    <Grid Margin="20,0">
                                        <Grid.ColumnDefinitions>
                                            <ColumnDefinition Width="Auto" />
                                            <ColumnDefinition Width="*" />
                                            <ColumnDefinition Width="Auto" />
                                        </Grid.ColumnDefinitions>
                                        <effectsView:SfEffectsView Style="{StaticResource StyleRippleEffectReportRowClose}"
                                                                   helpers:VisualTreeHelper.ReferenceObject="{Binding Source={Reference swipey}}">
                                            <effectsView:SfEffectsView.GestureRecognizers>
                                                <TapGestureRecognizer Tapped="OnGroupRowCloseTapped" />
                                            </effectsView:SfEffectsView.GestureRecognizers>
                                            <image:SVGImage Style="{StaticResource StyleSVGImageReportRowActionsClose}" />
                                        </effectsView:SfEffectsView>
                                        <StackLayout Grid.Column="2"
                                                     Orientation="Horizontal"
                                                     BindableLayout.ItemTemplate="{StaticResource DataTemplateReportsItemAction}"
                                                     BindableLayout.ItemsSource="{Binding ActionItems}" />
                                    </Grid>
                                </Grid>
                            </SwipeItemView>
                        </SwipeItems>
                    </SwipeView.RightItems>
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="*" />
                            <ColumnDefinition Width="Auto" />
                        </Grid.ColumnDefinitions>
                        <StackLayout Orientation="Horizontal"
                                     HorizontalOptions="FillAndExpand"
                                     BackgroundColor="{StaticResource ColorTransparent}"
                                     BindableLayout.ItemTemplate="{StaticResource DataTemplateReportsItemCell}"
                                     BindableLayout.ItemsSource="{Binding Cells}">
                            <StackLayout.GestureRecognizers>
                                <TapGestureRecognizer Command="{Binding BindingContext.NavigateCommand, Source={RelativeSource Mode=FindAncestor, AncestorType={x:Type views:BasePage}}}"
                                                      CommandParameter="{Binding}" />
                            </StackLayout.GestureRecognizers>
                        </StackLayout>
                        <Frame Grid.Column="1"
                               Padding="0"
                               BackgroundColor="{StaticResource ColorReportGroupRowActionsBackground}"
                               IsVisible="{Binding IsEditable}">
                            <image:SVGImage Style="{StaticResource StyleSVGImageSwipe}" />
                        </Frame>
                    </Grid>
                </SwipeView>
                <Grid BackgroundColor="{StaticResource ColorTransparent}"
                      IsVisible="{Binding IsEditable, Converter={StaticResource BoolToReverseBoolConverter}}">
                    <Grid.GestureRecognizers>
                        <TapGestureRecognizer Command="{Binding BindingContext.NavigateCommand, Source={RelativeSource Mode=FindAncestor, AncestorType={x:Type views:BasePage}}}"
                                              CommandParameter="{Binding}" />
                    </Grid.GestureRecognizers>
                </Grid>
            </Grid>
        </Grid>
    </DataTemplate>
</ContentView.Resources>
<ContentView.Content>
    <Grid>
        <gradient:SfGradientView BackgroundBrush="{StaticResource BrushViewBackgroundGradient}" />
        <Grid BackgroundColor="{StaticResource ColorReportsBrowserViewBackground}"
              Margin="10,0">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="*" />
            </Grid.RowDefinitions>
            <reports:ReportToolbarView />
            <RefreshView Grid.Row="1"
                         IsRefreshing="{Binding IsRefreshing, Mode=TwoWay}"
                         Command="{Binding RefreshCommand}"
                         BackgroundColor="{StaticResource ColorReportsViewBackground}"
                         Margin="10,0">
                <ScrollView HorizontalScrollBarVisibility="Never"
                            VerticalScrollBarVisibility="Default">
                    <StackLayout Orientation="Vertical">
                        <StackLayout BindableLayout.ItemsSource="{Binding ReportItems}"
                                     BindableLayout.ItemTemplate="{StaticResource DataTemplateReportsItem}"
                                     Orientation="Vertical" />
                        <BoxView HeightRequest="50" />
                    </StackLayout>
                </ScrollView>
            </RefreshView>
            <sortfilter:SortFilterView Grid.Row="1"
                                       VerticalOptions="Start"
                                       IsVisible="{Binding SortFilterModel.IsVisible}" />
        </Grid>
    </Grid>
</ContentView.Content>

For comparison, replacing the item template with a simple Label makes the page load extremely fast.

Is there a way to load lists of items faster in Xamarin Forms?

Upvotes: 0

Views: 372

Answers (1)

Markus Michel
Markus Michel

Reputation: 2299

Too many StackLayouts. Even worse, you stacked them into each other. Don't do that. StackLayouts need a lot of layout cycles, as they have to calculate their size based on the children you have put in. If one of those children is a StackLayout as well, that will more than double the layout passes necessary to get to the final result. And now guess what happens, when you have 50 or more items in your list. Those layout passes will have to run for each single item in your list.

My advice based on my experience would be to replace the StackLayouts with a grid and place your elements using their Grid.Row, Grid.Colum, Grid.RowSpan and Grid.Columnspan properties. Also set your grid sizes to either fixed values or use star based sizes. Don't use "Auto" as this needs more layout passes as well.

Upvotes: 0

Related Questions