概述
作为适用于 Android 版本的 .NET 的一部分,将处理 Android 资源,并通过生成的_Microsoft.Android.Resource.Designer.dll
程序集公开 Android ID。
例如,给定包含内容的文件 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 布局文件一个,其中包含布局文件中所有 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);
}
}
绑定的基类型不是适用于 Android 类库的 .NET 的一部分,Xamarin.Android.Design.LayoutBinding
而是随源形式的 .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);
}
}
OnLayoutItemNotFound()
找不到某个View
资源 ID 或Fragment
找不到资源 ID 时,将调用该方法。
处理程序必须返回,null
在这种情况下,InvalidOperationException
将引发或最好返回View
与传递给处理程序的 ID 对应的或Fragment
实例。 返回的对象 必须是 与相应 Binding 属性的类型匹配的正确类型。 返回的值将强制转换为该类型,因此,如果对象未正确键入,将引发异常。
代码隐藏
代码隐藏涉及生成类partial
的生成时间,该类包含布局文件中所有 ID 的强类型属性。
代码隐藏在绑定机制之上生成,同时要求布局文件使用新的 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;
}
}
这允许在布局中更“直观”地使用资源 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 属性
许多新的布局 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.
但是,如果上述尝试失败,则需要修改使用具有此类未映射类型的小组件将 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>
布局文件中的元素生成绑定代码时必须格外小心。 如果元素没有指定任何特定类型(托管或其他类型),则适用于 Android 的 <fragment>
.NET 必须选择一个Fragment
实现作为要使用的默认实现。 绑定代码生成器使用 $(AndroidFragmentType)
用于该目的的 MSBuild 属性。 用户可以重写该属性,以指定与默认类型不同的类型。 该属性默认设置为 Android.App.Fragment
,并由 AndroidX NuGet 包重写。
如果生成的代码未生成,则必须通过指定相关片段的托管类型来修改布局文件。
代码隐藏布局选择和处理
选择
默认情况下禁用代码隐藏生成。 若要为包含至少具有该属性的单个元素//*/@android:id
的任何Resource\layout*
目录中的所有布局启用处理,请将 MSBuild 属性设置为 $(AndroidGenerateLayoutBindings)
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" />
Processing
布局按名称分组,不同目录中的类似名称模板Resource\layout*
由单个组组成。 此类组的处理方式就像是单个布局一样。 在这种情况下,在属于同一组的不同布局中,两个小组件之间存在类型冲突。 在这种情况下,生成的属性将不能具有确切的小组件类型,而是“衰减”小组件类型。 衰减遵循以下算法:
如果所有冲突的小组件都是
View
派生组件,则属性类型将为Android.Views.View
如果所有冲突类型都是
Fragment
派生类型,则属性类型将为Android.App.Fragment
如果冲突的小组件同时包含 a
View
和 aFragment
,则属性类型将为global::System.Object
生成的代码
如果对生成的代码如何查找布局感兴趣,请查看 obj\$(Configuration)\generated
解决方案目录中的文件夹。