Можно ли span вложить в span

Обновлено: 29.01.2023

Используйте span, чтобы стилизовать текст в Android! Вы сможете изменить цвет нескольких символов, сделать их кликабельными, изменить размер текста или даже нарисовать свои маркеры для списка с помощью span. Span-ы могут изменить свойства TextPaint , могут рисовать на Canvas , или даже изменить текстовый лайаут и изменить высоту строки текста. Span-ы это объекты разметки, которые могут быть прикреплены или откреплены от текста; они могут быть применены к целому параграфу или части текста.

Давайте рассмотрим, как использовать span, что предоставляют span-ы “из коробки”, как проще создать свой span и наконец как их тестировать:

Android предлагает несколько способов стилизации текста:

  • Простые стили (Single style) — когда стиль применяется ко всему тексту отображаемому TextView
  • Мульти стили (Multi style) — когда несколько стилей могут быть применены к тексту, параграфу

Простой стиль (Single style) подразумевает стилизацию всего текста в TextView, используя XML атрибуты или стили и темы (которые применяются к TextView). Этот подход — простой, но не позволяет применить стилизацию к части текста. Например, при применении textStyle=”bold” , текст целиком будет жирным; вы не можете сделать жирным какой-то символ в тексте.

Multi style подразумевает добавление нескольких стилей к одному и тому же тексту. Например, сделать одно слово со стилем italic а другое bold. Мульти стили могут быть реализованы через использование HTML тегов, span-ов или управлением отрисовки текста на холсте Canvas.

Слева: Текст с одним простым стилем. TextView с textSize=”32sp” и textStyle=”bold”. Справа: Текст с мультистилями. Текст с ForegroundColorSpan , StyleSpan(ITALIC), ScaleXSpan(1.5f), StrikethroughSpan.

HTML теги это простое решение для решения простых задач, таких как: сделать текст жирным, с наклоном, или даже вывести маркеры для списка. Чтобы стилизовать текст, включающий HTML теги, вызовите метод Html.fromHtml . Под капотом - HTML форматирование конвертируется в span-ы. Пожалуйста обратите внимание, что класс Html — не поддерживает все HTML теги и css стили, например чтобы изменить цвет маркеров списка.

Что касается рисования текста на canvas, то это следует использовать, если нужно сделать что-то, что не поддерживается платформой, например рисование текста по кривой.

Span-ы позволяют вам применить одновременно несколько стилей с большой степенью кастомизации. Например, вы можете определить параграфы текста с маркерами, применив BulletSpan . Вы можете, например, изменить расстояние между маркером и текстом, также изменить цвет маркера. Начиная с Android P, вы можете даже установить радиус маркера списка. Вы также можете создать свою реализацию для span. Ознакомьтесь с секцией “Создание кастомных span-ов” ниже, чтобы узнать как это сделать.

Слева: Использование HTML тегов. По центру: использование BulletSpan с дефолтным размеров bullet (маркеры элементов списка). Справа: Использование BulletSpan на Android P или кастомная реализация.

Вы можете комбинировать простые стили и мульти стили. Вы можете рассматривать стили, которые вы применяете к TextView как “базовые” стили. Стилизация текста через span-ы применяется “поверх” базового стиля и переписывает базовый стиль. Например, когда меняет цвет текста TextView через атрибут textColor=”@color.blue” и применяем ForegroundColorSpan(Color.PINK) для первых 4 символов текста, то первые 4 символа будут розовыми (потому что span переопределил основной стиль), а цвет остальных символов будет определятся цветом, заданным через атрибут.


При использовании span-ов, вы будете работать с одним из следующих классов: SpannedString , SpannableString or SpannableStringBuilder . Разница между ними заключается в том, что текст или объекты разметки являются изменяемыми или неизменяемыми во внутренней структуре, которую они используют: SpannedString и SpannableString используют линейные массивы для хранения добавляемых span-ов, а SpannableStringBuilder использует дерево отрезков.

Как решить какой класс нужно использовать:

  • Если у вас есть неизменяемый текст, к которому вы хотите применить стили ? -> SpannedString
  • Если ваш стилизованный текст будет меняться -> SpannableStringBuilder
  • Установить небольшое кол-во span-ов ( <~10)? ->SpannableString
  • Установить большое кол-во span-ов (>~10) -> SpannableStringBuilder

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

