Create a Beautiful Photo Gallery Using .NET MAUI Tab View and ListView
Calvince Moth

Calvince Moth @calvinceawitim

About: Hello, I'm Calvince Moth, a dedicated blogger specializing in technology and personal development. Since I began blogging, I have focused on exploring the latest trends in tech and sharing valuable in

Location:
Kisumu,Kenya.
Joined:
Oct 28, 2024

Create a Beautiful Photo Gallery Using .NET MAUI Tab View and ListView

Publish Date: May 19
0 0

TL;DR: Want to build a sleek photo gallery in your .NET MAUI app? Learn how to combine Syncfusion’s Tab View and ListView controls to create a visually appealing, organized gallery. Follow step-by-step instructions to display, group, and navigate photos and albums with dynamic data binding, interactive layouts, and custom styles. Perfect for showcasing images with smooth performance and intuitive UI.

In today’s mobile-first world, users expect visually rich apps. Especially when it comes to photos. Whether it’s travel memories or event captures, organizing images in a sleek and interactive gallery enhances user experience.

In this tutorial, you’ll learn how to build a stunning photo gallery app using .NET MAUI and Syncfusion’s Tab View and ListView controls. We’ll guide you through step-by-step implementation with dynamic data, grouped layouts, and custom styles, optimized for performance across Android, iOS, and Windows.

Why use Syncfusion .NET MAUI Tab View?

The .NET MAUI Tab View provides:

  • Navigation: An intuitive way to organize and navigate through different parts of your app.
  • Customization: Enables unique and attractive UI through customizable headers and content areas.
  • Swiping: Seamless swipe functionality to switch between tabs.

Why use Syncfusion .NET MAUI ListView?

The .NET MAUI List View is excellent for building a photo gallery because:

  • Item templates: It supports rich UI through item templating.
  • Layouts: Supports both linear and grid layouts, ideal for a gallery.
  • Grouping: Offers customizable group headers with sticky views.

Steps to create your stunning photo gallery

Follow these steps to create your stunning photo gallery:

1. Create a simple .NET MAUI Tab View

Start by creating a MAUI application. Refer to the documentation for detailed instructions on setup. Include the following basic Tab View:

<tabView:SfTabView />

Enter fullscreen mode Exit fullscreen mode

2. Add multiple tabs with headers

Add tabs with different headers to categorize your images:

<tabView:SfTabView x:Name="tabView" TabBarPlacement="Bottom">
    <tabView:SfTabView.Items>
        <tabView:SfTabItem Header="Photos" ImageSource="photos.png" />
        <tabView:SfTabItem Header="Albums" ImageSource="albums.png" />
        <tabView:SfTabItem Header="Favorites" ImageSource="favorites.png" />
    </tabView:SfTabView.Items>
</tabView:SfTabView>

Enter fullscreen mode Exit fullscreen mode

3 Add ListView as content for each tab

You can refer to the documentation to create the Maui ListView. Include a List View for displaying photos within each tab. Here’s how you can set it up:

<tabView:SfTabItem Header="Photos" ImageSource="photos.png">
    <tabView:SfTabItem.Content>
        <listView:SfListView>
            <listView:SfListView.ItemTemplate>
                <DataTemplate>  
                    <!-- Bind your ImageData here -->
                </DataTemplate>
            </listView:SfListView.ItemTemplate>
        </listView:SfListView>
    </tabView:SfTabItem.Content>
</tabView:SfTabItem>

Enter fullscreen mode Exit fullscreen mode

4. Populate items in the ListView

1. Define model classes

Create model classes to hold image data, such as ImageInfo and AlbumInfo. Define properties related to each photo:

ImageInfo.cs

public class ImageInfo : INotifyPropertyChanged
{
    private string imageName;
    private string image;
    private string size;
    private DateTime dateTime;
    private bool isFavorite;

    // Properties with Change Notification
    public string ImageName
    {
        get => imageName;
        set
        {
            imageName = value;
            OnPropertyChanged();
        }
    }

