Contentdescription android studio что это
Перейти к содержимому

Contentdescription android studio что это

  • автор:

Адаптируем Android приложение для незрячих людей. Часть 1: основы

В один из зимних вечеров я сидел дома, читал замечательную книжку «Android-программирование для профессионалов» и наткнулся на главу про Accessibility.

Раньше я об этой теме не задумывался: клал null в поле contentDescription и жил себе спокойно. Но когда получил поверхностные знания, как слабовидящие люди пользуются смартфоном через утилиту TalkBack, у меня появилось желание погрузиться в тему доступности глубже.

Меня зовут Тимур Задворнов, я Android-разработчик в Surf. Расскажу и покажу, как работает Accessibility в Android. Посмотрим, легко ли адаптировать приложения для людей с ограниченными возможностями — или это слишком трудоёмкая задача, к которой даже не стоит подступаться.

«Разработчики и contentDescription». 2022, холст, масло

Погружаться в тему Accessibility я начал с гуглежа статистики. По данным ВОЗ, в мире более миллиарда человек обладает с той или иной формой инвалидности. «Немало», — подумал я и сразу задался другим вопросом: а почему, если в мире так много людей с инвалидностью, огромное количество популярных и не очень приложений не поддерживают приложения для слабовидящих, глухих, людей с заболеваниями опорно-двигательного аппарата? Решил, что надо исправлять это недоразумение и двигать Accessibility в массы.

Для начала разберемся, что вообще такое Accessibility

Accessibility в переводе с английского — «доступность». В контексте программного обеспечения — разработка ПО таким образом, чтобы им могли пользоваться люди с ограниченными возможностями. У термина есть общепринятое сокращение — a11y: между a и y в слове accessibility 11 букв.

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

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

Получаем данные, отправляем действия, чтобы получить новые данные

В Android существуют встроенные Accessibility Services: TalkBack, Switch Access, Voice Access и прочие. О них можно думать как о плагинах для Android, которые помогают людям с ограниченными возможностями пользоваться смартфоном.

Под термином «Accessibility Services» будут подразумеваться перечисленные сервисы, а не Android-сервисы для работы в бэкграунде. Пишу об этом, потому что в Android есть одноименный сервис — AccessibilityService, который работает в бэкграунде с отправляемыми ему AccessibilityEvent.

Какие Accessibility Services существуют

С каждой новой версией Android выкатывает всё больше новых сервисов для пользователей с ограниченными возможностями. Сейчас в Android есть:

  • TalkBack — зачитывает информацию о UI-компонентах;
  • Switch Access — управление устройством через специальный контроллер;
  • Voice Access — голосовое управление смартфоном;
  • Select to Speak — зачитывает текстовый контент в выделенной области;
  • Extra Dim — настройки яркости дисплея;
  • Magnification — экранная лупа;
  • Live Captions — экранные субтитры;
  • Live Transcribe — перевод речи в текст;
  • Sound Amplifier — управление звуком;
  • Lookout — распознавание объектов через камеру;
  • Action Blocks — создание сценариев использования.

И это ещё, возможно, не всё!

Остановимся подробнее на первых трёх сервисах: TalkBack, Switch Access, Voice Access. Их используют чаще всего: именно с ними придётся сталкиваться разработчику в повседневной работе. Остальные — вспомогательные. Более подробно про них можно посмотреть в официальной документации.

TalkBack

TalkBack — утилита, которая зачитывает информацию с экрана. Помогает слабовидящим людям пользоваться смартфоном.

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

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

На каждом смартфоне с Android на борту можно включить TalkBack и пройти туториал по использованию: всё это делается в настройках, пункт Accessibility.

В настройках TalkBack есть меню с настройками для разработчиков. Можно включить субтитры или добавить шорткат быстрого включения и выключения TalkBack — зажать кнопки громкости на несколько секунд. Очень помогает во время разработки.

Switch Access

Switch Access — сервис для управления смартфоном с помощью специального устройства, переключателя.

Так может выглядеть переключатель

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

Voice Access

