Хабрахабр

[Перевод] Как выглядит ваш текст?

Друзья, отличной всем пятницы. Хотим поделиться с вами переводом статьи, подготовленным специально для студентов курса «Android-разработчик. Продвинутый курс». Приятного прочтения.

Как декларативно стилизовать текст на Android.


Иллюстрация Вирджинии Полтрэк

Эти атрибуты можно установить непосредственно в layout’e, применить стиль к view или тему к layout’у или, если захотите, установить textAppearance. TextView в Android-приложениях предоставляет несколько атрибутов для стилизация текста и различные способы их применения. И что произойдет, если их скомбинировать? Но что же из этого следует использовать?


Что и когда использовать?

е. В этой статье описываются различные подходы к декларативной стилизации текста (т. когда вы определяете стили в XML файле), рассматриваются области применения и приоритет каждого из методов.

tl;dr;

Вам следует прочитать весь пост, но вот краткое изложение.

Помните о порядке приоритетности различных методов стилизации — если вы пытаетесь стилизовать какой-то текст и не видите ожидаемых результатов, то, скорее всего, ваши изменения переопределены чем-то с более высоким приоритетом в этой иерархии:


Иерархия методов стилизации текста

Я бы предложил следующий порядок действий для стилизации:

  1. Установите любой стиль приложения в textViewStyle как стиль по умолчанию для вашей темы.
  2. Установите (небольшой) набор TextAppearance, которые ваше приложение будет использовать (или использовать/наследовать от стилей MaterialComponent), и ссылайтесь на них прямо из ваших view
  3. Создайте style, устанавливая атрибуты, не поддерживаемые TextAppearance (которые сами собой будут определять один из ваших TextAppearance).
  4. Выполните любую уникальную стилизацию прямо в layout’е.

Продемонстрируйте немного стиля

Вы можете напрямую устанавливать атрибуты TextView в layout’е, но этот подход может быть более утомительным и небезопасным. Представьте себе, что вы таким образом пытаетесь обновить цвет всех TextView в приложении. Как и во всех других view, вы можете (и должны!) вместо этого использовать стили для обеспечения согласованности, повторного использования и простоты обновлений. С этой целью я рекомендую создавать стили для текста всякий раз, когда вы, вероятно, захотите применить один и тот же стиль к нескольким view. Это чрезвычайно просто и в значительной степени поддерживается view-системой Android.

Если вы когда-либо писали свой custom view, вы, вероятно, видели вызов context.obtainStyledAttributes(AttributeSet, int[], int, int). Что происходит под капотом, когда вы задаете стиль для view? Параметр AttributeSet, по сути, можно рассматривать как карту XML параметров, которые вы указываете в вашем layout’е. Таким образом view-система в Android передает в view атрибуты, указанные в layout’е. Таким образом, мы подошли к первому правилу приоритетности. Если AttributeSet устанавливает стиль, то стиль читается первым, а затем к нему применяются атрибуты, указанные непосредственно во view.

View → Style

Обратите внимание, что применяется сочетание атрибутов style и view; определение атрибута в view, который также указывается в стиле, не отменяет весь стиль. Атрибуты, определенные непосредственно во view, всегда «превалируют» и переопределяют атрибуты, определенные в стиле. Вы не можете получить оба варианта и выбрать. Также следует отметить, что в ваших view нет реального способа определить, откуда берется стилизация; это решается view системой за вас единожды при подобном вызове.

Одним из них является то, что вы можете применить только один стиль к view (в отличие от чего-то вроде CSS, где вы можете применять несколько классов). Хотя стили чрезвычайно полезны, у них есть свои ограничения. Если вы стилизуете текст через TextAppearance, то оставляете атрибут style свободным для других стилей, что выглядит практично. У TextView, однако, есть хитрость, он предоставляет атрибут TextAppearance, который работает аналогично style. Давайте подробнее рассмотрим, что такое TextAppearance и как он работает.

TextAppearance

