Creating a card stack layout using .NET MAUI

Creating a card stack layout using .NET MAUI

28 June 2022

.NET MAUI/Xamarin

Buy Me A Coffee

Hello and welcome back!

Today I am excited to share with you the guide on creating a card stack layout. CardStackLayout displays a list of cards as a stack on the screen. By swiping the top card you can see the next one. Swiping back returns the top card.

Some popular applications use it like this:

Tinder

So let's start. First, we need to create our CardLayout class:

public partial class CardsLayout : Layout, ILayoutManager
{
    public Size ArrangeChildren(Rect rectangle)
    {
        ...
    }

    public Size Measure(double widthConstraint, double heightConstraint)
    {
        ...
    }

    protected override ILayoutManager CreateLayoutManager() => this;
}

ILayoutManager allows overriding the CreateLayoutManager method of a layout to provide a custom implementation of measuring and positioning views. The Measure method takes height and width constraints and is responsible for measuring all of the layout’s children. The ArrangeChildren then sets each view's size and position according to the layout's rules.

We want to achieve a stack effect, so we can see a top card and some part of the cards behind.

Start by implementing Measure method to get control size:

public int Spacing { get; set; } = 5;

public Size Measure(double widthConstraint, double heightConstraint)
{
	double x = Padding.Left;
	double y = Padding.Top;
	double totalWidth = 0;
	double totalHeight = 0;

	foreach (var child in Children)
	{
		x += Spacing;
		widthConstraint -= Spacing;
		var current = child.Measure(widthConstraint, heightConstraint);
		totalWidth = Math.Max(totalWidth, x + current.Width);
		totalHeight = Math.Max(totalHeight, y + current.Height);
	}

	return new Size(totalWidth + Padding.HorizontalThickness, totalHeight + Padding.VerticalThickness);
}

Next step is ArrangeChildren method implementation:

public double CardScaling { get; set; } = 0.8;

public Size ArrangeChildren(Rect rectangle)
{
	int i = Children.Count - 1;
	double x = Padding.Left;
	double y = Padding.Top;

	double totalWidth = 0;
	double totalHeight = 0;
	var maxWidth = Children[^1].DesiredSize.Width;
	var maxHeight = Children[^1].DesiredSize.Height;
	foreach (var child in Children)
	{
		var width = child.DesiredSize.Width;
		var height = child.DesiredSize.Height * Math.Pow(CardScaling, i);
		child.Arrange(new Rect(x,
							y + (maxHeight - height) / 2,
							width,
							height));
		x += Spacing;
		totalWidth = Math.Max(totalWidth, x + width);
		totalHeight = Math.Max(totalHeight, y + height);
		i--;
	}

	return new Size(totalWidth + Padding.HorizontalThickness, totalHeight + Padding.VerticalThickness);
}

In this method we arrange each child into rectangle of size which we defined. For each next child we decrease its height and also change its location. Here we achieved left to right cards direction of card layout.

After defining layout we want to switch between cards by swiping them. We can achieve it using PanGestureRecognizer (Unfortunately SwipeGestureRecognizer doesn't work right now on custom controls):

SwipeDirection swipeDirection;

public CardsLayout()
{
	var panGesture = new PanGestureRecognizer();
	panGesture.PanUpdated += PanGesture_PanUpdated;
	GestureRecognizers.Add(panGesture);
}

private void PanGesture_PanUpdated(object? sender, PanUpdatedEventArgs e)
{
	switch (e.StatusType)
	{
		case GestureStatus.Running:
			HandleTouch(e.TotalX, e.TotalY);
			break;
		case GestureStatus.Completed:
			HandleTouchEnd(swipedDirection);
			break;
	}
}

Now we need to implement HandleTouch and HandleTouchEnd methods:

private readonly Stack<IView> cards = new();

private void HandleTouch(double eTotalX, double eTotalY)
{
	swipedDirection = null;
	const int delta = 50;
	if (eTotalX > delta)
	{
		swipedDirection = SwipeDirection.Right;
	}
	else if (eTotalX < -delta)
	{
		swipedDirection = SwipeDirection.Left;
	}
	else if (eTotalY > delta)
	{
		swipedDirection = SwipeDirection.Down;
	}
	else if (eTotalY < -delta)
	{
		swipedDirection = SwipeDirection.Up;
	}
}

private void HandleTouchEnd(SwipeDirection? swiped)
{
	if (swiped == null)
	{
		return;
	}

	switch (swiped)
	{
		case SwipeDirection.Right when Children.Count > 0:
			cards.Push(Children[^1]);
			Children.RemoveAt(Children.Count - 1);
			break;
		case SwipeDirection.Left when cards.Count > 0:
			Children.Add(cards.Pop());
			break;
	}
}

Now, when you swipe from left to right the top card will disappear. Swiping from right to left will return swiped card to the top.

As a result, you should receive such app:

.NET MAUI CardLayout

The full code with different layouts and swipe directions can be found on GitHub.

Happy coding!

Buy Me A Coffee

Related:

Secure .NET MAUI application

The article describes how to add secure surfaces to prevent content rendered into those surfaces by applications from appearing in screenshots or from being viewed on non-secure displays.

Mastering Composite Controls in .NET MAUI. Building a TabView from Scratch

Explore the creation of a custom TabView control in .NET MAUI using multiple methods like composing IndicatorView and CarouselView or ContentView with RadioButtons.

An unhandled error has occurred. Reload

🗙