共用方式為


概觀

在 .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 資源互動:

  1. 繫結
  2. 程序代碼後置

若要啟用這些新功能,請設定 $(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,會擲回 ,或者最好傳回 nullView 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
  • 查閱內部數據表中的許多硬式編碼類型。 清單目前包含下列類型:

  • 硬式編碼命名空間 前置詞的等量編號。 清單目前包含下列前置詞:

    • 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

這些類別 彼此不相容 ,因此在為 <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 檔案,並將 取代 AndroidResourceAndroidBoundLayout

<!-- This -->
<AndroidResource Include="Resources\layout\Main.axml" />
<!-- should become this -->
<AndroidBoundLayout Include="Resources\layout\Main.axml" />

正在處理

版面配置會依名稱分組,具有來自 不同目錄的Resource\layout* 類似名稱範本,由單一群組組成。 這類群組的處理方式就像是單一配置一樣。 在這種情況下,在屬於相同群組的不同版面配置中,兩個小工具之間可能會發生類型衝突。 在這種情況下,產生的屬性將無法擁有確切的小工具類型,而是「衰敗」屬性。 衰變遵循下列演算法:

  1. 如果所有衝突的小工具都是 View 衍生工具,屬性類型將會是 Android.Views.View

  2. 如果所有衝突的類型都是 Fragment 衍生專案,屬性類型將會是 Android.App.Fragment

  3. 如果衝突的小工具同時 View 包含 和 Fragment,則屬性類型將會是 global::System.Object

產生的程式碼

如果您有興趣了解產生的程式代碼如何尋找您的配置,請查看 obj\$(Configuration)\generated 方案目錄中的資料夾。