В TextAppearance нет ничего волшебного (например, секретного режима для применения нескольких стилей, о котором вы не должны знать!!!!), TextView избавляет вас от некоторой лишней работы. Давайте посмотрим на конструктор TextView, чтобы понять, что происходит.

TypedArray a = theme.obtainStyledAttributes(attrs, com.android.internal.R.styleable.TextViewAppearance, defStyleAttr, defStyleRes);
TypedArray appearance = null;
int ap = a.getResourceId(com.android.internal.R.styleable.TextViewAppearance_textAppearance, -1);
a.recycle();
if (ap != -1) { appearance = theme.obtainStyledAttributes(ap, com.android.internal.R.styleable.TextAppearance);
}
if (appearance != null) { readTextAppearance(context, appearance, attributes, false); appearance.recycle();
}
// a little later
a = theme.obtainStyledAttributes(attrs, com.android.internal.R.styleable.TextView, defStyleAttr, defStyleRes);
readTextAppearance(context, a, attributes, true);

Итак, что здесь происходит? По сути, TextView сначала смотрит, указали ли вы android:textAppearance, если это так, он загружает этот стиль и применяет все свойства, которые там указаны. Позже он загружает все атрибуты из view (которые помнит, включая стиль) и применяет их. Так мы подходим ко второму правилу приоритетности:

View → Style → TextAppearance

Поскольку внешний вид текста (text appearance) проверяется первым, любые атрибуты, определенные либо непосредственно во view, либо в style, будут переопределять его.

Чтобы лучше понять, что я имею в виду, давайте вернемся к этой строке: С TextAppearance следует помнить о еще одном предостережении: он поддерживает подмножество атрибутов стиля, которые предлагает TextView.

R.styleable. obtainStyledAttributes(ap, android. TextAppearance);

Она смотрит на данный стиль (как определено первым параметром id) и фильтрует его только по атрибутам в стиле, которые появляются во втором параметре, массиве attrs. Мы рассматривали версию receiveStyledAttributes с 4 аргументами, эта 2-аргументная версия немного отличается от нее. R.styleable. Таким образом, styleable android. Глядя на это определение, мы видим, что TextAppearance поддерживает многие, но не все атрибуты, которые поддерживает TextView. TextAppearance определяет область действия TextAppearance.

<attr name="textColor" />
<attr name="textSize" />
<attr name="textStyle" />
<attr name="typeface" />
<attr name="fontFamily" />
<attr name="textColorHighlight" />
<attr name="textColorHint" />
<attr name="textColorLink" />
<attr name="textAllCaps" format="boolean" />
<attr name="shadowColor" format="color" />
<attr name="shadowDx" format="float" />
<attr name="shadowDy" format="float" />
<attr name="shadowRadius" format="float" />
<attr name="elegantTextHeight" format="boolean" />
<attr name="letterSpacing" format="float" />
<attr name="fontFeatureSettings" format="string" />

Атрибуты стилизации, поддерживаемые TextAppearance

TextAppearance работает на уровне символов, а не параграфов, поэтому атрибуты, влияющие на весь layout, не поддерживаются. Вот некоторые атрибуты TextView, которые не включены в TextAppearance: lineHeight[Multiplier|Extra], lines, breakStrategy и hyphenationFrequency.

Однако он имеет ограниченную область применения и находится в нижней части цепочки приоритетов, так что не забывайте об ограничениях. Поэтому TextAppearance очень полезен, он позволяет нам определять стиль, ориентированный на атрибуты стилизации текста, и оставляет style в view свободным для других целей.

Разумные значения по умолчанию

Когда мы рассматривали то, как view-система Android разрешает атрибуты (context.obtainStyledAttributes), мы на самом деле немного упростили ее. Она вызывает theme.obtainStyledAttributes (используя текущую Theme Context’а). При проверке ссылки показывается порядок приоритетности, который мы рассматривали ранее, и указывается еще 2 места, которые он ищет для разрешения атрибутов: стиль по умолчанию для view и тема.

