Tăng khả năng tiếp cận cho khung hiển thị tuỳ chỉnh

Nếu ứng dụng của bạn cần một thành phần hiển thị tuỳ chỉnh, bạn phải làm cho thành phần đó trở nên dễ tiếp cận hơn. Các bước sau đây có thể cải thiện khả năng tiếp cận của khung hiển thị tuỳ chỉnh, như mô tả trên trang này:

  • Xử lý các lượt nhấp của thành phần điều khiển hướng.
  • Triển khai các phương thức API hỗ trợ tiếp cận.
  • Gửi các đối tượng AccessibilityEvent dành riêng cho khung hiển thị tuỳ chỉnh.
  • Điền vào AccessibilityEventAccessibilityNodeInfo cho khung hiển thị.

Xử lý các lượt nhấp của thành phần điều khiển hướng

Trên hầu hết các thiết bị, thao tác nhấn vào một khung hiển thị bằng một thành phần điều khiển hướng sẽ gửi một KeyEventKEYCODE_DPAD_CENTER đến khung hiển thị đang được lấy làm tâm điểm. Tất cả các khung hiển thị Android tiêu chuẩn đều xử lý KEYCODE_DPAD_CENTER một cách thích hợp. Khi xây dựng một thành phần điều khiển tuỳ chỉnh cho View, hãy đảm bảo sự kiện này có tác dụng tương tự như khi nhấn vào khung hiển thị trên màn hình cảm ứng.

Thành phần điều khiển tuỳ chỉnh phải xem sự kiện KEYCODE_ENTER giống như KEYCODE_DPAD_CENTER. Điều này giúp người dùng dễ dàng tương tác hơn với bàn phím đầy đủ.

Triển khai các phương thức API hỗ trợ tiếp cận

Sự kiện hỗ trợ tiếp cận là những thông báo về hoạt động tương tác của người dùng với các thành phần giao diện trực quan của ứng dụng. Những thông báo này do dịch vụ hỗ trợ tiếp cận xử lý. Dịch vụ này sử dụng thông tin trong các sự kiện này để tạo ý kiến phản hồi và các lời nhắc bổ sung. Các phương thức hỗ trợ tiếp cận nằm trong lớp ViewView.AccessibilityDelegate. Các phương thức này bao gồm như sau:

