Obsługa TalkBack w aplikacjach na telewizory

Aplikacje na telewizory mają czasem przypadki użycia, które utrudniają użytkownikom korzystającym z TalkBack korzystanie z nich. Aby zapewnić im lepsze działanie TalkBack, zapoznaj się z poszczególnymi sekcjami tego przewodnika i w razie potrzeby wprowadź zmiany w aplikacji. Jeśli Twoja aplikacja korzysta z widoków niestandardowych, zapoznaj się z odpowiednim przewodnikiem, w którym opisaliśmy, jak obsługiwać ułatwienia dostępu w widokach niestandardowych.

Obsługa widoków zagnieżdżonych

Użytkownikom TalkBack może mieć trudności z poruszaniem się po zagnieżdżonych widokach. Jeśli tylko jest to możliwe, TalkBack wybieraj widok nadrzędny lub podrzędny, ale nie oba jednocześnie.

Aby umożliwić zaznaczenie widoku przez TalkBack, ustaw wartość true dla atrybutów focusable i focusableInTouchMode. Ten krok jest wymagany, ponieważ niektóre telewizory mogą włączać i wyłączać tryb dotykowy, gdy włączona jest funkcja TalkBack.

Aby w widoku nie można było zaznaczyć, wystarczy ustawić w atrybucie focusable wartość false. Jeśli jednak widok jest możliwy do działania (czyli w odpowiadającym mu elementu AccessibilityNodeInfo występuje element ACTION_CLICK), nadal można go zaznaczyć.

Zmiana opisów działań

Domyślnie TalkBack czyta komunikat „Naciśnij przycisk wyboru, aby aktywować” w przypadku widoków zachęcających do działania. W przypadku niektórych działań określenie „aktywacja” może nie być odpowiednim opisem. Aby podać dokładniejszy opis, możesz go zmienić:

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);
  }
});

Dodaj obsługę suwaków

TalkBack na telewizory ma specjalną obsługę suwaków. Aby włączyć tryb suwaka, dodaj do widoku danych ACTION_SET_PROGRESS razem z obiektem RangeInfo.

Użytkownik włączy tryb suwaka, naciskając środkowy przycisk na pilocie. W tym trybie przyciski strzałek generują działania związane z ułatwieniami dostępu ACTION_SCROLL_FORWARD i ACTION_SCROLL_BACKWARD.

W tym przykładzie zaimplementowano suwak głośności o poziomach od 1 do 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;
  }
}