附加可綁定屬性
如果我們希望這個棋盤在螢幕的範圍内盡可能大,我們需要在頁面的SizeChanged處理程式中将BoxView元素添加到AbsoluteLayout,否則SizeChanged處理程式需要找到一些方法來改變 已經在Children集合中的BoxView元素的位置和大小。
這兩個選項都是可能的,但第二個選項是首選,因為我們可以在程式的構造函數中隻填充一次AbsoluteLayout的Children集合,然後稍後調整大小和位置。
在第一次遇到時,允許您設定已經在AbsoluteLayout中的子項的位置和大小的文法可能看起來有點奇怪。 如果view是View類型的對象而rect是一個Rectangle值,這裡的語句給出了一個rect的位置和大小:
AbsoluteLayout.SetLayoutBounds(view, rect);
這不是您正在進行SetLayoutBounds調用的AbsoluteLayout執行個體。不。這是AbsoluteLayout類的靜态方法。您可以在将視圖子項添加到AbsoluteLayout子集合之前或之後調用AbsoluteLayout.SetLayoutBounds。實際上,因為它是一個靜态方法,是以你可以在AbsoluteLayout執行個體化之前調用該方法!此SetLayoutBounds方法中根本不涉及AbsoluteLayout的特定執行個體。
讓我們看一些使用這個神秘的AbsoluteLayout.SetLayoutBounds方法的代碼,然後檢查它是如何工作的。
ChessboardDynamic程式頁面構造函數使用簡單的Add方法,無需定位或調整大小,以便在一個for循環中将32個BoxView元素添加到AbsoluteLayout。為了在棋盤周圍提供一點邊距,AbsoluteLayout是ContentView的子節點,并在頁面上設定了填充。此ContentView有一個SizeChanged處理程式,用于根據容器的大小定位和調整AbsoluteLayout子項的大小:
public class ChessboardDynamicPage : ContentPage
{
AbsoluteLayout absoluteLayout;
public ChessboardDynamicPage()
{
absoluteLayout = new AbsoluteLayout
{
BackgroundColor = Color.FromRgb(240, 220, 130),
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center
};
for (int i = 0; i < 32; i++)
{
BoxView boxView = new BoxView
{
Color = Color.FromRgb(0, 64, 0)
};
absoluteLayout.Children.Add(boxView);
}
ContentView contentView = new ContentView
{
Content = absoluteLayout
};
contentView.SizeChanged += OnContentViewSizeChanged;
this.Padding = new Thickness(5, Device.OnPlatform(25, 5, 5), 5, 5);
this.Content = contentView;
}
void OnContentViewSizeChanged(object sender, EventArgs args)
{
ContentView contentView = (ContentView)sender;
double squareSize = Math.Min(contentView.Width, contentView.Height) / 8;
int index = 0;
for (int row = 0; row < 8; row++)
{
for (int col = 0; col < 8; col++)
{
// Skip every other square.
if (((row ^ col) & 1) == 0)
continue;
View view = absoluteLayout.Children[index];
Rectangle rect = new Rectangle(col * squareSize,
row * squareSize,
squareSize, squareSize);
AbsoluteLayout.SetLayoutBounds(view, rect);
index++;
}
}
}
}
SizeChanged處理程式包含與ChessboardFixed中的構造函數相同的邏輯,除了BoxView元素已經存在于AbsoluteLayout的Children集合中。 所有必要的是在容器大小改變時(例如,在電話方向改變期間)定位和調整每個BoxView的大小。 for循環以每個BoxView的靜态AbsoluteLayout.SetLayoutBounds方法調用結束,并計算Rectangle值。
現在棋盤的大小适合螢幕稍微有點:

