Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,34 @@ void ClearLayoutListener()
_carouselViewLayoutListener = null;
}

// https://github.com/dotnet/maui/issues/13323
// CarouselView is a full-page pager; child-initiated rectangle scroll requests
// (e.g. EditText cursor positioning) must not scroll the carousel.
public override bool RequestChildRectangleOnScreen(
global::Android.Views.View child,
global::Android.Graphics.Rect rect,
bool immediate)
{
return false;
}

// https://github.com/dotnet/maui/issues/13323
// base.RequestChildFocus preserves normal focus propagation, but it may
// start a focus-driven scroll from an otherwise idle CarouselView.
public override void RequestChildFocus(
global::Android.Views.View child,
global::Android.Views.View focused)
{
var wasIdleBeforeFocus = ScrollState == RecyclerView.ScrollStateIdle;

base.RequestChildFocus(child, focused);

if (wasIdleBeforeFocus && ScrollState != RecyclerView.ScrollStateIdle)
{
StopScroll();
}
}

protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
// If the height or width are unbounded and the user is set to
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
#nullable enable
override Microsoft.Maui.Controls.Shapes.Shape.OnPropertyChanged(string? propertyName = null) -> void
~override Microsoft.Maui.Controls.Handlers.Items.MauiCarouselRecyclerView.RequestChildFocus(Android.Views.View child, Android.Views.View focused) -> void
~override Microsoft.Maui.Controls.Handlers.Items.MauiCarouselRecyclerView.RequestChildRectangleOnScreen(Android.Views.View child, Android.Graphics.Rect rect, bool immediate) -> bool
~override Microsoft.Maui.Controls.Handlers.Items.MauiRecyclerView<TItemsView, TAdapter, TItemsViewSource>.OnInterceptTouchEvent(Android.Views.MotionEvent e) -> bool
~override Microsoft.Maui.Controls.Handlers.Items.MauiRecyclerView<TItemsView, TAdapter, TItemsViewSource>.OnTouchEvent(Android.Views.MotionEvent e) -> bool
126 changes: 126 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue13323.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
namespace Maui.Controls.Sample.Issues;

