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, eine Datei Reources\layout\Main.axml mit folgendem Inhalt:

<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. Code-Behind

Um diese neuen Funktionen zu aktivieren, konfigurieren 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, Xamarin.Android.Design.LayoutBinding ist nicht Teil der .NET für Android-Klassenbibliothek, sondern wird stattdessen mit .NET für Android im Quellcode ausgeliefert und automatisch in den Build der Anwendung aufgenommen, sobald Bindungen verwendet werden.

Der generierte Bindungstyp kann um Activity Instanzen erstellt werden, sodass stark typisiert auf IDs innerhalb der Layoutdatei 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 ein stark typisierter Zugriff auf Ressourcen-IDs innerhalb der Ansicht oder ihrer untergeordneten Elemente ermöglicht wird.

var binding = new Binding.Main (some_view);

Fehlende Ressourcen-IDs

Eigenschaften von Bindungstypen verwenden weiterhin FindViewById<T>() in ihrer Implementierung. Wenn FindViewById<T>()null zurückgibt, ist das Standardverhalten, dass die Eigenschaft stattdessen eine InvalidOperationException wirft, anstatt null zurückzugeben.

Dieses Standardverhalten kann überschrieben werden, indem ein Fehlerbehandlungsdelegat während der Instanziierung der generierten Bindung ü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 null zurückgeben, in diesem Fall wird die InvalidOperationException ausgelöst, oder vorzugsweise die View- oder Fragment-Instanz zurückgeben, die 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.

Code-Behind

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

Code-Behind baut auf dem Bindungsmechanismus auf, wobei die Layoutdateien mithilfe des neuen xamarin:classes XML-Attributs, einer durch ; getrennten Liste vollständiger Klassennamen, die generiert werden soll, für die Code-Behind-Erstellung aktiviert werden müssen.

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 Code-Behind auf partiellen Klassen basiert, müssenalle Deklarationen einer partiellen Klasse das partial class in ihrer Deklaration verwenden, andernfalls wird zur Build-Zeit ein CS0260 C#-Compilerfehler generiert.

Anpassung

Der generierte CodeBehind-Typ überschreibt Activity.SetContentView() 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, wobei callBaseAfterReturn auf false gesetzt wird.

// 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 Binding und das Code-Behind-Verhalten, die sich im xamarin XML-Namespace (xmlns:xamarin="http://schemas.xamarin.com/android/xamarin/tools") befinden. Dazu zählen:

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

  • Schreiben Sie alle Komponenten des Typs-Namespace und des Namens groß. Zum Beispiel würde java.package.myButton zu Java.Package.MyButton

  • Schreiben Sie die Komponenten des Typ-Namespace mit zwei Buchstaben groß. Zum Beispiel würde android.os.SomeType zu Android.OS.SomeType

  • Suchen Sie nach mehreren hartcodierten 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 hartkodierter Typen in internen Tabellen. Derzeit enthält die Liste die folgenden Typen:

  • Streifen Sie die Anzahl der festcodierten Namensraum-Präfixe. 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 Typ CommonSampleLibrary.LogFragment für den nativen 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 auf Android.App.Fragment festgelegt 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 Code-Behind-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 Code-Behind für eine bestimmte .axml-Datei zu aktivieren, ändern Sie die Datei zu einer Build-Aktion 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" />

Verarbeitung

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 kann die generierte Eigenschaft nicht den genauen Widget-Typ haben, sondern einen "degradierten" Typ. 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 daran interessiert sind, wie der generierte Code für Ihre Layouts aussieht, werfen Sie bitte einen Blick in den obj\$(Configuration)\generated Ordner in Ihrem Lösungsverzeichnis.