Веб является одной из целевых платформ, поддерживаемых фреймворком Flutter. Приложения на Flutter обеспечивают идеальное отображение пикселей и согласованность с платформой благодаря рендерингу всего пользовательского интерфейса на элементе canvas. Однако по умолчанию элементы canvas недоступны. В этой публикации рассмотрим, как поддержка доступности работает для приложений на Flutter, рендеринг которых выполняется на холсте.
Во Flutter есть большое количество виджетов по умолчанию, которые генерируют дерево доступности автоматически. Дерево доступности представляет собой дерево объектов доступности, к которым вспомогательные технологии могут обращаться за атрибутами и свойствами и выполнять действия. Для настраиваемых виджетов класс Semantics позволяет разработчикам описывать значение своих виджетов, помогая вспомогательным технологиям понять смысл содержимого виджетов.
Из соображений производительности, на момент написания данного текста доступность в Flutter включается по умолчанию только при явном запросе. Команда Flutter хотела бы со временем включить семантику в Flutter Web по умолчанию. Однако на данный момент это приведет к заметному снижению производительности в значительном количестве случаев и требует некоторой оптимизации, прежде чем значение по умолчанию можно будет изменить. Разработчики, которые хотят всегда включать режим доступности во Flutter, могут сделать это с помощью следующего фрагмента кода.
import 'package:flutter/semantics.dart'; void main() { runApp(const MyApp()); if (kIsWeb) { SemanticsBinding.instance.ensureSemantics(); } }
Примечание: Если вашему приложению необходимо знать, использует ли пользователь такие устройства, как скринридеры, позвольте пользователям дать явное согласие.
Как только вы включите поддержку доступности в Flutter, HTML будет автоматически изменяться, как показано далее на этой странице.
Примечание: скринридеры — это только один пример вспомогательной технологии, которая выигрывает от описанного подхода. Cкринридеры используются как прокси для этой и других вспомогательных технологий для улучшения читаемости.
Включение поддержки доступности в Flutter
Механизм явного включения в Flutter реализуется с помощью скрытой кнопки. Он помещает кнопку, а точнее, элемент <flt-semantics-placeholder>
с role="button"
— невидимый и недоступный для зрячих пользователей — в HTML. Это настраиваемый элемент с наложенным стилем, поэтому он не отображается и не выбирается, если вы не используете скринридер.
<flt-semantics-placeholder role="button" aria-live="polite" aria-label="Enable accessibility" tabindex="0" style=" position: absolute; left: -1px; top: -1px; width: 1px; height: 1px;" ></flt-semantics-placeholder>
/* `<flt-semantics-placeholder>` inherits from `<flutter-view>`. */ flutter-view { user-select: none; }
Изменения после включения
Что происходит, когда пользователь скринридера нажимает на эту кнопку? Рассмотрим на примере карточки из галереи Flutter, как показано на следующем скриншоте.
Чтобы лучше понять, что конкретно меняется, когда пользователь нажимает на кнопку, сравните скриншоты до и после в Chrome DevTools, изучая дерево доступности. Второй скриншот раскрывает гораздо больше семантической информации, чем первый.
До явного включения:
После явного включения:
Детали реализации
Основная идея Flutter заключается в создании доступной структуры DOM, которая отражает то, что в данный момент отображается на холсте. Это включает в себя родительский элемент <flt-semantics-host>
, который содержит дочерние элементы <flt-semantics>
и <flt-semantics-container>
, которые, в свою очередь, могут быть вложены друг в друга. Рассмотрим виджет кнопки, например TextButton. Этот виджет представлен элементом <flt-semantics>
в DOM. Аннотации ARIA (например, role или aria-label) и другие свойства DOM (tabindex, слушатели событий) на элементе <flt-semantics>
позволяют скринридеру объявлять пользователю элемент как кнопку, а также поддерживать щелчки и тапы по нему, даже если это не буквально элемент <button>. На следующем скриншоте кнопка «Поделиться» является примером такой кнопки.
Этот элемент <flt-semantics>
позиционируется абсолютно, чтобы появляться точно в том месте, где на холсте нарисована соответствующая кнопка. Это происходит потому, что Flutter контролирует размещение всех виджетов и заранее рассчитывает позиции и размеры каждого семантического узла. Абсолютный layout позволяет разместить элемент доступности именно там, где его ожидает пользователь. Однако это также означает, что каждый раз, когда пользователь скроллит страницу, позиции необходимо корректировать, что в некоторых ситуациях может быть затратно.
Применяем подход на все виджеты по умолчанию
Поскольку Flutter знает, что то, что представлено как <flt-semantics role="button">
в структуре DOM в исходном коде Flutter, изначально было Flutter TextButton, становится несложно расширить подход и создать отображение всех существующих Flutter виджетов на соответствующие роли WAI-ARIA, что Flutter и делает из коробки для всех своих виджетов по умолчанию. Например, на данный момент Flutter поддерживает следующие роли:
-
Текст
-
Кнопки
-
Чекбоксы
-
Радиокнопки
-
Текстовые поля
-
Ссылки
-
Диалоги
-
Изображения
-
Слайдеры
-
«Живые» области
-
Прокручиваемые элементы
-
Контейнеры и группы
Обратите внимание: несмотря на короткий список ролей, многие различные категории виджетов часто имеют одинаковую роль. Например, Material TextField и CupertinoTextField могут разделять роль текстового поля. Большинство layout виджетов, таких как Stack, Column, Row, Flex и другие, могут быть представлены контейнером или группой.
Трудности с пользоватескими виджетами
При создании пользовательского виджета Flutter может не назначить ему корректную роль автоматически. Если виджет просто является декорированным вариантом существующего виджета (например, оболочка над EditableText), он может корректно отображаться (как текстовое поле). Однако, если вы создаете виджет с нуля, Flutter ожидает, что вы используете виджет Semantics для описания его свойств доступности. WAI-ARIA определяет различные роли для виджетов. Flutter поддерживает только подмножество этих ролей, хотя этот список постоянно расширяется.
Например, можно исследовать выбор класса команды в игре I/O Flip в режиме реального времени, как показано на следующем скриншоте. В терминах веба, это по сути <select>
, или listbox в терминах WAI-ARIA. Несмотря на то, что доступные варианты представлены как generic text (они должны быть элементами <option>), еще большей проблемой является то, что из дерева доступности не ясно, что существуют варианты для выбора, которые находятся за пределами области просмотра виджета. Обратите внимание на доступные варианты в дереве доступности до и после прокрутки.
До прокрутки:
После прокрутки:
Если вы посмотрите на исходный код, то увидите, что он не использует класс Semantics, поскольку Semantics пока не поддерживает аннотацию listbox и option role. Но он использует ListWheelScrollView, который похож на обычный ListView — поэтому он знает, что имеет дело со списком. Обратите внимание, что дерево доступности всегда показывает только видимые элементы, а также несколько элементов выше и ниже области просмотра, но никогда не все элементы. (Это обычный трюк для повышения производительности приложений, который мы почти реализовали в виде <virtual-scroller>).
Сравните дерево доступности Flutter с примером прокручиваемого списка из ARIA Authoring Practices Guide, где в дереве доступности отображаются все варианты — даже те, которые находятся за пределами области просмотра. На момент написания этой статьи, неполная поддержка этого случая использования списка (listbox) является недостатком решения Flutter, который будет решен в будущем.
Редактирование текста
В Flutter есть элемент <flt-text-editing-host>
, который в качестве дочернего элемента содержит либо <input>
, либо <textarea>
, и он размещается с точностью до пикселя на соответствующей области холста. Это значит, что такие удобства браузера, как автозаполнение, работают как ожидается. Эта функция всегда включена, независимо от того, включена ли доступность или нет. В дереве семантики текстовое поле представлено элементом <input>
, который потенциально имеет ARIA-метку для его описания. Следующий пример текстового поля взят из Flutter Gallery. Посмотрите, как поле <input>
динамически перемещается каждый раз, когда пользователь нажимает клавишу Tab.
В то время как для зрячих пользователей тексты меток, отображаемые в текстовых вводах, видны, для пользователей скринридеров текстовые поля объявляются как «edit, blank» с NVDA на Windows или «edit text, blank» с VoiceOver на macOS, поскольку Flutter на данный момент еще не создает элементы <label>
. Вы можете увидеть вывод экранной читалки VoiceOver в нижней части изображений. Flutter исправит это в будущем.
Когда текстовые поля правильно помечены, скринридер объявляет их предполагаемое значение, как показано в следующем примере на чистом HTML.
Заключение
В этой публикации мы углубились в тонкости того, как работает поддержка доступности в приложениях на Flutter с использованием холста в вебе. Доступность в Flutter реализуется через скрытую кнопку с определёнными атрибутами и стилем. При активации этот подход значительно улучшает опыт пользователей, полагающихся на скринридеры и другие вспомогательные технологии. Основная концепция в Flutter заключается в создании доступной DOM-структуры, которая отражает содержимое холста, используя такие пользовательские элементы, как <flt-semantics-host>
, <flt-semantics>
, <flt-semantics-container>
и другие. Хотя Flutter умело сопоставляет стандартные виджеты с ролями WAI-ARIA, команда признаёт наличие некоторых проблем. Исследование редактирования текста в Flutter демонстрирует приём с <flt-text-editing-host>
, содержащим <input>
или <textarea>
, что показывает динамическое перемещение полей ввода.
Забегая вперёд, отметим, что команда уже начала работать над совершенствованием системы доступности Flutter. В числе задач — решение проблемы использования listbox для пользовательских виджетов и улучшение создания элементов label для редактирования текста. Ожидается, что эти улучшения обеспечат более полный и нерперывный опыт доступности, что отражает стремление Flutter к постоянному совершенствованию своей цели по компиляции веб-страниц.
В заключение приглашаем всех желающих на открытый урок 24 июля, который посвящен тому, как делать Flutter-приложения плавным и отзывчивым.
На занятии мы поговорим о типичных проблемах, из-за которых возникают зависания интерфейса (в том числе на Impeller), научимся их обнаруживать с помощью инструментов DevTools, Perfetto и интегрировать замеры производительности в код с помощью dart:developer. Также мы посмотрим принципы работы ServiceExtensions и создадим собственное расширение для отслеживания виджетов с потенциальными проблемами. Для примера будем оптимизировать несложную игру с большим количеством визуальных эффектов, из-за которых в исходном варианте не получается достичь ожидаемых 60 кадров в секунду.
Записаться на урок можно на странице курса «Flutter Mobile Developer».
ссылка на оригинал статьи https://habr.com/ru/articles/829432/
Добавить комментарий