Poor performance of CollectionView with SwipeView

Georgi Georgiev 21 Reputation points
2021-04-07T12:02:38.25+00:00

I am facing performance problems when using SwipeView inside ListView or CollectionView. I have been searching for a solution for a while now to no avail.
I am developing an app, but the problem exists in the Xamarin samples, so I'll use those as an example. Long story short, I downloaded the CollectionView sample from the Xamarin.Forms documentation: https://learn.microsoft.com/en-us/samples/xamarin/xamarin-forms-samples/userinterface-collectionviewdemos/ .
It contains a page with CollectionView that has DataTemplate with a SwipeView. The CollectionView is binded to an ObservableCollection with 17 items in it. Running the sample on x86 Android emulator works well, but it has poor performance on my Android phone - Xiaomi A1. if I scroll slowly there is almost no visible signs of stutter, however scrolling fast is not smooth. While I scroll fast the UI is freezing for parts of a second. It is most definitely visible and very annoying.

What I have discovered so far is that both ListView and CollectionView are not scrolling smoothly. The scrolling is not smooth only when the List has SwipeView inside, even with the simplest layout for SwipveView.Content.
The scrolling is silky smooth when the List does not have SwipeView.

When using ListView with CachingStrategy.RetainElement the performance is degrading more and more with the number of items in the collection. It is degrading so much so that with 200 items the App is unusable. ListView with CachingStrategy.RecycleElement has a performance similar to the performance of CollectionView.
What's interesting is that SwipeView inside ScrollView has much better performance on my phone.

I tried using an IList instead of IEnumerable as suggested here:
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/listview/performance ,
but the problem still persists.

Is there a workaround to this problem?

Update.

Here is the code in question:

VerticalListSwipeContextItemsPage.xaml:

<?xml version="1.0" encoding="UTF-8"?>  
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"  
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"  
             xmlns:viewmodels="clr-namespace:CollectionViewDemos.ViewModels"  
             x:Class="CollectionViewDemos.Views.VerticalListSwipeContextItemsPage"  
             Title="Context menu items">  
    <StackLayout Margin="20">  
        <Image x:Name="image" Source="monkey.png" VerticalOptions="CenterAndExpand" />  
        <Button x:Name="startButton" Text="Start Animation" Clicked="OnStartAnimationButtonClicked" VerticalOptions="End" />  
        <Button x:Name="cancelButton" Text="Cancel Animation" Clicked="OnCancelAnimationButtonClicked" IsEnabled="false" />  
        <CollectionView x:Name="collectionView"  
                        ItemsSource="{Binding Monkeys}">  
            <CollectionView.ItemTemplate>  
                <DataTemplate>  
                    <SwipeView>  
                        <SwipeView.LeftItems>  
                            <SwipeItems>  
                                <SwipeItem Text="Favorite"  
                                           IconImageSource="favorite.png"  
                                           BackgroundColor="LightGreen"  
                                           Command="{Binding Source={x:Reference collectionView}, Path=BindingContext.FavoriteCommand}"  
                                           CommandParameter="{Binding}" />  
                                <SwipeItem Text="Delete"  
                                           IconImageSource="delete.png"  
                                           BackgroundColor="LightPink"  
                                           Command="{Binding Source={x:Reference collectionView}, Path=BindingContext.DeleteCommand}"  
                                           CommandParameter="{Binding}" />  
                            </SwipeItems>  
                        </SwipeView.LeftItems>  
                        <Grid BackgroundColor="White"  
                              Padding="10">  
                            <Grid.RowDefinitions>  
                                <RowDefinition Height="*" />  
                                <RowDefinition Height="*" />  
                            </Grid.RowDefinitions>  
                            <Grid.ColumnDefinitions>  
                                <ColumnDefinition Width="*" />  
                                <ColumnDefinition Width="*" />  
                            </Grid.ColumnDefinitions>  
                            <Label Grid.Column="1"   
                                   Text="{Binding Name}"   
                                   FontAttributes="Bold" />  
                            <Label Grid.Row="1"  
                                   Grid.Column="1"   
                                   Text="{Binding Location}"  
                                   FontAttributes="Italic"   
                                   VerticalOptions="End" />  
                        </Grid>  
                    </SwipeView>  
                </DataTemplate>  
            </CollectionView.ItemTemplate>  
        </CollectionView>  
    </StackLayout>  
