Приветствую, уважаемое сообщество.
В своем цикле статей по разработке Android-приложений я хочу поделиться с вами интересными и полезными приемами верстки сложных элементов управления. Мы рассмотрим как базовые приемы верстки, так и продвинутые способы ее оптимизации, которые существенно облегчают развитие и сопровождение Android-приложений, экономят время и деньги.
Первая часть предназначена для начинающих разработчиков. Я покажу, как сделать достаточно сложную кнопку исключительно версткой, не применяя Java-кода, ни тем более собственных компонентов. Знание этих приемов верстки пригодится и при работе с другими компонентами Android. По ходу статьи я буду подробно пояснять, что означают те или иные константы, атрибуты, команды и тому подобное. Но я также буду давать ссылки на официальную документацию Google, где вы можете подробно изучить каждую тему. Данная статья обзорная, я не ставлю цели привести здесь всю документацию, переведенную на русский язык. Поэтому я рекомендую изучать официальные источники, в частности те статьи, ссылки на которые я привожу здесь.
Что мы хотим сделать в данной статье? Допустим, мы делаем приложение, позволяющее включать/выключать функцию телефонии на смартфоне, в приложении будет кнопка “вкл/выкл”. Мы создадим кнопку с нашим собственным фоном и рамкой со скругленными углами. Фон и рамка будут меняться, когда кнопка получает фокус или становится нажатой. У кнопки будет текстовое название и иконка, поясняющая назначение кнопки визуально. Цвет текста и иконки также будет меняться при нажатии. Что еще? Так как кнопки “вкл./выкл.” встречаются в приложениях довольно часто, в Android уже есть готовый функционал для хранения/изменения состояния. Мы будем использовать его вместо того, чтобы изобретать собственный велосипед. Но отображение этого состояния мы сделаем свое, чтобы оно подходило нам по стилю.
Выглядеть это будет примерно так:
По левому краю кнопки располагаем иконку. Текст кнопки выравниваем по вертикали по центру, а по горизонтали левому краю. Но при этом текст не должен оказаться поверх иконки. По правому краю кнопки выравниваем индикатор включенного или выключенного телефона. Пространство между текстом на кнопке и правой иконкой индикатора “тянется” при необходимости. При нажатии кнопки фон становится серым, а рамка и все элементы на кнопке становятся белыми.
Делать все это будем только версткой. Почему верстка, а не код? Правильно сверстанная страница может легко стилизоваться, то есть ее можно менять практически до неузнаваемости простой заменой стиля. Можно даже предлагать пользователю выбирать тот стиль, который ему больше по душе. Стили в Android — это аналог каскадных таблиц CSS в HTML.
Решения, выполненные версткой, обычно компактнее аналогичных решений, выполненных кодом, при этом меньше шансов допустить ошибку. И большинство сверстанных без применения кода страниц можно просматривать и отлаживать в режиме дизайн (то есть прямо в IDE), для этого не нужно запускать приложение, ожидать деплоя и т.п.
Приступим к реализации. Для демонстрации я буду использовать Android Developer Tools (ADT), построенный на Eclipse.
Создадим новый проект. File->New->Android Application Project.
Application Name:
Project Name:
Package Name:
Остальные параметры оставим по умолчанию:
Next. На следующей странице для экономии времени позволим ADT создать для нас тестовую Activity:
Next. Иконки оставим по умолчанию, в данном случае речь не о них.
Next. Создаем пустую Activity, то есть опять же все по умолчанию:
Next. Имя Activity оставляем без измений:
Finish. Наконец-то можем перейти к делу.
Начнем с кода Layout. Откроем файл
А вот так мы задаем отображаемый пользователю текст:
Забегая вперед, скажу, что весь текст, который видит пользователь, должен храниться не в коде макетов страниц и не в Java-коде, а в ресурсах
Как
Есть еще константа
Я стараюсь использовать эти константы
Теперь, когда с основой окна разобрались, давайте перейдем к коду нашей Activity
В этом коде мы видим только одну имеющую смысл команду:
Этот метод устанавливает ресурс макета страницы, с которым будет работать Activity.
Давайте запустим наше приложение и посмотрим, что получилось:
А теперь откроем в Eclipse файл
Мы видим ту же кнопку с теми же размерами, в тех же цветах, что и на эмуляторе. Только чтобы это увидеть, нам не пришлось ждать запуска эмулятора и деплоя приложения. Сохранили время? Первый профит от верстки. В дальнейшем будем стараться обходиться без эмулятора, тестируя на нем не каждый шаг, а только готовую версию.
Теперь займемся выравниванием. Сейчас текст на кнопке выровнен по центру. Это стиль по умолчанию для кнопки. Но нам нужно выровнять текст по левому краю, сохранив выравнивание по центру по вертикали. Как это сделать? Добавим к кнопке атрибут
Этот атрибут может принимать одно или два значения, в нашем случае два. Значения разделяются вертикальной чертой
Как это выглядит:
О различных вариантах выравнивания и о том, что они означают, можно подробно почитать здесь
Попробуйте “поиграть” с этими вариантами и посмотреть, как это влияет на внешний вид кнопки. Если хотите сделать текст на кнопке жирным и/или курсивом, воспользуйтесь параметром
Зададим эти параметры нашей кнопке:
Я предпочитаю оставлять стили ближе к значениям по умолчанию, так как они тщательно подбирались высоко оплачиваемыми дизайнерами, и с моим чувством вкуса я очень сомневаюсь, что смогу сделать лучше. Данный пример призван показать читателю как можно больше возможностей, и не претендует на звание произведения искусства, поэтому прошу не пинать за то, что раньше оно выглядело лучше.
Идем дальше. Теперь нам нужно добавить иконку на кнопку. Иконка задается следующим файлом
кликните, чтобы скачать
Импортируем его в ресурсы приложения в каталог
Кроме
Как вы заметили, мы указали путь к иконке без расширения
Теперь, когда мы разобрались с drawable-ресурсами, давайте заменим фон нашей кнопки на тот, который нам нужен. Для этого мы будем использовать такую картинку
кликните, чтобы скачать
Импортируйте ее в
Это называется nine-patch drawable, об этой технике верстки подробно можно почитать здесь
Чтобы ресурс считался 9-patch, в его имени перед расширением должна присутствовать девятка, отделенная от остального имени еще одной точкой, как у нас:
Как назначить элементу такой растягиваемый фон? Откроем текст нашей кнопки и добавим ей атрибут
Выглядит не очень красиво, так как кнопка “прижалась” к сторонам экрана вплотную. Это случилось потому, что в фоне по умолчанию, который мы сменили на наш собственный, рамка кнопки была нарисована с отступом, и этот отступ мы видели на предыдущих скриншотах. Мы также можем перерисовать фон кнопки, но можем задать отступ и другим способом, и хорошо бы сразу для всех элементов окна, чтобы не дублировать код для каждого. Для этого подойдет атрибут
Что такое
Атрибут
Рассмотренные нами до сих пор атрибуты есть не только у кнопки, но и у других элементов. Например,
Идем дальше. Если запустить наше приложение, мы увидим, что при щелчке на кнопку ее внешний вид никак не меняется, то есть визуально не видно, нажата кнопка или нет. В таком виде оставлять нашу кнопку нельзя, так как пользователь не сможет понять, работает приложение, или нет.
Здесь нам на помощь приходят состояния. Когда с кнопкой ничего не происходит, она находится в не нажатом состоянии. Если на кнопке находится фокус ввода, она переходит в состояние
Начнем с фона. Зададим для нашей кнопки красную рамку, когда она в фокусе, и рамку с инвертированными цветами, когда она нажата. Для этого импортируйте в каталог
кликните, чтобы скачать
кликните, чтобы скачать
Теперь у нас есть 3 картинки фона на 3 состояния. Но атрибут
Щелкнем правой кнопкой мыши на каталоге
Finish.
Мы получили заготовку следующего содержания:
Добавим в селектор картинки для состояния
Обратите внимание, что для картинки
Как работает селектор? Когда селектор назначен какому-то элементу, он постоянно получает состояние элемента-хозяина, и возвращает ему первый из перечисленных ресурсов, который соответствует состоянию владельца.
Откроем текст нашего макета
Также затемним фон всего окна, чтобы лучше видеть белую рамку нажатой кнопки:
Теперь, если запустить приложение на эмуляторе, и нажать кнопку, мы увидим, что она меняет свой фон:
Уже неплохо, но можно и лучше. Давайте повторим процесс создания селектора для смены иконки телефона в нажатом состоянии. Импортируйте в
кликните, чтобы скачать
Создайте селектор
И в тексте кнопки замените
Теперь иконка меняется при нажатии на кнопку так же, как и фон. Осталось разобраться с цветом текста. Он пока что остается черным в любом состоянии. Если бы в нажатом виде текст тоже становился белым, как рамка и иконка, кнопка выглядела бы куда интереснее.
Управление цветом несколько отличается от картинок. Пойдем по порядку. Во-первых, цвета хранятся в отдельном каталоге, который мы должны создать. Добавим подкаталог
Далее в каталоге
И заменим его содержимое следующим:
По аналогии с картинками, здесь задаются цвета для состояния
В первом случае мы используем заранее созданный цвет в пространстве имен
Теперь вернемся к исходнику нашей кнопки и пропишем цвет текста
Теперь мы не можем видеть результат в Graphical Layout, это известная проблема плагина, которую, я надеюсь, Google когда-нибудь починит. К сожалению, тестировать цвета с состоянием можно только на эмуляторе или реальном устройстве.
На этом художественная часть верстки завершена. Сейчас кнопка выглядит так:
Далее поговорим о том, как отображать состояние телефонии на кнопке (вкл/выкл). Мы помним, что у кнопки иконка может быть не только слева. Для отображения текущего состояния мы добавим кнопке иконку справа. Пусть это будет галочка для состояния Вкл и крестик для состояния Выкл. Как мы будем менять иконки? Самый очевидный вариант — это определить обработчик события
К счастью, Android предоставляет для этих целей специальный компонент —
Так как
Теперь подготовим картинки для отображения состояния кнопки. Импортируйте в
кликните, чтобы скачать
кликните, чтобы скачать
кликните, чтобы скачать
кликните, чтобы скачать
Внимание: белые иконки на прозрачном фоне не очень здорово видны в браузере на белом фоне.
Зачем четыре иконки? Вспомним, что мы хотим отображать все элементы кнопки белыми, когда пользователь удерживает кнопку нажатой. Поэтому для каждого состояния Вкл и Выкл мы должны дать по две иконки: в нажатом и отпущенном состоянии. Итого четыре.
Создадим новый
Здесь видно, что компонент может иметь сразу несколько состояний, например он может быть одновременно отмеченным и нажатым, или отмеченным и ненажатым и так далее.
Теперь вернемся к нашей кнопке и добавим ей атрибут
Что у нас получится, если запустить приложение? Мы видим кнопку ненажатую, в режиме Выключено. При этом если ее нажать и держать, все элементы отображаются белым на сером фоне:
Отпускаем кнопку, состояние меняется на Включено. Если нажать еще раз, снова увидим серый фон и белые иконки, после чего состояние опять будет Выключено:
Именно то, что нам нужно.
На всякий случай давайте посмотрим, как можно в коде нашего приложения понять, включена кнопка или выключена. Для этого мы можем анализировать значение
В
Запустив приложение и нажав кнопку, мы увидим внизу экрана текстовые подсказки
Как видите, возможности верстки в Android весьма обширны: в данной статье весь функционал, кроме подсказок, реализован только версткой. Но это далеко не все.
В данной статье код XML намеренно оставлен неидеальным. Изучение базовых возможностей верстки и продвинутая оптимизация — это две разные темы. Конечно, настоящие гуру должны сразу писать оптимально. Но данная статья преследует учебные цели и рассчитана на начинающих разработчиков, поэтому я решил усложнять постепенно.
Всех желающих изучить пример оптимизации верстки в Android приглашаю прочесть и обсудить вторую часть данной статьи. В ней я рассматриваю следующие темы:
Эти вопросы будут изучены на примере оптимизации кода из первой части статьи. А пока благодарю всех, кто нашел время прочесть мою статью. Надеюсь, я смог показать что-то новое. Жду ваших отзывов и комментариев.
В своем цикле статей по разработке Android-приложений я хочу поделиться с вами интересными и полезными приемами верстки сложных элементов управления. Мы рассмотрим как базовые приемы верстки, так и продвинутые способы ее оптимизации, которые существенно облегчают развитие и сопровождение Android-приложений, экономят время и деньги.
Первая часть предназначена для начинающих разработчиков. Я покажу, как сделать достаточно сложную кнопку исключительно версткой, не применяя Java-кода, ни тем более собственных компонентов. Знание этих приемов верстки пригодится и при работе с другими компонентами Android. По ходу статьи я буду подробно пояснять, что означают те или иные константы, атрибуты, команды и тому подобное. Но я также буду давать ссылки на официальную документацию Google, где вы можете подробно изучить каждую тему. Данная статья обзорная, я не ставлю цели привести здесь всю документацию, переведенную на русский язык. Поэтому я рекомендую изучать официальные источники, в частности те статьи, ссылки на которые я привожу здесь.
Что мы хотим сделать в данной статье? Допустим, мы делаем приложение, позволяющее включать/выключать функцию телефонии на смартфоне, в приложении будет кнопка “вкл/выкл”. Мы создадим кнопку с нашим собственным фоном и рамкой со скругленными углами. Фон и рамка будут меняться, когда кнопка получает фокус или становится нажатой. У кнопки будет текстовое название и иконка, поясняющая назначение кнопки визуально. Цвет текста и иконки также будет меняться при нажатии. Что еще? Так как кнопки “вкл./выкл.” встречаются в приложениях довольно часто, в Android уже есть готовый функционал для хранения/изменения состояния. Мы будем использовать его вместо того, чтобы изобретать собственный велосипед. Но отображение этого состояния мы сделаем свое, чтобы оно подходило нам по стилю.
Выглядеть это будет примерно так:
По левому краю кнопки располагаем иконку. Текст кнопки выравниваем по вертикали по центру, а по горизонтали левому краю. Но при этом текст не должен оказаться поверх иконки. По правому краю кнопки выравниваем индикатор включенного или выключенного телефона. Пространство между текстом на кнопке и правой иконкой индикатора “тянется” при необходимости. При нажатии кнопки фон становится серым, а рамка и все элементы на кнопке становятся белыми.
Делать все это будем только версткой. Почему верстка, а не код? Правильно сверстанная страница может легко стилизоваться, то есть ее можно менять практически до неузнаваемости простой заменой стиля. Можно даже предлагать пользователю выбирать тот стиль, который ему больше по душе. Стили в Android — это аналог каскадных таблиц CSS в HTML.
Решения, выполненные версткой, обычно компактнее аналогичных решений, выполненных кодом, при этом меньше шансов допустить ошибку. И большинство сверстанных без применения кода страниц можно просматривать и отлаживать в режиме дизайн (то есть прямо в IDE), для этого не нужно запускать приложение, ожидать деплоя и т.п.
Создаем каркас приложения
Приступим к реализации. Для демонстрации я буду использовать Android Developer Tools (ADT), построенный на Eclipse.
Создадим новый проект. File->New->Android Application Project.
Application Name:
MysteriesOfButtonsPart1
Project Name:
MysteriesOfButtonsPart1
Package Name:
com.mysteriesofbuttons.part1
Остальные параметры оставим по умолчанию:
Next. На следующей странице для экономии времени позволим ADT создать для нас тестовую Activity:
Next. Иконки оставим по умолчанию, в данном случае речь не о них.
Next. Создаем пустую Activity, то есть опять же все по умолчанию:
Next. Имя Activity оставляем без измений:
Finish. Наконец-то можем перейти к делу.
Простейшая кнопка
Начнем с кода Layout. Откроем файл
/res/layout/activity_main.xml
и заменим все его содержимое следующим кодом:<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<Button
android:id="@+id/act_main_btn_telephony"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Телефония" />
</RelativeLayout>
RelativeLayout
— макет, в котором дочерние элементы размещаются относительно друг друга, например, расположить кнопку слева от другой кнопки и т.п. Подробнее о работе RelativeLayout
можно почитать здесь.Button
— наша кнопка. Мы задаем ей атрибут android:id
, по которому сможем идентифицировать ее в приложении. Формат @+id/
означает, что в ресурсах должен быть создан новый идентификатор. Если бы мы указали @id/
, то это бы означало, что в ресурсах указанный идентификатор уже должен быть. А вот так мы задаем отображаемый пользователю текст:
android:text="Телефония"
Забегая вперед, скажу, что весь текст, который видит пользователь, должен храниться не в коде макетов страниц и не в Java-коде, а в ресурсах
strings.xml
, чтобы приложение могло поддерживать более одного языка, но об этом подробнее в следующей части. Пока что мы укажем текст прямо в макете, чтобы улучшить наглядность примера.Как
RelativeLayout
, так и Button
имеют атрибуты android:layout_width
и android:layout_height
. Это обязательные атрибуты любого View
. Как следует из названия, они обозначают соответственно ширину и высоту элементов. Их можно задавать в различных единицах, но мы не используем конкретные размеры, как вы могли заметить. Мы используем константы match_parent
и wrap_content
. match_parent
означает, что элемент должен полностью заполнить своего родителя по горизонтали либо по вертикали, в зависимости от того, задаем ли мы ширину или высоту. wrap_content
означает, что размер элемента должен быть минимальным, но таким, чтобы все содержимое элемента в него поместилось. Есть еще константа
fill_parent
, которая означает ровно то же самое, что и match_parent
. Зачем использовать две одинаковых константы? fill_parent
был введен до версии API 8, а с восьмой версии является устаревшим и не рекомендуется к использованию. Дело в том, что для англоязычных разработчиков match_parent
точнее отражает смысл константы, чем fill_parent
.Я стараюсь использовать эти константы
match_parent
и wrap_content
везде, где только возможно, всячески избегая указания фиксированных размеров, так как приложения Android работают на устройствах с сильно отличающимися размерами экранов.Теперь, когда с основой окна разобрались, давайте перейдем к коду нашей Activity
MainActivity.java
:package com.mysteriesofbuttons.part1;
import android.app.Activity;
import android.os.Bundle;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
В этом коде мы видим только одну имеющую смысл команду:
setContentView(R.layout.activity_main);
Этот метод устанавливает ресурс макета страницы, с которым будет работать Activity.
Давайте запустим наше приложение и посмотрим, что получилось:
А теперь откроем в Eclipse файл
activity_main.xml
и нажмем кнопку Graphical Layout
:Мы видим ту же кнопку с теми же размерами, в тех же цветах, что и на эмуляторе. Только чтобы это увидеть, нам не пришлось ждать запуска эмулятора и деплоя приложения. Сохранили время? Первый профит от верстки. В дальнейшем будем стараться обходиться без эмулятора, тестируя на нем не каждый шаг, а только готовую версию.
Стили текста
Теперь займемся выравниванием. Сейчас текст на кнопке выровнен по центру. Это стиль по умолчанию для кнопки. Но нам нужно выровнять текст по левому краю, сохранив выравнивание по центру по вертикали. Как это сделать? Добавим к кнопке атрибут
android:gravity
: <Button
android:id="@+id/act_main_btn_telephony"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Телефония"
android:gravity="left|center_vertical" />
Этот атрибут может принимать одно или два значения, в нашем случае два. Значения разделяются вертикальной чертой
|
Как это выглядит:
О различных вариантах выравнивания и о том, что они означают, можно подробно почитать здесь
Попробуйте “поиграть” с этими вариантами и посмотреть, как это влияет на внешний вид кнопки. Если хотите сделать текст на кнопке жирным и/или курсивом, воспользуйтесь параметром
android:textStyle
, например android:textStyle="bold|italic"
. Если нужно изменить размер текста на кнопке, подойдет параметр android:textSize
, например: android:textSize="24sp"
. sp
— Scale-independent Pixels — единица измерения, основанная на плотности экрана и на настройках размера шрифта. Чтобы текст выглядел одинаково хорошо на разных экранах, рекомендуется задавать его размер именно в sp
.Зададим эти параметры нашей кнопке:
<Button
android:id="@+id/act_main_btn_telephony"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Телефония"
android:gravity="left|center_vertical"
android:textStyle="bold|italic"
android:textSize="24sp" />
Я предпочитаю оставлять стили ближе к значениям по умолчанию, так как они тщательно подбирались высоко оплачиваемыми дизайнерами, и с моим чувством вкуса я очень сомневаюсь, что смогу сделать лучше. Данный пример призван показать читателю как можно больше возможностей, и не претендует на звание произведения искусства, поэтому прошу не пинать за то, что раньше оно выглядело лучше.
Помещаем иконку на кнопку
Идем дальше. Теперь нам нужно добавить иконку на кнопку. Иконка задается следующим файлом
icon_phone_normal.png
:кликните, чтобы скачать
Импортируем его в ресурсы приложения в каталог
drawable-hdpi
. Чтобы задать иконку кнопке, добавим ей атрибут android:drawableLeft
: <Button
android:id="@+id/act_main_btn_telephony"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Телефония"
android:gravity="left|center_vertical"
android:textStyle="bold|italic"
android:textSize="24sp"
android:drawableLeft="@drawable/icon_phone_normal" />
Кроме
android:drawableLeft
, есть еще несколько атрибутов, которые позволяют задавать иконки, размещая их в других частях кнопки: android:drawableTop
, android:drawableBottom
, android:drawableRight
, android:drawableStart
, android:drawableEnd
.Как вы заметили, мы указали путь к иконке без расширения
PNG
, а также без суффикса -hdpi
. Это не опечатка. Расширение никогда не указывается, так как в имени идентификатора не может быть точки. А суффикс -hdpi
будет подставлен Android автоматически, так как это единственный каталог drawable
, в котором есть иконка icon_phone_normal
. Если бы иконка была не только в каталоге drawable-hdpi
, но и в drawable-mdpi
, к примеру, то Android выбрал бы наиболее полходящую для разрешения экрана того устройства, на котором запущено приложение. Так вы можете поставлять качественные продукты, одинаково хорошо выглядящие на устройствах с разным размером и плотностью экрана. О поддержке разных экранов у Google есть очень хорошая статья.Собственный фон для кнопки
Теперь, когда мы разобрались с drawable-ресурсами, давайте заменим фон нашей кнопки на тот, который нам нужен. Для этого мы будем использовать такую картинку
button_normal.png
:кликните, чтобы скачать
Импортируйте ее в
drawable-hdpi
. На картинке мы видим черные полоски вдоль каждой из границ экрана. Они служат для того, чтобы Android правильно растягивал картинку под нужный нам размер. Левая и верхняя линии показывают, какая область картинки будет растягиваться по вертикали и горизонтали соответственно. А правая и нижняя линии показывают, в какую область растянутой картинки нужно вписывать содержимое элемента, если у него есть дочерние элементы. При этом сами черные линии конечно же не видны на результирующей картинке.Это называется nine-patch drawable, об этой технике верстки подробно можно почитать здесь
Чтобы ресурс считался 9-patch, в его имени перед расширением должна присутствовать девятка, отделенная от остального имени еще одной точкой, как у нас:
button_normal.9.png
Как назначить элементу такой растягиваемый фон? Откроем текст нашей кнопки и добавим ей атрибут
android:background
: <Button
android:id="@+id/act_main_btn_telephony"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Телефония"
android:gravity="left|center_vertical"
android:textStyle="bold|italic"
android:textSize="24sp"
android:drawableLeft="@drawable/icon_phone_normal"
android:background="@drawable/button_normal" />
Выглядит не очень красиво, так как кнопка “прижалась” к сторонам экрана вплотную. Это случилось потому, что в фоне по умолчанию, который мы сменили на наш собственный, рамка кнопки была нарисована с отступом, и этот отступ мы видели на предыдущих скриншотах. Мы также можем перерисовать фон кнопки, но можем задать отступ и другим способом, и хорошо бы сразу для всех элементов окна, чтобы не дублировать код для каждого. Для этого подойдет атрибут
android:padding
у тега RelativeLayout
: <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="6dp" >
Что такое
dp
? Density-independent pixel — мера, которая будет автоматически масштабироваться на устройствах с разной плотностью пикселей экрана так, чтобы элемент выглядел одинаково. Всегда используйте dp
, а не px
, когда необходимо задать конкретный размер, иначе приложение будет выглядеть хорошо только на вашем телефоне.Атрибут
android:padding
задает одинаковые отступы со всех сторон. Если мы хотим задать разные отступы с каждой стороны отдельно, можем использовать атрибуты android:paddingLeft
, android:paddingRight
, android:paddingTop
, android:paddingBottom
, android:paddingStart
и android:paddingEnd
.Рассмотренные нами до сих пор атрибуты есть не только у кнопки, но и у других элементов. Например,
android:background
есть у всех видимых элементов, android:drawableLeft
— у TextEdit
и так далее.Идем дальше. Если запустить наше приложение, мы увидим, что при щелчке на кнопку ее внешний вид никак не меняется, то есть визуально не видно, нажата кнопка или нет. В таком виде оставлять нашу кнопку нельзя, так как пользователь не сможет понять, работает приложение, или нет.
Работаем с состояниями в Android
Здесь нам на помощь приходят состояния. Когда с кнопкой ничего не происходит, она находится в не нажатом состоянии. Если на кнопке находится фокус ввода, она переходит в состояние
state_focused
. Если нажать на кнопку пальцем, она будет находиться в состоянии state_pressed
, пока мы не отпустим палец. Как это нам поможет? Мы можем задавать внешний вид элементов для каждого состояния отдельно. Дальше мы детально рассмотрим, как это делается. Обратите внимание, что состояние можно использовать для отрисовки всего, что видно пользователю: иконки, картинки, отдельные цвета и т.п.Начнем с фона. Зададим для нашей кнопки красную рамку, когда она в фокусе, и рамку с инвертированными цветами, когда она нажата. Для этого импортируйте в каталог
drawable-hdpi
следующие изображения:кликните, чтобы скачать
кликните, чтобы скачать
Теперь у нас есть 3 картинки фона на 3 состояния. Но атрибут
android:background
можно задать только один раз. Чтобы выйти из ситуации, мы создадим новый drawable-ресурс selector
, объединяющий наши 3 картинки.Щелкнем правой кнопкой мыши на каталоге
drawable-hdpi
, выберем New->Android XML File. Введем название файла button_background
и выберем корневой элемент selector
:Finish.
Мы получили заготовку следующего содержания:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
</selector>
Добавим в селектор картинки для состояния
state_focused
и state_pressed
:<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:drawable="@drawable/button_pressed" android:state_pressed="true" />
<item android:drawable="@drawable/button_focused" android:state_focused="true" />
<item android:drawable="@drawable/button_normal" />
</selector>
Обратите внимание, что для картинки
button_normal
состояние не указывается. Это означает, что такая картинка будет использована всегда, если кнопка не в состоянии state_focused
или state_pressed
. Кроме рассмотренных состояний можно использовать еще несколько, полный перечень описан здесьКак работает селектор? Когда селектор назначен какому-то элементу, он постоянно получает состояние элемента-хозяина, и возвращает ему первый из перечисленных ресурсов, который соответствует состоянию владельца.
Откроем текст нашего макета
activity_main.xml
и заменим фон кнопки на button_background
: <Button
android:id="@+id/act_main_btn_telephony"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Телефония"
android:gravity="left|center_vertical"
android:textStyle="bold|italic"
android:textSize="24sp"
android:drawableLeft="@drawable/icon_phone_normal"
android:background="@drawable/button_background" />
Также затемним фон всего окна, чтобы лучше видеть белую рамку нажатой кнопки:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="6dp"
android:background="#dddddd" >
Теперь, если запустить приложение на эмуляторе, и нажать кнопку, мы увидим, что она меняет свой фон:
Изменяем иконку при нажатии
Уже неплохо, но можно и лучше. Давайте повторим процесс создания селектора для смены иконки телефона в нажатом состоянии. Импортируйте в
drawable-hdpi
иконку icon_phone_pressed.png
:кликните, чтобы скачать
Создайте селектор
icon_phone
со следующим текстом:<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:drawable="@drawable/icon_phone_pressed" android:state_pressed="true" />
<item android:drawable="@drawable/icon_phone_normal" />
</selector>
И в тексте кнопки замените
drawableLeft
на наш новый селектор icon_phone
: <Button
android:id="@+id/act_main_btn_telephony"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/button_background"
android:drawableLeft="@drawable/icon_phone"
android:gravity="left|center_vertical"
android:text="Телефония"
android:textSize="24sp"
android:textStyle="bold|italic" />
Изменяем цвет текста при нажатии
Теперь иконка меняется при нажатии на кнопку так же, как и фон. Осталось разобраться с цветом текста. Он пока что остается черным в любом состоянии. Если бы в нажатом виде текст тоже становился белым, как рамка и иконка, кнопка выглядела бы куда интереснее.
Управление цветом несколько отличается от картинок. Пойдем по порядку. Во-первых, цвета хранятся в отдельном каталоге, который мы должны создать. Добавим подкаталог
color
в каталоге res
, на одном уровне с каталогом drawable-hdpi
:Далее в каталоге
color
создадим Android XML File с именем text_color
и корневым элементом selector
:И заменим его содержимое следующим:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@android:color/white" android:state_pressed="true" />
<item android:color="#484848" />
</selector>
По аналогии с картинками, здесь задаются цвета для состояния
state_pressed
и состояния по умолчанию. Цвета здесь задаются двумя способами: android:color="@android:color/white"
и android:color="#484848"
В первом случае мы используем заранее созданный цвет в пространстве имен
android
. Во втором — указываем RGB-значение цвета в шестнадцатиричной системе исчисления. В данном случае мы задали цвет по умолчанию такой же, как цвет рамки в ненажатом виде.Теперь вернемся к исходнику нашей кнопки и пропишем цвет текста
android:textColor="@color/text_color"
: <Button
android:id="@+id/act_main_btn_telephony"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/button_background"
android:drawableLeft="@drawable/icon_phone"
android:gravity="left|center_vertical"
android:text="Телефония"
android:textSize="24sp"
android:textStyle="bold|italic"
android:textColor="@color/text_color" />
Теперь мы не можем видеть результат в Graphical Layout, это известная проблема плагина, которую, я надеюсь, Google когда-нибудь починит. К сожалению, тестировать цвета с состоянием можно только на эмуляторе или реальном устройстве.
На этом художественная часть верстки завершена. Сейчас кнопка выглядит так:
Прикручиваем ToggleButton
Далее поговорим о том, как отображать состояние телефонии на кнопке (вкл/выкл). Мы помним, что у кнопки иконка может быть не только слева. Для отображения текущего состояния мы добавим кнопке иконку справа. Пусть это будет галочка для состояния Вкл и крестик для состояния Выкл. Как мы будем менять иконки? Самый очевидный вариант — это определить обработчик события
OnClickListener
и поочередно менять иконку drawableRight
. Вполне рабочий вариант. Но что делать, если на странице не одна, а 10 кнопок, и вообще кнопка может быть не только на этой странице. Тогда наш путь приведет к дублированию кода, который будет копипастом кочевать из одной Activity в другую, не самое красивое решение. Да и если нужно будет что-то изменить, менять придется во многих местах. Хотелось бы этого избежать.К счастью, Android предоставляет для этих целей специальный компонент —
ToggleButton
. Эта кнопка может находиться в двух состояниях: включено и выключено. Заменим в нашем макете тег Button
на ToggleButton
: <ToggleButton
android:id="@+id/act_main_btn_telephony"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/button_background"
android:drawableLeft="@drawable/icon_phone"
android:gravity="left|center_vertical"
android:text="Телефония"
android:textSize="24sp"
android:textStyle="bold|italic"
android:textColor="@color/text_color" />
Так как
ToggleButton
наследуется от Button
, к ней применимы все атрибуты Button
, но есть нюанс. ToggleButton
игнорирует атрибут text
, зато вводит два новых: textOn
и textOff
. Они задают текст для включенного и выключенного состояний соответственно. Но мы хотим отображать состояние картинкой, а текст хотим оставить как есть. Поэтому пропишем наш текст обоим атрибутам, а атрибут text
уберем за ненадобностью: <ToggleButton
android:id="@+id/act_main_btn_telephony"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/button_background"
android:drawableLeft="@drawable/icon_phone"
android:gravity="left|center_vertical"
android:textOn="Телефония"
android:textOff="Телефония"
android:textSize="24sp"
android:textStyle="bold|italic"
android:textColor="@color/text_color" />
Теперь подготовим картинки для отображения состояния кнопки. Импортируйте в
drawable-hdpi
ресурсы icon_on_normal.png
, icon_on_pressed.png
, icon_off_normal.png
и icon_off_pressed.png
(представлены в порядке перечисления):кликните, чтобы скачать
кликните, чтобы скачать
кликните, чтобы скачать
кликните, чтобы скачать
Внимание: белые иконки на прозрачном фоне не очень здорово видны в браузере на белом фоне.
Зачем четыре иконки? Вспомним, что мы хотим отображать все элементы кнопки белыми, когда пользователь удерживает кнопку нажатой. Поэтому для каждого состояния Вкл и Выкл мы должны дать по две иконки: в нажатом и отпущенном состоянии. Итого четыре.
Создадим новый
drawable
селектор с именем icon_on_off
:<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:drawable="@drawable/icon_on_pressed"
android:state_checked="true"
android:state_pressed="true" />
<item
android:drawable="@drawable/icon_on_normal"
android:state_checked="true" />
<item
android:drawable="@drawable/icon_off_pressed"
android:state_checked="false"
android:state_pressed="true" />
<item
android:drawable="@drawable/icon_off_normal"
android:state_checked="false" />
</selector>
Здесь видно, что компонент может иметь сразу несколько состояний, например он может быть одновременно отмеченным и нажатым, или отмеченным и ненажатым и так далее.
android:state_checked="true"
соответствует кнопке в режиме Вкл, а android:state_checked="false"
— кнопке в режиме Выкл.Теперь вернемся к нашей кнопке и добавим ей атрибут
android:drawableRight="@drawable/icon_on_off"
. Для наглядности я добавил его сразу после android:drawableLeft
: <ToggleButton
android:id="@+id/act_main_btn_telephony"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/button_background"
android:drawableLeft="@drawable/icon_phone"
android:drawableRight="@drawable/icon_on_off"
android:gravity="left|center_vertical"
android:textOn="Телефония"
android:textOff="Телефония"
android:textSize="24sp"
android:textStyle="bold|italic"
android:textColor="@color/text_color" />
Что у нас получится, если запустить приложение? Мы видим кнопку ненажатую, в режиме Выключено. При этом если ее нажать и держать, все элементы отображаются белым на сером фоне:
Отпускаем кнопку, состояние меняется на Включено. Если нажать еще раз, снова увидим серый фон и белые иконки, после чего состояние опять будет Выключено:
Именно то, что нам нужно.
Немного кода
На всякий случай давайте посмотрим, как можно в коде нашего приложения понять, включена кнопка или выключена. Для этого мы можем анализировать значение
isChecked()
кнопки: true
— включена, false
— выключена. Добавим атрибут android:onClick="onToggleButtonClick"
к нашей кнопке: <ToggleButton
android:id="@+id/act_main_btn_telephony"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/button_background"
android:drawableLeft="@drawable/icon_phone"
android:drawableRight="@drawable/icon_on_off"
android:gravity="left|center_vertical"
android:textOn="Телефония"
android:textOff="Телефония"
android:textSize="24sp"
android:textStyle="bold|italic"
android:textColor="@color/text_color"
android:onClick="onToggleButtonClick" />
В
MainActivity.java
добавим соответствующий метод:package com.mysteriesofbuttons.part1;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
import android.widget.ToggleButton;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void onToggleButtonClick(View button) {
Toast.makeText(
getApplicationContext(),
Boolean.toString(((ToggleButton) button).isChecked()),
Toast.LENGTH_SHORT).show();
}
}
Запустив приложение и нажав кнопку, мы увидим внизу экрана текстовые подсказки
true
/false
. А это значит, что все работает.Заключение
Как видите, возможности верстки в Android весьма обширны: в данной статье весь функционал, кроме подсказок, реализован только версткой. Но это далеко не все.
В данной статье код XML намеренно оставлен неидеальным. Изучение базовых возможностей верстки и продвинутая оптимизация — это две разные темы. Конечно, настоящие гуру должны сразу писать оптимально. Но данная статья преследует учебные цели и рассчитана на начинающих разработчиков, поэтому я решил усложнять постепенно.
Всех желающих изучить пример оптимизации верстки в Android приглашаю прочесть и обсудить вторую часть данной статьи. В ней я рассматриваю следующие темы:
- использование строковых ресурсов
strings.xml
- собственные стили
styles.xml
и наследование стилей - темы
- ресурсы размерностей
dimens.xml
Эти вопросы будут изучены на примере оптимизации кода из первой части статьи. А пока благодарю всех, кто нашел время прочесть мою статью. Надеюсь, я смог показать что-то новое. Жду ваших отзывов и комментариев.
Полезные ссылки
- Готовый проект Android-приложения из данной статьи
- Тайны кнопок в Android. Часть 2: Рефакторинг верстки
- Верстка RelativeLayout
- Верстка Nine patch drawable
- Выравнивание текста в Button'ах и EditText'ах
- Поддержка экранов разных размеров и плотности
- Состояния, которые можно использовать в селекторе
- iPUMB — ПУМБ online — пример приложения, в котором я использовал описанные приемы верстки