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

简介: 内视过程中本章到目前为止提供的大部分信息都是从包含派生自各种元素(如StackLayout,ScrollView和Label)的类的测试程序汇编而来,覆盖虚拟方法(如GetSizeRequest,OnSizeRequest,OnSizeAllocated和LayoutChildren) ,并使用System.Diagnostics命名空间中的Debug.WriteLine方法在Visual Studio或Xamarin Studio的“输出”窗口中显示信息。

内视过程中
本章到目前为止提供的大部分信息都是从包含派生自各种元素(如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的黄色背景:
2019_05_15_095023
详细信息页面(如下所示)被网格划分为两行相等的高度。 顶行是一个简单的可视树,由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属性中):
2019_05_15_100104
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实现中的这种差异就会消失。

目录
相关文章
|
XML Android开发 数据格式
开发App抽屉功能,彻底掌握了吗?
在平时开发中,带有抽屉效果的App,应该还是挺多,今天就来看看这个效果是如何实现的,我们用DrawerLayout控件来实现!
288 0
开发App抽屉功能,彻底掌握了吗?
|
JavaScript Android开发
第二十六章:自定义布局(十)
不允许无约束的尺寸!有时您希望在屏幕上看到所有内容,可能是一系列大小统一的行和列。您可以使用带有星号定义的所有行和列定义的Grid执行类似的操作,以使它们具有相同的大小。唯一的问题是您可能还希望行数和列数基于子节点数,并针对屏幕空间的最佳使用进行了优化。
832 0
|
存储 缓存 JavaScript
第二十六章:自定义布局(九)
编码的一些规则从上面的讨论中,您可以为自己的Layout 衍生物制定几个规则:规则1:如果布局类定义了诸如间距或方向等属性,则这些属性应由可绑定属性支持。 在大多数情况下,这些可绑定属性的属性更改处理程序应调用InvalidateLayout。
2067 0
|
JavaScript Android开发
第二十六章:自定义布局(八)
失效假设您已在页面上组装了一些布局和视图,并且由于某种原因,代码隐藏文件(或者可能是触发器或行为)会更改Button的文本,或者可能只是字体大小或属性。 该更改可能会影响按钮的大小,这可能会对页面其余部分的布局更改产生连锁反应。
3417 0
|
JavaScript Android开发
第二十六章:自定义布局(七)
垂直和水平定位简化在VerticalStack中,LayoutChildren覆盖的末尾是一个switch语句,它有助于根据子级的HorizontalOptions属性设置水平定位每个子级。 这是整个方法: public class VerticalStack : Layout<View> { ...
869 0
|
JavaScript Android开发
第二十六章:自定义布局(十二)
更多附加的可绑定属性附加的可绑定属性也可以在XAML中设置并使用Style设置。 为了了解它是如何工作的,让我们检查一个名为CartesianLayout的类,它模仿一个二维的,四象限的笛卡尔坐标系。
538 0
|
JavaScript Android开发
第二十六章:自定义布局(六)
从Layout派生 我们现在拥有足够的知识来创建我们自己的布局类。布局中涉及的大多数公共和受保护方法都是由非泛型布局类定义的。 Layout 类派生自Layout,并将泛型类型约束为View及其派生类。
764 0
|
JavaScript Android开发
第二十六章:自定义布局(十一)
重叠的子项Layout 类可以在其子项上调用Layout方法,以便子项重叠吗?是的,但这可能会在你的脑海中提出另一个问题:什么决定孩子们的呈现顺序?哪些孩子看似坐在前台,可能部分或完全掩盖了背景中显示的其他孩子?在某些图形环境中,程序员可以访问名为Z-index的值。
640 0
|
Android开发
第二十六章:自定义布局(十三)
Layout和LayoutToVisualElement定义了一组转换属性。这些是AnchorX,AnchorY,Rotation,RotationX,RotationY,Scale,TranslationX和TranslationY,它们根本不影响布局。
754 0