Delen via


Overzicht

Als onderdeel van de .NET voor Android-build worden Android-resources verwerkt en worden Android-id's weergegeven via een gegenereerde _Microsoft.Android.Resource.Designer.dll assembly. Bijvoorbeeld, op basis van het bestand Reources\layout\Main.axml met inhoud:

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

Vervolgens tijdens de build een _Microsoft.Android.Resource.Designer.dll assembly met inhoud die vergelijkbaar is met:

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;}
  }
}

Normaal gesproken wordt interactie met resources uitgevoerd in code, met behulp van de constanten van het Resource type en de FindViewById<T>() methode:

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!";
    };
  }
}

Vanaf Xamarin.Android 8.4 zijn er twee extra manieren om te communiceren met Android-resources bij het gebruik van C#:

  1. Koppelingen
  2. Code-behind

Als u deze nieuwe functies wilt inschakelen, stelt u de $(AndroidGenerateLayoutBindings) MSBuild eigenschap voor True een van beide op de msbuild-opdrachtregel:

dotnet build -p:AndroidGenerateLayoutBindings=true MyProject.csproj

of in uw .csproj-bestand:

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

Bindings

Een binding is een gegenereerde klasse, één per Android-indelingsbestand, dat sterk getypte eigenschappen bevat voor alle id's in het indelingsbestand . Bindingstypen worden gegenereerd in de global::Bindings naamruimte, met typenamen die de bestandsnaam van het indelingsbestand weerspiegelen.

Bindingstypen worden gemaakt voor alle indelingsbestanden die Android-id's bevatten.

Gezien het Android-layout-bestand 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>

vervolgens wordt het volgende type gegenereerd:

// 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);
  }
}

Het basistype van de binding maakt Xamarin.Android.Design.LayoutBinding deel uit van de klassebibliotheek .NET voor Android, maar wordt geleverd met .NET voor Android in bronvorm en automatisch opgenomen in de build van de toepassing wanneer bindingen worden gebruikt.

Het gegenereerde bindingstype kan worden gemaakt rond Activity exemplaren, waardoor sterk getypte toegang tot id's in het indelingsbestand mogelijk is:

// 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!";
    };
  }
}

Bindingstypen kunnen ook worden samengesteld rond View exemplaren, waardoor sterk getypte toegang tot resource-id's in de weergave of onderliggende items wordt toegestaan:

var binding = new Binding.Main (some_view);

Ontbrekende resource-id's

Eigenschappen van bindingstypen gebruiken nog steeds FindViewById<T>() in hun implementatie. Als FindViewById<T>()null retourneert, is het standaardgedrag dat de eigenschap een InvalidOperationException gooit in plaats van een null retourneert.

Dit standaardgedrag kan worden overschreven door een gemachtigde van de fouthandler door te geven aan de gegenereerde binding tijdens de instantiëring:

// 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);
  }
}

De OnLayoutItemNotFound() methode wordt aangeroepen wanneer een resource-id voor een View of een Fragment niet kan worden gevonden.

De handler moet ofwel nullretourneren, in welk geval de InvalidOperationException wordt gegenereerd of, bij voorkeur, de View of Fragment instantie retourneren die overeenkomt met de id die aan de handler wordt doorgegeven. Het geretourneerde object moet van het juiste type zijn dat overeenkomt met het type van de bijbehorende bindingseigenschap. De geretourneerde waarde wordt naar dat type gecast, dus als het object niet correct is getypt, wordt er een uitzondering gegenereerd.

Code-Behind

Code-Behind omvat het genereren van buildtijd van een partial klasse die sterk getypte eigenschappen bevat voor alle id's in het indelingsbestand .

Code-Behind bouwt voort op het bindingsmechanisme, waarbij indelingsbestanden moeten 'opt-innen' om de Code-Behind-generatie in te schakelen door gebruik te maken van het nieuwe xamarin:classes XML-kenmerk, dat een door ; gescheiden lijst van volledige klassenamen bevat die gegenereerd moeten worden.