[Issue(IssueTracker.Github, 13323,
"CarouselView on Android does not work if HorizontalTextAlignment in Entry is not Start",
PlatformAffected.Android)]
public class Issue13323 : ContentPage
{
public Issue13323()
{
var items = Enumerable.Range(0, 5).ToArray();

var positionLabel = new Label
{
AutomationId = "PositionLabel",
Text = "Position:0",
HorizontalOptions = LayoutOptions.Center,
};

var carousel = new CarouselView
{
AutomationId = "CarouselView13323",
ItemsSource = items,
Loop = false,
HeightRequest = 250,
ItemTemplate = new DataTemplate(() =>
{
var label = new Label
{
HeightRequest = 100,
HorizontalOptions = LayoutOptions.Fill,
VerticalOptions = LayoutOptions.Start,
BackgroundColor = Colors.LightGray,
HorizontalTextAlignment = TextAlignment.Center,
VerticalTextAlignment = TextAlignment.Center,
};
label.SetBinding(Label.TextProperty, ".", stringFormat: "Item {0}");

var entry = new Entry
{
Placeholder = "Tap me",
HorizontalTextAlignment = TextAlignment.Center,
HorizontalOptions = LayoutOptions.FillAndExpand,
};
Comment thread
praveenkumarkarunanithi marked this conversation as resolved.
entry.SetBinding(AutomationIdProperty, ".", stringFormat: "CenterEntry_{0}");

return new VerticalStackLayout
{
Children = { label, entry }
};
})
};

var goToItem2Button = new Button
{
AutomationId = "GoToItem2",
Text = "Go to Item 2",
};
goToItem2Button.Clicked += (s, e) => carousel.ScrollTo(2, animate: false);

carousel.PositionChanged += (s, e) =>
{
positionLabel.Text = $"Position:{carousel.Position}";
};

var loopPositionLabel = new Label
{
AutomationId = "LoopPositionLabel",
Text = "LoopPosition:0",
HorizontalOptions = LayoutOptions.Center,
};

var loopCarousel = new CarouselView
{
AutomationId = "LoopCarouselView13323",
ItemsSource = items,
Loop = true,
HeightRequest = 250,
ItemTemplate = new DataTemplate(() =>
{
var label = new Label
{
HeightRequest = 100,
HorizontalOptions = LayoutOptions.Fill,
VerticalOptions = LayoutOptions.Start,
BackgroundColor = Colors.LightGray,
HorizontalTextAlignment = TextAlignment.Center,
VerticalTextAlignment = TextAlignment.Center,
};
label.SetBinding(Label.TextProperty, ".", stringFormat: "Loop Item {0}");

var entry = new Entry
{
Placeholder = "Tap me",
HorizontalTextAlignment = TextAlignment.Center,
HorizontalOptions = LayoutOptions.FillAndExpand,
};
Comment thread
praveenkumarkarunanithi marked this conversation as resolved.
entry.SetBinding(AutomationIdProperty, ".", stringFormat: "LoopCenterEntry_{0}");

return new VerticalStackLayout
{
Children = { label, entry }
};
})
};

var loopGoToItem2Button = new Button
{
AutomationId = "LoopGoToItem2",
Text = "Go to Loop Item 2",
};
loopGoToItem2Button.Clicked += (s, e) => loopCarousel.ScrollTo(2, animate: false);

loopCarousel.PositionChanged += (s, e) =>
{
loopPositionLabel.Text = $"LoopPosition:{loopCarousel.Position}";
};

Content = new ScrollView
{
Content = new VerticalStackLayout
{
Children = { positionLabel, goToItem2Button, carousel, loopPositionLabel, loopGoToItem2Button, loopCarousel }
}
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using Microsoft.Maui.TestCases.Tests;
using NUnit.Framework;
using UITest.Appium;
using UITest.Core;

namespace Microsoft.Maui.AppUITests.Issues;

public class Issue13323 : _IssuesUITest
Comment thread
praveenkumarkarunanithi marked this conversation as resolved.
{
public Issue13323(TestDevice device) : base(device) { }

public override string Issue => "CarouselView on Android does not work if HorizontalTextAlignment in Entry is not Start";

[Test]
[Category(UITestCategories.CarouselView)]
public void CarouselView_EntryTap_DoesNotChangePosition()
{
App.WaitForElement("CarouselView13323", timeout: TimeSpan.FromSeconds(30));
App.WaitForElement("PositionLabel", timeout: TimeSpan.FromSeconds(15));

App.Tap("GoToItem2");
App.WaitForElement("CenterEntry_2", timeout: TimeSpan.FromSeconds(10));

var positionBefore = App.FindElement("PositionLabel").GetText();
Comment thread
praveenkumarkarunanithi marked this conversation as resolved.
Comment thread
praveenkumarkarunanithi marked this conversation as resolved.
Assert.That(positionBefore, Is.EqualTo("Position:2"), $"CarouselView did not reach Position:2 before tapping Entry. Actual: {positionBefore}");

App.Tap("CenterEntry_2");

var positionAfter = App.FindElement("PositionLabel").GetText();
Comment thread
praveenkumarkarunanithi marked this conversation as resolved.
Comment thread
praveenkumarkarunanithi marked this conversation as resolved.
Assert.That(positionAfter, Is.EqualTo("Position:2"), $"CarouselView jumped after tapping Center-aligned Entry. Before: {positionBefore}, After: {positionAfter}");

App.DismissKeyboard();
#if ANDROID
App.WaitForKeyboardToHide();
#endif
}

[Test]
[Category(UITestCategories.CarouselView)]
public void CarouselView_Loop_EntryTap_DoesNotChangePosition()
{
App.WaitForElement("LoopCarouselView13323", timeout: TimeSpan.FromSeconds(30));
App.WaitForElement("LoopPositionLabel", timeout: TimeSpan.FromSeconds(15));

App.Tap("LoopGoToItem2");
App.WaitForElement("LoopCenterEntry_2", timeout: TimeSpan.FromSeconds(10));

var positionBefore = App.FindElement("LoopPositionLabel").GetText();
Assert.That(positionBefore, Is.EqualTo("LoopPosition:2"), $"Loop CarouselView did not reach LoopPosition:2 before tapping Entry. Actual: {positionBefore}");

App.Tap("LoopCenterEntry_2");

var positionAfter = App.FindElement("LoopPositionLabel").GetText();
Assert.That(positionAfter, Is.EqualTo("LoopPosition:2"), $"CarouselView (Loop=true) jumped after tapping Center-aligned Entry. Before: {positionBefore}, After: {positionAfter}");

App.DismissKeyboard();
#if ANDROID
App.WaitForKeyboardToHide();
#endif
}
}
Loading