Pull to refresh

Пишем первое приложение на Mono Android

Reading time13 min
Views57K
Здравствуйте. В этой статье я бы хотел поделиться своим опытом создания приложений для мобильной платформы Android с использованием Mono и разобрать простой пример.

Для начала работы с Mono Android необходимо установить:


Также, для создания xml layout (назовем его разметкой) нам понадобится один из графических инструментов. Я использую DroidDraw, но в сети существует множество альтернатив. Так что каждый может подобрать себе что-нибудь по вкусу.
После установки всего вышеперечисленного необходимо создать и сконфигурировать один (или несколько) эмуляторов устройств с платформой Android. Также это можно сделать при первом запуске приложения. В своем примере я использую эмулятор с версией Android 2.1.1.
Теперь можно создавать наш первый Mono Android проект.

Первый проект Mono Android


Создадим новый проект


И назовем его MyFirstMonoAndroidApplication.


Новый проект содержит несколько элементов: директории Assets и Resources и класс с названием Activity1.
Assets в данном примере затрагиваться не будут. На остальных элементах я бы хотел остановиться немного подробнее.
С ресурсами все достаточно просто. В директории Drawable хранятся, используемые в приложении, изображения. Директория Layout содержит набор xml разметок. А в директории Values хранятся различные предопределенные строковые значения, которые мы используем в своем приложении.
Все ресурсы, использующиеся в приложении, должны быть отмечены в свойствах как AndroidResource.
Перейдем к нашему классу Activity1. Как видно, он наследуется от базового класса Activity. Что такое Activity? Activity это одна определенная задача, которую может выполнить пользователь. Практически все Activity взаимодействуют с пользователем посредством графического интерфейса (xml разметки).

Обратим внимание на строку SetContentView(Resource.Layout.Main);
Именно в этом месте происходит загрузка и установка разметки для текущей Activity.
Немного изменим разметку и код самой Activity.
Пусть это будет простая форма с двумя кнопками, каждая из которых обрабатывает исполнение другой Activity.

Разметка:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical">
  <Button
  android:id="@+id/FirstExample"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  android:text="First Example"></Button>
<Button
  android:id="@+id/SecondExample"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  android:text="Second Example"></Button>
</LinearLayout>


* This source code was highlighted with Source Code Highlighter.


Основной класс приложения. Именно его мы увидим при запуске.
[Activity(Label = "My first mono android activity", MainLauncher = true, Icon = "@drawable/icon")]
 public class MyMainActivity : Activity
 {
  protected override void OnCreate(Bundle bundle)
  {
   base.OnCreate(bundle);
   SetContentView(Resource.Layout.Main);
   Button firstExample = FindViewById<Button>(Resource.Id.FirstExample);
   firstExample.Click += firstExample_Click;
   Button secondExample = FindViewById<Button>(Resource.Id.SecondExample);
   secondExample.Click += secondExample_Click;
  }

  void firstExample_Click(object sender, EventArgs e)
  {
   Intent titlesIntent = new Intent(this, typeof(TitlesListActivity));
   StartActivity(titlesIntent);
  }

  void secondExample_Click(object sender, EventArgs e)
  {
   Intent friendlyListIntent = new Intent(this, typeof(MoreFriendlyRssFeedActivity));
   StartActivity(friendlyListIntent);
  }
 }


* This source code was highlighted with Source Code Highlighter.


Код достаточно прост и понятен, за исключением использования нового класса Intent.
Что такое Intent? Это абстрактное описание выполняемой операции. Другими словами это намерение пользователя сделать что-то определенное. В нашем случае, при нажатии на кнопку, пользователь перейдет на другую Activity.
Чтобы скомпилировать код необходимо добавить классы, на которые мы ссылаемся. Через контекстное меню проекта добавляем две новых Activity с именами TitlesListActivity и MoreFriendlyRssFeedActivity.

Я хотел бы продемонстрировать несколько простых примеров, которые могут помочь в изучении Mono Android. Одна из основных вещей, с которой сталкивается любой программист, это отображение списка.
Пусть это будет список заголовков из RSS ленты. Создадим простенький класс для чтения RSS.
public class RssReader
  {
    private const string _title = "title";
    private const string _link = "link";
    private const string _item = "item";
    private const string _channel = "channel";

    private static Func<XElement, RssListItem> GetTitlesAndLinksFunc = (x => new RssListItem { Title = x.Element(_title).Value, Link = x.Element(_link).Value });

    public static IList<RssListItem> GetRssListItems(params string[] rssUris)
    {
      List<RssListItem> fullList = new List<RssListItem>();

      IEnumerable<XElement> itemsFromConcreteIteration;
      foreach (string rssUri in rssUris)
      {
        itemsFromConcreteIteration = GetRssFeedChannel(rssUri).Elements(_item);
        fullList.AddRange(itemsFromConcreteIteration.Select(GetTitlesAndLinksFunc));
      }

      return fullList;
    }

    public static IEnumerable<string> GetTitles(string rssUri)
    {
      IEnumerable<XElement> items = GetRssFeedChannel(rssUri).Elements(_item);
      return items.Select(x => x.Element(_title).Value);
    }

    public static XElement GetRssFeedChannel(string rssUri)
    {
      XElement feed = XElement.Load(rssUri);
      return feed.Element(_channel);
    }
  }

  public class RssListItem
  {
    public string Title { get; set; }
    public string Link { get; set; }
  }