Voice Access — сервис для голосового управления смартфоном. Пользователь говорит, какое действие нужно сделать на экране: нажать кнопку «Лайк», пролистать вниз, ввести текст в поле «Сообщение». Действие выполняется. На видео — Voice Access в действии.

На самом деле Voice Access далеко не идеальная вещь: иногда тупит и некорректно распознаёт слова — особенно названия приложений.

Что происходит со стороны разработчиков

Давайте посмотрим, какие инструменты есть для разработчиков, чтобы UX приложений не страдал. Хорошие новости: все компоненты из стандартной библиотеки Android по умолчанию поддерживают Accessibility. Не придётся с нуля разбираться и делать доступными базовые компоненты: TextView, EditText, Switch и другие.

Приложение Accessibility Scanner для помощи разработчикам

Google выпустил для разработчиков приложение Accessibility Scanner: оно помогает найти некомфортные для людей с ограниченными возможностями места в приложении.

Accessibility Scanner работает просто:

Результаты работы Accessibility Scanner на экране приложения Twitter. Оранжевым подсвечиваются проблемные места. Есть подсказки, как исправить ошибки, с пояснениями, почему нужно сделать так

  • Открываем, даём все разрешения, оно отображается над всеми приложениями как плавающая кнопка.
  • Заходим в приложение, которое нужно проверить, переходим на нужный экран, нажимаем на кнопку Snapshot.
  • Ждём, пока Accessibility Scanner проанализирует экран, и смотрим на результат.

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

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

Шрифты

Правило простое: всегда указывайте размер шрифтов в sp! Вы меня спросите: «Зачем для шрифтов придумали какую-то отдельную единицу? Если я укажу размер в dp, никто не умрёт».

Если бы всё было так просто, мои вы хорошие

Если пользователь увеличит шрифты в системе, нужно, чтобы они увеличились и в приложении. Для этого и пригодится sp-юнит: если указать размер текста 16sp, а пользователь в системе поставил коэффициент увеличения текста = 1,25, то размер шрифта станет 20dp. На иллюстрациях видно, как изменяется размер шрифта, указанный в sp.

Заголовки

TalkBack предоставляет удобную навигацию между заголовками. Если на экране много текста с заголовками или, например, на экране есть список с подсписками, которые также помечены заголовками, в эти TextView дополнительно следует положить true в поле accessibilityHeading . Тогда текст будет считываться как заголовок.

Этот параметр появился в Android 9. Если нужно использовать его на более старых версиях, эту проблему решает compat-версия этого параметра — ViewCompat.setAccessibilityHeading(view, boolean) .

Контраст

Люди с нарушениями зрения могут воспринимать цвета по-другому: стоит следить, все ли элементы на экране чётко различимы между собой, есть ли цветовой контраст. По гайдлайнам Android, хорошая практика — устанавливать цветовой контраст на наложенных друг на друга компонентах с соотношением более чем 3:1 для больших текстов и 4,5:1 для остального контента.

