-
Notifications
You must be signed in to change notification settings - Fork 1.9k
[Android] Fixed the Label Width related issues #29620
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
Hey there @@Ahamed-Ali! Thank you so much for your PR! Someone from the team will get assigned to your PR shortly and we'll get it reviewed. |
|
/azp run MAUI-UITests-public |
|
Azure Pipelines successfully started running 1 pipeline(s). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR fixes label clipping issues on Android when rotating devices by switching to a static layout measurement and adds automated UI tests to verify correct sizing in a CollectionView scenario.
- Replaces reliance on
LayoutwithTextLayoutUtils.CreateLayoutinMauiTextView.OnMeasure - Introduces a UI test (
Issue29542) to ensure labels size properly after orientation changes - Adds a HostApp reproduction page for the issue with a
CollectionViewand test automation IDs
Reviewed Changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| src/Core/src/Platform/Android/MauiTextView.cs | Use StaticLayout from TextLayoutUtils for width measurement instead of the native Layout logic |
| src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue29542.cs | Added a UI test that rotates the emulator and verifies label sizing in a CollectionView |
| src/Controls/tests/TestCases.HostApp/Issues/Issue29542.cs | Provided a sample page with a CollectionView and navigation button to reproduce the clipping bug |
Comments suppressed due to low confidence (1)
src/Core/src/Platform/Android/MauiTextView.cs:28
- [nitpick] The local variable
layoutshadows theLayoutproperty from the base class; consider renaming it to something more descriptive likestaticLayoutto avoid confusion.
var layout = TextLayoutUtils.CreateLayout(Text, Paint, availableWidth - totalPadding, Android.Text.Layout.Alignment.AlignNormal);
| protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec) | ||
| { | ||
| if (MeasureSpec.GetMode(widthMeasureSpec) == MeasureSpecMode.AtMost && Layout is not null) | ||
| if (MeasureSpec.GetMode(widthMeasureSpec) == MeasureSpecMode.AtMost && !string.IsNullOrEmpty(Text)) | ||
| { | ||
| // Ensure the Layout is valid and measured before reading LineCount or GetLineWidth(i) to avoid unnecessary calculations. | ||
| if (Layout.Width > 0) | ||
| { | ||
| // Calculate the total width needed based on text content plus padding | ||
| int contentWidth = (int)Math.Ceiling(GetMaxLineWidth(Layout)); | ||
| int totalPadding = CompoundPaddingLeft + CompoundPaddingRight; | ||
| int requiredWidth = contentWidth + totalPadding; | ||
|
|
||
| // Constrain to maximum available width from original spec | ||
| int availableWidth = MeasureSpec.GetSize(widthMeasureSpec); | ||
| int desiredWidth = Math.Min(requiredWidth, availableWidth); | ||
| widthMeasureSpec = MeasureSpec.MakeMeasureSpec(desiredWidth, MeasureSpecMode.AtMost); | ||
| } | ||
| int availableWidth = MeasureSpec.GetSize(widthMeasureSpec); | ||
| int totalPadding = CompoundPaddingLeft + CompoundPaddingRight; | ||
| var layout = TextLayoutUtils.CreateLayout(Text, Paint, availableWidth - totalPadding, Android.Text.Layout.Alignment.AlignNormal); | ||
| int contentWidth = (int)Math.Ceiling(GetMaxLineWidth(layout)); |
Copilot
AI
May 23, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Creating a new StaticLayout on every measure may be expensive; consider caching the layout when the text or width changes to avoid redundant allocations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of creating a new layout every time OnMeasure is called, caching the layout if the text and width haven’t changed might improve performance
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have created a new layout only when necessary to improve performance. @jsuarezruiz
| @@ -0,0 +1,25 @@ | |||
| #if TEST_FAILS_ON_CATALYST && TEST_FAILS_ON_WINDOWS //The test fails on Windows and MacCatalyst because the SetOrientation method, which is intended to change the device orientation, is only supported on mobile platforms iOS and Android. | |||
Copilot
AI
May 23, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The preprocessor directive uses && which means the test will only compile when both macros are defined; likely you intended || so it’s excluded on either platform where it fails.
| #if TEST_FAILS_ON_CATALYST && TEST_FAILS_ON_WINDOWS //The test fails on Windows and MacCatalyst because the SetOrientation method, which is intended to change the device orientation, is only supported on mobile platforms iOS and Android. | |
| #if TEST_FAILS_ON_CATALYST || TEST_FAILS_ON_WINDOWS //The test fails on Windows and MacCatalyst because the SetOrientation method, which is intended to change the device orientation, is only supported on mobile platforms iOS and Android. |
|
/azp run MAUI-UITests-public |
|
Azure Pipelines successfully started running 1 pipeline(s). |
| protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec) | ||
| { | ||
| if (MeasureSpec.GetMode(widthMeasureSpec) == MeasureSpecMode.AtMost && Layout is not null) | ||
| if (MeasureSpec.GetMode(widthMeasureSpec) == MeasureSpecMode.AtMost && !string.IsNullOrEmpty(Text)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You check !string.IsNullOrEmpty(Text), but if Text is empty later, will the component resize correctly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have verified this by dynamically setting the text to an empty string (string.Empty); it resizes correctly, just as it did before the fix.
| protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec) | ||
| { | ||
| if (MeasureSpec.GetMode(widthMeasureSpec) == MeasureSpecMode.AtMost && Layout is not null) | ||
| if (MeasureSpec.GetMode(widthMeasureSpec) == MeasureSpecMode.AtMost && !string.IsNullOrEmpty(Text)) | ||
| { | ||
| // Ensure the Layout is valid and measured before reading LineCount or GetLineWidth(i) to avoid unnecessary calculations. | ||
| if (Layout.Width > 0) | ||
| { | ||
| // Calculate the total width needed based on text content plus padding | ||
| int contentWidth = (int)Math.Ceiling(GetMaxLineWidth(Layout)); | ||
| int totalPadding = CompoundPaddingLeft + CompoundPaddingRight; | ||
| int requiredWidth = contentWidth + totalPadding; | ||
|
|
||
| // Constrain to maximum available width from original spec | ||
| int availableWidth = MeasureSpec.GetSize(widthMeasureSpec); | ||
| int desiredWidth = Math.Min(requiredWidth, availableWidth); | ||
| widthMeasureSpec = MeasureSpec.MakeMeasureSpec(desiredWidth, MeasureSpecMode.AtMost); | ||
| } | ||
| int availableWidth = MeasureSpec.GetSize(widthMeasureSpec); | ||
| int totalPadding = CompoundPaddingLeft + CompoundPaddingRight; | ||
| var layout = TextLayoutUtils.CreateLayout(Text, Paint, availableWidth - totalPadding, Android.Text.Layout.Alignment.AlignNormal); | ||
| int contentWidth = (int)Math.Ceiling(GetMaxLineWidth(layout)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of creating a new layout every time OnMeasure is called, caching the layout if the text and width haven’t changed might improve performance
| // Calculate the required width based on the content and padding | ||
| int requiredWidth = contentWidth + totalPadding; | ||
| int desiredWidth = Math.Min(requiredWidth, availableWidth); | ||
| widthMeasureSpec = MeasureSpec.MakeMeasureSpec(desiredWidth, MeasureSpecMode.AtMost); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assumes that desiredWidth is always valid. If padding is larger than available width, does this result in unexpected behavior?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tested with a higher padding value, and it behaves the same as before the fix.
However, I have restricted the creation of a new layout if the padding exceeds the available width, as base.Measure can handle this scenario. @jsuarezruiz
|
/azp run MAUI-UITests-public |
|
Azure Pipelines successfully started running 1 pipeline(s). |
jsuarezruiz
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is similar to 294542 but, could you include another test using the sample case from #29727?
Yes, I have added another test using the sample case from issue #29727. @jsuarezruiz |
|
/azp run |
|
Azure Pipelines successfully started running 3 pipeline(s). |
|
/azp run |
|
Azure Pipelines successfully started running 3 pipeline(s). |
PureWeen
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LabelShouldSizeProperlyOnCollectionViewWithoutBlankSpace is failing
I modified the test case to resolve the test failure. @PureWeen |
|
/azp run |
|
Azure Pipelines successfully started running 3 pipeline(s). |
PureWeen
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a bit curious about the build up of changes from OnMeasure that started here
If I run all the tests from this PR and 28215 and I completely revert all the changes added into OnMeasure everything seems to work fine.
There's a slight adjustment of text size but the issue articulated here #27614 doesn't reproduce
Here's the diff when I revert this PR and 28215 against the tests added in 28215
Also here's the test added to this PR with all changes reverted
I'm not sure I'm seeing a use case yet for why we keep adding code into OnMeasure ?
|
Closing in favor of |


Note
Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!
Root Cause of the issue
Description of Change
Issues Fixed
Fixes #29542
Fixes #29727
Fixes #28928
Tested the behaviour in the following platforms
Screenshot
CollectionViewLabelIssue.mov
CollectionViewLabelFix.mov
BlankSpaceIssue.mov
BlankSpaceIssuefixed.mov