作为适用于 Android 版本的 .NET 的一部分,将处理 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 命令行上将 MSBuild 属性设置为 True 以下任一:
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);
}
}
当找不到View的资源 ID 或Fragment的资源 ID 时,将调用OnLayoutItemNotFound()方法。
处理程序必须返回null,在这种情况下,InvalidOperationException将被引发;或者更好的是,返回与传递给处理程序的ID对应的View或Fragment实例。 返回的对象 必须是 与相应 Binding 属性的类型匹配的正确类型。 返回的值将强制转换为该类型,因此,如果对象未正确键入,将引发异常。
Code-Behind
Code-Behind 涉及在构建期间生成一个类 partial,该类包含布局文件中所有 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# 编译器错误。
自定义
生成的代码隐藏类型 始终 重写 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 属性
许多新的 Layout XML 属性用于控制绑定和代码隐藏行为,这些行为位于 xamarin XML 命名空间 (xmlns:xamarin="http://schemas.xamarin.com/android/xamarin/tools")。
这些包括:
xamarin:classes
xamarin:classes XML 属性用作代码隐藏的一部分,用于指定应生成哪些类型。
xamarin:classes XML 属性包含;应生成的完整类名的分隔列表。
xamarin:managedType
布局 xamarin:managedType 属性用于显式指定托管类型以公开绑定 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随基础 Android 系统一同发布的“经典”Fragment -
AndroidX.Fragment.App.Fragment在Xamarin.AndroidX.FragmentNuGet 包。
这些类 彼此不兼容, 因此在为 <fragment> 布局文件中的元素生成绑定代码时必须格外小心。 适用于 Android 的 .NET 必须选择一个<fragment>实现作为默认实现使用,如果Fragment元素没有指定任何特定类型(如托管代码或其他运行时)。 绑定代码生成器的使用
$(AndroidFragmentType) 用于该目的的 MSBuild 属性。 用户可以重写该属性,以指定与默认类型不同的类型。 该属性默认为设置为 Android.App.Fragment,由 AndroidX NuGet 包进行覆盖。
如果生成的代码无法编译,则必须通过指定片段的类型来修改布局文件。
后台代码布局选择和处理
选择
默认情况下禁用后台代码生成。 若要为任何包含至少一个带有//*/@android:id属性元素的Resource\layout*目录中的所有布局启用处理,可以通过 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" />
Processing
布局按名称分组,不同目录中的类似名称模板Resource\layout*由单个组组成。 此类组的处理方式就像是单个布局一样。 在这种情况下,属于同一组的不同布局中的两个小组件可能会出现类型冲突。 在这种情况下,生成的属性将不能具有确切的小部件类型,而是退化后的类型。 衰减遵循以下算法:
如果所有冲突的小组件都是
View派生组件,则属性类型将为Android.Views.View如果所有冲突类型都是
Fragment派生类型,则属性类型将为Android.App.Fragment如果存在冲突的小组件同时包含
View和Fragment,则属性类型将为global::System.Object
生成的代码
如果您对生成的代码在布局中是什么样子感兴趣,请查看解决方案目录中的 obj\$(Configuration)\generated 文件夹。