воскресенье, 13 марта 2011 г.

Android XML Drawables

В Android довольно продуманная система организации ресурсов. И особого внимания заслуживают ресурсы-изображения.

О самих картинках сказать можно мало что. Основная фича тут такая: можно адаптировать картинки для разных разрешений и размеров экранов и складывать в специализированные папки. Для различных разрешений экрана используются drawable-hdpi, drawable-mdpi и пр.. Аналогичное деление для размеров экрана: drawable-normal, drawable-large и т.д.. Можно даже одно с другим совмещать и создавать папки типа drawable-normal-hdpi. Изображения, которые должны оставаться неизменными вне зависимости от разрешения экрана, следует размещать в папке drawable-nodpi.

Но тема данной статьи несколько другая. Изображение (drawable) в Android-приложении — это не обязательно картинка. Бывают еще изображения, заданные с помощью XML. Их-то мы и рассмотрим подробнее. Будет много XML и мелких картинок.

В документации приведен список и краткое описания типов изображений, которые можно использовать в приложении. В статье же описываются только следующие:

Shape Drawable (ShapeDrawable)
Геометрическая форма.
Layer List (LayerDrawable)
Изображение, состоящее из нескольких других изображений.
State List (StateListDrawable)
Правило, согласно которому задаются изображения для различных состояний компонента.

Shape Drawable

Как уже было сказано ранее, изображение — это не всегда картинка. Можно создавать и собственные изображения на основе геометрических фигур.

Вот простой пример:

<?xml version="1.0" encoding="utf-8"?>
<selector
  xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <shape android:shape="rectangle">
            <solid android:color="#0377BE"/>
            <stroke android:width="2dip" android:color="#084B72" />
        </shape>
    </item>
</selector>

Здесь мы задали прямоугольник, его цвет и абрис. Если выведем это изображение в ImageView, получим следующее:

Solid rectangle

Типов фигур, которые мы можем задавать, всего 4:

ИзображениеРазметкаПримечание
rectangle (Прямоугольник)Solid rectangle

shape_rect.xml

<selector
  xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <shape android:shape="rectangle">
            <solid android:color="#0377BE"/>
        </shape>
    </item>
</selector>
Атрибут android:shape здесь необязателен: rectangle — это значение по умолчанию.
oval (Эллипс)Oval

shape_oval.xml

<?xml version="1.0" encoding="utf-8"?>
<selector
  xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <shape android:shape="oval">
            <solid android:color="#FCD366"/>
        </shape>
    </item>
</selector>
ring (Кольцо)Ring

shape_ring.xml

<?xml version="1.0" encoding="utf-8"?>
<selector 
  xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
      <shape android:shape="ring" android:innerRadiusRatio="3"
          android:thicknessRatio="5.333">
          <solid android:color="#7DBE15"/>
      </shape>
    </item>
</selector>

Для кольца имеются дополнительные атрибуты:

innerRadius
Внутренний радиус
innerRadiusRatio
Отношение между внешним и внутренним радиусами
thickness
Толщина кольца (т.е. разница между внешним и внутренним радиусами)
thicknessRatio
Отношение ширины кольца к его толщине
line (Горизонтальная линия)Line

shape_ring.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <shape android:shape="line">
            <stroke android:width="1dip" android:color="#F20107" />
        </shape>
    </item>
</selector>
Линия может быть только горизонтальной

Рассмотрим дополнительные возможности рисования.

Скругление углов

Актуально для формы rectangle. Имеется специальный тег corners. Можно скруглить все углы:

rectangle_rounded_all.xml

<?xml version="1.0" encoding="utf-8"?>
<selector
  xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <shape>
            <solid android:color="#B902B0"/>
            <corners android:radius="10.0dip" />
        </shape>
    </item>
</selector>
Углы скруглены одинаково

А можно задать радиусы для всех углов отдельно:

rectangle_rounded_some.xml

<?xml version="1.0" encoding="utf-8"?>
<selector
  xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <shape>
            <solid android:color="#EC6118"/>
            <corners android:bottomRightRadius="0.1dp"
                 android:bottomLeftRadius="7dip"
                 android:topLeftRadius="7dip"
                 android:topRightRadius="0.1dp"/>
        </shape>
    </item>
