Freigeben über


Übersicht

Im Rahmen des .NET für Android-Builds werden Android-Ressourcen verarbeitet, wodurch Android-IDs über eine generierte _Microsoft.Android.Resource.Designer.dll Assembly verfügbar sind. Beispiel: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>

Dann während der Buildzeit eine _Microsoft.Android.Resource.Designer.dll Assembly mit Inhalten ähnlich:

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

Die Interaktion mit Ressourcen erfolgt traditionell im Code, wobei die Konstanten aus dem Resource Typ und der FindViewById<T>() Methode verwendet werden:

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

Ab Xamarin.Android 8.4 gibt es zwei zusätzliche Möglichkeiten für die Interaktion mit Android-Ressourcen bei Verwendung von C#:

  1. Bindungen
  2. CodeBehind

Um diese neuen Features zu aktivieren, legen Sie die $(AndroidGenerateLayoutBindings) MSBuild-Eigenschaft auf True einer der msbuild-Befehlszeilen:

dotnet build -p:AndroidGenerateLayoutBindings=true MyProject.csproj

oder in Ihrer CSPROJ-Datei:

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

Bindungen

Eine Bindung ist eine generierte Klasse, eine pro Android-Layoutdatei, die stark typierte Eigenschaften für alle IDs in der Layoutdatei enthält. Bindungstypen werden im global::Bindings Namespace mit Typnamen generiert, die den Dateinamen der Layoutdatei spiegeln.

Bindungstypen werden für alle Layoutdateien erstellt, die alle Android-IDs enthalten.

Aufgrund der Android-Layoutdatei 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>

dann wird der folgende Typ generiert:

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

Der Basistyp der Bindung ist nicht Teil der .NET für Android-Klassenbibliothek, Xamarin.Android.Design.LayoutBinding sondern wird stattdessen in .NET für Android im Quellformular ausgeliefert und automatisch im Build der Anwendung enthalten, wenn Bindungen verwendet werden.

Der generierte Bindungstyp kann um Activity Instanzen erstellt werden, sodass in der Layoutdatei stark typisch auf IDs zugegriffen werden kann:

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

Bindungstypen können auch um View Instanzen erstellt werden, sodass der Zugriff auf Ressourcen-IDs innerhalb der Ansicht oder der untergeordneten Elemente stark typiert wird:

var binding = new Binding.Main (some_view);

Fehlende Ressourcen-IDs

Eigenschaften für Bindungstypen werden weiterhin in ihrer Implementierung verwendet FindViewById<T>() . Wenn FindViewById<T>() zurückgegeben nullwird, ist das Standardverhalten dafür vorgesehen, dass die Eigenschaft anstelle der Rückgabe eingibtnullInvalidOperationException.

Dieses Standardverhalten kann überschrieben werden, indem ein Fehlerhandlerdelegat an die generierte Bindung für die Instanziierung übergeben wird:

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

Die OnLayoutItemNotFound() Methode wird aufgerufen, wenn eine Ressourcen-ID für eine View oder eine Fragment nicht gefunden werden konnte.

Der Handler muss entweder nullzurückgeben, in diesem Fall wird die InvalidOperationException ausgelöste oder vorzugsweise die Instanz zurückgegeben, die Fragment View der an den Handler übergebenen ID entspricht. Das zurückgegebene Objekt muss vom richtigen Typ sein, der dem Typ der entsprechenden Binding-Eigenschaft entspricht. Der zurückgegebene Wert wird in diesen Typ umgewandelt. Wenn das Objekt also nicht ordnungsgemäß eingegeben wird, wird eine Ausnahme ausgelöst.

CodeBehind

CodeBehind umfasst die Erstellungszeitgenerierung einer partial Klasse, die stark typierte Eigenschaften für alle IDs in der Layoutdatei enthält.

CodeBehind baut auf dem Bindungsmechanismus auf, und erfordert, dass die Layoutdateien mithilfe des neuen xamarin:classes XML-Attributs , das eine ;durch -getrennte Liste vollständiger Klassennamen generiert werden soll, zur CodeBehind-Generierung "opt-in" erforderlich sind.

