[Android] Fix for Label WordWrap width issue causing HorizontalOptions misalignment #33281
Conversation
There was a problem hiding this comment.
Pull request overview
This PR fixes an Android-specific Label rendering issue where multi-line labels with LineBreakMode="WordWrap" and non-Fill horizontal alignment incorrectly report the full constraint width instead of the actual rendered text width. This causes unnecessary trailing space and incorrect alignment. The fix updates GetDesiredSize() in LabelHandler.Android.cs to calculate and return the actual maximum line width for wrapped, multi-line labels.
Key changes:
- Override
GetDesiredSize()inLabelHandler.Android.csto compute actual text width by finding the widest line - Add public API entry for the new override method
- Add UI test to verify the fix with automated assertions
Reviewed changes
Copilot reviewed 4 out of 5 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
src/Core/src/PublicAPI/net-android/PublicAPI.Unshipped.txt |
Adds public API entry for the new GetDesiredSize override in LabelHandler |
src/Core/src/Handlers/Label/LabelHandler.Android.cs |
Implements GetDesiredSize override to calculate actual width for multi-line wrapped labels |
src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue31782.cs |
Adds NUnit test to verify label width is narrower than container when end-aligned |
src/Controls/tests/TestCases.HostApp/Issues/Issue31782.cs |
Creates test page demonstrating the issue with wrapped label in fixed-width container |
src/Controls/tests/TestCases.Android.Tests/snapshots/android/WordWrapLineBreakModeNoExtraSpace.png |
Adds visual verification snapshot (binary PNG file) |
|
/rebase |
6d3a120 to
f7ac170
Compare
|
/azp run maui-pr-uitests |
|
Azure Pipelines successfully started running 1 pipeline(s). |
PR Review: #33281 - [Android] Fix for Label WordWrap width issue causing HorizontalOptions misalignmentDate: 2026-01-05 | Issue: #31782 | PR: #33281 ✅ Final Recommendation: APPROVE
📋 Issue SummaryProblem: On Android,
Steps to Reproduce:
Platforms Affected:
Key Issue Comments:
📁 Files Changed
💬 PR Discussion SummaryKey Comments:
Reviewer Feedback:
Disagreements to Investigate:
Author Uncertainty:
🧪 TestsStatus: ✅ COMPLETE
Test Files:
Test Logic:
🚦 Gate - Test VerificationStatus: ✅ PASSED
Result: PASSED ✅ - Tests correctly detect the issue (fail without fix, pass with fix) 🔍 AnalysisStatus: ✅ COMPLETE
Root Cause: Android's Why does Android behave this way?
Alternative Approaches Considered:
My Approach: The PR's approach is correct and minimal. Overriding
⚖️ CompareStatus: ✅ COMPLETE
Recommendation: The PR's fix is the correct approach:
🔬 RegressionStatus: ✅ COMPLETE Edge Cases Verified:
Code Safety Analysis:
Disagreements Investigated:
Potential Regressions: 🚨 CRITICAL FINDING - Previous Similar Fix Was Reverted: A very similar fix was attempted before and REVERTED:
Comparison of Reverted Fix vs Current PR:
Why This Fix MAY Be Safer:
✅ GOOD NEWS: Test Coverage Exists for the Regression Scenario! Issue29542 test (
This test SHOULD catch if PR #33281 causes the same regression. Recommended Action:
Regression Test Result (2026-01-05):
Alternative Approaches Considered:
Final Recommendation: ✅ APPROVE 📚 Technical Background & SourcesWhy This Fix Is Necessary (Android TextView Measurement Behavior)The Core Problem: Android's Official Android Documentation
How Native Android Developers Solve ThisNative Android developers face the same issue. The standard solution is to override // Standard native Android pattern for content-width measurement
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Layout layout = getLayout();
if (layout != null && layout.getLineCount() > 1) {
float maxWidth = 0;
for (int i = 0; i < layout.getLineCount(); i++) {
maxWidth = Math.max(maxWidth, layout.getLineWidth(i));
}
int width = (int) Math.ceil(maxWidth) + getPaddingLeft() + getPaddingRight();
setMeasuredDimension(width, getMeasuredHeight());
}
}MAUI Codebase PrecedentThe same technique is already used in MAUI's Graphics library: public static SizeF GetTextSizeAsSizeF(this StaticLayout target, bool hasBoundedWidth)
{
// We need to know if the static layout was created with a bounded width,
// as this is what StaticLayout.Width returns.
if (hasBoundedWidth)
return new SizeF(target.Width, target.Height);
float maxWidth = 0;
int lineCount = target.LineCount;
for (int i = 0; i < lineCount; i++)
{
float lineWidth = target.GetLineWidth(i);
if (lineWidth > maxWidth)
maxWidth = lineWidth;
}
return new SizeF(maxWidth, target.Height);
}The comment explicitly states: "We need to know if the static layout was created with a bounded width, as this is what StaticLayout.Width returns." Common Developer Resources
Both discussions recommend using Android Source Code (AOSP)TextView.java in AOSP shows that when Justification:
|
…s misalignment (#33281) ### Root Cause On Android, for `Label` controls with `LineBreakMode="WordWrap"` and multiple lines, `GetDesiredSize()` returns the full constraint width instead of the actual rendered text width. This causes extra trailing space and incorrect horizontal alignment when `HorizontalLayoutAlignment` is set to `Start`, `Center`, or `End`. Single-line and Fill alignment cases are unaffected. ### Description of Change Updated `GetDesiredSize()` in `LabelHandler.Android.cs` to calculate the actual content width for wrapped, multi-line labels with non-Fill alignment. The implementation determines the widest rendered line using `layout.GetLineWidth(i)` and uses that value (plus padding) as the desired width. ### Issues Fixed Fixes #31782 Fixes #27614 Tested the behaviour in the following platforms - [x] Android - [x] Windows - [x] iOS - [x] Mac ### Output Video | Before Issue Fix | After Issue Fix | |------------------|-----------------| | <img width="350" alt="withoutfix" src="https://github.com/user-attachments/assets/0eca49ce-3cfd-4f42-85cb-98560385a056" /> | <img width="350" alt="withfix" src="https://github.com/user-attachments/assets/a1cba4c6-d422-46ab-96ae-db0dc6dc0dc9" /> |
…s misalignment (#33281) ### Root Cause On Android, for `Label` controls with `LineBreakMode="WordWrap"` and multiple lines, `GetDesiredSize()` returns the full constraint width instead of the actual rendered text width. This causes extra trailing space and incorrect horizontal alignment when `HorizontalLayoutAlignment` is set to `Start`, `Center`, or `End`. Single-line and Fill alignment cases are unaffected. ### Description of Change Updated `GetDesiredSize()` in `LabelHandler.Android.cs` to calculate the actual content width for wrapped, multi-line labels with non-Fill alignment. The implementation determines the widest rendered line using `layout.GetLineWidth(i)` and uses that value (plus padding) as the desired width. ### Issues Fixed Fixes #31782 Fixes #27614 Tested the behaviour in the following platforms - [x] Android - [x] Windows - [x] iOS - [x] Mac ### Output Video | Before Issue Fix | After Issue Fix | |------------------|-----------------| | <img width="350" alt="withoutfix" src="https://github.com/user-attachments/assets/0eca49ce-3cfd-4f42-85cb-98560385a056" /> | <img width="350" alt="withfix" src="https://github.com/user-attachments/assets/a1cba4c6-d422-46ab-96ae-db0dc6dc0dc9" /> |
…s misalignment (#33281) ### Root Cause On Android, for `Label` controls with `LineBreakMode="WordWrap"` and multiple lines, `GetDesiredSize()` returns the full constraint width instead of the actual rendered text width. This causes extra trailing space and incorrect horizontal alignment when `HorizontalLayoutAlignment` is set to `Start`, `Center`, or `End`. Single-line and Fill alignment cases are unaffected. ### Description of Change Updated `GetDesiredSize()` in `LabelHandler.Android.cs` to calculate the actual content width for wrapped, multi-line labels with non-Fill alignment. The implementation determines the widest rendered line using `layout.GetLineWidth(i)` and uses that value (plus padding) as the desired width. ### Issues Fixed Fixes #31782 Fixes #27614 Tested the behaviour in the following platforms - [x] Android - [x] Windows - [x] iOS - [x] Mac ### Output Video | Before Issue Fix | After Issue Fix | |------------------|-----------------| | <img width="350" alt="withoutfix" src="https://github.com/user-attachments/assets/0eca49ce-3cfd-4f42-85cb-98560385a056" /> | <img width="350" alt="withfix" src="https://github.com/user-attachments/assets/a1cba4c6-d422-46ab-96ae-db0dc6dc0dc9" /> |
…s misalignment (#33281) ### Root Cause On Android, for `Label` controls with `LineBreakMode="WordWrap"` and multiple lines, `GetDesiredSize()` returns the full constraint width instead of the actual rendered text width. This causes extra trailing space and incorrect horizontal alignment when `HorizontalLayoutAlignment` is set to `Start`, `Center`, or `End`. Single-line and Fill alignment cases are unaffected. ### Description of Change Updated `GetDesiredSize()` in `LabelHandler.Android.cs` to calculate the actual content width for wrapped, multi-line labels with non-Fill alignment. The implementation determines the widest rendered line using `layout.GetLineWidth(i)` and uses that value (plus padding) as the desired width. ### Issues Fixed Fixes #31782 Fixes #27614 Tested the behaviour in the following platforms - [x] Android - [x] Windows - [x] iOS - [x] Mac ### Output Video | Before Issue Fix | After Issue Fix | |------------------|-----------------| | <img width="350" alt="withoutfix" src="https://github.com/user-attachments/assets/0eca49ce-3cfd-4f42-85cb-98560385a056" /> | <img width="350" alt="withfix" src="https://github.com/user-attachments/assets/a1cba4c6-d422-46ab-96ae-db0dc6dc0dc9" /> |
…s misalignment (#33281) ### Root Cause On Android, for `Label` controls with `LineBreakMode="WordWrap"` and multiple lines, `GetDesiredSize()` returns the full constraint width instead of the actual rendered text width. This causes extra trailing space and incorrect horizontal alignment when `HorizontalLayoutAlignment` is set to `Start`, `Center`, or `End`. Single-line and Fill alignment cases are unaffected. ### Description of Change Updated `GetDesiredSize()` in `LabelHandler.Android.cs` to calculate the actual content width for wrapped, multi-line labels with non-Fill alignment. The implementation determines the widest rendered line using `layout.GetLineWidth(i)` and uses that value (plus padding) as the desired width. ### Issues Fixed Fixes #31782 Fixes #27614 Tested the behaviour in the following platforms - [x] Android - [x] Windows - [x] iOS - [x] Mac ### Output Video | Before Issue Fix | After Issue Fix | |------------------|-----------------| | <img width="350" alt="withoutfix" src="https://github.com/user-attachments/assets/0eca49ce-3cfd-4f42-85cb-98560385a056" /> | <img width="350" alt="withfix" src="https://github.com/user-attachments/assets/a1cba4c6-d422-46ab-96ae-db0dc6dc0dc9" /> |
…e - 1 (#33602) This PR addresses the UI test image failures that occurred in the inflight/candidate branch #33574 and includes updates to improve rendering and test stability across platforms. - Resaved snapshots for these test cases due to this PR fix #33281: CanvasShouldHonorBlur, VerifyScrollViewWithScrollToPositionCenterAndGridContent, VerifyScrollViewWithRTLAndGridContent, VerifyScrollViewWithScrollToPositionEndAndGridContent, VerifyScrollViewWithScrollToPositionMakeVisibleAndGridContent, VerifyScrollViewWithScrollToPositionStartAndGridContent, DownSizeImageAppearProperly, Issue30440ImageShouldClipCorrectly, IndicatorViewWithTemplatedIcon, VerifyCarouselLayoutOrientationChange, VerifyCarouselViewItemsSourceClearedDynamically, EmptyViewShouldDisplayWhenCollectionViewIsInsideVerticalStackLayout. - Issue18751Test - Added the failing condition for Android due to the image not loading on CI. Already restricted for other platforms; now it fails on Android as well. - Issue18751Test - Added the failing condition for Windows due to AutomationId not working on layout in Appium. - VariousSpanGesturePermutation - Increased the tap coordinates due to this PR fix #33281 changes. --------- Co-authored-by: sheiksyedm <sheiksyedm@syncfusion.com>
…s misalignment (#33281) ### Root Cause On Android, for `Label` controls with `LineBreakMode="WordWrap"` and multiple lines, `GetDesiredSize()` returns the full constraint width instead of the actual rendered text width. This causes extra trailing space and incorrect horizontal alignment when `HorizontalLayoutAlignment` is set to `Start`, `Center`, or `End`. Single-line and Fill alignment cases are unaffected. ### Description of Change Updated `GetDesiredSize()` in `LabelHandler.Android.cs` to calculate the actual content width for wrapped, multi-line labels with non-Fill alignment. The implementation determines the widest rendered line using `layout.GetLineWidth(i)` and uses that value (plus padding) as the desired width. ### Issues Fixed Fixes #31782 Fixes #27614 Tested the behaviour in the following platforms - [x] Android - [x] Windows - [x] iOS - [x] Mac ### Output Video | Before Issue Fix | After Issue Fix | |------------------|-----------------| | <img width="350" alt="withoutfix" src="https://github.com/user-attachments/assets/0eca49ce-3cfd-4f42-85cb-98560385a056" /> | <img width="350" alt="withfix" src="https://github.com/user-attachments/assets/a1cba4c6-d422-46ab-96ae-db0dc6dc0dc9" /> |
…s misalignment (#33281) ### Root Cause On Android, for `Label` controls with `LineBreakMode="WordWrap"` and multiple lines, `GetDesiredSize()` returns the full constraint width instead of the actual rendered text width. This causes extra trailing space and incorrect horizontal alignment when `HorizontalLayoutAlignment` is set to `Start`, `Center`, or `End`. Single-line and Fill alignment cases are unaffected. ### Description of Change Updated `GetDesiredSize()` in `LabelHandler.Android.cs` to calculate the actual content width for wrapped, multi-line labels with non-Fill alignment. The implementation determines the widest rendered line using `layout.GetLineWidth(i)` and uses that value (plus padding) as the desired width. ### Issues Fixed Fixes #31782 Fixes #27614 Tested the behaviour in the following platforms - [x] Android - [x] Windows - [x] iOS - [x] Mac ### Output Video | Before Issue Fix | After Issue Fix | |------------------|-----------------| | <img width="350" alt="withoutfix" src="https://github.com/user-attachments/assets/0eca49ce-3cfd-4f42-85cb-98560385a056" /> | <img width="350" alt="withfix" src="https://github.com/user-attachments/assets/a1cba4c6-d422-46ab-96ae-db0dc6dc0dc9" /> |
…e - 1 (#33602) This PR addresses the UI test image failures that occurred in the inflight/candidate branch #33574 and includes updates to improve rendering and test stability across platforms. - Resaved snapshots for these test cases due to this PR fix #33281: CanvasShouldHonorBlur, VerifyScrollViewWithScrollToPositionCenterAndGridContent, VerifyScrollViewWithRTLAndGridContent, VerifyScrollViewWithScrollToPositionEndAndGridContent, VerifyScrollViewWithScrollToPositionMakeVisibleAndGridContent, VerifyScrollViewWithScrollToPositionStartAndGridContent, DownSizeImageAppearProperly, Issue30440ImageShouldClipCorrectly, IndicatorViewWithTemplatedIcon, VerifyCarouselLayoutOrientationChange, VerifyCarouselViewItemsSourceClearedDynamically, EmptyViewShouldDisplayWhenCollectionViewIsInsideVerticalStackLayout. - Issue18751Test - Added the failing condition for Android due to the image not loading on CI. Already restricted for other platforms; now it fails on Android as well. - Issue18751Test - Added the failing condition for Windows due to AutomationId not working on layout in Appium. - VariousSpanGesturePermutation - Increased the tap coordinates due to this PR fix #33281 changes. --------- Co-authored-by: sheiksyedm <sheiksyedm@syncfusion.com> # Conflicts: # src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue18751.cs
## What's Coming .NET MAUI inflight/candidate introduces significant improvements across all platforms with focus on quality, performance, and developer experience. This release includes 16 commits with various improvements, bug fixes, and enhancements. ## Checkbox - [Android] Implement material3 support for CheckBox by @HarishwaranVijayakumar in #33339 <details> <summary>🔧 Fixes</summary> - [Implement Material3 Support for CheckBox](#33338) </details> ## CollectionView - [Android] Fixed EmptyView doesn’t display when CollectionView is placed inside a VerticalStackLayout by @NanthiniMahalingam in #33134 <details> <summary>🔧 Fixes</summary> - [CollectionView does not show an EmptyView template with an empty collection](#32932) </details> ## Essentials - [Windows]Fix NullReferenceException in OpenReadAsync for FileResult created with full path by @devanathan-vaithiyanathan in #28238 <details> <summary>🔧 Fixes</summary> - [[Windows] FileResult(string fullPath) not initialized properly](#26858) </details> ## Image - Fix Glide IllegalArgumentException in MauiCustomTarget.clear() for destroyed activities by @jfversluis via @Copilot in #29780 <details> <summary>🔧 Fixes</summary> - [java.lang.IllegalArgumentException: You cannot start a load for a destroyed activity - glide](#29699) </details> ## Label - [Android] Fix for Label WordWrap width issue causing HorizontalOptions misalignment by @praveenkumarkarunanithi in #33281 <details> <summary>🔧 Fixes</summary> - [[Android] Unexpected Line Breaks in Android, Label with WordWrap Mode Due to Trailing Space.](#31782) - [Label not sized correctly on Android](#27614) </details> - Fix to Improve Flyout Accessibility by Adjusting UITableViewController Labels by @SuthiYuvaraj in #31619 <details> <summary>🔧 Fixes</summary> - [Navigation section present under hamburger are programmatically define as table :A11y_.NET maui_User can get all the insights of Dashboard_Devtools](#30894) </details> ## Mediapicker - [Regression][iOS] Fix MediaPicker PickPhotosAsync getting file name in contentType property by @devanathan-vaithiyanathan in #33390 <details> <summary>🔧 Fixes</summary> - [[iOS] MediaPicker PickPhotosAsync getting file name in contentType property](#33348) </details> ## Navigation - Fix handler not disconnected when removing non visible pages using RemovePage() by @Vignesh-SF3580 in #32289 <details> <summary>🔧 Fixes</summary> - [NavigationPage.Navigation.RemovePage() fails to disconnect handlers when removing pages, unlike ContentPage.Navigation.RemovePage()](#32239) </details> ## Picker - [Android] Fix Picker IsOpen not reset when picker is dismissed by @devanathan-vaithiyanathan in #33332 <details> <summary>🔧 Fixes</summary> - [[Android] Picker IsOpen not reset when picker is dismissed](#33331) </details> ## Shell - [iOS & Catalyst ] Fixed IsEnabled property should work on Tabs by @SubhikshaSf4851 in #33369 <details> <summary>🔧 Fixes</summary> - [[Catalyst] TabBarBackgroundColor, TabBarUnselectedColor, and IsEnabled Not Working as Expected in Shell](#33158) </details> - [iOS,Windows] Fix navigation bar colors not resetting when switching ShellContent by @Vignesh-SF3580 in #33228 <details> <summary>🔧 Fixes</summary> - [[iOS, Windows] Shell Navigation bar colors are not updated correctly when switching ShellContent](#33227) </details> - [iOS] Fixed Shell navigation on search handler suggestion selection by @SubhikshaSf4851 in #33406 <details> <summary>🔧 Fixes</summary> - [[iOS] Clicking on search suggestions fails to navigate to detail page correctly](#33356) </details> ## Templates - Fix VoiceOver doesnot announces the State of the ComboBox by @SuthiYuvaraj in #32286 ## Xaml - [XSG][BindingSourceGen] Add support for CommunityToolkit.Mvvm ObservablePropertyAttribute by @simonrozsival via @Copilot in #33028 <details> <summary>🔧 Fixes</summary> - [[XSG] Add heuristic to support bindable properties generated by other source generators](#32597) </details> <details> <summary>📦 Other (2)</summary> - [XSG] Improve diagnostic reporting during binding compilation by @simonrozsival via @Copilot in #32905 - [Testing] Fixed Test case failure in PR 33574 - [01/19/2026] Candidate - 1 by @TamilarasanSF4853 in #33602 </details> **Full Changelog**: main...inflight/candidate
Root Cause
On Android, for
Labelcontrols withLineBreakMode="WordWrap"and multiple lines,GetDesiredSize()returns the full constraint width instead of the actual rendered text width. This causes extra trailing space and incorrect horizontal alignment whenHorizontalLayoutAlignmentis set toStart,Center, orEnd. Single-line and Fill alignment cases are unaffected.Description of Change
Updated
GetDesiredSize()inLabelHandler.Android.csto calculate the actual content width for wrapped, multi-line labels with non-Fill alignment. The implementation determines the widest rendered line usinglayout.GetLineWidth(i)and uses that value (plus padding) as the desired width.Issues Fixed
Fixes #31782
Fixes #27614
Tested the behaviour in the following platforms
Output Video