Vue d’ensemble
Dans le cadre de la build .NET pour Android, les ressources Android sont traitées, exposant des ID Android via un assembly généré _Microsoft.Android.Resource.Designer.dll
.
Par exemple, étant donné le fichier Reources\layout\Main.axml
avec le contenu :
<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>
Ensuite, pendant la génération, un assembly avec du _Microsoft.Android.Resource.Designer.dll
contenu similaire à :
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;}
}
}
Traditionnellement, l’interaction avec les ressources est effectuée dans le Resource
code, à l’aide des constantes du type et de la FindViewById<T>()
méthode :
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!";
};
}
}
À compter de Xamarin.Android 8.4, il existe deux façons supplémentaires d’interagir avec les ressources Android lors de l’utilisation de C# :
Pour activer ces nouvelles fonctionnalités, définissez la $(AndroidGenerateLayoutBindings)
Propriété MSBuild sur True
la ligne de commande msbuild :
dotnet build -p:AndroidGenerateLayoutBindings=true MyProject.csproj
ou dans votre fichier .csproj :
<PropertyGroup>
<AndroidGenerateLayoutBindings>true</AndroidGenerateLayoutBindings>
</PropertyGroup>
Liaisons
Une liaison est une classe générée, une par fichier de disposition Android, qui contient des propriétés fortement typées pour tous les ID dans le fichier de disposition. Les types de liaison sont générés dans l’espace global::Bindings
de noms, avec des noms de types qui reflètent le nom de fichier de disposition.
Les types de liaison sont créés pour tous les fichiers de disposition qui contiennent des ID Android.
Étant donné le fichier Resources\layout\Main.axml
de disposition Android :
<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>
ensuite, le type suivant sera généré :
// 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);
}
}
Le type de base de la liaison ne Xamarin.Android.Design.LayoutBinding
fait pas partie de la bibliothèque de classes .NET pour Android, mais plutôt fourni avec .NET pour Android sous forme source et inclus automatiquement dans la build de l’application chaque fois que des liaisons sont utilisées.
Le type de liaison généré peut être créé autour Activity
des instances, ce qui permet un accès fortement typé aux ID dans le fichier de disposition :
// 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!";
};
}
}
Les types de liaison peuvent également être construits autour View
des instances, ce qui permet un accès fortement typé aux ID de ressource au sein de la vue ou de ses enfants :
var binding = new Binding.Main (some_view);
ID de ressource manquants
Les propriétés sur les types de liaison sont toujours utilisées FindViewById<T>()
dans leur implémentation. Si FindViewById<T>()
elle est retournée null
, le comportement par défaut est que la propriété lève un InvalidOperationException
élément au lieu de retourner null
.
Ce comportement par défaut peut être substitué en transmettant un délégué de gestionnaire d’erreurs à la liaison générée sur son instanciation :
// 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);
}
}
La OnLayoutItemNotFound()
méthode est appelée lorsqu’un ID de ressource pour un View
ou un objet Fragment
est introuvable.
Le gestionnaire doit retourner soit null
, auquel cas le InvalidOperationException
sera levée ou, de préférence, renvoyer l’instance View
Fragment
qui correspond à l’ID passé au gestionnaire. L’objet retourné doit être du type correct correspondant au type de la propriété Binding correspondante. La valeur retournée est convertie en ce type. Par conséquent, si l’objet n’est pas correctement typé, une exception est levée.
code-behind
Code-Behind implique la génération au moment de la génération d’une partial
classe qui contient des propriétés fortement typées pour tous les ID dans le fichier de disposition.
Code-Behind s’appuie sur le mécanisme de liaison, tout en exigeant que les fichiers de disposition « opt-in » à la génération Code-Behind utilisent le nouvel xamarin:classes
attribut XML, qui est une ;
liste séparée par -des noms de classes complètes à générer.
Étant donné le fichier Resources\layout\Main.axml
de disposition Android :
<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>
au moment de la génération, le type suivant est généré :
// 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;
}
}
Cela permet une utilisation plus « intuitive » des ID de ressources dans la disposition :
// 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!";
};
}
}
Le OnLayoutItemNotFound
gestionnaire d’erreurs peut être passé en tant que dernier paramètre de la surcharge de SetContentView
l’activité à l’aide de :
// 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;
}
}
Comme Code-Behind s’appuie sur des classes partielles, toutes les déclarations d’une classe partielle doivent être utilisées partial class
dans leur déclaration, sinon une erreur du compilateur CS0260 C# sera générée au moment de la génération.
Personnalisation
Le type Code Behind généré remplace Activity.SetContentView()
toujours , et par défaut, il appelle base.SetContentView()
toujours , en transférant les paramètres. Si ce n’est pas souhaité, l’une des OnSetContentView()
partial
méthodes doit être remplacée, en définissant callBaseAfterReturn
sur :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);
}
}
Exemple de code généré
// 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;
}
}
Attributs XML de disposition
De nombreux nouveaux attributs XML de disposition contrôlent le comportement Liaison et Code-Behind, qui se trouvent dans l’espace xamarin
de noms XML (xmlns:xamarin="http://schemas.xamarin.com/android/xamarin/tools"
).
Il s’agit notamment des paramètres suivants :
xamarin:classes
L’attribut xamarin:classes
XML est utilisé dans le cadre de Code-Behind pour spécifier les types à générer.
L’attribut xamarin:classes
XML contient une ;
liste séparée par -des noms de classes complètes qui doivent être générés.
xamarin:managedType
L’attribut xamarin:managedType
de disposition est utilisé pour spécifier explicitement le type managé pour exposer l’ID lié comme. S’il n’est pas spécifié, le type est déduit du contexte déclarant, par exemple <Button/>
, entraîne un Android.Widget.Button
résultat et <fragment/>
entraîne un Android.App.Fragment
.
L’attribut xamarin:managedType
permet d’obtenir des déclarations de type plus explicites.
Mappage de type managé
Il est très courant d’utiliser des noms de widgets basés sur le package Java dont ils proviennent et, aussi souvent, le nom .NET managé de ce type aura un autre nom (style .NET) dans le terrain managé. Le générateur de code peut effectuer plusieurs ajustements très simples pour essayer de faire correspondre le code, par exemple :
Mettre en majuscule tous les composants de l’espace de noms et du nom de type. Par exemple
java.package.myButton
, devenirJava.Package.MyButton
Mettre en majuscule les composants à deux lettres de l’espace de noms de type. Par exemple
android.os.SomeType
, devenirAndroid.OS.SomeType
Recherchez un certain nombre d’espaces de noms codés en dur qui ont des mappages connus. Actuellement, la liste inclut les mappages suivants :
android.view
->Android.Views
com.actionbarsherlock
->ABSherlock
com.actionbarsherlock.widget
->ABSherlock.Widget
com.actionbarsherlock.view
->ABSherlock.View
com.actionbarsherlock.app
->ABSherlock.App
Recherchez un certain nombre de types codés en dur dans des tables internes. Actuellement, la liste comprend les types suivants :
WebView
->Android.Webkit.WebView
Nombre de préfixes d’espace de noms codés en dur. Actuellement, la liste inclut les préfixes suivants :
com.google.
Si, toutefois, les tentatives ci-dessus échouent, vous devez modifier la disposition qui utilise un widget avec un tel type non mappé pour ajouter la xamarin
déclaration d’espace de noms XML à l’élément racine de la disposition et à xamarin:managedType
l’élément nécessitant le mappage. Exemple :
<fragment
android:id="@+id/log_fragment"
android:name="commonsamplelibrary.LogFragment"
xamarin:managedType="CommonSampleLibrary.LogFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
Utilise le CommonSampleLibrary.LogFragment
type pour le type commonsamplelibrary.LogFragment
natif.
Vous pouvez éviter d’ajouter la déclaration d’espace de noms XML et l’attribut xamarin:managedType
en nommant simplement le type à l’aide de son nom managé, par exemple le fragment ci-dessus peut être redéclaré comme suit :
<fragment
android:name="CommonSampleLibrary.LogFragment"
android:id="@+id/secondary_log_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
Fragments : un cas spécial
L’écosystème Android prend actuellement en charge deux implémentations distinctes du Fragment
widget :
Android.App.Fragment
Fragment « classique » fourni avec le système Android de baseAndroidX.Fragment.App.Fragment
, dans leXamarin.AndroidX.Fragment
Package NuGet.
Ces classes ne sont pas compatibles les unes avec les autres, et un soin particulier doit donc être pris lors de la génération de code de liaison pour <fragment>
les éléments dans les fichiers de disposition. .NET pour Android doit choisir une Fragment
implémentation comme implémentation par défaut à utiliser si l’élément <fragment>
n’a pas de type spécifique (géré ou autre) spécifié. Le générateur de code de liaison utilise le $(AndroidFragmentType)
Propriété MSBuild à cet effet. La propriété peut être remplacée par l’utilisateur pour spécifier un type différent de celui par défaut. La propriété est définie Android.App.Fragment
par défaut et remplacée par les packages NuGet AndroidX.
Si le code généré ne génère pas, le fichier de disposition doit être modifié en spécifiant le type géré du fragment en question.
Sélection et traitement de la disposition du code-behind
Sélection
Par défaut, la génération code-behind est désactivée. Pour activer le traitement de toutes les dispositions dans l’un des répertoires qui contiennent au moins un élément unique avec l’attribut//*/@android:id
, définissez la $(AndroidGenerateLayoutBindings)
propriété True
MSBuild sur l’une ou l’autre sur la ligne de Resource\layout*
commande msbuild :
dotnet build -p:AndroidGenerateLayoutBindings=true MyProject.csproj
ou dans votre fichier .csproj :
<PropertyGroup>
<AndroidGenerateLayoutBindings>true</AndroidGenerateLayoutBindings>
</PropertyGroup>
Vous pouvez également laisser le code-behind désactivé globalement et l’activer uniquement pour des fichiers spécifiques. Pour activer Code-Behind pour un fichier particulier .axml
, modifiez le fichier pour qu’il dispose d’une action de génération de @(AndroidBoundLayout)
en modifiant votre .csproj
fichier et en AndroidResource
remplaçant par AndroidBoundLayout
:
<!-- This -->
<AndroidResource Include="Resources\layout\Main.axml" />
<!-- should become this -->
<AndroidBoundLayout Include="Resources\layout\Main.axml" />
Processing
Les dispositions sont regroupées par nom, avec des modèles nommés comme à partir de différentsResource\layout*
répertoires comprenant un seul groupe. Ces groupes sont traités comme s’ils étaient une disposition unique. Dans ce cas, il est possible qu’il y ait un conflit de type entre deux widgets trouvés dans des dispositions différentes appartenant au même groupe. Dans ce cas, la propriété générée ne sera pas en mesure d’avoir le type de widget exact, mais plutôt un type de widget « décomposé ». La décomposition suit l’algorithme ci-dessous :
Si tous les widgets en conflit sont
View
des dérivés, le type de propriété seraAndroid.Views.View
Si tous les types en conflit sont
Fragment
des dérivés, le type de propriété seraAndroid.App.Fragment
Si les widgets en conflit contiennent à la fois un
View
et unFragment
, le type de propriété seraglobal::System.Object
Code généré
Si vous êtes intéressé par la façon dont le code généré recherche vos dispositions, consultez le obj\$(Configuration)\generated
dossier de votre répertoire de solution.