Aufgrund der Android-Layoutdatei 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>

zur Erstellungszeit wird der folgende Typ erstellt:

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

Dies ermöglicht eine intuitivere Verwendung von Ressourcen-IDs innerhalb des Layouts:

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

Der OnLayoutItemNotFound Fehlerhandler kann als letzter Parameter der überlasteten SetContentView Aktivität übergeben werden:

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

Da CodeBehind auf partielle Klassen basiert, müssen alle Deklarationen einer partiellen Klasse in ihrer Deklaration verwendet partial class werden, andernfalls wird zum Buildzeit ein CS0260 C#-Compilerfehler generiert.

Anpassung

Der generierte CodeBehind-Typ überschreibt Activity.SetContentView()immer und ruft standardmäßig immer aufbase.SetContentView(), um die Parameter weiterzuleiten. Wenn dies nicht gewünscht ist, sollte eine der OnSetContentView()partial Methoden überschrieben werden, und legen Sie callBaseAfterReturn folgendes fest: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);
  }
}

Beispiel für generierten 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-Attribute

Viele neue Layout-XML-Attribute steuern das Bindungs- und CodeBehind-Verhalten, das sich im xamarin XML-Namespace (xmlns:xamarin="http://schemas.xamarin.com/android/xamarin/tools") befindet. Dazu gehören:

xamarin:classes

Das xamarin:classes XML-Attribut wird als Teil von CodeBehind verwendet, um anzugeben, welche Typen generiert werden sollen.

Das xamarin:classes XML-Attribut enthält eine ;durch -getrennte Liste vollständiger Klassennamen , die generiert werden sollen.

xamarin:managedType

Das xamarin:managedType Layout-Attribut wird verwendet, um den verwalteten Typ explizit anzugeben, um die gebundene ID verfügbar zu machen. Wenn nicht angegeben, wird der Typ aus dem deklarierenden Kontext abgeleitet, z. B. <Button/> zu einem Android.Widget.Button, und <fragment/> führt zu einem Android.App.Fragment.

Das xamarin:managedType Attribut ermöglicht explizitere Typdeklarationen.

Zuordnung verwalteter Typen

Es ist üblich, Widgetnamen basierend auf dem Java-Paket zu verwenden, aus dem sie stammen, und ebenso oft hat der verwaltete .NET-Name dieses Typs einen anderen Namen (.NET-Stil) im verwalteten Land. Der Codegenerator kann eine Reihe von sehr einfachen Anpassungen ausführen, um zu versuchen, den Code abzugleichen, z. B.:

  • Großbuchstaben aller Komponenten des Typnamespaces und des Namens. Zum Beispiel java.package.myButton würde es sich um eine Java.Package.MyButton

  • Schreiben Sie zwei Buchstaben große Komponenten des Typnamespaces. Zum Beispiel android.os.SomeType würde es sich um eine Android.OS.SomeType

  • Suchen Sie nach einer Reihe hartcodierter Namespaces, die bekannte Zuordnungen haben. Derzeit enthält die Liste die folgenden Zuordnungen:

    • android.view ->Android.Views
    • com.actionbarsherlock ->ABSherlock
    • com.actionbarsherlock.widget ->ABSherlock.Widget
    • com.actionbarsherlock.view ->ABSherlock.View
    • com.actionbarsherlock.app ->ABSherlock.App
  • Suchen Sie eine Reihe hartcodierter Typen in internen Tabellen. Derzeit enthält die Liste die folgenden Typen:

  • Strip number of hard-coded namespace prefixes. Derzeit enthält die Liste die folgenden Präfixe:

    • com.google.

Wenn die oben genannten Versuche jedoch fehlschlagen, müssen Sie das Layout ändern, das ein Widget mit einem solchen nicht zugeordneten Typ verwendet, um sowohl die xamarin XML-Namespacedeklaration zum Stammelement des Layouts als auch zum xamarin:managedType Element hinzuzufügen, das die Zuordnung erfordert. Beispiel:

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

