maui icon indicating copy to clipboard operation
maui copied to clipboard

Scrolling of Editor placed in ScollView does not work.

Open cat0363 opened this issue 2 years ago • 11 comments

If this issue is related to #9827, is there a specific solution? This Issue also occurred in Xamarin.Forms, but continues to occur in .NET MAUI.

Discussed in https://github.com/dotnet/maui/discussions/13632

Originally posted by cat0363 March 2, 2023 To reproduce, layout as follows.

    <ScrollView Orientation="Vertical">
        <Grid RowDefinitions="100,100,100,100,*,100">
            <Grid Grid.Row="0" BackgroundColor="Red" />
            <Grid Grid.Row="1" BackgroundColor="Yellow" />
            <Grid Grid.Row="2" BackgroundColor="Green" />
            <Grid Grid.Row="3" BackgroundColor="Blue" />
            <Editor Grid.Row="4" x:Name="txtTest" HeightRequest="150" WidthRequest="250" 
                    VerticalOptions="Start" HorizontalOptions="Start" 
                    Text="1&#xa;2&#xa;3&#xa;4&#xa;5&#xa;6&#xa;7&#xa;8&#xa;9&#xa;10" />
            <Grid Grid.Row="5" BackgroundColor="Purple" />
        </Grid>
    </ScrollView>

Code: https://github.com/cat0363/Maui-Issue13634

