Các nguyên tắc để cải thiện khả năng hỗ trợ tiếp cận của ứng dụng

Để 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 trueandroid: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:

Hình 1. Ví dụ về cách tạo thành phần giao diện người dùng chỉ sử dụng màu sắc (bên trái) và sử dụng màu sắc, hình dạng và văn bản (bên phải).

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:

Lớp học lập trình

Bài đăng trên blog