内视过程中
本章到目前为止提供的大部分信息都是从包含派生自各种元素(如StackLayout,ScrollView和Label)的类的测试程序汇编而来,覆盖虚拟方法(如GetSizeRequest,OnSizeRequest,OnSizeAllocated和LayoutChildren) ,并使用System.Diagnostics命名空间中的Debug.WriteLine方法在Visual Studio或Xamarin Studio的“输出”窗口中显示信息。
探索过程中的一小部分 - 但使用手机本身显示此信息 - 显示在ExploreChildSizes示例中。
ExploreChildSizes使用MasterDetailPage在Master页面上显示一组单选按钮,在Detail部分上显示可视树。单选按钮使用第25章“页面变体”中提供的RadioButtonManager和RadioButtonItem类。这是带有单选按钮的主页面,用于在详细信息页面上为子视图选择HorizontalOptions和VerticalOptions属性:
<MasterDetailPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:ExploreChildSizes;assembly=ExploreChildSizes"
xmlns:toolkit=
"clr-namespace:Xamarin.FormsBook.Toolkit;assembly=Xamarin.FormsBook.Toolkit"
x:Class="ExploreChildSizes.ExploreChildSizesPage">
<MasterDetailPage.Master>
<ContentPage Title="swap">
<ContentPage.Icon>
<OnPlatform x:TypeArguments="FileImageSource"
WinPhone="Images/refresh.png" />
</ContentPage.Icon>
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness"
iOS="0, 20, 0, 0" />
</ContentPage.Padding>
<ScrollView>
<StackLayout Padding="20"
Spacing="20">
<StackLayout>
<StackLayout.BindingContext>
<toolkit:RadioButtonManager x:Name="vertRadios"
x:TypeArguments="LayoutOptions" />
</StackLayout.BindingContext>
<StackLayout HorizontalOptions="Start">
<Label Text="Child VerticalOptions"
FontSize="Medium" />
<BoxView Color="Accent"
HeightRequest="3" />
</StackLayout>
<local:RadioButton BindingContext="{Binding Items[0]}" />
<local:RadioButton BindingContext="{Binding Items[1]}" />
<local:RadioButton BindingContext="{Binding Items[2]}" />
<local:RadioButton BindingContext="{Binding Items[3]}" />
<local:RadioButton BindingContext="{Binding Items[4]}" />
<local:RadioButton BindingContext="{Binding Items[5]}" />
<local:RadioButton BindingContext="{Binding Items[6]}" />
<local:RadioButton BindingContext="{Binding Items[7]}" />
</StackLayout>
<StackLayout>
<StackLayout.BindingContext>
<toolkit:RadioButtonManager x:Name="horzRadios"
x:TypeArguments="LayoutOptions" />
</StackLayout.BindingContext>
<StackLayout HorizontalOptions="Start">
<Label Text="Child HorizontalOptions"
FontSize="Medium" />
<BoxView Color="Accent"
HeightRequest="3" />
</StackLayout>
<local:RadioButton BindingContext="{Binding Items[0]}" />
<local:RadioButton BindingContext="{Binding Items[1]}" />
<local:RadioButton BindingContext="{Binding Items[2]}" />
<local:RadioButton BindingContext="{Binding Items[3]}" />
<local:RadioButton BindingContext="{Binding Items[4]}" />
<local:RadioButton BindingContext="{Binding Items[5]}" />
<local:RadioButton BindingContext="{Binding Items[6]}" />
<local:RadioButton BindingContext="{Binding Items[7]}" />
</StackLayout>
</StackLayout>
</ScrollView>
</ContentPage>
</MasterDetailPage.Master>
__
</MasterDetailPage>
这个页面在Xamarin.FormsBook.Toolkit库中使用了一个名为RadioButtonManager的类,您可以在闲暇时阅读它。 它允许成为与所选按钮关联的项目的绑定源。 RadioButton类使用Accent颜色和Bold属性来指示所选项:
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ExploreChildSizes.RadioButton">
<Label Text="{Binding Name}"
FontSize="Medium">
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding Command}"
CommandParameter="{Binding Value}"/>
</Label.GestureRecognizers>
<Label.Triggers>
<DataTrigger TargetType="Label"
Binding="{Binding IsSelected}"
Value="True">
<Setter Property="TextColor" Value="Accent" />
<Setter Property="FontAttributes" Value="Bold" />
</DataTrigger>
</Label.Triggers>
</Label>
</ContentView>
这是三个平台上的Master页面。 在所有三个屏幕的右侧,您可以看到一个详细信息页面的切片,其中包含StackLayout的黄色背景:
详细信息页面(如下所示)被网格划分为两行相等的高度。 顶行是一个简单的可视树,由StackLayout和Label以及BoxView组成。 但是,此可视树中的类实际上是从StackLayout,Label和BoxView派生的,并且称为OpenStackLayout,OpenLabel和OpenBoxView。 请注意,OpenLabel和OpenBoxView的VerticalOptions和HorizontalOptions属性绑定到Master页面上的两个RadioButtonManager对象:
<MasterDetailPage __ >
__
<MasterDetailPage.Detail>
<ContentPage>
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness"
iOS="0, 20, 0, 0" />
</ContentPage.Padding>
<Grid>
<local:OpenStackLayout x:Name="openStackLayout"
Grid.Row="0"
BackgroundColor="Yellow"
Padding="15">
<local:OpenLabel
x:Name="openLabel"
Text="This is a label with text sufficiently long enough to wrap"
FontSize="Large"
BackgroundColor="Gray"
VerticalOptions="{Binding Source={x:Reference vertRadios},
Path=SelectedValue}"
HorizontalOptions="{Binding Source={x:Reference horzRadios},
Path=SelectedValue}" />
<local:OpenBoxView
x:Name="openBoxView"
Color="Pink"
VerticalOptions="{Binding Source={x:Reference vertRadios},
Path=SelectedValue}"
HorizontalOptions="{Binding Source={x:Reference horzRadios},
Path=SelectedValue}" />
</local:OpenStackLayout>
__
</Grid>
</ContentPage>
</MasterDetailPage.Detail>
</MasterDetailPage>
此上下文中的Open前缀表示这些类定义公共属性,这些属性显示GetSizeRequest调用的参数和返回值,以及(在OpenStackLayout的情况下)LayoutChildren的参数。 所有这些属性都由只读可绑定属性支持,以便它们可以作为数据绑定的源。 此外,Bounds属性镜像在名为ElementBounds的属性中,也由只读可绑定属性支持:
这是OpenLabel类。 另外两个是相似的:
class OpenLabel : Label
{
static readonly BindablePropertyKey ConstraintKey =
BindableProperty.CreateReadOnly(
"Constraint",
typeof(Size),
typeof(OpenLabel),
new Size());
public static readonly BindableProperty ConstraintProperty =
ConstraintKey.BindableProperty;
static readonly BindablePropertyKey SizeRequestKey =
BindableProperty.CreateReadOnly(
"SizeRequest",
typeof(SizeRequest),
typeof(OpenLabel),
new SizeRequest());
public static readonly BindableProperty SizeRequestProperty =
SizeRequestKey.BindableProperty;
static readonly BindablePropertyKey ElementBoundsKey =
BindableProperty.CreateReadOnly(
"ElementBounds",
typeof(Rectangle),
typeof(OpenLabel),
new Rectangle());
public static readonly BindableProperty ElementBoundsProperty =
ElementBoundsKey.BindableProperty;
public OpenLabel()
{
SizeChanged += (sender, args) =>
{
ElementBounds = Bounds;
};
}
public Size Constraint
{
private set { SetValue(ConstraintKey, value); }
get { return (Size)GetValue(ConstraintProperty); }
}
public SizeRequest SizeRequest
{
private set { SetValue(SizeRequestKey, value); }
get { return (SizeRequest)GetValue(SizeRequestProperty); }
}
public Rectangle ElementBounds
{
private set { SetValue(ElementBoundsKey, value); }
get { return (Rectangle)GetValue(ElementBoundsProperty); }
}
public override SizeRequest GetSizeRequest(double widthConstraint, double heightConstraint)
{
Constraint = new Size(widthConstraint, heightConstraint);
SizeRequest sizeRequest = base.GetSizeRequest(widthConstraint, heightConstraint);
SizeRequest = sizeRequest;
return sizeRequest;
}
}
详细信息页面上网格的下半部分包含一个可滚动的StackLayout,其中包含数据绑定以显示这些属性:
<MasterDetailPage __ >
__
<MasterDetailPage.Detail>
<ContentPage>
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness"
iOS="0, 20, 0, 0" />
</ContentPage.Padding>
<Grid>
__
<ScrollView Grid.Row="1"
Padding="10, 0">
<StackLayout>
<StackLayout.Resources>
<ResourceDictionary>
<Style TargetType="Label">
<Setter Property="FontSize" Value="Small" />
</Style>
</ResourceDictionary>
</StackLayout.Resources>
<StackLayout
BindingContext="{Binding Source={x:Reference openStackLayout}">
<Label Text="StackLayout:"
FontAttributes="Bold" />
<Label Text="{Binding Path=Constraint,
StringFormat='Constraint = {0}'}" />
<Label Text="{Binding Path=SizeRequest.Request,
StringFormat='Request = {0}'}" />
<Label Text="{Binding Path=SizeRequest.Minimum,
StringFormat='Minimum = {0}'}" />
<Label Text="{Binding Path=ElementBounds,
StringFormat='Bounds = {0}'}" />
<Label Text="{Binding Path=LayoutBounds,
StringFormat='Layout = {0}'}" />
</StackLayout>
<StackLayout BindingContext="{Binding Source={x:Reference openLabel}">
<Label Text="Label:"
FontAttributes="Bold" />
<Label Text="{Binding Path=Constraint,
StringFormat='Constraint = {0}'}" />
<Label Text="{Binding Path=SizeRequest.Request,
StringFormat='Request = {0}'}" />
<Label Text="{Binding Path=SizeRequest.Minimum,
StringFormat='Minimum = {0}'}" />
<Label Text="{Binding Path=ElementBounds,
StringFormat='Bounds = {0}'}" />
</StackLayout>
<StackLayout BindingContext="{Binding Source={x:Reference openBoxView}">
<Label Text="BoxView:"
FontAttributes="Bold" />
<Label Text="{Binding Path=Constraint,
StringFormat='Constraint = {0}'}" />
<Label Text="{Binding Path=SizeRequest.Request,
StringFormat='Request = {0}'}" />
<Label Text="{Binding Path=SizeRequest.Minimum,
StringFormat='Minimum = {0}'}" />
<Label Text="{Binding Path=ElementBounds,
StringFormat='Bounds = {0}'}" />
</StackLayout>
</StackLayout>
</ScrollView>
</Grid>
</ContentPage>
</MasterDetailPage.Detail>
</MasterDetailPage>
然后,您可以在Label和BoxView上设置VerticalOptions和HorizontalOptions的各种组合,并查看它们如何影响参数并从GetSizeRequest方法返回值以及Layout方法的参数(这些都反映在Bounds属性中):
Label和BoxView上的VerticalOptions设置无效,除非Expands标志为true。 HorizontalOptions设置将项目定位在左侧,中间或右侧。
您可能会注意到一些奇怪之处:首先,OpenStackLayout不会调用其GetSizeRequest方法。这就是为什么屏幕下半部分的前三项全部为零。此GetSizeRequest调用通常来自Grid,它是其父级。但是,Grid的大小基于屏幕的大小,Grid包含两行大小相等的行。 OpenStackLayout将其VerticalOptions和HorizontalOptions属性设置为LayoutOptions.Fill,因此它的大小将基于Grid而不是其内容。
如果您想进一步调查此行为,则需要在“详细信息”页面上的标记中更改OpenStackLayout的VerticalOptions或HorizontalOptions属性。在这种情况下,Grid将调用OpenStackLayout和OpenStackLayout的GetSizeRequest方法,然后对Label和BoxView进行GetSizeRequest调用,因为它需要知道OpenStackLayout大小来定位它。
OpenLabel和OpenBoxView都使用Double.PositiveInfinity的高度约束来调用其GetSizeRequest方法,但Label显示了平台之间的一些不一致。
在各种Windows平台上,从显示的值中可以看出,Label的约束宽度不等于StackLayout的布局宽度。但是进一步的探索揭示了GetSizeRequest方法不止一次被调用 - 第一次使用布局宽度,然后使用所请求的Label宽度。
Android Label将宽度约束作为其请求的宽度返回,这意味着Label上的HorizontalOptions设置对其水平位置没有影响。 当文本只占一行时,Android实现中的这种差异就会消失。