dispatchPopulateAccessibilityEvent()
Hệ thống gọi phương thức này khi khung hiển thị tuỳ chỉnh tạo ra một sự kiện hỗ trợ tiếp cận. Phương thức triển khai mặc định của phương thức này sẽ gọi onPopulateAccessibilityEvent() cho khung hiển thị này, rồi gọi phương thức dispatchPopulateAccessibilityEvent() cho mỗi thành phần con của khung hiển thị này.
onInitializeAccessibilityEvent()
Hệ thống gọi phương thức này để lấy thêm thông tin về trạng thái của khung hiển thị ngoài nội dung văn bản. Nếu khung hiển thị tuỳ chỉnh của bạn cung cấp chức năng kiểm soát tương tác ngoài một TextView hoặc Button đơn giản, bạn nên ghi đè phương thức này và thiết lập thông tin bổ sung về khung hiển thị bằng phương thức này. Thông tin bổ sung gồm có loại trường mật khẩu, loại hộp đánh dấu hoặc các trạng thái cung cấp cho người dùng thông tin phản hồi hoặc hoạt động tương tác của người dùng về sự kiện. Nếu bạn ghi đè phương thức này, hãy gọi phương thức triển khai lớp cha của phương thức này và chỉ sửa đổi các thuộc tính không do lớp cha đặt.
onInitializeAccessibilityNodeInfo()
Phương thức này cung cấp thông tin về trạng thái của khung hiển thị cho các dịch vụ hỗ trợ tiếp cận. Phương thức triển khai View mặc định có một tập hợp các thuộc tính khung hiển thị chuẩn. Nhưng nếu khung hiển thị tuỳ chỉnh của bạn cung cấp chức năng kiểm soát tương tác ngoài một TextView hoặc Button đơn giản, bạn nên ghi đè phương thức này và thiết lập thông tin bổ sung về khung hiển thị vào đối tượng AccessibilityNodeInfo được phương thức này xử lý.
onPopulateAccessibilityEvent()
Phương thức này sẽ thiết lập lời nhắc bằng văn bản nói của AccessibilityEvent dành cho khung hiển thị của bạn. Phương thức này cũng được gọi nếu khung hiển thị đó là con của khung hiển thị tạo ra một sự kiện hỗ trợ tiếp cận.
onRequestSendAccessibilityEvent()
Hệ thống gọi phương thức này khi một thành phần con của khung hiển thị tạo ra một AccessibilityEvent. Bước này cho phép khung hiển thị mẹ sửa đổi sự kiện hỗ trợ tiếp cận bằng thông tin bổ sung. Chỉ triển khai phương thức này nếu khung hiển thị tuỳ chỉnh của bạn có khung hiển thị con và nếu khung hiển thị mẹ có thể cung cấp thông tin ngữ cảnh cho sự kiện hỗ trợ tiếp cận hữu ích cho các dịch vụ hỗ trợ tiếp cận.
sendAccessibilityEvent()
Hệ thống gọi phương thức này khi người dùng thao tác trên một khung hiển thị. Sự kiện này được phân loại theo loại hành động của người dùng, chẳng hạn như TYPE_VIEW_CLICKED. Nói chung, bạn phải gửi AccessibilityEvent bất cứ khi nào nội dung trong khung hiển thị tuỳ chỉnh của bạn thay đổi.
sendAccessibilityEventUnchecked()
Phương thức này được dùng khi mã gọi cần trực tiếp điều khiển việc kiểm tra trạng thái bật của chức năng hỗ trợ tiếp cận trên thiết bị (AccessibilityManager.isEnabled()). Nếu triển khai phương thức này, bạn phải thực hiện lệnh gọi như khi chức năng hỗ trợ tiếp cận được bật, bất kể chế độ cài đặt thực tế của hệ thống. Thông thường, bạn không cần triển khai phương thức này đối với khung hiển thị tuỳ chỉnh.

Để hỗ trợ tính năng hỗ trợ tiếp cận, hãy ghi đè và triển khai các phương thức hỗ trợ tiếp cận trước đó ngay trong lớp khung hiển thị tuỳ chỉnh.

Ở mức tối thiểu, hãy triển khai các phương thức hỗ trợ tiếp cận sau đây cho lớp khung hiển thị tuỳ chỉnh:

  • dispatchPopulateAccessibilityEvent()
  • onInitializeAccessibilityEvent()
  • onInitializeAccessibilityNodeInfo()
  • onPopulateAccessibilityEvent()

Để biết thêm thông tin về cách triển khai các phương thức này, hãy xem phần điền các sự kiện hỗ trợ tiếp cận.

Gửi các sự kiện hỗ trợ tiếp cận

Tuỳ thuộc vào các chi tiết cụ thể của khung hiển thị tuỳ chỉnh, bạn có thể cần gửi các đối tượng AccessibilityEvent vào các thời điểm khác nhau hoặc cho các sự kiện không được xử lý bằng phương thức triển khai mặc định. Lớp View cung cấp phương thức triển khai mặc định cho các loại sự kiện sau:

Nói chung, bạn phải gửi AccessibilityEvent bất cứ khi nào nội dung trong khung hiển thị tuỳ chỉnh của bạn thay đổi. Ví dụ: nếu bạn đang triển khai một thanh trượt tuỳ chỉnh cho phép người dùng chọn một giá trị số bằng cách nhấn phím mũi tên trái hoặc phải, thì khung hiển thị tuỳ chỉnh của bạn phải phát ra một sự kiện của TYPE_VIEW_TEXT_CHANGED bất cứ khi nào giá trị của thanh trượt thay đổi. Mã mẫu sau đây minh hoạ việc sử dụng phương thức sendAccessibilityEvent() để báo cáo sự kiện này.

