В этой статье хотел бы поделиться своими наблюдениями и опытом в построении приложений под платформу 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 для 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 теге. Выносить в colors стоит цвета которые используются для стандартизации полного UI, а не каких-либо мелких деталей одного из элементов. Так сказать золотая серидина между параноей и ленью.
Надеюсь кому то эти заметка сохранят время или помогут в чем-то более глубоко разобраться.
Автор: vladlichonos