Обзор
В рамках сборки .NET для Android ресурсы Android обрабатываются, предоставляя идентификаторы Android через созданную _Microsoft.Android.Resource.Designer.dll
сборку.
Например, учитывая файл Reources\layout\Main.axml
с содержимым:
<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>
Затем во время сборки сборки _Microsoft.Android.Resource.Designer.dll
с содержимым, похожим на следующее:
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;}
}
}
Традиционно взаимодействие с ресурсами выполняется в коде с помощью констант из Resource
типа и FindViewById<T>()
метода:
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!";
};
}
}
Начиная с Xamarin.Android 8.4, существует два дополнительных способа взаимодействия с ресурсами Android при использовании C#:
Чтобы включить эти новые функции, задайте $(AndroidGenerateLayoutBindings)
Свойство MSBuild в True
командной строке msbuild:
dotnet build -p:AndroidGenerateLayoutBindings=true MyProject.csproj
или в CSPROJ-файле:
<PropertyGroup>
<AndroidGenerateLayoutBindings>true</AndroidGenerateLayoutBindings>
</PropertyGroup>
Привязки
Привязка — это созданный класс, один на файл макета Android, который содержит строго типизированные свойства для всех идентификаторов в файле макета. Типы привязки создаются в global::Bindings
пространстве имен с именами типов, которые отражают имя файла макета.
Типы привязки создаются для всех файлов макета, содержащих все идентификаторы Android.
Учитывая файл Resources\layout\Main.axml
макета 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>
затем будет создан следующий тип:
// 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);
}
}
Базовый тип привязки не является частью библиотеки классов .NET для Android, Xamarin.Android.Design.LayoutBinding
а не поставляется с .NET для Android в исходной форме и включается в сборку приложения автоматически при использовании привязок.
Созданный тип привязки можно создать вокруг Activity
экземпляров, что позволяет строго типизированным доступом к идентификаторам в файле макета:
// 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!";
};
}
}
Типы привязки также могут создаваться вокруг View
экземпляров, позволяя строго типизированный доступ к идентификаторам ресурсов в представлении или его дочерних элементах:
var binding = new Binding.Main (some_view);
Отсутствующие идентификаторы ресурсов
Свойства типов привязки по-прежнему используются FindViewById<T>()
в их реализации. Если FindViewById<T>()
возвращается null
, то поведение по умолчанию предназначено для того, чтобы свойство вызывалось InvalidOperationException
вместо возврата null
.
Это поведение по умолчанию может быть переопределено путем передачи делегата обработчика ошибок созданной привязке в его экземпляре:
// 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);
}
}
Метод OnLayoutItemNotFound()
вызывается при обнаружении идентификатора ресурса для View
Fragment
не удалось найти.
Обработчик должен возвращать null
либо , в этом случае InvalidOperationException
будет возникать исключение или, предпочтительно, возвращать View
или Fragment
экземпляр, соответствующий идентификатору, переданного обработчику. Возвращаемый объект должен иметь правильный тип, соответствующий типу соответствующего свойства Binding. Возвращаемое значение приводится к такому типу, поэтому если объект не правильно типизированный исключение будет создано.
код программной части
Code-Behind включает создание partial
класса во время сборки, содержащего строго типизированные свойства для всех идентификаторов в файле макета .
Code-Behind создает на вершине механизма привязки, требуя, чтобы файлы макета "согласились" на создание Code-Behind с помощью нового xamarin:classes
XML-атрибута, который является разделенным списком ;
имен полных классов для создания.
Учитывая файл Resources\layout\Main.axml
макета 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>
Во время сборки будет создан следующий тип:
// 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;
}
}
Это позволяет использовать более интуитивно понятные идентификаторы ресурсов в макете:
// 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!";
};
}
}
Обработчик OnLayoutItemNotFound
ошибок можно передать в качестве последнего параметра любой SetContentView
перегрузки действия:
// 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;
}
}
Поскольку Code-Behind использует частичные классы, все объявления частичного класса должны использоваться partial class
в объявлении, в противном случае ошибка компилятора C# CS0260 будет создана во время сборки.
Пользовательская настройка
Созданный тип кода за ним всегда переопределяетсяActivity.SetContentView()
, и по умолчанию он всегда вызывает base.SetContentView()
параметры пересылки. Если это не нужно, то один из OnSetContentView()
partial
методов должен быть переопределен, для параметра :callBaseAfterReturn
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);
}
}
Пример созданного кода
// 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;
}
}
Атрибуты XML макета
Многие новые xml-атрибуты макета управляют поведением xamarin
привязки и кода позади, которые находятся в пространстве имен XML (xmlns:xamarin="http://schemas.xamarin.com/android/xamarin/tools"
).
Например:
xamarin:classes
xamarin:classes
АТРИБУТ XML используется в составе Code-Behind, чтобы указать, какие типы следует создать.
xamarin:classes
XML-атрибут содержит разделенный ;
список полных имен классов, которые следует создать.
xamarin:managedType
xamarin:managedType
Атрибут макета используется для явного указания управляемого типа для предоставления привязанного идентификатора как. Если этот тип не указан, тип будет выводиться из декларающего контекста, например<Button/>
, приведет к Android.Widget.Button
<fragment/>
возникновению и приведет к Android.App.Fragment
возникновению.
Атрибут xamarin:managedType
позволяет использовать более явные объявления типов.
Сопоставление управляемых типов
В управляемой земле часто используются имена мини-приложений на основе пакета Java, из которых они приходят, и, как часто, управляемое имя .NET такого типа будет иметь другое имя (стиль .NET) в управляемой земле. Генератор кода может выполнить ряд очень простых настроек, чтобы попытаться сопоставить код, например:
Заглавная буква всех компонентов пространства имен типа и имени. Например,
java.package.myButton
станетJava.Package.MyButton
Заглавная буква компонентов пространства имен типа. Например,
android.os.SomeType
станетAndroid.OS.SomeType
Просмотрите ряд жестко закодированных пространств имен, которые имеют известные сопоставления. В настоящее время список содержит следующие сопоставления:
android.view
->Android.Views
com.actionbarsherlock
->ABSherlock
com.actionbarsherlock.widget
->ABSherlock.Widget
com.actionbarsherlock.view
->ABSherlock.View
com.actionbarsherlock.app
->ABSherlock.App
Поиск ряда жестко закодированных типов во внутренних таблицах. В настоящее время список включает следующие типы:
WebView
->Android.Webkit.WebView
Количество префиксов жестко закодированного пространства имен. В настоящее время список содержит следующие префиксы:
com.google.
Однако если приведенные выше попытки завершаются сбоем, необходимо изменить макет, использующий мини-приложение с таким несопоставленным типом, чтобы добавить xamarin
объявление пространства имен XML в корневой элемент макета и xamarin:managedType
в элемент, требующий сопоставления. Например:
<fragment
android:id="@+id/log_fragment"
android:name="commonsamplelibrary.LogFragment"
xamarin:managedType="CommonSampleLibrary.LogFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
Будет использовать CommonSampleLibrary.LogFragment
тип для собственного типа commonsamplelibrary.LogFragment
.
Вы можете избежать добавления объявления пространства имен XML и xamarin:managedType
атрибута, просто именуя тип с помощью управляемого имени, например приведенный выше фрагмент может быть переопределен следующим образом:
<fragment
android:name="CommonSampleLibrary.LogFragment"
android:id="@+id/secondary_log_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
Фрагменты: особый случай
Экосистема Android в настоящее время поддерживает две различные реализации Fragment
мини-приложения:
Android.App.Fragment
Классический фрагмент, поставляемый с базовой системой AndroidAndroidX.Fragment.App.Fragment
ВXamarin.AndroidX.Fragment
Пакет NuGet.
Эти классы несовместимы друг с другом, поэтому при создании кода привязки для <fragment>
элементов в файлах макета необходимо учитывать особое внимание. .NET для Android должен выбрать одну Fragment
реализацию в качестве используемой по умолчанию, если <fragment>
элемент не имеет определенного типа (управляемого или другого). Генератор кода привязки использует $(AndroidFragmentType)
Свойство MSBuild для этой цели. Свойство может быть переопределено пользователем, чтобы указать тип, отличный от типа по умолчанию. Свойство задано Android.App.Fragment
по умолчанию и переопределяется пакетами NuGet AndroidX.
Если созданный код не выполняет сборку, файл макета необходимо изменить, указав управляемый тип фрагмента.
Выбор и обработка макета программной части
Выбор
По умолчанию поколение кода отключается. Чтобы включить обработку для всех макетов в любом Resource\layout*
из каталогов, содержащих по крайней мере один элемент с //*/@android:id
атрибутом, задайте $(AndroidGenerateLayoutBindings)
для свойства MSBuild значение True
в командной строке msbuild:
dotnet build -p:AndroidGenerateLayoutBindings=true MyProject.csproj
или в CSPROJ-файле:
<PropertyGroup>
<AndroidGenerateLayoutBindings>true</AndroidGenerateLayoutBindings>
</PropertyGroup>
Кроме того, вы можете оставить код отключенным глобально и включить его только для определенных файлов. Чтобы включить Code-Behind для определенного .axml
файла, измените файл на действие сборки @(AndroidBoundLayout)
изменив .csproj
файл и заменив AndroidResource
на AndroidBoundLayout
:
<!-- This -->
<AndroidResource Include="Resources\layout\Main.axml" />
<!-- should become this -->
<AndroidBoundLayout Include="Resources\layout\Main.axml" />
Обработка
Макеты группируются по имени с такими же именами шаблонов из разныхResource\layout*
каталогов, состоящих из одной группы. Такие группы обрабатываются так, как если бы они были одним макетом. В таком случае может возникнуть столкновение типов между двумя мини-приложениями, найденными в разных макетах, принадлежащих одной группе. В таком случае созданное свойство не сможет иметь точный тип мини-приложения, а скорее "разложенный". Разложение соответствует приведенному ниже алгоритму:
Если все конфликтующие мини-приложения являются
View
производными, тип свойства будетAndroid.Views.View
Если все конфликтующие типы являются
Fragment
производными, тип свойства будетAndroid.App.Fragment
Если конфликтующие мини-приложения содержат как a,
View
так и aFragment
, тип свойства будетglobal::System.Object
Созданный код
Если вы заинтересованы в том, как созданный код ищет макеты, просмотрите папку obj\$(Configuration)\generated
в каталоге решения.