</selector>
Углы скруглены по-разному

Градиентная заливка

Все любят градиенты. Градиенты — это хорошо и полезно. В нашем мире без них просто нельзя обойтись. И конечно, очень здорово, когда есть средство простого создания градиентов без использования дополнительных изображений.

Вы наверно заметили, что во всех примерах выше для задания сплошной заливки мы использовали тег solid. Несложно догадаться, что для тег для задания градиентов называется gradient.

Доступны градиенты следующих видов:

ИзображениеРазметкаПримечание
linearЛинейный градиент

gradient_linear.xml

<?xml version="1.0" encoding="utf-8"?>
<selector
  xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <shape>
            <gradient android:type="linear" android:endColor="#3C0000" android:startColor="#FF0202" android:angle="45.0" />
            <corners android:radius="10.0dip" />
        </shape>
    </item>
</selector>
  • android:type="linear" можно опустить, оно так и есть по умолчанию.
  • Атрибут android:angle используется только линейным градиентом. А еще он должен быть кратен 45.
radialРадиальный градиент

gradient_radial.xml

<?xml version="1.0" encoding="utf-8"?>
<selector
  xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <shape>
            <gradient android:type="radial" android:endColor="#3C0000" android:startColor="#FF0202" android:gradientRadius="50"/>
            <corners android:radius="10.0dip" />
        </shape>
    </item>
</selector>
  • android:gradientRadius является обязательным для радиального градиента, а у остальных игнорируется.
  • Имеются также атрибуты android:centerX и android:centerY, в которых можно задавать относительное (0.0 – 1.0) расположение центра градиента.
sweepКонический градиент градиент

gradient_sweep.xml

<?xml version="1.0" encoding="utf-8"?>
<selector
  xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <shape>
            <gradient android:type="sweep" android:endColor="#3C0000" android:startColor="#FF0202" />
            <corners android:radius="10.0dip" />
        </shape>
    </item>
</selector>
  • android:centerX и android:centerY также можно использовать.

В общем, с градиентами все довольно просто: type, startColor и endColor. Также иногда оказывается полезным атрибут centerColor.

Другие полезные свойства форм

stroke

Где-то выше мы уже задавали абрис для формы. Тут все просто: тег stroke, у которого есть атрибуты width (толщина) и color (цвет). Собственно, хочется обозначить еще два атрибута — dashWidth и dashGap, с помощью которых можно сделать абрис не простой линией, а штриховой.

padding

Внутреннее поле. Соответственно, атрибуты top, bottom, left, right.

Layer List

С помощью XML-разметки можем задавать не только простые формы, но и их комбинации. Для этого служит класс LayerDrawable. Объяснять тут особенно нечего, просто приведу пару примеров:

Пример 1: Кнопка с бликом

Кнопка с бликом

Такую кнопку можно сотворить с помощью такого XML-файла:

layers_button.xml

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
  <item>
    <shape>
        <gradient android:startColor="#339999" android:endColor="#006666"  android:angle="-90.0"/>
        <corners android:radius="10.0dip" />
    </shape>
  </item>
  <item android:bottom="20dip">
    <shape>
        <solid android:color="#88339999"/>
        <corners android:bottomRightRadius="0.1dip"
             android:bottomLeftRadius="0.1dip"
             android:topLeftRadius="10dip"
             android:topRightRadius="10dip"/>
     </shape>
  </item>
</layer-list>

Пример 2: Марка

Марка

Строится из следующего XML-файла:

layers_postmark.xml

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
  <item>
      <shape>
          <solid android:color="#ffffff"/>
          <stroke android:width="5dip" android:color="#ffffff" android:dashWidth="4dp" android:dashGap="4dp" />
          <corners android:radius="4dip" />
          <padding android:left="5dip" android:top="5dip" android:right="5dip" android:bottom="5dip"/>
      </shape>
  </item>
  <item>
      <shape>
          <solid android:color="#FFFFE1"/>
          <stroke android:width="1dip" android:color="#4A3321"/>
          <padding android:left="10dip" android:top="10dip" android:right="10dip" android:bottom="10dip"/>
      </shape>
  </item>
  <item>
      <bitmap android:src="@drawable/image"/>
  </item>
