Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Como parte da versão .NET para Android, os recursos do Android são processados, expondo os IDs Android através de um assembly gerado _Microsoft.Android.Resource.Designer.dll .
Por exemplo, dado o ficheiro Reources\layout\Main.axml com o 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>
Depois, durante o tempo de compilação, um _Microsoft.Android.Resource.Designer.dll assembly com conteúdos semelhantes 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 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, existem duas formas adicionais de interagir com recursos Android ao usar C#:
Para ativar estas novas funcionalidades, defina a
$(AndroidGenerateLayoutBindings) Propriedade MSBuild para True qualquer um dos dois na linha de comandos msbuild:
dotnet build -p:AndroidGenerateLayoutBindings=true MyProject.csproj
ou no seu ficheiro .csproj:
<PropertyGroup>
<AndroidGenerateLayoutBindings>true</AndroidGenerateLayoutBindings>
</PropertyGroup>
Vinculações
Uma binding é uma classe gerada, uma por ficheiro de layout do Android, que contém propriedades de tipagem forte para todos os ids presentes no layout. Os tipos de ligação são gerados no espaço de nomes global::Bindings, com nomes de tipo que refletem o nome do ficheiro de layout.
Os tipos de binding são criados para todos os ficheiros de layout que contenham quaisquer IDs Android.
Dado o ficheiro de Android Layout 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>
Depois, será gerado o seguinte 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);
}
}
O tipo base da ligação, Xamarin.Android.Design.LayoutBinding, não faz parte da biblioteca de classes .NET para Android. Em vez disso, é fornecido em forma de código-fonte com o .NET para Android e incluído automaticamente no processo de compilação da aplicação sempre que se utilizam ligações.
O tipo de binding gerado pode ser criado em torno de instâncias Activity, permitindo acesso de tipo seguro a IDs dentro do ficheiro 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 ligação também podem ser construídos em torno View das instâncias, permitindo o acesso fortemente tipado a IDs de Recursos dentro da Visualização ou dos seus elementos filhos.
var binding = new Binding.Main (some_view);
IDs de Recursos em Falta
As propriedades dos tipos de ligação continuam a usar FindViewById<T>() na sua implementação. Se FindViewById<T>() devolver null, o comportamento padrão é que a propriedade lance um InvalidOperationException em vez de retornar null.
Este comportamento por defeito pode ser substituído passando um delegado de função tratador de erros para a ligação gerada na sua criaçã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 um ID de recurso para a View ou a Fragment não pode ser encontrado.
O handler deve devolver ou null, caso em que o InvalidOperationException será atirado, ou, preferencialmente, devolver a instância View ou Fragment que corresponde ao ID passado ao handler. O objeto devolvido deve ser do tipo correto, correspondendo ao tipo da propriedade de ligação correspondente. O valor devolvido é convertido para o tipo correto, logo, se o objeto não for corretamente tipado, será lançada uma exceção.
Code-Behind
Code-Behind envolve a geração em tempo de construção de uma partial classe que contém propriedades fortemente tipadas para todos os IDs dentro do ficheiro de layout.
Code-Behind baseia-se no mecanismo de Ligação, exigindo que os ficheiros de layout 'optem' pela geração de Code-Behind usando o novo atributo XML xamarin:classes, que é uma lista separada por ; dos nomes completos das classes a ser gerada.
Proposto o ficheiro de layout Android Resources\layout\Main.axml:
<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 da compilação, será produzido o seguinte 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;
}
}
Isto permite uma utilização mais "intuitiva" dos IDs de Recursos 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 erro pode ser passado como o último parâmetro de qualquer sobrecarga de SetContentView que a atividade está a usar.
// 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 na sua declaração, caso contrário será gerado um erro do compilador CS0260 C# em tempo de compilação.
Personalização
O tipo Code Behind gerado sempre sobrepõe-se a Activity.SetContentView(), e por defeito chamabase.SetContentView() sempre, encaminhando os parâmetros. Se isto não for desejado, então um dos OnSetContentView()partial métodos deve ser anulado, definindo callBaseAfterReturn para 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);
}
}
Código Gerado por Exemplo
// 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 se encontram dentro do xamarin namespace XML (xmlns:xamarin="http://schemas.xamarin.com/android/xamarin/tools").
Estes são, entre outros:
xamarin:classes
O xamarin:classes atributo XML é usado como parte do Code-Behind para especificar que tipos devem ser gerados.
O xamarin:classes atributo XML contém uma lista separada por ; dos nomes completos das classes que devem ser gerados.
xamarin:managedType
O atributo xamarin:managedType layout é usado para especificar explicitamente o tipo gerido para expor o ID vinculado como. Se não especificado, o tipo será inferido a partir do contexto declarante, por exemplo, <Button/> resultará num Android.Widget.Button, e <fragment/> resultará num Android.App.Fragment.
O xamarin:managedType atributo permite declarações de tipo mais explícitas.
Mapeamento de tipos gerido
É bastante comum usar nomes de widgets baseados no pacote Java de onde provêm e, com a mesma frequência, o nome gerido .NET desse tipo terá um nome diferente (estilo .NET) na área gerida. O gerador de código pode realizar vários ajustes muito simples para tentar corresponder ao código, tais como:
Capitalize todos os componentes do namespace e nome do tipo. Por exemplo
java.package.myButton, tornaria-seJava.Package.MyButtonCapitalize os componentes de duas letras do espaço de nomes do tipo. Por exemplo
android.os.SomeType, tornaria-seAndroid.OS.SomeTypeProcura vários namespaces codificados fixamente 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
-
Procura vários tipos codificados fixamente em tabelas internas. Atualmente, a lista inclui os seguintes tipos:
-
WebView->Android.Webkit.WebView
-
Remover o número de prefixos de namespace codificados. Atualmente, a lista inclui os seguintes prefixos:
com.google.
Se, no entanto, as tentativas acima falharem, terá de modificar o layout que usa um widget de tipo não mapeado para adicionar tanto a declaração de namespace XML xamarin ao elemento raiz do layout quanto xamarin:managedType ao 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"
/>
Vou usar o CommonSampleLibrary.LogFragment tipo para o tipo commonsamplelibrary.LogFragmentnativo.
Pode evitar adicionar a declaração do espaço de nomes XML e o atributo xamarin:managedType simplesmente nomeando o tipo usando o seu nome gerenciado, por exemplo, o fragmento acima poderia ser redeclarado 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
O ecossistema Android suporta atualmente duas implementações distintas do Fragment widget:
-
Android.App.FragmentO Fragment "clássico" veio com o sistema Android base -
AndroidX.Fragment.App.Fragment, noXamarin.AndroidX.FragmentPacote NuGet.
Estas classes não são compatíveis entre si, pelo que é necessário ter especial cuidado ao gerar código de ligação para <fragment> elementos nos ficheiros de layout. O .NET para Android deve escolher uma Fragment implementação como a implementação predefinida a ser utilizada se o elemento <fragment> não tiver nenhum tipo específico (gerido ou não) especificado. O gerador de código de ligação utiliza o
$(AndroidFragmentType) Propriedade MSBuild para esse fim. A propriedade pode ser sobreposta pelo utilizador para especificar um tipo diferente do padrão. A propriedade está definida como Android.App.Fragment por padrão e é sobrescrita pelos pacotes NuGet do AndroidX.
Se o código gerado não for compilado, o ficheiro de layout deve ser modificado especificando o tipo gerido do fragmento em questão.
Seleção e processamento do layout no code-behind
Seleção
Por padrão, a geração "code-behind" está desativada. Para permitir o processamento de todos os layouts em qualquer um dos Resource\layout* diretórios que contenham pelo menos um elemento que tenha o atributo //*/@android:id, defina a $(AndroidGenerateLayoutBindings) propriedade MSBuild para True na linha de comandos do msbuild:
dotnet build -p:AndroidGenerateLayoutBindings=true MyProject.csproj
ou no seu ficheiro .csproj:
<PropertyGroup>
<AndroidGenerateLayoutBindings>true</AndroidGenerateLayoutBindings>
</PropertyGroup>
Alternativamente, podes deixar o code-behind desativado globalmente e ativá-lo apenas para ficheiros específicos. Para ativar Code-Behind para um ficheiro em particular .axml, altere o ficheiro para ter uma ação de compilação de
@(AndroidBoundLayout) editando o seu .csproj ficheiro 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 a constituírem um único grupo. Tais grupos são processados como se fossem um único layout. É possível que, nesse caso, haja um choque de tipos entre dois widgets encontrados em layouts diferentes pertencentes ao mesmo grupo. Nesse caso, a propriedade gerada não poderá ter exatamente o tipo de widget, mas sim uma propriedade "decadente". O decaimento segue o algoritmo abaixo:
Se todos os widgets conflitantes forem
Viewderivados, o tipo de propriedade seráAndroid.Views.ViewSe todos os tipos em conflito forem
Fragmentderivados, o tipo de propriedade seráAndroid.App.FragmentSe os widgets em conflito contiverem tanto a
Viewcomo aFragment, o tipo de propriedade seráglobal::System.Object
Código gerado
Se estiver interessado em saber como o código gerado se apresenta para os seus layouts, por favor consulte a obj\$(Configuration)\generated pasta no diretório da sua solução.