次の方法で共有


概要

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 リソースを操作する方法が 2 つあります。

  1. バインディング
  2. コードビハインド

これらの新機能を有効にするには、 $(AndroidGenerateLayoutBindings) MSBuild プロパティを True msbuild コマンドラインのいずれかで設定します。

dotnet build -p:AndroidGenerateLayoutBindings=true MyProject.csproj

または、.csproj ファイル内で。

<PropertyGroup>
    <AndroidGenerateLayoutBindings>true</AndroidGenerateLayoutBindings>
</PropertyGroup>

バインディング

バインディングは生成されたクラスであり、Android レイアウト ファイルごとに 1 つであり、レイアウト ファイル内のすべての 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 インスタンスを中心に構築することもできます。これにより、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が見つからなかった場合に呼び出されます。

ハンドラーは、を返すnull。その場合、InvalidOperationExceptionがスローされるか、できればハンドラーに渡された ID に対応するViewまたはFragmentインスタンスを返します。 返されるオブジェクトは、対応する Binding プロパティの型に一致する正しい型である 必要があります 。 戻り値はその型にキャストされるため、オブジェクトが正しく型指定されていない場合は例外がスローされます。

コードビハインド

Code-Behind には、レイアウト ファイル内のすべての partial の厳密に型指定されたプロパティを含む クラスのビルド時の生成が含まれます。

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 メソッド のいずれかをオーバーライドし、 callBaseAfterReturnfalseに設定する必要があります。

// 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") 内にあるバインディングと Code-Behind 動作を制御します。 これらには次のものが含まれます。

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.myButtonJava.Package.MyButton

  • 型名前空間の 2 文字のコンポーネントを大文字にします。 たとえば、 android.os.SomeTypeAndroid.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.LogFragmentcommonsamplelibrary.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 ウィジェットの 2 つの異なる実装がサポートされています。

これらの クラスは互 いに互換性がないため、レイアウト ファイル内の <fragment> 要素のバインド コードを生成するときは、特別な注意を払う必要があります。 .NET for Android では、Fragment要素に特定の型 (マネージドまたはそれ以外の方法) が指定されていない場合に使用する既定の実装として、1 つの<fragment>実装を選択する必要があります。 バインディング コード ジェネレーターでは、次のコードが使用されます。 $(AndroidFragmentType) その目的のための MSBuild プロパティ。 プロパティは、既定の型とは異なる型を指定するために、ユーザーによってオーバーライドできます。 このプロパティは既定で Android.App.Fragment に設定され、AndroidX NuGet パッケージによってオーバーライドされます。

生成されたコードがビルドされない場合は、問題のフラグメントの管理型を指定してレイアウト ファイルを修正する必要があります。

コードビハインドのレイアウト選択と処理

[選択]

既定では、コードビハインドの生成は無効になっています。 Resource\layout*属性を持つ少なくとも 1 つの要素を含む//*/@android:id ディレクトリ内のすべてのレイアウトの処理を有効にするには、msbuild コマンド ラインで $(AndroidGenerateLayoutBindings) MSBuild プロパティをTrueに設定します。

dotnet build -p:AndroidGenerateLayoutBindings=true MyProject.csproj

または、.csproj ファイルで:

<PropertyGroup>
  <AndroidGenerateLayoutBindings>true</AndroidGenerateLayoutBindings>
</PropertyGroup>

または、コード ビハインドをグローバルに無効のままにして、特定のファイルに対してのみ有効にすることもできます。 特定の .axml ファイルに対して Code-Behind を有効にするには、ファイルのビルド アクションを変更します。 @(AndroidBoundLayout) .csprojファイルを編集し、AndroidResourceAndroidBoundLayoutに置き換えます。

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

処理中

レイアウトは名前でグループ化され、同じ名前のテンプレートが異なるディレクトリから1つのグループを構成します。 このようなグループは、単一のレイアウトであるかのように処理されます。 このような場合は、同じグループに属する異なるレイアウトで見つかった 2 つのウィジェット間で型の競合が発生する可能性があります。 このような場合、生成されたプロパティは正確なウィジェットタイプを持つことができず、代わりに「劣化した」ものになります。 減衰は、次のアルゴリズムに従います。

  1. 競合するすべてのウィジェットがViewの派生物の場合、プロパティの型はAndroid.Views.Viewとなります。

  2. 競合するすべての型が Fragment 派生である場合、プロパティの型は Android.App.Fragment になります。

  3. 競合するウィジェットに ViewFragmentの両方が含まれている場合、プロパティの種類は になります。 global::System.Object

生成されたコード

生成されたコードによるレイアウトの検索方法に関心がある場合は、ソリューション ディレクトリの obj\$(Configuration)\generated フォルダーを参照してください。