Kotlin

override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
    return when(keyCode) {
        KeyEvent.KEYCODE_DPAD_LEFT -> {
            currentValue--
            sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED)
            true
        }
        ...
    }
}

Java

@Override
public boolean onKeyUp (int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
        currentValue--;
        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
        return true;
    }
    ...
}

Điền các sự kiện hỗ trợ tiếp cận

Mỗi AccessibilityEvent có một tập hợp các thuộc tính bắt buộc để mô tả trạng thái hiện tại của khung hiển thị. Các thuộc tính này bao gồm các thông tin về khung hiển thị như: tên lớp, mô tả về nội dung và trạng thái đã đánh dấu. Các thuộc tính cụ thể bắt buộc đối với mỗi loại sự kiện được mô tả trong tài liệu tham khảo về AccessibilityEvent.

Phương thức triển khai View cung cấp các giá trị mặc định cho các thuộc tính bắt buộc này. Nhiều giá trị trong số này, bao gồm tên lớp và dấu thời gian của sự kiện, được cung cấp tự động. Nếu đang tạo một khung hiển thị tuỳ chỉnh, bạn phải cung cấp thông tin về nội dung và đặc điểm của khung hiển thị này. Thông tin này có thể đơn giản như nhãn nút và có thể bao gồm thông tin bổ sung về trạng thái mà bạn muốn thêm vào sự kiện.

Sử dụng các phương thức onPopulateAccessibilityEvent()onInitializeAccessibilityEvent() để điền hoặc sửa đổi thông tin trong AccessibilityEvent. Sử dụng phương thức onPopulateAccessibilityEvent() dành riêng để thêm hoặc sửa đổi nội dung văn bản của sự kiện. Nội dung này sẽ được các dịch vụ hỗ trợ tiếp cận (như TalkBack) chuyển thành lời nhắc âm thanh. Sử dụng phương thức onInitializeAccessibilityEvent() để điền thêm thông tin về sự kiện, chẳng hạn như trạng thái lựa chọn của khung hiển thị.

Ngoài ra, hãy triển khai phương thức onInitializeAccessibilityNodeInfo(). Dịch vụ hỗ trợ tiếp cận sử dụng các đối tượng AccessibilityNodeInfo được điền bằng phương thức này để điều tra hệ phân cấp khung hiển thị tạo ra một sự kiện hỗ trợ tiếp cận sau khi sự kiện đó được tiếp nhận và đưa ra ý kiến phản hồi thích hợp cho người dùng.

Ví dụ về mã sau đây cho thấy cách ghi đè 3 phương thức này trong khung hiển thị của bạn:

Kotlin

override fun onPopulateAccessibilityEvent(event: AccessibilityEvent?) {
    super.onPopulateAccessibilityEvent(event)
    // Call the super implementation to populate its text for the
    // event. Then, add text not present in a super class.
    // You typically only need to add the text for the custom view.
    if (text?.isNotEmpty() == true) {
        event?.text?.add(text)
    }
}

override fun onInitializeAccessibilityEvent(event: AccessibilityEvent?) {
    super.onInitializeAccessibilityEvent(event)
    // Call the super implementation to let super classes
    // set appropriate event properties. Then, add the new checked
    // property that is not supported by a super class.
    event?.isChecked = isChecked()
}

override fun onInitializeAccessibilityNodeInfo(info: AccessibilityNodeInfo?) {
    super.onInitializeAccessibilityNodeInfo(info)
    // Call the super implementation to let super classes set
    // appropriate info properties. Then, add the checkable and checked
    // properties that are not supported by a super class.
    info?.isCheckable = true
    info?.isChecked = isChecked()
    // You typically only need to add the text for the custom view.
    if (text?.isNotEmpty() == true) {
        info?.text = text
    }
}