Verwendet den CommonSampleLibrary.LogFragment Typ für den systemeigenen Typ commonsamplelibrary.LogFragment.

Sie können das Hinzufügen der XML-Namespacedeklaration und des xamarin:managedType Attributs vermeiden, indem Sie einfach den Typ mit dem verwalteten Namen benennen, z. B. könnte das obige Fragment wie folgt neu deklariert werden:

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

Fragmente: ein Sonderfall

Das Android-Ökosystem unterstützt derzeit zwei unterschiedliche Implementierungen des Fragment Widgets:

Diese Klassen sind nicht miteinander kompatibel, daher muss beim Generieren von Bindungscode für <fragment> Elemente in den Layoutdateien besondere Sorgfalt beachtet werden. .NET für Android muss eine Fragment Implementierung als Standardimplementierung auswählen, die verwendet werden soll, wenn das <fragment> Element keinen bestimmten Typ (verwaltet oder anderweitig) angegeben hat. Der Bindungscodegenerator verwendet die $(AndroidFragmentType) MSBuild-Eigenschaft für diesen Zweck. Die Eigenschaft kann vom Benutzer außer Kraft gesetzt werden, um einen anderen Typ als der Standardtyp anzugeben. Die Eigenschaft ist standardmäßig festgelegt Android.App.Fragment und wird von den AndroidX NuGet-Paketen überschrieben.

Wenn der generierte Code nicht erstellt wird, muss die Layoutdatei durch Angabe des verwalteten Typs des fraglichen Fragments geändert werden.

CodeBehind-Layoutauswahl und -verarbeitung

Auswahl

Standardmäßig ist die CodeBehind-Generierung deaktiviert. Wenn Sie die Verarbeitung für alle Layouts in einem der Verzeichnisse aktivieren möchten, die Resource\layout* mindestens ein einzelnes Element mit dem //*/@android:id Attribut enthalten, legen Sie die $(AndroidGenerateLayoutBindings) MSBuild-Eigenschaft True auf eine der Folgenden auf der Befehlszeile "msbuild" fest:

dotnet build -p:AndroidGenerateLayoutBindings=true MyProject.csproj

oder in Ihrer CSPROJ-Datei:

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

Alternativ können Sie CodeBehind global deaktiviert lassen und nur für bestimmte Dateien aktivieren. Um CodeBehind für eine bestimmte .axml Datei zu aktivieren, ändern Sie die Datei so, dass eine Buildaktion von @(AndroidBoundLayout) durch Bearbeiten der .csproj Datei und Ersetzen AndroidResource durch AndroidBoundLayout:

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

Processing

Layouts werden nach Namen gruppiert, mit ähnlich benannten Vorlagen aus verschiedenenResource\layout* Verzeichnissen, die aus einer einzelnen Gruppe bestehen. Solche Gruppen werden so verarbeitet, als wären sie ein einzelnes Layout. Es ist möglich, dass in diesem Fall ein Typkonflikt zwischen zwei Widgets in verschiedenen Layouts vorhanden ist, die derselben Gruppe angehören. In diesem Fall ist die generierte Eigenschaft nicht in der Lage, den genauen Widgettyp, sondern einen "verfallenen" typ zu haben. Der Verfall folgt dem folgenden Algorithmus:

  1. Wenn alle widersprüchlichen Widgets Derivate sind View , lautet der Eigenschaftstyp Android.Views.View

  2. Wenn alle widersprüchlichen Typen Derivate sind Fragment , lautet der Eigenschaftstyp Android.App.Fragment

  3. Wenn die in Konflikt stehenden Widgets sowohl ein View als auch eins Fragmententhalten, lautet der Eigenschaftstyp global::System.Object

Generierter Code

Wenn Sie interessiert sind, wie der generierte Code nach Ihren Layouts sucht, sehen Sie sich den obj\$(Configuration)\generated Ordner in Ihrem Lösungsverzeichnis an.