Panoramica
Nell'ambito della compilazione .NET per Android, le risorse Android vengono elaborate, esponendo gli ID Android tramite un assembly generato_Microsoft.Android.Resource.Designer.dll
.
Ad esempio, dato il file Reources\layout\Main.axml
con contenuto:
<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>
Quindi durante la fase di compilazione un _Microsoft.Android.Resource.Designer.dll
assembly con contenuto simile al seguente:
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;}
}
}
In genere, l'interazione con Le risorse viene eseguita nel codice, usando le costanti del Resource
tipo e il FindViewById<T>()
metodo :
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!";
};
}
}
A partire da Xamarin.Android 8.4, esistono due modi aggiuntivi per interagire con le risorse Android quando si usa C#:
Per abilitare queste nuove funzionalità, impostare $(AndroidGenerateLayoutBindings)
Proprietà MSBuild su True
nella riga di comando msbuild:
dotnet build -p:AndroidGenerateLayoutBindings=true MyProject.csproj
o nel file con estensione csproj:
<PropertyGroup>
<AndroidGenerateLayoutBindings>true</AndroidGenerateLayoutBindings>
</PropertyGroup>
Bindings
Un'associazione è una classe generata, una per ogni file di layout Android, che contiene proprietà fortemente tipizzate per tutti gli ID all'interno del file di layout. I tipi di associazione vengono generati nello spazio dei global::Bindings
nomi, con nomi di tipo che rispecchiano il nome file del file di layout.
I tipi di associazione vengono creati per tutti i file di layout che contengono tutti gli ID Android.
Dato il file Resources\layout\Main.axml
di layout 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>
verrà quindi generato il tipo seguente:
// 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);
}
}
Il tipo Xamarin.Android.Design.LayoutBinding
di base dell'associazione non fa parte della libreria di classi .NET per Android, ma viene fornito con .NET per Android nel formato di origine e incluso automaticamente nella compilazione dell'applicazione ogni volta che vengono usate le associazioni.
Il tipo di associazione generato può essere creato intorno alle Activity
istanze, consentendo l'accesso fortemente tipizzato agli ID all'interno del file di layout:
// 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!";
};
}
}
I tipi di associazione possono anche essere costruiti intorno View
alle istanze, consentendo l'accesso fortemente tipizzato agli ID risorsa all'interno della visualizzazione o dei relativi elementi figlio:
var binding = new Binding.Main (some_view);
ID risorsa mancanti
Le proprietà sui tipi di associazione usano FindViewById<T>()
ancora nell'implementazione. Se FindViewById<T>()
restituisce null
, il comportamento predefinito è che la proprietà generi un oggetto InvalidOperationException
anziché restituire null
.
Questo comportamento predefinito può essere sottoposto a override passando un delegato del gestore errori all'associazione generata nella relativa creazione di istanze:
// 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);
}
}
Il OnLayoutItemNotFound()
metodo viene richiamato quando non è stato possibile trovare un ID risorsa per un View
oggetto o .Fragment
Il gestore deve restituire null
, nel qual caso InvalidOperationException
verrà generata o, preferibilmente, restituire l'istanza View
o Fragment
che corrisponde all'ID passato al gestore. L'oggetto restituito deve essere del tipo corretto corrispondente al tipo della proprietà Binding corrispondente. Viene eseguito il cast del valore restituito a tale tipo, pertanto se l'oggetto non è digitato correttamente, verrà generata un'eccezione.
code-behind
Code-Behind prevede la generazione in fase di compilazione di una partial
classe che contiene proprietà fortemente tipizzate per tutti gli ID all'interno del file di layout.
Code-Behind compila in cima al meccanismo di associazione, richiedendo al tempo stesso che i file di layout "acconsentino" alla generazione code-behind usando il nuovo xamarin:classes
attributo XML, ovvero un ;
elenco separato da un elenco di nomi di classe completi da generare.
Dato il file Resources\layout\Main.axml
di layout 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>
in fase di compilazione verrà prodotto il tipo seguente:
// 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;
}
}
Ciò consente un uso più "intuitivo" degli ID risorsa all'interno del layout:
// 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!";
};
}
}
Il OnLayoutItemNotFound
gestore degli errori può essere passato come ultimo parametro di qualsiasi overload dell'attività SetContentView
in uso:
// 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;
}
}
Poiché Code-Behind si basa su classi parziali, tutte le dichiarazioni di una classe parziale devono essere usate partial class
nella dichiarazione. In caso contrario, verrà generato un errore del compilatore C# CS0260 in fase di compilazione.
Personalizzazione
Il tipo Code Behind generato esegue sempre l'override Activity.SetContentView()
di e per impostazione predefinita chiama base.SetContentView()
sempre , inoltrando i parametri. Se non si vuole, è necessario eseguire l'override di uno dei OnSetContentView()
partial
metodi, impostando callBaseAfterReturn
su :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);
}
}
Codice generato di esempio
// 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;
}
}
Attributi XML di layout
Molti nuovi attributi XML layout controllano il comportamento Binding e Code-Behind, che si trovano all'interno dello spazio dei xamarin
nomi XML (xmlns:xamarin="http://schemas.xamarin.com/android/xamarin/tools"
).
tra cui:
xamarin:classes
L'attributo xamarin:classes
XML viene usato come parte di Code-Behind per specificare quali tipi devono essere generati.
L'attributo xamarin:classes
XML contiene un ;
elenco separato da -di nomi di classi completi che devono essere generati.
xamarin:managedType
L'attributo xamarin:managedType
layout viene usato per specificare in modo esplicito il tipo gestito per esporre l'ID associato come . Se non specificato, il tipo verrà dedotto dal contesto dichiarante, ad esempio <Button/>
verrà generato un Android.Widget.Button
oggetto e <fragment/>
verrà generato un oggetto Android.App.Fragment
.
L'attributo xamarin:managedType
consente dichiarazioni di tipo più esplicite.
Mapping dei tipi gestiti
È piuttosto comune usare nomi di widget basati sul pacchetto Java da cui provengono e, altrettanto spesso, il nome .NET gestito di tale tipo avrà un nome diverso (stile.NET) nella terra gestita. Il generatore di codice può eseguire una serie di modifiche molto semplici per provare a trovare la corrispondenza con il codice, ad esempio:
Capitalizzare tutti i componenti dello spazio dei nomi e del nome del tipo. Ad esempio
java.package.myButton
, sarebbe diventatoJava.Package.MyButton
In maiuscolo i componenti di due lettere dello spazio dei nomi del tipo. Ad esempio
android.os.SomeType
, sarebbe diventatoAndroid.OS.SomeType
Cercare una serie di spazi dei nomi hardcoded con mapping noti. Attualmente l'elenco include i mapping seguenti:
android.view
->Android.Views
com.actionbarsherlock
->ABSherlock
com.actionbarsherlock.widget
->ABSherlock.Widget
com.actionbarsherlock.view
->ABSherlock.View
com.actionbarsherlock.app
->ABSherlock.App
Cercare diversi tipi hardcoded nelle tabelle interne. Attualmente l'elenco include i tipi seguenti:
WebView
->Android.Webkit.WebView
Numero di prefissi dello spazio dei nomi hardcoded. Attualmente l'elenco include i prefissi seguenti:
com.google.
Se, tuttavia, i tentativi precedenti hanno esito negativo, sarà necessario modificare il layout che usa un widget con tale tipo non mappato per aggiungere sia la xamarin
dichiarazione dello spazio dei nomi XML all'elemento radice del layout che xamarin:managedType
all'elemento che richiede il mapping. Ad esempio:
<fragment
android:id="@+id/log_fragment"
android:name="commonsamplelibrary.LogFragment"
xamarin:managedType="CommonSampleLibrary.LogFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
Userà il CommonSampleLibrary.LogFragment
tipo per il tipo commonsamplelibrary.LogFragment
nativo .
È possibile evitare di aggiungere la dichiarazione dello spazio dei nomi XML e l'attributo xamarin:managedType
semplicemente denominando il tipo usando il relativo nome gestito, ad esempio il frammento precedente potrebbe essere dichiarato nuovamente come segue:
<fragment
android:name="CommonSampleLibrary.LogFragment"
android:id="@+id/secondary_log_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
Frammenti: un caso speciale
L'ecosistema Android supporta attualmente due implementazioni distinte del Fragment
widget:
Android.App.Fragment
Il frammento "classico" fornito con il sistema Android di baseAndroidX.Fragment.App.Fragment
NelXamarin.AndroidX.Fragment
Pacchetto NuGet.
Queste classi non sono compatibili tra loro e pertanto è necessario prestare particolare attenzione quando si genera codice di associazione per <fragment>
gli elementi nei file di layout. .NET per Android deve scegliere un'implementazione Fragment
come predefinita da usare se l'elemento <fragment>
non ha alcun tipo specifico (gestito o in altro modo) specificato. Il generatore di codice di associazione usa il $(AndroidFragmentType)
Proprietà MSBuild a tale scopo. La proprietà può essere sostituita dall'utente per specificare un tipo diverso da quello predefinito. La proprietà è impostata su per impostazione predefinita ed è sottoposta a Android.App.Fragment
override dai pacchetti NuGet AndroidX.
Se il codice generato non viene compilato, il file di layout deve essere modificato specificando il tipo manged del frammento in questione.
Selezione ed elaborazione del layout code-behind
Selezione
Per impostazione predefinita, la generazione code-behind è disabilitata. Per abilitare l'elaborazione per tutti i layout in una delle Resource\layout*
directory che contengono almeno un singolo elemento con l'attributo //*/@android:id
, impostare la $(AndroidGenerateLayoutBindings)
proprietà MSBuild su True
nella riga di comando msbuild:
dotnet build -p:AndroidGenerateLayoutBindings=true MyProject.csproj
o nel file con estensione csproj:
<PropertyGroup>
<AndroidGenerateLayoutBindings>true</AndroidGenerateLayoutBindings>
</PropertyGroup>
In alternativa, è possibile lasciare il code-behind disabilitato a livello globale e abilitarlo solo per file specifici. Per abilitare Code-Behind per un file specifico.axml
, modificare il file in modo che abbia un'azione di compilazione di@(AndroidBoundLayout)
modificando il .csproj
file e sostituendo AndroidResource
con AndroidBoundLayout
:
<!-- This -->
<AndroidResource Include="Resources\layout\Main.axml" />
<!-- should become this -->
<AndroidBoundLayout Include="Resources\layout\Main.axml" />
in lavorazione
I layout vengono raggruppati in base al nome, con modelli con nome simile a directory diverseResource\layout*
che comprendono un singolo gruppo. Tali gruppi vengono elaborati come se fossero un singolo layout. È possibile che in questo caso si verifichi un conflitto di tipi tra due widget presenti in layout diversi appartenenti allo stesso gruppo. In questo caso, la proprietà generata non sarà in grado di avere il tipo di widget esatto, ma piuttosto una proprietà "decadita". Il decadimento segue l'algoritmo seguente:
Se tutti i widget in conflitto sono
View
derivati, il tipo di proprietà saràAndroid.Views.View
Se tutti i tipi in conflitto sono
Fragment
derivati, il tipo di proprietà saràAndroid.App.Fragment
Se i widget in conflitto contengono sia un oggetto
View
che unFragment
, il tipo di proprietà saràglobal::System.Object
Codice generato
Se si è interessati al modo in cui il codice generato cerca i layout, esaminare la cartella nella directory della obj\$(Configuration)\generated
soluzione.