Java

@Override
public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
    super.onPopulateAccessibilityEvent(event);
    // Call the super implementation to populate its text for the
    // event. Then, add the text not present in a super class.
    // You typically only need to add the text for the custom view.
    CharSequence text = getText();
    if (!TextUtils.isEmpty(text)) {
        event.getText().add(text);
    }
}

@Override
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
    super.onInitializeAccessibilityEvent(event);
    // Call the super implementation to let super classes
    // set appropriate event properties. Then, add the new checked
    // property that is not supported by a super class.
    event.setChecked(isChecked());
}

@Override
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
    super.onInitializeAccessibilityNodeInfo(info);
    // Call the super implementation to let super classes set
    // appropriate info properties. Then, add the checkable and checked
    // properties that are not supported by a super class.
    info.setCheckable(true);
    info.setChecked(isChecked());
    // You typically only need to add the text for the custom view.
    CharSequence text = getText();
    if (!TextUtils.isEmpty(text)) {
        info.setText(text);
    }
}

Bạn có thể triển khai các phương thức này trực tiếp trong lớp khung hiển thị tuỳ chỉnh.

Cung cấp bối cảnh hỗ trợ tiếp cận tuỳ chỉnh

Các dịch vụ hỗ trợ tiếp cận có thể kiểm tra hệ phân cấp khung hiển thị có chứa một thành phần trên giao diện người dùng có tạo ra sự kiện hỗ trợ tiếp cận. Điều này giúp các dịch vụ hỗ trợ tiếp cận cung cấp thông tin theo ngữ cảnh phong phú hơn để hỗ trợ người dùng.

Có những trường hợp mà dịch vụ hỗ trợ tiếp cận không thể nhận đầy đủ thông tin từ hệ phân cấp khung hiển thị. Ví dụ: thành phần điều khiển giao diện tuỳ chỉnh có 2 hoặc nhiều khu vực có thể nhấn riêng biệt (chẳng hạn như thành phần điều khiển lịch). Trong trường hợp này, các dịch vụ không thể nhận đủ thông tin vì các phần phụ có thể nhấn không nằm trong hệ phân cấp khung hiển thị.

Hình 1. Khung hiển thị tuỳ chỉnh cho lịch có các phần tử ngày có thể chọn.

Trong ví dụ ở hình 1, toàn bộ lịch được triển khai dưới dạng một khung hiển thị, vì vậy, các dịch vụ hỗ trợ tiếp cận không nhận đủ thông tin về nội dung của khung hiển thị và lựa chọn của người dùng trong khung hiển thị đó, trừ phi nhà phát triển cung cấp thêm thông tin. Ví dụ: nếu người dùng nhấp vào ngày có nhãn 17, khung hỗ trợ tiếp cận sẽ chỉ nhận được thông tin mô tả cho thành phần điều khiển lịch. Trong trường hợp này, dịch vụ hỗ trợ tiếp cận TalkBack sẽ thông báo "Lịch" hoặc "Lịch tháng Tư" và người dùng không biết ngày nào được chọn.

Để cung cấp đầy đủ thông tin bối cảnh cho các dịch vụ hỗ trợ tiếp cận trong những tình huống như vậy, khung hỗ trợ tiếp cận sẽ cung cấp cách thức để chỉ định một hệ phân cấp khung hiển thị ảo. Hệ phân cấp khung hiển thị ảo là một cách giúp các nhà phát triển ứng dụng cung cấp một hệ phân cấp khung hiển thị bổ sung cho các dịch vụ hỗ trợ tiếp cận để phù hợp hơn với thông tin trên màn hình. Phương pháp này giúp các dịch vụ hỗ trợ tiếp cận cung cấp thêm thông tin hữu ích về bối cảnh cho người dùng.

