Focus Order е последователността, в която елементите на интерфейса получават фокус при навигация с клавиатура, Switch Control, VoiceOver или TalkBack. В мобилните приложения редът на фокуса определя как потребителят се придвижва между контролите с жестове или бутони. Според W3C WCAG 2.2, Success Criterion 2.4.3, 2023, фокусът трябва да следва логичен ред, който запазва смисъла на съдържанието. Нарушаването на този принцип е една от честите причини за провал на одита за достъпност.
Основни моменти
Focus Order е последователността, в която потребителят се придвижва между интерактивните елементи с помощта на алтернативни методи за въвеждане: клавиатура (Tab), Switch Control (стъпка по стъпка), VoiceOver (жест надясно/наляво) или TalkBack. За разлика от мишката или сензорния екран, където потребителят избира елемента директно, фокусната навигация е линейна — всяка стъпка премества фокуса върху следващия елемент.
Според Apple HIG, 2024, VoiceOver използва реда на елементите в дървото за достъпност, което се изгражда въз основа на визуалното разположение: горен ляв ъгъл → долен десен ъгъл. Ако екранът съдържа сложна подредба (колони, Grid, ZStack), дървото може да не съответства на визуалния ред.
Принципът на WCAG 2.4.3: „Ако уеб страницата може да се навигира последователно по секции и редът на фокуса влияе на смисъла, то фокусът трябва да следва ред, който запазва смисъла и възможността за управление”. Изключение: динамично съдържание, при което фокусът може да скача, за да привлече вниманието (предупреждения, модални прозорци).
Потребителят на Switch Control (хора с двигателни увреждания) се придвижва между елементите автоматично — цикъл след цикъл. Ако редът е нарушен, потребителят отделя 3 пъти повече време за попълване на формуляр. Според Deque University, 2024, коректният Focus Order намалява времето за попълване на формуляри с 60% за потребителите на помощни технологии.
Особено внимание се отделя на модалните прозорци. След отваряне на модален прозорец фокусът трябва незабавно да се премести върху първия интерактивен елемент вътре в модала (обикновено бутонът „Затвори” или „Потвърди”). След затваряне — да се върне върху елемента, който е извикал модалния прозорец. Това е изискване на WCAG 2.4.3 и същевременно често срещана грешка.
В iOS VoiceOver автоматично изгражда реда на базата на геометрията: елементите се сортират по Y, след това по X. За екрани със сложна структура този ред може да е неправилен — разработчикът трябва да се намеси.
Основните инструменти:
Пример за задаване на персонализиран ред за карта на продукт:
class ProductCardView: UIView {
let titleLabel = UILabel()
let priceLabel = UILabel()
let buyButton = UIButton()
override var accessibilityElements: [Any]? {
get {
return [titleLabel!, priceLabel!, buyButton!]
}
set {}
}
}
За програмно преместване на фокуса след действие:
UIAccessibility.post(
notification: .layoutChanged,
argument: newlyAddedItem
)
Свойството shouldGroupAccessibilityElement е полезно за картите в колекции. Ако зададете true на родителската карта, VoiceOver възприема цялата карта като един елемент. Потребителят може да докосне два пъти, за да активира цялата карта, или да настрои ротора за навигация вътре в нея. Препоръчва се за UICollectionViewCell и UITableViewCell.
В Android TalkBack също използва геометричния ред, но приоритет се дава на изричните атрибути nextFocus*. Тези атрибути се задават в XML или програмно:
| Атрибут | Предназначение | Пример |
|---|---|---|
| nextFocusDown | Елемент при навигация надолу | @+id/field_email |
| nextFocusUp | Елемент при навигация нагоре | @+id/field_name |
| nextFocusLeft | Елемент отляво | @+id/btn_back |
| nextFocusRight | Елемент отдясно | @+id/btn_next |
Пример за формуляр за регистрация:
<EditText
android:id="@+id/field_email"
android:nextFocusDown="@+id/field_password" />
<EditText
android:id="@+id/field_password"
android:nextFocusDown="@+id/btn_submit" />
За RecyclerView редът на фокуса е динамичен — определя се от адаптера. Ако клетките имат сложна структура, задайте descendantFocusability = "beforeDescendants" и определете реда във възела на елемента от списъка. За Jetpack Compose редът на фокуса се задава чрез Modifier.focusOrder() и FocusOrder. Приоритет: previous (дъщерен), next (следващ), custom key.
Ако елементът е твърде малък за фокус (по-малък от 44pt), увеличете зоната за докосване чрез TouchDelegate в iOS или minWidth/minHeight в Android. Според Google Material Design, 2024, минималната зона за докосване е 48×48dp. VoiceOver и TalkBack фокусират върху bounding box на елемента. Елементите с размер под 30pt може да са недостъпни за фокус с жест — потребителят физически не може да ги достигне с пръст.
Скачащ фокус — когато след действие (например изтриване на елемент) фокусът се премества в началото на списъка или върху системния бутон „Назад”. Потребителят на VoiceOver губи контекста. Решение: програмно преместете фокуса върху елемента, най-близо до изтрития.
Невидим фокус — елементът получава фокус, но няма визуален индикатор (потребителите на клавиатура не виждат къде са). В iOS проверявайте UIAccessibility.isVoiceOverRunning за персонализирани индикатори. Според Deque University, 2024, невидимият фокус е втората по честота причина за провал на одита за достъпност.
Модални прозорци — фокусът остава върху фоновия контент след отваряне на модален прозорец. В iOS модалният view автоматично улавя фокуса, ако е зададен modalPresentationStyle = .pageSheet. В Android използвайте setFocusable(true) върху контейнера на диалога.
Обратният проблем: фокусът засяда вътре в модалния прозорец и не може да излезе (освен при затваряне). Това е допустимо само за модалните прозорци — потребителят трябва съзнателно да затвори прозореца. За обикновените екрани focus trap е критична грешка. Решение: уверете се, че последният елемент на модалния прозорец (бутонът „Затвори”) връща фокуса обратно.
За персонализираните екрани (карти, платна, игри) автоматичният геометричен ред не е приложим. Разработчикът трябва да изгради дървото за достъпност ръчно. В iOS за това се предефинира методът UIAccessibilityContainer.
Пример за персонализирано платно:
class CanvasView: UIView {
var shapes: [ShapeView] = []
override var accessibilityElements: [Any]? {
get {
// Сортираме фигурите по Z-индекс, а не по геометрия
return shapes.sorted { $0.zIndex < $1.zIndex }
}
set {}
}
}
В Android за персонализиран View предефинирайте onInitializeAccessibilityNodeInfo:
override fun onInitializeAccessibilityNodeInfo(
info: AccessibilityNodeInfo
) {
super.onInitializeAccessibilityNodeInfo(info)
info.addChild(firstElement)
info.addChild(secondElement)
info.isFocusable = true
}
За динамичните списъци (чат, новинарска емисия) след добавяне на елемент извикайте преместване на фокуса върху първия нов елемент. В iOS: UIAccessibility.post(notification: .layoutChanged, argument: newMessage). В Android: sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED).
iOS автоматично определя областта на фокуса на базата на frame на елемента. Ако елементът има трансформация (transform, rotation), VoiceOver може да фокусира върху неправилната област. Изрично задайте accessibilityFrame в координатите на екрана: element.accessibilityFrame = UIAccessibility.convertToScreenCoordinates(element.bounds, in: element). Това гарантира, че VoiceOver ще подчертае правилната област.
За анимираните екрани (UIKit Dynamics, Lottie, SpriteKit) програмният фокус е особено важен. VoiceOver не може да изгради дърво за достъпност за динамично движещите се елементи. Задавайте isAccessibilityElement = false върху контейнерите на анимацията и true само върху интерактивните елементи вътре.
Ръчно тестване: включете VoiceOver (iOS) или TalkBack (Android), прекарайте с жест надясно по цялата последователност. Фокусът трябва да следва визуалния ред — отляво надясно, отгоре надолу. Всеки интерактивен елемент трябва да получи фокус точно веднъж.
Автоматизираното тестване е трудно, но възможно:
func testKeyboardFocusOrder() {
let app = XCUIApplication()
app.launch()
app.textFields["Email"].tap()
// Tab — само с апаратна клавиатура
}
За Android използвайте Accessibility Testing Framework:
@Test
fun testFocusOrder() {
onView(withId(R.id.fieldEmail))
.check(matches(isFocusable()))
onView(withId(R.id.fieldEmail))
.perform(focus())
onView(withId(R.id.fieldPassword))
.check(matches(isFocused()))
}
Най-надеждният метод е UI тест на сценария: попълнете формуляра стъпка по стъпка (Email → Парола → Изпрати), проверявайки, че всяка стъпка завършва успешно. Ако редът на фокуса е нарушен, сценарият ще се провали при опит за взаимодействие с елемент извън фокуса.
Инструментът Accessibility Inspector в Xcode показва пълното дърво за достъпност. Можете да преминете през елементите в реда на VoiceOver и да видите точния път на фокуса. Използвайте раздела „Audit” за автоматично търсене на нарушения на Focus Order.
Често задавани въпроси
WCAG 2.4.3 (Focus Order) — критерий за успех от ниво A. Изисква редът на фокуса да запазва смисъла на съдържанието при последователна навигация. Нарушението се счита за критично и блокира сертификацията.
Скритите елементи трябва да имат isAccessibilityElement = false в iOS или visibility = gone/invisible в Android. При появата им — програмно преместете фокуса чрез UIAccessibility.post(notification: .layoutChanged).
iOS управлява чрез accessibilityElements и shouldGroupAccessibilityElement, Android — чрез атрибутите nextFocus* и AccessibilityNodeInfo. Принципът е един и същ: геометричен ред по подразбиране с възможност за предефиниране.
Задайте descendantFocusability = "beforeDescendants" върху кореновия елемент и настройте реда в адаптера чрез onInitializeAccessibilityNodeInfo за всяка клетка.
Свържете апаратна клавиатура чрез Bluetooth или USB. В iOS натиснете Tab за преместване на фокуса. В Android включете TalkBack и използвайте клавишите Tab и стрелките.
Обобщение
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също