简体   繁体   中英

Xamarin.Forms dynamic layout depending on screen orientation or size

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.

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)

The screen on portrait mode: 纵向模式下的屏幕

The screen on landscape mode: 横向模式下的屏幕

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.

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 sa bit - or other layout parameters, but basically this should do.

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>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM