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#:
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.axml
de 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 , null
caso 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.axml
de 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 tornariaJava.Package.MyButton
Coloque em maiúscula os componentes de duas letras do namespace de tipo. Por exemplo,
android.os.SomeType
se tornariaAndroid.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:
WebView
->Android.Webkit.WebView
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.LogFragment
nativo .
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:
Android.App.Fragment
O fragmento "clássico" foi lançado com o sistema Android básicoAndroidX.Fragment.App.Fragment
, noXamarin.AndroidX.Fragment
Pacote NuGet.
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:
Se todos os widgets conflitantes forem
View
derivados, o tipo de propriedade seráAndroid.Views.View
Se todos os tipos conflitantes forem
Fragment
derivados, o tipo de propriedade seráAndroid.App.Fragment
Se os widgets conflitantes contiverem a
View
e aFragment
, 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.