Reputation: 336
Did Xamarin.Forms already contain a control/layout which orders it's content depending on the screen orientation or size?
What I want: Two stacklayouts which are ordered horizontal, if the screen got enough space. When the Screen changes, so that the screen got not enough horizontal-space, the two stacklayouts should be ordered vertical.
I don't want to do it in code behind.
I search for an solution which only uses the xaml.
Upvotes: 7
Views: 12160
Reputation: 402
Hope I'm not too late. Using Xamarin.Essentials
is an easier solution for me.
Model.cs
using Xamarin.Essentials;
.
.
.
private DisplayOrientation orientation = DeviceDisplay.MainDisplayInfo.Orientation;
public DisplayOrientation Orientation
{
get => orientation;
set => orientation = value;
}
View.xaml.cs
using Xamarin.Essentials;
.
.
.
protected override void OnSizeAllocated(double width, double height)
{
base.OnSizeAllocated(width, height);
model.Orientation = DeviceDisplay.MainDisplayInfo.Orientation;
}
View.xaml
<ContentPage xmlns:ess="clr-namespace:Xamarin.Essentials;assembly=Xamarin.Essentials">
<ContentPage.BindingContext>
<!--Model here-->
</ContentPage.BindingContext>
<DataTrigger TargetType="..."
Binding="{Binding Orientation}"
Value="{x:Static ess:DisplayOrientation.Portrait}">
<Setter Property="..." Value="..." />
</DataTrigger>
<DataTrigger TargetType="..."
Binding="{Binding Orientation}"
Value="{x:Static ess:DisplayOrientation.Landscape}">
<Setter Property="..." Value="..." />
</DataTrigger>
</ContentPage>
Don't forget the INotifyPropertyChanged
implications.
Upvotes: 0
Reputation: 31
Option #1 'Orientation States' - Portrait & Landscape. Use 'OrientationStates' inside visual state manager like so:
Inside .XAML File
<Grid x:Name="myGrid"
Margin="10,30,10,10">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="OrientationStates">
<!-- Row & Column Definitions of Grid - [2 Rows, 1 Col = Portrait] & [1 Row, 2 Cols = Landscape] -->
<VisualState x:Name="Portrait">
<VisualState.Setters>
<Setter Property="Grid.RowDefinitions"
Value="*,*" />
<Setter Property="Grid.ColumnDefinitions"
Value="*" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Landscape">
<VisualState.Setters>
<Setter Property="Grid.RowDefinitions"
Value="*" />
<Setter Property="Grid.ColumnDefinitions"
Value="*,*" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<!-- Change position of stack layouts in grid, to match above stated orientation requirement -->
<StackLayout x:Name="firstStackLayout">
<Entry Placeholder="Enter first words" />
<Button Text="Nothing Happens" />
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState x:Name="Portrait">
<VisualState.Setters>
<Setter Property="Grid.Row" Value="0" />
<Setter Property="Grid.Column" Value="0" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Landscape">
<VisualState.Setters>
<Setter Property="Grid.Row" Value="0" />
<Setter Property="Grid.Column" Value="0" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</StackLayout>
<StackLayout x:Name="secondStackLayout">
<Entry Placeholder="Enter last words" />
<Button Text="Still Nothing Happens" />
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState x:Name="Portrait">
<VisualState.Setters>
<Setter Property="Grid.Row" Value="1" />
<Setter Property="Grid.Column" Value="0" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Landscape">
<VisualState.Setters>
<Setter Property="Grid.Row" Value="0" />
<Setter Property="Grid.Column" Value="1" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</StackLayout>
</Grid>
With an added 'OnSizeAllocated()' function in CodeBehind like so:
Inside .XAML.CS File
public partial class RegisterPage : ContentPage
{
public RegisterPage()
{
InitializeComponent();
}
protected override void OnSizeAllocated(double width, double height)
{
base.OnSizeAllocated(width, height);
var state = (width > height) ? "Landscape" : "Portrait";
// Call the 'Portrait' & 'Landscape' States in .XAML File
VisualStateManager.GoToState(myGrid, state);
VisualStateManager.GoToState(firstStackLayout, state);
VisualStateManager.GoToState(secondStackLayout, state);
}
}
[OR]
Option #2 The magic bullet - Your ONLY .XAML File solution to that problem.
'Orientation Triggers' - Portrait & Landscape.
Use 'OrientationStateTriggers' inside 'State Triggers' in Visual State like so:
Inside .XAML File
<Grid Margin="10,30,10,10">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<!-- Row & Column Definitions of Grid - [2 Rows, 1 Col = Portrait] & [1 Row, 2 Cols = Landscape] -->
<VisualState x:Name="gridPortrait">
<VisualState.StateTriggers>
<OrientationStateTrigger Orientation="Portrait" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Property="Grid.RowDefinitions"
Value="*,*" />
<Setter Property="Grid.ColumnDefinitions" Value="*" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="gridLandscape">
<VisualState.StateTriggers>
<OrientationStateTrigger Orientation="Landscape" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Property="Grid.RowDefinitions"
Value="*" />
<Setter Property="Grid.ColumnDefinitions"
Value="*,*" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<!-- Change position of stack layouts in grid, to match above stated orientation requirement -->
<StackLayout>
<Entry Placeholder="Enter first words" />
<Button Text="Nothing Happens" />
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState x:Name="myPortraitState">
<VisualState.StateTriggers>
<OrientationStateTrigger Orientation="Portrait" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Property="Grid.Row" Value="0" />
<Setter Property="Grid.Column" Value="0" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="myLandscapeState">
<VisualState.StateTriggers>
<OrientationStateTrigger Orientation="Landscape" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Property="Grid.Row" Value="0" />
<Setter Property="Grid.Column" Value="0" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</StackLayout>
<StackLayout>
<Entry Placeholder="Enter last words" />
<Button Text="Still Nothing Happens" />
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState x:Name="myPortraitState">
<VisualState.StateTriggers>
<OrientationStateTrigger Orientation="Portrait" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Property="Grid.Row" Value="1" />
<Setter Property="Grid.Column" Value="0" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="myLandscapeState">
<VisualState.StateTriggers>
<OrientationStateTrigger Orientation="Landscape" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Property="Grid.Row" Value="0" />
<Setter Property="Grid.Column" Value="1" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</StackLayout>
</Grid>
Upvotes: 1
Reputation: 5313
I guess you can't achieve this using ONLY XAML. Certainly, you will need some c# code. The XAML on Xamarin.Forms is designed to be responsive, and you often define the view properties in a relative mode (instead of absolute).
You can see an example of the behavior you want at this topic where we can see a screen changing the orientation of the StackLayout according to the device orientation (you can use it as your guideline to write your own layout component)
That is accomplished with the following XAML:
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ResponsiveLayout.StackLayoutPageXaml"
Title="Stack Photo Editor - XAML">
<ContentPage.Content>
<StackLayout Spacing="10" Padding="5" Orientation="Vertical"
x:Name="outerStack"> <!-- can change orientation to make responsive -->
<ScrollView>
<StackLayout Spacing="5" HorizontalOptions="FillAndExpand"
WidthRequest="1000">
<StackLayout Orientation="Horizontal">
<Label Text="Name: " WidthRequest="75"
HorizontalOptions="Start" />
<Entry Text="deer.jpg"
HorizontalOptions="FillAndExpand" />
</StackLayout>
<StackLayout Orientation="Horizontal">
<Label Text="Date: " WidthRequest="75"
HorizontalOptions="Start" />
<Entry Text="07/05/2015"
HorizontalOptions="FillAndExpand" />
</StackLayout>
<StackLayout Orientation="Horizontal">
<Label Text="Tags:" WidthRequest="75"
HorizontalOptions="Start" />
<Entry Text="deer, tiger"
HorizontalOptions="FillAndExpand" />
</StackLayout>
<StackLayout Orientation="Horizontal">
<Button Text="Save" HorizontalOptions="FillAndExpand" />
</StackLayout>
</StackLayout>
</ScrollView>
<Image Source="deer.jpg" />
</StackLayout>
</ContentPage.Content>
</ContentPage>
Some C# is used to change the orientation of outerStack based on the orientation of the device:
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) {
outerStack.Orientation = StackOrientation.Horizontal;
} else {
outerStack.Orientation = StackOrientation.Vertical;
}
}
}
I hope it help you.
Upvotes: 6
Reputation: 9722
As far as I know this is not possible. I did basically exactly what you want 'manually'. It's not too hard, though. First of all, you'll have to wrap your stack layouts in another StackLayout
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="App.Views.TestPage">
<ContentPage.Content>
<StackLayout x:Name="OuterStackLayout">
<StackLayout>
<!-- Inner stack layout 1 -->
</StackLayout>
<StackLayout>
<!-- Inner stack layout 2 -->
</StackLayout>
</StackLayout>
</ContentPage.Content>
</ContentPage>
Next, you'll have to override OnSizeAllocated
and set the outer OuterStackLayout.Orientation
according to your screen orientation
protected override void OnSizeAllocated(double width, double height)
{
base.OnSizeAllocated(width, height);
if (SizeHasChanged(width, height)) // elided, just compare width, height with the stored values
{
StoreSize(width, height); // store in private members
if (IsLandscape)
{
this.OuterStackLayout.Orientation = StackOrientation.Horizontal;
}
else
{
this.OuterStackLayout.Orientation = StackOrientation.Vertical;
}
}
}
public bool IsLandscape => _width > _height;
Maybe you'll have to fiddle around with the horizontal options of the inner StackLayout
s a bit - or other layout parameters, but basically this should do.
Upvotes: 2