    // Similar properties for other fields...

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

Enter fullscreen mode Exit fullscreen mode

AlbumInfo.cs

public class AlbumInfo
{
    public string AlbumName { get; set; }
    public string CoverImage { get; set; }
    public double Count { get; set; }
    public ObservableCollection<ImageInfo> Photos { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

2. Create a ViewModel to manage your data

Use a GalleryViewModel class to generate and manage collections of images and albums.

GalleryViewModel.cs

public class GalleryViewModel
{
    public ObservableCollection<ImageInfo> Photos { get; set; }
    public ObservableCollection<ImageInfo> Countries { get; set; }
    public ObservableCollection<ImageInfo> Birds { get; set; }
    public ObservableCollection<ImageInfo> Electronics { get; set; }
    public ObservableCollection<ImageInfo> Foods { get; set; }
    public ObservableCollection<ImageInfo> Recents { get; set; }
    public ObservableCollection<ImageInfo> Favorites { get; set; }
    public ObservableCollection<AlbumInfo> Albums { get; set; }

    public GalleryViewModel()
    {
        GeneratePhotos();
        GenerateAlbums();
    }

    // Method to generate a collection of images
    private void GeneratePhotos()
    {
        DateTime dateTime = DateTime.Now.Date;
        Photos = new ObservableCollection<ImageInfo>
        {
            new ImageInfo { ImageName = "place_1", Image = "place_1.jpg", Size = "2MB", DateTime = dateTime.AddHours(15) },
            new ImageInfo { ImageName = "bird01", Image = "bird01.png", Size = "2MB", DateTime = dateTime.AddDays(-1).AddHours(8) },
            new ImageInfo { ImageName = "India", Image = "india.jpg", Size = "2MB", DateTime = dateTime.AddDays(-27).AddHours(10), IsFavorite = true },
            // Additional images...
        };

        // Consolidate images into a single 'Recents' collection
        Recents = new ObservableCollection<ImageInfo>();
        foreach (var category in new[] { Photos, Countries, Birds, Foods, Electronics })
        {
            Recents = new ObservableCollection<ImageInfo>(Recents.Concat(category));
        }

        // Sort recent images by date
        var sortedRecentList = Recents.OrderByDescending(item => item.DateTime).ToList();
        Recents.Clear();
        foreach (var item in sortedRecentList)
        {
            Recents.Add(item);
        }

        // Initialize favorites
        Favorites = new ObservableCollection<ImageInfo>();
        foreach (var photo in Recents)
        {
            if (photo.IsFavorite)
            {
                Favorites.Add(photo);
            }
        }
    }

    // Method to generate albums
    private void GenerateAlbums()
    {
        Albums = new ObservableCollection<AlbumInfo>
        {
            new AlbumInfo { AlbumName = "Recents", CoverImage = "place_1.jpg", Count = 58, Photos = Recents },
            new AlbumInfo { AlbumName = "Countries", CoverImage = "argentina.jpg", Count = 13, Photos = Countries },
            new AlbumInfo { AlbumName = "Electronics", CoverImage = "blindinglights.png", Count = 8, Photos = Electronics },
            new AlbumInfo { AlbumName = "Birds", CoverImage = "bird01.png", Count = 10, Photos = Birds },
            new AlbumInfo { AlbumName = "Foods", CoverImage = "bread.png", Count = 15, Photos = Foods },
        };
    }
}

Enter fullscreen mode Exit fullscreen mode

5. Bind the image collection to the ListView

In your MainPage.xaml, define the layout and bind your image data to display within the ListView:

<tabView:SfTabView x:Name="tabView" TabBarPlacement="Bottom">

    <!-- Photos Tab -->
    <tabView:SfTabItem Header="Photos" ImageSource="photos.png">
        <listView:SfListView
            x:Name="listViewPhotos"
            ItemsSource="{Binding Photos}"
            SelectionMode="None"
            ItemSize="90"
            ItemSpacing="5">

            <listView:SfListView.ItemsLayout>
                <listView:GridLayout SpanCount="4" />
            </listView:SfListView.ItemsLayout>

            <listView:SfListView.ItemTemplate>
                <DataTemplate>
                    <Image
                        Source="{Binding Image}"
                        Aspect="AspectFill"
                        HeightRequest="88"
                        WidthRequest="88" />
                </DataTemplate>
            </listView:SfListView.ItemTemplate>

        </listView:SfListView>
    </tabView:SfTabItem>

    <!-- Albums Tab -->
    <tabView:SfTabItem Header="Albums" ImageSource="albums.png">
        <listView:SfListView
            ItemsSource="{Binding Albums}"
            ItemSize="230"
            SelectionMode="None">

            <listView:SfListView.ItemsLayout>
                <listView:GridLayout SpanCount="2" />
            </listView:SfListView.ItemsLayout>

            <listView:SfListView.ItemTemplate>
                <DataTemplate>
                    <Grid RowDefinitions="*,20,20">
                        <Image
                            Source="{Binding CoverImage}"
                            Aspect="Fill"
                            HeightRequest="180"
                            WidthRequest="180" />

                        <Label
                            Text="{Binding AlbumName}"
                            FontSize="15"
                            Margin="10,0,0,0"
                            Grid.Row="1" />

                        <Label
                            Text="{Binding Count, StringFormat='{0} items'}"
                            Margin="10,0,0,0"
                            FontSize="12"
                            Grid.Row="2" />
                    </Grid>
                </DataTemplate>
            </listView:SfListView.ItemTemplate>

        </listView:SfListView>
    </tabView:SfTabItem>

    <!-- Favorites Tab -->
    <tabView:SfTabItem Header="Favorites" ImageSource="favorites.png">
        <listView:SfListView
            ItemsSource="{Binding Favorites, Mode=TwoWay}"
            ItemSize="300"
            ItemSpacing="10"
            SelectionMode="None"
            AutoFitMode="Height">

            <listView:SfListView.ItemsLayout>
                <listView:GridLayout SpanCount="2" />
            </listView:SfListView.ItemsLayout>

            <listView:SfListView.ItemTemplate>
                <DataTemplate>
                    <Grid RowDefinitions="30,*">
                        <Label
                            Text="{Binding DateTime, StringFormat='{0:ddd, dd MMM, yyyy}'}"
                            FontAttributes="Bold"
                            FontSize="15" />

                        <Image
                            Source="{Binding Image}"
                            Aspect="AspectFill"
                            Grid.Row="1" />
                    </Grid>
                </DataTemplate>
            </listView:SfListView.ItemTemplate>

        </listView:SfListView>
    </tabView:SfTabItem>

</tabView:SfTabView>

Enter fullscreen mode Exit fullscreen mode

Photos


Photos

Albums


Albums

6. Create pages to display images

Step 1: Create a page to display an individual image

You need a simple content page to show a single image when a user selects it from your photo gallery.

ImagePage.Xaml

<ContentPage
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    x:Class="PhotoGallery.ImagePage"
    Title="Image Details">

    <Grid RowDefinitions="*,60">
        <Image
            Source="{Binding Image}"
            Aspect="AspectFit" />
    </Grid>

</ContentPage>

Enter fullscreen mode Exit fullscreen mode

Step 2: Create a page to display images from an album

To show a collection of images within an album, create another content page. This will use a List to display all images associated with a selected album.

AlbumCollectionPage.Xaml

<ContentPage
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:listView="clr-namespace:Syncfusion.Maui.ListView;assembly=Syncfusion.Maui.ListView"
    x:Class="PhotoGallery.AlbumCollectionPage"
    Title="{Binding AlbumName}">

    <Grid>

        <listView:SfListView
            x:Name="listView"
            ItemsSource="{Binding Photos}"
            SelectionMode="None"
            ItemSize="90"
            ItemSpacing="5">

            <listView:SfListView.ItemsLayout>
                <listView:GridLayout SpanCount="4" />
            </listView:SfListView.ItemsLayout>

            <listView:SfListView.ItemTemplate>
                <DataTemplate>
                    <Image
                        Source="{Binding Image}"
                        Aspect="AspectFill"
                        HeightRequest="88"
                        WidthRequest="88" />
                </DataTemplate>
            </listView:SfListView.ItemTemplate>

        </listView:SfListView>

    </Grid>

</ContentPage>

Enter fullscreen mode Exit fullscreen mode

7. Implementing navigation with tapped events

Step 1: Define the ItemTapped event in XAML

In your XAML file, define the ItemTapped event in ListView for navigation. This allows users to navigate to a detailed view of the photo when they tap on an image in the list.

MainPage.Xaml

<tabView:SfTabView x:Name="tabView" TabBarPlacement="Bottom">

    <tabView:SfTabItem 
        x:Name="photosTab" 
        Header="Photos" 
        ImageSource="photos.png" 
        ImageTextSpacing="5">

        <!-- ListView for displaying photos -->
        <listView:SfListView 
            x:Name="listViewPhotos" 
            ItemsSource="{Binding Photos}" 
            SelectionMode="None"
            ItemSize="90" 
            ItemSpacing="5" 
            ItemTapped="OnPhotosItemTapped">

            <!-- ItemTemplate and other configurations -->

        </listView:SfListView>

    </tabView:SfTabItem>

    ...

</tabView:SfTabView>

Enter fullscreen mode Exit fullscreen mode

Step 2: Implement navigation in the code-behind

In your code-behind file, implement the logic to navigate to the image details page when an item is tapped.

MainPage.Xaml.cs

private void OnPhotosItemTapped(object sender, Syncfusion.Maui.ListView.ItemTappedEventArgs e)
        {
            // Create an instance of ImagePage
            ImagePage imagePage = new ImagePage(this.BindingContext as GalleryViewModel, e.DataItem as ImageInfo); 
            imagePage.BindingContext = e.DataItem as ImageInfo; // Pass the selected image to the new page

            // Navigate to the ImagePage
            Navigation.PushAsync(imagePage);
        }

Enter fullscreen mode Exit fullscreen mode

8. Customizing the Tab View for selection

Step 1: Create a style for TabViewItem

Define styles to customize the text color of TabViewItem based on the selection state using visual states.

MainPage.Xaml

<ContentPage.Resources>
        <Style TargetType="tabView:SfTabItem">
            <Setter Property="VisualStateManager.VisualStateGroups">
                <VisualStateGroupList>
                    <VisualStateGroup>

                        <VisualState x:Name="Normal">
                            <VisualState.Setters>
                                <Setter Property="TextColor" Value="#000000" />
                            </VisualState.Setters>
                        </VisualState>

                        <VisualState x:Name="Selected">
                            <VisualState.Setters>
                                <Setter Property="TextColor" Value="#6750A4" />
                            </VisualState.Setters>
                        </VisualState>

                    </VisualStateGroup>
                </VisualStateGroupList>
            </Setter>
        </Style>
    </ContentPage.Resources>

Enter fullscreen mode Exit fullscreen mode

Step 2: Customize the tab header

Customize the tab header’s appearance and behavior by setting properties such as IndicatorStrokeThickness, TabBarHeight, and using FontImageSource for icons.

MainPage.Xaml

<tabView:SfTabView 
    x:Name="tabView" 
    TabBarPlacement="Bottom" 
    EnableSwiping="True" 
    IndicatorStrokeThickness="0" 
    TabBarHeight="55">

    <tabView:SfTabItem 
        Header="Photos" 
        x:Name="photosTab" 
        ImageTextSpacing="5">

        <tabView:SfTabItem.ImageSource>
            <FontImageSource 
                Glyph="" 
                Color="{Binding Source={x:Reference photosTab}, Path=TextColor}" 
                FontFamily="PhotoGallery"/>
        </tabView:SfTabItem.ImageSource>

    </tabView:SfTabItem>

    <tabView:SfTabItem 
        Header="Albums" 
        x:Name="albumsTab" 
        ImageTextSpacing="5">

        <!-- Similar setup for other tabs -->

    </tabView:SfTabItem>

</tabView:SfTabView>

Enter fullscreen mode Exit fullscreen mode

9. Group the ListView images

Group images in your ListView based on a property, such as DateTime, by using GroupDescriptor.

MainPage.Xaml:

public MainPage()
        {
            InitializeComponent();

            listViewPhotos.DataSource.GroupDescriptors.Add(new GroupDescriptor()
            {
                PropertyName = "DateTime",
                KeySelector = (object obj1) =>
                {
                    var item = obj1 as ImageInfo;
                    if (item != null)
                    {
                        if (item.DateTime.Date == DateTime.Now.Date)
                        {
                            return "Today";
                        }
                        else if (item.DateTime.Date == DateTime.Now.Date.AddDays(-1))
                        {
                            return "Yesterday";
                        }
                        else if (item.DateTime.Year == DateTime.Now.Year)
                        {
                            return item.DateTime.ToString("ddd, dd MMM", CultureInfo.InvariantCulture);
                        }
                        else
                        {
                            return item.DateTime.ToString("ddd, dd MMM, yyyy", CultureInfo.InvariantCulture);
                        }
                    }
                    else
                    {
                        return "";
                    }
                }
            });

        }

Enter fullscreen mode Exit fullscreen mode

<alt-text>


Grouping the ListView images

10. Customize the image display page

Step 1: Create a converter for favorite icon

Implement a converter to change the display of favorite icons based on whether an image is marked as a favorite.

FavoriteToIconConverter

public class FavoriteToIconConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value is bool isFavorite)
            {
                // Assume '' is the outline and '' is the filled icon.
                return isFavorite ? "\uE707" : "\uE706";
            }
            return "\uE706"; // Default to outline
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

Enter fullscreen mode Exit fullscreen mode

Step 2: Handle property changes for adding to favorites

Update property change events in your ViewModel to handle the dynamic addition and removal of images from the favorites collection.

GalleryViewModel.cs

private void GeneratePhotos()
{
    ...
    foreach (var category in new[] { Photos, Countries, Birds, Foods, Electronics })
    {
        foreach (var photo in category)
        {
            photo.PropertyChanged += ImageInfo_PropertyChanged;
        }

        // Add to Recents
        Recents = new ObservableCollection<ImageInfo>(Recents.Concat(category));
    }
    ...
}

private void ImageInfo_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    if (sender is ImageInfo imageInfo && e.PropertyName == nameof(ImageInfo.IsFavorite))
    {
        if (imageInfo.IsFavorite && !Favorites.Contains(imageInfo))
        {
            Favorites.Add(imageInfo);
        }
        else if (!imageInfo.IsFavorite && Favorites.Contains(imageInfo))
        {
            Favorites.Remove(imageInfo);
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

Step 3: Implement UI actions in the image display page

Use the converter in your XAML to update the UI and define actions for user interactions like tapping icons.

ImagePage.Xaml

<ContentPage.Resources>
    <local:FavoriteToIconConverter x:Key="FavoriteToIconConverter"/>
</ContentPage.Resources>

<Grid RowDefinitions="*,60">
    <Image Source="{Binding Image}" Aspect="AspectFit"/>

    <HorizontalStackLayout VerticalOptions="End" Spacing="20" HorizontalOptions="Center">
        <!-- Favorite Button -->
        <Border StrokeThickness="0" HeightRequest="60" WidthRequest="60">
            <Border.StrokeShape>
                <RoundRectangle CornerRadius="30"/>
            </Border.StrokeShape>
            <effects:SfEffectsView TouchDownEffects="Highlight">
                <effects:SfEffectsView.GestureRecognizers>
                    <TapGestureRecognizer Tapped="OnFavoriteTapped"/>
                </effects:SfEffectsView.GestureRecognizers>
                <VerticalStackLayout HorizontalOptions="Center" VerticalOptions="Center">
                    <Label Text="{Binding IsFavorite, Converter={StaticResource FavoriteToIconConverter}}"
                           FontFamily="sPhotoGallery"
                           FontSize="24"
                           HorizontalOptions="Center"/>
                    <Label Text="Favorite" />
                </VerticalStackLayout>
            </effects:SfEffectsView>
        </Border>

        <!-- Delete Button -->
        <Border StrokeThickness="0" HeightRequest="60" WidthRequest="60">
            <Border.StrokeShape>
                <RoundRectangle CornerRadius="30"/>
            </Border.StrokeShape>
            <effects:SfEffectsView TouchDownEffects="Highlight">
                <effects:SfEffectsView.GestureRecognizers>
                    <TapGestureRecognizer Tapped="OnDeleteTapped"/>
                </effects:SfEffectsView.GestureRecognizers>
                <VerticalStackLayout HorizontalOptions="Center" VerticalOptions="Center">
                    <Label Text=""
                           FontFamily="PhotoGallery"
                           FontSize="24"
                           HorizontalOptions="Center"/>
                    <Label Text="Delete" />
                </VerticalStackLayout>
            </effects:SfEffectsView>
        </Border>
        ...
    </HorizontalStackLayout>
</Grid>

Enter fullscreen mode Exit fullscreen mode

ImagePage.Xaml.cs

private void OnFavoriteTapped(object sender, TappedEventArgs e)
{
    if (BindingContext is ImageInfo imageInfo)
    {
        imageInfo.IsFavorite = !imageInfo.IsFavorite;
    }
}

private async void OnDeleteTapped(object sender, TappedEventArgs e)
{
    if (BindingContext is ImageInfo imageInfo)
    {
        // Confirm the deletion action
        bool isConfirmed = await DisplayAlert("Delete Image", "Are you sure you want to delete this image?", "Yes", "No");
        if (isConfirmed)
        {
            if (_viewModel.Recents.Contains(imageInfo))
                _viewModel.Recents.Remove(imageInfo);

            // Remove from Photos collection
            if (_viewModel.Photos.Contains(imageInfo))
                _viewModel.Photos.Remove(imageInfo);
            else if (_viewModel.Countries.Contains(imageInfo))
                _viewModel.Countries.Remove(imageInfo);
            else if (_viewModel.Foods.Contains(imageInfo))
                _viewModel.Foods.Remove(imageInfo);
            else if (_viewModel.Electronics.Contains(imageInfo))
                _viewModel.Electronics.Remove(imageInfo);
            else if (_viewModel.Birds.Contains(imageInfo))
                _viewModel.Birds.Remove(imageInfo);

            // Remove from Favorites if it's there
            if (imageInfo.IsFavorite)
            {
                _viewModel.Favorites.Remove(imageInfo);
            }

            // Navigate back to the main page or previous navigation stack
            await Navigation.PopAsync();
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

Implementing UI actions in the image display page


Implementing UI actions in the image display page

UI Actions

.NET MAUI Tab View and ListView


.NET MAUI Tab View and ListView

GitHub references

For more details, refer to the GitHub demo

Conclusion

Thanks for reading! With Syncfusion’s Tab View and ListView controls, building a responsive, interactive photo gallery in .NET MAUI becomes effortless. You’ve now seen how to group photos, display albums, and bind dynamic image data using modern UI components.

This approach not only improves user experience but also ensures your app is scalable and visually appealing across devices

Download Essential Studio for .NET MAUI to start evaluating the latest features immediately.

If you have any questions, you can also contact us through our support forum, support portal, or feedback portal. We are always happy to help you!

Related Blogs

This article was originally published at Syncfusion.com.

Comments 0 total

    Add comment