Grid是一种强大的布局机制,可将其子项组织为单元格的行和列。起初,Grid似乎与HTML表类似,但有一个非常重要的区别:HTML表是为演示目的而设计的,而Grid仅用于布局。例如,网格中没有标题的概念,并且没有内置功能来在单元格周围绘制框或使用分隔线分隔行和列。 Grid的优势在于使用三个高度和宽度设置选项指定单元格尺寸。
正如您所见,StackLayout非常适合一维儿童收藏。尽管可以在StackLayout中嵌套StackLayout以容纳第二维并模仿表,但结果通常会出现对齐问题。然而,Grid专为二维儿童阵列而设计。正如您将在本章末尾看到的那样,Grid对于管理适应纵向和横向模式的布局也非常有用。
基本网格
可以使用代码或XAML中的子节点定义和填充网格,但XAML方法更容易和更清晰,因此到目前为止更常见。
XAML中的网格
在XAML中定义时,Grid几乎总是具有固定数量的行和列。 Grid定义通常以两个重要属性开头,名为RowDefinitions(RowDefinition对象的集合)和ColumnDefinitions(ColumnDefinition对象的集合)。 这些集合包含Grid中每行的一个RowDefinition和每列的一个ColumnDefinition,它们定义Grid的行和列特征。
网格可以由单行或单列组成(在这种情况下,它不需要两个定义集合中的一个),甚至只需要一个单元格。
RowDefinition具有GridLength类型的Height属性,ColumnDefinition具有Width属性,也是GridLength类型。 GridLength结构根据GridUnitType枚举指定行高或列宽,该枚举有三个成员:
- 绝对 - 宽度或高度是与设备无关的单位中的值(XAML中的数字)
- 自动 - 宽度或高度根据单元格内容自动调整(XAML中的“自动”)
- 按比例分配星级剩余宽度或高度(XAML中带有“*”的数字)
这是SimpleGridDemo项目中XAML文件的前半部分:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="SimpleGridDemo.SimpleGridDemoPage">
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness"
iOS="0, 20, 0, 0" />
</ContentPage.Padding>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="100" />
<RowDefinition Height="2*" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
__
</Grid>
</ContentPage>
该网格有四行两列。第一行的高度是“自动” - 意味着高度是根据占据第一行的所有元素的最大高度计算的。第二行是100个与设备无关的单位高度。
使用“*”(发音为“star”)的两个高度设置需要一些额外的解释:此特定网格的总高度是页面的高度减去iOS上的填充设置。在内部,Grid根据该行的内容确定第一行的高度,并且它知道第二行的高度为100.它从它自己的高度中减去这两个高度,并在第三行中按比例分配剩余高度和第四行基于星形设置中的数字。第三行是第四行高度的两倍。
两个ColumnDefinition对象都将Width设置为“”,这与“1 ”相同,这意味着屏幕的宽度在两列之间平均分配。
您将从第14章“绝对布局”中回忆一下,AbsoluteLayout类定义了两个附加的可绑定属性和四个静态Set和Get方法,这些方法允许程序在代码或XAML中指定AbsoluteLayout子项的位置和大小。
网格非常相似。 Grid类定义了四个附加的可绑定属性,用于指定Grid的子节点占用的一个或多个单元格:
- Grid.RowProperty-从零开始的行;默认值为0
- Grid.ColumnProperty-从零开始的列; 默认值为0
- Grid.RowSpanProperty - 子跨越的行数; 默认值为1
- Grid.ColumnSpanProperty - 子跨越的列数; 默认值为1
所有四个属性都定义为int类型。
例如,要在代码中指定名为view的Grid子项驻留在特定的行和列中,可以调用:
view.SetValue(Grid.RowProperty, 2);
view.SetValue(Grid.ColumnProperty, 1);
这些是从零开始的行号和列号,因此将子项分配给第三行和第二列。
Grid类还定义了八种静态方法,用于在代码中简化设置和获取这些属性:
- Grid.SetRow 和 Grid.GetRow
- Grid.SetColumn 和 Grid.GetColumn
- Grid.SetRowSpan 和 Grid.GetRowSpan
- Grid.SetColumnSpan 和 Grid.GetColumnSpan
这相当于您刚刚看到的两个SetValue调用:
Grid.SetRow(view, 2);
Grid.SetColumn(view, 1);
正如您在学习AbsoluteLayout时所了解的那样,这些静态Set和Get方法是使用Grid的子节点上的SetValue和GetValue调用实现的。 例如,以下是如何在Grid类中定义SetRow:
public static void SetRow(BindableObject bindable, int value)
{
bindable.SetValue(Grid.RowProperty, value);
}
您无法在XAML中调用这些方法,因此您可以使用以下属性在Grid的子级上设置附加的可绑定属性:
- Grid.Row
- Grid.Column
- Grid.RowSpan
- Grid.ColumnSpan
这些XAML属性实际上并不是由Grid类定义的,但XAML解析器知道它必须引用Grid定义的关联附加可绑定属性。
您不需要在Grid的每个子项上设置所有这些属性。 如果子节点只占用一个单元格,则不要设置Grid.RowSpan或Grid.ColumnSpan,因为默认值为1. Grid.Row和Grid.Column属性的默认值为0,因此您不需要 如果子项占据第一行或第一列,则设置值。 但是,为了清楚起见,本书中的代码通常会显示这两个属性的设置。 为了节省空间,这些属性通常会出现在XAML列表的同一行中。
这是SimpleGridDemo的完整XAML文件:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="SimpleGridDemo.SimpleGridDemoPage">
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness"
iOS="0, 20, 0, 0" />
</ContentPage.Padding>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="100" />
<RowDefinition Height="2*" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label Text="Grid Demo"
Grid.Row="0" Grid.Column="0"
FontSize="Large"
HorizontalOptions="End" />
<Label Text="Demo the Grid"
Grid.Row="0" Grid.Column="1"
FontSize="Small"
HorizontalOptions="End"
VerticalOptions="End" />
<Image BackgroundColor="Gray"
Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2">
<Image.Source>
<OnPlatform x:TypeArguments="ImageSource"
iOS="Icon-60.png"
Android="icon.png"
WinPhone="Assets/StoreLogo.png" />
</Image.Source>
</Image>
<BoxView Color="Green"
Grid.Row="2" Grid.Column="0" />
<BoxView Color="Red"
Grid.Row="2" Grid.Column="1" Grid.RowSpan="2" />
<BoxView Color="Blue"
Opacity="0.5"
Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2" />
</Grid>
</ContentPage>
具有不同FontSize设置的两个Label元素占据第一行的两列。 该行的高度由最高元素控制。 HorizontalOptions和VerticalOptions的设置可以将子项定位在单元格中。
第二行的高度为100个与设备无关的单元。 该行由显示具有灰色背景的应用程序图标的Image元素占用。 Image元素跨越该行的两列。
底部的两行由三个BoxView元素占用,一个跨越两行,另一个跨越两列,这些在右下角的单元格中重叠:
屏幕截图确认第一行的大小与大型Label的高度相同;第二行是100个与设备无关的单位高;并且第三和第四行占据所有剩余空间。第三排是第四排的两倍。两列宽度相等,将整个网格分成两半。红色和蓝色的BoxView元素在右下角的单元格中重叠,但蓝色的BoxView显然位于红色的顶部,因为它的不透明度设置为0.5,结果为紫色。
由于白色背景,蓝色半透明BoxView的左半部分在iPhone和Windows 10移动设备上比在Android手机上轻。
如您所见,Grid的子节点可以共享单元格。子项在XAML文件中出现的顺序是将子项放入Grid中的顺序,后来的子项看起来就像是在早期子项之上(并且模糊了)。
您会注意到一点间隙似乎将背景透过的行和列分开。这由两个Grid属性控制:
RowSpacing-默认值为6
TolumnSpacing-默认值为6
如果要关闭该空间,可以将这些属性设置为0,如果希望窥视颜色不同,则可以设置Grid的BackgroundColor属性。您还可以使用网格上的“填充”设置在网格内部围绕其周边添加空间。
您现在已经了解了Grid定义的所有公共属性和方法。
在继续之前,让我们用SimpleGridDemo进行几个实验。首先,注释掉或删除网格顶部附近的整个RowDefinitions和ColumnDefinitions部分,然后重新部署该程序。这是你会看到的:
如果未定义自己的RowDefinition和ColumnDefinition对象,则Grid会在将视图添加到Children集合时自动生成它们。 但是,默认的RowDefinition和ColumnDefinition是“*”(星号),这意味着现在四行平均分为四分之一屏幕,每个单元格占总网格的八分之一。
这是另一个实验。 恢复RowDefinitions和ColumnDefinitions部分,并将Grid自身的HorizontalOptions和VerticalOptions属性设置为Center。 默认情况下,这两个属性是Fill,这意味着Grid填充其容器。 以下是Center发生的情况:
第三行仍然是底行高度的两倍,但现在底行的高度基于BoxView的默认HeightRequest,即40。
将Grid放入StackLayout时,您会看到类似的效果。 您还可以将StackLayout放在网格单元格中,或者放入网格单元格中的另一个网格中,但不要使用此技术:嵌套网格和其他布局越深,嵌套布局对性能的影响就越大。