Theo mặc định, các API Material, Compose UI và Foundation triển khai và cung cấp nhiều phương pháp hỗ trợ tiếp cận. Các thành phần này chứa ngữ nghĩa tích hợp sẵn theo vai trò và chức năng cụ thể của chúng. Điều này có nghĩa là hầu hết các tính năng hỗ trợ tiếp cận đều được cung cấp mà không cần hoặc cần rất ít thao tác bổ sung.
Việc sử dụng các API phù hợp cho mục đích phù hợp có nghĩa là các thành phần thường đi kèm với các hành vi hỗ trợ tiếp cận được xác định trước, bao gồm các trường hợp sử dụng tiêu chuẩn. Tuy nhiên, hãy luôn kiểm tra kỹ xem các giá trị mặc định này có phù hợp với nhu cầu hỗ trợ tiếp cận của bạn hay không. Nếu không, Compose sẽ cung cấp các cách để đáp ứng các yêu cầu cụ thể hơn.
Việc hiểu rõ ngữ nghĩa và mẫu hỗ trợ tiếp cận mặc định trong các API Compose giúp bạn sử dụng các API này một cách dễ dàng. Thư viện này cũng giúp bạn hỗ trợ khả năng hỗ trợ tiếp cận trong nhiều thành phần tuỳ chỉnh hơn.
Kích thước đích chạm tối thiểu
Mọi phần tử trên màn hình mà ai đó có thể nhấp, chạm vào hoặc tương tác đều phải đủ lớn để tương tác đáng tin cậy. Khi xác định kích thước các phần tử này, hãy đảm bảo đặt kích thước tối thiểu là 48dp để tuân thủ đúng hướng dẫn hỗ trợ tiếp cận Material Design.
Các thành phần Material, chẳng hạn như Checkbox, RadioButton, Switch, Slider và Surface — đặt kích thước tối thiểu này nội bộ, nhưng chỉ khi thành phần có thể nhận được thao tác của người dùng. Ví dụ: khi tham số onCheckedChange của Checkbox được đặt giá trị rỗng, thì hộp đánh dấu sẽ bao gồm khoảng đệm để có chiều rộng và chiều cao ít nhất là 48 dp.
@Composable private fun CheckableCheckbox() { Checkbox(checked = true, onCheckedChange = {}) }
Khi tham số onCheckedChange được đặt thành rỗng, khoảng đệm không được thêm vào vì không thể tương tác trực tiếp với thành phần này.
@Composable private fun NonClickableCheckbox() { Checkbox(checked = true, onCheckedChange = null) }
Khi triển khai các chế độ điều khiển lựa chọn như Switch, RadioButton hoặc Checkbox, bạn thường nâng hành vi có thể nhấp lên vùng chứa mẹ bằng cách đặt lệnh gọi lại lượt nhấp trên thành phần kết hợp thành null rồi thêm một đối tượng sửa đổi toggleable hoặc selectable cho thành phần kết hợp mẹ.
@Composable private fun CheckableRow() { MaterialTheme { var checked by remember { mutableStateOf(false) } Row( Modifier .toggleable( value = checked, role = Role.Checkbox, onValueChange = { checked = !checked } ) .padding(16.dp) .fillMaxWidth() ) { Text("Option", Modifier.weight(1f)) Checkbox(checked = checked, onCheckedChange = null) } } }
Khi kích thước của một thành phần kết hợp có thể nhấp nhỏ hơn kích thước đích chạm tối thiểu, Compose vẫn tăng kích thước đích chạm. Compose làm như vậy bằng cách mở rộng kích thước đích chạm nằm ngoài phạm vi của thành phần kết hợp.
Ví dụ sau đây chứa một Box rất nhỏ có thể nhấp. Khu vực đích chạm tự động mở rộng ra ngoài phạm vi của Box, vì vậy, việc nhấn vào bên cạnh Box sẽ vẫn kích hoạt sự kiện nhấp chuột.
@Composable private fun SmallBox() { var clicked by remember { mutableStateOf(false) } Box( Modifier .size(100.dp) .background(if (clicked) Color.DarkGray else Color.LightGray) ) { Box( Modifier .align(Alignment.Center) .clickable { clicked = !clicked } .background(Color.Black) .size(1.dp) ) } }
Để ngăn chặn sự trùng lặp có thể giữa các khu vực cảm ứng của các composable, hãy luôn sử dụng kích thước tối thiểu đủ lớn cho composable. Trong ví dụ, điều đó có nghĩa là sử dụng thành phần sửa đổi sizeIn để đặt kích thước tối thiểu cho hộp bên trong:
@Composable private fun LargeBox() { var clicked by remember { mutableStateOf(false) } Box( Modifier .size(100.dp) .background(if (clicked) Color.DarkGray else Color.LightGray) ) { Box( Modifier .align(Alignment.Center) .clickable { clicked = !clicked } .background(Color.Black) .sizeIn(minWidth = 48.dp, minHeight = 48.dp) ) } }
Nếu phần tử phải giữ kích thước hiển thị nhỏ hơn, hãy sử dụng đối tượng sửa đổi minimumInteractiveComponentSize trong Material 3. Thành phần này dành ít nhất 48 dp không gian bố cục theo mỗi chiều và căn giữa thành phần trong không gian đó, nhờ vậy thành phần này giữ nguyên kích thước được vẽ và các thành phần xung quanh di chuyển để nhường chỗ. Đối tượng sửa đổi không tự mở rộng đích chạm: đối tượng này để lại không gian trong bố cục cho việc mở rộng được mô tả trước đó.
Các thành phần Material như Checkbox sử dụng đối tượng sửa đổi này trong nội bộ. Hãy đặt đối tượng sửa đổi này trước bất kỳ đối tượng sửa đổi kích thước nào, vì đối tượng sửa đổi kích thước trước đó sẽ giới hạn không gian mà đối tượng sửa đổi này có thể đặt trước.
@Composable private fun MinimumInteractiveBox() { var clicked by remember { mutableStateOf(false) } Row(verticalAlignment = Alignment.CenterVertically) { Box( Modifier .clickable { clicked = !clicked } .minimumInteractiveComponentSize() .size(24.dp) .background(if (clicked) Color.DarkGray else Color.Black) ) Text("Option") } }
Thành phần đồ hoạ
Khi bạn xác định thành phần kết hợp Image hoặc Icon, không có cách tự động nào để khung Android hiểu nội dung mà ứng dụng đang hiển thị. Bạn cần truyền mô tả dạng văn bản của phần tử đồ hoạ.
Hãy tưởng tượng một màn hình mà người dùng có thể chia sẻ trang hiện tại với bạn bè. Màn hình này chứa biểu tượng chia sẻ có thể nhấp:
Chỉ dựa vào biểu tượng này, khung Android sẽ không thể mô tả cho người dùng khiếm thị. Khung Android cần có thêm mô tả bằng văn bản của biểu tượng đó.
Tham số contentDescription mô tả một phần tử đồ hoạ. Sử dụng chuỗi đã được bản địa hoá vì người dùng có thể nhìn thấy chuỗi này.
@Composable private fun ShareButton(onClick: () -> Unit) { IconButton(onClick = onClick) { Icon( imageVector = Icons.Filled.Share, contentDescription = stringResource(R.string.label_share) ) } }
Một số thành phần đồ hoạ chỉ mang tính chất trang trí và bạn có thể không muốn thông báo cho người dùng. Khi đặt tham số contentDescription thành null, bạn chỉ báo cho khung Android rằng phần tử này không có hành động hoặc trạng thái liên kết.
@Composable private fun PostImage(post: Post, modifier: Modifier = Modifier) { val image = post.imageThumb ?: painterResource(R.drawable.placeholder_1_1) Image( painter = image, // Specify that this image has no semantic meaning contentDescription = null, modifier = modifier .size(40.dp, 40.dp) .clip(MaterialTheme.shapes.small) ) }
contentDescription chủ yếu dùng cho các thành phần đồ hoạ, chẳng hạn như hình ảnh. Các thành phần Material, chẳng hạn như Button hoặc Text, và các hành vi có thể thực hiện, chẳng hạn như clickable hoặc toggleable, đi kèm với các ngữ nghĩa được xác định trước khác mô tả hành vi nội tại của chúng và có thể được thay đổi thông qua các API Compose khác.
Yếu tố tương tác
API Material và Foundation Compose tạo ra các phần tử trên giao diện người dùng mà người dùng có thể tương tác thông qua API đối tượng sửa đổi clickable và toggleable. Vì các thành phần có thể tương tác có thể bao gồm nhiều phần tử, nên theo mặc định, clickable và toggleable sẽ hợp nhất ngữ nghĩa của các phần tử con để thành phần đó được coi là một thực thể logic.
Ví dụ: Button Material có thể bao gồm một biểu tượng con và một số văn bản. Thay vì coi các phần tử con là các cá thể, Button Material sẽ hợp nhất ngữ nghĩa của các phần tử con theo mặc định để các dịch vụ hỗ trợ tiếp cận có thể nhóm chúng theo cách phù hợp:
Tương tự, việc sử dụng đối tượng sửa đổi clickable cũng khiến một composable hợp nhất ngữ nghĩa của các thành phần con thành một thực thể duy nhất, được gửi đến các dịch vụ hỗ trợ tiếp cận bằng một bản trình bày hành động tương ứng:
Row( // Uses `mergeDescendants = true` under the hood modifier = Modifier.clickable { openArticle() } ) { Icon( painter = painterResource(R.drawable.ic_logo), contentDescription = "Open", ) Text("Accessibility in Compose") }
Bạn cũng có thể đặt một onClickLabel cụ thể trên thành phần mẹ có thể nhấp để cung cấp thêm thông tin cho các dịch vụ hỗ trợ tiếp cận và cung cấp thông tin đại diện rõ ràng hơn về hành động:
Row( modifier = Modifier .clickable(onClickLabel = "Open this article") { openArticle() } ) { Icon( painter = painterResource(R.drawable.ic_logo), contentDescription = "Open" ) Text("Accessibility in Compose") }
Lấy TalkBack làm ví dụ, đối tượng sửa đổi clickable này và nhãn nhấp của đối tượng này sẽ cho phép TalkBack cung cấp gợi ý hành động "Nhấn đúp để mở bài viết này", thay vì phản hồi mặc định chung chung hơn là "Nhấn đúp để kích hoạt".
Phản hồi này thay đổi tuỳ thuộc vào loại hành động. Thao tác nhấp và giữ sẽ cung cấp gợi ý TalkBack là "Nhấn đúp và giữ để", theo sau là một nhãn:
Row( modifier = Modifier .combinedClickable( onLongClickLabel = "Bookmark this article", onLongClick = { addToBookmarks() }, onClickLabel = "Open this article", onClick = { openArticle() }, ) ) {}
Trong một số trường hợp, bạn có thể không có quyền truy cập trực tiếp vào đối tượng sửa đổi clickable (ví dụ: khi đối tượng này được đặt ở một vị trí nào đó trong lớp lồng nhau thấp hơn), nhưng vẫn muốn thay đổi nhãn thông báo từ nhãn mặc định. Để làm việc này, hãy tách việc đặt clickable khỏi việc sửa đổi thông báo bằng cách sử dụng đối tượng sửa đổi semantics và đặt nhãn lượt nhấp ở đó để sửa đổi cách trình bày hành động:
@Composable private fun ArticleList(openArticle: () -> Unit) { NestedArticleListItem( // Clickable is set separately, in a nested layer: onClickAction = openArticle, // Semantics are set here: modifier = Modifier.semantics { onClick( label = "Open this article", action = { // Not needed here: openArticle() true } ) } ) }
Bạn không cần phải truyền hành động nhấp hai lần. Các API Compose hiện có, chẳng hạn như clickable hoặc Button, sẽ xử lý việc này cho bạn. Logic hợp nhất xác minh rằng nhãn và hành động của đối tượng sửa đổi ngoài cùng được thực hiện cho thông tin hiện có. Trong ví dụ trước, NestedArticleListItem tự động chuyển hành động nhấp openArticle() đến ngữ nghĩa clickable của nó. Bạn có thể để trống hành động nhấp trong hành động của đối tượng sửa đổi ngữ nghĩa thứ hai. Tuy nhiên, nhãn nhấp được lấy từ đối tượng sửa đổi ngữ nghĩa thứ hai onClick(label = "Open this document") vì nhãn này không có trong đối tượng sửa đổi ngữ nghĩa đầu tiên.
Bạn có thể gặp phải trường hợp muốn hợp nhất ngữ nghĩa con vào ngữ nghĩa mẹ, nhưng điều đó không xảy ra. Hãy xem phần Hợp nhất và xoá để biết thêm thông tin chi tiết.
Thành phần tuỳ chỉnh
Khi tạo một thành phần tuỳ chỉnh, hãy xem xét cách triển khai một thành phần tương tự trong thư viện Material hoặc các thư viện Compose khác. Sau đó,
bắt chước hoặc sửa đổi hành vi hỗ trợ tiếp cận của thành phần đó cho phù hợp. Ví dụ: nếu bạn thay thế Checkbox trong Material bằng phương pháp triển khai của riêng mình, thì việc xem phương pháp triển khai Checkbox hiện có sẽ nhắc bạn thêm đối tượng sửa đổi triStateToggleable, đối tượng này xử lý các thuộc tính hỗ trợ tiếp cận cho thành phần. Ngoài ra, hãy tận dụng nhiều thành phần sửa đổi Foundation, vì các thành phần sửa đổi này bao gồm các tính năng hỗ trợ tiếp cận tích hợp sẵn và các phương pháp Compose hiện có được đề cập trong phần này.
Bạn cũng có thể tìm thấy ví dụ về thành phần bật/tắt tuỳ chỉnh trong phần Xoá và đặt ngữ nghĩa, cũng như thông tin chi tiết hơn về cách hỗ trợ khả năng hỗ trợ tiếp cận trong các thành phần tuỳ chỉnh trong nguyên tắc API.
Đề xuất cho bạn
- Lưu ý: văn bản có đường liên kết sẽ hiện khi JavaScript tắt
- Hỗ trợ tiếp cận trong Compose
- Kiểm thử bố cục Compose