Một tình huống khác mà bạn có thể cần phải sử dụng hệ phân cấp khung hiển thị ảo là giao diện người dùng chứa một nhóm các thành phần điều khiển View có chức năng liên quan chặt chẽ, trong đó thao tác trên một thành phần điều khiển sẽ ảnh hưởng đến nội dung của một hoặc nhiều thành phần, chẳng hạn như bộ chọn số với các nút lên và xuống riêng biệt. Trong trường hợp này, các dịch vụ hỗ trợ tiếp cận không thể nhận đủ thông tin vì một thao tác trên một thành phần điều khiển sẽ thay đổi nội dung trong thành phần điều khiển khác và mối quan hệ của các thành phần điều khiển đó có thể không rõ ràng so với dịch vụ.

Để xử lý tình huống này, hãy nhóm các thành phần điều khiển có liên quan bằng một khung hiển thị để chứa tất cả, đồng thời cung cấp một hệ phân cấp khung hiển thị ảo từ vùng chứa này để thể hiện rõ thông tin và hành vi do các thành phần điều khiển cung cấp.

Để cung cấp hệ phân cấp khung hiển thị ảo cho một khung hiển thị, hãy ghi đè phương thức getAccessibilityNodeProvider() trong khung hiển thị hoặc nhóm khung hiển thị tuỳ chỉnh và trả về một phương thức triển khai của AccessibilityNodeProvider. Bạn có thể triển khai một hệ phân cấp khung hiển thị ảo bằng cách sử dụng Thư viện hỗ trợ với phương thức ViewCompat.getAccessibilityNodeProvider() và cung cấp phương thức triển khai cho AccessibilityNodeProviderCompat.

Để đơn giản hoá nhiệm vụ cung cấp thông tin cho các dịch vụ hỗ trợ tiếp cận và quản lý tập trung vào tính năng hỗ trợ tiếp cận, bạn có thể triển khai ExploreByTouchHelper. Phương thức này cung cấp AccessibilityNodeProviderCompat và có thể được đính kèm dưới dạng AccessibilityDelegateCompat của khung hiển thị bằng cách gọi setAccessibilityDelegate. Để biết ví dụ, vui lòng xem ExploreByTouchHelperActivity. ExploreByTouchHelper cũng được các tiện ích khung như CalendarView sử dụng thông qua khung hiển thị con SimpleMonthView của nó.

Xử lý sự kiện nhấn tuỳ chỉnh

Các thành phần điều khiển khung hiển thị tuỳ chỉnh có thể yêu cầu hành vi không theo chuẩn mực của sự kiện chạm, như được minh hoạ trong các ví dụ sau.

Khai báo các hành động dựa trên lượt nhấp

Nếu tiện ích của bạn sử dụng giao diện OnClickListener hoặc OnLongClickListener, hệ thống sẽ xử lý hành động ACTION_CLICKACTION_LONG_CLICK cho bạn. Nếu ứng dụng của bạn sử dụng một tiện ích được tuỳ chỉnh nhiều hơn dựa trên giao diện OnTouchListener, hãy khai báo các trình xử lý tuỳ chỉnh cho các hành động hỗ trợ tiếp cận dựa trên lượt nhấp. Để thực hiện việc này, hãy gọi phương thức replaceAccessibilityAction() cho từng hành động, như minh hoạ trong đoạn mã sau đây:

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    ...

    // Assumes that the widget is designed to select text when tapped, and selects
    // all text when tapped and held. In its strings.xml file, this app sets
    // "select" to "Select" and "select_all" to "Select all".
    ViewCompat.replaceAccessibilityAction(
        binding.textSelectWidget,
        ACTION_CLICK,
        getString(R.string.select)
    ) { view, commandArguments ->
        selectText()
    }

    ViewCompat.replaceAccessibilityAction(
        binding.textSelectWidget,
        ACTION_LONG_CLICK,
        getString(R.string.select_all)
    ) { view, commandArguments ->
        selectAllText()
    }
}