При определении окончательного значения определенного атрибута в игру вступают четыре входных параметра:

  1. Любые значения атрибутов в данном AttributeSet.
  2. Ресурс стиля, указанный в AttributeSet (с именем «style»).
  3. Стиль по умолчанию, указанный в defStyleAttr и defstyleres
  4. Базовые значения в этой теме.

Порядок приоритетов стилизации из документации Theme

Что это еще за стиль по умолчанию? Мы вернемся к темам, но давайте сначала вглянем на стили по умолчанию. Когда вы вставляете <Button> в свой layout, он выглядит примерно так. Чтобы ответить на этот вопрос, я думаю, что было бы полезно сделать небольшой выход из темы TextView и взглянуть на простой Button.


Стандартный Button

Если вы посмотрите на исходный код Button, то увидете, что он довольно скудный: Почему?

public class Button extends TextView public Button(Context context, AttributeSet attrs) { this(context, attrs, com.android.internal.R.attr.buttonStyle); } public Button(Context context, AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); } public Button(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } @Override public CharSequence getAccessibilityClassName() { return Button.class.getName(); } @Override public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) { if (getPointerIcon() == null && isClickable() && isEnabled()) { return PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_HAND); } return super.onResolvePointerIcon(event, pointerIndex); }
}

Это все! Вот весь класс (без комментариев). Вы можете сами проверить здесь. Я подожду. Так откуда же берутся фон, заглавные буквы, пульсация и т.д.? Возможно, вы пропустили, но это все определятся в конструкторе с 2 аргументами; тот, который вызывается, когда layout берется из XML. Это последний параметр, который определяет defaultStyleAttr в com.android.internal.R.attr.buttonStyle. Это стиль по умолчанию, по сути, являющийся точкой косвенного обращения, позволяющей вам указать стиль, который будет использоваться по умолчанию. Он не указывает непосредственно на стиль, но позволяет вам указать на один из тех, что указаны в вашей теме, который он будет проверять при разрешении атрибутов. И это именно то, что делают все темы, от которых вы обычно наследуетесь, чтобы обеспечить вид и восприятие стандартных виджетов. Например, если посмотреть на тему Material, она определяет @style/Widget.Material.Light.Button, и именно этот стиль предоставляет все атрибуты, которые передаст theme.obtainStyledAttributes, если вы не указали ничего другого.

Это может быть очень удобно, если вы хотите применить некоторые стили к каждому TextView в вашем приложении. Вернемся к TextView, он также предлагает стиль по умолчанию: textViewStyle. Вы можете сделать это с помощью style/TextAppearance и попытаться применить во время код-ревью (или, может быть, даже с помощью элегантого пользовательского правила в Lint), но вам нужно быть бдительными и быть уверенными в том, что вы наберете новых членов команды, будете осторожны с рефакторингом и т. Допустим, вы хотите установить значение межстрочного интервала по умолчанию на 1,2. д.

Вы можете сделать это, установив свой собственный стиль для textViewStyle, который наследуется от платформы или от MaterialComponents/AppCompat по умолчанию. Лучшим подходом может быть указание собственного стиля по умолчанию для всех TextView в приложении, задающего желаемое поведение.

<style name="Theme.MyApp" parent="@style/Theme.MaterialComponents.Light"> ... <item name="android:textViewStyle">@style/Widget.MyApp.TextView</item></style>
<style name="Widget.MyApp.TextView" parent="@android:style/Widget.Material.TextView"> <item name="android:textAppearance">@style/TextAppearance.MyApp.Body</item> <item name="android:lineSpacingMultiplier">@dimen/text_line_spacing</item>
</style>

С учетом этого, наше правило приоритетности принимает вид:

View → Style → Default Style → TextAppearance

Стили по умолчанию могут быть очень удобными. Как часть разрешения атрибутов view-системы, этот слот заполняется после стилей (так что все в стиле по умолчанию аннулируется примененным стилем или атрибутами view), но все равно будет переопределять text appearance. Если вы когда-нибудь надумаете писать свой собственный custom view, то они могут стать мощным способом реализации поведения по умолчанию, позволяя легко его настраивать.