Gezien het Android-lay-outbestand 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>

op bouwtijd wordt het volgende type geproduceerd:

// 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;
  }
}

Hierdoor is het gebruik van resource-id's binnen de lay-out intuïtiever mogelijk.

// 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!";
    };
  }
}

De activiteit gebruikt de OnLayoutItemNotFound-fouthandler als de laatste parameter van elke overload van 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;
  }
}

Aangezien Code-Behind afhankelijk is van gedeeltelijke klassen, moetenalle declaraties van een gedeeltelijke klasse in hun declaratie worden gebruiktpartial class, anders wordt er tijdens de build een C#-compilerfout in CS0260 gegenereerd.

Aanpassingsmogelijkheden

Het gegenereerde Code Behind-type overschrijft altijd Activity.SetContentView(), en standaard worden de parameters altijd naar doorgestuurd base.SetContentView(). Als dit niet gewenst is, moet een van de OnSetContentView()partial methoden worden overschreven, door callBaseAfterReturn in te stellen op 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);
  }
}

Voorbeeld van gegenereerde code

// 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;
  }
}

Layout-XML-attributen

Veel nieuwe XML-kenmerken voor indeling bepalen binding en Code-Behind gedrag, die zich binnen de xamarin XML-naamruimte (xmlns:xamarin="http://schemas.xamarin.com/android/xamarin/tools") bevinden. Deze omvatten:

xamarin:classes

Het xamarin:classes XML-kenmerk wordt gebruikt als onderdeel van Code-Behind om op te geven welke typen moeten worden gegenereerd.

Het xamarin:classes XML-kenmerk bevat een ;door -gescheiden lijst met volledige klassenamen die moeten worden gegenereerd.

xamarin:managedType

Het xamarin:managedType-opmaakkenmerk wordt gebruikt om expliciet het beheerde type op te geven waarmee de gebonden ID wordt gepresenteerd. Als dit niet is opgegeven, wordt het type afgeleid uit de declaratiecontext, bijvoorbeeld <Button/> resulteert in een Android.Widget.Button, en <fragment/> resulteert in een Android.App.Fragment.

Het xamarin:managedType kenmerk biedt meer expliciete typedeclaraties.

Toewijzing van beheerd type

Het is vrij gebruikelijk om widgetnamen te gebruiken op basis van het Java-pakket waarvan ze afkomstig zijn en, net zo vaak, de beheerde .NET-naam van dit type heeft een andere naam (.NET-stijl) in het beheerde land. De codegenerator kan een aantal zeer eenvoudige aanpassingen uitvoeren om te proberen overeen te komen met de code, zoals:

  • Zet alle onderdelen van de typenaamruimte en de naam in hoofdletters. Zou bijvoorbeeld java.package.myButton worden Java.Package.MyButton

  • Maak de tweelettige componenten van de typenaamruimte hoofdletters. Zou bijvoorbeeld android.os.SomeType worden Android.OS.SomeType

  • Zoek een aantal in code vastgelegde naamruimten op die bekende toewijzingen hebben. Momenteel bevat de lijst de volgende toewijzingen:

    • android.view ->Android.Views
    • com.actionbarsherlock ->ABSherlock
    • com.actionbarsherlock.widget ->ABSherlock.Widget
    • com.actionbarsherlock.view ->ABSherlock.View
    • com.actionbarsherlock.app ->ABSherlock.App
  • Zoek een aantal in code vastgelegde typen op in interne tabellen. Momenteel bevat de lijst de volgende typen:

  • Verwijder de in code vastgelegde naamruimtevoorvoegsels. Momenteel bevat de lijst de volgende voorvoegsels:

    • com.google.