Все эти классы имплементятся от интерфейса Spanned , но классы которые имеют объекты разметки ( SpannableString и SpannableStringBuilder ) наследуются от Spannable .

Spanned -> неизменный текст и неизменная разметка

Spannable (расширяет Spanned )-> неизменный текст и изменяемая разметка

Примените span через вызов setSpan(Object what, int start, int end, int flags) на Spannable объекте. Здесь what — это объект, который будет применен к тексту с позиции start и по позицию end текста. Флаг указывает, должен ли диапазон расширяться, чтобы включить текст, вставленный в их начальную или конечную точку, или нет. Независимо от того, какой флаг установлен, всякий раз, когда текст вставлен в положение больше начальной точки и меньше конечной точки, диапазон автоматически расширяется.

Например, настройка ForegroundColorSpan может быть такой:

Тк span был настроен используя флаг SPAN_EXCLUSIVE_INCLUSIVE , то когда вставляется текст в конец span-а, span будет расширен, чтобы включить новый текст (те стиль будет применен к добавляемому тексту):

Слева: Текст с ForegroundColorSpan. Спарва: Текст с ForegroundColorSpan и Spannable.SPAN_EXCLUSIVE_INCLUSIVE

Если span настроен с флагом Spannable.SPAN_EXCLUSIVE_EXCLUSIVE , то вставляемый в конец текст не будет изменен (те стиль не будет применен к добавляемому тексту).

Множество span-ов может быть составлено и присоединено к одному и тому же текстовому сегменту. Например, текст, выделенный как полужирным, так и красным, можно построить таким образом:


Android framework определяет несколько интерфейсов и абстрактных классов, которые проверяются во время измерения и во время рендеринга. У этих классов есть методы, которые позволяют span-у получить доступ к объектам как например TextPaint или Canvas .

Android framework предлагает более 20 классов и интерфейсов в пакете android.text.stylepackage для работы со span-ами, производных от главных интерфейсов и абстрактных классов. Мы сгруппировали span-ы на группы:

  • Исходя из того, изменяет ли span только внешний вид, а также текстовую метрику / макет
  • Исходя из того, влияют ли они на текст по символам или на уровне абзаца

Span-ы которые влияют на представление и метрику текста

Первая категория влияет на представление текста на уровне симовлов: цвет текста или фона, подчеркивание, зачеркивание, и т.д. — заставляет перерисовать текст без его перекомпоновки (пересчета размера view, которое отображает текст). Эти span-ы имплементят UpdateAppearance и расширяют CharacterStyle . CharacterStyle субклассы определяют как рисовать текст посредством предоставления доступа к TextPaint .

Span-ы влияющие на метрику — изменяют размеры лайаута и требуют повторного рендеринга компонентов.

Например, span который влияет на размер текста требует изменение размеров лайаута и перекомпоновки компонентов. Эти span-ы обычно расширяют MetricAffectingSpan класс. Этот абстрактый класс позволяет субклассам определить как span влияет на измерение текста (это делает через предоставление доступа к TextPaint ). Поскольку MetricAffectingSpan расширяет CharacterSpan , подклассы влияют на внешний вид текст на уровне символов.

У вас может быть соблазн всегда пересоздавать CharSequence с текстом и разметкой вызывая TextView.setText(CharSequence) . Но это всегда требует повторного пересчета размеров лайаута и создания дополнительных объектов (что может сказать на отзывчивости ui). Чтобы избежать этого установите текст через вызов TextView.setText(Spannable, BufferType.SPANNABLE) и затем, когда вам будет необходимо изменить span-ы, извлеките Spannable объект из TextView через приведение TextView.getText() к Spannable . Мы заглянем что происходит под капотом TextView.setText в следующей статье.

Для примера, рассмотрим Spannable объект установленный и извлеченный следующим образом:

Теперь, когда мы определили spannableText , мы можем не вызывать textView.setText повторно, потому что мы можем изменять CharSequence объект напрямую через объект spannableText.

Вот как мы можем установить изменить внешний вид, применив span:

Вариант 1: Изменение внешнего вида текста, без изменеия размеров TextView

В приведенном выше примере будет вызван TextView.onDraw , но не TextView.onLayout .