Например, если вы наследуетесь от AppCompatTextView и пишете свой собственный конструктор с 2 аргументами, обязательно передайте android. Если вы наследуете виджет и не указываете свой собственный стиль по умолчанию, тогда обязательно используйте стиль родительских классов по умолчанию в конструкторах (не передавайте просто 0). R.attr.textViewStyle как defaultStyleAttr (как здесь), иначе вы потеряете поведение родительского класса.

Темы

Как упоминалось ранее, есть еще один (последний, я обещаю) способ предоставления информации о стилизации. Другое место theme.obtainStyledAttributes будет смотреть прямо в саму тему. То есть, если вы добавите в свою тему атрибут стиля, например android:textColor, view-система выберет его в качестве последнего средства. Как правило, это плохая идея — смешивать атрибуты темы и атрибуты стиля, то есть то, что вы применяете непосредственно к view, как правило, никогда не должно устанавливаться для темы (и наоборот), но есть пара редких исключений.

Вы можете использовать один из методов, описанных выше, но ручная настройка стилей/внешнего вида текста повсюду будет монотонна и небезопасна, а стили по умолчанию будут работать только на уровне виджета; подклассы могут переопределять это поведение, например кнопки определяют свой собственный android:buttonStyle, который не подхватит ваш android:textViewStyle. Одним из примеров может быть ситуация, когда вы пытаетесь изменить шрифт во всем приложении. Вместо этого вы можете указать шрифт в вашей теме:

<style name="Theme.MyApp" parent="@style/Theme.MaterialComponents.Light"> ... <item name="android:fontFamily">@font/space_mono</item>
</style>

Теперь любой view, который поддерживает этот атрибут, подхватит его, если это не будет переопределено чем-то с более высоким приоритетом:

View → Style → Default Style → Theme → TextAppearance

Опять же, поскольку это является частью системы стилизации view, она будет переопределять все, что предоставляется в текстовом виде, но будет переопределена любыми более конкретными атрибутами.

В нашем примере со шрифтом для всего приложения вы можете ожидать, что Toolbar подхватит этот шрифт, так как он содержит заголовок, который является TextView. Помните об этом приоритете. Стили на уровне темы могут быть полезны, но их легко переопределить, поэтому убедитесь, что они применяются должным образом. Сам класс Toolbar, однако, определяет стиль по умолчанию, содержащий titleTextAppearance, который определяет android:fontFamily, и непосредственно устанавливает его в заголовке TextView, переопределяя значение уровня темы.

Бонус: Нерешенные вопросы

Вся эта статья была посвящена декларативному оформлению текста на уровне view, то есть тому, как стилизовать весь TextView во время заполнения. Любой стиль, примененный после заполнения (например, textView.setTextColor(…)), переопределит декларативную стилизацию. TextView также поддерживает более мелкие стили через Span. Я не буду вдаваться в эту тему, так как она подробно описана в статьях Флорины Мунтенеску (Florina Muntenescu).

→ Spantastic text styling with Spans
→ Underspanding spans

Я упомяну это для полноты картины, чтобы иметь в виду, что программная стилизация и span’ы будут находиться в верхней части порядка приоритетности:

Span → Setters → View → Style → Default Style → Theme → TextAppearance

Выберите свой стиль

Несмотря на то, что существует несколько способов стилизации текста, понимание различий между методами и их ограничениями помогает вам найти подходящий инструмент для выполнения конкретной задачи или понять, почему один метод имеет приоритет над другим.

Удачной стилизации текста!

Всех желающих приглашаем на бесплатный вебинар в рамках которого познакомимся с DI фреймворком Dagger 2: изучим, как Dagger2 генерирует код, разберемся с аннотациями JSR 330 и конструкциями Dagger2, будем учиться использовать Dagger2 в многомодульном приложении и рассмотрим Dagger Android Injector.

Показать больше

Похожие публикации

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»