Ü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#:
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 null
wird, ist das Standardverhalten dafür vorgesehen, dass die Eigenschaft anstelle der Rückgabe eingibtnull
InvalidOperationException
.
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 null
zurü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 eineJava.Package.MyButton
Schreiben Sie zwei Buchstaben große Komponenten des Typnamespaces. Zum Beispiel
android.os.SomeType
würde es sich um eineAndroid.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:
WebView
->Android.Webkit.WebView
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:
Android.App.Fragment
Das mit dem Android-Basissystem ausgelieferte "klassische" FragmentAndroidX.Fragment.App.Fragment
imXamarin.AndroidX.Fragment
NuGet-Paket.
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:
Wenn alle widersprüchlichen Widgets Derivate sind
View
, lautet der EigenschaftstypAndroid.Views.View
Wenn alle widersprüchlichen Typen Derivate sind
Fragment
, lautet der EigenschaftstypAndroid.App.Fragment
Wenn die in Konflikt stehenden Widgets sowohl ein
View
als auch einsFragment
enthalten, lautet der Eigenschaftstypglobal::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.