-
Notifications
You must be signed in to change notification settings - Fork 1.8k
[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
base: main
Are you sure you want to change the base?
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
Layout
withTextLayoutUtils.CreateLayout
inMauiTextView.OnMeasure
- Introduces a UI test (
Issue29542
) to ensure labels size properly after orientation changes - Adds a HostApp reproduction page for the issue with a
CollectionView
and 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
layout
shadows theLayout
property from the base class; consider renaming it to something more descriptive likestaticLayout
to 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)); |
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.
Copilot uses AI. Check for mistakes.
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. |
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. |
Copilot uses AI. Check for mistakes.
/azp run MAUI-UITests-public |
Azure Pipelines successfully started running 1 pipeline(s). |
@@ -20,21 +21,16 @@ internal override void OnLayoutFormatted(bool changed, int l, int t, int r, int | |||
} | |||
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). |
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
Tested the behaviour in the following platforms
Screenshot
CollectionViewLabelIssue.mov
CollectionViewLabelFix.mov