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

尺寸和定位
佈局過程從可視樹的頂部開始,然後通過可視樹的所有分支繼續進行,以包含頁面上的每個可視元素。 作爲其他元素的父母的元素負責相對於他們自己的孩子的大小調整和定位。 這要求父元素調用子元素中的某些公共方法。 這些公共方法通常會導致調用每個元素中的其他方法,要設置的屬性以及要觸發的事件。
也許佈局中涉及的最重要的公共方法被命名(非常恰當)佈局。 此方法由VisualElement定義,並由派生自VisualElement的每個類繼承:

public void Layout(Rectangle bounds)

Layout方法指定元素的兩個特徵:

  • 呈現元素的矩形區域(由Rectangle值的Width和Height屬性指示); 和
  • 元素左上角相對於其父左上角的位置(X和Y屬性)。

當應用程序啓動並且需要顯示第一頁時,第一個佈局調用是指向Page對象,“寬度”和“高度”屬性指示屏幕的大小或頁面佔用的屏幕區域。從第一個Layout調用開始,Layout調用通過可視樹有效傳播:作爲其他元素(Page,Layout和Layout 衍生物)的父元素的每個元素負責在其子元素上調用Layout方法,導致頁面上的每個可視元素都調用其Layout方法。 (你會很快看到它是如何工作的。)
整個過程稱爲佈局循環,如果您將手機側向轉動,則佈局週期
使用Page對象從可視樹的頂部開始。如果某些更改會影響佈局,則佈局週期也可能發生在可視樹的子集上。這些更改包括從集合中添加或刪除的項目(例如ListView或StackLayout或其他Layout類中的項目,元素的IsVisible屬性的更改或元素大小的更改(出於某種原因) )。
在VisualElement內部,Layout方法導致設置元素的五個屬性。這些屬性都由VisualElement定義:

  • Bounds類型是 Rectangle
  • X類型是 double
  • Y類型是double
  • Width類型是 double
  • Height類型是 double

這些屬性都是同步的。 VisualElement的X,Y,Width和Height屬性始終與Bounds矩形的X,Y,Width和Height屬性值相同。這些屬性指示元素的實際渲染大小及其相對於其父級左上角的位置。
這五個屬性都沒有公共集訪問器。對於外部代碼,這些屬性是getonly。
在元素的第一個佈局調用之前,X和Y屬性的值爲0,但Width和Height屬性的“mock”值爲-1,表示尚未設置屬性。只有在佈局週期發生後,這些屬性的有效值纔可用。在執行構成可視樹的元素的構造函數期間,有效值不可用。
X,Y,Width和Height屬性都由可綁定屬性支持,因此它們可以是數據綁定的源。 Bounds屬性不受可綁定屬性的支持,也不會觸發PropertyChanged事件。不要將Bounds用作數據綁定源。
對Layout的調用也會觸發對SizeAllocated方法的調用,該方法由VisualElement定義,如下所示:

protected void SizeAllocated(double width, double height)

這兩個參數與Bounds矩形的Width和Height屬性相同。 SizeAllocated方法調用受保護的虛方法名稱OnSizeAllocated:

protected virtual void OnSizeAllocated(double width, double height)

在OnSizeAllocated方法返回並且大小已從其先前值更改後,VisualElement將觸發SizeChanged事件,其定義如下:

public event EventHandler SizeChanged;

這表示元素的大小已設置或隨後已更改。正如您在前面的章節中所看到的,當您需要實現一些特定於大小的處理時,SizeChanged事件是訪問Bounds屬性或Width和Height屬性以獲取頁面或任何元素的有效大小的絕佳機會。這頁紙。通過觸發SizeChanged事件完成對Layout方法的調用。
作爲SizeChanged事件的替代方法,應用程序可以覆蓋ContentPage派生中的OnSizeAllocated以獲取頁面的新大小。 (如果這樣做,請務必調用OnSizeAllocated的基類實現。)您會發現,當元素的大小實際上沒有變化時,有時會調用OnSizeAllocated。 SizeChanged事件僅在大小更改時觸發,並且對於應用程序級別的特定於大小的處理更好。
OnSizeAllocated方法未定義爲虛擬,因此應用程序可以覆蓋它,但允許Xamarin.Forms中的類覆蓋它。只有兩個類重寫OnSizeAllocated來執行自己的專門處理,但它們是非常重要的類:

  • Page
  • Layout

