在UI设计中,页面布局非常重要,良好的布局不仅可以有效的利用空间,还能提升交互体验,以达到事半功倍的效果。所以对于Avalonia UI初学者来说,布局控件的了解与学习也非常的重要,今天以一些小例子,简述Avalonia UI框架中布局控件的使用,仅供学习分享使用,如有不足之处,还请指正。
布局系统
布局系统描述了容器对元素集合的成员进行测量和排列的过程。布局是一个密集的过程,Children集合越大,进行的计算越多。每次对子控件更改位置,都可能触发布局系统的新计算,所以不必要的调用可能会导致应用程序的性能下降。布局系统对子元素集合的每个成员完成两个传递:测量传递和排列传递。每个Panel提供自己的MeasureOverride和ArrangeOverride方法,以实现其特定的布局行为。常用的布局面板有以下几种:
- Panel 将所有子元素布局以填充 Panel 的边界
- Canvas 定义一个区域,您可以在其中使用相对于 Canvas 区域的坐标明确定位子元素
- DockPanel 定义一个区域,其中您可以相对于彼此将子元素水平或垂直地排列
- Grid 定义由列和行组成的灵活网格区域
- RelativePanel 将子元素相对于其他元素或面板本身排列
- StackPanel 将子元素排列成一行,可以是水平或垂直方向
- WrapPanel 将子元素按从左到右的顺序放置,当内容到达容器框的边缘时,将在下一行中断。后续排序顺序是顺序从上到下或从右到左,这取决于 Orientation 属性的值。
注意:在 WPF 中,Panel 是一个抽象类,通常使用没有行/列的 Grid 来布局多个控件以填充可用空间。在 Avalonia 中,Panel 是一个可用的控件,其布局行为与没有行/列的 Grid 相同,但运行时占用更少的资源。
对齐与边距
在Avalonia UI中,提供了四个常用属性(HorizontalAlignment、Margin、Padding和VerticalAlignment),用于精确定位子元素。它们是控制元素在应用程序中位置的基础。主要如下:
- HorizontalAlignment属性声明了要应用于子元素的水平对齐特性,常用的属性值有四个:
-
- Left 子元素与父元素分配的布局空间的左侧对齐。
- Center 子元素与父元素分配的布局空间的中心对齐。
- Right 子元素与父元素分配的布局空间的右侧对齐。
- Stretch (默认) 子元素被拉伸以填充父元素分配的布局空间。明确的Width和Height值优先级更高。
- VerticalAlignment属性声明了要应用于子元素的垂直对齐特性,常用的属性值有四个:
-
- Top 子元素与父元素分配的布局空间的顶部对齐。
- Center 子元素与父元素分配的布局空间的中心对齐。
- Bottom 子元素与父元素分配的布局空间的底部对齐。
- Stretch(默认) 子元素被拉伸以填充父元素分配的布局空间。明确的Width和Height值优先级更高。
- Margin属性描述了元素与其子元素或同级元素之间的距离。Margin值可以是相同的,通过使用像Margin="20"的语法,元素将使用相同的20个设备独立像素的Margin。Margin值也可以是四个不同的值,每个值描述了应用于左、上、右和下的不同边距(按顺序),如Margin="0,10,5,25"。在许多情况下,统一的边距是不合适的。在这些情况下,可以应用非统一间距。正确使用Margin属性可以非常精确地控制元素的渲染位置及其相邻元素和子元素的渲染位置。
- Padding属性描述了元素与其内容之间的距离,在大多数方面与Margin相似。Padding属性仅暴露在少数类上,主要是为了方便。Border、TemplatedControl、和TextBlock是暴露了Padding属性的类的范例。Padding属性通过指定的Thickness值扩大了子元素的有效尺寸。
注意:HorizontalAlignment和VerticalAlignment属性描述了一个子元素应该如何在父元素分配的布局空间内定位。元素上明确设置的Height和Width属性的优先级高于Stretch属性。如果明确设置了Height和Width,再将HorizontalAlignment设置为Stretch,这样会忽略Stretch属性。
对齐与边距应用示例如下所示:
<Window xmlns="https://github.com/avaloniaui"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"x:Class="AvaloniaApplication2.MainWindow"Title="AvaloniaApplication2"><Border Background="LightBlue"BorderBrush="Black"BorderThickness="2"Padding="15"><StackPanel Background="White"HorizontalAlignment="Center"VerticalAlignment="Top"><TextBlock Margin="5,0"FontSize="18"HorizontalAlignment="Center">Alignment, Margin and Padding Sample</TextBlock><Button HorizontalAlignment="Left" Margin="20">Button 1</Button><Button HorizontalAlignment="Right" Margin="10">Button 2</Button><Button HorizontalAlignment="Stretch">Button 3</Button></StackPanel></Border>
</Window>
示例效果如下图所示:
Panel控件
Panel是Avalonia提供的布局支持的元素的基类,默认情况下,Panel和没有行列个Grid相同,可以采用对齐或边距(HorizontalAlignment、Margin、Padding和VerticalAlignment)进行定位,但相对于Grid等复杂布局容器,Panel运行时占用的资源较少。示例如下所示:
<Panel><TextBlock Background="LightGray" Text="Left" Width="100" Height="20" HorizontalAlignment="Left" /><TextBlock Background="LightGray" Text="Right" Width="100" Height="20" HorizontalAlignment="Right"/><TextBlock Background="LightGray" Text="Top" Width="100" Height="20" VerticalAlignment="Top" /><TextBlock Background="LightGray" Text="Bottom" Width="100" Height="20" VerticalAlignment="Bottom"/>
</Panel>
示例效果如下所示:
如果将Panel中的元素换成Button,将会有不同的效果,示例代码如下:
<Panel><Button Background="LightGray" Content="Left" Width="100" Height="30" HorizontalAlignment="Left" /><Button Background="LightGray" Content="Right" Width="100" Height="30" HorizontalAlignment="Right"/><Button Background="LightGray" Content="Top" Width="100" Height="30" VerticalAlignment="Top" /><Button Background="LightGray" Content="Bottom" Width="100" Height="30" VerticalAlignment="Bottom"/>
</Panel>
示例效果如下所示:
思考一下:都是Panel布局,将TextBlock换成Button,为什么效果会不同呢?
Canvas
Canvas(画布容器)允许按绝对坐标(x,y)定位子元素。子元素可以绘制在不同的位置,也可以绘制在同一个位置,如果两个控件在Canvas容器中的位置相同,则依据出现的顺序进行显示。Canvas提供了最灵活的布局支持,Height和Width属性定义画布的大小,其中的元素被赋予相对于Canvas区域的绝对坐标。通过四个附加属性 (Canvas.Left、Canvas.Top、Canvas.Right 和 Canvas.Bottom )来精确地控制对象在 Canvas 内的位置,从而使开发人员可以精确定位和排列元素在屏幕上的位置。
Canvas布局示例代码,如下所示:
<Canvas><Button Background="Red" Content="Red" Width="100" Height="100" /><Button Background="Green" Content="Green" Width="100" Height="100" Canvas.Left="100" Canvas.Top="100"/><Button Background="Blue" Content="Blue" Width="100" Height="100" Canvas.Left="200" Canvas.Top="200" /><Button Background="Yellow" Content="Yellow" Width="100" Height="100" Canvas.Right="100" Canvas.Bottom="100"/>
</Canvas>
示例效果如下所示:
在Canvas中,如果没有明确设置子元素在容器中的位置,则默认位于左上角,即Left,Top为0。另外,Canvas的默认行为是允许子元素绘制在父Canvas界限之外,如果不希望出现这种情况,可以设置ClipToBounds属性为True,会自动裁剪界限之外的内容。
在上述示例中,Canvas.Left,Canvas.Top,Canvas.Bottom,Canvas.Right,作为Canvas的附加属性,应用在Canvas容器中的子元素上,则前缀“Canvas.”不可以省略。如果省略,且子元素本身也有Left,Top等属性,则会造成编译器无法准确识别。
DockPanel
DockPanel(停靠面板)使用附加属性DockPanel.Dock来设置子内容元素在容器边缘的位置。当 DockPanel.Dock 设置为 Top 或 Bottom 时,它会将子元素放置在彼此的上方或下方。当 DockPanel.Dock 设置为 Left 或 Right 时,它会将子元素放置在彼此的左侧或右侧。LastChildFill 属性决定了最后一个作为 DockPanel 子元素添加的元素的位置。
如果没有指定DockPanel的Width和Height属性,它的大小根据内容来确定。大小可以根据其子元素的大小进行增长或减小。然而,当指定了这些属性并且没有足够的空间来容纳下一个指定的子元素时,DockPanel 不会显示该子元素或随后的子元素,并且不会对随后的子元素进行测量。
DockPanel示例源码,如下所示:
<DockPanel LastChildFill="True"><Border Height="25" Background="SkyBlue" BorderBrush="Black" BorderThickness="1" DockPanel.Dock="Top"><TextBlock Foreground="Black">Dock = "Top"</TextBlock></Border><Border Height="25" Background="SkyBlue" BorderBrush="Black" BorderThickness="1" DockPanel.Dock="Top"><TextBlock Foreground="Black">Dock = "Top"</TextBlock></Border><Border Height="25" Background="LemonChiffon" BorderBrush="Black" BorderThickness="1" DockPanel.Dock="Bottom"><TextBlock Foreground="Black">Dock = "Bottom"</TextBlock></Border><Border Width="200" Background="PaleGreen" BorderBrush="Black" BorderThickness="1" DockPanel.Dock="Left"><TextBlock Foreground="Black">Dock = "Left"</TextBlock></Border><Border Background="White" BorderBrush="Black" BorderThickness="1"><TextBlock Foreground="Black">This content will "Fill" the remaining space</TextBlock></Border>
</DockPanel>
示例效果如下所示:
注意,默认情况下,DockPanel 元素的最后一个子元素将“填充”剩余的未分配空间。如果不希望出现这种情况,可以将 LastChildFill 属性设置为 false。
Grid
Grid容器结合了绝对定位和表格数据控件的功能。Grid允许定义灵活的行和列分组,并且可以在多个Grid之间共享大小信息。在Grid控件中,通过ColumnDefinitions="Auto,*,*,*,*" RowDefinitions="Auto,Auto,*,Auto"实现定义行列,多行多列用逗号隔开,子元素通过Grid.Row,Grid.Column附加属性进行定位。在XAML中,行列的大小可以通过几种方式进行设置:
- 通过Star(*)进行设置,如Width="*"或Width="2*",则该行列会按比例获得剩余可用空间。
- 通过Auto进行设置,如Width="Auto",则行或列会根据内容的大小进行分配空间。
- 绝对大小,默认单位为像素值,如Width="100",表示宽度为100px。
如下Grid代码定义了4行5列,具体如下所示:
<Grid Background="Gainsboro" HorizontalAlignment="Left" VerticalAlignment="Top" Width="425" Height="165"ColumnDefinitions="Auto,*,*,*,*" RowDefinitions="Auto,Auto,*,Auto"><TextBlock Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="4" Text="选择打开的文件." TextWrapping="Wrap" /><TextBlock Grid.Row="1" Grid.Column="0" Text="Open:" /><TextBox Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="5" /><Button Grid.Row="3" Grid.Column="2" Content="OK" Margin="10,0,10,15" /><Button Grid.Row="3" Grid.Column="3" Content="Cancel" Margin="10,0,10,15" /><Button Grid.Row="3" Grid.Column="4" Content="Browse ..." Margin="10,0,10,15" />
</Grid>
示例效果如下所示:
StackPanel
StackPanel(栈面板)允许在指定的方向上堆叠元素,默认的堆叠方法是垂直的,如果想要水平方向排列,可以通过Orientation属性进行设置,来控制子元素内容的排列方向。
StackPanel示例代码如下所示:
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Top" Spacing="25"><Button Content="Button 1" /><Button Content="Button 2" /><Button Content="Button 3" />
</StackPanel>
示例效果如下所示:
WrapPanel
WrapPanel用于按照从指定的顺序定位子元素,并在其父容器的边缘到达时将内容换行。WrapPanel和StackPanel一样,可以通过Orientation属性指定子元素排列的方向,默认为水平排列。
WrapPanel示例代码如下所示:
<Border HorizontalAlignment="Left" VerticalAlignment="Top" BorderBrush="Black" BorderThickness="2"><WrapPanel Background="LightBlue" Width="200" Height="100"><Button Width="200">Button 1</Button><Button>Button 2</Button><Button>Button 3</Button><Button>Button 4</Button></WrapPanel>
</Border>
示例效果如下所示:
UniformGrid
UniformGrid提供统一的网格布局面板,此网格布局面板中的所有单元格大小相同,与Grid不同,此网格布局面板不支持显示设置行,和列,也没有Grid.Row,Grid.Column等附加属性,它主要用于显示大小相同的子元素控件。UniformGrid常用属性如下:
- 行和列:UniformGrid 使用 Rows 和 Columns 属性来确定其子元素的布局。如果只设置其中一个属性,UniformGrid 将自动计算另一个属性,以创建适合子元素总数的网格。如果两个属性都不设置,UniformGrid 默认为 1 行 1 列的网格。
- FirstColumn:FirstColumn 属性允许您在网格的第一行中留下一定数量的空单元格。
UniformGrid示例如下所示:
<UniformGrid Rows="3" Columns="4"><Rectangle Width="50" Height="50" Fill="#330000"/><Rectangle Width="50" Height="50" Fill="#660000"/><Rectangle Width="50" Height="50" Fill="#990000"/><Rectangle Width="50" Height="50" Fill="#CC0000"/><Rectangle Width="50" Height="50" Fill="#FF0000"/><Rectangle Width="50" Height="50" Fill="#FF3300"/><Rectangle Width="50" Height="50" Fill="#FF6600"/><Rectangle Width="50" Height="50" Fill="#FF9900"/><Rectangle Width="50" Height="50" Fill="#FFCC00"/><Rectangle Width="50" Height="50" Fill="#FFFF00"/><Rectangle Width="50" Height="50" Fill="#FFFF33"/><Rectangle Width="50" Height="50" Fill="#FFFF66"/>
</UniformGrid>
在上面的示例中,每个 Rectangle 被自动分配到网格中的一个单元格,按照它们被添加的顺序进行分配。示例效果如下所示:
布局嵌套
在实际应用中,不同的Panel可以相互嵌套,以创建复杂的布局。虽然理论上可以无限嵌套面板元素,但嵌套越多,性能越低。所以熟练掌握各种布局容器的特点,才能根据业务需求,采用合理的布局面板,这样不仅能提升性能,还会起到事半功倍的效果。
参考文章
本文主要参考官方文档:
https://docs.avaloniaui.net/zh-Hans/docs/basics/user-interface/building-layouts/
以上就是《Avalonia系列文章之布局简介》的全部内容,希望可以抛砖引玉,一起学习,共同进步。