作為 .NET for Android 建置的一部分,Android 資源會被處理,透過產生的組合語言公開 _Microsoft.Android.Resource.Designer.dll。
例如,給定檔案內容 Reources\layout\Main.axml 為:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android">
<Button android:id="@+id/myButton" />
<fragment
android:id="@+id/log_fragment"
android:name="commonsamplelibrary.LogFragment"
/>
<fragment
android:id="@+id/secondary_log_fragment"
android:name="CommonSampleLibrary.LogFragment"
/>
</LinearLayout>
接著在建置時,建立一個類似於_Microsoft.Android.Resource.Designer.dll 內容的組件:
namespace _Microsoft.Android.Resource.Designer;
partial class Resource {
partial class Id {
public static int myButton {get;}
public static int log_fragment {get;}
public static int secondary_log_fragment {get;}
}
partial class Layout {
public static int Main {get;}
}
}
傳統上,與資源的互動通常在程式碼中完成,並使用Resource的型別和FindViewById<T>()方法的常數:
partial class MainActivity : Activity {
protected override void OnCreate (Bundle savedInstanceState)
{
base.OnCreate (savedInstanceState);
SetContentView (Resource.Layout.Main);
Button button = FindViewById<Button>(Resource.Id.myButton);
button.Click += delegate {
button.Text = $"{count++} clicks!";
};
}
}
從 Xamarin.Android 8.4 開始,使用 C# 時有兩種額外方式可以與 Android 資源互動:
要啟用這些新功能,請設定
$(AndroidGenerateLayoutBindings) MSBuild 屬性可設定為 True msbuild 命令列中的任一:
dotnet build -p:AndroidGenerateLayoutBindings=true MyProject.csproj
或者在你的 .csproj 檔案中:
<PropertyGroup>
<AndroidGenerateLayoutBindings>true</AndroidGenerateLayoutBindings>
</PropertyGroup>
繫結
綁定是針對每個 Android 佈局檔案 生成的類別,當中包含該佈局檔案中所有 ID 的強型別屬性。 綁定型別會被產生到 global::Bindings 命名空間中,型別名稱會鏡像到版面檔案的檔名。
所有包含任何 Android ID 的版面檔案都會建立綁定類型。
給定 Android 版面配置檔案 Resources\layout\Main.axml:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xamarin="http://schemas.xamarin.com/android/xamarin/tools">
<Button android:id="@+id/myButton" />
<fragment
android:id="@+id/fragmentWithExplicitManagedType"
android:name="commonsamplelibrary.LogFragment"
xamarin:managedType="CommonSampleLibrary.LogFragment"
/>
<fragment
android:id="@+id/fragmentWithInferredType"
android:name="CommonSampleLibrary.LogFragment"
/>
</LinearLayout>
接著會產生以下類型:
// Generated code
namespace Binding {
sealed class Main : global::Xamarin.Android.Design.LayoutBinding {
[global::Android.Runtime.PreserveAttribute (Conditional=true)]
public Main (
global::Android.App.Activity client,
global::Xamarin.Android.Design.OnLayoutItemNotFoundHandler itemNotFoundHandler = null)
: base (client, itemNotFoundHandler) {}
[global::Android.Runtime.PreserveAttribute (Conditional=true)]
public Main (
global::Android.Views.View client,
global::Xamarin.Android.Design.OnLayoutItemNotFoundHandler itemNotFoundHandler = null)
: base (client, itemNotFoundHandler) {}
Button __myButton;
public Button myButton => FindView (global::Xamarin.Android.Tests.CodeBehindFew.Resource.Id.myButton, ref __myButton);
CommonSampleLibrary.LogFragment __fragmentWithExplicitManagedType;
public CommonSampleLibrary.LogFragment fragmentWithExplicitManagedType =>
FindFragment (global::Xamarin.Android.Tests.CodeBehindFew.Resource.Id.fragmentWithExplicitManagedType, __fragmentWithExplicitManagedType, ref __fragmentWithExplicitManagedType);
global::Android.App.Fragment __fragmentWithInferredType;
public global::Android.App.Fragment fragmentWithInferredType =>
FindFragment (global::Xamarin.Android.Tests.CodeBehindFew.Resource.Id.fragmentWithInferredType, __fragmentWithInferredType, ref __fragmentWithInferredType);
}
}
綁定的基類型 Xamarin.Android.Design.LayoutBinding並不屬於 .NET for Android 類別庫的一部分,而是以原始碼形式隨 .NET for Android 一同提供,當使用綁定時會自動包含在應用程式的建置中。
產生的繫結類型可圍繞 Activity 實例建立,允許對佈局檔案中的 ID 進行強型別存取。
// User-written code
partial class MainActivity : Activity {
protected override void OnCreate (Bundle savedInstanceState)
{
base.OnCreate (savedInstanceState);
SetContentView (Resource.Layout.Main);
var binding = new Binding.Main (this);
Button button = binding.myButton;
button.Click += delegate {
button.Text = $"{count++} clicks!";
};
}
}
綁定型別也可以圍繞 View 實例構建,允許強型別存取檢視圖或其子節點 中的 資源 ID:
var binding = new Binding.Main (some_view);
缺少資源 ID
綁定類型的屬性在實作中仍然使用FindViewById<T>()。 若 FindViewById<T>() 返回 null,則預設行為是該屬性拋出 , InvalidOperationException 而非返回 null。
此預設行為可透過將錯誤處理代理傳遞給生成的綁定來覆寫:
// User-written code
partial class MainActivity : Activity {
Java.Lang.Object? OnLayoutItemNotFound (int resourceId, Type expectedViewType)
{
// Find and return the View or Fragment identified by `resourceId`
// or `null` if unknown
return null;
}
protected override void OnCreate (Bundle savedInstanceState)
{
base.OnCreate (savedInstanceState);
SetContentView (Resource.Layout.Main);
var binding = new Binding.Main (this, OnLayoutItemNotFound);
}
}
當找不到 a OnLayoutItemNotFound() 或 a View 的資源 ID 時,會呼叫此Fragment方法。
處理器 必須 回傳 null,在這種情況下會拋出 InvalidOperationException,或更佳的做法是回傳與傳給處理器的 ID 對應的 View 或 Fragment 實例。 回傳的物件 必須 是正確型別,且與相應的 Binding 屬性類型相符。 回傳的值會被轉換成該類型,因此如果物件未被正確類型化,會拋出一個例外狀況。
Code-Behind
Code-Behind 涉及建置時間產生 partial 一個類別,該類別包含 layout 檔案中所有 id 的強型別屬性。
Code-Behind 建立在綁定機制之上,並且需要版面檔案透過新的 xamarin:classes XML 屬性「選擇加入」以進行 Code-Behind 產生。該屬性是一個以 ; 分隔的完整類別名稱清單。
給定 Android 版面配置檔案 Resources\layout\Main.axml:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xamarin="http://schemas.xamarin.com/android/xamarin/tools"
xamarin:classes="Example.MainActivity">
<Button android:id="@+id/myButton" />
<fragment
android:id="@+id/fragmentWithExplicitManagedType"
android:name="commonsamplelibrary.LogFragment"
xamarin:managedType="CommonSampleLibrary.LogFragment"
/>
<fragment
android:id="@+id/fragmentWithInferredType"
android:name="CommonSampleLibrary.LogFragment"
/>
</LinearLayout>
在建置時將產生以下類型:
// Generated code
namespace Example {
partial class MainActivity {
Binding.Main __layout_binding;
public override void SetContentView (global::Android.Views.View view);
void SetContentView (global::Android.Views.View view,
global::Xamarin.Android.Design.LayoutBinding.OnLayoutItemNotFoundHandler onLayoutItemNotFound);
public override void SetContentView (global::Android.Views.View view, global::Android.Views.ViewGroup.LayoutParams @params);
void SetContentView (global::Android.Views.View view, global::Android.Views.ViewGroup.LayoutParams @params,
global::Xamarin.Android.Design.LayoutBinding.OnLayoutItemNotFoundHandler onLayoutItemNotFound);
public override void SetContentView (int layoutResID);
void SetContentView (int layoutResID,
global::Xamarin.Android.Design.LayoutBinding.OnLayoutItemNotFoundHandler onLayoutItemNotFound);
partial void OnSetContentView (global::Android.Views.View view, ref bool callBaseAfterReturn);
partial void OnSetContentView (global::Android.Views.View view, global::Android.Views.ViewGroup.LayoutParams @params, ref bool callBaseAfterReturn);
partial void OnSetContentView (int layoutResID, ref bool callBaseAfterReturn);
public Button myButton => __layout_binding?.myButton;
public CommonSampleLibrary.LogFragment fragmentWithExplicitManagedType => __layout_binding?.fragmentWithExplicitManagedType;
public global::Android.App.Fragment fragmentWithInferredType => __layout_binding?.fragmentWithInferredType;
}
}
這使得在版面中更「直覺」地使用資源 ID:
// User-written code
partial class MainActivity : Activity {
protected override void OnCreate (Bundle savedInstanceState)
{
base.OnCreate (savedInstanceState);
SetContentView (Resource.Layout.Main);
myButton.Click += delegate {
button.Text = $"{count++} clicks!";
};
}
}
OnLayoutItemNotFound錯誤處理程式可作為活動中所使用過載SetContentView的最後一個參數傳遞:
// User-written code
partial class MainActivity : Activity {
protected override void OnCreate (Bundle savedInstanceState)
{
base.OnCreate (savedInstanceState);
SetContentView (Resource.Layout.Main, OnLayoutItemNotFound);
}
Java.Lang.Object? OnLayoutItemNotFound (int resourceId, Type expectedViewType)
{
// Find and return the View or Fragment identified by `resourceId`
// or `null` if unknown
return null;
}
}
由於 Code-Behind 依賴部分類別,所有部分類別的宣告必須partial class在宣告中使用,否則在建置時會產生 CS0260 C# 編譯錯誤。
自訂
產生的 Code Behind 型別 總是 覆蓋 Activity.SetContentView(),且在預設下 總是 呼叫 base.SetContentView(),並轉發參數。 若不希望如此,則應重寫其中一個OnSetContentView()partial方法,設為callBaseAfterReturnfalse:
// Generated code
namespace Example
{
partial class MainActivity {
partial void OnSetContentView (global::Android.Views.View view, ref bool callBaseAfterReturn);
partial void OnSetContentView (global::Android.Views.View view, global::Android.Views.ViewGroup.LayoutParams @params, ref bool callBaseAfterReturn);
partial void OnSetContentView (int layoutResID, ref bool callBaseAfterReturn);
}
}
範例生成程式碼
// Generated code
namespace Example
{
partial class MainActivity {
Binding.Main? __layout_binding;
public override void SetContentView (global::Android.Views.View view)
{
__layout_binding = new global::Binding.Main (view);
bool callBase = true;
OnSetContentView (view, ref callBase);
if (callBase) {
base.SetContentView (view);
}
}
void SetContentView (global::Android.Views.View view, global::Xamarin.Android.Design.LayoutBinding.OnLayoutItemNotFoundHandler onLayoutItemNotFound)
{
__layout_binding = new global::Binding.Main (view, onLayoutItemNotFound);
bool callBase = true;
OnSetContentView (view, ref callBase);
if (callBase) {
base.SetContentView (view);
}
}
public override void SetContentView (global::Android.Views.View view, global::Android.Views.ViewGroup.LayoutParams @params)
{
__layout_binding = new global::Binding.Main (view);
bool callBase = true;
OnSetContentView (view, @params, ref callBase);
if (callBase) {
base.SetContentView (view, @params);
}
}
void SetContentView (global::Android.Views.View view, global::Android.Views.ViewGroup.LayoutParams @params, global::Xamarin.Android.Design.LayoutBinding.OnLayoutItemNotFoundHandler onLayoutItemNotFound)
{
__layout_binding = new global::Binding.Main (view, onLayoutItemNotFound);
bool callBase = true;
OnSetContentView (view, @params, ref callBase);
if (callBase) {
base.SetContentView (view, @params);
}
}
public override void SetContentView (int layoutResID)
{
__layout_binding = new global::Binding.Main (this);
bool callBase = true;
OnSetContentView (layoutResID, ref callBase);
if (callBase) {
base.SetContentView (layoutResID);
}
}
void SetContentView (int layoutResID, global::Xamarin.Android.Design.LayoutBinding.OnLayoutItemNotFoundHandler onLayoutItemNotFound)
{
__layout_binding = new global::Binding.Main (this, onLayoutItemNotFound);
bool callBase = true;
OnSetContentView (layoutResID, ref callBase);
if (callBase) {
base.SetContentView (layoutResID);
}
}
partial void OnSetContentView (global::Android.Views.View view, ref bool callBaseAfterReturn);
partial void OnSetContentView (global::Android.Views.View view, global::Android.Views.ViewGroup.LayoutParams @params, ref bool callBaseAfterReturn);
partial void OnSetContentView (int layoutResID, ref bool callBaseAfterReturn);
public Button myButton => __layout_binding?.myButton;
public CommonSampleLibrary.LogFragment fragmentWithExplicitManagedType => __layout_binding?.fragmentWithExplicitManagedType;
public global::Android.App.Fragment fragmentWithInferredType => __layout_binding?.fragmentWithInferredType;
}
}
版面 XML 屬性
許多新的 Layout XML 屬性控制綁定與後端代碼行為,這些屬性位於 xamarin XML 命名空間(xmlns:xamarin="http://schemas.xamarin.com/android/xamarin/tools")。
這些包括:
xamarin:classes
xamarin:classes XML 屬性作為 Code-Behind 的一部分,用來指定應產生哪些類型。
xamarin:classes XML 屬性包含以;分隔的完整類別名稱清單,應予以產生。
xamarin:managedType
xamarin:managedType layout 屬性用來明確指定要暴露綁定 ID 的受管理型別。 若未指定,該型別將從宣告上下文推斷,例如 <Button/> 會產生 Android.Widget.Button,且 <fragment/> 會產生 Android.App.Fragment。
屬性 xamarin:managedType 允許更明確的型別宣告。
管理的類型映射
根據 Java 套件的來源使用小部件名稱是相當常見的,且同樣常見的是,這類型在受管理環境中的 .NET 名稱會有不同的(.NET 樣式)名稱。 碼產生器可以執行一些非常簡單的調整來嘗試匹配碼數,例如:
把型別命名空間和名稱的所有元件都大寫。 例如
java.package.myButton,將變成Java.Package.MyButton將型別命名空間中的兩字母組件大寫。 例如
android.os.SomeType,將變成Android.OS.SomeType查閱一些有已知映射的硬編碼命名空間。 目前列表包含以下地圖:
-
android.view->Android.Views -
com.actionbarsherlock->ABSherlock -
com.actionbarsherlock.widget->ABSherlock.Widget -
com.actionbarsherlock.view->ABSherlock.View -
com.actionbarsherlock.app->ABSherlock.App
-
在內部資料表中查找多種硬編碼型別。 目前清單包含以下類型:
-
WebView->Android.Webkit.WebView
-
去除硬編碼命名空間 前綴的數量。 目前名單包含以下前綴:
com.google.
然而,如果上述嘗試失敗,你需要修改使用該未映射型別的小工具的版面,將 xamarin XML 命名空間宣告加入版面的根元素,並將 xamarin:managedType 加入需要映射的元素。 例如:
<fragment
android:id="@+id/log_fragment"
android:name="commonsamplelibrary.LogFragment"
xamarin:managedType="CommonSampleLibrary.LogFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
將使用 CommonSampleLibrary.LogFragment 類型作為本機類型 commonsamplelibrary.LogFragment。
你可以直接用受管理名稱命名型別,避免新增 XML 命名空間宣告和 xamarin:managedType 屬性,例如上述片段可以重新宣告如下:
<fragment
android:name="CommonSampleLibrary.LogFragment"
android:id="@+id/secondary_log_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
碎片:特殊案例
Android 生態系統目前支援兩種不同的 Fragment 小工具實現:
-
Android.App.Fragment「經典」Fragment 隨基礎 Android 系統一同出貨 -
AndroidX.Fragment.App.Fragment, 在Xamarin.AndroidX.FragmentNuGet 套件。
這些類別 彼此不 相容,因此在產生 layout 檔案中元素的綁定程式碼 <fragment> 時必須特別小心。 如果該Fragment元素沒有指定特定類型(或受管理型別),.NET for Android 必須選擇一個<fragment>實作作為預設實作。 使用綁定碼產生器
$(AndroidFragmentType) MSBuild 的屬性是為了這個目的。 使用者可以覆寫該屬性,以指定與預設不同的類型。 該屬性預設值為Android.App.Fragment,並被AndroidX NuGet套件覆蓋。
若產生的程式碼無法建置,則必須修改版面檔案,指定該片段的管理型態。
程式碼後方版面選擇與處理
選擇
預設情況下,程式碼背後產生是被禁用的。 若要啟用任何目錄中至少包含該Resource\layout*屬性元素的版面配置//*/@android:id的處理,請在 msbuild 命令列中將 MSBuild 屬性設$(AndroidGenerateLayoutBindings)為True以下任一:
dotnet build -p:AndroidGenerateLayoutBindings=true MyProject.csproj
或者在你的 .csproj 檔案中:
<PropertyGroup>
<AndroidGenerateLayoutBindings>true</AndroidGenerateLayoutBindings>
</PropertyGroup>
或者,你也可以在全域範圍內禁用「程式碼後置」功能,僅對特定檔案啟用它。 要啟用特定 .axml 檔案的 Code-Behind,請將該檔案的 編譯動作 改為
@(AndroidBoundLayout) 透過編輯你的 .csproj 檔案並替換 AndroidResource 為 AndroidBoundLayout:
<!-- This -->
<AndroidResource Include="Resources\layout\Main.axml" />
<!-- should become this -->
<AndroidBoundLayout Include="Resources\layout\Main.axml" />
加工業
版面依名稱分組,來自 不同Resource\layout* 目錄的同名範本組成一個群組。 這些群組被當作單一佈局來處理。 在這種情況下,不同配置中屬於同一群組的兩個小工具之間可能會產生型別衝突。 在這種情況下,產生的屬性無法擁有完全相同的元件類型,而是「衰減型」的類型。 衰減演算法如下:
若所有衝突的小工具皆為
View衍生函數,則屬性類型為Android.Views.View若所有衝突類型皆為
Fragment衍生型,則性質類型為Android.App.Fragment若衝突的元件同時包含 a
View與Fragment,則屬性型態為global::System.Object
產生的程序代碼
如果你對生成的程式碼如何對應你的版面配置感興趣,請查看解決方案目錄中的 obj\$(Configuration)\generated 資料夾。