* This source code was highlighted with Source Code Highlighter.


Теперь отобразим этот список в Activity с именем TitlesListActivity.
Для этого будем использовать ListActivity, чье основное (и единственное) назначение – отображение списка.
Заметим, что для него не нужно задавать xml разметку и вызывать SetContentView.

  [Activity(Label = "List")]
  public class TitlesListActivity : ListActivity
  {
    protected override void OnCreate(Bundle bundle)
    {
      base.OnCreate(bundle);
      
      var titles = RssReader.GetTitles("http://habrahabr.ru/rss/blogs/mono/");
      ListAdapter = new ArrayAdapter<string>(this, Android.Resource.Layout.SimpleListItem1, titles.ToArray());
    }
  }


* This source code was highlighted with Source Code Highlighter.


Особый интерес представляет следующий ресурс: Android.Resource.Layout.SimpleListItem1. Это один из предопределенных ресурсов, полный список которых (вместе с разметкой), можно найти по ссылке.

Его разметка:
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@android:id/text1"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  android:textAppearance="?android:attr/textAppearanceLarge"
  android:gravity="center_vertical"
  android:paddingLeft="6dip"
  android:minHeight="?android:attr/listPreferredItemHeight"
/>


* This source code was highlighted with Source Code Highlighter.


Пришло время запуска нашего приложения.


Нажатие на первую кнопку открывает новую Activity с отображением списка заголовков в RSS ленте.

Отлично! Все работает, но, конечно же, хочется чего-то более сложного.

Усложним задачу


Добавим возможность выбора конкретной RSS ленты из списка и открытия записи из ленты в браузере.
Для начала немного поработаем с ресурсами: добавим два изображения с логотипами Mono и .NET в директорию Drawable (не забываем указывать тип ресурса!). Также в файл Strings.xml (директория Values) добавим массив значений для выпадающего списка:
<?xml version="1.0" encoding="utf-8"?>
<resources>  
  <string name="ApplicationName">MyFirstMonoAndroidApplication</string>
  <string-array name="frameworks">
    <item>Mono</item>
    <item>.Net</item>
    <item>Mono and .Net</item>
  </string-array>
</resources>


* This source code was highlighted with Source Code Highlighter.


Теперь необходимо создать новую разметку MoreFriendlyRssFeed.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical">
  <Spinner
  android:id="@+id/FrameworkSelector"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content">
  </Spinner>
  <ListView
  android:id="@+id/RssEntries"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content">
  </ListView>
</LinearLayout>


* This source code was highlighted with Source Code Highlighter.


Все очень просто: Spinner для выбора ленты и ListView для отображения списка. Важно понимать, что про разметку элементов внутри списка ListView (а точнее, его адаптер), на данном этапе, ничего не знает. Исходя из данной разметки можно сказать, что ListView просто отображает «какой-то» список.

Также необходимо создать разметку для элемента списка. Назовем ее RssRow.xml. Пусть каждый элемент списка включает кнопку с изображением и текст – заголовок статьи.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  android:padding="6dip">
  <ImageButton
  android:id="@+id/LogoButton"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:src="@drawable/mono">
  </ImageButton>
  <TextView
  android:id="@+id/Title"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content">
  </TextView>
</LinearLayout>


* This source code was highlighted with Source Code Highlighter.


Настало время переходить к самому главному: реализации Activity, которая свяжет все наши части воедино.
 [Activity(Label = "Friendly List")]
 public class MoreFriendlyRssFeedActivity : Activity
 {
  protected override void OnCreate(Bundle bundle)
  {
   base.OnCreate(bundle);
   SetContentView(Resource.Layout.MoreFriendlyRssFeed);

   Spinner spinner = FindViewById<Spinner>(Resource.Id.FrameworkSelector);
   spinner.ItemSelected += spinner_ItemSelected;
   ArrayAdapter adapter = ArrayAdapter.CreateFromResource(this, Resource.Array.frameworks, Android.Resource.Layout.SimpleSpinnerItem);
   adapter.SetDropDownViewResource(
Android.Resource.Layout.SimpleSpinnerDropDownItem);
   spinner.Adapter = adapter;
  }

  void spinner_ItemSelected(object sender, ItemEventArgs e)
  {}
 }


* This source code was highlighted with Source Code Highlighter.


Обратим внимание на еще один предопределенный ресурс Android.Resource.Layout.SimpleSpinnerItem. Для удобства, также приведу его разметку:
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/text1"
style="?android:attr/spinnerItemStyle"
android:singleLine="true"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />


