概要
.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 つあります。
これらの新機能を有効にするには、$(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
が見つからなかった場合に呼び出されます。
ハンドラー must は null
を返します。その場合、 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# コンパイラ エラーがビルド時に生成されます。
カスタマイズ
生成された分離コード型alwaysはActivity.SetContentView()
をオーバーライドし、既定ではalwaysbase.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 属性は、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.myButton
はJava.Package.MyButton
型名前空間の 2 文字のコンポーネントを大文字にします。 たとえば、
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.
ただし、上記の試行が失敗した場合は、マップされていない型のウィジェットを使用するレイアウトを変更して、 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
ウィジェットの 2 つの異なる実装がサポートされています。
Android.App.Fragment
基本 Android システムに付属する "クラシック" フラグメントAndroidX.Fragment.App.Fragment
のXamarin.AndroidX.Fragment
NuGet パッケージ。
これらのクラスは相互に互換性がないため、レイアウト ファイル内の<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
ファイルを編集し、AndroidResource
をAndroidBoundLayout
に置き換えます。
<!-- This -->
<AndroidResource Include="Resources\layout\Main.axml" />
<!-- should become this -->
<AndroidBoundLayout Include="Resources\layout\Main.axml" />
処理
レイアウトは名前でグループ化され、同じ名前のテンプレートが 1 つのグループで構成される differentResource\layout*
ディレクトリから取得されます。 このようなグループは、単一のレイアウトであるかのように処理されます。 このような場合は、同じグループに属する異なるレイアウトで見つかった 2 つのウィジェット間で型の競合が発生する可能性があります。 このような場合、生成されたプロパティは正確なウィジェットの種類ではなく、"減衰" されます。 減衰は、次のアルゴリズムに従います。
競合するすべてのウィジェットが派生物
View
場合、プロパティの型はAndroid.Views.View
競合するすべての型が派生
Fragment
場合、プロパティの型は a0/&になります。Android.App.Fragment
競合するウィジェットに
View
とFragment
の両方が含まれている場合、プロパティの種類は になります。global::System.Object
生成されたコード
生成されたコードによるレイアウトの検索方法に関心がある場合は、ソリューション ディレクトリの obj\$(Configuration)\generated
フォルダーを参照してください。