Вариант 2: Изменение размеров текста

Тк RelativeSizeSpan изменяет размер текста, то высота и ширина view может измениться , TextView вызовет методы onMeasure и onLayout .

Слева: ForegroundColorSpan — измнение внешнего вида без изменения размеров TextView . Справа: RelativeSizeSpan — Изменение размеров Textview

Character vs paragraph affecting spans

Span могут влиять на текст как на символьном уровне, изменяя например цвет фона, стиль, размер символов, так и на уровне параграфа, например изменяя выравнивание текста в параграфе целиком. В зависимости от того, как нужно стилизовать текст span-ы расширяют CharacterStyle или имплиментят ParagraphStyle . Span-ы которые расширяют ParagraphStyle должны быть прикреплены от первого символа до последнего символа параграфа, иначе вы не стилизация текста не будет отображена. В Android параграфы определяются с новой строки с помощью символа перевода каретки ( \n ) .

ParagraphStyle span, QuoteSpan , может быть прикреплен только к началу параграфа. Например, “Text is\nspantastic” включает символ новой строки на 8м символе текста, поэтому мы можем прикрепить QuoteSpan только начиная с позиции 0 или 8, иначе текст не будет стилизован.

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

  • Изменение стилизации текста на символьном уровне ->CharacterStyle
  • Изменение стилизации текста на уровне параграфа ->ParagraphStyle
  • Стилизация текста без изменения размера TextView ->UpdateAppearance
  • Стилизация текста c изменением размера TextView ->UpdateLayout

Предположим, что нам нужно реализовать span, который увеличивает размер текста в соотвествии с пропорциями TextView так, как делает это RelativeSizeSpan , и устанавливает цвет текста, как это делает ForegroundColorSpan . Чтобы сделать это вы можете расширить RelativeSizeSpan переопределив метод updateDrawState в котором установить цвет текста используя объект TextPaint .

Замечание: точно такой же эффект может быть достигнут если применить оба существующих класса RelativeSizeSpan и ForegroundColorSpan к тексту.

Тестирование span-ов означает проверку того, что действительно были внесены ожидаемые изменения в TextPaint или что на Canvas были отрисованы элементы. Например, рассмотрим пользовательскую реализацию span-a, который добавляет точку (bullet point) абзаца с указанным размером и цветом, а также отступ слева от точки (bullet point). Вы можете посмотреть реализацию здесь android-text sample. Чтобы протестировать этот класс, реализуйте AndroidJUnit , чтобы проверить:

  • Отрисовку круга определенного размера
  • Ничего не рисуется, если span не прикреплен к тексту
  • Установлен правильный отступ, ширина которого передается параметров конструктора

Ниже пример кода для тестирования.

Проверьте полный код теста здесь — BulletPointSpanTest .

Spanned интерфейс позволяет настраивать и извлекать span-ы из текста. Проверьте что span-ы корректно добавлены в нужное место через реализацию Android JUnit теста. В android-text sample мы конвертируем теги разметки точки (bullet point) в маркеры (которые будут отрисованы в TextView). Это можно сделать прицепив BulletPointSpans к тексту, в нужную позицию. Ниже код как это можно протестировать:

Посмотрите MarkdownBuilderTest для других примеров тестов.

Span-ы — это мощная концепция, встроенная в функциональность рендеринга текста. Она позволяет стилизовать текст по средством доступа к компонентам TextPaint и Canvas. В Android P мы добавили обширную документацию по framework spans , так что перед тем как реализовывать свои span-ы, проверьте какие span-ы доступны.

В будущих статьях мы расскажем больше о работе span-ов “под капотом” и как их эффективно использовать. Оставайтесь с нами!

Что значит правильно? Что является источником истины при работе с HTML? Конечно же это специфиакция! В данный момент существует так называемый «живой стандарт» пятой версии HTML. Это значит, что у него нет мажорных версий и он обновляется регулярно. Посмотреть последнее обновление спецификации можно здесь.

Спецификация HTML — это увесистый документ с кучей разделов. Она существует как для разработчиков браузеров, так и для нас — верстальщиков. Конкретно нас интересуют третий и четвёрный раздел (Semantics, structure, and APIs of HTML documents и The elements of HTML). Эти разделы описывают, как теги можно вкладывать друг в друга и что обозначает каждый тег.

