次の方法で共有


概要

.NET for Android ビルドの一環として、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;}
  }
}

従来リソースとの対話は、 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を返す場合、既定の動作は、プロパティがnullを返すのではなく、InvalidOperationExceptionをスローすることです。

この既定の動作は、インスタンス化時に生成されたバインディングにエラー ハンドラー デリゲートを渡すことによってオーバーライドできます。

// 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が見つからなかった場合に呼び出されます。

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

分離コード

分離コードには、レイアウト ファイル内のすべてのidsの厳密に型指定されたプロパティを含む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;
  }
}

これにより、レイアウト内でリソース 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;
  }
}

分離コードは部分クラスに依存するため、部分クラスの all 宣言 must 宣言で partial class を使用します。それ以外の場合は、 CS0260 C# コンパイラ エラーがビルド時に生成されます。

カスタマイズ

生成された分離コード型alwaysActivity.SetContentView()をオーバーライドし、既定ではalwaysbase.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 属性

多くの新しい Layout XML 属性は、 xamarin XML 名前空間 (xmlns:xamarin="http://schemas.xamarin.com/android/xamarin/tools") 内にあるバインディングと分離コードの動作を制御します。 これには以下が含まれます。

xamarin:classes

xamarin:classes XML 属性は、Code-Behind の一部として使用して、生成する必要がある型を指定します。

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 パッケージによってオーバーライドされます。

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

分離コード レイアウトの選択と処理

[選択]

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

dotnet build -p:AndroidGenerateLayoutBindings=true MyProject.csproj

または 、.csproj ファイルで次の手順を実行します。

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

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

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

処理

レイアウトは名前でグループ化され、同じ名前のテンプレートが 1 つのグループで構成される differentResource\layout* ディレクトリから取得されます。 このようなグループは、単一のレイアウトであるかのように処理されます。 このような場合は、同じグループに属する異なるレイアウトで見つかった 2 つのウィジェット間で型の競合が発生する可能性があります。 このような場合、生成されたプロパティは正確なウィジェットの種類ではなく、"減衰" されます。 減衰は、次のアルゴリズムに従います。

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

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

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

生成されたコード

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