</layer-list>

State List

У некоторых элементов управления есть некоторые состояния. Вроде: нажато, выбрано, зачекано и т.д.. В Android есть возможность задавать изображения для любого из таких состояний. Рассмотрим примеры.

Пример 1

Вот стандартная кнопка, выглядящая довольно уныло:

Стандартная кнопка

И вот, разработчик хочет, чтобы она выглядела как-нибудь эдак:

Красивая кнопка

Да еще и чтобы подсвечивалась при нажатии.

Сделать это просто. Добавляем в ресурсы изображения нормального (button_up.png) и нажатого (button_down.png) состояний кнопки. И создаем в папке drawables XML-файл со следующим содержанием:

states_button.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true" android:drawable="@drawable/button_down" />
    <item android:drawable="@drawable/button_up" />
</selector>

Важный момент: в списке состояний нужно сначала указывать специализированные состояния, а последним должно следовать состояние по умолчанию (без атрибутов state-* вообще).

Кстати говоря, ранее в статье не приводилось примера использования XML-изображения в стилизации элементов управления. Тут ничего сложного: с точки зрения Андроида это то же самое, что обычная картинка. В разметке можно обращаться к ней по имени (@drawable/resource-name). Вот так:

main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent" android:layout_height="match_parent">

    <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
        android:background="@drawable/states_button"
        android:text="Custom button"
        android:textSize="18dip"
    />
</RelativeLayout>

В результате получим означенный эффект.

Доступны следующие состояния со вполне очевидными названиями:

  • state_focused
  • state_window_focused
  • state_enabled
  • state_checkable
  • state_checked
  • state_selected
  • state_active
  • state_pressed

Для списков есть также:

  • state_single
  • state_first
  • state_middle
  • state_last

Пример 2

Можно использовать уже упомянутые нами геометрические изображения:

states_shapes.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true">
        <shape>
            <gradient android:type="linear" android:endColor="#6699CC" android:startColor="#99CCFF" android:angle="90.0" />
            <corners android:radius="5.0dip" />
            <padding android:left="20dip" android:right="20dip" android:top="7dip" android:bottom="7dip"/>
        </shape>
    </item>
    <item>
        <shape>
            <gradient android:type="linear" android:endColor="#336699" android:startColor="#99CCFF" android:angle="90.0" />
            <corners android:radius="5.0dip" />
            <padding android:left="20dip" android:right="20dip" android:top="7dip" android:bottom="7dip"/>
        </shape>
    </item>
</selector>

Результат будет таким:

Shape button

Пример 3

Похожим образом можно задавать стили для RadioButtom, CheckBox и т.п.. Но тут мало того, что можно отдельным ресурсом задавать фон, так отдельным же ресурсом можно задавать саму пиктограмму (атрибут button).

Вот, например, изображение

states_compound.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_checked="true">
      <shape android:shape="oval">
          <gradient android:startColor="#00FF00" android:endColor="#00000000" android:type="radial" android:gradientRadius="7"/>
          <size android:width="10dip" android:height="10dip" />
      </shape>
    </item>
    
    <item>
      <shape android:shape="oval">
          <gradient android:startColor="#FF0000" android:endColor="#00000000" android:type="radial" android:gradientRadius="7"/>
          <size android:width="10dip" android:height="10dip" />
       </shape>
    </item>
</selector>

Так — разметка:

main.xml

<CheckBox android:layout_width="wrap_content" android:layout_height="wrap_content"
    android:button="@drawable/states_compound"
    android:text="Item 1"
    android:paddingLeft="20dip"
/>
<CheckBox android:layout_width="wrap_content" android:layout_height="wrap_content"
    android:button="@drawable/states_compound"
    android:checked="true"
    android:text="Item 2"
    android:paddingLeft="20dip"
