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 для 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, а не каких-либо мелких деталей одного из элементов. Так сказать золотая серидина между параноей и ленью.

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

UPD:
Спасибо sdfsdhgjkbmnmxc и andreich за указание ошибок в тексте.
Метки:
Поделиться публикацией
Комментарии 42
  • +3
    После миграции на устройство с большей плотностью, используя dp, у меня все элементы поехали, а текст стал совершенно других размеров (на взгляд).

    По идее наоборот должно быть. Используя dp, у вас на экранах с разной плотностью пикселей, все элементы будут выглядеть одинаково. А вот если указывать все размеры в пикселях, то на экранах с большей плотностью элементы интерфейса будут выглядеть физически меньше.
    • –1
      Увы, на практике это не так. Одинаково они будут выглядеть, если производитель изменил dp пропорционально увеличению самого экрана, а это скорее исключение, чем правило.
      • –1
        Венро. На практике я точно знаю, что разрешение определенное, и если вбить точно в пикселях, то как оказалось на 3х разных устройствах с разной шириной экрана выглядит все на своих местах.
        • 0
          Вот что что, а забивать размеры в пикселах точно не нужно!
    • +12
      Supporting multiple screens
      Custom components
      Внимательно и полностью читайте доки перед тем как писать костыли и учить людей неправильным вещам.
      • 0
        Прошу прощения, что именно я не правильно написал? Разумеется я знаю и не раз читал, то что там описано.
        • 0
          можно сказать, все статья посвящена тому, чтобы люди не использовали px. используйте dip, density independent pixel.
          Если у вас поползла верстка и контролы, это проблема не андроида и производителей, а именно вашей верстки.
          Старайтесь чаще использовать RelativeLayout, 9patch, wrap_content, веса, и если нет другого выбора, dip.
          Если выносить dipы в разные values папки либо делать картинки под разные плотности экранов, проблем с версткой не будет.
          Использовать px в разметке очень плохо.
          • 0
            Понял, в принципе и так стараюсь использовать Relative и wrap/match_content. Ну не вся статья, еще есть раздел про Custom Views и Layout, просто все заоострили внимание на dimensions. Спасибо за ответ.
            • 0
              ну я конечно утрировал, но в андроиде достаточно средств для построения хорошего интерфейса.
      • –2
        Верстка у андроида неблагодарная вещь. Я стараюсь вообще уйти от px и dp к процентным отображениям. По крайней мере на всех устройствах будет выглядеть одинаково без лишнего геморроя.
        • 0
          И как же у вас такое получается, если в Андроиде нет понятия процента для Вьюхи?
          Или вы имеете ввиду задание атрибута layout_weight?
          • 0
            Именно его я и имею в виду. В остальных местах пересчет программный.
          • +1
            Не лучшая идея. Вместо того, что бы дать больше места для контента на устройстве с большим экраном — вы просто будете растягивать элементы управления. А учитывая, что только среди телефонов распространены эканы от 2.5 до 5+ инчей причем с разными пропорциями, ваше «выглядит одинаково без лишнего геморроя» — звучит наивно.
          • +1
            Весь абзац про Dimensions — в топку. Использование px оправдано только в одном случае — если вы рисуете на чем-то вроде SurfaceView и при масштабировании опираетесь на конкретное разрешение экрана. В остальных случаях у вас элемент, который занимает пол-экрана на 320*480, будет где-то в уголке на планшете.
            Dp был сделан как независимый от устройства пиксель, и он работает, но с одной оговоркой — он хорошо масштабирует на тех наборах устройств, где разрешение и dpi возрастают примерно в равных пропорциях. Беда в том, что большинство производителей болт клали на рекомендации Гугла и в результате этого, текст который у меня выражен в dp и занимает 10% от высоты экрана на эмуляторе среднего телефона, на планшете реально занимает уже 4-5%. Соотношение размеров экрана и dpi нарушено, и все рухнуло. Решение — выносить все такие величины в стили, а файлы стилей также как и layout разносить по разным папкам с классификаторами, чтобы он подгружал тот стиль, который нужно. Муторная очень работа и, как правило, все косяки не исправишь, но самое гибкое и универсальное решение.
            • 0
              Намучавшись с layout и дизайнами. Я пришел к выводу, что буду делать hdpi-standard = mdpi-large. То есть все ресурсы high-def использую для больших экранов. Layout для большего экрана тупо умножаю на 1.5. Получается выглядит по размеру практически одинаково для на стандартных экранах и больших.

              Естественно это не везде подходит и high-def большие экраны в пролете (для них все равно надо рисовать ресурсы отдельно или будут масштабироваться). В большем случаев мне кажется это нормально, так как растягивать с телефона на планшет выглядит как-то не очень :) Особенно кнопки на весь экран.
              • 0
                Согласен. У меня не много другая ситуация, и говорю просто по опыту. Было 3 устройства с одинаковым разрешением, но разные версии андроида и производители. С DP все уехало в стороны, с PX все на месте. Так же молчу, что в 2.3 hdpi это mdpi в 4.0 (папки). Тоже проблема. Тут думаю больше подойдет к какой-то определенной задаче, определенное решение.
                • 0
                  Что-то вы наверное неверно поняли про «что в 2.3 hdpi это mdpi в 4.0». Скорее всего у вас просто у девайсов были разные размеры экрана — от этого и плотность разная была. Вот тут все хорошо описано — developer.android.com/guide/practices/screens_support.html
                • 0
                  Текст в dp? Текст должен быть в sp, потому наверное и ползет разметка.
                  • 0
                    От этого верстка точно не поползет, скорее наоборот — sp это тот же dp, только учитывающий размер шрифта, заданный пользователем в настройках системы.
                    • 0
                      ну тогда надо ставить
                      minHeight
                      gravity=center
                      и padding

                      тогда ни где ничего не будет наезжать друг на друга
                      • 0
                        Ну или грамотно использовать RelativeLayout. А то самая частая ошибка что я встречал это когда выравнивают один элемент по левому краю родительского, второй — по правому, а третий — по центру, а надо бы правее первого и левее второго…
                        • 0
                          В таком случае проще и лучше использовать LinearLayout
                          • 0
                            Не всегда. Сам раньше лепил LinearLayout направо и налево. Все же RelativeLayout намного универсальнее. А вообще — каждому из Layout-ов находится свое место.
                            • +1
                              RelativeLayout выгоден в большинстве случаев если надо и вертикально и горизонтально элементы разместить, если же элементы расположены только горизонтально или только вертикально, то это 100% LinearLayout + веса.

                              В общем использовать в зависимоти от ситуации, в каждом случае тот лэйаут который подходит больше.
                        • 0
                          Ну а вообще, лучший вариант — это, конечно, делать различный дизайн для различных размеров экранов. Потому что некоторые элементы интерфейса, которые уместны для большого экрана, вполне можно вынести, например, в меню для маленького — для лучшего восприятия основной информации. Потому что если дизайн сложный — трудно добиться одинакового восприятия на различных устройствах. Такой «универсализм» приводит к тому, что приложение и там и там смотрится не очень.
                          • 0
                            По любому надо делать не только разные, но и делать нормальную разметку, никогда не угадаешь на каком разрешении могут запустить ваше приложение.
                  • 0
                    спасибо, неплохая статья. если позволите, от себя добавлю, так как я наступал на точно такие же грабли.
                    я отказался от папок layout без указания размера экрана. совсем отказался, так как вёрстка под телефон ужасно выглядит на планшете (либо сжимается в углу, либо уродливо растягивается на весь экран с кучей свободного места). вёрстка же для планшета на телефоне превращается в кучу закрывающих друг-друга контролов.
                    сейчас у меня в проектах вёрстки layout-small (мелкие телефончики), layout-normal (большая часть современных телефонов), layout-large (большая часть планшетов и Galaxy Note) и layout-xlarge (большие планшеты, типа Galaxy Tab). конечно, работы получается в 4 раза больше, но зато экран любого устройства используется полностью. и да, для каждой из 4-х указанных папок картинки в нескольких плотностях, например drawable-normal-mdpi, drawable-normal-hdpi, drawable-xlarge-mdpi и т.д. размеры экрана и плотности берутся на основе статистики
                    • 0
                      для планшетов можно использовать фрагменты и multi pane layout. это решает проблемы с растягиванием и добавляет адекватный UX
                      • 0
                        То есть вы все же решили прочитать офф документ — Supporting multiple screens? :)
                        • 0
                          это было непростое решение
                      • 0
                        textSize в dp, не в sp, верно? ОООООООООООООООК.
                        • 0
                          > Как оказалось, используй я с самого начала px везде и игнорируй предупреждения Lint, я бы не тратил дополнительное время на переписывание dp на px.

                          Ну да, как же.

                          А теперь представим, что вы указали высоту панели в 80px, да это отлично будет смотрется на устройствах ~800х480, зато будет выглядеть как крошечная полоска на том же Galaxy nexus с его xhdpi (1280 x 720)

                          А теперь смотрим, если бы вы указали размер в dp, то ваши 80пикселов на mdpi, превратились бы в 80*320/160 = 160px, что на деле уже выглядит намного лучше, чем фиксированный размер в пикселах.

                          Cсобственно та же история с ldpi, hdpi и тд.
                          • 0
                            Согласен. Но факт остается фактом. Я пытался уточнить в статье:

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

                            Уже столько комментариев про dp/xp, думаю я и остальные хабралюди сделали вывод. Заминусовали конечно, ну да ладно. Странно, что нет комментариев на счет Custom Views.
                            • 0
                              А что там комментировать? Ну создали пустой вью и какой то аттрибут, чего обсуждать то?

                              Если вас еще не хватает критики, то вот:
                              > По моим наблюдениям и тому, что приходилось использовать, есть такой список format (тип свойства):…

                              Какие еще наблюдения?
                              Откройте документацию, там все про все подробно расписано, это не что то секретное.
                              • 0
                                Я наверное плохо искал, но могли бы вы указать о какой документации вы говорите, где можно посмотреть весь список доступных format? Я только находил в исходных кодах Android.
                                  • 0
                                    А я все таки еще раз спрошу, поделитесь ссылкой где описаны все значения для format для declare-styleable. То что вы мне предложили не описывает declare-styleable. Раз вы так любите критиковать, то пожалуйста подкрепите вашу критику:

                                    > Какие еще наблюдения?
                                    Откройте документацию, там все про все подробно расписано, это не что то секретное.
                                    • 0
                                      Да вы правы конкретно declare-styleable не описаны(косяк документации), но сами типы описаны.

                                      А значения для атрибута format впринципе все можно найти в samples в android sdk:
                                      format: color, dimensions, integer, string, reference, и уже описание самих типов, находится по ссылкам выше.
                                      В примерах так же встречается enum, и раз уж вы начали писать про attrs и declare-styleable, тоже не плохо было бы enum упомянуть.
                                      • 0
                                        О так вы много еще типов пропустили:
                                        * color
                                        * float
                                        * boolean
                                        * fraction (api 11)
                                        * enum
                                        * flag

                                        И нет информации о том, что типы в format можно совмещать.
                                        Например, если нам надо использовать цвет или drawable в качестве фона:
                                        />

                                        или если только цвет:
                                        />

                                        Так же нет информации о том, что если аттрибут существует, его можно переиспользовать, без указания format. Можно использовать как свои уже ранее описанные, так и все android:*

                                        зы На вашем месте я бы переписал статью заново.
                                        • 0
                                          сожралось:

                                          Например, если нам надо использовать цвет или drawable в качестве фона:
                                          <attr name="background" format="reference|color" />


                                          или если только цвет:
                                          <attr name="color" format="color" />
                                    • 0
                                      Особенно обратите внимание на dimensions, у вас даже половина из возможных значений не написана:
                                      Dimension: dp, sp, pt, px, mm, in
                              • 0
                                После первого запуска на устройстве определите размер экрана, вычислите положения всех контролов в пикселях и где-нибудь закешируйте раз и навсегда.

                                Шутка, но…

                                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.