這些是所有Xamarin.Forms元素的基類,它們充當Xamarin.Forms可視樹中其他元素的父級。 (儘管ListView和TableView似乎也有子節點,但這些子節點的佈局是在這些視圖的平臺實現中處理的。)
從Page和Layout派生的一些類具有View類型的Content屬性。 這些類是ContentPage,ContentView,Frame和ScrollView。 Content屬性是單個子項。 從Page(MasterDetailPage,TabbedPage和CarouselPage)派生的其他類有多個子節點。 從Layout 派生的類具有IList 類型的Children proprty; 這些類是StackLayout,AbsoluteLayout,RelativeLayout和Grid。
Page和Layout類具有並行結構,以OverSizeAllocated方法的覆蓋開始。 這兩個類都定義了從OnSizeAllocated覆蓋調用的以下方法:

protected void UpdateChildrenLayout()

兩個版本的UpdateChildrenLayout都調用名爲LayoutChildren的方法。 在Page和Layout中,此方法的定義略有不同。 在Page中,LayoutChildren方法定義爲virtual:

protected virtual void LayoutChildren(double x, double y, double width, double height)

在Layout中,它被定義爲抽象:

protected abstract void LayoutChildren(double x, double y, double width, double height);

每個具有Content或Children屬性的Xamarin.Forms類都具有可覆蓋的LayoutChildren方法。當您編寫自己的類派生自Layout (這是本章的主要目標)時,您將覆蓋LayoutChildren以提供佈局“子”的自定義組織。
LayoutChildren覆蓋的責任是在所有元素的子元素上調用Layout方法,這些元素通常是設置爲元素的View對象的Content屬性或元素中的View對象的子集合。這是佈局中最重要的部分。
正如您所記得的,對Layout方法的調用會導致設置Bounds,X,Y,Width和Height屬性,並調用SizeAllocated和OnSizeAllocated。如果元素是Layout de rivative,則OnSizeAllocated調用UpdateChildrenLayout和LayoutChildren。然後LayoutChildren在其子項上調用Layout。這就是佈局調用從可視樹的頂部傳播到頁面上的所有分支和每個元素的方式。
Page和Layout都定義了LayoutChanged事件:

public event EventHandler LayoutChang

UpdateChildrenLayout方法通過觸發此事件來結束,但前提是至少有一個子節點具有新的Bounds屬性。
您已經看到Page和Layout類都覆蓋了OnSizeAllocated方法,並且都定義了UpdateChildrenLayout和LayoutChildren方法以及LayoutChanged事件。 Page和Layout類還有另一個相似之處:它們都定義了Padding屬性。 此填充自動反映在LayoutChildren的參數中。
例如,請考慮以下頁面定義:

<ContentPage __ Padding="20">
    <ContentView Padding="15">
        <Label Text="Sample text" />
    </ContentView>
</ContentPage>

假設縱向模式下的屏幕測量360乘640.OconmentsPage調用其Layout方法,邊界矩形等於(0,0,360,640)。 這開始了佈局週期。
雖然ContentPage中的Layout方法的參數爲(0,0,360,640),但該頁面中的LayoutChildren調用的Padding屬性調整爲20.寬度和高度均減少40(每邊20個) 並且x和y參數增加20,因此LayoutChildren參數爲(20,20,320,600)。 這是相對於頁面的矩形,其中Con tentPage可以定位其子項。
ContentPage中的LayoutChildren方法調用其子窗體(ContentView)中的Layout方法,爲ContentView提供頁面可用的整個空間減去頁面上的填充。 此Layout調用的bounds矩形參數是(20,20,320,600),它將ContentView 20單元的左上角定位在ContentPage的左上角的右下方。
在ContentView中對LayoutChildren覆蓋的調用反映了該佈局區域,但是由Padding設置15減少,因此ContentView中LayoutChildren覆蓋的參數爲(15,15,290,570)。 此LayoutChildren方法使用該值調用Label中的Layout方法。
現在讓我們做一點改變:

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

ContentPage中的LayoutChildren覆蓋現在需要做一些不同的事情。 它不能簡單地在ContentView上使用自己的大小減去填充來調用Layout。 它必須調用ContentView中的Layout方法,使ContentView在其可用空間內垂直居中。
但是怎麼樣? 要使ContentView相對於自身垂直居中,ContentPage必須知道ContentView的高度。 但ContentView的高度取決於Label的高度,並且該高度取決於文本,也可能取決於可能在Label上設置的各種字體屬性。 此外,Label能夠將文本包裝到多行,並且Label無法知道它需要多少行而不知道可用的水平空間。
此問題意味着涉及更多步驟。

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