dotnet / maui

.NET MAUI is the .NET Multi-platform App UI, a framework for building native device applications spanning mobile, tablet, and desktop.
https://dot.net/maui
MIT License
22.03k stars 1.73k forks source link

Flickering occurs while updating the width of ContentView through PanGestureRecognizer. #20772

Open RasikaPalanisamy opened 7 months ago

RasikaPalanisamy commented 7 months ago

Description

I have created the ContomView, which is derived from CustomLayout. CustomView has 2 children that are derived from contentView. The second child has the PanGestureRecognizer. While the PanGesture is running, I have changed the width of the first child. Now, flickering occurs on the control because the PanGestureRecognizerEventArgs.TotalX value is updated incorrectly.

Steps to Reproduce

1.Run the attached sample.

  1. Drag the second child from left to right.

Observed behavior: flickering occurs on the control.

https://github.com/dotnet/maui/assets/92015119/44dce4bf-6eea-4f54-8e3d-f566606271d7

Link to public reproduction project repository

https://github.com/RasikaPalanisamy/CustomSample

Version with bug

Unknown/Other

Is this a regression from previous behavior?

Not sure, did not test other versions

Last version that worked well

Unknown/Other

Affected platforms

Android, Windows

Affected platform versions

No response

Did you find any workaround?

No response

Relevant log output

No response

PureWeen commented 7 months ago

@RasikaPalanisamy it might be useful to ask this in discussions or StackOverflow to see if there's a way to optimize how you are updating.

ghost commented 7 months ago

We've added this issue to our backlog, and we will work to address it as time and resources allow. If you have any additional information or questions about this issue, please leave a comment. For additional info about issue management, please read our Triage Process.

RasikaPalanisamy commented 7 months ago

@PureWeen We need the correct PanGestureRecognizerEventArgs.TotalX value. However, the PanGestureRecognizerEventArgs.TotalX value is returning an incorrect value.

danielftz commented 6 months ago

Can confirm PanGestureRecognizerEventArgs.TotalX is returning nonsensical value on Android, and needs to be taken out of the backlog

Pairoba commented 6 months ago

I ran into the same issue and used a workaround for those who are interested. Feel free to provide suggestions for improvement.

private double _lastPanX1 = 0;
private double _lastPanX2 = 0;
private double _lastPanX3 = 0;

private bool IsValidPanEvent(object? sender, PanUpdatedEventArgs e)
{
    bool valid = false;
    if (e.StatusType == GestureStatus.Started)
    {
        _lastPanX1 = 0;
        _lastPanX2 = 0;
        _lastPanX3 = 0;

        valid = true;
    }
    else if (e.StatusType == GestureStatus.Running)
    {
        var lastPanX4 = _lastPanX3;
        _lastPanX3 = _lastPanX2;
        _lastPanX2 = _lastPanX1;
        _lastPanX1 = e.TotalX;

        // detect whether user is increasing or decreasing
        // therefore, we will compare 1 with 3 and 2 with 4:
        // if different, we expect an invalid call
        // due to update speed I see no reason to skip some of the events

        bool totalXIncreases1 = _lastPanX1 > _lastPanX3;
        bool totalXIncreases2 = _lastPanX2 > lastPanX4;

        if (totalXIncreases1 == totalXIncreases2)
        {
            if (totalXIncreases1)
            {
                // if we expect increase, we want to make sure, that the new value is actually higher
                if (_lastPanX1 > _lastPanX2)
                {
                    valid = true;
                }
            }
            else
            {
                if (_lastPanX1 < _lastPanX2)
                {
                    valid = true;
                }
            }
        }
    }

    return valid;
}
XamlTest commented 6 months ago

Verified this on VS 17.10.0 Preview 2.0(8.0.14). Repro on Android 14.0-API34, not repro on iOS 17.2 and MacCatalyst. Project: CustomSample.zip Android: DragAndroid

Different behavior on Windows 11: Drag

BurningLights commented 6 months ago

I noticed in src/Controls/src/Core/Platform/GestureManager/GesturePlatformManager.Android.cs in OnPlatformViewTouched, the event consumed boolean value returned from OnTouchEvent isn't propagated out. The lines

if (e.Event != null)
    OnTouchEvent(e.Event);

should be

if (e.Event != null)
    e.Handled = OnTouchEvent(e.Event);
BurningLights commented 5 months ago

I think I've figured it out. It looks like InnerGestureListener should use RawX and RawY instead of GetX() and GetY(). The raw versions return absolute screen coordinates, while the non-raw versions return the coordinates relative to the view. If the view is being translated, that shifts the view so the relative coordinates get messed up for calculating the length of the swipe, but the absoulte coordinates would still give the correct length of the swipe.

BurningLights commented 5 months ago

Here is the workaround I'm using to create smooth panning on Android. I've subclassed PanGestureRecognizer and re-implemented the IPanGestureController. Just be sure to set the PanUpdated handler using either XAML or a variable of the inherited type, due to having to use the new keyword to create an event handler the inherited class can trigger.

internal sealed class CorrectedPanGestureRecognizer : PanGestureRecognizer, IPanGestureController
{
#if ANDROID
    // Index 0 is X, index 1 is Y
    private readonly int[] startingLocation = new int[2];
    private readonly int[] currentLocation = new int[2];
#endif

    void IPanGestureController.SendPan(Element sender, double totalX, double totalY, int gestureId)
    {
#if ANDROID
        ArgumentNullException.ThrowIfNull(sender.Handler.MauiContext?.Context);
        Android.Views.View view = sender.ToPlatform(sender.Handler.MauiContext);
        view.GetLocationOnScreen(currentLocation);
        totalX += sender.Handler.MauiContext.Context.FromPixels(currentLocation[0] - startingLocation[0]);
        totalY += sender.Handler.MauiContext.Context.FromPixels(currentLocation[1] - startingLocation[1]);

#endif
        PanUpdated?.Invoke(sender, new PanUpdatedEventArgs(GestureStatus.Running, gestureId, totalX, totalY));
    }

    void IPanGestureController.SendPanCanceled(Element sender, int gestureId)
    {
        PanUpdated?.Invoke(sender, new PanUpdatedEventArgs(GestureStatus.Canceled, gestureId));
    }

    void IPanGestureController.SendPanCompleted(Element sender, int gestureId)
    {
        PanUpdated?.Invoke(sender, new PanUpdatedEventArgs(GestureStatus.Completed, gestureId));
    }

    void IPanGestureController.SendPanStarted(Element sender, int gestureId)
    {
#if ANDROID
        ArgumentNullException.ThrowIfNull(sender.Handler.MauiContext);
        Android.Views.View view = sender.ToPlatform(sender.Handler.MauiContext);
        view.GetLocationOnScreen(startingLocation);
#endif
        PanUpdated?.Invoke(sender, new PanUpdatedEventArgs(GestureStatus.Started, gestureId));
    }

    public new event EventHandler<PanUpdatedEventArgs>? PanUpdated;
}
alistair-ocad commented 2 months ago

I've also noticed that if dragging across the whole screen the values returned don't reflect the actual position of the finger/pen. This is only noticeable if dragging a larger distance, feels almost as if it's a factor of 2 out. Again, only on Android.

beeradmoore commented 1 month ago

I was looking into a different pan issue (#24252) and when I tested on Android I came across this problem as well.

The CorrectedPanGestureRecognizer from BurningLights above does fix the issue for me.

Broken:

https://github.com/user-attachments/assets/e9ba78c9-c5e6-41e1-8699-e7eb2e54fd26

Working:

https://github.com/user-attachments/assets/0bd821e9-0ab8-4dbc-9195-9e46e23994b8