Hỗ trợ TalkBack trong các ứng dụng truyền hình

Đôi khi, các ứng dụng dành cho TV có những trường hợp sử dụng gây khó khăn cho người dùng phải dùng TalkBack. Để cung cấp trải nghiệm TalkBack tốt hơn cho những người dùng này, hãy xem lại từng phần trong hướng dẫn này và triển khai các thay đổi trong ứng dụng của bạn nếu cần. Nếu ứng dụng của bạn dùng khung hiển thị tuỳ chỉnh, thì bạn cũng nên tham khảo hướng dẫn tương ứng trong đó mô tả cách hỗ trợ khả năng hỗ trợ tiếp cận bằng khung hiển thị tuỳ chỉnh.

Xử lý các khung hiển thị lồng nhau

Người dùng TalkBack có thể gặp khó khăn khi di chuyển trong chế độ xem lồng nhau. Bất cứ khi nào có thể, hãy để TalkBack hoặc chế độ xem con có thể lấy làm tâm điểm, chứ không được làm cả hai.

Để TalkBack có thể lấy tiêu điểm một khung hiển thị, hãy đặt focusable và thuộc tính focusableInTouchMode thành true. Bước này cần thiết vì một số thiết bị TV có thể chuyển sang và thoát khỏi chế độ cảm ứng khi TalkBack đang hoạt động.

Để làm cho một khung hiển thị không thể lấy tiêu điểm, bạn chỉ cần đặt thuộc tính focusable thành false. Tuy nhiên, nếu khung hiển thị đó có thể hành động (nghĩa là AccessibilityNodeInfo tương ứng của khung hiển thị có ACTION_CLICK), thì khung hiển thị đó vẫn có thể làm tâm điểm.

Thay đổi nội dung mô tả cho Hành động

Theo mặc định, TalkBack sẽ thông báo "Nhấn chọn để kích hoạt" cho các chế độ xem có thể thao tác. Đối với một số hành động, cụm từ "kích hoạt" có thể không phải là nội dung mô tả phù hợp. Để cung cấp thông tin mô tả chính xác hơn, bạn có thể thay đổi:

Kotlin

findViewById<View>(R.id.custom_actionable_view).accessibilityDelegate = object : View.AccessibilityDelegate() {
  override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfo) {
    super.onInitializeAccessibilityNodeInfo(host, info)
    info.addAction(
      AccessibilityAction(
        AccessibilityAction.ACTION_CLICK.id,
        getString(R.string.custom_label)
      )
    )
  }
}

Java

findViewById(R.id.custom_actionable_view).setAccessibilityDelegate(new AccessibilityDelegate() {
  @Override
  public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
    super.onInitializeAccessibilityNodeInfo(host, info);
    AccessibilityAction action = new AccessibilityAction(
        AccessibilityAction.ACTION_CLICK.getId(), getString(R.string.custom_label));
    info.addAction(action);
  }
});

Thêm tính năng hỗ trợ cho thanh trượt

TalkBack trên TV có tính năng hỗ trợ đặc biệt cho thanh trượt. Để bật chế độ thanh trượt, hãy thêm ACTION_SET_PROGRESS vào một khung hiển thị cùng với đối tượng RangeInfo.

Người dùng chuyển sang chế độ thanh trượt bằng cách nhấn nút giữa của điều khiển từ xa TV. Ở chế độ này, các nút mũi tên tạo ra thao tác hỗ trợ tiếp cận ACTION_SCROLL_FORWARDACTION_SCROLL_BACKWARD.

Ví dụ sau đây triển khai thanh trượt âm lượng có các mức từ 1 đến 10:

Kotlin

/**
 *   This example only provides slider functionality for TalkBack users. Additional logic should
 *   be added for other users. Such logic may reuse the increase and decrease methods.
 */
class VolumeSlider(context: Context?, attrs: AttributeSet?) : LinearLayout(context, attrs) {
  private val min = 1
  private val max = 10
  private var current = 5
  private var textView: TextView? = null

  init {
    isFocusable = true
    isFocusableInTouchMode = true
    val rangeInfo =
      AccessibilityNodeInfo.RangeInfo(
        AccessibilityNodeInfo.RangeInfo.RANGE_TYPE_INT,
        min.toFloat(),
        max.toFloat(),
        current.toFloat()
      )
    accessibilityDelegate =
      object : AccessibilityDelegate() {
        override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfo) {
          super.onInitializeAccessibilityNodeInfo(host, info)
          info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_PROGRESS)
          info.rangeInfo = rangeInfo
        }

        override fun performAccessibilityAction(host: View, action: Int, args: Bundle?): Boolean {
          if (action == AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD.id) {
            increase()
            return true
          }
          if (action == AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD.id) {
            decrease()
            return true
          }
          return super.performAccessibilityAction(host, action, args)
        }
      }
  }

  override fun onFinishInflate() {
    super.onFinishInflate()
    textView = findViewById(R.id.text)
    textView!!.text = context.getString(R.string.level, current)
  }

  private fun increase() {
    update((current + 1).coerceAtMost(max))
  }

  private fun decrease() {
    update((current - 1).coerceAtLeast(min))
  }

  private fun update(newLevel: Int) {
    if (textView == null) {
      return
    }
    val newText = context.getString(R.string.level, newLevel)
    // Update the user interface with the new volume.
    textView!!.text = newText
    // Announce the new volume.
    announceForAccessibility(newText)
    current = newLevel
  }
}

Java

/**
 *   This example only provides slider functionality for TalkBack users. Additional logic should
 *   be added for other users. Such logic can reuse the increase and decrease methods.
 */
public class VolumeSlider extends LinearLayout {
  private final int min = 1;
  private final int max = 10;
  private int current = 5;
  private TextView textView;

  public VolumeSlider(Context context, AttributeSet attrs) {
    super(context, attrs);
    setFocusable(true);
    setFocusableInTouchMode(true);
    RangeInfo rangeInfo = new RangeInfo(RangeInfo.RANGE_TYPE_INT, min, max, current);
    setAccessibilityDelegate(
        new AccessibilityDelegate() {
          @Override
          public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
            super.onInitializeAccessibilityNodeInfo(host, info);
            info.addAction(AccessibilityAction.ACTION_SET_PROGRESS);
            info.setRangeInfo(rangeInfo);
          }

          @Override
          public boolean performAccessibilityAction(View host, int action, Bundle args) {
            if (action == AccessibilityAction.ACTION_SCROLL_FORWARD.getId()) {
              increase();
              return true;
            }
            if (action == AccessibilityAction.ACTION_SCROLL_BACKWARD.getId()) {
              decrease();
              return true;
            }
            return super.performAccessibilityAction(host, action, args);
          }
        });
  }

  @Override
  protected void onFinishInflate() {
    super.onFinishInflate();
    textView = findViewById(R.id.text);
    textView.setText(getContext().getString(R.string.level, current));
  }

  private void increase() {
    update(Math.min(current + 1, max));
  }

  private void decrease() {
    update(Math.max(current - 1, min));
  }

  private void update(int newLevel) {
    if (textView == null) {
      return;
    }
    String newText = getContext().getString(R.string.level, newLevel);
    // Update the user interface with the new volume.
    textView.setText(newText);
    // Announce the new volume.
    announceForAccessibility(newText);
    current = newLevel;
  }
}