顯然,神秘的AbsoluteLayout.SetLayoutBounds方法可行,但如何? 它有什麼作用? 如何在不引用特定AbsoluteLayout對象的情況下設法完成它的工作?
您剛剛看到的AbsoluteLayout.SetLayoutBounds調用如下所示:
AbsoluteLayout.SetLayoutBounds(view, rect);
該方法調用完全等同于子視圖上的以下調用:
view.SetValue(AbsoluteLayout.LayoutBoundsProperty, rect);
這是對子視圖的SetValue調用。這兩個方法調用完全等價,因為第二個是AbsoluteLayout在内部定義SetLayoutBounds靜态方法的方式。 AbsoluteLayout.SetLayoutBounds隻是一種快捷方法,類似的靜态AbsoluteLayout.GetLayoutBounds方法是GetValue調用的快捷方式。
您會記得SetValue和GetValue是由BindableObject定義的,用于實作可綁定屬性。僅從名稱判斷,AbsoluteLayout.LayoutBoundsProperty當然看起來是一個BindableProperty對象,就是這樣。但是,它是一種非常特殊的可綁定屬性,稱為附加可綁定屬性。
隻能在定義屬性的類的執行個體或派生類的執行個體上設定正常的可綁定屬性。附加的可綁定屬性可以破壞該規則:附加的可綁定屬性由一個類定義 - 在本例中為AbsoluteLayout - 但設定在另一個對象上,在本例中為AbsoluteLayout的子對象。該物業有時被稱為附屬于兒童,是以得名。
AbsoluteLayout的子節點不知道傳遞給其SetValue方法的附加可綁定屬性的目的,并且子節點在其自己的内部邏輯中不使用該值。子程序的SetValue方法隻是将Rectangle值儲存在子程序中由BindableObject維護的字典中,實際上将此值附加到可能在某些子程序中使用的子程序中。
由父級指向 - AbsoluteLayout對象。
當AbsoluteLayout布置其子節點時,它可以通過調用子節點上的AbsoluteLayout.GetLayoutBounds靜态方法來查詢每個子節點上此屬性的值,該子節點又使用AbsoluteLayout.LayoutBoundsProperty附加的可綁定屬性調用子節點上的GetValue。對GetValue的調用從存儲在子項中的字典中擷取Rectangle值。
您可能想知道:為什麼需要這樣一個迂回過程來為AbsoluteLayout的孩子設定定位和大小調整資訊? View是否更容易定義應用程式可以設定的簡單X,Y,Width和Height屬性?
也許,但這些屬性僅适用于AbsoluteLayout。使用Grid時,應用程式需要在Grid的子項上指定Row和Column值,并且在使用您自己設計的布局類時,可能還需要其他一些屬性。附加的可綁定屬性可以處理所有這些情況以及更多。
附加的可綁定屬性是一種通用機制,它允許将一個類定義的屬性存儲在另一個類的執行個體中。您可以使用名為CreateAttached和CreateAttachedReadOnly的BindableObject的靜态建立方法來定義自己的附加可綁定屬性。 (您将在第27章“自定義渲染器”中看到一個示例。)
附加屬性主要用于布局類。正如您将看到的,Grid定義了附加的可綁定屬性以指定每個子節點的行和列,而RelativeLayout也定義了附加的可綁定屬性。
之前您已經看到了AbsoluteLayout的Children集合定義的其他Add方法。這些實際上是使用這些附加的可綁定屬性實作的。調用absoluteLayout.Children.Add(view,rect);
像這樣實作:
AbsoluteLayout.SetLayoutBounds(view, rect);
absoluteLayout.Children.Add(view);
僅使用Point參數的Add調用僅設定子項的位置,并讓子項自身大小:
absoluteLayout.Children.Add(view, new Point(x, y));
這是使用相同的靜态AbsoluteLayout.SetLayoutBounds調用實作的,但對視圖的寬度和高度使用特殊常量:
AbsoluteLayout.SetLayoutBounds(view,
new Rectangle(x, y, AbsoluteLayout.AutoSize, AbsoluteLayout.AutoSize));
absoluteLayout.Children.Add(view);
您可以在自己的代碼中使用AbsoluteLayout.AutoSize常量。