Search code examples
c#xamlmauimaui-community-toolkit

Issues with pan gesture on Android using .NET MAUI


I am currently trying to create a Tinder-like swipe left and swipe right behavior. I have had on and off it working on Android. It currently works very well on running it on the windows machine.

enter image description here

However, on Android, running either the emulator or wired to my phone, it does not function correctly.

enter image description here

First of all, I noticed that it only allows you to click the outer edge on Android. Not what I was expecting. Secondly, the animation glitches unbelievably, so when trying to slide the frame left or right it will just glitch around the screen.

I can provide the XAML code or code-behind. However, here is the repo with it working well on Windows, but badly on mobile: https://github.com/Tmmcmasters/FinalMixrMaui

I have tried using some libraries/nuget packages, but most of them are for Xamarin.Forms and do not work well with the new .NET MAUI. I have tried using https://github.com/AndreiMisiukevich/CardView.MAUI, however this is littered with bugs even when cloning the repo. I cannot directly install his nuget package even.

I have tried using a Grid instead of a Frame, but that did not fix the issue. Maybe I am just a noob as well as using extensive AI bots and alike to see if they can help. I have tried getting rid of the gradient as I know that MAUI can have issues with that. However, there were still excessive glitches. I did solve the problem with not being able to click on the middle of the card.

Please let me know if I need to reorient my question in any way.

My XAML CODE:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="FinalMixrMaui.MainPage">

    <Grid>
        <Image Source="background_image.jpg" Aspect="AspectFill" />
        <ScrollView>
            <VerticalStackLayout Spacing="25" Padding="30,0" VerticalOptions="Center">
                <Frame x:Name="frame"
                       Padding="10"
                       BackgroundColor="White"
                       CornerRadius="10"
                       WidthRequest="300"
                       HeightRequest="400">
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="*" />
                            <RowDefinition Height="Auto" />
                        </Grid.RowDefinitions>
                        <Image Grid.RowSpan="2" Source="party_image.jpg" Aspect="AspectFill" />
                        <Frame Grid.RowSpan="2"
                               CornerRadius="0"
                               BorderColor="Transparent">
                            <Frame.Background>
                                <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
                                    <GradientStop Color="Transparent" Offset="0" />
                                    <GradientStop Color="#808e2de2" Offset="0.8" />
                                    <GradientStop Color="#4a00e0" Offset="1" />
                                </LinearGradientBrush>
                            </Frame.Background>
                        </Frame>

                        <!-- Price label (green) -->
                        <Label Grid.Row="0" Text="$25.00"
                               HorizontalOptions="End"
                               Margin="0,0,2,0"
                               FontSize="20"
                               FontAttributes="Bold"
                               TextColor="Green"
                               BackgroundColor="Transparent"
                               TextTransform="Uppercase" 
                               FontFamily="Roboto-Bold"/>

                        <!-- Spots remaining label (red) -->
                        <Label Grid.Row="0" Text="5 spots left"
                               HorizontalOptions="Start"
                               Margin="2,0,0,0"
                               FontSize="20"
                               FontAttributes="Bold"
                               TextColor="Red"
                               BackgroundColor="Transparent"
                               TextTransform="Uppercase" 
                               FontFamily="Roboto-Bold"/>

                        <Grid Grid.Row="1" Padding="10">
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto" />
                                <RowDefinition Height="Auto" />
                            </Grid.RowDefinitions>
                            <Label Grid.Row="0" Text="EVENT TITLE"
                                   HorizontalOptions="Start"
                                   VerticalOptions="End"
                                   Margin="0,0,0,0"
                                   FontSize="34"
                                   FontAttributes="Bold"
                                   TextColor="White"
                                   TextTransform="Uppercase" 
                                   FontFamily="Roboto-Bold"/>

                            <ScrollView Grid.Row="1" HeightRequest="80" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
                                <Label Text="Event description goes here. Scroll here to see how the event description can longer :) and scrollable. SCROLLING ACTION OHHHHHHHHHHHHHHHHHHHHHHHHHH YEAHHHHHHHHHHH!"
                                       Margin="0,0,0,0"
                                       FontSize="16"
                                       TextColor="White" 
                                       FontFamily="Roboto-Regular"/>
                            </ScrollView>
                        </Grid>
                    </Grid>

                    <Frame.GestureRecognizers>
                        <PanGestureRecognizer PanUpdated="OnPanUpdated" />
                    </Frame.GestureRecognizers>
                </Frame>
            </VerticalStackLayout>
        </ScrollView>
    </Grid>
</ContentPage>

MY XAML.CS CODE FILE:

namespace FinalMixrMaui;

public partial class MainPage : ContentPage
{

    public MainPage()
    {
        InitializeComponent();
    }

    double x = 0, y = 0;
    void OnPanUpdated(object sender, PanUpdatedEventArgs e)
    {
        var frame = (Frame)sender;
        double multiplier = 1.1; // Adjust this value to change the speed of the gesture
        double swipeThreshold = this.Width / 4;

        switch (e.StatusType)
        {
            case GestureStatus.Running:
                // Translate and ensure we don't pan beyond the wrapped user interface element bounds.
                frame.TranslationX = x + (e.TotalX * multiplier);

                // Animate rotation and scale based on swipe distance
                frame.Rotation = (frame.TranslationX / this.Width) * 45;
                frame.Scale = 1 - Math.Abs((frame.TranslationX / this.Width) * 0.2);
                break;

            case GestureStatus.Completed:
                // Store the translation applied during the pan
                x = frame.TranslationX;

                // Check if the swipe was far enough to be considered a swipe left or right
                if (x < -swipeThreshold)
                {
                    // Swipe left
                    // TODO: Add your logic for swiping left here

                    // Animate frame off the left side of the screen
                    frame.Animate("SwipeLeft", p =>
                    {
                        frame.TranslationX = p;
                        frame.Rotation = (p / this.Width) * 45;
                        frame.Scale = 1 - Math.Abs((p / this.Width) * 0.2);
                    }, frame.TranslationX, -this.Width, easing: Easing.CubicIn);
                }
                else if (x > swipeThreshold)
                {
                    // Swipe right
                    // TODO: Add your logic for swiping right here

                    // Animate frame off the right side of the screen
                    frame.Animate("SwipeRight", p =>
                    {
                        frame.TranslationX = p;
                        frame.Rotation = (p / this.Width) * 45;
                        frame.Scale = 1 - Math.Abs((p / this.Width) * 0.2);
                    }, frame.TranslationX, this.Width + frame.Width, easing: Easing.CubicIn);
                }
                else
                {
                    // Not a far enough swipe, reset position
                    frame.Animate("Reset", p =>
                    {
                        frame.TranslationX = p;
                        frame.Rotation = (p / this.Width) * 45;
                        frame.Scale = 1 - Math.Abs((p / this.Width) * 0.2);
                    }, frame.TranslationX, 0, easing: Easing.SpringOut);

                    x = 0;
                }
                break;
        }
    }

}

Solution

  • I hope you have found a way to fix the issue. If not I have a working suggestion.

    Replace the inner frame with a StackLayout with Orientation='Vertical'. The inner frame is the one causing the problem. Remember to remove attributes not suitable for StackLayout.

    Do this and it will work.