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

约束和大小请求
您刚刚看到LayoutChildren覆盖在某些情况下如何仅基于LayoutChildren参数调用其子或子上的Layout。 但在更一般的情况下,LayoutChildren需要在调用子项的Layout方法之前知道其子项的大小。 因此,LayoutChildren覆盖通常会在每个子节点上按此顺序调用两个公共方法:

  • GetSizeRequest
  • Layout

为什么父母需要在其孩子身上调用GetSizeRequest? 为什么父母不能通过访问孩子的Bounds属性或其Width和Height属性来简单地获得孩子的大小?
因为,在一般情况下,尚未设置这些属性!回想一下,这些属性是通过调用Layout来设置的,并且尚未发生布局调用。在一般情况下,在父母知道孩子的请求大小之前,不会发生布局调用。在一般情况下,GetSizeRequest调用是Layout调用的先决条件。
GetSizeRequest返回的信息完全独立于Layout可能设置的任何信息。相反,Layout的参数通常取决于GetSizeRequest返回的信息。
GetSizeRequest调用获取有时称为元素的所需大小的内容。这通常与元素的原生大小有关,并且通常取决于特定平台。在对比中,Layout调用在元素上强加了特定的大小。有时这两种尺寸是相同的,有时不是。如果元素的Horizo​​ntalOptions和VerticalOptions设置是LayoutOptions.Fill,则这两个大小通常不相同。在这种情况下,元素占用的大小通常基于元素父级可用的区域,而不是元素所需的大小。
某些元素的原生大小是固定且不灵活的。例如,在任何特定平台中,Switch始终是由其在该平台中的实现确定的固定大小。但对于其他类型的元素,情况并非总是如此。有时,尺寸的一个尺寸是固定的,但另一个尺寸更灵活。水平滑块的高度由平台实现固定,但滑块的宽度可以与其父级一样宽。
有时元素的大小取决于其属性设置。 Button或Label的大小取决于元素显示的文本和字体大小。由于Label显示的文本可以换行到多行,因此Label的高度取决于显示的行数,并且由Label可用的宽度决定。有时元素的高度或宽度取决于其子元素的高度或宽度。 StackLayout就是这种情况。
这些复杂性要求元素根据约束确定其大小,这通常表示元素的父元素中该元素的可用空间。
与Layout类似,GetSizeRequest方法由VisualElement定义。这是一个公共方法,父元素调用它来获取每个子元素的大小:

public virtual SizeRequest GetSizeRequest(double widthConstraint, double heightConstraint)

widthConstraint和heightConstraint参数通常表示父级可用于子级的大小; 子项负责实现此方法,以根据这些约束为自己确定合适的大小。 例如,Label根据特定宽度确定其文本所需的行数。
VisualElement还定义了一个名为OnSizeRequest的非常相似的受保护方法:

protected virtual SizeRequest OnSizeRequest(double widthConstraint, double heightConstraint)

显然,这两种方法是相关的,容易混淆。 这两种方法都被定义为虚拟,但在整个Xamarin.Forms中,只有一个类重写了GetSizeRequest方法,而这是Layout类,它将方法标记为已密封。

另一方面,从Layout或Layout 派生的每个类都会覆盖OnSizeRequest。这是布局类通过调用其子项的GetSizeRequest方法来确定所需大小的位置。
对于View衍生(但不是布局衍生),公共GetSizeRequest方法调用受保护的OnSizeRequest方法,该方法负责获取元素的本机大小
来自特定于平台的实现。
从GetSizeRequest和OnSizeRequest返回的SizeRequest结构有两个属性:

  • 类型大小的请求
  • 最小类型大小

尝试在新创建的对象(如Label和BoxView和Slider)上调用GetSizeRequest很有诱惑力,并检查返回的大小。但是,除非元素是实际可视化树的一部分,否则GetSizeRequest调用将不起作用,因为只有这样才能使用底层平台对象实现Xamarin.Forms元素。
大多数元素返回具有相同请求和最小大小的SizeRequest值。它们统一不同的唯一元素是ListView和TableView,其中最小大小为(40,40),可能允许显示ListView或TableView的某些部分,即使没有足够的空间可供整体使用事情。
但是,一般情况下,最小大小似乎在Xamarin.Forms布局系统中没有起到太大作用,并且您不需要花费很大的时间来适应它。 SizeRequest结构有一个构造函数,允许您将两个属性设置为相同的Size值。
您可能还记得,VisualElement定义了四个属性,其中包含单词Request作为其名称的一部分:

  • WidthRequest类型为double
  • HeightRequest类型为double
  • DoubleWidthRequest类型为double
  • DoubleHeightRequest类型为double