</ContentPage>  

VerticalListSwipeContextItemsPage.xaml.cs:

using System;  
using System.Threading.Tasks;  
using CollectionViewDemos.ViewModels;  
using Xamarin.Forms;  
  
namespace CollectionViewDemos.Views  
{  
    public partial class VerticalListSwipeContextItemsPage : ContentPage  
    {  
        public VerticalListSwipeContextItemsPage()  
        {  
            InitializeComponent();  
            BindingContext = new MonkeysViewModel();       
        }  
  
		void SetIsEnabledButtonState(bool startButtonState, bool cancelButtonState)  
		{  
			startButton.IsEnabled = startButtonState;  
			cancelButton.IsEnabled = cancelButtonState;  
		}  
  
		async void OnStartAnimationButtonClicked(object sender, EventArgs e)  
		{  
			SetIsEnabledButtonState(false, true);  
  
			await Task.WhenAll(  
			  image.RotateYTo(360 * 10, 60 * 1000)  
			);  
  
			image.Rotation = 0;  
  
			SetIsEnabledButtonState(true, false);  
		}  
  
		void OnCancelAnimationButtonClicked(object sender, EventArgs e)  
		{  
			ViewExtensions.CancelAnimations(image);  
			SetIsEnabledButtonState(true, false);  
		}  
	}  
}  

MonkeysViewModel.cs:

using System.Collections.Generic;  
using System.Collections.ObjectModel;  
using System.ComponentModel;  
using System.Linq;  
using System.Runtime.CompilerServices;  
using System.Windows.Input;  
using CollectionViewDemos.Models;  
using Xamarin.Forms;  
  
namespace CollectionViewDemos.ViewModels  
{  
    public class MonkeysViewModel : INotifyPropertyChanged  
    {  
        readonly IList<Monkey> source;  
        Monkey selectedMonkey;  
        int selectionCount = 1;  
  
        public ObservableCollection<Monkey> Monkeys { get; private set; }  
        public IList<Monkey> EmptyMonkeys { get; private set; }  
  
        public Monkey SelectedMonkey  
        {  
            get  
            {  
                return selectedMonkey;  
            }  
            set  
            {  
                if (selectedMonkey != value)  
                {  
                    selectedMonkey = value;  
                }  
            }  
        }  
  
        ObservableCollection<object> selectedMonkeys;  
        public ObservableCollection<object> SelectedMonkeys  
        {  
            get  
            {  
                return selectedMonkeys;  
            }  
            set  
            {  
                if (selectedMonkeys != value)  
                {  
                    selectedMonkeys = value;  
                }  
            }  
        }  
  
        public string SelectedMonkeyMessage { get; private set; }  
  
        public ICommand DeleteCommand { get; set; }  
        public ICommand FavoriteCommand { get; set; }  
        public ICommand FilterCommand { get; set; }  
        public ICommand MonkeySelectionChangedCommand { get; set; }  
  
        public MonkeysViewModel()  
        {  
            source = new List<Monkey>();  
            CreateMonkeyCollection();  
  
            selectedMonkey = Monkeys.Skip(3).FirstOrDefault();  
            MonkeySelectionChanged();  
  
            SelectedMonkeys = new ObservableCollection<object>()  
            {  
                Monkeys[1], Monkeys[3], Monkeys[4]  
            };  
  
            DeleteCommand= new Command<Monkey>(RemoveMonkey);  
            FavoriteCommand = new Command<Monkey>(FavoriteMonkey);  
            FilterCommand = new Command<string>(FilterItems);  
            MonkeySelectionChangedCommand = new Command(MonkeySelectionChanged);  
        }  
  
