Partager via


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# :

  1. Liaisons
  2. Code-Behind

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.axmlde 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.axmlde 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.Buttonré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 , devenir Java.Package.MyButton

  • Mettre en majuscule les composants à deux lettres de l’espace de noms de type. Par exemple android.os.SomeType , devenir Android.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 :

  • 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.LogFragmentnatif.

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 :

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 :

  1. Si tous les widgets en conflit sont View des dérivés, le type de propriété sera Android.Views.View

  2. Si tous les types en conflit sont Fragment des dérivés, le type de propriété sera Android.App.Fragment

  3. Si les widgets en conflit contiennent à la fois un View et un Fragment, le type de propriété sera global::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.