与Width和Height属性不同,这四个属性具有公共集访问器。您的应用程序可以设置元素的WidthRequest和HeightRequest属性以覆盖其惯用大小。这对BoxView特别有用,它将WidthRequest和HeightRequest值初始化为40.您可以将这些属性设置为不同的值,以使BoxView成为您想要的任何大小。
默认情况下,这四个属性的“模拟”值为-1。如果将它们设置为实际值,则GetSizeRequest和OnSizeRequest如何与它们进行交互:
首先,GetSizeRequest找到widthConstraint参数的最小值和元素的WidthRequest属性以及heightConstraint和HeightRequest的最小值。 这些是传递给OnSizeRequest的值。 本质上,该元素的大小与WidthRequest和HeightRequest属性所指示的大小相同。
基于这些约束,OnSizeRequest将SizeRequest值返回给GetSizeRequest。 SizeRequest值具有Request和Minimum属性。 然后,GetSizeRequest查找Request属性的Width和Height属性的最小值以及元素上设置的WidthRequest和HeightRequest属性。 它还会找到Minimum属性的Width和Height属性的最小值,以及在元素上设置的MinimumWidthRequest和MinimumHeightRequest属性。 GetSizeRequest然后根据这些最小值返回一个新的SizeRequest值。
这是一些简单的标记:

<ContentPage __ Padding="20">
    <Label Text="Sample text"
           HorizontalOptions="Center"
           VerticalOptions="Center" />
</ContentPage>

假设纵向模式下的屏幕是360乘640.布局循环从调用ContentPage的Layout方法开始,边界矩形为(0,0,360,640)。 ContentPage中的LayoutChildren覆盖的参数针对填充进行了调整,因此参数为(20,20,320,600)。
由于Label的Horizo​​ntalOptions和VerticalOptions属性未设置为LayoutOptions.Fill,因此页面必须通过调用约束为(320,600)的GetSizeRequest来确定Label的大小。 Label返回的信息取决于平台,但我们假设Label返回的大小为(100,24)。然后,ContentPage必须将该Label定位在其子级可用的(320,600)区域的中心。从320的宽度,它减去标签宽度100并除以2.那是110,但这是相对于孩子可用的区域,而不是相对于页面的左上角,其中包括边缘20.因此,Label与ContentPage的水平偏移实际上是130。
ContentPage对高度执行类似的计算:600减24,除以2,再加上20或308.然后,ContentPage调用Label的Layout方法,并使用bounds矩形(130,308,100,24)来定位和标签相对于自身的大小。
Label上的WidthRequest和HeightRequest设置如何影响这个?这是一个WidthRequest,它不仅仅是Label所需要的,而是一个更少的HeightRequest:

<Label Text="Sample text"
       WidthRequest="200"
       HeightRequest="12"
       HorizontalOptions="Center"
       VerticalOptions="Center" />

ContentPage仍然使用约束(320,600)调用Label的GetSizeRequest方法,但GetSizeRequest将这些约束修改为(200,12),并且这是传递给OnSizeRequest覆盖的内容。 Label仍返回请求的大小(100,24),但GetSizeRequest再次调整宽度和高度请求的大小,并返回(200,12)返回ContentPage。
然后,ContentPage基于标签尺寸(200,12)而不是(100,24)调用Label的Layout方法。 Label上的Layout调用现在具有(80,314,200,12)的边界矩形。标签显示的宽度是文本所需宽度的两倍,但高度只有一半。文字在底部被裁剪掉了。
如果将Label上的WidthRequest设置设置为小于100(例如50),则使用widthConstraint参数50调用OnSizeRequest方法,Label会计算文本的高度,从而将文本包装到文本中多行

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章