Compartir a través de


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

  1. Enlaces
  2. Código subyacente

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.axmlde 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 , nullen 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.axmlde 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 falseen :

// 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 en Java.Package.MyButton

  • Capitalice los componentes de dos letras del espacio de nombres de tipo. Por ejemplo android.os.SomeType , se convertiría en Android.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:

  • 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.LogFragmentnativo .

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:

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:

  1. Si todos los widgets en conflicto son View derivados, el tipo de propiedad será Android.Views.View

  2. Si todos los tipos en conflicto son Fragment derivados, el tipo de propiedad será Android.App.Fragment

  3. Si los widgets en conflicto contienen y View , Fragmentel 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.