Compartilhar via


Visão geral

Como parte do build do .NET para Android, os recursos do Android são processados, expondo IDs do Android em 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 build, 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 recursos seria feita no 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 recursos do Android ao usar C#:

  1. Vinculações
  2. Code-Behind

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

dotnet build -p:AndroidGenerateLayoutBindings=true MyProject.csproj

ou no arquivo .csproj:

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

Vinculações

Uma vinculação é uma classe gerada, uma por arquivo de layout do Android, que contém propriedades fortemente tipadas para todas as 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.

Tipos de associação são criados para todos os arquivos de layout que contêm IDs do Android.

Dado o arquivo de layout do Android 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>

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, Xamarin.Android.Design.LayoutBinding, não faz parte da biblioteca de classes do .NET para Android, mas é fornecido com o .NET para Android no formato de código-fonte e incluído automaticamente na compilação do aplicativo sempre que as associações são usadas.

O tipo de associação gerado pode ser criado em torno das instâncias Activity, 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 vinculação também podem ser construídos em torno das instâncias View, permitindo acesso fortemente tipado a IDs de recurso dentro da View ou de seus elementos filhos:

var binding = new Binding.Main (some_view);

IDs de recurso ausentes

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

Esse comportamento padrão pode ser substituído passando um delegado de manipulador de erros para o vínculo gerado durante 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 um View ou um Fragment não foi encontrado.

O manipulador deve retornar null, caso em que o InvalidOperationException será lançado ou, preferencialmente, retornar a instância View ou Fragment que corresponde ao ID passado para o manipulador. O objeto retornado deve ser do tipo correto que corresponde ao tipo da propriedade Binding correspondente. O valor retornado é convertido nesse tipo, portanto, se o objeto não for digitado corretamente, uma exceção será gerada.

Code-Behind

Code-Behind envolve a geração durante o tempo de build de uma partial classe que contém propriedades tipadas fortemente para todos os ids dentro do arquivo de layout.

Code-Behind baseia-se no mecanismo de associação, ao mesmo tempo em que exige que os arquivos de layout optem por aderir à geração Code-Behind, usando o novo atributo XML xamarin:classes, que é uma lista separada por ; de nomes completos de classes a serem gerados.

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

No momento do build, 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 o uso mais "intuitivo" de IDs de recurso dentro do 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 que a atividade SetContentView está 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 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, ele sempre chama base.SetContentView(), encaminhando os parâmetros. Se isso não for desejado, um dos OnSetContentView()partial métodos deverá 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 Binding e Code-Behind, que estão dentro do namespace XML xamarin (xmlns:xamarin="http://schemas.xamarin.com/android/xamarin/tools"). Elas incluem:

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 de nomes de classe completos, separados por ;, que devem ser gerados.

xamarin:managedType

O xamarin:managedType atributo de layout é usado para especificar explicitamente o tipo gerenciado para expor a ID vinculada como. Se não for especificado, o tipo será inferido do contexto de declaração, por exemplo <Button/> , resultará em um Android.Widget.Buttone <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 do qual eles vêm e, igualmente, o nome .NET gerenciado desse tipo terá um nome diferente (estilo .NET) na terra gerenciada. O gerador de código pode executar vários ajustes muito simples para tentar corresponder ao código, como:

  • Capitalize todos os componentes do namespace e do nome do tipo. Por exemplo java.package.myButton , se tornaria Java.Package.MyButton

  • Capitalize componentes de duas letras do namespace de tipo. Por exemplo android.os.SomeType , se tornaria Android.OS.SomeType

  • Consulte vários namespaces codificados que têm 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 codificados 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 um tipo não mapeado para adicionar a xamarin declaração de namespace XML ao elemento raiz do layout e ao xamarin:managedType elemento que exige 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 tipo CommonSampleLibrary.LogFragment para o tipo nativo commonsamplelibrary.LogFragment.

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

<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 do Android dá 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 a 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) Propriedade 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 pode ser substituída pelos pacotes NuGet do AndroidX.

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

Seleção e processamento de layout no code-behind

Seleção

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

dotnet build -p:AndroidGenerateLayoutBindings=true MyProject.csproj

ou no 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 Compilação 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" />

Processamento

Os layouts são agrupados por nome, com modelos semelhantes de diretórios diferentesResource\layout* 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 tipos entre dois widgets encontrados em layouts diferentes que pertencem ao mesmo grupo. Nesse caso, a propriedade gerada não poderá ter o tipo exato de widget, mas sim um tipo "decaído". A decadência 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 um View e um Fragment, o tipo de propriedade será global::System.Object

Código gerado

Se você estiver interessado em como o código gerado se parece para os seus layouts, examine a pasta obj\$(Configuration)\generated no diretório da sua solução.