Assume that the Editor has enough Text to scroll. If you scroll vertically on the editor after scrolling to the editor, scrolling on the editor side will not work. Similar to when the Map is placed inside a ScrollView (#13628), It seems that the tap event is being handled by the Editor's parent control, ScrollView.

Since the build environment has not been prepared, it cannot be confirmed for iOS, but at least it is as described for Android.

https://user-images.githubusercontent.com/125236133/222359185-639224e3-5ead-4883-8f69-feecea7f079d.mp4

The only solution I found is below.

    public override bool DispatchTouchEvent(MotionEvent e) 
    {
        if (e.Action == MotionEventActions.Down ||
            e.Action == MotionEventActions.Move ||
            e.Action == MotionEventActions.Up) 
        {
            // Search EditText Control
            var editors = (Window.DecorView as ViewGroup).GetChildrenOfType<EditText>();

            foreach (var editor in editors) 
            {
                // Initialize EditText screen position 
                int[] pos = new int[2];
                // Set EditText screen position 
                editor.GetLocationOnScreen(pos);
                // Create hit test rectangle 
                Rect hitRect = new Rect(pos[0], pos[1], editor.Width, editor.Height);
                // Judge hit test
                bool isHitTest = hitRect.Contains(e.GetX(), e.GetY());
                // Hit test result is OK
                if (isHitTest) 
                {
                    // Touch event intercept
                    editor.Parent.RequestDisallowInterceptTouchEvent(true);
                }
            }
        }

        return base.DispatchTouchEvent(e);
    }

In the MainActivity.cs file, re-implement DispatchTouchEvent, get the EditText that exists in the page, execute the hit test, and if it is a EditText, intercept the touch event. Alternatively, you can reimplement the EditText's TouchListener in your EditorHandler to intercept touch events.

https://user-images.githubusercontent.com/125236133/222359497-c32f6388-dd6a-429c-bb92-a5dbd75232a3.mp4

Any other good ideas? I have not confirmed how it works on iOS, so I would like to know if there is no problem with iOS. Thank you.

cat0363 avatar Mar 02 '23 07:03 cat0363

Hi @cat0363. We have added the "s/needs-repro" label to this issue, which indicates that we require steps and sample code to reproduce the issue before we can take further action. Please try to create a minimal sample project/solution or code samples which reproduce the issue, ideally as a GitHub repo that we can clone. See more details about creating repros here: https://github.com/dotnet/maui/blob/main/.github/repro.md

This issue will be closed automatically in 7 days if we do not hear back from you by then - please feel free to re-open it if you come back to this issue after that time.

ghost avatar Mar 02 '23 19:03 ghost

I have uploaded the code for this issue to github. https://github.com/cat0363/Maui-Issue13634

cat0363 avatar Mar 03 '23 01:03 cat0363

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.

ghost avatar Mar 07 '23 21:03 ghost

Is the same problem with ios.

hector2d2 avatar Mar 22 '23 00:03 hector2d2

Hi, @hector2d2 I didn't have an iOS build environment ready yet, so thanks for investigating.

cat0363 avatar Mar 22 '23 02:03 cat0363

I also confirmed that this issue occurs on iOS as well. .NET MAUI 7.0 (Current) iOS 16.4

cat0363 avatar May 12 '23 07:05 cat0363

Verified this issue with Visual Studio Enterprise 17.7.0 Preview 2.0. Can repro on android platform with sample project. Maui-Issue13634-main.zip 13634

Zhanglirong-Winnie avatar Jun 26 '23 02:06 Zhanglirong-Winnie

Having the same issue. With .Net 7 on iOS only

OudomMunint avatar Nov 10 '23 09:11 OudomMunint

I am still seeing this issue in Maui

SamuelJames101 avatar May 23 '24 07:05 SamuelJames101

I am still seeing this issue in Maui

maonaoda avatar Jul 08 '24 10:07 maonaoda

I've hit same issue. I went with the Handler based alternative workaround, which worked well:

Anywhere in /Platforms/Android folder:

public class EditorScrollTouchListener : Java.Lang.Object, View.IOnTouchListener
{
	public bool OnTouch(View? v, MotionEvent? e)
	{
		if (v is AppCompatEditText editText)
		{
			if (IsTextScrollable(editText))
			{
				// It is not sufficient to just return false from OnTouch
				// you must request this from the parent control also
				editText.Parent?.RequestDisallowInterceptTouchEvent(true);
			}
		}

		return false;
	}

	private bool IsTextScrollable(EditText editText)
	{
		int textHeight = editText.Layout?.Height ?? 0;
		int visibleHeight = editText.Height - editText.PaddingTop - editText.PaddingBottom;

		return textHeight > visibleHeight;
	}
}

On startup in MauiProgram.cs:

		Microsoft.Maui.Handlers.EditorHandler.Mapper.AppendToMapping("EnableVerticalScroll", (handler, view) =>
		{
#if ANDROID
			handler.PlatformView.VerticalScrollBarEnabled = true;
			handler.PlatformView.SetOnTouchListener(new EditorScrollTouchListener());
#endif
		});

Thanks for sharing @cat0363

breenbob avatar Aug 23 '24 20:08 breenbob

I've added some notes here

https://github.com/dotnet/maui/pull/24531#issuecomment-2374724124

This isn't inherently a bug

Ideally here we wouldn't just change the behavior for Android users as that might frustrate some set of users. The approach here would be to add some mechanism to allow developers to control how touch behavior is captured when the user scrolls

PureWeen avatar Sep 25 '24 17:09 PureWeen

I've hit same issue. I went with the Handler based alternative workaround, which worked well:

Anywhere in /Platforms/Android folder:

public class EditorScrollTouchListener : Java.Lang.Object, View.IOnTouchListener
{
	public bool OnTouch(View? v, MotionEvent? e)
	{
		if (v is AppCompatEditText editText)
		{
			if (IsTextScrollable(editText))
			{
				// It is not sufficient to just return false from OnTouch
				// you must request this from the parent control also
				editText.Parent?.RequestDisallowInterceptTouchEvent(true);
			}
		}

		return false;
	}

	private bool IsTextScrollable(EditText editText)
	{
		int textHeight = editText.Layout?.Height ?? 0;
		int visibleHeight = editText.Height - editText.PaddingTop - editText.PaddingBottom;

		return textHeight > visibleHeight;
	}
}

Can you share the entire file for EditorScrollTouchListener. Various elements cannot be resolved (like View.IOnTouchListener or AppCompatEditText) due to missing usings that cannot be found or are incompatible.

Much appreciated.

LaraSQP avatar Oct 05 '24 22:10 LaraSQP

@LaraSQP all that is missing is the using statements:

using Android.Views;
using Android.Widget;
using AndroidX.AppCompat.Widget;
using View = Android.Views.View;

Hope this helps.

breenbob avatar Oct 07 '24 21:10 breenbob

@LaraSQP all that is missing is the using statements:

using Android.Views;
using Android.Widget;
using AndroidX.AppCompat.Widget;
using View = Android.Views.View;

Hope this helps.

Much appreciated. You rock.

LaraSQP avatar Oct 07 '24 21:10 LaraSQP

For now, an improved version of the touch listener for Editor that allows parent scrolling when the editor's content is already at the top or bottom, preventing unnecessary blocking of scroll gestures, until the official fix is released.

Anywhere in /Platforms/Android folder:

using Android.Views;
using Android.Widget;
using AndroidX.AppCompat.Widget;
using View = Android.Views.View;

namespace YourNamespace.Platforms.Android
{
    public class EditorScrollTouchListener : Java.Lang.Object, View.IOnTouchListener
    {
        private float? _lastY;

        public bool OnTouch(View? v, MotionEvent? e)
        {
            if (v is not AppCompatEditText editText || e == null)
                return false;

            switch (e.Action)
            {
                case MotionEventActions.Down:
                    _lastY = e.GetY();
                    editText.Parent?.RequestDisallowInterceptTouchEvent(true);
                    break;

                case MotionEventActions.Move:
                    if (!_lastY.HasValue)
                    {
                        _lastY = e.GetY();
                        return false;
                    }

                    float deltaY = e.GetY() - _lastY.Value;
                    _lastY = e.GetY();

                    bool isScrollable = IsTextScrollable(editText);
                    bool atTop = editText.ScrollY <= 0;
                    bool atBottom = editText.ScrollY >= GetMaxScroll(editText);

                    editText.Parent?.RequestDisallowInterceptTouchEvent(isScrollable && !(deltaY > 0 && atTop || deltaY < 0 && atBottom));
                    break;

                case MotionEventActions.Up:
                case MotionEventActions.Cancel:
                    _lastY = null;
                    editText.Parent?.RequestDisallowInterceptTouchEvent(false);
                    break;
            }
            return false;
        }

        private int GetMaxScroll(EditText editText) => (editText.Layout?.Height ?? 0) - editText.Height + editText.PaddingTop + editText.PaddingBottom;

        private bool IsTextScrollable(EditText editText) => (editText.Layout?.Height ?? 0) > editText.Height - editText.PaddingTop - editText.PaddingBottom;
    }
}

CreateMauiApp in MauiProgram.cs.

        Microsoft.Maui.Handlers.EditorHandler.Mapper.AppendToMapping("EnableVerticalScroll", (handler, view) =>
        {
#if ANDROID
            handler.PlatformView.VerticalScrollBarEnabled = true;
            handler.PlatformView.SetOnTouchListener(new YourNamespace.Platforms.Android.EditorScrollTouchListener());
#endif
        });

netstersk avatar Jul 23 '25 19:07 netstersk