第二十六章:自定义布局(五)

内视过程中
本章到目前为止提供的大部分信息都是从包含派生自各种元素(如StackLayout,ScrollView和Label)的类的测试程序汇编而来,覆盖虚拟方法(如GetSizeRequest,OnSizeRequest,OnSizeAllocated和LayoutChildren) ,并使用System.Diagnostics命名空间中的Debug.WriteLine方法在Visual Studio或Xamarin Studio的“输出”窗口中显示信息。
探索过程中的一小部分 - 但使用手机本身显示此信息 - 显示在ExploreChildSizes示例中。
ExploreChildSizes使用MasterDetailPage在Master页面上显示一组单选按钮,在Detail部分上显示可视树。单选按钮使用第25章“页面变体”中提供的RadioButtonManager和RadioButtonItem类。这是带有单选按钮的主页面,用于在详细信息页面上为子视图选择Horizo​​ntalOptions和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。 Horizo​​ntalOptions设置将项目定位在左侧,中间或右侧。
您可能会注意到一些奇怪之处:首先,OpenStackLayout不会调用其GetSizeRequest方法。这就是为什么屏幕下半部分的前三项全部为零。此GetSizeRequest调用通常来自Grid,它是其父级。但是,Grid的大小基于屏幕的大小,Grid包含两行大小相等的行。 OpenStackLayout将其VerticalOptions和Horizo​​ntalOptions属性设置为LayoutOptions.Fill,因此它的大小将基于Grid而不是其内容。
如果您想进一步调查此行为,则需要在“详细信息”页面上的标记中更改OpenStackLayout的VerticalOptions或Horizo​​ntalOptions属性。在这种情况下,Grid将调用OpenStackLayout和OpenStackLayout的GetSizeRequest方法,然后对Label和BoxView进行GetSizeRequest调用,因为它需要知道OpenStackLayout大小来定位它。
OpenLabel和OpenBoxView都使用Double.PositiveInfinity的高度约束来调用其GetSizeRequest方法,但Label显示了平台之间的一些不一致。
在各种Windows平台上,从显示的值中可以看出,Label的约束宽度不等于StackLayout的布局宽度。但是进一步的探索揭示了GetSizeRequest方法不止一次被调用 - 第一次使用布局宽度,然后使用所请求的Label宽度。
Android Label将宽度约束作为其请求的宽度返回,这意味着Label上的HorizontalOptions设置对其水平位置没有影响。 当文本只占一行时,Android实现中的这种差异就会消失。

上一篇:Android跳转到系统Activity关闭并返回原Activity的解决办法


下一篇:android各页面数据共享