Để hỗ trợ người dùng có nhu cầu về hỗ trợ tiếp cận, khung Android cho phép bạn tạo dịch vụ hỗ trợ tiếp cận có thể trình bày nội dung của ứng dụng cho người dùng, đồng thời cũng vận hành ứng dụng thay cho người dùng.
Android cung cấp một số dịch vụ hỗ trợ tiếp cận hệ thống, bao gồm:
- TalkBack: giúp người có thị lực kém hoặc người khiếm thị. Dịch vụ này thông báo nội dung thông qua một giọng nói tổng hợp và thực hiện các thao tác trên ứng dụng theo cử chỉ của người dùng.
- Tiếp cận bằng công tắc: giúp người bị khuyết tật vận động. Dịch vụ này làm nổi bật các thành phần tương tác và thực hiện các thao tác để phản hồi khi người dùng nhấn nút. Dịch vụ này cũng cho phép điều khiển thiết bị chỉ bằng một hoặc hai nút.
Để giúp người có nhu cầu hỗ trợ tiếp cận sử dụng được ứng dụng của bạn, ứng dụng phải làm theo các phương pháp hay nhất được mô tả trên trang này. Các phương pháp này được xây dựng dựa trên các nguyên tắc được mô tả trong phần Giúp ứng dụng dễ tiếp cận hơn.
Mỗi phương pháp hay nhất sau đây (mô tả trong các phần tiếp theo) có thể cải thiện khả năng hỗ trợ tiếp cận của ứng dụng:
- Thành phần nhãn
- Người dùng phải hiểu được nội dung và mục đích của từng thành phần tương tác và giao diện người dùng có ý nghĩa trong ứng dụng.
- Thêm thao tác hỗ trợ tiếp cận
- Bằng cách thêm thao tác hỗ trợ tiếp cận, bạn đã cung cấp cho người dùng các dịch vụ hỗ trợ tiếp cận để hoàn thành các luồng người dùng quan trọng trong ứng dụng của bạn.
- Mở rộng tiện ích hệ thống
- Xây dựng dựa trên các thành phần khung hiển thị mà khung này sử dụng, thay vì tạo các thành phần khung hiển thị tuỳ chỉnh của riêng bạn. Các lớp tiện ích và thành phần hiển thị của khung đã cung cấp hầu hết tính năng hỗ trợ tiếp cận mà ứng dụng của bạn cần.
- Dùng chú thích ngoài màu sắc
- Người dùng phải phân biệt được rõ ràng các danh mục thành phần trong giao diện người dùng. Để làm như vậy, hãy sử dụng các mẫu và vị trí để thể hiện những khác biệt này (cùng với màu sắc).
- Tăng khả năng hỗ trợ tiếp cận của nội dung đa phương tiện
- Hãy thêm nội dung mô tả vào nội dung video hoặc âm thanh của ứng dụng để người dùng tiêu thụ nội dung này không cần dựa vào các gợi ý hoàn toàn bằng hình ảnh hay âm thanh.
Thành phần nhãn
Quan trọng là bạn phải cung cấp cho người dùng các nhãn hữu ích và mang tính mô tả cho từng thành phần giao diện người dùng tương tác trong ứng dụng. Mỗi nhãn phải giải thích ý nghĩa và mục đích của một thành phần cụ thể. Các trình đọc màn hình như TalkBack có thể thông báo các nhãn này cho người dùng.
Trong hầu hết trường hợp, bạn chỉ định nội dung mô tả của một thành phần giao diện người dùng trong tệp tài nguyên bố cục chứa thành phần. Thường thì bạn thêm nhãn bằng cách sử dụng thuộc tính contentDescription
, như đã giải thích trong hướng dẫn để tăng khả năng hỗ trợ tiếp cận của ứng dụng. Nhưng có một số kỹ thuật gắn nhãn khác cũng cần được lưu ý theo mô tả trong phần tiếp theo.
Thành phần có thể chỉnh sửa
Khi gắn nhãn thành phần có thể chỉnh sửa (chẳng hạn như các đối tượng EditText
), bạn nên cho thấy văn bản đưa ra ví dụ về giá trị nhập hợp lệ trong chính thành phần đó, bên cạnh việc tạo văn bản mẫu được cung cấp cho trình đọc màn hình. Trong các trường hợp này, bạn có thể sử dụng thuộc tính android:hint
như minh hoạ trong đoạn mã sau:
<!-- The hint text for en-US locale would be "Apartment, suite, or building". --> <EditText android:id="@+id/addressLine2" android:hint="@string/aptSuiteBuilding" ... />
Trong trường hợp này, thuộc tính android:labelFor
của đối tượng View
phải được đặt thành mã nhận dạng của thành phần EditText
. Để biết thêm thông tin chi tiết, hãy xem phần tiếp theo.
Cặp thành phần mà một thành phần mô tả thành phần còn lại
Thường thì một thành phần EditText
sẽ có một đối tượng View
tương ứng để mô tả nội dung mà người dùng phải nhập vào phần tử EditText
. Bạn có thể chỉ định mối quan hệ này bằng cách đặt thuộc tính android:labelFor
của đối tượng View
.
Ví dụ về cách gắn nhãn các cặp thành phần như vậy xuất hiện trong đoạn mã sau:
<!-- Label text for en-US locale would be "Username:" --> <TextView android:id="@+id/usernameLabel" ... android:text="@string/username" android:labelFor="@+id/usernameEntry" /> <EditText android:id="@+id/usernameEntry" ... /> <!-- Label text for en-US locale would be "Password:" --> <TextView android:id="@+id/passwordLabel" ... android:text="@string/password android:labelFor="@+id/passwordEntry" /> <EditText android:id="@+id/passwordEntry" android:inputType="textPassword" ... />
Các thành phần trong một bộ sưu tập
Khi thêm nhãn vào các thành phần của một bộ sưu tập, mỗi nhãn phải là duy nhất. Nhờ vậy, các dịch vụ hỗ trợ tiếp cận của hệ thống có thể tham chiếu chính xác một thành phần trên màn hình khi bạn thông báo về một nhãn. Quan hệ tương ứng này giúp người dùng biết được thời điểm họ đi qua giao diện người dùng hoặc khi họ di chuyển tiêu điểm đến một thành phần mà họ phát hiện ra.
Cụ thể là hãy đưa thêm thông tin dạng văn bản hoặc ngữ cảnh vào các thành phần trong bố cục tái sử dụng (chẳng hạn như các đối tượng RecyclerView
) để mỗi thành phần con đều được xác định riêng biệt.
Để làm như vậy, hãy đặt phần mô tả nội dung trong quá trình triển khai trình chuyển đổi, như minh hoạ trong đoạn mã sau:
Kotlin
data class MovieRating(val title: String, val starRating: Integer) class MyMovieRatingsAdapter(private val myData: Array<MovieRating>): RecyclerView.Adapter<MyMovieRatingsAdapter.MyRatingViewHolder>() { class MyRatingViewHolder(val ratingView: ImageView) : RecyclerView.ViewHolder(ratingView) override fun onBindViewHolder(holder: MyRatingViewHolder, position: Int) { val ratingData = myData[position] holder.ratingView.contentDescription = "Movie ${position}: " + "${ratingData.title}, ${ratingData.starRating} stars" } }
Java
public class MovieRating { private String title; private int starRating; // ... public String getTitle() { return title; } public int getStarRating() { return starRating; } } public class MyMovieRatingsAdapter extends RecyclerView.Adapter<MyAdapter.MyRatingViewHolder> { private MovieRating[] myData; public static class MyRatingViewHolder extends RecyclerView.ViewHolder { public ImageView ratingView; public MyRatingViewHolder(ImageView iv) { super(iv); ratingView = iv; } } @Override public void onBindViewHolder(MyRatingViewHolder holder, int position) { MovieRating ratingData = myData[position]; holder.ratingView.setContentDescription("Movie " + position + ": " + ratingData.getTitle() + ", " + ratingData.getStarRating() + " stars") } }
Nhóm nội dung có liên quan
Nếu ứng dụng của bạn hiện một số thành phần trên giao diện người dùng để tạo thành một nhóm tự nhiên, chẳng hạn như thông tin chi tiết về một bài hát hoặc thuộc tính của một tin nhắn, hãy sắp xếp các thành phần đó trong một vùng chứa, thường là lớp con của ViewGroup
. Đặt thuộc tính android:screenReaderFocusable
của đối tượng vùng chứa thành true
và android:focusable
của từng đối tượng bên trong thành false
. Nhờ vậy, các dịch vụ hỗ trợ tiếp cận có thể trình bày từng phần mô tả nội dung của các thành phần bên trong một thông báo.
Việc hợp nhất các thành phần có liên quan này giúp người dùng công nghệ hỗ trợ khám phá thông tin trên màn hình một cách hiệu quả hơn.
Đoạn mã sau chứa các phần nội dung có liên quan đến một đoạn khác, do đó, thành phần vùng chứa, một thực thể của ConstraintLayout
, có thuộc tính android:screenReaderFocusable
đặt thành true
và thành phần TextView
bên trong có thuộc tính android:focusable
đặt thành false
:
<!-- In response to a single user interaction, accessibility services announce both the title and the artist of the song. --> <ConstraintLayout android:id="@+id/song_data_container" ... android:screenReaderFocusable="true"> <TextView android:id="@+id/song_title" ... android:focusable="false" android:text="@string/my_song_title" /> <TextView android:id="@+id/song_artist" android:focusable="false" android:text="@string/my_songwriter" /> </ConstraintLayout>
Vì các dịch vụ hỗ trợ tiếp cận thông báo về nội dung mô tả của thành phần bên trong qua một cách nói duy nhất, nên bạn phải giữ cho mỗi đoạn mô tả càng ngắn gọn càng tốt, đồng thời vẫn truyền tải ý nghĩa của các thành phần đó.
Lưu ý: Nhìn chung, bạn không nên tổng hợp văn bản của các thành phần con để tạo thành nội dung mô tả về một nhóm. Làm như vậy sẽ khiến nội dung mô tả về nhóm đó trở nên rườm rà; cũng như nếu văn bản của thành phần con thay đổi thì có thể nội dung mô tả về nhóm đó sẽ không khớp với văn bản mà người dùng thấy.
Đối với danh sách hoặc lưới, trình đọc màn hình có thể hợp nhất văn bản trên các nút văn bản con của phần tử lưới hoặc danh sách. Bạn không nên sửa đổi thông báo này.
Nhóm được lồng
Nếu giao diện của ứng dụng thể hiện thông tin đa chiều, chẳng hạn như danh sách sự kiện lễ hội hằng ngày, hãy dùng thuộc tính android:screenReaderFocusable
trên vùng chứa nhóm bên trong. Giao thức gắn nhãn này mang đến sự cân bằng phù hợp giữa số lượng thông báo cần thiết để khám phá nội dung của màn hình và thời lượng của từng thông báo.
Đoạn mã sau đây cho thấy một phương thức gắn nhãn nhóm bên trong các nhóm lớn hơn:
<!-- In response to a single user interaction, accessibility services announce the events for a single stage only. --> <ConstraintLayout android:id="@+id/festival_event_table" ... > <ConstraintLayout android:id="@+id/stage_a_event_column" android:screenReaderFocusable="true"> <!-- UI elements that describe the events on Stage A. --> </ConstraintLayout> <ConstraintLayout android:id="@+id/stage_b_event_column" android:screenReaderFocusable="true"> <!-- UI elements that describe the events on Stage B. --> </ConstraintLayout> </ConstraintLayout>
Tiêu đề trong văn bản
Một số ứng dụng dùng tiêu đề (heading) để tóm tắt các nhóm văn bản xuất hiện trên màn hình. Nếu một thành phần cụ thể View
đại diện cho tiêu đề, bạn có thể cho biết mục đích của thành phần đó cho các dịch vụ hỗ trợ tiếp cận bằng cách đặt thuộc tính android:accessibilityHeading
của thành phần đó thành true
.
Người dùng các dịch vụ hỗ trợ tiếp cận có thể chọn di chuyển giữa các tiêu đề thay vì giữa các đoạn văn hoặc giữa các từ. Tính linh hoạt này cải thiện trải nghiệm điều hướng bằng văn bản.
Tiêu đề của ngăn hỗ trợ tiếp cận
Trong Android 9 (API cấp 28) trở lên, bạn có thể cung cấp tiêu đề dễ hỗ trợ tiếp cận cho các ngăn (pane) trên màn hình. Đối với mục đích hỗ trợ tiếp cận, ngăn là một phần riêng biệt của cửa sổ, chẳng hạn như nội dung của một mảnh. Để các dịch vụ hỗ trợ tiếp cận hiểu hành vi của ngăn giống như cửa sổ, hãy đặt tiêu đề mô tả cho các ngăn của ứng dụng. Các dịch vụ hỗ trợ tiếp cận sau đó có thể cung cấp thêm thông tin chi tiết cho người dùng khi giao diện hoặc nội dung của ngăn thay đổi.
Để chỉ định tiêu đề của một ngăn, hãy sử dụng thuộc tính android:accessibilityPaneTitle
, như thể hiện trong đoạn mã sau:
<!-- Accessibility services receive announcements about content changes that are scoped to either the "shopping cart view" section (top) or "browse items" section (bottom) --> <MyShoppingCartView android:id="@+id/shoppingCartContainer" android:accessibilityPaneTitle="@string/shoppingCart" ... /> <MyShoppingBrowseView android:id="@+id/browseItemsContainer" android:accessibilityPaneTitle="@string/browseProducts" ... />
Thành phần trang trí
Nếu một thành phần trong giao diện người dùng của bạn chỉ dành cho mục đích giãn cách hình ảnh hoặc giao diện thì hãy đặt thuộc tính android:importantForAccessibility
thành "no"
.
Thêm thao tác hỗ trợ tiếp cận
Quan trọng là phải cho phép người dùng sử dụng dịch vụ hỗ trợ tiếp cận dễ dàng triển khai mọi luồng người dùng trong ứng dụng. Ví dụ: khi người dùng vuốt vào một mục trong danh sách, các dịch vụ Hỗ trợ tiếp cận cũng cho thấy thao tác này nhằm có thêm một lựa chọn để hoàn tất cùng một luồng người dùng.
Giúp mọi thao tác dễ tiếp cận hơn
Có thể người dùng TalkBack, Điều khiển bằng giọng nói hoặc Tiếp cận bằng công tắc sẽ cần những cách khác để hoàn tất một số luồng người dùng trong ứng dụng. Đối với các thao tác liên quan đến các cử chỉ như kéo và thả hoặc vuốt, ứng dụng có thể cho người dùng dịch vụ hỗ trợ tiếp cận thấy các thao tác này theo cách dễ tiếp cận.
Ứng dụng có thể cung cấp nhiều lựa chọn giúp người dùng hoàn thành một thao tác thông qua tính năng Thao tác hỗ trợ tiếp cận.
Chẳng hạn như nếu ứng dụng của bạn cho phép người dùng vuốt vào một mục, thì bạn cũng có thể cho thấy chức năng đó thông qua một thao tác hỗ trợ tiếp cận tuỳ chỉnh như sau:
Kotlin
ViewCompat.addAccessibilityAction( // View to add accessibility action itemView, // Label surfaced to user by an accessibility service getText(R.id.archive) ) { _, _ -> // Same method executed when swiping on itemView archiveItem() true }
Java
ViewCompat.addAccessibilityAction( // View to add accessibility action itemView, // Label surfaced to user by an accessibility service getText(R.id.archive), (view, arguments) -> { // Same method executed when swiping on itemView archiveItem(); return true; } );
With the custom accessibility action implemented, users can access the action through the actions menu.
Make available actions understandable
When a view supports actions such as touch & hold, an accessibility service such as TalkBack announces it as "Double tap and hold to long press."
This generic announcement doesn't give the user any context about what a touch & hold action does.
To make this announcement more descriptive, you can replace the accessibility action’s announcement like so:
Kotlin
ViewCompat.replaceAccessibilityAction( // View that contains touch & hold action itemView, AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_LONG_CLICK, // Announcement read by TalkBack to surface this action getText(R.string.favorite), null )
Java
ViewCompat.replaceAccessibilityAction( // View that contains touch & hold action itemView, AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_LONG_CLICK, // Announcement read by TalkBack to surface this action getText(R.string.favorite), null );
This results in TalkBack announcing "Double tap and hold to favorite," helping users understand the purpose of the action.
Extend system widgets
Note: When you design your app's UI, use or extend
system-provided widgets that are as far down Android's class hierarchy as
possible. System-provided widgets that are far down the hierarchy already
have most of the accessibility capabilities your app needs. It's easier
to extend these system-provided widgets than to create your own from the more
generic View
,
ViewCompat
,
Canvas
, and
CanvasCompat
classes.
If you must extend View
or Canvas
directly, which
might be necessary for a highly customized experience or a game level, see
Make custom views more
accessible.
This section uses the example of implementing a special type of
Switch
called TriSwitch
while following
best practices around extending system widgets. A TriSwitch
object works similarly to a Switch
object, except that each instance of
TriSwitch
allows the user to toggle among three possible states.
Extend from far down the class hierarchy
The Switch
object inherits from several framework UI classes in its hierarchy:
View ↳ TextView ↳ Button ↳ CompoundButton ↳ Switch
Tốt nhất là lớp TriSwitch
mới có thể mở rộng trực tiếp từ lớp Switch
. Theo đó, Khung hỗ trợ tiếp cận Android cung cấp hầu hết tính năng hỗ trợ tiếp cận mà lớp TriSwitch
cần đến:
- Thao tác hỗ trợ tiếp cận: thông tin cho hệ thống về cách các dịch vụ hỗ trợ tiếp cận có thể mô phỏng từng hoạt động đầu vào mà người dùng có thể thực hiện trên đối tượng
TriSwitch
. (Kế thừa từView
.) - Sự kiện hỗ trợ tiếp cận: thông tin cho các dịch vụ hỗ trợ tiếp cận về mọi cách có thể mà giao diện của đối tượng
TriSwitch
có thể thay đổi khi màn hình làm mới hoặc cập nhật. (Kế thừa từView
.) - Đặc điểm: thông tin chi tiết về từng đối tượng
TriSwitch
, chẳng hạn như nội dung của văn bản bất kỳ mà đối tượng đó hiển thị. (Kế thừa từTextView
.) - Thông tin trạng thái: nội dung mô tả trạng thái hiện tại của đối tượng
TriSwitch
, chẳng hạn như "đã đánh dấu" ("checked") hoặc "chưa đánh dấu" ("unchecked"). (Kế thừa từCompoundButton
.) - Mô tả văn bản về trạng thái: nội dung giải thích dựa trên văn bản về đặc điểm mà mỗi trạng thái đại diện. (Kế thừa từ
Switch
.)
Hành vi này của Switch
và các lớp cấp cao gần giống hành vi của các đối tượng TriSwitch
. Do đó, quá trình triển khai có thể tập trung vào việc mở rộng số lượng trạng thái có thể từ 2 lên 3.
Xác định sự kiện tuỳ chỉnh
Khi mở rộng một tiện ích hệ thống, có thể bạn sẽ thay đổi một phần cách người dùng tương tác với tiện ích đó. Quan trọng là bạn phải xác định những thay đổi tương tác này để các dịch vụ hỗ trợ tiếp cận có thể cập nhật tiện ích của ứng dụng như khi người dùng tương tác trực tiếp với tiện ích.
Nguyên tắc chung là đối với mỗi lệnh gọi lại dựa trên thành phần hiển thị mà bạn ghi đè, bạn cũng phải xác định lại thao tác hỗ trợ tiếp cận tương ứng bằng cách ghi đè ViewCompat.replaceAccessibilityAction()
.
Trong các kiểm thử của ứng dụng, bạn có thể xác thực hành vi của các thao tác được xác định lại này bằng cách gọi ViewCompat.performAccessibilityAction()
.
Cách thức hoạt động của nguyên tắc này đối với đối tượng TriSwitch
Không giống như đối tượng Switch
thông thường, thao tác nhấn vào một đối tượng TriSwitch
sẽ trải qua 3 trạng thái có thể có. Do đó, bạn cần cập nhật thao tác hỗ trợ tiếp cận ACTION_CLICK
tương ứng:
Kotlin
class TriSwitch(context: Context) : Switch(context) { // 0, 1, or 2 var currentState: Int = 0 private set init { updateAccessibilityActions() } private fun updateAccessibilityActions() { ViewCompat.replaceAccessibilityAction(this, ACTION_CLICK, action-label) { view, args -> moveToNextState() }) } private fun moveToNextState() { currentState = (currentState + 1) % 3 } }
Java
public class TriSwitch extends Switch { // 0, 1, or 2 private int currentState; public int getCurrentState() { return currentState; } public TriSwitch() { updateAccessibilityActions(); } private void updateAccessibilityActions() { ViewCompat.replaceAccessibilityAction(this, ACTION_CLICK, action-label, (view, args) -> moveToNextState()); } private void moveToNextState() { currentState = (currentState + 1) % 3; } }
Dùng chú thích ngoài màu sắc
Để hỗ trợ người dùng mù màu, hãy sử dụng các tín hiệu khác ngoài màu sắc để phân biệt các thành phần giao diện người dùng trong màn hình của ứng dụng. Có thể kể đến những kỹ thuật như sử dụng nhiều hình dạng hoặc kích thước, cung cấp văn bản hoặc hình ảnh trực quan hoặc thêm phản hồi dựa trên âm thanh hoặc xúc giác (để đánh dấu sự khác biệt giữa các thành phần).
Hình 1 cho thấy hai phiên bản của một hoạt động. Một phiên bản chỉ sử dụng màu sắc để phân biệt hai thao tác có thể thực hiện trong một quy trình. Phiên bản còn lại áp dụng phương pháp tốt nhất là sử dụng màu và văn bản bên cạnh màu để làm nổi bật sự khác biệt giữa 2 lựa chọn:
Tăng khả năng hỗ trợ tiếp cận của nội dung đa phương tiện
Nếu bạn đang phát triển một ứng dụng chứa nội dung đa phương tiện, chẳng hạn như một đoạn video hoặc một bản ghi âm, hãy cố gắng hỗ trợ người dùng có nhu cầu hỗ trợ tiếp cận trong việc tìm hiểu nội dung này. Cụ thể, bạn nên làm theo những lời khuyên sau:
- Cung cấp các lựa chọn điều khiển cho phép người dùng tạm dừng hoặc dừng nội dung đa phương tiện, thay đổi âm lượng và bật/tắt phụ đề.
- Nếu video trình bày thông tin quan trọng để hoàn thành quy trình công việc, hãy cung cấp nội dung tương tự ở định dạng thay thế, chẳng hạn như bản chép lời.
Tài nguyên khác
Để tìm hiểu thêm về cách giúp ứng dụng dễ tiếp cận hơn, hãy xem thêm các tài nguyên sau: