Поделиться через


Обзор

В рамках сборки .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#:

  1. Bindings
  2. Код за пределами

Чтобы включить эти новые функции, задайте $(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
  • Поиск ряда жестко закодированных типов во внутренних таблицах. В настоящее время список включает следующие типы:

  • Количество префиксов жестко закодированного пространства имен. В настоящее время список содержит следующие префиксы:

    • 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 мини-приложения:

Эти классы несовместимы друг с другом, поэтому при создании кода привязки для <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* каталогов, состоящих из одной группы. Такие группы обрабатываются так, как если бы они были одним макетом. В таком случае может возникнуть столкновение типов между двумя мини-приложениями, найденными в разных макетах, принадлежащих одной группе. В таком случае созданное свойство не сможет иметь точный тип мини-приложения, а скорее "разложенный". Разложение соответствует приведенному ниже алгоритму:

  1. Если все конфликтующие мини-приложения являются View производными, тип свойства будет Android.Views.View

  2. Если все конфликтующие типы являются Fragment производными, тип свойства будет Android.App.Fragment

  3. Если конфликтующие мини-приложения содержат как a, View так и a Fragment, тип свойства будет global::System.Object

Созданный код

Если вы заинтересованы в том, как созданный код ищет макеты, просмотрите папку obj\$(Configuration)\generated в каталоге решения.