Что это за цифры? Это соотношение между цветами, которое показывает, насколько сильно контрастируют два цвета между собой. Чтобы было понятно, разберем на примере: соотношение белого цвета (#FFFFFF) к чёрному (#000000) равняется 21:1 — это максимальное возможное соотношение. Два одинаковых цвета имеют отношение 1:1. Проверить контраст между цветами можно на сайте WebAIM.

Пример плохого и хорошего контраста

КАПС — НЕ НАШ БРО

На некоторых девайсах TalkBack читает текст, написанный заглавными буквами, как аббревиатуру – по букве. Сами понимаете, воспринимать это на слух очень трудно.

Заглавными буквами следует писать только аббревиатуры. Основной текст — строчными буквами или sentence case. Если всё-таки нужно оставить текст заглавными буквами, используйте параметр android:textAllCaps=”true” — и тогда всё будет ок.

Размеры элементов

Любые кликабельные элементы на экране должны иметь размер как минимум 48x48dp. Такой размер рекомендован Google: область нажатия пальцем на экран равняется приблизительно этому значению. В вебе, например, рекомендованный размер 44x44px.

Content Description

Не будем лукавить: все мы когда-то устанавливали поле contentDescription в null , потому что «да зачем оно мне надо». Но если вы хотите, чтобы приложение было доступным, придётся разбираться, что это за поле за такое.

Поле contentDescription используется в основном для визуальных компонентов без текста — ImageButton , ImageView и так далее. Это нужно, чтобы TalkBack при наведении на этот элемент не зачитал «Unlabeled, Button, double-tap to activate», а дал конкретную информацию: что за элемент, зачем он нужен на экране и что произойдет, если нажать на него. Эта строка обязательно должна быть локализована: если пользователь из Франции запустит TalkBack, а ему на чистом японском зачитают «дескрипшен», вряд ли кому-то от этого станет легче. И вообще: любые строки, связанные с a11y, будь то contentDescription или дополнительные действия TalkBack, о которых мы поговорим далее, должны быть локализованы.

В этой статье я буду использовать английскую версию TalkBack и локализованные строки так же на английском языке. Не беспокойтесь: на русском TalkBack тоже работает.

Если ImageView имеет на экране чисто декоративную роль, contentDescription можно не указывать. Тогда важно указать другой параметр — importantForAccessibility . Он отвечает за необходимость прочтения TalkBack содержимого этого компонента. У этого параметра есть значения:

  • yes — компонент обязателен для прочтения TalkBack.
  • no — компонент пропускается.
  • noHideDescendants — компонент и его дочерние компоненты пропускаются.
  • auto — система сама определяет, обязателен ли компонент для TalkBack. Это значение по умолчанию.
Live Regions

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

  • none — ничего не обновлять, значение по умолчанию.
  • polite — если значение обновилось, но TalkBack ещё воспроизводит старое значение, новое значение зачитается после того, как TalkBack закончит говорить.
  • assertive — противоположное значению polite . При обновлении значения TalkBack перестанет зачитывать старое значение и сразу начнёт зачитывать новое.

Проще понять на примере: у нас есть кнопка Increment, которая увеличивает значение на 1 и отображает новое значение в TextView. Изменяем значение accessibilityLiveRegion у TextView на polite и вот какой будет результат.

Порядок навигации между View

В идеале порядок должен идти слева направо, сверху вниз, но иногда этот порядок приходится менять для более удобной навигации между компонентами. На картинке ниже — как раз такой пример. Он надуманный, это можно сделать гораздо лучше: объединить заголовок и значение или использовать параметр labelFor .

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

Также у View есть противоположный параметр — accessibilityTraversalAfter . В нём лежит идентификатор компонента, после которого текущий компонент будет выделен TalkBack. Иногда случается, что нужно задать параметр динамически через код: для этого у объекта класса AccessibilityNodeInfo вызвать метод setTraversalBefore или setTraversalAfter . Подробнее про AccessibilityNodeInfo и AccessibilityDelegate расписано далее.

Давайте укажем к каждому TextView с заголовком параметр android:accessibilityTraversalBefore=»@id/value_textview» , где @id/value_textview — идентификатор соответствующего заголовка TextView со значением. Получим следующий порядок навигации:

Кастомные View

Обычно кастомные вью состоят из нескольких более простых компонентов: текстов, кнопок, свитчеров. Хорошая практика — делать так, чтобы компонент выделялся TalkBack как единый, а не выделял отдельно компоненты-составные, и предлагал какие-то действия что можно сделать с этим сложным компонентом.

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

Кастомная вью, которую мы будем делать доступной

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

Что зачитывает TalkBack

Как можно улучшить UX для незрячих? Сделаем компонент более доступным за несколько простых шагов.

1. Все дочерние элементы пометить importantForAccessibility = no , чтобы TalkBack не читал ничего, кроме кнопок «пополнить» и «перевести». С ними разберёмся позже.

2. Даём корневому элементу понятный contentDescription . Нужно определить, какая информация полезна для прочтения TalkBack. Пользователю будет полезно знать название кошелька и баланс. Обновлять contentDescription будем в функции setData , которая вызывается при инициализации компонента.

fun setData( title: String, balance: String, address: String ) = with(binding) < titleTv.text = title balanceTv.text = balance addressTv.text = address // Строка со значением "Wallet card: %s, balance is %s" root.contentDescription = context.getString( R.string.wallet_card_accessibility_description, title, balance ) >

3. Обрабатываем взаимодействие с компонентом. Какой смысл этого компонента? Что он делает? Выделим список возможных действий:

  • По нажатию на карточку переходим на экран деталей по кошельку.
  • По нажатию на кнопку «Перевести» или «Пополнить» переходим на экран переводов. С кнопками делать ничего не нужно, мы их не помечали как игнорируемые и они по умолчанию поддерживают accessibility.
  • Возможность скопировать адрес кошелька.
  • Возможность отобразить адрес в формате QR-кода.

Для перехода к деталям кошелька почти ничего не требуется, если уже назначен слушатель на событие нажатия на карточку. Единственное, что стоит переопределить, — название действия. По умолчанию при нажатии на кликабельный элемент TalkBack будет зачитывать «Double tap to activate» Это понятно, если элемент — кнопка или свитчер. Но если компонент сложный, «activate» не передаёт нужного контекста. Вот бы было можно заменить описание действия, дав больше контекста. И — сюрприз — так можно!

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

Давайте создадим делегата для нашей карточки. Будем использовать класс ViewCompat для обратной совместимости. Всё это опишем в методе setupAccessibility , который также вызываем при инициализации компонента.

ViewCompat.setAccessibilityDelegate( binding.root, object : AccessibilityDelegateCompat() < override fun onInitializeAccessibilityNodeInfo( host: View, info: AccessibilityNodeInfoCompat ) < super.onInitializeAccessibilityNodeInfo(host, info) val customAction = AccessibilityNodeInfoCompat.AccessibilityActionCompat( AccessibilityNodeInfoCompat.ACTION_CLICK, // Строка со значением "View wallet details" context.getString(R.string.wallet_card_accessibility_card_click_action) ) info.addAction(customAction) >> )

Вы великолепны. Теперь TalkBack при наведении на карточку зачитает действие как «Double tap to view wallet details».

Разберёмся с функциональностью, связанной с адресом. Для компонента помимо основных действий — нажатия, долгого нажатия и так далее — можно добавлять кастомные действия и вызывать их через меню TalkBack. Чтобы открыть меню, нужно при включенном TalkBack сделать L-образный свайп сверху вниз или свайп тремя пальцами вниз — если версия TalkBack выше 9.1. Для добавления кастомного действия используются:

  • Функция ViewCompat.addAccessibilityAction , куда передаём корневую вью и название действия.
  • Класс AccessibilityViewCommand , в которое кладём код с действием, — просто лямбда.
private fun setupAccessibility() < . . . ViewCompat.addAccessibilityAction( binding.root, // Строка "Show address QR code" context.getString(R.string.wallet_card_accessibility_show_qr_code_action) ) < _, _ ->// Открываем диалог с QR здесь Toast.makeText(context, "QR code has shown", Toast.LENGTH_SHORT).show() true > >

По такой же логике добавляем второе действие — копирование адреса кошелька.

private fun setupAccessibility() < . . . ViewCompat.addAccessibilityAction( binding.root, // Строка "Copy the wallet address" context.getString(R.string.wallet_card_accessibility_copy_the_address_action) ) < _, _ ->// Кладём строку с адресом в буфер обмена Toast.makeText(context, "Address has copied", Toast.LENGTH_SHORT).show() true > >

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

Можно и дальше придумывать, как сделать этот компонент более доступным. Я разобрал базовые случаи и инструменты для работы с Accessibility. Например, ещё придумал, что можно добавить кастомный экшн на чтение адреса полностью или первых и последних пяти символов.

Если получается много однотипного кода, можно вынести повторяющиеся вещи в функции-расширения. Кастомные действия также подходят для более сложных случаев взаимодействия с UI: перетаскивание элементов в списке, swipe-to-delete и прочие.

Возьмём другой пример. Пойдём дальше по тематике приложения с криптовалютами. У нас есть такой вот глупенький кастомный свитч, у которого есть 2 состояния: пополнить или перевести. Внутри это просто LinearLayout с двумя кнопками.

Кастомный свитч

По смыслу это один компонент, но если ничего не делать, TalkBack будет читать эти две кнопки как отдельные компоненты. Чтобы сделать свитчер более доступным, нужно следующее:

1. Дать осмысленное название свитчу с помощью параметра contentDescription . В этот раз мы можем сделать это прямо в XML, потому что контент вью неизменный, в отличие от прошлой вью — там у нас на входе были название кошелька и баланс.

2. Все кнопки пометить importantForAccessibility = no , чтобы TalkBack не выделял их.

3. У этого компонента два состояния: пополнить или перевести. По смыслу эта кастомная вью — свитчер, но TalkBack про это ничего не знает. Чтобы добавить состояние для компонента, есть поле stateDescription . А зачем оно нужно? Мы же просто можем класть состояние в contentDescription и эффект будет тот же.

Всё опять не так просто, как хотелось бы

Каждый раз, когда мы обновляем состояние компонента и, следовательно, обновляем contentDescription , TalkBack будет его зачитывать.

В contentDescription , помимо состояния, есть ещё описание самого компонента. Чтобы его не повторять, а воспроизводить только изменённое состояние, это состояние выделили в отдельное поле. Хорошая практика: в contentDescription класть только описание компонента — «Operation type». Состояние класть в stateDescription — «Deposit» или «Transfer». Давайте обновлять у нашего свитчера состояние при изменении:

depositButton.setOnClickListener < depositButton.select() transferButton.deselect() updateStateDescription() >transferButton.setOnClickListener
private fun updateStateDescription() < ViewCompat.setStateDescription( binding.root, context.getString( if (binding.transferButton.isSelected) < R.string.operation_switch_transfer_label >else < R.string.operation_switch_deposit_label >) ) >

4. Помимо состояния, вью обозначает, что за компонент и какую роль он играет на экране: кнопки, переключателя, чекбокса, меню-бара и так далее. Можно просто закинуть эту роль в contentDescription — но, конечно, это плохая практика.

Есть два способа обозначить роли: указать className или использовать поле roleDescription . В чём их отличие? Поле className следует перегружать, если кастомный компонент имеет роль стандартного компонента: Button, Checkbox, RadioButton — просто его вёрстка и реализация отличаются.

Если компонент уникальный и у него нет аналогов в библиотеке стандартных компонентов, можно явно указать его название его название с помощью параметра roleDescription . Такими компонентами могут быть рекламные баннеры или кастомные меню-панели. В нашем случае мы создаём аналог Switch, поэтому будем использовать установку роли через className .

ViewCompat.setAccessibilityDelegate( root, object : AccessibilityDelegateCompat() < override fun onInitializeAccessibilityNodeInfo( host: View, info: AccessibilityNodeInfoCompat ) < super.onInitializeAccessibilityNodeInfo(host, info) info.className = Switch::class.java.name // Если вам нужно указать отличную от стандартной роль info.roleDescription = context.getString(R.string.role_description_switch) >> )

5. Мы хотим, чтобы при двойном нажатии на вью при включенном TalkBack свитчер менял своё значение. Этого мы можем добиться с помощью функции ViewCompat.replaceAccessibilityAction . Она переопределяет поведение переданного в функцию экшна, в нашем случае ACTION_CLICK на поведение, переданное в лямбде.

ViewCompat.replaceAccessibilityAction( root, AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK, // Строка "Toggle" context.getString(R.string.operation_switch_accessibility_toggle_action) ) < _, _ ->if (transferButton.isSelected) < depositButton.performClick() >else < transferButton.performClick() >true >

Всё! Что у нас получилось в итоге, можно посмотреть на видео ниже. Все примеры кода лежат у меня на GitHub.

Accessibility проще, чем кажется

  • Всё не так сложно, как казалось на первый взгляд. Не нужно создавать отдельную версию приложения для людей с ограниченными возможностями.
  • Стоит чаще запускать Accessibility Scanner и исправлять ошибки. Не забываем про указание заголовков для удобной навигации, не кладём null в contentDescription , следим за порядком навигации между компонентами и делаем кастомные вью доступными!
  • Все строки, связанные с a11y, следует выносить в strings.xml и делать для них локализацию для языков, которые поддерживаются приложением.
  • Все стандартные компоненты Android SDK по умолчанию поддерживают a11y.
  • Во время тестирования приложения попробуйте попользоваться им, не подглядывая на экран, — только через TalkBack. Может быть, вы найдете моменты, которые можно было бы сделать лучше. ��

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

Больше полезного про Android — в нашем телеграм-канале Surf Android Team. Здесь мы публикуем кейсы, лучшие практики, новости и вакансии Surf, а также проводим прямые эфиры. Присоединяйтесь!

Больше кейсов команды Surf ищите на сайте >>

  • surf
  • accessibility
  • доступность
  • мобильная разработка
  • разработка под андроид
  • разработка под android
  • talkback
  • разработка мобильных приложений

What is android:contentDescription=»@string/desc» in ImageView’s xml?

I added an imageView in GraphicalLayout but it looks different on my real device than on AVD. I found that I need to add contentDescription in a layout .xml , but when I add: contentDescription=»@string/desc» there is an error:

«No resource found that matches the given name (at ‘contentDescription’ with value ‘@string/desc’)»

What is this string «desc»? What should it looks like?
asked Jul 21, 2012 at 21:05
8,714 6 6 gold badges 33 33 silver badges 33 33 bronze badges

6 Answers 6

It’s for the Accessibility Features of Android. The contentDescription is what get’s read back to the (assumingly blind or hard-of-sight user) so they have an idea of what the picture is since they aren’t able to view it.

As to answer your question, @iturki gives the start of it. You write the string to use for the description in strings.xml and give it a name of ‘desc’ or whatever your heart desires, then you set the value of contentDescription in the ImageView to @string/desc , either in the layout or programmatically. Like I said before, it’s to help users with poor eyesight get a general idea of what the image is portraying 🙂

answered Jul 21, 2012 at 21:12
starscream_disco_party starscream_disco_party
2,846 5 5 gold badges 26 26 silver badges 42 42 bronze badges

It’s not technically an error, it’s a warning saying that the Accessibility isn’t as good as it could be. Adding a string to contentDescription just gives TalkBack , or whichever Accessibility app the user has, content to read back to describe the image, since it’s not physically viewable by the impaired user.

Jul 21, 2012 at 21:27

And for purely decorative images, you can set android:contentDescription=»@null» to no content description.

Как проявляется contentDescription

В учебнике по Android’у несколько раз упоминается про параметр (и метод) contentDescription . К примеру, в разметке я его устанавливал в ImageView :

Но я никак не могу понять для чего это! На картинку и нажимал, и удерживал, но никаких эффектов, текста и тд. Так для чего же этот contentDescription и как он проявляется?

Отслеживать
задан 25 мар 2016 в 23:05
user189127 user189127

1 ответ 1

Сортировка: Сброс на вариант по умолчанию

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

Озвучка идёт чрез гугловое приложение

Отслеживать
ответ дан 25 мар 2016 в 23:12
69.8k 9 9 золотых знаков 66 66 серебряных знаков 122 122 бронзовых знака

Я бы хотел добавить, что это очень важный аттрибут. Я никогда о нем не задумывался пока мне не написал незрячий человек. Пришлось несколько дней отлаживать всю программу, что бы такие люди могли ей пользоваться. Обычно используют TalkBack. Еще такой момент, есть текст в виде html, так вот для зрячих его видно красиво, а вот TalkBack может начать читать тэги и прочую лабуду. Пришлось и в такие компоненты прописывать чистый текст.

Урок 5. Background и border. Изображения.

В этом уроке разбираемся какие возможности у нас есть при создании фона и границ элементов; какие функции используются для вывода на экран изображений; как показать картинку из Интернет.

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

Рассмотрим эти возможности на простом примере:

import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.foundation.background import androidx.compose.ui.graphics.Color import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.width import androidx.compose.ui.Alignment.Companion.Center import androidx.compose.ui.unit.dp @Composable fun HomeScreen() < Box(contentAlignment = Center, modifier = Modifier.fillMaxSize()) < Box(modifier = Modifier .width(160.dp).height(120.dp) .background(color = Color.Cyan) ) >>

В центре экрана выводим пустой Box размером 160×120.

На нем и будем тренироваться.

Background

В примере мы в background указали только color. Но там есть еще один параметр — shape. Он имеет значение по умолчанию — RectangleShape. В результате мы получаем обычный прямоугольник.

Давайте используем другую форму, например, закругленные углы — RoundedCornerShape

.background(color = Color.Cyan, shape = RoundedCornerShape(16.dp))

Или срезанные углы — CutCornerShape:

.background(color = Color.Cyan, shape = CutCornerShape(16.dp))

Еще есть круглый фон:

.background(color = Color.Cyan, shape = CircleShape)

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

Вернемся к цвету.

Если нам нужен не фиксированный цвет, а что-то посложнее, используем другую версию background, которая принимает на вход Brush. С его помощью можно сделать градиент:

.background( brush = Brush.linearGradient(colors = listOf(Color.Red, Color.Yellow, Color.Green)), shape = RoundedCornerShape(16.dp) )

Также вместе с Brush можно указать и прозрачность:

.background( brush = Brush.linearGradient(colors = listOf(Color.Red, Color.Yellow, Color.Green)), alpha = 0.2f, shape = RoundedCornerShape(16.dp) )

Border

С помощью border мы можем задать как будет выглядеть граница нашего элемента. Уберем пока background, и добавим border:

import androidx.compose.foundation.border @Composable fun HomeScreen() < Box(contentAlignment = Center, modifier = Modifier.fillMaxSize()) < Box(modifier = Modifier .width(160.dp).height(120.dp) .border(width = 2.dp, color = Color.DarkGray) ) >>

Так же, как и у background, у border есть параметр shape, где мы можем указать нужную нам фигуру:

.border(width = 2.dp, color = Color.DarkGray, shape = RoundedCornerShape(16.dp))

Также есть и Brush:

.border(width = 2.dp, brush = Brush.linearGradient(colors = listOf(Color.Red, Color.Yellow, Color.Green)), shape = RoundedCornerShape(16.dp))

Фон и границу можно ставить вместе. При этом можно даже поставить им разные формы

.background(color = Color.Cyan, shape = RoundedCornerShape(16.dp)) .border(width = 2.dp, color = Color.DarkGray, shape = CircleShape)

но результат будет так себе:

Изображения

Для изображений у нас есть два элемента: Image и Icon

import androidx.compose.foundation.Image import androidx.compose.material.Icon import androidx.compose.ui.res.painterResource @Composable fun HomeScreen() < Box(contentAlignment = Center, modifier = Modifier.fillMaxSize()) < Image( painter = painterResource(id = R.drawable.sand), contentDescription = null ) Icon( painter = painterResource(id = R.drawable.ic_launcher_foreground), contentDescription = null ) >>

Оба используют painterResource, чтобы достать картинку из ресурсов

Разница между ними понятна из их названий.

Image — для картинок. У него есть стандартные параметры для настройки положения и отображения картинки: alignment, contentScale, alpha, colorFilter.

Icon — для иконок. У него есть параметр tint, который позволяет задать оттенок иконки.

Для отображения картинок из Интернет можно использовать библиотеку Coil.

implementation 'io.coil-kt:coil-compose:2.2.2'

И используем AsyncImage, который под капотом скачает картинку и отобразит ее на экране.

AsyncImage( model = "https://developer.android.com/images/android-go/next-billion-users_856.png", contentDescription = null )

Не забудьте в манифест добавить Permission на Internet.

Если картинка не грузится, то попробуйте перезапустить или переустановить приложение.

Присоединяйтесь к нам в Telegram:

— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.

— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Compose, Kotlin, RxJava, Dagger, Тестирование, Performance

— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня

Комментарии

# RE: Урок 5. Background и border. Изображения. — Максим 19.12.2022 11:00

.border(width = 2.dp, brush = Brush.linearGradient(colors = listOf(Color.Red, Color.Yellow, Color.Green)), shape = RoundedCornerShape(16.dp))
И тут я просто расплакался

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

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