        void CreateMonkeyCollection()  
        {  
            source.Add(new Monkey  
            {  
                Name = "Baboon",  
                Location = "Africa & Asia",  
                Details = "Baboons are African and Arabian Old World monkeys belonging to the genus Papio, part of the subfamily Cercopithecinae.",  
            });  
  
            source.Add(new Monkey  
            {  
                Name = "Capuchin Monkey",  
                Location = "Central & South America",  
                Details = "The capuchin monkeys are New World monkeys of the subfamily Cebinae. Prior to 2011, the subfamily contained only a single genus, Cebus.",  
            });  
  
            source.Add(new Monkey  
            {  
                Name = "Blue Monkey",  
                Location = "Central and East Africa",  
                Details = "The blue monkey or diademed monkey is a species of Old World monkey native to Central and East Africa, ranging from the upper Congo River basin east to the East African Rift and south to northern Angola and Zambia",  
            });  
  
            source.Add(new Monkey  
            {  
                Name = "Squirrel Monkey",  
                Location = "Central & South America",  
                Details = "The squirrel monkeys are the New World monkeys of the genus Saimiri. They are the only genus in the subfamily Saimirinae. The name of the genus Saimiri is of Tupi origin, and was also used as an English name by early researchers.",  
            });  
  
            source.Add(new Monkey  
            {  
                Name = "Golden Lion Tamarin",  
                Location = "Brazil",  
                Details = "The golden lion tamarin also known as the golden marmoset, is a small New World monkey of the family Callitrichidae.",  
            });  
  
            source.Add(new Monkey  
            {  
                Name = "Howler Monkey",  
                Location = "South America",  
                Details = "Howler monkeys are among the largest of the New World monkeys. Fifteen species are currently recognised. Previously classified in the family Cebidae, they are now placed in the family Atelidae.",  
            });  
  
            source.Add(new Monkey  
            {  
                Name = "Japanese Macaque",  
                Location = "Japan",  
                Details = "The Japanese macaque, is a terrestrial Old World monkey species native to Japan. They are also sometimes known as the snow monkey because they live in areas where snow covers the ground for months each",  
            });  
  
            source.Add(new Monkey  
            {  
                Name = "Mandrill",  
                Location = "Southern Cameroon, Gabon, Equatorial Guinea, and Congo",  
                Details = "The mandrill is a primate of the Old World monkey family, closely related to the baboons and even more closely to the drill. It is found in southern Cameroon, Gabon, Equatorial Guinea, and Congo.",  
            });  
  
            source.Add(new Monkey  
            {  
                Name = "Proboscis Monkey",  
                Location = "Borneo",  
                Details = "The proboscis monkey or long-nosed monkey, known as the bekantan in Malay, is a reddish-brown arboreal Old World monkey that is endemic to the south-east Asian island of Borneo.",  
            });  
  
            source.Add(new Monkey  
            {  
                Name = "Red-shanked Douc",  
                Location = "Vietnam, Laos",  
                Details = "The red-shanked douc is a species of Old World monkey, among the most colourful of all primates. This monkey is sometimes called the \"costumed ape\" for its extravagant appearance. From its knees to its ankles it sports maroon-red \"stockings\", and it appears to wear white forearm length gloves. Its attire is finished with black hands and feet. The golden face is framed by a white ruff, which is considerably fluffier in males. The eyelids are a soft powder blue. The tail is white with a triangle of white hair at the base. Males of all ages have a white spot on both sides of the corners of the rump patch, and red and white genitals.",  
            });  
  
            source.Add(new Monkey  
            {  
                Name = "Gray-shanked Douc",  
                Location = "Vietnam",  
                Details = "The gray-shanked douc langur is a douc species native to the Vietnamese provinces of Quảng Nam, Quảng Ngãi, Bình Định, Kon Tum, and Gia Lai. The total population is estimated at 550 to 700 individuals. In 2016, Dr Benjamin Rawson, Country Director of Fauna & Flora International - Vietnam Programme, announced a discovery of an additional population of more than 500 individuals found in Central Vietnam, bringing the total population up to approximately 1000 individuals.",  
            });  
  
            source.Add(new Monkey  
            {  
                Name = "Golden Snub-nosed Monkey",  
                Location = "China",  
                Details = "The golden snub-nosed monkey is an Old World monkey in the Colobinae subfamily. It is endemic to a small area in temperate, mountainous forests of central and Southwest China. They inhabit these mountainous forests of Southwestern China at elevations of 1,500-3,400 m above sea level. The Chinese name is Sichuan golden hair monkey. It is also widely referred to as the Sichuan snub-nosed monkey. Of the three species of snub-nosed monkeys in China, the golden snub-nosed monkey is the most widely distributed throughout China.",  
            });  
  
            source.Add(new Monkey  
            {  
                Name = "Black Snub-nosed Monkey",  
                Location = "China",  
                Details = "The black snub-nosed monkey, also known as the Yunnan snub-nosed monkey, is an endangered species of primate in the family Cercopithecidae. It is endemic to China, where it is known to the locals as the Yunnan golden hair monkey and the black golden hair monkey. It is threatened by habitat loss. It was named after Bishop Félix Biet.",  
            });  
  
            source.Add(new Monkey  
            {  
                Name = "Tonkin Snub-nosed Monkey",  
                Location = "Vietnam",  
                Details = "The Tonkin snub-nosed monkey or Dollman's snub-nosed monkey is a slender-bodied arboreal Old World monkey, endemic to northern Vietnam. It is a black and white monkey with a pink nose and lips and blue patches round the eyes. It is found at altitudes of 200 to 1,200 m (700 to 3,900 ft) on fragmentary patches of forest on craggy limestone areas. First described in 1912, the monkey was rediscovered in 1990 but is exceedingly rare. In 2008, fewer than 250 individuals were thought to exist, and the species was the subject of intense conservation effort. The main threats faced by these monkeys is habitat loss and hunting, and the International Union for Conservation of Nature has rated the species as \"critically endangered\".",  
            });  
  
            source.Add(new Monkey  
            {  
                Name = "Thomas's Langur",  
                Location = "Indonesia",  
                Details = "Thomas's langur is a species of primate in the family Cercopithecidae. It is endemic to North Sumatra, Indonesia. Its natural habitat is subtropical or tropical dry forests. It is threatened by habitat loss. Its native names are reungkah in Acehnese and kedih in Alas.",  
            });  
  
            source.Add(new Monkey  
            {  
                Name = "Purple-faced Langur",  
                Location = "Sri Lanka",  
                Details = "The purple-faced langur, also known as the purple-faced leaf monkey, is a species of Old World monkey that is endemic to Sri Lanka. The animal is a long-tailed arboreal species, identified by a mostly brown appearance, dark face (with paler lower face) and a very shy nature. The species was once highly prevalent, found in suburban Colombo and the \"wet zone\" villages (areas with high temperatures and high humidity throughout the year, whilst rain deluges occur during the monsoon seasons), but rapid urbanization has led to a significant decrease in the population level of the monkeys.",  
            });  
  
            source.Add(new Monkey  
            {  
                Name = "Gelada",  
                Location = "Ethiopia",  
                Details = "The gelada, sometimes called the bleeding-heart monkey or the gelada baboon, is a species of Old World monkey found only in the Ethiopian Highlands, with large populations in the Semien Mountains. Theropithecus is derived from the Greek root words for \"beast-ape.\" Like its close relatives the baboons, it is largely terrestrial, spending much of its time foraging in grasslands.",  
            });  
  
            source.Add(new Monkey  
            {  
                Name = "Baboon",  
                Location = "Africa & Asia",  
                Details = "Baboons are African and Arabian Old World monkeys belonging to the genus Papio, part of the subfamily Cercopithecinae.",  
            });  
  
            source.Add(new Monkey  
            {  
                Name = "Capuchin Monkey",  
                Location = "Central & South America",  
                Details = "The capuchin monkeys are New World monkeys of the subfamily Cebinae. Prior to 2011, the subfamily contained only a single genus, Cebus.",  
            });  
  
            source.Add(new Monkey  
            {  
                Name = "Blue Monkey",  
                Location = "Central and East Africa",  
                Details = "The blue monkey or diademed monkey is a species of Old World monkey native to Central and East Africa, ranging from the upper Congo River basin east to the East African Rift and south to northern Angola and Zambia",  
            });  
  
            source.Add(new Monkey  
            {  
                Name = "Squirrel Monkey",  
                Location = "Central & South America",  
                Details = "The squirrel monkeys are the New World monkeys of the genus Saimiri. They are the only genus in the subfamily Saimirinae. The name of the genus Saimiri is of Tupi origin, and was also used as an English name by early researchers.",  
            });  
  
            source.Add(new Monkey  
            {  
                Name = "Golden Lion Tamarin",  
                Location = "Brazil",  
                Details = "The golden lion tamarin also known as the golden marmoset, is a small New World monkey of the family Callitrichidae.",  
            });  
  
            source.Add(new Monkey  
            {  
                Name = "Howler Monkey",  
                Location = "South America",  
                Details = "Howler monkeys are among the largest of the New World monkeys. Fifteen species are currently recognised. Previously classified in the family Cebidae, they are now placed in the family Atelidae.",  
            });  
  
            source.Add(new Monkey  
            {  
                Name = "Japanese Macaque",  
                Location = "Japan",  
                Details = "The Japanese macaque, is a terrestrial Old World monkey species native to Japan. They are also sometimes known as the snow monkey because they live in areas where snow covers the ground for months each",  
            });  
  
            source.Add(new Monkey  
            {  
                Name = "Mandrill",  
                Location = "Southern Cameroon, Gabon, Equatorial Guinea, and Congo",  
                Details = "The mandrill is a primate of the Old World monkey family, closely related to the baboons and even more closely to the drill. It is found in southern Cameroon, Gabon, Equatorial Guinea, and Congo.",  
            });  
  
            source.Add(new Monkey  
            {  
                Name = "Proboscis Monkey",  
                Location = "Borneo",  
                Details = "The proboscis monkey or long-nosed monkey, known as the bekantan in Malay, is a reddish-brown arboreal Old World monkey that is endemic to the south-east Asian island of Borneo.",  
            });  
  
            source.Add(new Monkey  
            {  
                Name = "Red-shanked Douc",  
                Location = "Vietnam, Laos",  
                Details = "The red-shanked douc is a species of Old World monkey, among the most colourful of all primates. This monkey is sometimes called the \"costumed ape\" for its extravagant appearance. From its knees to its ankles it sports maroon-red \"stockings\", and it appears to wear white forearm length gloves. Its attire is finished with black hands and feet. The golden face is framed by a white ruff, which is considerably fluffier in males. The eyelids are a soft powder blue. The tail is white with a triangle of white hair at the base. Males of all ages have a white spot on both sides of the corners of the rump patch, and red and white genitals.",  
            });  
  
            source.Add(new Monkey  
            {  
                Name = "Gray-shanked Douc",  
                Location = "Vietnam",  
                Details = "The gray-shanked douc langur is a douc species native to the Vietnamese provinces of Quảng Nam, Quảng Ngãi, Bình Định, Kon Tum, and Gia Lai. The total population is estimated at 550 to 700 individuals. In 2016, Dr Benjamin Rawson, Country Director of Fauna & Flora International - Vietnam Programme, announced a discovery of an additional population of more than 500 individuals found in Central Vietnam, bringing the total population up to approximately 1000 individuals.",  
            });  
  
            source.Add(new Monkey  
            {  
                Name = "Golden Snub-nosed Monkey",  
                Location = "China",  
                Details = "The golden snub-nosed monkey is an Old World monkey in the Colobinae subfamily. It is endemic to a small area in temperate, mountainous forests of central and Southwest China. They inhabit these mountainous forests of Southwestern China at elevations of 1,500-3,400 m above sea level. The Chinese name is Sichuan golden hair monkey. It is also widely referred to as the Sichuan snub-nosed monkey. Of the three species of snub-nosed monkeys in China, the golden snub-nosed monkey is the most widely distributed throughout China.",  
            });  
  
            source.Add(new Monkey  
            {  
                Name = "Black Snub-nosed Monkey",  
                Location = "China",  
                Details = "The black snub-nosed monkey, also known as the Yunnan snub-nosed monkey, is an endangered species of primate in the family Cercopithecidae. It is endemic to China, where it is known to the locals as the Yunnan golden hair monkey and the black golden hair monkey. It is threatened by habitat loss. It was named after Bishop Félix Biet.",  
            });  
  
            source.Add(new Monkey  
            {  
                Name = "Tonkin Snub-nosed Monkey",  
                Location = "Vietnam",  
                Details = "The Tonkin snub-nosed monkey or Dollman's snub-nosed monkey is a slender-bodied arboreal Old World monkey, endemic to northern Vietnam. It is a black and white monkey with a pink nose and lips and blue patches round the eyes. It is found at altitudes of 200 to 1,200 m (700 to 3,900 ft) on fragmentary patches of forest on craggy limestone areas. First described in 1912, the monkey was rediscovered in 1990 but is exceedingly rare. In 2008, fewer than 250 individuals were thought to exist, and the species was the subject of intense conservation effort. The main threats faced by these monkeys is habitat loss and hunting, and the International Union for Conservation of Nature has rated the species as \"critically endangered\".",  
            });  
  
            source.Add(new Monkey  
            {  
                Name = "Thomas's Langur",  
                Location = "Indonesia",  
                Details = "Thomas's langur is a species of primate in the family Cercopithecidae. It is endemic to North Sumatra, Indonesia. Its natural habitat is subtropical or tropical dry forests. It is threatened by habitat loss. Its native names are reungkah in Acehnese and kedih in Alas.",  
            });  
  
            source.Add(new Monkey  
            {  
                Name = "Purple-faced Langur",  
                Location = "Sri Lanka",  
                Details = "The purple-faced langur, also known as the purple-faced leaf monkey, is a species of Old World monkey that is endemic to Sri Lanka. The animal is a long-tailed arboreal species, identified by a mostly brown appearance, dark face (with paler lower face) and a very shy nature. The species was once highly prevalent, found in suburban Colombo and the \"wet zone\" villages (areas with high temperatures and high humidity throughout the year, whilst rain deluges occur during the monsoon seasons), but rapid urbanization has led to a significant decrease in the population level of the monkeys.",  
            });  
  
            source.Add(new Monkey  
            {  
                Name = "Gelada",  
                Location = "Ethiopia",  
                Details = "The gelada, sometimes called the bleeding-heart monkey or the gelada baboon, is a species of Old World monkey found only in the Ethiopian Highlands, with large populations in the Semien Mountains. Theropithecus is derived from the Greek root words for \"beast-ape.\" Like its close relatives the baboons, it is largely terrestrial, spending much of its time foraging in grasslands.",  
            });  
  
            Monkeys = new ObservableCollection<Monkey>(source);  
        }  
  