* This source code was highlighted with Source Code Highlighter.


Для отображения текста в списке используется класс ArrayAdapter. Если же нам необходимо отобразить нечто более сложное, то мы сталкиваемся с необходимостью реализации собственного класса, наследника от ArrayAdapter, переопределяющего метод GetView. Создадим класс RssListItemAdapter для привязки и отображения данных:
public class RssListItemAdapter: ArrayAdapter<RssListItem>
  {
    private IList <RssListItem> Items;

    public RssListItemAdapter(Context context, int textViewResourceId, IList<RssListItem> items)
      : base(context, textViewResourceId, items)
    {
      Items = items;
    }

    public override View GetView(int position, View convertView, ViewGroup parent)
    {
      View view = convertView;
      if (view == null)
      {
        LayoutInflater inflater = (LayoutInflater)Context.GetSystemService(Context.LayoutInflaterService);
        //выбираем разметку, которую будем наполнять данными.
        view = inflater.Inflate(Resource.Layout.RssRow, null);
      }

      //получаем текущий элемент
      RssListItem item = Items[position];

      //находим и привязываем к данным содержимое одного элемента в списке
      //пусть при нажатии на кнопку открывается браузер с соответствующей статьей
      ImageButton btnLogo = (ImageButton)view.FindViewById(Resource.Id.LogoButton);
      btnLogo.Click += delegate
                 {
                   Intent browserIntent = new Intent("android.intent.action.VIEW", Android.Net.Uri.Parse(item.Link));
                   Context.StartActivity(browserIntent);
                 };
      
      //добавляем картинку на кнопку (из ресурсов)
      btnLogo.SetImageResource(item.Title.StartsWith("Mono") ? Resource.Drawable.mono : Resource.Drawable.net);
      
      //устанавливаем текст
      TextView txtTitle = (TextView)view.FindViewById(Resource.Id.Title);      
      txtTitle.Text = item.Title;

      //возвращаем элемент списка с привязанными данными
      return view;
    }
  }


* This source code was highlighted with Source Code Highlighter.


Теперь возможно привязать данные к списку и отобразить их в нужном нам виде. Добавим возможность привязки к данным в обработчик события выбора элемента из списка.
    void spinner_ItemSelected(object sender, ItemEventArgs e)
    {
      ListView view = FindViewById<ListView>(Resource.Id.RssEntries);      

      switch (e.Position)
      {
        case 0:
          view.Adapter = new RssListItemAdapter(this, Resource.Layout.RssRow, RssReader.GetRssListItems("http://habrahabr.ru/rss/blogs/mono/"));
          break;
        case 1:
          view.Adapter = new RssListItemAdapter(this, Resource.Layout.RssRow, RssReader.GetRssListItems("http://habrahabr.ru/rss/blogs/net/"));
          break;
        case 2:
          view.Adapter = new RssListItemAdapter(this, Resource.Layout.RssRow, RssReader.GetRssListItems("http://habrahabr.ru/rss/blogs/mono/", "http://habrahabr.ru/rss/blogs/net/"));
          break;
      }
    }


* This source code was highlighted with Source Code Highlighter.


Запустим приложение и откроем второй пример. Теперь у нас есть возможность выбора RSS ленты (или набора лент).


Наш список будет выглядеть следующим образом:


При нажатии на кнопку рядом с заголовком, соответствующая статья откроется в браузере.


Запуск приложения на Android устройстве


Дело за малым: запустить приложение на конкретном Android устройстве. Если мы попытаемся сделать это прямо сейчас, то получим ошибку при попытке сборки apk (Android Package) файла. Также хотел бы указать, что trial версия Mono Android позволяет запускать приложения только на эмуляторе. К счастью, моя версия уже не trial, поэтому я смогу довести пример до логического завершения.
Для корректной сборки инсталляционного пакета необходимо создать AndroidManifest.xml. Этот файл содержит ключевую информацию о приложении, информацию без которой запуск приложения на устройстве невозможен.

Существует возможность создания AndroidManifest.xml через свойства проекта:


Но гораздо интереснее будет написать его руками:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="First.MonoApp" android:versionCode="1" android:versionName="1.0">
  <application android:label="MyMonoApp">
  </application>
  <uses-sdk android:minSdkVersion="4" />
  <uses-permission android:name="android.permission.INTERNET" />
</manifest>


* This source code was highlighted with Source Code Highlighter.


Теперь можно собирать apk файл. Build -> Package MyFirstMonoAndroidApplication for Android (.apk)
Копируем его на устройство с системой Android и запускаем. В процессе инсталляции будет показано предупреждение:
Allow this application to: Full access to Internet.
То есть все необходимые требования из манифеста будут показаны пользователю до инсталляции. Подтверждаем, ждем окончания инсталляции. И…


Спасибо за внимание.

Ссылка на исходный код приложения
Tags:
Hubs:
+51
Comments51

Articles