Focus Order là trình tự mà các phần tử giao diện nhận tiêu điểm khi điều hướng bằng bàn phím, Switch Control, VoiceOver hoặc TalkBack. Trong ứng dụng di động, thứ tự tiêu điểm xác định cách người dùng di chuyển giữa các điều khiển bằng cử chỉ hoặc nút. Theo W3C WCAG 2.2, Success Criterion 2.4.3, 2023, tiêu điểm phải tuân theo một trình tự logic nhằm bảo toàn ý nghĩa của nội dung. Vi phạm nguyên tắc này là một trong những nguyên nhân phổ biến dẫn đến thất bại trong kiểm tra khả năng tiếp cận.
Những điểm chính
Focus Order là trình tự mà người dùng di chuyển giữa các phần tử tương tác bằng các phương pháp nhập thay thế: bàn phím (Tab), Switch Control (từng bước), VoiceOver (vuốt phải/trái) hoặc TalkBack. Không giống chuột hoặc màn hình cảm ứng, nơi người dùng chọn trực tiếp phần tử, điều hướng bằng tiêu điểm là tuyến tính — mỗi bước di chuyển tiêu điểm đến phần tử tiếp theo.
Theo Apple HIG, 2024, VoiceOver sử dụng thứ tự các phần tử trong cây khả năng tiếp cận, được xây dựng dựa trên vị trí trực quan: góc trên bên trái → góc dưới bên phải. Nếu màn hình có bố cục phức tạp (cột, Grid, ZStack), cây có thể không khớp với thứ tự trực quan.
Nguyên tắc WCAG 2.4.3: “Nếu một trang web có thể được điều hướng tuần tự qua các phần và thứ tự tiêu điểm ảnh hưởng đến ý nghĩa, thì tiêu điểm phải tuân theo một thứ tự bảo toàn ý nghĩa và khả năng thao tác”. Ngoại lệ: nội dung động nơi tiêu điểm có thể nhảy để thu hút sự chú ý (cảnh báo, cửa sổ modal).
Người dùng Switch Control (người khuyết tật vận động) di chuyển tự động qua các phần tử — hết chu kỳ này đến chu kỳ khác. Nếu thứ tự bị phá vỡ, người dùng mất gấp 3 lần thời gian để hoàn thành biểu mẫu. Theo Deque University, 2024, Focus Order chính xác giảm thời gian hoàn thành biểu mẫu 60% cho người dùng công nghệ hỗ trợ.
Đặc biệt chú ý — cửa sổ modal. Sau khi mở modal, tiêu điểm phải ngay lập tức di chuyển đến phần tử tương tác đầu tiên bên trong modal (thường là nút “Đóng” hoặc “Xác nhận”). Sau khi đóng — quay lại phần tử đã kích hoạt modal. Đây là yêu cầu của WCAG 2.4.3 và cũng là một lỗi phổ biến.
Trong iOS, VoiceOver tự động xây dựng thứ tự dựa trên hình học: các phần tử được sắp xếp theo Y, sau đó theo X. Đối với màn hình có cấu trúc phức tạp, thứ tự này có thể không chính xác — nhà phát triển phải can thiệp.
Công cụ chính:
Ví dụ thiết lập thứ tự tùy chỉnh cho thẻ sản phẩm:
class ProductCardView: UIView {
let titleLabel = UILabel()
let priceLabel = UILabel()
let buyButton = UIButton()
override var accessibilityElements: [Any]? {
get {
return [titleLabel!, priceLabel!, buyButton!]
}
set {}
}
}
Để di chuyển tiêu điểm bằng lệnh sau một hành động:
UIAccessibility.post(
notification: .layoutChanged,
argument: newlyAddedItem
)
Thuộc tính shouldGroupAccessibilityElement hữu ích cho các thẻ trong bộ sưu tập. Nếu được đặt thành true trên thẻ cha, VoiceOver coi toàn bộ thẻ là một phần tử duy nhất. Người dùng có thể chạm hai lần để kích hoạt toàn bộ thẻ hoặc cấu hình rotor để điều hướng nội bộ. Được khuyến nghị cho UICollectionViewCell và UITableViewCell.
Trong Android, TalkBack cũng sử dụng thứ tự hình học, nhưng các thuộc tính nextFocus* rõ ràng được ưu tiên. Các thuộc tính này được đặt trong XML hoặc bằng lệnh:
| Thuộc tính | Mục đích | Ví dụ |
|---|---|---|
| nextFocusDown | Phần tử khi điều hướng xuống | @+id/field_email |
| nextFocusUp | Phần tử khi điều hướng lên | @+id/field_name |
| nextFocusLeft | Phần tử bên trái | @+id/btn_back |
| nextFocusRight | Phần tử bên phải | @+id/btn_next |
Ví dụ cho biểu mẫu đăng ký:
<EditText
android:id="@+id/field_email"
android:nextFocusDown="@+id/field_password" />
<EditText
android:id="@+id/field_password"
android:nextFocusDown="@+id/btn_submit" />
Đối với RecyclerView, thứ tự tiêu điểm là động — do bộ chuyển đổi xác định. Nếu các ô có cấu trúc phức tạp, hãy đặt descendantFocusability = “beforeDescendants” và xác định thứ tự trong nút phần tử danh sách. Đối với Jetpack Compose, thứ tự tiêu điểm được thiết lập qua Modifier.focusOrder() và FocusOrder. Ưu tiên: previous (con), next (tiếp theo), khóa tùy chỉnh.
Nếu một phần tử quá nhỏ cho tiêu điểm (nhỏ hơn 44pt), hãy tăng vùng chạm qua TouchDelegate trong iOS hoặc minWidth/minHeight trong Android. Theo Google Material Design, 2024, vùng chạm tối thiểu là 48×48dp. VoiceOver và TalkBack tập trung vào hộp giới hạn của phần tử. Các phần tử nhỏ hơn 30pt có thể không truy cập được bằng tiêu điểm cử chỉ — người dùng không thể chạm vào chúng.
Tiêu điểm nhảy — khi sau một hành động (ví dụ, xóa phần tử) tiêu điểm di chuyển đến đầu danh sách hoặc nút “Quay lại” hệ thống. Người dùng VoiceOver mất ngữ cảnh. Giải pháp: di chuyển tiêu điểm bằng lệnh đến phần tử gần nhất với phần tử đã xóa.
Tiêu điểm vô hình — một phần tử nhận tiêu điểm nhưng không có chỉ báo trực quan (người dùng bàn phím không thấy họ ở đâu). Trong iOS, hãy kiểm tra UIAccessibility.isVoiceOverRunning cho các chỉ báo tùy chỉnh. Theo Deque University, 2024, tiêu điểm vô hình là nguyên nhân phổ biến thứ hai dẫn đến thất bại kiểm tra khả năng tiếp cận.
Modal — tiêu điểm vẫn ở lại trên nội dung nền sau khi mở modal. Trong iOS, chế độ xem modal tự động chiếm tiêu điểm nếu modalPresentationStyle = .pageSheet được đặt. Trong Android, sử dụng setFocusable(true) trên container hộp thoại.
Vấn đề ngược lại: tiêu điểm bị kẹt bên trong modal và không thể thoát ra (ngoại trừ đóng). Điều này chỉ chấp nhận được duy nhất đối với cửa sổ modal — người dùng phải chủ động đóng cửa sổ. Đối với màn hình thông thường, bẫy tiêu điểm là một lỗi nghiêm trọng. Giải pháp: đảm bảo phần tử cuối cùng của modal (nút “Đóng”) trả lại tiêu điểm.
Đối với màn hình tùy chỉnh (bản đồ, canvas, trò chơi), thứ tự hình học tự động không áp dụng được. Nhà phát triển phải xây dựng cây khả năng tiếp cận thủ công. Trong iOS, phương thức UIAccessibilityContainer được ghi đè cho mục đích này.
Ví dụ cho canvas tùy chỉnh:
class CanvasView: UIView {
var shapes: [ShapeView] = []
override var accessibilityElements: [Any]? {
get {
// Sắp xếp hình theo Z-index, không theo hình học
return shapes.sorted { $0.zIndex < $1.zIndex }
}
set {}
}
}
Trong Android, đối với View tùy chỉnh, hãy ghi đè onInitializeAccessibilityNodeInfo:
override fun onInitializeAccessibilityNodeInfo(
info: AccessibilityNodeInfo
) {
super.onInitializeAccessibilityNodeInfo(info)
info.addChild(firstElement)
info.addChild(secondElement)
info.isFocusable = true
}
Đối với danh sách động (trò chuyện, nguồn cấp tin tức), sau khi thêm phần tử, hãy di chuyển tiêu điểm đến phần tử mới đầu tiên. Trong iOS: UIAccessibility.post(notification: .layoutChanged, argument: newMessage). Trong Android: sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED).
iOS tự động xác định vùng tiêu điểm dựa trên khung của phần tử. Nếu phần tử có biến đổi (transform, rotation), VoiceOver có thể tiêu điểm vào vùng sai. Hãy đặt rõ ràng accessibilityFrame trong tọa độ màn hình: element.accessibilityFrame = UIAccessibility.convertToScreenCoordinates(element.bounds, in: element). Điều này đảm bảo VoiceOver làm nổi bật đúng vùng.
Đối với màn hình hoạt hình (UIKit Dynamics, Lottie, SpriteKit), tiêu điểm bằng lệnh đặc biệt quan trọng. VoiceOver không thể xây dựng cây khả năng tiếp cận cho các phần tử di chuyển động. Hãy đặt isAccessibilityElement = false trên container hoạt hình và true chỉ trên các phần tử tương tác bên trong.
Kiểm tra thủ công: bật VoiceOver (iOS) hoặc TalkBack (Android), vuốt sang phải qua toàn bộ trình tự. Tiêu điểm phải tuân theo thứ tự trực quan — trái sang phải, trên xuống dưới. Mỗi phần tử tương tác phải nhận tiêu điểm đúng một lần.
Kiểm tra tự động khó khăn nhưng có thể:
func testKeyboardFocusOrder() {
let app = XCUIApplication()
app.launch()
app.textFields["Email"].tap()
// Tab — chỉ với bàn phím vật lý
}
Đối với Android, sử dụng 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()))
}
Phương pháp đáng tin cậy nhất là kiểm tra kịch bản giao diện: điền biểu mẫu từng bước (Email → Mật khẩu → Gửi), kiểm tra từng bước hoàn thành thành công. Nếu thứ tự tiêu điểm bị phá vỡ, kịch bản sẽ thất bại khi cố tương tác với phần tử ngoài tiêu điểm.
Công cụ Accessibility Inspector trong Xcode hiển thị toàn bộ cây khả năng tiếp cận. Bạn có thể duyệt qua các phần tử theo thứ tự VoiceOver và xem đường dẫn tiêu điểm chính xác. Sử dụng tab “Audit” để tự động phát hiện vi phạm Focus Order.
Câu hỏi thường gặp
WCAG 2.4.3 (Focus Order) là tiêu chí thành công cấp A. Nó yêu cầu thứ tự tiêu điểm phải bảo toàn ý nghĩa nội dung trong quá trình điều hướng tuần tự. Vi phạm được coi là nghiêm trọng và chặn chứng nhận.
Các phần tử ẩn phải có isAccessibilityElement = false trong iOS hoặc visibility = gone/invisible trong Android. Khi xuất hiện, hãy di chuyển tiêu điểm bằng lệnh qua UIAccessibility.post(notification: .layoutChanged).
iOS quản lý qua accessibilityElements và shouldGroupAccessibilityElement, Android qua các thuộc tính nextFocus* và AccessibilityNodeInfo. Nguyên tắc giống nhau: thứ tự hình học mặc định có thể ghi đè.
Đặt descendantFocusability = “beforeDescendants” trên phần tử gốc và cấu hình thứ tự trong bộ chuyển đổi qua onInitializeAccessibilityNodeInfo cho mỗi ô.
Kết nối bàn phím vật lý qua Bluetooth hoặc USB. Trên iOS nhấn Tab để di chuyển tiêu điểm. Trên Android bật TalkBack và sử dụng phím Tab và các phím mũi tên.
Tổng kết
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm