Представим, что вам необходимо поменять размер текста и цвет. Вам придётся внести изменения в 6 местах. Специально для того, чтобы локализировать изменения в одном месте мы воспользуемся стилем. Мы видим, что во всех TextView повторяются атрибуты:
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:textSize="16sp"
Добавление стилей
Зайдём в файл styles.xml, который располагается по пути app/res/value, и создадим стиль с этими атрибутами. Там же будет находиться AppTheme для нашего приложения, но мы пока оставим его без внимания.
Вы можете заметить, что старые атрибуты удалились из элементов, а вместо них появился один новый style="@style/TextViewPrimary. Так стили и работают.
Когда вы запустите приложение или посмотрите на панель Preview, то увидите, что все атрибуты применяются как и прежде. Теперь вы можете изменять размер текста, его цвет, отступы в одном месте. Очень важно создавать стили на ранних этапах проектирования layout. Однако они необходимы только в том случае, когда атрибуты действительно должны переиспользоваться в нескольких элементах.
Давайте сделаем наш layout чуть более приближенный к реальности. Помним, что часть TextView должна отображаться чёрным цветом, а часть – серым.
Для такого случая необходимо создать второй стиль. Мы можем скопировать первый стиль, просто поменяв цвет текста.
Как только вы поняли, что вы копируете часть кода (xml или java, да и вообще любого кода) это признак того, что вы что-то делаете не так.
Наследование стилей
Явное наследование стилей
Именно для таких случаев в Android существует наследование стилей. Мы можем унаследоваться от нашего первого стиля и переопределить (как методы в java) атрибуты, которые мы унаследовали от нашего parent-стиля:
styles.xml
<resources>
<!-- Остальные элементы сверху не изменились-->
<stylename="TextViewPrimary">
<itemname="android:layout_width">wrap_content</item>
<itemname="android:layout_height">wrap_content</item>
<itemname="android:textSize">16sp</item>
<itemname="android:layout_marginTop">5dp</item>
</style>
<stylename="TextViewSecondary"parent="TextViewPrimary">
<itemname="android:textColor">@color/gray_dove_light</item>
</style>
</resources>
Выглядит уже лучше, но предположим, что нам надо добавить атрибут marginBottom к стилю TextViewPrimary, но в стиле TextViewSecondary нам не нужен этот атрибут. В этом случае нам нужно будет переопределить этот атрибут, поставив ему значение ноль в TextViewSecondary:
styles.xml
<resources>
<!-- Остальные элементы сверху не изменились-->
<stylename="TextViewPrimary">
<itemname="android:layout_width">wrap_content</item>
<itemname="android:layout_height">wrap_content</item>
<itemname="android:textSize">16sp</item>
<itemname="android:layout_marginTop">5dp</item>
<itemname="android:layout_marginBottom">10dp</item>
</style>
<stylename="TextViewSecondary"parent="TextViewPrimary">
<itemname="android:textColor">@color/gray_dove_light</item>
<!-- встречаете 0dp, значит что-то пошло не так -->
<itemname="android:layout_marginBottom">0dp</item>
</style>
</resources>
В будущем нам нужно будет делать так с каждым атрибутом. Это опять должно вас наталкивать на мысль, что что-то пошло не так. Для того, чтобы избежать этого, нам необходимо создать базовый стиль с общими атрибутами и унаследовать от него два наших стиля. Также давайте добавим цвет @color/black к нашему основному тексту, чтобы он брался не из темы, а из нашего стиля:
styles.xml
<resources>
<!-- Остальные элементы сверху не изменились-->
<stylename="TextView">
<itemname="android:layout_width">wrap_content</item>
<itemname="android:layout_height">wrap_content</item>
<itemname="android:textSize">16sp</item>
<itemname="android:layout_marginTop">5dp</item>
</style>
<stylename="TextViewPrimary"parent="TextView">
<itemname="android:textColor">@color/black</item>
</style>
<stylename="TextViewSecondary"parent="TextView">
<itemname="android:textColor">@color/gray_dove_light</item>
</style>
</resources>
Так наши стили организованы намного удобнее с точки зрения чтения кода и сопровождения. Если нам надо добавить какой-то общий атрибут, то мы добавляем его в стиль TextView. Если в какой-то другой, то добавляем его в одно место, нигде ничего не исправляя.
Неявное наследование с помощью символа “точка” (.)
Также стоит отметить, что в стилях, которые мы создали, мы можем использовать неявное наследование. Вместо этого просто дописываем точку после стиля, от которого хотим унаследоваться. Вот как выглядят наши стили, если использовать этот подход наследования:
styles.xml
<resources>
<!-- Остальные элементы сверху не изменились-->
<stylename="TextView">
<itemname="android:layout_width">wrap_content</item>
<itemname="android:layout_height">wrap_content</item>
<itemname="android:textSize">16sp</item>
<itemname="android:layout_marginTop">5dp</item>
</style>
<stylename="TextView.Primary">
<itemname="android:textColor">@color/black</item>
</style>
<stylename="TextView.Secondary">
<itemname="android:textColor">@color/gray_dove_light</item>
</style>
</resources>
Такой подход выглядит более читабельным. Такие стили мы и оставим с вами для дальнейшего использования.
Важно: мы не можем использовать этот подход, если наследуемся от стилей библиотеки Android.
TextAppearance
В Android нет множественного наследования стилей, хотя иногда это может быть полезным.
Для этого для атрибутов текста в Android можно использовать свойство TextAppearance. Оно по факту представляет из себя ещё один стиль, но принимающий только атрибуты текста.
Чаще всего в приложении текст выглядит схожим в элементах Button, EditText, TextView. Однако все остальные атрибуты у них могут различаться. Поэтому можно создать стили для текста и включать их в стили всех остальных компонентов.
Посмотрим на наш экран ещё раз:
Красными прямоугольниками выделен текст, который должен отображаться серым цветом (Secondary). Т.е. у нас в приложении текст будет делиться на Primary, Secondary. Т.к размер у текста пока один и тот же, создадим базовый стиль Text, в котором оставим размер текста. Добавим эти стили:
styles.xml
<resources>
<!-- Остальные элементы сверху не изменились-->
<stylename="Text">
<itemname="android:textSize">16sp</item>
</style>
<stylename="Text.Primary">
<itemname="android:textColor">@color/black</item>
</style>
<stylename="Text.Secondary">
<itemname="android:textColor">@color/gray_dove_light</item>
</style>
</resources>
Теперь давайте добавим использование наших стилей текста в стили TextView:
styles.xml
<resources>
<!-- Остальные элементы сверху не изменились-->
<stylename="TextView">
<itemname="android:layout_width">wrap_content</item>
<itemname="android:layout_height">wrap_content</item>
<itemname="android:layout_marginTop">5dp</item>
<!--включаем стиль текста с помощью атрибута-->
<itemname="android:textAppearance">@style/Text</item>
</style>
<stylename="TextView.Primary">
<!--включаем стиль текста с помощью атрибута-->
<itemname="android:textAppearance">@style/Text.Primary</item>
</style>
<stylename="TextView.Secondary">
<!--включаем стиль текста с помощью атрибута-->
<itemname="android:textAppearance">@style/Text.Secondary</item>
</style>
</resources>
На данном этапе это может выглядеть бесполезно, но это очень важный шаг. В будущем при добавлении других элементов с таким же отображением текста мы просто сможем сослаться на соответствующий стиль для текста.
Теперь добавим использование этих стилей в файл activity_user_info.xml. Помним, что элементы user_nick_text_view, user_location_text_view, following_text_view, followers_text_view отображаются как текст серого цвета (Secondary). А все остальные, как основной (Primary) текст. В результате наш activity_user_info.xml выглядит так:
Можем заметить, что элементы followers_text_view, following_text_view отображаются на английском языке. Это связано с тем, что мы ссылаемся на ресурсы, и во вкладке Preview по умолчанию подтягиваются ресурсы из стандартного файла values/strings.xml. Это можно изменить, в верхнем баре режима Preview выбрав язык, для которого Android Studio будет подтягивать ресурсы. Во время запуска приложения на русском языке всё будет отображаться так, как нам и надо.
Удаление атрибута marginTop из базового стиля
Один момент, который нам лучше решить сейчас, а то в будущем с ним будет много проблем. Мы оставили атрибут marginTop в базовом стиле TextView. Мы будем использовать этот стиль ещё на нескольких экранах нашего приложения, причём атрибут marginTop нам будет мешать.
Обычно в базовые стили редко добавляют атрибуты, которые влияют на местоположение элемента: gravity, layoutGravity, margin. Потому что это вопрос времени, когда появится атрибут, которому этот стиль будет мешать.
У нас есть два варианта, как решить эту проблему:
Создать стили TextView.Primary.MarginTop, TextView.Secondary.MarginTop, TextView.Header.MarginTop, TextView.Bold.MarginTop.
Указать атрибуты marginTop непосредственно в layout, а не в стилях.
Первый подход выглядит привлекательнее. Однако представьте ситуацию, когда у нас появился ещё один атрибут (допустим padding). Причём у части элементов он есть, а у части отсутствует. Тогда нам придётся создать ещё кучу стилей такого вида: TextView.Primary.MarginTop.Padding, TextView.Primary.Padding и т.д.
Если вы пишите непосредственно название атрибута в стиле, это может наталкивать на мысль, что вам лучше написать этот атрибут непосредственно в layout файле. Зато стили будут более организованными, читабельными.
Давайте удалим из стиля TextView атрибут <item name="android:layout_marginTop">5dp</item> и добавим его к каждому TextView в нашем layout.
styles.xml
<resources>
<!-- Остальные элементы сверху не изменились-->
<stylename="TextView">
<itemname="android:layout_width">wrap_content</item>
<itemname="android:layout_height">wrap_content</item>
<!--включаем стиль текста с помощью атрибута-->
<itemname="android:textAppearance">@style/Text</item>
</style>
<!-- Остальные элементы снизу не изменились-->
</resources>
В нашем layout стало чуть больше кода. И добавилась ещё одна проблема: представим, что мы решили, что отступ должен быть равен 6dp. Теперь нам надо внести изменение в 6 местах.
Знакомство с файлом dimens
Для решения этой проблемы в Android есть файл, в котором можно хранить размеры отступов, текстов. Файл называется dimens.xml. Давайте создадим его: нажмём правой кнопкой по папке res/values, выберем New -> Value resource file
Введём имя файла dimens.
Файл создался. Добавим в него строку вида:
<dimen name="text_small_margin">5dp</dimen>
Догадались? Теперь давайте в нашем layout файле будем использовать ссылку на этот ресурс вместо того, чтобы писать 5dp. Теперь нам достаточно поменять значения атрибута только в одном месте.
Стили нужны, чтобы локализировать изменения в xml коде.
Когда чувствуете, что копируете xml разметку, остановитесь и вынесите переиспользуемые атрибуты в стиль.
Лучше всего, когда вы не переопределяете в наследуемом стиле атрибуты parent. Если вы так делаете, то стоит задумать о создании базового стиля, который будет хранить только общие атрибуты.
Стили для текста рекомендуется хранить в TextAppearance стилях. Это позволяет комбинировать стили элемента и стили текста.
Если вы используете одно и то же значение размера, отступа в нескольких местах, то лучше вынести это значение в файл dimens и просто ссылаться на него. Это позволит вносить изменение в одном месте.
Сайт использует cookie-файлы для того, чтобы вам было удобнее им пользоваться. Для
продолжения работы с сайтом, вам необходимо принять использование cookie-файлов.
Код начала урока:
gitzip
Структура урока:
Видео версия урока
Файл
styles.xml
нужен для того, чтобы объединять повторяющиеся атрибуты элементов в стили. Давайте рассмотрим это сразу на практике.На прошлом уроке мы закончили на такой структуре файла.
activity_user_info.xml
Представим, что вам необходимо поменять размер текста и цвет. Вам придётся внести изменения в 6 местах. Специально для того, чтобы локализировать изменения в одном месте мы воспользуемся стилем. Мы видим, что во всех
TextView
повторяются атрибуты:android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:textSize="16sp"
Добавление стилей
Зайдём в файл
styles.xml
, который располагается по пути app/res/value, и создадим стиль с этими атрибутами. Там же будет находитьсяAppTheme
для нашего приложения, но мы пока оставим его без внимания.styles.xml
Мы добавили стиль
TextViewPrimary
. Посмотрим как применить его в нашемlayout
файле:activity_user_info.xml
Вы можете заметить, что старые атрибуты удалились из элементов, а вместо них появился один новый
style="@style/TextViewPrimary
. Так стили и работают.Когда вы запустите приложение или посмотрите на панель
Preview
, то увидите, что все атрибуты применяются как и прежде. Теперь вы можете изменять размер текста, его цвет, отступы в одном месте. Очень важно создавать стили на ранних этапах проектированияlayout
. Однако они необходимы только в том случае, когда атрибуты действительно должны переиспользоваться в нескольких элементах.Давайте сделаем наш
layout
чуть более приближенный к реальности. Помним, что частьTextView
должна отображаться чёрным цветом, а часть – серым.Для такого случая необходимо создать второй стиль. Мы можем скопировать первый стиль, просто поменяв цвет текста.
Как только вы поняли, что вы копируете часть кода (
xml
илиjava
, да и вообще любого кода) это признак того, что вы что-то делаете не так.Наследование стилей
Явное наследование стилей
Именно для таких случаев в
Android
существует наследование стилей. Мы можем унаследоваться от нашего первого стиля и переопределить (как методы вjava
) атрибуты, которые мы унаследовали от нашегоparent
-стиля:styles.xml
Выглядит уже лучше, но предположим, что нам надо добавить атрибут
marginBottom
к стилюTextViewPrimary
, но в стилеTextViewSecondary
нам не нужен этот атрибут. В этом случае нам нужно будет переопределить этот атрибут, поставив ему значение ноль вTextViewSecondary
:styles.xml
В будущем нам нужно будет делать так с каждым атрибутом. Это опять должно вас наталкивать на мысль, что что-то пошло не так. Для того, чтобы избежать этого, нам необходимо создать базовый стиль с общими атрибутами и унаследовать от него два наших стиля. Также давайте добавим цвет
@color/black
к нашему основному тексту, чтобы он брался не из темы, а из нашего стиля:styles.xml
Так наши стили организованы намного удобнее с точки зрения чтения кода и сопровождения. Если нам надо добавить какой-то общий атрибут, то мы добавляем его в стиль
TextView
. Если в какой-то другой, то добавляем его в одно место, нигде ничего не исправляя.Неявное наследование с помощью символа “точка” (.)
Также стоит отметить, что в стилях, которые мы создали, мы можем использовать неявное наследование. Вместо этого просто дописываем точку после стиля, от которого хотим унаследоваться. Вот как выглядят наши стили, если использовать этот подход наследования:
styles.xml
Такой подход выглядит более читабельным. Такие стили мы и оставим с вами для дальнейшего использования.
Важно: мы не можем использовать этот подход, если наследуемся от стилей библиотеки
Android
.TextAppearance
В
Android
нет множественного наследования стилей, хотя иногда это может быть полезным.Для этого для атрибутов текста в
Android
можно использовать свойствоTextAppearance
. Оно по факту представляет из себя ещё один стиль, но принимающий только атрибуты текста.Чаще всего в приложении текст выглядит схожим в элементах
Button
,EditText
,TextView
. Однако все остальные атрибуты у них могут различаться. Поэтому можно создать стили для текста и включать их в стили всех остальных компонентов.Посмотрим на наш экран ещё раз:
Красными прямоугольниками выделен текст, который должен отображаться серым цветом (
Secondary
). Т.е. у нас в приложении текст будет делиться наPrimary
,Secondary
. Т.к размер у текста пока один и тот же, создадим базовый стильText
, в котором оставим размер текста. Добавим эти стили:styles.xml
Теперь давайте добавим использование наших стилей текста в стили
TextView
:styles.xml
На данном этапе это может выглядеть бесполезно, но это очень важный шаг. В будущем при добавлении других элементов с таким же отображением текста мы просто сможем сослаться на соответствующий стиль для текста.
Теперь добавим использование этих стилей в файл
activity_user_info.xml
. Помним, что элементыuser_nick_text_view
,user_location_text_view
,following_text_view
,followers_text_view
отображаются как текст серого цвета (Secondary
). А все остальные, как основной (Primary
) текст. В результате нашactivity_user_info.xml
выглядит так:activity_user_info.xml
Поздравляем! Мы пришли к нужному результату:
Можем заметить, что элементы
followers_text_view
,following_text_view
отображаются на английском языке. Это связано с тем, что мы ссылаемся на ресурсы, и во вкладкеPreview
по умолчанию подтягиваются ресурсы из стандартного файлаvalues/strings.xml
. Это можно изменить, в верхнем баре режимаPreview
выбрав язык, для которогоAndroid Studio
будет подтягивать ресурсы. Во время запуска приложения на русском языке всё будет отображаться так, как нам и надо.Удаление атрибута marginTop из базового стиля
Один момент, который нам лучше решить сейчас, а то в будущем с ним будет много проблем. Мы оставили атрибут
marginTop
в базовом стилеTextView
. Мы будем использовать этот стиль ещё на нескольких экранах нашего приложения, причём атрибутmarginTop
нам будет мешать.Обычно в базовые стили редко добавляют атрибуты, которые влияют на местоположение элемента:
gravity
,layoutGravity
,margin
. Потому что это вопрос времени, когда появится атрибут, которому этот стиль будет мешать.У нас есть два варианта, как решить эту проблему:
TextView.Primary.MarginTop
,TextView.Secondary.MarginTop
,TextView.Header.MarginTop
,TextView.Bold.MarginTop
.marginTop
непосредственно вlayout
, а не в стилях.Первый подход выглядит привлекательнее. Однако представьте ситуацию, когда у нас появился ещё один атрибут (допустим
padding
). Причём у части элементов он есть, а у части отсутствует. Тогда нам придётся создать ещё кучу стилей такого вида:TextView.Primary.MarginTop.Padding
,TextView.Primary.Padding
и т.д.Если вы пишите непосредственно название атрибута в стиле, это может наталкивать на мысль, что вам лучше написать этот атрибут непосредственно в
layout
файле. Зато стили будут более организованными, читабельными.Давайте удалим из стиля
TextView
атрибут<item name="android:layout_marginTop">5dp</item>
и добавим его к каждомуTextView
в нашемlayout
.styles.xml
activity_user_info.xml
В нашем
layout
стало чуть больше кода. И добавилась ещё одна проблема: представим, что мы решили, что отступ должен быть равен6dp
. Теперь нам надо внести изменение в 6 местах.Знакомство с файлом dimens
Для решения этой проблемы в
Android
есть файл, в котором можно хранить размеры отступов, текстов. Файл называетсяdimens.xml
. Давайте создадим его: нажмём правой кнопкой по папкеres/values
, выберемNew -> Value resource file
Введём имя файла
dimens
.Файл создался. Добавим в него строку вида:
<dimen name="text_small_margin">5dp</dimen>
Догадались? Теперь давайте в нашем
layout
файле будем использовать ссылку на этот ресурс вместо того, чтобы писать5dp
. Теперь нам достаточно поменять значения атрибута только в одном месте.Итоговая версия:
activity_user_info.xml
styles.xml
dimens.xml
Ключевые моменты:
xml
коде.xml
разметку, остановитесь и вынесите переиспользуемые атрибуты в стиль.parent
. Если вы так делаете, то стоит задумать о создании базового стиля, который будет хранить только общие атрибуты.TextAppearance
стилях. Это позволяет комбинировать стили элемента и стили текста.dimens
и просто ссылаться на него. Это позволит вносить изменение в одном месте.Полезные материалы:
Полный листинг изменений кода:
Code diff