Описание элемента

Каждый элемент имеет метаинформацию и описание.

Сверху размещены метаданные, куда включна следующая информация:

куда можно вкладывать тег;

какие теги можно вкладывать внутрь этого тега;

перечень аттрибутов тега (глобальные, дополнительные и ARIA);

информация о доступности;

Далее размещено описание тега — что он обозначает и как его можно использовать.


Спецификация элемента на примере тега

Метаданные тега


Метаданные тега

Метаинформация о теге включает в себя несколько пунктов:

Категории — обозначает, к каким типам тегов относится элемент, могут быть следующие типы:

Metadata content (например и ) — метаданные страницы, обозначающие представление или поведение содержимого, или отношение к другим документам;

Flow content (например , ,

) — это все теги, используемые в , то есть почти все в целом, за исключением некоторых мета-тегов;

Sectioning content (например ) — какие-либо большие секции, как правило имеющие конкретную структуру и заголовок;

Heading content (все заголовки – , а также тег, про который я забыл рассказать в прошлой статье — ) — определяет заголовок секции, обозначенной явно, либо .

Embedded content (например , , ) — контент, встраиваемый извне (другие сайты, файлы, скрипты);

Контекст использования элемента — где мы можем размещать элемент.

Контентная модель — важная для этой статьи часть! Это как раз то, что мы можем размещать внутри тега.

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

Доступные аттрибуты тега.

Раздел, касающийся доступности. Надеюсь когда-нибудь дойдут руки написать о работе с доступностью в рамках спецификации.

DOM Interface — раздел, необходимый для JavaScript-разработчиков, подробно на нём останавливаться не будем.

И всё же, как правильно вкладывать теги друг в друга?

Категории элементов по HTML-спецификации

Категории элементов по HTML-спецификации

Две основные категории тегов — это Metadata (метаданные) и Flow (поточные теги). Метаданные — это то что в основном входит в , а Flow — то что можно положить в . Однако, некоторые мета-теги мы можем разместить в , поэтому они заходят на Flow-контент (например это , и ).

Внутри потоковых тегов находятся все остальные категории.

Heading — это просто залоговки всех уровней, а также тег .

Sectioning — это большие смысловые части документа, секции.

Phrasing — небольшие слова или словосочетания, фразы. В основном это строчные элементы (по крайней мере они такими являлись в спецификации HTML4). Все остальные теги, не имеющие категории Phrasing, как правило являются блочными.

Embedded — как уже писал выше, контент, имеющий внешний источник данных, то есть не имеющий прямое отношение к HTML-документу.

Interactive — категория, пересекающаяся с Flow, Phrasing и Embedded, обозначающая все элементы, с которыми пользователь может взаимодействовать.

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

Смотрим на контентную модель тега, в который вкладываем.

Смотрим на категории тега, который вкладываем.

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

Давайте разберёмся на примере. Есть тег и мы хотим вложить в него тег : у секции контентая модель Flow, у категории Flow, Phrasing и Palpable. Соответственно вложение допустимо.

Частая задача, когда нам нужно внутри лейбла держать и инпут и его описание. Проверям:

Для лейбла контентной моделью является Phrasing Content, но без вложения других лейблов.

не имеет категории Phrasing, поэтому вкладывать

в нельзя!

имеет категорию Phrasing, поэтому мы можем вложить его в .

Если сильно упрощать, то во Flow мы можем вкладывать и Flow, и Phrasing, а во Phrasing только Phrasing.

Заключение части 2

Ну и не забывайте валидировать код с помощью W3C Validator, который тоже сможет подсказать, правильно ли вы вкладывали теги, или нет.

2. span можно задать как block но ведь есть же div но им говорят не злоупотреблять, когда лучше, обосновано span block а когда div?

3. Постоянно сталкиваюсь с конструкциями вида:

Вроде всё правильно но чем дольше работаю тем больше кажется что это антипаттерн какой-то потому что по ходу работы надо проектом часто возникает необходимость иметь в блоке 2 и более ссылок по-разному оформленных.

И т.п. где об этом можно почитать?

werty1001

DrunkMaster

