Compartilhar via


Visão geral

Como parte do build do .NET para Android, os recursos do Android são processados, expondo IDs do Android por meio de um assembly gerado _Microsoft.Android.Resource.Designer.dll . Por exemplo, dado o arquivo Reources\layout\Main.axml com conteúdo:

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

Em seguida, durante o tempo de compilação, um _Microsoft.Android.Resource.Designer.dll assembly com conteúdo semelhante a:

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, a interação com Resources seria feita em código, usando as constantes do Resource tipo e do 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 do Xamarin.Android 8.4, há duas maneiras adicionais de interagir com os recursos do Android ao usar o C#:

  1. Associações
  2. Code-Behind

Para habilitar esses novos recursos, defina o $(AndroidGenerateLayoutBindings) MSBuild para True qualquer um na linha de comando msbuild:

dotnet build -p:AndroidGenerateLayoutBindings=true MyProject.csproj

ou em seu arquivo .csproj:

<PropertyGroup>
    <AndroidGenerateLayoutBindings>true</AndroidGenerateLayoutBindings>
</PropertyGroup>

Associações

Uma associação é uma classe gerada, uma por arquivo de layout do Android, que contém propriedades fortemente tipadas para todos os IDs dentro do arquivo de layout. Os tipos de associação são gerados no global::Bindings namespace, com nomes de tipo que espelham o nome do arquivo de layout.

Os tipos de vinculação são criados para todos os arquivos de layout que contêm IDs do Android.

Dado o arquivo Resources\layout\Main.axmlde layout do 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>

Em seguida, o seguinte tipo será gerado:

// 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);
  }
}

O tipo base da associação não faz parte da biblioteca de classes do .NET para Android, Xamarin.Android.Design.LayoutBinding mas é fornecido com o .NET para Android no formato de origem e incluído no build do aplicativo automaticamente sempre que as associações são usadas.

O tipo de associação gerado pode ser criado em torno Activity de instâncias, permitindo acesso fortemente tipado a IDs dentro do arquivo de 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!";
    };
  }
}

Os tipos de associação também podem ser construídos em torno View de instâncias, permitindo acesso fortemente tipado a IDs de recursos dentro do View ou de seus filhos:

var binding = new Binding.Main (some_view);

IDs de recursos ausentes

As propriedades em tipos de associação ainda são usadas FindViewById<T>() em sua implementação. Se FindViewById<T>() retornar null, o comportamento padrão é que a propriedade lance um em InvalidOperationException vez de retornar null.

Esse comportamento padrão pode ser substituído passando um delegado do manipulador de erros para a associação gerada em sua instanciação:

// 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);
  }
}

O OnLayoutItemNotFound() método é invocado quando uma ID de recurso para a View ou a Fragment não pôde ser encontrada.

O manipulador deve retornar , nullcaso em que o InvalidOperationException será lançado ou, de preferência, retornar a View instância ou Fragment que corresponde à ID passada para o manipulador. O objeto retornado deve ser do tipo correto correspondente ao tipo da propriedade Binding correspondente. O valor retornado é convertido para esse tipo, portanto, se o objeto não for digitado corretamente, uma exceção será lançada.

Code-behind

Code-Behind envolve a geração em tempo de compilação de uma partial classe que contém propriedades fortemente tipadas para todas as ids dentro do arquivo de layout.

O Code-Behind é compilado com base no mecanismo Binding, ao mesmo tempo em que exige que os arquivos de layout "aceitem" a geração Code-Behind usando o novo xamarin:classes atributo XML, que é uma ;lista separada de nomes de classe completos a serem gerados.

Dado o arquivo Resources\layout\Main.axmlde layout do 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>

Em tempo de compilação, o seguinte tipo será produzido:

// 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;
  }
}

Isso permite um uso mais "intuitivo" de IDs de recursos no 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!";
    };
  }
}

O OnLayoutItemNotFound manipulador de erros pode ser passado como o último parâmetro de qualquer sobrecarga da SetContentView atividade que esteja usando:

// 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 o Code-Behind depende de classes parciais, todas as declarações de uma classe parcial devem ser usadas partial class em sua declaração, caso contrário, um erro do compilador CS0260 C# será gerado no momento da compilação.

Personalização

O tipo Code Behind gerado sempre substitui Activity.SetContentView(), e, por padrão , sempre chama base.SetContentView(), encaminhando os parâmetros. Se isso não for desejado, um dos OnSetContentView()partial métodos deve ser substituído, definindo callBaseAfterReturn como: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);
  }
}

Exemplo de código gerado

// 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 layout

Muitos novos atributos XML de layout controlam o comportamento de vinculação e code-behind, que estão dentro do xamarin namespace XML (xmlns:xamarin="http://schemas.xamarin.com/android/xamarin/tools"). Estão incluídos:

xamarin:classes

O xamarin:classes atributo XML é usado como parte do Code-Behind para especificar quais tipos devem ser gerados.

O xamarin:classes atributo XML contém uma ;lista separada de nomes de classe completos que devem ser gerados.

xamarin:managedType

O xamarin:managedType atributo layout é usado para especificar explicitamente o tipo gerenciado para expor a ID associada. Se não for especificado, o tipo será inferido a partir do contexto de declaração, por exemplo <Button/> , resultará em um Android.Widget.Button, e <fragment/> resultará em um Android.App.Fragment.