/>
<CheckBox android:layout_width="wrap_content" android:layout_height="wrap_content"
    android:button="@drawable/states_compound"
    android:text="Item 3"
    android:paddingLeft="20dip"
/>

А таким будет результат:

Заключение

Итак, мы рассмотрели некоторые случаи использования XML drawables. Это далеко не все, что можно творить с ресурсами-изображениями, но наиболее распространенные задачи.

Исходники

Ссылки

22 комментария:

vovkab комментирует...

Спасибо, очень подробно. Будет очень многим полезно.

Одно дополнение к закругленным краям:
для того что бы убрать закругление у некоторых углов, нужно в начале установить android:radius в значение больше 1, затем просетить каждый угол отдельно, например:

android:radius="7dp"
android:topLeftRadius="7dp"
android:topRightRadius="7dp"
android:bottomLeftRadius="0dp"
android:bottomRightRadius="0dp"

Максим Юдин комментирует...

Спасибо за статью :) Ты всегда пишешь большие подробные статьи :)

Анонимный комментирует...

Спасибо

Dima L. комментирует...

Очень познавательно! Спасибо!

Alex Bonel комментирует...

Даша, Вы настоящий молодец. Приятно осознавать, что в ряду Российских разработчиков ПО для мобильных платформ выделяются девушки. Очень полезный блог и статья)))

android_manjak комментирует...

Спасибо за замечательную статью, Дарья

Анатолий комментирует...

Спасибо за статью.

zak_dk комментирует...

Спасибо огромное! А можно фоном shape поставить текстурку png, что бы она повторялась?

alexeym комментирует...

В Intelliji 10.5.2 Community Edition IDE ругается на попытку создать структуру Drawable:



ругается: 'drawable' attribute should be defined


Eclipse все обрабатывает нормально.

В чем проблема?

G.L. комментирует...

Дарья, спасибо за интересные статьи! У меня вопрос по аттрибутам android:innerRadiusRatio и android:thicknessRatio - нигде не нашел на него ответа. Как они работают? Скажем в ситуации если у нас кольцо с внешним радиусом R1 и внутренним R2. По документации получается что
innerRadiusRatio = 2*r1 / r2;
thicknessRatio = 2*r1 / r1 - r2;
Но в таком случае, путем нехитрых преобразований, легко получить что параметры должы быть связаны! По формуле thicknessRatio*(1-2/innerRadiusRatio)=2. А мы задаем их независимо, и что самое интересное, по факту - разный результат на экране...

AlexKozlov комментирует...

Поделитесь, каким блог-клиентом пользуетесь? Ваши статьи так хорошо оформлены, что прямо зависть берет, для меня вставка кода и XML вечная проблема

darja комментирует...

@AlexKozlov
HTML руками + собственный подсветчик синтаксиса на Перле, который всё мечтаю переписать.

cvstsk комментирует...

не подскажете, как реализовать подчеркивание у активного чекбокса?

cvstsk комментирует...

реализовал, поправив 9patch и включив его в ресурсы своего приложения. спасибо за статью!

Elena Akimenko комментирует...

Спасибо за статью :)
Остался вопрос, как бы оставить края закруглёнными при заливке повторяющимся bitmap?

Алекс Зезекало комментирует...

Познавательная статья, спасибо! У меня вопрос: а как заюзать допустим shape:rectangle с параметрами, определенными в XML, как объект в приложении, тобы в приложени можно было бы задавать координаты этого объекта, а остальные характеристики - в XML?

vshelkov комментирует...

Пример с тором не работает.
Нужно так





Gally Knum комментирует...

Раньше учился по вашей шпаргалке MVVM, теперь учусь по android )
Спасибо большое за статьи!

Дима Степанченко комментирует...
Этот комментарий был удален автором.
Дима Степанченко комментирует...
Этот комментарий был удален автором.
Григорий Мочалин комментирует...

ОГРОМНОЕ спасибо))))

Den комментирует...

Посмотрите онлайн генератор XML Android Shapes: http://shapes.softartstudio.com/
Иногда удобно им пользоваться.