通过


概述

作为适用于 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 资源进行交互:

  1. 绑定
  2. 后台代码

若要启用这些新功能,请设置 $(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对应的ViewFragment实例。 返回的对象 必须是 与相应 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
  • 在内部表中查找许多硬编码类型。 目前,列表包括以下类型:

  • 硬编码命名空间 前缀的条带编号。 目前,列表包含以下前缀:

    • 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 控件实现:

这些类 彼此不兼容, 因此在为 <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*由单个组组成。 此类组的处理方式就像是单个布局一样。 在这种情况下,属于同一组的不同布局中的两个小组件可能会出现类型冲突。 在这种情况下,生成的属性将不能具有确切的小部件类型,而是退化后的类型。 衰减遵循以下算法:

  1. 如果所有冲突的小组件都是 View 派生组件,则属性类型将为 Android.Views.View

  2. 如果所有冲突类型都是 Fragment 派生类型,则属性类型将为 Android.App.Fragment

  3. 如果存在冲突的小组件同时包含ViewFragment,则属性类型将为global::System.Object

生成的代码

如果您对生成的代码在布局中是什么样子感兴趣,请查看解决方案目录中的 obj\$(Configuration)\generated 文件夹。