maui
maui copied to clipboard
[Android] updating StackLayout.Orientation does not trigger a re-layout of the children
Description
The code I wrote to show two squares in a responsive layout does not work properly on Android Systems because the width and height values passed to the OnSizeAllocated method are incorrect.
Steps to Reproduce
- Create a new MAUI project.
- Add following code to MainPage.xaml to showing two square.
<StackLayout x:Name="parent"
Orientation="Horizontal">
<BoxView Color="Blue"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand"/>
<BoxView Color="Green"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand"/>
</StackLayout>
- Add following code to the MainPage class in the MainPage.xaml.cs file to getting responsive layout.
double width, height;
protected override void OnSizeAllocated(double width, double height)
{
base.OnSizeAllocated(width, height);
if (width != this.width || height != this.height)
{
this.width = width;
this.height = height;
if (width > height)
{
parent.Orientation = StackOrientation.Horizontal;
}
else
{
parent.Orientation = StackOrientation.Vertical;
}
}
}
Version with bug
Release Candidate 3 (current)
Last version that worked well
Unknown/Other
Affected platforms
Android
Affected platform versions
Android 12
Did you find any workaround?
No response
Relevant log output
No response
Verified this issue with Visual Studio Enterprise 17.3.0 Preview 1.0 [32427.455.main]. Repro on Android 12. Sample Project: MauiApp9.zip
I am experiencing the same problem but with some differences.
In my case the code I use to change orientation to the stacklayout is this:
DisplayOrientation _displayOrientation = DisplayOrientation.Unknown;
protected override void OnSizeAllocated(double width, double height)
{
base.OnSizeAllocated(width, height);
if (DeviceDisplay.MainDisplayInfo.Orientation != _displayOrientation)
{
_displayOrientation = DeviceDisplay.MainDisplayInfo.Orientation;
if (_displayOrientation == DisplayOrientation.Portrait)
mainStackLayout.Orientation = StackOrientation.Vertical;
else if (_displayOrientation == DisplayOrientation.Landscape)
mainStackLayout.Orientation = StackOrientation.Horizontal;
//this.ForceLayout();
}
}
Even using this, the orientation of the stacklayout is wrong. It is as if once the Orientation
variable is set the actual change is not propagated and the layout is not updated. By rotating the screen again, the change happens, but it turns out to be the previous one. So if we try to reverse it:
if (_displayOrientation == DisplayOrientation.Portrait)
mainStackLayout.Orientation = StackOrientation.Horizontal;
else if (_displayOrientation == DisplayOrientation.Landscape)
mainStackLayout.Orientation = StackOrientation.Vertical;
the result turns out to be partially correct.
By debugging the function, the height and width values look correct to me. The problem does not occur on windows or on ios.
We've moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. To learn more about our issue management process and to have better expectation regarding different types of issues you can read our Triage Process.
This issue is still present (current framework, latest VS 2022, etc.)
The issue is present for me with VS 2022 version 17.5.0, following the exact description by @FedericoNembrini above (https://github.com/dotnet/maui/issues/7134#issuecomment-1174873953). The values of width and height seem correct, but setting the orientation yields the opposite orientation, on Android only.
I'm trying to play with the source code a bit, from what I can see when changing the orientation, the method OrientationChanged
is called
static void OrientationChanged(BindableObject bindable, object oldValue, object newValue)
{
var layout = (StackLayout)bindable;
layout.InvalidateMeasure();
}
This however, at least for android, does not re-arrange the children of the element.
At the moment, I have not yet been able to understand what the InvalidateMeasure function (and its sub-functions) actually does.
By manually calling up the arrangement of it's child, the stacklyout is displayed correctly. I see that there is a predisposition for a function called InvalidateArrange
, but it is not currently used.
PS: In my opinion, this issue should change its name to reflect the problem.
Verified this issue with Visual Studio Enterprise 17.6.0 Preview 5.0. Repro on Android emulator with Sample Project:
MauiApp9.zip
I checked the code and I agree with @FedericoNembrini - this is not an OnSizeAllocated
issue because the values in there are correct - but the subsequint layout is not being reflected. I also tried calling InvalidateMeasure
after updating the orientation and this does not appear to do anything.
One workaround was to use the dispatcher to request a re-layout outside the current layout pass:
Dispatcher.Dispatch(() => ((IView)parent).InvalidateMeasure());
I just added this to the bottom of the OnSizeAllocated
method and it seems to work.
In the issue linked above (#9227), @mattleibow suggested wrapping the StackLayout
in a Grid
(which, AIUI, provides something to respond to the layout invalidation and actually process the remeasure).
That issue is about containing the control in a ScrollView
, but as this issue shows, that is not the only problem scenario. Nevertheless, I found the same fix worked when the Stacklayout
is the root layout in a ContentPage
and was similarly reacting inconsistently, wrongly or not at all to Orientation
changes (on Android - not tested other platforms).
TL;DR: A workaround in some (perhaps all) circumstances is to wrap the StackLayout
in a Grid
.
Thanks for the workarounds. I am able to repro with .NET 7 on Pixel 4 device running Android 13. Keeping controls inside a StackLayout
and changing the layout orientation when a mobile device is tilted (portrait -> landscape, & vice versa) seems like a common design choice for .NET MAUI.
<Style x:Key="DynamicStackLayout" TargetType="StackLayout">
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup>
<VisualState x:Name="Horizontal">
<VisualState.StateTriggers>
<OrientationStateTrigger Orientation="Landscape" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Property="Orientation" Value="Horizontal" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Vertical">
<VisualState.StateTriggers>
<OrientationStateTrigger Orientation="Portrait" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Property="Orientation" Value="Vertical" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
Designing UI that accounts for device orientation has been challenging enough with .NET MAUI. I can't justify the time adding a workaround to my app with hundreds of ContentPage
s, especially when the issue is specific to one platform. Hoping this issue can get resolved sooner than later, it has been a real time sink.
A similar issue is still present in MAUI .Net8, my issue has been with a custom grid layout component that reacts to the aspect ratio of the available space for the control. The control fails to work as expected on android but works on other platforms, my workaround is to nest the control inside a content view and uses Dispatcher.Dispatch(() => ((IView)Parent).InvalidateMeasure());
as @mattleibow suggests.
this issue keeps showing up and causing problems ... has anyone found a work around that does not involve nesting views, I need to control shell behaviour based on screen size and cant call Dispatcher.Dispatch(()
=> ((IView)Parent).InvalidateMeasure());
to resolve the problem
this issue keeps showing up and causing problems ... has anyone found a work around that does not involve nesting views, I need to control shell behaviour based on screen size and cant call
Dispatcher.Dispatch(()
=>((IView)Parent).InvalidateMeasure());
to resolve the problem
My issue is resolved on Maui 8.0.7 Nuget. No Dispatcher with InvalidateMeasure is needed now. Entry tests are showing, that it works flawlessly
this issue keeps showing up and causing problems ... has anyone found a work around that does not involve nesting views, I need to control shell behaviour based on screen size and cant call
Dispatcher.Dispatch(()
=>((IView)Parent).InvalidateMeasure());
to resolve the problemMy issue is resolved on Maui 8.0.7 Nuget. No Dispatcher with InvalidateMeasure is needed now. Entry tests are showing, that it works flawlessly
Wish I could replicate your success, android behavior is still buggy and inconsistent for me on 8.0.7