Java

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...

    // Assumes that the widget is designed to select text when tapped, and select
    // all text when tapped and held. In its strings.xml file, this app sets
    // "select" to "Select" and "select_all" to "Select all".
    ViewCompat.replaceAccessibilityAction(
            binding.textSelectWidget,
            ACTION_CLICK,
            getString(R.string.select),
            (view, commandArguments) -> selectText());

    ViewCompat.replaceAccessibilityAction(
            binding.textSelectWidget,
            ACTION_LONG_CLICK,
            getString(R.string.select_all),
            (view, commandArguments) -> selectAllText());
}

Tạo sự kiện lượt nhấp tuỳ chỉnh

Một thành phần điều khiển tuỳ chỉnh có thể sử dụng phương thức trình nghe onTouchEvent(MotionEvent) để phát hiện các sự kiện ACTION_DOWNACTION_UP và kích hoạt một sự kiện nhấp chuột đặc biệt. Để duy trì khả năng tương thích với các dịch vụ hỗ trợ tiếp cận, mã xử lý sự kiện nhấp chuột tuỳ chỉnh này phải làm như sau:

  1. Tạo một AccessibilityEvent thích hợp cho hành động nhấp chuột được diễn giải.
  2. Bật dịch vụ hỗ trợ tiếp cận để thực hiện thao tác nhấp chuột tuỳ chỉnh cho những người dùng không thể sử dụng màn hình cảm ứng.

Để xử lý các yêu cầu này một cách hiệu quả, mã của bạn phải ghi đè phương thức performClick(). Phương thức này phải gọi phương thức triển khai lớp cha tương ứng, sau đó thực thi bất kỳ hành động nào mà sự kiện nhấp chuột yêu cầu. Khi phát hiện thao tác nhấp chuột tuỳ chỉnh, mã đó sẽ gọi phương thức performClick(). Ví dụ về mã sau đây minh hoạ cho mẫu này.

Kotlin

class CustomTouchView(context: Context) : View(context) {

    var downTouch = false

    override fun onTouchEvent(event: MotionEvent): Boolean {
        super.onTouchEvent(event)

        // Listening for the down and up touch events.
        return when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                downTouch = true
                true
            }

            MotionEvent.ACTION_UP -> if (downTouch) {
                downTouch = false
                performClick() // Call this method to handle the response and
                // enable accessibility services to
                // perform this action for a user who can't
                // tap the touchscreen.
                true
            } else {
                false
            }

            else -> false  // Return false for other touch events.
        }
    }

    override fun performClick(): Boolean {
        // Calls the super implementation, which generates an AccessibilityEvent
        // and calls the onClick() listener on the view, if any.
        super.performClick()

        // Handle the action for the custom click here.

        return true
    }
}

Java

class CustomTouchView extends View {

    public CustomTouchView(Context context) {
        super(context);
    }

    boolean downTouch = false;

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        super.onTouchEvent(event);

        // Listening for the down and up touch events
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                downTouch = true;
                return true;

            case MotionEvent.ACTION_UP:
                if (downTouch) {
                    downTouch = false;
                    performClick(); // Call this method to handle the response and
                                    // enable accessibility services to
                                    // perform this action for a user who can't
                                    // tap the touchscreen.
                    return true;
                }
        }
        return false; // Return false for other touch events.
    }

    @Override
    public boolean performClick() {
        // Calls the super implementation, which generates an AccessibilityEvent
        // and calls the onClick() listener on the view, if any.
        super.performClick();

        // Handle the action for the custom click here.

        return true;
    }
}

Mẫu trước giúp đảm bảo rằng sự kiện nhấp chuột tuỳ chỉnh tương thích với các dịch vụ hỗ trợ tiếp cận bằng cách sử dụng phương thức performClick() để tạo một sự kiện hỗ trợ tiếp cận và cung cấp điểm truy cập để các dịch vụ hỗ trợ tiếp cận thay người dùng thực hiện sự kiện nhấp chuột tuỳ chỉnh.