        void FilterItems(string filter)  
        {  
            var filteredItems = source.Where(monkey => monkey.Name.ToLower().Contains(filter.ToLower())).ToList();  
            foreach (var monkey in source)  
            {  
                if (!filteredItems.Contains(monkey))  
                {  
                    Monkeys.Remove(monkey);  
                }  
                else  
                {  
                    if (!Monkeys.Contains(monkey))  
                    {  
                        Monkeys.Add(monkey);  
                    }  
                }  
            }  
        }  
  
        void MonkeySelectionChanged()  
        {  
            SelectedMonkeyMessage = $"Selection {selectionCount}: {SelectedMonkey.Name}";  
            OnPropertyChanged("SelectedMonkeyMessage");  
            selectionCount++;  
        }  
  
        void RemoveMonkey(Monkey monkey)  
        {  
            if (Monkeys.Contains(monkey))  
            {  
                Monkeys.Remove(monkey);  
            }  
        }  
  
        void FavoriteMonkey(Monkey monkey)  
        {  
            monkey.IsFavorite = !monkey.IsFavorite;  
        }  
  
        #region INotifyPropertyChanged  
        public event PropertyChangedEventHandler PropertyChanged;  
  
        void OnPropertyChanged([CallerMemberName] string propertyName = null)  
        {  
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));  
        }  
        #endregion  
    }  
}  
Xamarin
Xamarin
A Microsoft open-source app platform for building Android and iOS apps with .NET and C#.
5,380 questions
0 comments No comments
{count} votes

