Theo mặc định, hành vi của trình đọc màn hình hỗ trợ tiếp cận trong ứng dụng Compose được triển khai theo thứ tự đọc dự kiến, thường là từ trái sang phải, sau đó là từ trên xuống dưới.
Tuy nhiên, có một số loại bố cục ứng dụng mà thuật toán không thể xác định thứ tự đọc thực tế nếu không có gợi ý bổ sung. Trong các ứng dụng dựa trên khung hiển thị, bạn có thể khắc phục các vấn đề như vậy bằng cách sử dụng thuộc tính traversalBefore
và traversalAfter
.
Kể từ Compose 1.5, Compose cung cấp một API linh hoạt không kém, nhưng có mô hình khái niệm mới.
isTraversalGroup
và traversalIndex
là các thuộc tính ngữ nghĩa cho phép bạn điều khiển chức năng hỗ trợ tiếp cận và thứ tự lấy nét TalkBack trong các trường hợp mà thuật toán sắp xếp mặc định không phù hợp. isTraversalGroup
xác định các nhóm quan trọng về mặt ngữ nghĩa, trong khi traversalIndex
điều chỉnh thứ tự của các phần tử riêng lẻ trong các nhóm đó. Bạn có thể sử dụng riêng isTraversalGroup
hoặc sử dụng traversalIndex
để tuỳ chỉnh thêm.
Sử dụng isTraversalGroup
và traversalIndex
trong ứng dụng của bạn để kiểm soát thứ tự truyền tải trình đọc màn hình.
Nhóm các phần tử bằng isTraversalGroup
isTraversalGroup
là một thuộc tính boolean xác định xem nút ngữ nghĩa có phải là một nhóm truyền tải hay không. Loại nút này là nút có chức năng đóng vai trò là ranh giới hoặc đường viền trong việc sắp xếp các phần tử con của nút.
Việc đặt isTraversalGroup = true
trên một nút có nghĩa là mọi thành phần con của nút đó đều được truy cập trước khi chuyển sang các phần tử khác. Bạn có thể đặt isTraversalGroup
trên các nút có thể làm tâm điểm không phải của trình đọc màn hình, chẳng hạn như Cột, Hàng hoặc Hộp.
Ví dụ sau đây sử dụng isTraversalGroup
. Biểu ngữ này trao đổi 4 thành phần văn bản. Hai phần tử bên trái thuộc về một phần tử CardBox
, trong khi hai phần tử bên phải thuộc về một phần tử CardBox
khác:
// CardBox() function takes in top and bottom sample text. @Composable fun CardBox( topSampleText: String, bottomSampleText: String, modifier: Modifier = Modifier ) { Box(modifier) { Column { Text(topSampleText) Text(bottomSampleText) } } } @Composable fun TraversalGroupDemo() { val topSampleText1 = "This sentence is in " val bottomSampleText1 = "the left column." val topSampleText2 = "This sentence is " val bottomSampleText2 = "on the right." Row { CardBox( topSampleText1, bottomSampleText1 ) CardBox( topSampleText2, bottomSampleText2 ) } }
Mã tạo ra kết quả tương tự như sau:
Vì chưa có ngữ nghĩa nào được thiết lập nên hành vi mặc định của trình đọc màn hình là truyền tải các phần tử từ trái sang phải và từ trên xuống dưới. Do mặc định này, TalkBack sẽ đọc các đoạn câu theo thứ tự không chính xác:
"Câu này nằm ở" → "Câu này là" → "cột bên trái". → "ở bên phải".
Để sắp xếp các phân đoạn một cách chính xác, hãy sửa đổi đoạn mã ban đầu để đặt isTraversalGroup
thành true
:
@Composable fun TraversalGroupDemo2() { val topSampleText1 = "This sentence is in " val bottomSampleText1 = "the left column." val topSampleText2 = "This sentence is" val bottomSampleText2 = "on the right." Row { CardBox( // 1, topSampleText1, bottomSampleText1, Modifier.semantics { isTraversalGroup = true } ) CardBox( // 2, topSampleText2, bottomSampleText2, Modifier.semantics { isTraversalGroup = true } ) } }
Vì isTraversalGroup
được đặt cụ thể trên mỗi CardBox
, nên các ranh giới CardBox
sẽ áp dụng khi sắp xếp các phần tử của chúng. Trong trường hợp này, CardBox
bên trái sẽ được đọc trước, sau đó là CardBox
bên phải.
Lúc này, TalkBack sẽ đọc các đoạn câu theo đúng thứ tự:
"Câu này nằm ở" → "cột bên trái". → "Câu này là" → "ở bên phải".
Tuỳ chỉnh thêm thứ tự duyệt qua
traversalIndex
là một thuộc tính số thực cho phép bạn tuỳ chỉnh thứ tự truyền tải TalkBack. Nếu việc nhóm các thành phần lại với nhau là chưa đủ để TalkBack hoạt động chính xác, hãy sử dụng traversalIndex
kết hợp với isTraversalGroup
để tuỳ chỉnh thêm thứ tự của trình đọc màn hình.
Thuộc tính traversalIndex
có các đặc điểm sau:
- Các phần tử có giá trị
traversalIndex
thấp hơn sẽ được ưu tiên trước. - Có thể mang tính tích cực hoặc tiêu cực.
- Giá trị mặc định là
0f
. - Chỉ ảnh hưởng đến các nút có thể lấy tiêu điểm của trình đọc màn hình, chẳng hạn như các phần tử trên màn hình như văn bản hoặc nút. Ví dụ: việc chỉ đặt
traversalIndex
trên một cột sẽ không có hiệu lực, trừ phi cột đó cũng đặtisTraversalGroup
.
Ví dụ sau cho thấy cách bạn có thể sử dụng kết hợp traversalIndex
và isTraversalGroup
.
Ví dụ: Mặt đồng hồ đi ngang
Mặt đồng hồ là một trường hợp phổ biến khi thứ tự truyền tải tiêu chuẩn không hoạt động. Ví dụ trong phần này là một bộ chọn giờ, trong đó người dùng có thể chuyển qua các số trên mặt đồng hồ và chọn các chữ số cho khung giờ và phút.
Trong đoạn mã được đơn giản hoá sau đây, có một CircularLayout
, trong đó có 12 số được vẽ, bắt đầu bằng số 12 và di chuyển theo chiều kim đồng hồ xung quanh vòng tròn:
@Composable fun ClockFaceDemo() { CircularLayout { repeat(12) { hour -> ClockText(hour) } } } @Composable private fun ClockText(value: Int) { Box(modifier = Modifier) { Text((if (value == 0) 12 else value).toString()) } }
Vì mặt đồng hồ không được đọc một cách hợp lý với thứ tự mặc định từ trái sang phải và từ trên xuống dưới, nên TalkBack sẽ đọc các số theo thứ tự. Để khắc phục vấn đề này, hãy sử dụng giá trị bộ đếm tăng dần như trong đoạn mã sau:
@Composable fun ClockFaceDemo() { CircularLayout(Modifier.semantics { isTraversalGroup = true }) { repeat(12) { hour -> ClockText(hour) } } } @Composable private fun ClockText(value: Int) { Box(modifier = Modifier.semantics { this.traversalIndex = value.toFloat() }) { Text((if (value == 0) 12 else value).toString()) } }
Để thiết lập đúng thứ tự truyền tải, trước tiên, hãy đặt CircularLayout
làm một nhóm truyền tải và đặt isTraversalGroup = true
. Sau đó, khi mỗi văn bản đồng hồ được vẽ vào bố cục, hãy đặt traversalIndex
tương ứng của văn bản đó thành giá trị bộ đếm.
Vì giá trị bộ đếm liên tục tăng nên traversalIndex
của mỗi giá trị đồng hồ sẽ lớn hơn khi các số được thêm vào màn hình – giá trị đồng hồ 0 có traversalIndex
là 0 và giá trị đồng hồ 1 có traversalIndex
là 1.
Bằng cách này, thứ tự mà TalkBack đọc các tin nhắn đó sẽ được đặt. Giờ đây, các số bên trong CircularLayout
được đọc theo thứ tự dự kiến.
Vì traversalIndexes
đã được đặt chỉ tương ứng với các chỉ mục khác trong cùng một nhóm, nên phần còn lại của thứ tự màn hình đã được giữ nguyên. Nói cách khác, thay đổi về ngữ nghĩa hiển thị trong đoạn mã trước đó chỉ sửa đổi thứ tự trong mặt đồng hồ đã đặt isTraversalGroup = true
.
Xin lưu ý rằng nếu bạn không đặt ngữ nghĩa CircularLayout's
thành isTraversalGroup =
true
, những thay đổi đối với traversalIndex
vẫn áp dụng. Tuy nhiên, nếu không có CircularLayout
để liên kết, thì 12 chữ số của mặt đồng hồ sẽ được đọc lần cuối, sau khi mọi phần tử khác trên màn hình đã được truy cập. Điều này xảy ra vì tất cả các phần tử khác đều có traversalIndex
mặc định là 0f
và các phần tử văn bản đồng hồ được đọc sau tất cả các phần tử 0f
khác.
Ví dụ: Tuỳ chỉnh thứ tự duyệt cho nút hành động nổi
Trong ví dụ này, traversalIndex
và isTraversalGroup
kiểm soát thứ tự truyền tải của nút hành động nổi (FAB) trong Material Design. Cơ sở của ví dụ này là bố cục sau:
Theo mặc định, bố cục trong ví dụ này có thứ tự TalkBack như sau:
Thanh ứng dụng trên cùng → Văn bản mẫu từ 0 đến 6 → nút hành động nổi (FAB) → Thanh ứng dụng ở dưới cùng
Trước tiên, bạn nên cho trình đọc màn hình tập trung vào nút hành động nổi. Để đặt traversalIndex
trên một phần tử Material như FAB, hãy làm như sau:
@Composable fun FloatingBox() { Box(modifier = Modifier.semantics { isTraversalGroup = true; traversalIndex = -1f }) { FloatingActionButton(onClick = {}) { Icon(imageVector = Icons.Default.Add, contentDescription = "fab icon") } } }
Trong đoạn mã này, việc tạo một hộp có isTraversalGroup
được đặt thành true
và đặt traversalIndex
trên chính hộp đó (-1f
thấp hơn giá trị mặc định là 0f
) có nghĩa là hộp nổi sẽ xuất hiện trước mọi thành phần khác trên màn hình.
Tiếp theo, bạn có thể đặt hộp nổi và các thành phần khác vào một scaffold (giàn giáo), giúp triển khai bố cục Material Design:
@OptIn(ExperimentalMaterial3Api::class) @Composable fun ColumnWithFABFirstDemo() { Scaffold( topBar = { TopAppBar(title = { Text("Top App Bar") }) }, floatingActionButtonPosition = FabPosition.End, floatingActionButton = { FloatingBox() }, content = { padding -> ContentColumn(padding = padding) }, bottomBar = { BottomAppBar { Text("Bottom App Bar") } } ) }
TalkBack tương tác với các phần tử theo thứ tự sau:
FAB → Thanh ứng dụng trên cùng → Văn bản mẫu từ 0 đến 6 → Thanh ứng dụng dưới cùng
Tài nguyên khác
- Hỗ trợ tiếp cận: Các khái niệm và kỹ thuật thiết yếu phổ biến cho tất cả hoạt động phát triển ứng dụng Android
- Tạo ứng dụng dễ tiếp cận: Các bước chính bạn có thể thực hiện để tăng khả năng hỗ trợ tiếp cận cho ứng dụng của mình
- Nguyên tắc cải thiện khả năng hỗ trợ tiếp cận của ứng dụng: Những nguyên tắc chính cần lưu ý khi cải thiện khả năng hỗ trợ tiếp cận cho ứng dụng của bạn
- Kiểm thử khả năng hỗ trợ tiếp cận: Các nguyên tắc và công cụ kiểm thử khả năng hỗ trợ tiếp cận của Android