Всем Андроид-разработчикам должен быть очень знаком код вроде:
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 для совместной работы.