Accepted answer
  1. Igor Kravchenko 281 Reputation points
    2021-04-07T20:04:05.387+00:00

    That sample has so much counter-performant issues.

    1. Image with UriImageSource.
    NEVER use UriImageSource. I don't know why but UriImageSource is really blocking UI thread.

    <Image Grid.RowSpan="2"
    Source="{Binding ImageUrl}"
    Aspect="AspectFill"
    HeightRequest="60"
    WidthRequest="60" />

    I need some time to show how to load image better.
    Try this:

    public class BytesImageSource : ImageSource
        {
            public override bool IsEmpty
            {
                get
                {
                    return this.Buffer == null || this.Buffer.Length <= 0;
                }
            }
    
            public static readonly BindableProperty BufferProperty = BindableProperty.Create(nameof(Buffer), typeof(byte[]),
               typeof(BytesImageSource), default(byte[]), BindingMode.TwoWay, propertyChanged: OnBufferChanged);
    
            public byte[] Buffer
            {
                get { return (byte[])GetValue(BufferProperty); }
                set { SetValue(BufferProperty, value); }
            }
    
            private static void OnBufferChanged(BindableObject bindable, object oldValue, object newValue)
            {
                if (!(bindable is BytesImageSource imageSource))
                {
                    return;
                }
                imageSource.OnSourceChanged();
            }
        }
    

    In Android:

    public class BytesImageSourceHandler : IImageSourceHandler
        {
            public Task<Bitmap> LoadImageAsync(ImageSource imagesource, Context context, CancellationToken cancelationToken = default)
            {
                if (!(imagesource is BytesImageSource byteArrayImageSource))
                {
                    return Task.FromResult<Bitmap>(null);
                }
                if (byteArrayImageSource.Buffer == null || byteArrayImageSource.Buffer.Length <= 0)
                {
                    return Task.FromResult<Bitmap>(null);
                }
                return BitmapFactory.DecodeByteArrayAsync(byteArrayImageSource.Buffer, 0, byteArrayImageSource.Buffer.Length);
            }
        }
    

    In MainActivity in OnCreate method before XF initialize add this

    Xamarin.Forms.Internals.Registrar.Registered.Register(typeof(BytesImageSource), typeof(BytesImageSourceHandler));
    

    In Monkey class add property

    public byte[] ImageBytes { get; set; }
    

    In MonkeysViewModel we will load all images:

    public async Task LoadImages()
            {
                List<Task> tasks = new List<Task>();
                foreach (Monkey monkey in this.Monkeys)
                {
                    Task task = Task.Run(() => this.LoadImage(monkey));
                    tasks.Add(task);
                }
                await Task.WhenAll(tasks);
            }
    

    And call it in page by OnAppearing:

    public MonkeysViewModel ViewModel => (MonkeysViewModel)this.BindingContext;
    
            public VerticalListSwipeContextItemsPage()
            {
                InitializeComponent();
                BindingContext = new MonkeysViewModel();            
            }
    
            protected override async void OnAppearing()
            {
                base.OnAppearing();
                await ViewModel.LoadImages();
            }
    

    2. Grid row height as Auto is much slower than fixed size.
    So, instead of this:

    RowDefinitions="Auto, Auto"
    ColumnDefinitions="Auto, Auto"
    

    Use this:

    RowDefinitions="30, 30"
    ColumnDefinitions="60, *"
    

    3. Another less important but terrible things are in viewModel:

        public ICommand DeleteCommand => new Command<Monkey>(RemoveMonkey);
        public ICommand FavoriteCommand => new Command<Monkey>(FavoriteMonkey);
        public ICommand FilterCommand => new Command<string>(FilterItems);
        public ICommand MonkeySelectionChangedCommand => new Command(MonkeySelectionChanged);
    

    For every "get" you will create new object.
    Much better is:

    public ICommand DeleteCommand { get; }
    
    public MonkeysViewModel()
            {
                ...
                this.DeleteCommand = new Command<Monkey>(RemoveMonkey);
            }
    

    Check this video https://youtu.be/RZvdql3Ev0E

    Update
    The most problem of this sample is working with images.
    I made some experiments. And created custom ImageView with simple Android implementation. It is similar to XF Image. But XF Image is too complex and I need to spend more time to investigate what exactly freezes UI.
    Shared control:

    public class ImageView : View
        {
            public static readonly BindableProperty BufferProperty = BindableProperty.Create(nameof(Buffer), typeof(byte[]),
              typeof(BytesImageSource), default(byte[]), BindingMode.TwoWay);
    
            public byte[] Buffer
            {
                get { return (byte[])GetValue(BufferProperty); }
                set { SetValue(BufferProperty, value); }
            }
        }
    

    Android Renderer:

    [assembly: ExportRenderer(typeof(SharedImageView), typeof(ImageViewRenderer))]
    namespace ...
    public class ImageViewRenderer : ViewRenderer<SharedImageView, ImageView>
        {
            public ImageViewRenderer(Context context) : base(context)
            {
            }
    
            protected override ImageView CreateNativeControl()
            {
                return new ImageView(this.Context);
            }
    
            protected override async void OnElementChanged(ElementChangedEventArgs<SharedImageView> e)
            {
                base.OnElementChanged(e);
                if (e.OldElement == null)
                {
                    var view = CreateNativeControl();
                    SetNativeControl(view);
                }
                await this.SetBitmap();
            }
    
            protected override async void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
            {
                base.OnElementPropertyChanged(sender, e);
                if (e.PropertyName == nameof(SharedImageView.Buffer))
                {
                    await this.SetBitmap();
                }
            }
    
            protected virtual async Task SetBitmap()
            {
                if (this.Element.Buffer == null || this.Element.Buffer.Length <= 0)
                    return;
                if (this.Control == null)
                    return;
                //AspectFill hardcoded instead of creating BindableProperty Aspect in shared control
                this.Control.SetScaleType(ImageView.ScaleType.CenterCrop);
                Bitmap originalBitmap = await BitmapFactory.DecodeByteArrayAsync(this.Element.Buffer, 0, this.Element.Buffer.Length);
                this.Control.SetImageBitmap(originalBitmap);
            }
        }
    

    Use:

    <controls:ImageView Grid.RowSpan="2"
                                       Buffer="{Binding ImageBytes, Mode=OneWay}"
                                       HeightRequest="60" 
                                       WidthRequest="60"/>
    

    You can check original XF android renderers:
    Renderer
    Fast renderer (used by default)

    And one more important thing.
    Ideally you need to get already resized image from API.
    To resize it on device you can use this (using DependencyService):
    Shared:

    public interface IImageResizer
        {
            Task<byte[]> Resize(byte[] buffer, int width, int height);
        }
    

    Android:

    [assembly: Dependency(typeof(ImageResizer))]
    namespace ...
    public class ImageResizer : IImageResizer
        {
            public static int CalculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight)
            {
                int height = options.OutHeight;
                int width = options.OutWidth;
                int inSampleSize = 1;
    
                if (height > reqHeight || width > reqWidth)
                {
                    int halfHeight = height / 2;
                    int halfWidth = width / 2;
    
                    while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth)
                    {
                        inSampleSize *= 2;
                    }
                }
    
                return inSampleSize;
            }
    
            public async Task<byte[]> Resize(byte[] buffer, int width, int height)
            {
                BitmapFactory.Options options = new BitmapFactory.Options();
                options.InJustDecodeBounds = true;
                await BitmapFactory.DecodeByteArrayAsync(buffer, 0, buffer.Length, options);
    
                // Calculate inSampleSize
                options.InSampleSize = CalculateInSampleSize(options, width, height);
    
                // Decode bitmap with inSampleSize set
                options.InJustDecodeBounds = false;
                Bitmap resizedBitmap = await BitmapFactory.DecodeByteArrayAsync(buffer, 0, buffer.Length, options);
                using (MemoryStream stream = new MemoryStream())
                {
                    await resizedBitmap.CompressAsync(Bitmap.CompressFormat.Jpeg, 100, stream);
                    byte[] resizedBuffer = stream.ToArray();
                    return resizedBuffer;
                }
            }
        }
    

    In ViewModel:

    private async Task LoadImage(Monkey monkey)
            {
                try
                {
                    using (HttpClient httpClient = new HttpClient())
                    {
                        byte[] buffer = await httpClient.GetByteArrayAsync(monkey.ImageUrl);
                        monkey.ImageBytes = await DependencyService.Get<IImageResizer>().Resize(buffer, 60, 60);
                    }
                }
                catch (HttpRequestException e)
                {
                    Console.WriteLine(e.Message);
                }
            }
    

    More here
    https://developer.android.com/topic/performance/graphics/load-bitmap.html


0 additional answers

Sort by: Most helpful

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.