UWP: Facebook style image gallery

Intro

I like how Facebook shows pictures in my feed. Facebook image gallery can look similar as depicted above. This style is common for the web and for the native Facebook apps.

The gallery is great from UX perspective. Part of photos is available immediately, so no need to do additional few clicks if they are not catchy. There is a counter, which identifies how many pictures are left in the full-screen mode. And images are arranged differently depending on aspect ratio and quantity, so it brings a cool dynamic style. With all that said why not to have the same look gallery in any other UWP app :).

Possible solutions

A naive implementation can be approached with the idea to place ItemsControl in the top and bottom rows of Grid, bind them to two collections and somehow manage a number of visible items for both rows. But it increases code complexity and causes XAML duplications. Two data bindings might lead to the situations when the rows are rendered not in the same time. Eventually, dynamic behavior is lost and the implementation is prone to have performance issues.

I achieved the desired result by implementing a custom panel, where I override MeasureOverride and ArrangeOverride methods. These two virtual methods contain core logic of any class inherited from FrameworkElement. They involved in every layout cycle. MeasureOverride is responsible for calculating the final size, which will be taken by a framework element when it’s been rendered. The purpose of ArrangeOverride is to position a framework element and its children with the given size. These methods well described in Windows Dev Center documentation.

When the custom panel was done, I used it as ItemsPanelTemplate for ItemsControl. ItemsControl is a lightweight list-based control. No reason in my case to have a selection or apply virtualization, so using ListView or GridView is unnecessary.

Step by step implementation

First I define a class and inherit it from Windows.UI.Xaml.Controls.Panel. I named my custom panel ImageGalleryPanel. I also want to manage the number of items shown in the first and second row in runtime, that’s why I expose two dependency properties ItemsInFirstRow and IntemsInSecondRow in the panel class:

Size parameter of FrameworkElement.MeassureOverride is always passed from a parent. And it’s responsibility of framework element to split up the given size among children and measure a child with the available height and width. In ImageGalleryPanel.MeasureOverride, I calculate the width for children items based on ItemsInFirstRow and ItemsInSecondRow values. Then I measure every child with calculated size. After children are measured, I return back the final size which will be taken by the panel:

When the desired size is known for every child, the system calls FrameworkElement.ArrangeOverride to position every child. In ImageGallery.ArrangeOverride, I arrange children with the desired size of a particular child and number of elements configured per a row:

XAML for the image gallery is straightforward:

The final step is to show how many photos will be available in the full mode. One of the possible ways to implement this indicator is to extend ItemsControl.ItemTemplate with a transparent Border and TextBlock. TextBlock contains a number equals to hidden items. Border overlay and TextBlock are only visible for the last item:

In code behind of the main page I initialize ItemsControl:

For sake of simplicity, I define a ViewModel for an item as an anonymous type and declare OverlayVisiblity as a type of Windows.UI.Xaml.Visiblity. In a real project, I would keep above logic in a separated class and convert bool  to Visiblity with IValueConverter.

Placeholders

If you launch the app, you will immediately notice how elements have been arranged. It’s because the panel is measured and arranged in few cycles. At the first cycle the final size of images is unknown because they are not loaded yet, so we only see the overlay and the number of items in full mode. As soon as images are loaded, the panel measures and arranges children again. It doesn’t look good at all. And will look worse, if images will be downloaded from the web.

I want to show a placeholder for every child item immediately the page loaded and hide the placeholders when images become visible:

To show placeholders, I extended the DataTemplate with ViewBox. This piece of XAML does the trick:

Viewbox scales the given content to the all available space. Basically, ViewBox content is stretched to the size passed to it from the parent at the first layout cycle. I also set Height and Width of the placeholder Border proportional to the given images size. It makes the border stretched with the aspect ratio of images. When images loaded, they are shown above placeholders.

The code of the panel is available at github.

 


Leave a Reply