DrunkMaster: да мне самому интересны правила хорошего тона в верстке, только БЭМ к этому никакого отношения не имеет.

werty1001

Олег: БЭм это неужный костыль в эпоху компонентов. Он никак не решает вопрос автора "В каких случаях имеет смысл писать текст прямо в div, А в каких оборачивать в p или span"

delphinpro

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

werty1001

Серьезно, я пытался верстать по БЭМу когда-то. У меня на дрочилово с придумыванием, упорядочиванием и написанием этих свитков классов уходило больше времени, чем на версту. Конечно, я допускаю мысль что я делал что-то неправильно или у меня просто личная аллергия на эту методику.

delphinpro

lufasab: Ну я тоже пишу на реакте. И мне категорически не нравится идея инлайн-стилей. А если их выносить во внешний файл, то возвращаемся к той же проблеме глобальной видимости в css. Да, есть конечно, приблуды, которые генерят рандомные классы, но годятся они только для SPA и по сути тоже являются костылями. Если бы всё было так просто, как вам кажется.

Более того, я смею утверждать, что сам по себе БЭМ ломает мышление, это больше подходит для наемных сотрудников где-то в крупных компаниях (в реалье же, это пару околояндексных контор - все)

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

delphinpro

lufasab: а я и не говорю, что они не работают =) работают, использую, я использую. но это же не панацея на все случаи жизни. Далеко не на все.
Что касается стандартов, тут и так всё очевидно - ждем поддержку web-components, настоящих компонентов.

0example

Про БЭМ уже сказали, а про спан и див коротко: должны быть общее определение всяких дивов и спанов, т.е если я буду читать ваш код и увижу span, я пойму что это строчный элемент, мне будет странно, если он окажется блочным.

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

Про ваш тег p, лично я любой текст, который вложен в статью или блок с текстом, всегда обозначаю через этот тег. Выделил это для того, чтоб не было желания писать следующее: Текст

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

Если вам нужно чтобы каждая ссылка в меню была по-разному оформлена, то все равно все так же и будет записываться, с небольшой корректировкой:


HTML5


Блочные элементы


Строчные элементы


Универсальные элементы


Нестандартные теги


Осуждаемые теги


Видео


Документ


Звук


Изображения


Объекты


Скрипты


Списки


Ссылки


Таблицы


Текст


Форматирование


Формы


Фреймы

Время чтения: меньше 5 мин

Обновлено 13 декабря 2021

Кратко

С помощью тега можно выбрать часть текста или другой информации в блоке и стилизовать её.

Пример

Как это понять

Например, хочется, чтобы одно слово в абзаце было написано красным цветом. Помести это слово в коде в контейнер . < / span>и примени к нему CSS-стиль.

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

Как пишется

Подсказки

💡 Иногда, чтобы отформатировать часть текста, можно использовать семантические элементы — это те, которые не просто являются контейнерами, а имеют своё значение, например, тег , с помощью которого вы создаёте «шапку» своей страницы с меню и логотипом. Поэтому, если вместо можно использовать семантический тег, например, для выделения автора материала курсивом или для выделения текста жёлтым маркером, то используйте их.

Ещё пример

На практике

Дока Дог

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

Фишка в том, что в можно встраивать вообще всё, что угодно. Внутри можно собирать целые блоки, списки и, по факту, он может работать не только с текстом: я такое встречал очень часто. можно встраивать друг в друга сколько угодно раз, чего не сделаешь, например, с тегом

. Допустим, ты хочешь, чтобы текст на сайте появлялся по одной букве, то ты добавляешь каждую букву в отдельный , делаешь задержку и отдельно уже управляешь через JavaScript или CSS. — тег, без которого современные сайты практически не могут существовать.

Егор Левченко

🛠 — строковый элемент, поэтому по умолчанию, у него нет высоты. Если нужна высота, то элементу стоит задать display : block или display : inline - block , или подумать: «А не нужен ли там ?»

Алёна Батицкая

🛠 Тег удобен, если нужно оформить другими стилями отдельное слово или словосочетание в тексте. Этот приём очень любят дизайнеры, чтобы акцентировать внимание на какой-то информации.

Автор статьи

Куприянов Денис Юрьевич

Куприянов Денис Юрьевич

Юрист частного права

Страница автора

Читайте также: