第二十六章:自定義佈局(三)

約束和大小請求
您剛剛看到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會計算文本的高度,從而將文本包裝到文本中多行

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