概觀
在 .NET for Android 組建中,會處理 Android 資源,並透過產生的_Microsoft.Android.Resource.Designer.dll
元件公開 Android 識別符。
例如,假設檔案 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;}
}
}
傳統上,使用型別和 FindViewById<T>()
方法中的Resource
常數,即可在程序代碼中與資源互動:
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 命令行上的 MSBuild 屬性:True
dotnet build -p:AndroidGenerateLayoutBindings=true MyProject.csproj
或在您的 .csproj 檔案中:
<PropertyGroup>
<AndroidGenerateLayoutBindings>true</AndroidGenerateLayoutBindings>
</PropertyGroup>
繫結
系結是產生的類別,每個 Android 版面配置檔案各一個,其中包含配置檔內所有標識符的強型別屬性。 系結類型會產生至 global::Bindings
命名空間,其類型名稱會鏡像配置檔案的檔名。
系結類型是針對包含任何 Android 識別碼的所有版面配置檔案所建立。
指定 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);
}
}
系結的基底類型不是 .NET for Android 類別庫的一部分, Xamarin.Android.Design.LayoutBinding
而是隨附於來源窗體中的 .NET for Android,並在使用系結時自動包含在應用程式的組建中。
您可以圍繞實體建立 Activity
產生的系結類型,以允許強型別存取版面設定檔案內的識別碼:
// 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
建構,以強型別存取 View 或其子系內的資源識別碼:
var binding = new Binding.Main (some_view);
遺漏資源標識碼
系結類型的屬性仍會在其 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);
}
}
OnLayoutItemNotFound()
找不到 或 Fragment
的資源識別碼View
時,會叫用 方法。
處理程式必須傳回 ,在此情況下InvalidOperationException
,會擲回 ,或者最好傳回 null
View
Fragment
或 實例,其對應至處理程式的標識碼。 傳回的對象 必須是 符合對應 Binding 屬性類型的正確型別。 傳回的值會轉換成該類型,因此如果物件未正確輸入,則會擲回例外狀況。
程式碼後置
程序代碼後置牽涉到類別的partial
建置時間產生,其中包含配置檔案內所有標識符的強型別屬性。
程序代碼後置會建置系結機制,而要求配置檔案「加入」程序代碼後置產生,方法是使用新的 xamarin:classes
XML 屬性,這是 ;
要產生的完整類別名稱分隔清單。
指定 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;
}
}
這可讓您在版面配置中更「直覺」地使用資源識別碼:
// 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;
}
}
由於程式代碼後置依賴部分類別,部分類別的所有宣告都必須在其partial class
宣告中使用,否則會在建置時產生 CS0260 C# 編譯程序錯誤。
自訂
產生的程式代碼後置類型 一律 會 Activity.SetContentView()
覆寫 ,而且根據預設,它 一律 會呼叫 base.SetContentView()
、轉送參數。 如果這是不需要的,則應該覆寫其中OnSetContentView()
partial
一個方法,將 設定callBaseAfterReturn
為 :false
// 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 屬性
許多新的版面配置 XML 屬性控制系結和程式代碼後置行為,這些行為位於 xamarin
XML 命名空間 (xmlns:xamarin="http://schemas.xamarin.com/android/xamarin/tools"
)。
包括:
xamarin:classes
xamarin:classes
XML 屬性是程式代碼後置的一部分,用來指定應該產生哪些類型。
xamarin:classes
XML 屬性包含;
應該產生之完整類別名稱的分隔清單。
xamarin:managedType
設定 xamarin:managedType
屬性是用來明確指定 Managed 型別,以公開系結標識碼為 。 如果未指定,則會從宣告內容推斷型別,例如 <Button/>
將會產生 Android.Widget.Button
,並 <fragment/>
會產生 Android.App.Fragment
。
屬性 xamarin:managedType
允許更明確的類型宣告。
Managed 類型對應
根據它們來自的 Java 套件使用小工具名稱相當常見,同樣地,這類類型的 Managed .NET 名稱在 Managed Land 中會有不同的 (.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.
不過,如果上述嘗試失敗,您必須修改配置,其使用具有這類未對應的型別的小工具,將 XML 命名空間宣告新增 xamarin
至配置根元素,以及 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
隨附於基底 Android 系統的「傳統」片段AndroidX.Fragment.App.Fragment
在Xamarin.AndroidX.Fragment
NuGet 套件。
這些類別 彼此不相容 ,因此在為 <fragment>
版面配置檔案中的元素產生系結程式代碼時,必須特別小心。 如果項目沒有指定任何特定類型,則<fragment>
適用於 Android 的 .NET 必須選擇一個Fragment
實作作為預設實作。 系結程式代碼產生器會使用 $(AndroidFragmentType)
MSBuild 屬性用於該用途。 用戶可以覆寫 屬性,以指定與默認類型不同的類型。 屬性預設會設定為 Android.App.Fragment
,並由 AndroidX NuGet 套件覆寫。
如果產生的程式代碼未建置,則必須藉由指定有問題的片段的受管理類型來修改版面配置檔案。
程序代碼後置配置選取和處理
選取範圍
預設會停用程式代碼後置產生。 若要針對包含至少具有 //*/@android:id
屬性的單一元素之任何Resource\layout*
目錄中的所有配置啟用處理,請將 $(AndroidGenerateLayoutBindings)
msbuild 屬性設定為 True
msbuild 命令行上的任一個:
dotnet build -p:AndroidGenerateLayoutBindings=true MyProject.csproj
或在您的 .csproj 檔案中:
<PropertyGroup>
<AndroidGenerateLayoutBindings>true</AndroidGenerateLayoutBindings>
</PropertyGroup>
或者,您可以將程式代碼後置保留為全域停用,並只針對特定檔案加以啟用。 若要啟用特定.axml
檔案的程式代碼後置,請將檔案變更為具有的建置動作@(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
如果衝突的小工具同時
View
包含 和Fragment
,則屬性類型將會是global::System.Object
產生的程式碼
如果您有興趣了解產生的程式代碼如何尋找您的配置,請查看 obj\$(Configuration)\generated
方案目錄中的資料夾。