Xamarin.Forms根据屏幕方向或大小动态布局

7

Xamarin.Forms中是否已经包含一个控件/布局可以根据屏幕方向或大小对其内容进行排序?

我的要求: 如果屏幕有足够的空间,应该有两个水平排列的stacklayouts。 当屏幕尺寸变化时,如果屏幕没有足够的水平空间,则应该将这两个stacklayouts垂直排列。

我不想在代码后台完成它。

我正在寻找一个只使用xaml的解决方案。


我知道UWP有这个功能,但我也很好奇Xamarin目前是否支持它。 - Tony
“仅使用XAML的解决方案”:没有布局可以做到这一点,当然你可以写一个或在代码后台处理,但你已排除了这种可能性。 - SushiHangover
@Tony,你能给我一份UWP的文档或参考资料吗?也许我可以进行适应。 - Neuxz
1
UWP自适应UI XAML https://msdn.microsoft.com/zh-cn/magazine/mt590974.aspx - Tony
4个回答

6
我猜你不能仅使用XAML实现这个功能。当然,你需要一些C#代码。 Xamarin.Forms上的XAML被设计为响应式的,通常以相对模式(而不是绝对模式)定义视图属性。
你可以在这个主题中看到一个你想要的行为示例,在那里我们可以看到一个屏幕根据设备方向改变StackLayout的方向(你可以将其用作编写自己的布局组件的指南)。 竖屏模式下的屏幕: 竖屏模式下的屏幕 横屏模式下的屏幕: 横屏模式下的屏幕

以下是实现此操作的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>

一些C#代码用于根据设备的方向改变outerStack的方向:
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;
        }
    }
}

我希望它能帮到你。


1
非常感谢!很遗憾没有仅限于XAML的变体。 - Neuxz
我同意这个观点。但请记住,XAML是一种标记语言,你无法在上面完成所有的编程任务,很遗憾。我建议你观看Charles Pretzold在2016年Xamarin Evolve大会上的这次演讲,对我帮助很大!这是一个关于XAML的惊人方法。在观看后,我成为它的粉丝。 - Diego Rafael Souza

2
据我所知,这是不可能的。我基本上手动完成了您想要的操作。虽然不是太难。首先,您需要将堆栈布局包装在另一个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>

接下来,您需要覆盖OnSizeAllocated并根据屏幕方向设置外部OuterStackLayout.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;

也许您需要稍微调整内部StackLayout的水平选项或其他布局参数,但基本上这样做就可以了。

1

选项#1 '方向状态' - 竖屏和横屏。在可视状态管理器中使用'OrientationStates',如下所示:

在.XAML文件中

<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>

在 CodeBehind 中添加 'OnSizeAllocated()' 函数,如下所示:

在 .XAML.CS 文件中

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);
    }
}

[或者]

选项 #2 魔法子弹 - 您的唯一 .XAML 文件解决方案。

'方向触发器' - 竖屏和横屏。

在可视状态中使用 '状态触发器' 中的 '方向状态触发器',如下所示:

在 .XAML 文件内部

<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>

0
希望我来得不算太晚。对我来说,使用 Xamarin.Essentials 是一个更简单的解决方案。

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>

不要忘记 INotifyPropertyChanged 的影响。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接