Строготипизированный доступ к UI-элементам в Xamarin.Android

Всем Андроид-разработчикам должен быть очень знаком код вроде:

private Button _myButton;
public Button MyButton { get { return _myButton ?? (_myButton =  this.FindViewById<Button>(Resource.Id.MyButton)); } }

Все еще пишете это руками? Используете FindViewById? Если при этом вы еще и используете Xamarin — то у меня есть решение! :)

В чем же, собственно, проблема? Вспомним, как с подобными вещами обстоит дело на других платформах. В старом-добром WinForms при добавлении на форму (Form1) кнопок или любых других элементов управления, Visual Studio создает файл дизайнера (Form1.designer.cs), в котором автогенерируется код для доступа ко всем контролам. Таким образом обращаться к кнопкам можно из Form1.cs как-то так:

button1.Text = "Some text";

В мобильной разработке для iOS с помощью Xamarin присутствует подобная же магия: при создании ViewController’ов ссылки на слинкованные контролы появляются в partial классе, и код становится очень похожим на winforms-вариант.

В Xamarin.Android, к сожалению, подобной «магии» нет, и доступ к контролам из code-behind обычно осуществляется по их идентификаторам с помощью метода FindViewById()
Например, при разметке:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <Button
        android:id="@+id/MyButton"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/Hello" />
</LinearLayout>

Чтобы назначить обработчик события для кнопки необходимо сделать следующее:

Button button = FindViewById<Button>(Resource.Id.MyButton);
button.Click += delegate { button.Text = string.Format("{0} clicks!", count++); };

Возникающие при этом проблемы довольно очевидны:

  • отсутствие строгой типизации (возможность указать неверный тип контрола);
  • можно перепутать ID элемента и сослаться на несуществующий в текущей разметке контрол;
  • приходится писать типовой код вручную;

Исправить досадное упущение и добавить .designer.cs в Xamarin.Android призвано расширение AndroidDesignerGenerator. После его установки для каждой .axml разметки шаблоном Т4 будет сгенерирован partial класс, содержащий свойства, аналогичные самому первому примеру в этой заметке.

T4-шаблон анализирует axml-разметку, и создает требуемые partial-классы. Чтобы их использовать, к вашим собственным Activity/Fragment классам тоже нужно добавить ключевое слово partial (public partial class Activity1 : Activity).
Генератор поддерживает стандартные Android-контролы, кастомные контролы при указании в разметке полного пути (вместе с namespace), а также теги include и merge.
Вместе с t4-шаблоном идет файл с настройками в формате xml, смысл которого достаточно легко можно понять:

<AndroidDesignerGenerator>
  <DefaultDesignerNamespace>AndroidApplication1</DefaultDesignerNamespace>
  <Axmls>
    <!-- Simple usage (will generate designers for all .axml files). Classname is the same as layout file name (without .axml), Namespace is your project's default namespace -->
    <Axml>.*\.axml</Axml>

    <!-- You can generate designers only for certain files by providing a regular expression filter -->   
    <!-- <Axml>(.*)Activity.axml</Axml> -->

    <!-- You could also specify a custom namespace or class names for certain layouts. You can refer to regex matches in Namespace or ClassName tags: '$1', '$2', '$3', etc. -->   
    <!-- <Axml Namespace="AndroidApplication1.$1" ClassName="$0">(.*)Activity.axml</Axml> -->

  </Axmls>
</AndroidDesignerGenerator>

Все это позволит с легкостью, удобством и строгой типизацией работать с UI элементами в Андроиде.
И даже при использовании mvvm-фреймворков типа MvvmCross, где можно создавать байндинги прямо из разметки, добавление привязок через код может быть предпочтительнее, именно за счет строгой типизации. В этом случае при переименовании свойства во ВьюМодели, о сломавшемся байндинге мы узнаем уже на этапе компиляции, а не при запуске приложения и проверке всех элементов.

Установить всё это счастье можно через nuget, пакет называется AndroidDesignerGenerator. Все исходники идут прямо в t4-шаблоне, полная свобода для кастомизаций по вашему желанию :) По запросу могу выложить исходники на github для совместной работы.

Опубликовать в Facebook
Опубликовать в Google Plus

3 комментария

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *