Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
81 changes: 81 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue34120.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
namespace Maui.Controls.Sample.Issues;

[Issue(IssueTracker.Github, 34120, "Label text truncated in ScrollView when MaxLines is set", PlatformAffected.Android)]
public class Issue34120 : ContentPage
{
// Reproduces the N3_Navigation layout: horizontal ScrollView with BindableLayout,
// 200×200 Border cards, Image (HeightRequest=120), and a Label with MaxLines=2.
record Issue34120MonkeyItem(string Name, string ImageUrl);

public Issue34120()
{
// Long names ("Golden Snub-nosed Monkey", "Tonkin Snub-nosed Monkey") are the ones
// that triggered truncation; "Baboon" is a short-name reference card.
var monkeys = new List<Issue34120MonkeyItem>
{
new("Golden Snub-nosed Monkey", "golden.jpg"),
new("Baboon", "papio.jpg"),
new("Tonkin Snub-nosed Monkey", "bluemonkey.jpg"),
new("Howler Monkey", "alouatta.jpg"),
new("Squirrel Monkey", "saimiri.jpg"),
};

var itemTemplate = new DataTemplate(() =>
{
var image = new Image
{
Aspect = Aspect.AspectFit,
HeightRequest = 120,
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center,
};
image.SetBinding(Image.SourceProperty, "ImageUrl");

var nameLabel = new Label
{
FontSize = 14,
FontAttributes = FontAttributes.Bold,
BackgroundColor = Color.FromArgb("#AAFFFFFF"),
TextColor = Colors.Black,
Padding = new Thickness(4, 2),
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center,
HorizontalTextAlignment = TextAlignment.Center,
LineBreakMode = LineBreakMode.WordWrap,
MaxLines = 2,
};
nameLabel.SetBinding(Label.TextProperty, "Name");
nameLabel.SetBinding(Label.AutomationIdProperty, "Name");

var card = new Border
{
Padding = new Thickness(10),
Stroke = Colors.LightGray,
StrokeThickness = 1,
WidthRequest = 200,
HeightRequest = 200,
BackgroundColor = Colors.White,
Content = new VerticalStackLayout
{
Spacing = 10,
Children = { image, nameLabel }
}
};
return card;
});

var horizontalStack = new HorizontalStackLayout
{
Spacing = 15,
Padding = new Thickness(5),
};
BindableLayout.SetItemTemplate(horizontalStack, itemTemplate);
BindableLayout.SetItemsSource(horizontalStack, monkeys);

Content = new ScrollView
{
Orientation = ScrollOrientation.Horizontal,
Content = horizontalStack,
};
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using NUnit.Framework;
using UITest.Appium;
using UITest.Core;

namespace Microsoft.Maui.TestCases.Tests.Issues;

public class Issue34120 : _IssuesUITest
{
public override string Issue => "Label text truncated in ScrollView when MaxLines is set";

public Issue34120(TestDevice device) : base(device) { }

[Test]
[Category(UITestCategories.Label)]
public void LabelNotTruncatedWithMaxLines()
{
// Wait for the page to load, then verify labels are not truncated.
App.WaitForElement("Golden Snub-nosed Monkey");
VerifyScreenshot();
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
31 changes: 30 additions & 1 deletion src/Core/src/Handlers/Label/LabelHandler.Android.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,12 @@ public override Size GetDesiredSize(double widthConstraint, double heightConstra

// Android TextView reports full available width instead of actual text width when
// text wraps to multiple lines, causing incorrect positioning for non-Fill alignments.
// We narrow the desired width to the widest rendered line, but only when that narrowing
// won't cause re-wrapping that exceeds MaxLines and truncates visible text.
if (VirtualView.HorizontalLayoutAlignment != Primitives.LayoutAlignment.Fill &&
PlatformView?.Layout is Layout layout &&
layout.LineCount > 1)
layout.LineCount > 1 &&
PlatformView.Ellipsize == null)
{
float maxLineWidth = 0;
for (int i = 0; i < layout.LineCount; i++)
Expand All @@ -38,7 +41,33 @@ public override Size GetDesiredSize(double widthConstraint, double heightConstra
{
var actualWidth = Context.FromPixels(maxLineWidth + PlatformView.PaddingLeft + PlatformView.PaddingRight);
if (actualWidth < size.Width)
{
// When MaxLines is constrained, verify that narrowing doesn't cause the text
// to re-wrap into more lines than MaxLines allows (which would truncate text).
// Re-measure at exactly the pixel width the view will be arranged at.
if (PlatformView.MaxLines != int.MaxValue)
{
var narrowedPx = (int)Context.ToPixels(actualWidth);

// AtMost mirrors how the layout pass constrains width, ensuring the
// re-measurement reflects the same wrapping behaviour the view will
// experience when arranged at actualWidth.
PlatformView.Measure(
MeasureSpecMode.AtMost.MakeMeasureSpec(narrowedPx),
MeasureSpecMode.Unspecified.MakeMeasureSpec(0));

// Fail-safe: if Layout is null after re-measurement we cannot verify
// that truncation won't occur, so return the original size.
var measuredLayout = PlatformView.Layout;

if (measuredLayout is null || measuredLayout.LineCount > PlatformView.MaxLines)
{
return size; // Narrowing causes truncation (or unverifiable); return original size
}
}

return new Size(actualWidth, size.Height);
}
}
}

Expand Down
Loading