Thursday, April 26, 2012

Android. Заметка на будущее

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

Хотел бы рассказать о том, как я считаю, наиболее правильно строить приложение под Android.

Layouts

Думаю каждый начинающий программист под платформу Android, сразу замечает, чтобы построить какой-либо UI, требуется создать xml файл в директории проекта /res/layout, а потом еще чудесным образом связать его с кодом на Java. По началу это кажется своебразным методом и сразу появлется ощущение того, что чего-то не хватает, и UI дизайнер в плагине Android для IDE далеко не чудо. Ну хватит разглагольствований, к делу!

Если сильно не углубляться в разнообразие устройств на рынке, что в моем случае так и вышло, есть платформа Android какой-то версии, знаем размер экрана целевого устройства. Допустим у вас такая же ситуация.

В общем случае приходиться иметь две папки /res/layout и /res/layout-land. Здесь -land выступает квалификатором (qualifier), который обознает, что любой layout в этой папке доступен только для Landscape (горизонтального) режима. Если существует layout, который одинакого выглядит для обоих режимов, вертикального и горизонтального, то его помещают в /res/layout. Android самостоятельно вызывает деструктор Activity и создает новое Activity при повороте экрана, если не указана конкретная ориентация в AndroidManifest. Таким образом, можно размещать layout с одним и тем же именем в обоих папках /res/layout и /res/layout-land, а Android позаботиться о загрузки актуального layout.

В коде Activity, как обычно, вызывается
setContentView(R.layout.[имя layout]);
Что от меня? И правда, описал в кратце то, что можно и так найти. Дело в том, что мне пришлось писать очень графически изменнное приложение. Большенство элементов были очень изменены. Первое что пришло в голову, было ошибочно. Решил написать свой дочерний компонент, от того же ListView, к примеру, и там понеслось: onDraw, dispatchDraw и др. Так мне показалось мало, еще и вбил конкретные значения в пикселях при отрисовке какого-либо элемента.

Это то, как не надо делать. Даже если нет выхода, пытайтесь до последнего не создавать компонент, а выкручиваться layout'ами. Намного лучше написать BaseAdapter для ListView, к примеру, где загружать другой layout и его контроллировать. Если выхода нет, то все значения для UI в пикселях, выносить в свойства компонента (attrs), которые будет передаваться при описании компонента в xml. Сами значения в xml, так же не указывать на прямую, а использовать dimensions, ссылки.

Давайте рассмотрим, что я подразумиваю под attrs и dimensions.

Attrs

Android предоставляет неявный способ расширять ваши нестандартные компоненты дополнительными свойствами. Потребуется создать attrs.xml файл в /res/values. Вполне вероятно, назвать данный файл можно как угодно по другому, но это название я считаю стандартом.

Содержание attrs.xml вполне доступно для чтение человеком. Давайте рассмотрим простой пример:
<?xml version="1.0" encoding="utf-8" ?>
<resources>

    <declare-styleable name="MyExampleView">
        <attr name="exampleAttrWidth" format="dimension" />
    </declare-styleable>

</resources>
Resources встречается постоянно, используется для хранение каких-либо ресурсов и впоследствии доступен в коде Java через статический класс R пакета приложения. К примеру наше объявляение доступно через R.styleable.MyExampleView. По моим наблюдениям и тому, что приходилось использовать, есть такой список format (тип свойства):
  • dimension - может быть значение типа 10px, 10dip или ссылка на @dimen/[имя значения]
  • integer - может быть значение типа 10, 5, 2. Так же думаю, что и ссылка может сработать
  • string - просто текстовое значение типа "Hello World" или ссылка на @string/[имя значения]
  • reference - ссылка на @drawable к примеру, что в свою очередь может быть @drawable, @color или что-то другое
Допустим у нас есть собственный класс, наследник View: com.android.example.view.MyExampleView. Опишем его просто:
package com.android.example.view;

// import

public class MyExampleView extends View {

 private exampleWidth;

 public MyExampleView(Context context) {
  super(context);

  // значение по умолчанию
  this.exampleWidth = 128;
 }