Als de bovenstaande pogingen echter mislukken, moet u de indeling wijzigen die gebruikmaakt van een widget met een dergelijk niet-toegewezen type om zowel de xamarin XML-naamruimtedeclaratie toe te voegen aan het hoofdelement van de indeling als het xamarin:managedType element waarvoor de toewijzing is vereist. Bijvoorbeeld:

<fragment
    android:id="@+id/log_fragment"
    android:name="commonsamplelibrary.LogFragment"
    xamarin:managedType="CommonSampleLibrary.LogFragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
/>

Gebruikt het CommonSampleLibrary.LogFragment type voor het systeemeigen type commonsamplelibrary.LogFragment.

U kunt voorkomen dat u de declaratie van de XML-naamruimte en het xamarin:managedType kenmerk toevoegt door gewoon het type een naam te geven met behulp van de beheerde naam. Het bovenstaande fragment kan bijvoorbeeld als volgt opnieuw worden gedeclareerd:

<fragment
    android:name="CommonSampleLibrary.LogFragment"
    android:id="@+id/secondary_log_fragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
/>

Fragmenten: een speciaal geval

Het Android-ecosysteem ondersteunt momenteel twee verschillende implementaties van de Fragment widget:

Deze klassen zijn niet compatibel met elkaar en daarom moet er speciale aandacht worden besteed aan het genereren van bindingscode voor <fragment> elementen in de indelingsbestanden. .NET voor Android moet één Fragment implementatie kiezen als de standaard implementatie die moet worden gebruikt als het <fragment> element geen specifiek type (beheerd of anderszins) heeft opgegeven. Bindingscodegenerator maakt gebruik van de $(AndroidFragmentType) MSBuild-eigenschap voor dat doel. De eigenschap kan door de gebruiker worden overschreven om een ander type op te geven dan de standaardinstelling. De eigenschap is standaard ingesteld op Android.App.Fragment en wordt overschreven door de AndroidX NuGet-pakketten.

Als de gegenereerde code niet compileert, moet het indelingsbestand worden gewijzigd door het beheerde type van het betreffende fragment op te geven.

Selectie en verwerking van code-behind-indeling

Selectie

Standaard is het genereren van code-behind uitgeschakeld. Als u verwerking wilt inschakelen voor alle indelingen in een van de Resource\layout* directories die ten minste één element met het //*/@android:id kenmerk bevatten, stelt u de $(AndroidGenerateLayoutBindings) MSBuild-eigenschap True in op de msbuild-opdrachtregel:

dotnet build -p:AndroidGenerateLayoutBindings=true MyProject.csproj

of in uw .csproj-bestand:

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

U kunt code-behind ook wereldwijd uitschakelen en het alleen voor specifieke bestanden inschakelen. Als u Code-Behind voor een bepaald .axml-bestand wilt inschakelen, wijzigt u het bestand om een Build-actie van te hebben. @(AndroidBoundLayout) door het .csproj bestand te bewerken en te AndroidResource vervangen door AndroidBoundLayout:

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

Processing

Indelingen worden gegroepeerd op naam, met gelijknamige sjablonen uit verschillende mappen die één groep vormen. Dergelijke groepen worden verwerkt alsof ze één indeling zijn. Het is mogelijk dat er in dat geval een typeconflict tussen twee widgets in verschillende indelingen van dezelfde groep wordt gevonden. In dat geval kan de gegenereerde eigenschap niet het exacte widgettype hebben, maar in plaats daarvan een 'rotte' eigenschap. Verval volgt het onderstaande algoritme:

  1. Als alle conflicterende widgets derivaten zijn View , is het eigenschapstype Android.Views.View

  2. Als alle conflicterende typen derivaten zijn Fragment , is het eigenschapstype Android.App.Fragment

  3. Als de conflicterende widgets zowel een als View een Fragmentbevatten, is het eigenschapstype global::System.Object

Gegenereerde code

Als u geïnteresseerd bent in hoe de gegenereerde code eruitziet voor uw indelingen, kijkt u in de obj\$(Configuration)\generated-map in uw oplossingdirectory.