Sensor-based Parallax in .NET MAUI using the Gyroscope #MAUIUIJuly

Parallax effects add depth and interactivity to your app's UI by making different layers move at different speeds to simulate 3D depth. In this article, we will cover how to implement a flexible parallax system in your .NET MAUI app.

Sensor-based Parallax in .NET MAUI using the Gyroscope #MAUIUIJuly
🎉
This post is part of .NET MAUI UI July 2025 (https://goforgoldman.com/posts/mauiuijuly-25/) - a month-long community celebration of .NET MAUI user interfaces. #dotnetmaui #MAUIUIJuly

Intro

For this article, we will use the Gyroscope sensor. If you've missed it, I wrote an article about all the sensors and the values we can get from them :

Sensors in .NET MAUI #MAUIUIJuly
In this post, we will focus on how our phone’s sensors work. We will explore six key sensors available in .NET MAUI, complete with real-time visualizations and code samples.

Many people have requested this feature to be added to MAUI over the years, and I still couldn't find any nice example ... So, I made one. As simple as I could:

And here is how it looks like:

0:00
/0:13

The Parallax Layer: The Foundation

ParallaxLayer is a custom view that applies translation effects to its child content based on parallax offset values. Each layer moves independently to simulate depth.

Key features:

  • Attaches to a ParallaxOffsetSource (the driver of the effect)
    • Adds itself to the Source's list of Listeners
  • Configurable maximum movement distances for X and Y axes
    • A negative maximum movement distance allows you to have a reverse Parallax effect for background layers, see below.
  • Listens for offset changes, updates its translation accordingly
/// <summary>
/// A content view that applies parallax translation effects to its child content.
/// </summary>
public class ParallaxLayer : ContentView, IParallaxOffsetListener
{
    /// <summary>
    /// Initializes a new instance of the ParallaxLayer class.
    /// </summary>
    public ParallaxLayer()
    {
        VerticalOptions = LayoutOptions.Fill;
        HorizontalOptions = LayoutOptions.Fill;
    }

    /// <summary>
    /// Gets or sets the current horizontal parallax offset value.
    /// </summary>
    public double ParallaxX { get; set; } = 0.0;

    /// <summary>
    /// Gets or sets the current vertical parallax offset value.
    /// </summary>
    public double ParallaxY { get; set; } = 0.0;

    /// <summary>
    /// Called when the parallax offset source provides new offset values.
    /// </summary>
    /// <param name="x">The horizontal offset value between -1 and 1.</param>
    /// <param name="y">The vertical offset value between -1 and 1.</param>
    public void OnParallaxOffsetChanged(double x, double y)
    {
        ParallaxX = x;
        ParallaxY = y;
        UpdateTranslation();
    }

    /// <summary>
    /// Updates the translation transform based on current parallax values and maximum distances.
    /// </summary>
    public void UpdateTranslation()
    {
        // Calculate translation based on parallax offset and maximum distance
        TranslationX = ParallaxX * ParallaxMaxDistanceX;
        TranslationY = ParallaxY * ParallaxMaxDistanceY;
    }

    /// <summary>
    /// Gets or sets the parallax offset source that provides the parallax data.
    /// </summary>
    public ParallaxOffsetSource? ParallaxOffsetSource
    {
        get => (ParallaxOffsetSource?)GetValue(ParallaxOffsetSourceProperty);
        set => SetValue(ParallaxOffsetSourceProperty, value);
    }

    /// <summary>
    /// Bindable property for ParallaxOffsetSource.
    /// </summary>
    public readonly static BindableProperty ParallaxOffsetSourceProperty =
        BindableProperty.Create(nameof(ParallaxOffsetSource), typeof(ParallaxOffsetSource), typeof(ParallaxLayer), null,
            propertyChanged: OnParallaxOffsetSourceChanged);

    /// <summary>
    /// Gets or sets the maximum horizontal distance for parallax movement in pixels.
    /// </summary>
    public double ParallaxMaxDistanceX
    {
        get => (double)GetValue(ParallaxMaxDistanceXProperty);
        set => SetValue(ParallaxMaxDistanceXProperty, value);
    }

    /// <summary>
    /// Bindable property for ParallaxMaxDistanceX.
    /// </summary>
    public readonly static BindableProperty ParallaxMaxDistanceXProperty =
        BindableProperty.Create(nameof(ParallaxMaxDistanceX), typeof(double), typeof(ParallaxLayer), 10.0,
            propertyChanged: OnParallaxDistanceChanged);

    /// <summary>
    /// Gets or sets the maximum vertical distance for parallax movement in pixels.
    /// </summary>
    public double ParallaxMaxDistanceY
    {
        get => (double)GetValue(ParallaxMaxDistanceYProperty);
        set => SetValue(ParallaxMaxDistanceYProperty, value);
    }

    /// <summary>
    /// Bindable property for ParallaxMaxDistanceY.
    /// </summary>
    public readonly static BindableProperty ParallaxMaxDistanceYProperty =
        BindableProperty.Create(nameof(ParallaxMaxDistanceY), typeof(double), typeof(ParallaxLayer), 10.0,
            propertyChanged: OnParallaxDistanceChanged);

    /// <summary>
    /// Handles changes to the parallax distance properties.
    /// </summary>
    private static void OnParallaxDistanceChanged(BindableObject bindable, object oldValue, object newValue)
    {
        if (bindable is ParallaxLayer layer)
        {
            layer.UpdateTranslation();
        }
    }

    /// <summary>
    /// Handles changes to the ParallaxOffsetSource property.
    /// </summary>
    private static void OnParallaxOffsetSourceChanged(BindableObject bindable, object oldValue, object newValue)
    {
        if (bindable is not ParallaxLayer layer)
            return;

        // Remove listener from old source
        if (oldValue is ParallaxOffsetSource oldSource)
        {
            oldSource.RemoveListener(layer);
        }

        // Add listener to new source
        if (newValue is ParallaxOffsetSource newSource)
        {
            newSource.AddListener(layer);
        }
    }
}

Example usage:

<views:ParallaxLayer ParallaxOffsetSource="{StaticResource SharedParallaxSource}"
                     ParallaxMaxDistanceX="40"
                     ParallaxMaxDistanceY="40">
    <!– Your content here -->
</views:ParallaxLayer>

Bonus: I have extracted a IParallaxOffsetListener :

/// <summary>
/// Interface for objects that can listen to parallax offset changes.
/// </summary>
public interface IParallaxOffsetListener
{
    /// <summary>
    /// Called when the parallax offset values change.
    /// The X and Y values will be between -1 and 1, representing the parallax effect.
    /// </summary>
    /// <param name="x">The horizontal offset value between -1 and 1.</param>
    /// <param name="y">The vertical offset value between -1 and 1.</param>
    void OnParallaxOffsetChanged(double x, double y);
}

This allows you to also apply a parallax effect on your own elements, without the need to put it inside a Layer.

The Parallax Source: The controller

Now that we have a ParallaxLayer that moves, we need to tell it how to move. The ParallaxOffsetSource is there for that.

Regardless of the source for the X and Y values, the Sources should call NotifyListeners(double x, double y) with the new values.

/// <summary>
/// Abstract base class for parallax offset sources that provide X and Y values from -1 to 1.
/// </summary>
public abstract class ParallaxOffsetSource : BindableObject
{
    private List<IParallaxOffsetListener> Listeners { get; } = new List<IParallaxOffsetListener>();

    public virtual void AddListener(IParallaxOffsetListener listener)
    {
        if (listener == null)
            throw new ArgumentNullException(nameof(listener));

        lock (Listeners)
        {
            // Ensure the listener is not already added
            if (Listeners.Contains(listener))
                return;

            // Add the listener to the list
            Listeners.Add(listener);
        }
    }

    public virtual void RemoveListener(IParallaxOffsetListener listener)
    {
        if (listener == null)
            throw new ArgumentNullException(nameof(listener));

        lock (Listeners)
        {
            Listeners.Remove(listener);
        }
    }

    // Useful for cumulative calculation and debug
    public double CurrentOffsetX { get; private set; }
    public double CurrentOffsetY { get; private set; }

    /// <summary>
    /// Notifies all listeners about the new parallax offset.
    /// </summary>
    protected void NotifyListeners(double x, double y)
    {
        x = Math.Clamp(x, -1, 1);
        y = Math.Clamp(y, -1, 1);

        lock (Listeners)
        {
            foreach (var listener in Listeners)
            {
                listener.OnParallaxOffsetChanged(x, y);
            }
        }

        CurrentOffsetX = x;
        CurrentOffsetY = y;
        OnPropertyChanged(nameof(CurrentOffsetX));
        OnPropertyChanged(nameof(CurrentOffsetY));
    }
}

Manual Control: Bindable Parallax Source

For demos, sliders, or custom animations, you may want to control the parallax offset directly. The ParallaxOffsetFromBindingSource class exposes bindable properties for X and Y offsets, making it easy to connect to UI controls.

How it works:

  • Exposes ParallaxXValue and ParallaxYValue as bindable properties
  • Notifies all registered layers when values change
  • Values are clamped between -1 and 1
/// <summary>
/// Parallax offset source that uses bindable properties for X and Y values.
/// </summary>
public class ParallaxOffsetFromBindingSource : ParallaxOffsetSource
{
    /// <summary>
    /// Initializes a new instance of the ParallaxOffsetFromBindingSource.
    /// </summary>
    public ParallaxOffsetFromBindingSource()
    {
    }

    /// <summary>
    /// Gets or sets the X offset value for parallax effect.
    /// </summary>
    public double ParallaxXValue
    {
        get => (double)GetValue(ParallaxXValueProperty);
        set => SetValue(ParallaxXValueProperty, value);
    }

    /// <summary>
    /// Bindable property for ParallaxXValue.
    /// </summary>
    public readonly static BindableProperty ParallaxXValueProperty =
        BindableProperty.Create(nameof(ParallaxXValue), typeof(double), typeof(ParallaxOffsetFromBindingSource), 0.0, propertyChanged: OnParallaxXValueChanged);

    /// <summary>
    /// Gets or sets the Y offset value for parallax effect.
    /// </summary>
    public double ParallaxYValue
    {
        get => (double)GetValue(ParallaxYValueProperty);
        set => SetValue(ParallaxYValueProperty, value);
    }

    /// <summary>
    /// Bindable property for ParallaxYValue.
    /// </summary>
    public readonly static BindableProperty ParallaxYValueProperty =
        BindableProperty.Create(nameof(ParallaxYValue), typeof(double), typeof(ParallaxOffsetFromBindingSource), 0.0, propertyChanged: OnParallaxYValueChanged);

    /// <summary>
    /// Handles changes to the ParallaxXValue property.
    /// </summary>
    protected static void OnParallaxXValueChanged(BindableObject bindable, object oldValue, object newValue)
    {
        if (bindable is ParallaxOffsetFromBindingSource source)
        {
            source.OnParallaxValueChanged();
        }
    }

    /// <summary>
    /// Handles changes to the ParallaxYValue property.
    /// </summary>
    protected static void OnParallaxYValueChanged(BindableObject bindable, object oldValue, object newValue)
    {
        if (bindable is ParallaxOffsetFromBindingSource source)
        {
            source.OnParallaxValueChanged();
        }
    }

    /// <summary>
    /// Notifies all listeners when either X or Y values change.
    /// </summary>
    protected void OnParallaxValueChanged()
    {
        NotifyListeners(ParallaxXValue, ParallaxYValue);
    }
}

Example XAML :

<ContentPage.Resources>
    <ResourceDictionary>
        <views:ParallaxOffsetFromBindingSource x:Key="SharedParallaxSource" />
    </ResourceDictionary>
</ContentPage.Resources>

<!-- Sliders bound to the parallax source -->
<Slider Value="{Binding Source={StaticResource SharedParallaxSource}, Path=ParallaxXValue, Mode=TwoWay}" />
<Slider Value="{Binding Source={StaticResource SharedParallaxSource}, Path=ParallaxYValue, Mode=TwoWay}" />

This approach is perfect for tutorials, design-time tweaking, or letting users control the effect.

0:00
/0:12

Sensor-Driven Parallax: Gyroscope Integration

For a more immersive experience, you can drive the parallax effect using the device's gyroscope. The ParallaxOffsetFromGyroscopeSource class listens to real-time gyroscope data and updates the parallax offset accordingly.

Features:

  • Uses a GyroscopeSensorService to receive angular velocity updates
  • Supports a Multiplier property to control sensitivity
  • Optional IsCumulative mode
    • false: The acceleration (and its return to 0 on idle) will be the source
    • true: The motion adds up, making the effect smoother

But the question is, which value from our sensor do we want to take? Well, let's check our post from yesterday:

0:00
/0:11

Seems like we want:

  • The bottom one (Y - Pitch) for the horizontal movement
  • The right one (X - Roll) for the vertical movement

How it works:

  • The gyroscope measures device rotation speed
  • The parallax source converts this data into X/Y offsets
/// <summary>
/// Parallax offset source that uses gyroscope sensor data to create movement effects.
/// </summary>
public class ParallaxOffsetFromGyroscopeSource : ParallaxOffsetSource, IDisposable
{
    private readonly GyroscopeSensorService _gyroscopeSensorService;

    public ParallaxOffsetFromGyroscopeSource()
    {
        _gyroscopeSensorService = MauiProgram.Services?.GetRequiredService<GyroscopeSensorService>() ?? throw new InvalidOperationException("GyroscopeSensorService is not registered in the service collection.");
        _gyroscopeSensorService.AddListener(OnGyroscopeDataChanged);
    }

    public void Dispose()
    {
        _gyroscopeSensorService.RemoveListener(OnGyroscopeDataChanged);
    }

    private void OnGyroscopeDataChanged(GyroscopeData update)
    {
        double x = 0, y = 0;
        float horizontalMovement = update.AngularVelocity.Y; // Pitch
        float verticalMovement = update.AngularVelocity.X; // Roll

        if (IsCumulative)
        {
            // If cumulative, add the new values to the existing offset
            x = CurrentOffsetX + horizontalMovement * Multiplier;
            y = CurrentOffsetY + verticalMovement * Multiplier;
        }
        else
        {
            // If not cumulative, just use the new values directly
            x = horizontalMovement * Multiplier;
            y = verticalMovement * Multiplier;
        }

        NotifyListeners(x, y);
    }

    /// <summary>
    /// Gets or sets whether the gyroscope values are cumulative or direct.
    /// </summary>
    public bool IsCumulative
    {
        get => (bool)GetValue(IsCumulativeProperty);
        set => SetValue(IsCumulativeProperty, value);
    }

    /// <summary>
    /// Bindable property for IsCumulative.
    /// </summary>
    public readonly static BindableProperty IsCumulativeProperty = BindableProperty.Create(nameof(IsCumulative), typeof(bool), typeof(ParallaxOffsetFromGyroscopeSource), false);

    /// <summary>
    /// Gets or sets the multiplier for gyroscope values.
    /// </summary>
    public double Multiplier
    {
        get => (double)GetValue(ParallaxGyroscopeMultiplierProperty);
        set => SetValue(ParallaxGyroscopeMultiplierProperty, value);
    }

    /// <summary>
    /// Bindable property for Multiplier.
    /// </summary>
    public readonly static BindableProperty ParallaxGyroscopeMultiplierProperty = BindableProperty.Create(nameof(Multiplier), typeof(double), typeof(ParallaxOffsetFromGyroscopeSource), 0.2d);
}

Example XAML:

<ContentPage.Resources>
    <ResourceDictionary>
        <views:ParallaxOffsetFromGyroscopeSource x:Key="SharedParallaxSource" />
    </ResourceDictionary>
</ContentPage.Resources>

<!-- Bind UI controls to adjust gyroscope behavior -->
<Slider Value="{Binding Source={StaticResource SharedParallaxSource}, Path=Multiplier, Mode=TwoWay}" />
<Switch IsToggled="{Binding Source={StaticResource SharedParallaxSource}, Path=IsCumulative, Mode=TwoWay}" />
0:00
/0:25

Conclusion

Implementing parallax in .NET MAUI can be easy and straightforward. By separating the concept of a parallax layer from the source of its movement, you can:

  • Manually control effects for demos or user input
  • Leverage device sensors for immersive, real-world interaction
  • Easily extend or combine sources for new effects

We could now add many sources:

  • Touch / Mouse-hover: X/Y based on touch or cursor position on screen
  • Compass: Horizontal only parallax
  • Scroll: As the scroll view scrolls, Vertical only parallax

Experiment with different layer arrangements, movement ranges, and input sources to create unique, engaging UIs in your MAUI apps!


Check out my MAUI playground for a full sample:

GitHub - framinosona/Maui-Developer-Sample: This is my playground, I try to make things as clean as possible so that I can reuse them.
This is my playground, I try to make things as clean as possible so that I can reuse them. - framinosona/Maui-Developer-Sample
🎉
This post is part of .NET MAUI UI July 2025 (https://goforgoldman.com/posts/mauiuijuly-25/) - a month-long community celebration of .NET MAUI user interfaces. #dotnetmaui #MAUIUIJuly

Here is the code of the sample in the videos:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:views="clr-namespace:Maui_Developer_Sample.Pages.UI.Views"
             x:Class="Maui_Developer_Sample.Pages.UI.ParallaxGyroscope_Page"
             Title="Parallax Gyroscope">
    <ContentPage.Resources>
        <ResourceDictionary>
            <!-- Shared parallax offset source -->
            <views:ParallaxOffsetFromGyroscopeSource x:Key="SharedParallaxSource" />
        </ResourceDictionary>
    </ContentPage.Resources>

    <ContentPage.Content>
        <Grid RowDefinitions="Auto, 1*">

            <!-- Control Panel -->
            <Border Grid.Row="0"
                    BackgroundColor="{AppThemeBinding Light={StaticResource Gray100}, Dark={StaticResource Gray900}}"
                    Stroke="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray600}}"
                    StrokeThickness="1"
                    Margin="10"
                    Padding="15">
                <VerticalStackLayout Spacing="10">
                    <Label Text="Parallax Controls"
                           FontAttributes="Bold"
                           FontSize="16"
                           HorizontalOptions="Center" />

                    <!-- Gyroscope Multiplier -->
                    <Grid ColumnDefinitions="Auto, 1*, Auto"
                          ColumnSpacing="10">
                        <Label Grid.Column="0"
                               Text="Gyroscope Multiplier:"
                               VerticalOptions="Center"
                               MinimumWidthRequest="20" />
                        <Slider Grid.Column="1"
                                Minimum="0"
                                Maximum="1"
                                x:DataType="{x:Type views:ParallaxOffsetFromGyroscopeSource}"
                                Value="{Binding Source={StaticResource SharedParallaxSource}, Path=Multiplier, Mode=TwoWay}"
                                ThumbColor="{StaticResource Primary}" />
                        <Label Grid.Column="2"
                               x:DataType="{x:Type views:ParallaxOffsetFromGyroscopeSource}"
                               Text="{Binding Source={StaticResource SharedParallaxSource}, Path=Multiplier, StringFormat='{0:F2}'}"
                               VerticalOptions="Center"
                               MinimumWidthRequest="40" />
                    </Grid>

                    <!-- IsCumulative -->
                    <Grid ColumnDefinitions="Auto, 1*"
                          ColumnSpacing="10">
                        <Label Text="Cumulative Movement:"
                               VerticalOptions="Center"
                               MinimumWidthRequest="20" />
                        <Switch Grid.Column="1"
                                x:DataType="{x:Type views:ParallaxOffsetFromGyroscopeSource}"
                                IsToggled="{Binding Source={StaticResource SharedParallaxSource}, Path=IsCumulative, Mode=TwoWay}"
                                ThumbColor="{StaticResource Primary}" />
                    </Grid>

                </VerticalStackLayout>
            </Border>

            <!-- Parallax Demo Area -->
            <Border Grid.Row="3"
                    BackgroundColor="{AppThemeBinding Light={StaticResource Gray100}, Dark={StaticResource Gray950}}"
                    Stroke="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}"
                    StrokeThickness="2"
                    Margin="10">
                <Grid>

                    <!-- Background Layer - Opposite movement -->
                    <views:ParallaxLayer ParallaxOffsetSource="{StaticResource SharedParallaxSource}"
                                         ParallaxMaxDistanceX="-40"
                                         ParallaxMaxDistanceY="-40"
                                         Scale="1.2"
                                         ZIndex="-1">
                        <Image Source="hike.jpg"
                               Aspect="AspectFill"
                               HorizontalOptions="Fill"
                               VerticalOptions="Fill" />
                    </views:ParallaxLayer>
                    
                    <!-- Far Layer - Slowest movement -->
                    <views:ParallaxLayer ParallaxOffsetSource="{StaticResource SharedParallaxSource}"
                                         ParallaxMaxDistanceX="20"
                                         ParallaxMaxDistanceY="20"
                                         ZIndex="1">
                        <Ellipse Fill="{StaticResource Tertiary}"
                                 Opacity="0.3"
                                 WidthRequest="200"
                                 HeightRequest="200"
                                 HorizontalOptions="Center"
                                 VerticalOptions="Center" />
                    </views:ParallaxLayer>

                    <!-- Middle Layer - Medium movement -->
                    <views:ParallaxLayer ParallaxOffsetSource="{StaticResource SharedParallaxSource}"
                                         ParallaxMaxDistanceX="40"
                                         ParallaxMaxDistanceY="40"
                                         ZIndex="2">
                        <Border BackgroundColor="{StaticResource Primary}"
                                Stroke="{StaticResource Secondary}"
                                StrokeThickness="3"
                                WidthRequest="120"
                                HeightRequest="120"
                                HorizontalOptions="Center"
                                VerticalOptions="Center">
                            <Border.StrokeShape>
                                <RoundRectangle CornerRadius="15" />
                            </Border.StrokeShape>
                            <Label Text="Layer 2"
                                   TextColor="White"
                                   FontAttributes="Bold"
                                   HorizontalOptions="Center"
                                   VerticalOptions="Center" />
                        </Border>
                    </views:ParallaxLayer>

                    <!-- Foreground Layer - Fastest movement -->
                    <views:ParallaxLayer ParallaxOffsetSource="{StaticResource SharedParallaxSource}"
                                         ParallaxMaxDistanceX="80"
                                         ParallaxMaxDistanceY="80"
                                         ZIndex="3">
                        <Border BackgroundColor="{StaticResource Secondary}"
                                Stroke="{StaticResource Primary}"
                                StrokeThickness="2"
                                WidthRequest="80"
                                HeightRequest="80"
                                HorizontalOptions="Center"
                                VerticalOptions="Center">
                            <Border.StrokeShape>
                                <Ellipse />
                            </Border.StrokeShape>
                            <Label Text="Layer 3"
                                   TextColor="White"
                                   FontSize="10"
                                   FontAttributes="Bold"
                                   HorizontalOptions="Center"
                                   VerticalOptions="Center" />
                        </Border>
                    </views:ParallaxLayer>

                    <!-- Additional decorative elements -->
                    <views:ParallaxLayer ParallaxOffsetSource="{StaticResource SharedParallaxSource}"
                                         ParallaxMaxDistanceX="60"
                                         ParallaxMaxDistanceY="30"
                                         ZIndex="4">
                        <StackLayout Orientation="Horizontal"
                                     Spacing="30"
                                     HorizontalOptions="Center"
                                     VerticalOptions="End"
                                     Margin="0,0,0,50">
                            <BoxView BackgroundColor="{StaticResource Tertiary}"
                                     WidthRequest="20"
                                     HeightRequest="40" />
                            <BoxView BackgroundColor="{StaticResource Tertiary}"
                                     WidthRequest="20"
                                     HeightRequest="60" />
                            <BoxView BackgroundColor="{StaticResource Tertiary}"
                                     WidthRequest="20"
                                     HeightRequest="30" />
                            <BoxView BackgroundColor="{StaticResource Tertiary}"
                                     WidthRequest="20"
                                     HeightRequest="50" />
                        </StackLayout>
                    </views:ParallaxLayer>

                    <!-- Info overlay that doesn't move -->
                    <Border
                        BackgroundColor="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Black}}"
                        Stroke="{StaticResource Primary}"
                        StrokeThickness="1"
                        Opacity="0.9"
                        HorizontalOptions="Start"
                        VerticalOptions="Start"
                        Margin="20"
                        Padding="10"
                        ZIndex="10">
                        <Border.StrokeShape>
                            <RoundRectangle CornerRadius="5" />
                        </Border.StrokeShape>
                        <VerticalStackLayout Spacing="2">
                            <Label Text="Layer Info:"
                                   FontAttributes="Bold"
                                   FontSize="10" />
                            <Label Text="• Background: -40px distance"
                                   FontSize="9" />
                            <Label Text="• Circle: 20px distance"
                                   FontSize="9" />
                            <Label Text="• Square: 40px distance"
                                   FontSize="9" />
                            <Label Text="• Small Circle: 80px distance"
                                   FontSize="9" />
                            <Label Text="• Bars: 60px X, 30px Y"
                                   FontSize="9" />
                        </VerticalStackLayout>
                    </Border>

                </Grid>
            </Border>
        </Grid>
    </ContentPage.Content>
</ContentPage>