 public MyExampleView(Context context, AttributeSet attrs) {
  super(context, attrs);

  initialize(context, attrs, 0);
 }

 public MyExampleView(Context context, AttributeSet attrs, int defStyle) {
  super(context, attrs, defStyle);

  initialize(context, attrs, defStyle);
 }

 private void initialize(Context context, AttributeSet attrs, int defStyle) {
  // запрашиваем свойства описанные в xml
  final TypedArray styledAttributes = context.obtainStyledAttributes(attrs, R.styleable.MyExampleView, defStyle, 0);
  try {
   // считываем значение exampleWidth типа dimension в пикселях
   this.verticalSpace = styledAttributes.getDimensionPixelSize(R.styleable.MyExampleView_exampleWidth, 128 /* значение по умолчанию */);
  } finally {
   // сообщаем Android о том, что данный объект можно переиспользовать
   styledAttributes.recycle();
  }
 }
}
Таким образом мы создали собственный элемент управления, который можно настраивать прямо из xml, и данный метод очень гибок, т.к. отсутствует привязка к определенным значениям в коде, кроме по умолчанию. Чтобы создать элемент, напишим в layout:
<!-- в верхнем элементе добавить запись xmlns:example="http://schemas.android.com/apk/res/com.android.example.view" для того, чтобы была возможно писать example:[имя аттрибута] -->

<com.android.example.view.MyExampleView example:exampleWidth="@dimen/exampleWidth" />

<!-- ... -->
Dimensions

В идеальном мире все значения выносить в /res/values/dimensions.xml в формате: [значение]dp, после чего использовать в xml или коде через ссылку на @dimen/[имя]. Я советую, как и поступаю на данный момент, выносить размер текста, главных элементов приложения, к примеру смещения каких-то панелей, padding/margin по умолчанию и др. Не выносить значения для каких-то конкретных элементов, например в одном диалоге кнопка от кнопки на расстоянии в 10 пикселей.

Такой подход поможет быть уверенный, что в приложении весь текст выглядит стандартизированно, например большие заголовки в 30 пикселей, среднии в 24, а обычный текст в 16. Если не понравилось - меняем только в одном месте.

В принципе останавливать на это долго не стоит, но есть один момент. Недавно Google обновила плагин Android Dev для Eclipse, соответственно, и теперь там есть такой зверь Lint. Так вот, он мне все время подмигивал и убеждал, что надо бы использовать dp, а не px, и расписывал еще по какой такой причине. Я и поверил, сделал. А теперь давайте вспомним о Density. Насколько я понимаю, это значение показывает насколько плотно расположены пиксели друг к другу. Т.е. на пример у вас есть устройство в разрешением в 800x600. Но дело в том, что одно устройство имеет 800 * 600 пикселей, в другое 2 * 800 * 2 * 600. Думаю уловили разницу? Т.е. разрешение одно, но качество и соответственно плотность пикселей совершенно другая. И именно в этом скрывается подвох Lint. После миграции на устройство с большей плотностью, используя dp, у меня все элементы поехали, а текст стал совершенно другим размеров (на взгляд).

Как оказалось, используй я с самого начала px везде и игнорируй предупреждения Lint, я бы не тратил дополнительное время на переписывание dp на px.

Colors

Цвета в Android так же могут (должны) быть представлены в xml в форматах: argb, rgb. К примеру, белый цвет:
  • rgb = #fff. Это не #0f0f0f или #f0f0f0 - это сокращенная форма, в итоге имеем непрозрачный белый цвет #ffffffff
  • argb = #ffff. На подобии предыдущего, только включая alpha составляющую
  • rgb = #ffffff. Полная форма rgb
  • argb = #ffffffff. Полная форма argb
В принципе очень схоже на dimensions правила, обычно располагается в /res/values/colors.xml, так же в resources теге.


Надеюсь кому то эти заметка сохранят время или помогут в чем-то более глубоко разобраться. В будущем постараюсь писать чаще. Спасибо.

No comments:

Post a Comment