diff --git a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/WordWrapLineBreakModeNoExtraSpace.png b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/WordWrapLineBreakModeNoExtraSpace.png index 043b5348f47e..02ff38e545c3 100644 Binary files a/src/Controls/tests/TestCases.Android.Tests/snapshots/android/WordWrapLineBreakModeNoExtraSpace.png and b/src/Controls/tests/TestCases.Android.Tests/snapshots/android/WordWrapLineBreakModeNoExtraSpace.png differ diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue31782.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue31782.cs new file mode 100644 index 000000000000..987355f98900 --- /dev/null +++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue31782.cs @@ -0,0 +1,34 @@ +namespace Maui.Controls.Sample.Issues; + +[Issue(IssueTracker.Github, 31782, "Unexpected Line Breaks in Android, Label with WordWrap Mode Due to Trailing Space", PlatformAffected.Android)] +public class Issue31782 : ContentPage +{ + public Issue31782() + { + var layout = new VerticalStackLayout { Spacing = 20, Padding = 20 }; + + var container = new Grid + { + AutomationId = "Container", + WidthRequest = 300, + HorizontalOptions = LayoutOptions.Center, + BackgroundColor = Colors.LightBlue + }; + + var label = new Label + { + AutomationId = "TestLabel", + Text = "How does .NET MAUI handle mauinavigaton?", + LineBreakMode = LineBreakMode.WordWrap, + TextColor = Color.FromArgb("#212121"), + FontSize = 20, + HorizontalOptions = LayoutOptions.End, + BackgroundColor = Colors.LightGreen + }; + + container.Add(label); + layout.Add(container); + + Content = layout; + } +} diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue31782.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue31782.cs new file mode 100644 index 000000000000..b4e684ef76ec --- /dev/null +++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue31782.cs @@ -0,0 +1,25 @@ +using NUnit.Framework; +using UITest.Appium; +using UITest.Core; + +namespace Microsoft.Maui.TestCases.Tests.Issues; + +public class Issue31782 : _IssuesUITest +{ + public Issue31782(TestDevice device) : base(device) { } + + public override string Issue => "Unexpected Line Breaks in Android, Label with WordWrap Mode Due to Trailing Space"; + + [Test] + [Category(UITestCategories.Label)] + public void LabelWithEndAlignmentShouldNotHaveTrailingSpace() + { + var labelRect = App.WaitForElement("TestLabel").GetRect(); + var containerRect = App.WaitForElement("Container").GetRect(); + + // Before fix: Label width ≈ container width (300pt with trailing space) + // After fix: Label width < container width (measures only longest line) + Assert.That(labelRect.Width, Is.LessThan(containerRect.Width - 10), + $"End-aligned label ({labelRect.Width}pt) should be narrower than container ({containerRect.Width}pt)"); + } +} diff --git a/src/Core/src/Handlers/Label/LabelHandler.Android.cs b/src/Core/src/Handlers/Label/LabelHandler.Android.cs index ac4f3fd7d229..d70b114984d7 100644 --- a/src/Core/src/Handlers/Label/LabelHandler.Android.cs +++ b/src/Core/src/Handlers/Label/LabelHandler.Android.cs @@ -1,3 +1,4 @@ +using Android.Text; using Android.Views; using AndroidX.AppCompat.Widget; using Microsoft.Maui.Graphics; @@ -15,6 +16,35 @@ public override void PlatformArrange(Rect frame) base.PlatformArrange(frame); } + public override Size GetDesiredSize(double widthConstraint, double heightConstraint) + { + var size = base.GetDesiredSize(widthConstraint, heightConstraint); + + // Android TextView reports full available width instead of actual text width when + // text wraps to multiple lines, causing incorrect positioning for non-Fill alignments. + if (VirtualView.HorizontalLayoutAlignment != Primitives.LayoutAlignment.Fill && + PlatformView?.Layout is Layout layout && + layout.LineCount > 1) + { + float maxLineWidth = 0; + for (int i = 0; i < layout.LineCount; i++) + { + float lineWidth = layout.GetLineWidth(i); + if (lineWidth > maxLineWidth) + maxLineWidth = lineWidth; + } + + if (maxLineWidth > 0) + { + var actualWidth = Context.FromPixels(maxLineWidth + PlatformView.PaddingLeft + PlatformView.PaddingRight); + if (actualWidth < size.Width) + return new Size(actualWidth, size.Height); + } + } + + return size; + } + internal static void MapBackground(ILabelHandler handler, ILabel label) { handler.PlatformView?.UpdateBackground(label); diff --git a/src/Core/src/PublicAPI/net-android/PublicAPI.Unshipped.txt b/src/Core/src/PublicAPI/net-android/PublicAPI.Unshipped.txt index 7dc5c58110bf..ebf19fa9dfd5 100644 --- a/src/Core/src/PublicAPI/net-android/PublicAPI.Unshipped.txt +++ b/src/Core/src/PublicAPI/net-android/PublicAPI.Unshipped.txt @@ -1 +1,2 @@ #nullable enable +override Microsoft.Maui.Handlers.LabelHandler.GetDesiredSize(double widthConstraint, double heightConstraint) -> Microsoft.Maui.Graphics.Size