天天看點

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

尺寸和定位

布局過程從可視樹的頂部開始,然後通過可視樹的所有分支繼續進行,以包含頁面上的每個可視元素。 作為其他元素的父母的元素負責相對于他們自己的孩子的大小調整和定位。 這要求父元素調用子元素中的某些公共方法。 這些公共方法通常會導緻調用每個元素中的其他方法,要設定的屬性以及要觸發的事件。

也許布局中涉及的最重要的公共方法被命名(非常恰當)布局。 此方法由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無法知道它需要多少行而不知道可用的水準空間。

此問題意味着涉及更多步驟。

繼續閱讀