O xamarin:managedType atributo permite declarações de tipo mais explícitas.

Mapeamento de tipo gerenciado

É bastante comum usar nomes de widget com base no pacote Java de onde eles vêm e, com a mesma frequência, o nome .NET gerenciado desse tipo terá um nome diferente (estilo .NET) na terra gerenciada. O gerador de código pode realizar uma série de ajustes muito simples para tentar corresponder ao código, como:

  • Coloque em maiúscula todos os componentes do namespace e do nome do tipo. Por exemplo, java.package.myButton se tornaria Java.Package.MyButton

  • Coloque em maiúscula os componentes de duas letras do namespace de tipo. Por exemplo, android.os.SomeType se tornaria Android.OS.SomeType

  • Procure vários namespaces embutidos em código que tenham mapeamentos conhecidos. Atualmente, a lista inclui os seguintes mapeamentos:

    • android.view ->Android.Views
    • com.actionbarsherlock ->ABSherlock
    • com.actionbarsherlock.widget ->ABSherlock.Widget
    • com.actionbarsherlock.view ->ABSherlock.View
    • com.actionbarsherlock.app ->ABSherlock.App
  • Procure vários tipos embutidos em código em tabelas internas. Atualmente, a lista inclui os seguintes tipos:

  • Remova o número de prefixos de namespace embutidos em código. Atualmente, a lista inclui os seguintes prefixos:

    • com.google.

Se, no entanto, as tentativas acima falharem, você precisará modificar o layout que usa um widget com esse tipo não mapeado para adicionar a xamarin declaração de namespace XML ao elemento raiz do layout e ao xamarin:managedType elemento que requer o mapeamento. Por exemplo:

<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á o CommonSampleLibrary.LogFragment tipo para o tipo commonsamplelibrary.LogFragmentnativo .

Você pode evitar adicionar a declaração de namespace XML e o xamarin:managedType atributo simplesmente nomeando o tipo usando seu nome gerenciado, por exemplo, o fragmento acima pode ser redeclarado da seguinte maneira:

<fragment
    android:name="CommonSampleLibrary.LogFragment"
    android:id="@+id/secondary_log_fragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
/>

Fragmentos: um caso especial

Atualmente, o ecossistema Android oferece suporte a duas implementações distintas do Fragment widget:

Essas classes não são compatíveis entre si e, portanto, deve-se tomar cuidado especial ao gerar código de associação para <fragment> elementos nos arquivos de layout. O .NET para Android deve escolher uma Fragment implementação como padrão a ser usada se o <fragment> elemento não tiver nenhum tipo específico (gerenciado ou não) especificado. O gerador de código de associação usa o $(AndroidFragmentType) MSBuild para essa finalidade. A propriedade pode ser substituída pelo usuário para especificar um tipo diferente do padrão. A propriedade é definida como Android.App.Fragment por padrão e é substituída pelos pacotes NuGet do AndroidX.

Se o código gerado não for compilado, o arquivo de layout deverá ser alterado especificando o tipo gerenciado do fragmento em questão.

Seleção e processamento de layout code-behind

Seleção

Por padrão, a geração code-behind está desabilitada. Para habilitar o processamento de todos os layouts em qualquer um dos diretórios que contêm pelo menos um único elemento com o //*/@android:id atributo, defina a $(AndroidGenerateLayoutBindings) propriedade MSBuild como True na linha de Resource\layout* comando msbuild:

dotnet build -p:AndroidGenerateLayoutBindings=true MyProject.csproj

ou em seu arquivo .csproj:

<PropertyGroup>
  <AndroidGenerateLayoutBindings>true</AndroidGenerateLayoutBindings>
</PropertyGroup>

Como alternativa, você pode deixar o code-behind desabilitado globalmente e habilitá-lo apenas para arquivos específicos. Para habilitar o Code-Behind para um arquivo específico .axml , altere o arquivo para ter uma ação de Build de @(AndroidBoundLayout) editando seu .csproj arquivo e substituindo AndroidResource por AndroidBoundLayout:

<!-- This -->
<AndroidResource Include="Resources\layout\Main.axml" />
<!-- should become this -->
<AndroidBoundLayout Include="Resources\layout\Main.axml" />

Processing

Os layouts são agrupados por nome, com modelos com nomes semelhantes de diferentesResource\layout* diretórios que compõem um único grupo. Esses grupos são processados como se fossem um único layout. É possível que, nesse caso, haja um conflito de tipo entre dois widgets encontrados em layouts diferentes pertencentes ao mesmo grupo. Nesse caso, a propriedade gerada não poderá ter o tipo exato de widget, mas sim um "decaído". O decaimento segue o algoritmo abaixo:

  1. Se todos os widgets conflitantes forem View derivados, o tipo de propriedade será Android.Views.View

  2. Se todos os tipos conflitantes forem Fragment derivados, o tipo de propriedade será Android.App.Fragment

  3. Se os widgets conflitantes contiverem a View e a Fragment, o tipo de propriedade será global::System.Object

Código gerado

Se você estiver interessado em como o código gerado se parece com seus layouts, dê uma olhada na pasta no diretório da obj\$(Configuration)\generated solução.