Información general
Como parte de la compilación de .NET para Android, los recursos de Android se procesan, exponiendo identificadores de Android a través de un ensamblado generado _Microsoft.Android.Resource.Designer.dll
.
Por ejemplo, dado el archivo Reources\layout\Main.axml
con contenido:
<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>
A continuación, durante el tiempo de compilación, un _Microsoft.Android.Resource.Designer.dll
ensamblado con contenido similar al siguiente:
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;}
}
}
Tradicionalmente, la interacción con recursos se realizaría en el código mediante las constantes del Resource
tipo y el FindViewById<T>()
método :
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 partir de Xamarin.Android 8.4, hay dos maneras adicionales de interactuar con los recursos de Android al usar C#:
Para habilitar estas nuevas características, establezca $(AndroidGenerateLayoutBindings)
Propiedad de MSBuild para True
cualquiera de las dos en la línea de comandos de msbuild:
dotnet build -p:AndroidGenerateLayoutBindings=true MyProject.csproj
o en su archivo .csproj:
<PropertyGroup>
<AndroidGenerateLayoutBindings>true</AndroidGenerateLayoutBindings>
</PropertyGroup>
Enlaces
Un enlace es una clase generada, una por archivo de diseño de Android, que contiene propiedades fuertemente tipadas para todos los identificadores del archivo de diseño. Los tipos de enlace se generan en el global::Bindings
espacio de nombres, con nombres de tipo que reflejan el nombre de archivo del archivo de diseño.
Los tipos de enlace se crean para todos los archivos de diseño que contienen los identificadores de Android.
Dado el archivo Resources\layout\Main.axml
de diseño de 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>
a continuación, se generará el siguiente tipo:
// 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);
}
}
El tipo Xamarin.Android.Design.LayoutBinding
base del enlace no forma parte de la biblioteca de clases de .NET para Android, sino que se incluye con .NET para Android en formato de origen y se incluye automáticamente en la compilación de la aplicación siempre que se usen enlaces.
El tipo de enlace generado se puede crear en torno Activity
a las instancias, lo que permite el acceso fuertemente tipado a los identificadores dentro del archivo de diseño:
// 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!";
};
}
}
Los tipos de enlace también se pueden construir en torno View
a instancias, lo que permite el acceso fuertemente tipado a identificadores de recursos dentro de la vista o sus elementos secundarios:
var binding = new Binding.Main (some_view);
Identificadores de recursos que faltan
Las propiedades de los tipos de enlace siguen usando FindViewById<T>()
en su implementación. Si FindViewById<T>()
devuelve null
, el comportamiento predeterminado es para que la propiedad inicie en InvalidOperationException
lugar de devolver null
.
Este comportamiento predeterminado se puede invalidar pasando un delegado de controlador de errores al enlace generado en su creación de instancias:
// 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);
}
}
El OnLayoutItemNotFound()
método se invoca cuando no se encuentra un identificador de recurso para o View
un Fragment
.
El controlador debe devolver , null
en cuyo caso InvalidOperationException
se producirá o, preferiblemente, devolver la View
instancia o Fragment
que corresponda al identificador pasado al controlador. El objeto devuelto debe ser del tipo correcto que coincida con el tipo de la propiedad Binding correspondiente. El valor devuelto se convierte en ese tipo, por lo que si el objeto no está escrito correctamente, se producirá una excepción.
código subyacente
El código subyacente implica la generación en tiempo de compilación de una partial
clase que contiene propiedades fuertemente tipadas para todos los identificadores del archivo de diseño.
El código subyacente se basa en el mecanismo binding, al tiempo que requiere que los archivos de diseño "opt-in" en la generación de código subyacente mediante el nuevo xamarin:classes
atributo XML, que es una ;
lista separada por separados de nombres de clase completos que se van a generar.
Dado el archivo Resources\layout\Main.axml
de diseño de 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>
en tiempo de compilación se generará el siguiente tipo:
// 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;
}
}
Esto permite un uso más "intuitivo" de los identificadores de recursos dentro del diseño:
// 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!";
};
}
}
El OnLayoutItemNotFound
controlador de errores se puede pasar como último parámetro de cualquier sobrecarga de SetContentView
la actividad que use:
// 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;
}
}
Como Code-Behind se basa en clases parciales, todas las declaraciones de una clase parcial deben usarse partial class
en su declaración; de lo contrario, se generará un error del compilador de C# CS0260 en tiempo de compilación.
Personalización
El tipo de código subyacente generado siempre invalida Activity.SetContentView()
y, de forma predeterminada , siempre llama a base.SetContentView()
, reenviando los parámetros. Si no se desea, se debe invalidar uno de los OnSetContentView()
partial
métodos , estableciendo callBaseAfterReturn
false
en :
// 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);
}
}
Código generado de ejemplo
// 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;
}
}
Atributos XML de diseño
Muchos atributos XML de diseño nuevos controlan el comportamiento enlace y código subyacente, que están dentro del xamarin
espacio de nombres XML (xmlns:xamarin="http://schemas.xamarin.com/android/xamarin/tools"
).
Entre ellas se incluyen las siguientes:
xamarin:classes
El xamarin:classes
atributo XML se usa como parte de Code-Behind para especificar qué tipos se deben generar.
El xamarin:classes
atributo XML contiene una ;
lista separada por separados de nombres de clase completos que se deben generar.
xamarin:managedType
El xamarin:managedType
atributo layout se usa para especificar explícitamente el tipo administrado para exponer el identificador enlazado como. Si no se especifica, el tipo se deducirá del contexto declarativo, por ejemplo <Button/>
, dará como resultado un Android.Widget.Button
, y <fragment/>
dará como resultado un Android.App.Fragment
.
El xamarin:managedType
atributo permite declaraciones de tipos más explícitas.
Asignación de tipos administrados
Es bastante habitual usar nombres de widget basados en el paquete de Java del que proceden y, por igual que con frecuencia, el nombre de .NET administrado de este tipo tendrá un nombre diferente (estilo.NET) en la tierra administrada. El generador de código puede realizar una serie de ajustes muy sencillos para intentar coincidir con el código, como:
Capitalice todos los componentes del espacio de nombres de tipo y el nombre. Por ejemplo
java.package.myButton
, se convertiría enJava.Package.MyButton
Capitalice los componentes de dos letras del espacio de nombres de tipo. Por ejemplo
android.os.SomeType
, se convertiría enAndroid.OS.SomeType
Busque una serie de espacios de nombres codificados de forma rígida que tienen asignaciones conocidas. Actualmente, la lista incluye las siguientes asignaciones:
android.view
->Android.Views
com.actionbarsherlock
->ABSherlock
com.actionbarsherlock.widget
->ABSherlock.Widget
com.actionbarsherlock.view
->ABSherlock.View
com.actionbarsherlock.app
->ABSherlock.App
Busque varios tipos codificados de forma rígida en tablas internas. Actualmente, la lista incluye los siguientes tipos:
WebView
->Android.Webkit.WebView
Seccione el número de prefijos de espacio de nombres codificados de forma rígida. Actualmente, la lista incluye los siguientes prefijos:
com.google.
Sin embargo, si se produce un error en los intentos anteriores, deberá modificar el diseño que usa un widget con un tipo sin asignar para agregar la declaración del xamarin
espacio de nombres XML al elemento raíz del diseño y al xamarin:managedType
elemento que requiere la asignación. Por ejemplo:
<fragment
android:id="@+id/log_fragment"
android:name="commonsamplelibrary.LogFragment"
xamarin:managedType="CommonSampleLibrary.LogFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
Usará el CommonSampleLibrary.LogFragment
tipo para el tipo commonsamplelibrary.LogFragment
nativo .
Puede evitar agregar la declaración de espacio de nombres XML y el xamarin:managedType
atributo simplemente asignando un nombre al tipo mediante su nombre administrado, por ejemplo, el fragmento anterior podría volver a declararse de la siguiente manera:
<fragment
android:name="CommonSampleLibrary.LogFragment"
android:id="@+id/secondary_log_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
Fragmentos: un caso especial
Actualmente, el ecosistema de Android admite dos implementaciones distintas del Fragment
widget:
Android.App.Fragment
El fragmento "clásico" enviado con el sistema Android baseAndroidX.Fragment.App.Fragment
EnXamarin.AndroidX.Fragment
Paquete NuGet.
Estas clases no son compatibles entre sí y, por tanto, se debe tener especial cuidado al generar código de enlace para <fragment>
los elementos de los archivos de diseño. .NET para Android debe elegir una Fragment
implementación como la predeterminada que se va a usar si el <fragment>
elemento no tiene ningún tipo específico (administrado o de otro modo) especificado. El generador de código de enlace usa el $(AndroidFragmentType)
Propiedad de MSBuild para ese propósito. El usuario puede invalidar la propiedad para especificar un tipo diferente al predeterminado. La propiedad se establece Android.App.Fragment
en de forma predeterminada y se invalida mediante los paquetes NuGet de AndroidX.
Si el código generado no se compila, el archivo de diseño debe modificarse especificando el tipo administrado del fragmento en cuestión.
Selección y procesamiento del diseño subyacente del código subyacente
Selección
De forma predeterminada, la generación de código subyacente está deshabilitada. Para habilitar el procesamiento de todos los diseños de cualquiera de los Resource\layout*
directorios que contienen al menos un único elemento con el //*/@android:id
atributo , establezca la $(AndroidGenerateLayoutBindings)
propiedad True
MSBuild en cualquiera de las dos en la línea de comandos de msbuild:
dotnet build -p:AndroidGenerateLayoutBindings=true MyProject.csproj
o en su archivo .csproj:
<PropertyGroup>
<AndroidGenerateLayoutBindings>true</AndroidGenerateLayoutBindings>
</PropertyGroup>
Como alternativa, puede dejar deshabilitado el código subyacente globalmente y habilitarlo solo para archivos específicos. Para habilitar código subyacente para un archivo determinado .axml
, cambie el archivo para que tenga una acción de compilación de . @(AndroidBoundLayout)
editando el .csproj
archivo y reemplazando AndroidResource
por AndroidBoundLayout
:
<!-- This -->
<AndroidResource Include="Resources\layout\Main.axml" />
<!-- should become this -->
<AndroidBoundLayout Include="Resources\layout\Main.axml" />
Procesamiento
Los diseños se agrupan por nombre, con plantillas similares de directorios diferentesResource\layout*
que componen un único grupo. Estos grupos se procesan como si fueran un único diseño. Es posible que en tal caso haya un conflicto de tipos entre dos widgets encontrados en diseños diferentes que pertenecen al mismo grupo. En tal caso, la propiedad generada no podrá tener el tipo de widget exacto, sino un "decaído". La descomposición sigue el algoritmo siguiente:
Si todos los widgets en conflicto son
View
derivados, el tipo de propiedad seráAndroid.Views.View
Si todos los tipos en conflicto son
Fragment
derivados, el tipo de propiedad seráAndroid.App.Fragment
Si los widgets en conflicto contienen y
View
,Fragment
el tipo de propiedad seráglobal::System.Object
Código generado
Si está interesado en cómo busca el código generado para los diseños, eche un vistazo a la carpeta del directorio de la obj\$(Configuration)\generated
solución.