Сделайте пользовательские представления более доступными

Если вашему приложению требуется собственный компонент представления , необходимо сделать представление более доступным. Следующие шаги могут улучшить доступность вашего пользовательского представления, как описано на этой странице:

  • Обработка щелчков контроллера направления.
  • Реализуйте методы API специальных возможностей.
  • Отправляйте объекты AccessibilityEvent , специфичные для вашего пользовательского представления.
  • Заполните AccessibilityEvent и AccessibilityNodeInfo для вашего представления.

Обработка щелчков контроллера направления

На большинстве устройств щелчок по представлению с помощью контроллера направления отправляет KeyEvent с KEYCODE_DPAD_CENTER к представлению, которое в данный момент находится в фокусе. Все стандартные представления Android обрабатывают KEYCODE_DPAD_CENTER соответствующим образом. При создании пользовательского элемента управления View убедитесь, что это событие имеет тот же эффект, что и касание представления на сенсорном экране.

Ваш пользовательский элемент управления должен обрабатывать событие KEYCODE_ENTER так же, как KEYCODE_DPAD_CENTER . Это упрощает взаимодействие с полной клавиатурой для пользователей.

Реализация методов API специальных возможностей

События доступности — это сообщения о взаимодействии пользователей с компонентами визуального интерфейса вашего приложения. Эти сообщения обрабатываются службами доступности , которые используют информацию из этих событий для создания дополнительных отзывов и подсказок. Методы доступности являются частью классов View и View.AccessibilityDelegate . Методы следующие:

dispatchPopulateAccessibilityEvent()
Система вызывает этот метод, когда ваше пользовательское представление генерирует событие доступности. Реализация этого метода по умолчанию вызывает onPopulateAccessibilityEvent() для этого представления, а затем метод dispatchPopulateAccessibilityEvent() для каждого дочернего элемента этого представления.
onInitializeAccessibilityEvent()
Система вызывает этот метод для получения дополнительной информации о состоянии представления помимо текстового содержимого. Если ваше пользовательское представление обеспечивает интерактивное управление, выходящее за рамки простого TextView или Button , переопределите этот метод и установите дополнительную информацию о вашем представлении, например тип поля пароля, тип флажка или состояния, которые обеспечивают взаимодействие пользователя или обратную связь с событием, используя этот метод. . Если вы переопределите этот метод, вызовите его суперреализацию и измените только те свойства, которые не установлены суперклассом.
onInitializeAccessibilityNodeInfo()
Этот метод предоставляет службам доступности информацию о состоянии представления. Реализация View по умолчанию имеет стандартный набор свойств представления, но если ваше пользовательское представление обеспечивает интерактивное управление, выходящее за рамки простого TextView или Button , переопределите этот метод и установите дополнительную информацию о вашем представлении в объект AccessibilityNodeInfo , обрабатываемый этим методом.
onPopulateAccessibilityEvent()
Этот метод устанавливает голосовое текстовое приглашение AccessibilityEvent для вашего представления. Он также вызывается, если представление является дочерним по отношению к представлению, генерирующему событие доступности.
onRequestSendAccessibilityEvent()
Система вызывает этот метод, когда дочерний элемент вашего представления генерирует AccessibilityEvent . Этот шаг позволяет родительскому представлению изменить событие доступности, добавив дополнительную информацию. Реализуйте этот метод только в том случае, если ваше пользовательское представление может иметь дочерние представления и если родительское представление может предоставлять контекстную информацию для события доступности, полезную для служб специальных возможностей.
sendAccessibilityEvent()
Система вызывает этот метод, когда пользователь выполняет действие над представлением. Событие классифицируется по типу действия пользователя, например TYPE_VIEW_CLICKED . Как правило, вы должны отправлять AccessibilityEvent при каждом изменении содержимого вашего пользовательского представления.
sendAccessibilityEventUnchecked()
Этот метод используется, когда вызывающему коду необходимо напрямую контролировать проверку включения доступности на устройстве ( AccessibilityManager.isEnabled() ). Если вы реализуете этот метод, выполняйте вызов так, как если бы специальные возможности были включены, независимо от настроек системы. Обычно вам не нужно реализовывать этот метод для пользовательского представления.

Чтобы поддержать специальные возможности, переопределите и реализуйте предыдущие методы специальных возможностей непосредственно в своем пользовательском классе представления.

Как минимум, реализуйте следующие методы доступности для вашего пользовательского класса представления:

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

Дополнительные сведения о реализации этих методов см. в разделе о заполнении событий доступности .

Отправлять события доступности

В зависимости от особенностей вашего пользовательского представления может потребоваться отправлять объекты AccessibilityEvent в разное время или для событий, не обрабатываемых реализацией по умолчанию. Класс View предоставляет реализацию по умолчанию для этих типов событий:

Как правило, вы должны отправлять AccessibilityEvent при каждом изменении содержимого вашего пользовательского представления. Например, если вы реализуете настраиваемый ползунок, который позволяет пользователю выбирать числовое значение, нажимая клавишу со стрелкой влево или вправо, ваше настраиваемое представление должно генерировать событие TYPE_VIEW_TEXT_CHANGED при каждом изменении значения ползунка. В следующем примере кода показано использование метода sendAccessibilityEvent() для сообщения об этом событии.

Котлин

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

Ява

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

Заполнение событий доступности

Каждое AccessibilityEvent имеет набор обязательных свойств, описывающих текущее состояние представления. Эти свойства включают в себя такие вещи, как имя класса представления, описание содержимого и проверенное состояние. Конкретные свойства, необходимые для каждого типа событий, описаны в справочной документации AccessibilityEvent .

Реализация View предоставляет значения по умолчанию для этих обязательных свойств. Многие из этих значений, включая имя класса и метку времени события, предоставляются автоматически. Если вы создаете пользовательский компонент представления, вы должны предоставить информацию о содержимом и характеристиках представления. Эта информация может быть такой же простой, как метка кнопки, и может включать дополнительную информацию о состоянии, которую вы хотите добавить к событию.

Используйте методы onPopulateAccessibilityEvent() и onInitializeAccessibilityEvent() для заполнения или изменения информации в AccessibilityEvent . Используйте метод onPopulateAccessibilityEvent() специально для добавления или изменения текстового содержимого события, которое преобразуется в звуковые подсказки службами специальных возможностей, такими как TalkBack. Используйте метод onInitializeAccessibilityEvent() для заполнения дополнительной информации о событии, такой как состояние выбора представления.

Кроме того, реализуйте метод onInitializeAccessibilityNodeInfo() . Службы специальных возможностей используют объекты AccessibilityNodeInfo , заполненные этим методом, для исследования иерархии представлений, которая генерирует событие доступности после его получения, и предоставления соответствующей обратной связи пользователям.

В следующем примере кода показано, как переопределить эти три метода в вашем представлении:

Котлин

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

Ява

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

Вы можете реализовать эти методы непосредственно в своем пользовательском классе представления.

Обеспечьте индивидуальный контекст доступности.

Службы доступности могут проверять содержащуюся иерархию представлений компонента пользовательского интерфейса, который генерирует событие доступности. Это позволяет службам доступности предоставлять более подробную контекстную информацию для помощи пользователям.

Бывают случаи, когда службы доступности не могут получить адекватную информацию из иерархии представлений. Примером этого является настраиваемый элемент управления интерфейсом, который имеет две или более отдельно кликабельные области, например элемент управления «Календарь». В этом случае службы не могут получить адекватную информацию, поскольку кликабельные подразделы не являются частью иерархии представлений.

Рис. 1. Пользовательский вид календаря с выбираемыми элементами дня.

В примере на рисунке 1 весь календарь реализован как единое представление, поэтому службы специальных возможностей не получают достаточной информации о содержимом представления и выборе пользователя внутри представления, если разработчик не предоставит дополнительную информацию. Например, если пользователь щелкает день с меткой 17 , платформа специальных возможностей получает только информацию описания для всего элемента управления календарем. В этом случае служба специальных возможностей TalkBack объявляет «Календарь» или «Апрельский календарь», и пользователь не знает, какой день выбран.

Чтобы предоставить адекватную контекстную информацию для служб доступности в подобных ситуациях, платформа предоставляет способ указать иерархию виртуальных представлений. Иерархия виртуальных представлений — это способ для разработчиков приложений предоставить дополнительную иерархию представлений службам специальных возможностей, которая более точно соответствует информации на экране. Такой подход позволяет службам доступности предоставлять пользователям более полезную контекстную информацию.

Другая ситуация, когда может потребоваться иерархия виртуального представления, — это пользовательский интерфейс, содержащий набор элементов управления View , имеющих тесно связанные функции, где действие над одним элементом управления влияет на содержимое одного или нескольких элементов — например, средство выбора чисел с разделением и кнопки вниз. В этом случае службы доступности не могут получить адекватную информацию, поскольку действие над одним элементом управления изменяет содержимое другого, и взаимосвязь этих элементов управления может быть неочевидна для службы.

Чтобы справиться с этой ситуацией, сгруппируйте связанные элементы управления с содержащим представлением и создайте иерархию виртуального представления из этого контейнера, чтобы четко представлять информацию и поведение, предоставляемые элементами управления.

Чтобы обеспечить иерархию виртуального представления для представления, переопределите метод getAccessibilityNodeProvider() в пользовательском представлении или группе представлений и верните реализацию AccessibilityNodeProvider . Вы можете реализовать иерархию виртуального представления, используя библиотеку поддержки с методом ViewCompat.getAccessibilityNodeProvider() и предоставить реализацию с помощью AccessibilityNodeProviderCompat .

Чтобы упростить задачу предоставления информации службам доступности и управления фокусом доступности, вы можете вместо этого реализовать ExploreByTouchHelper . Он предоставляет AccessibilityNodeProviderCompat и может быть присоединен как AccessibilityDelegateCompat представления путем вызова setAccessibilityDelegate . Пример см. в разделе ExploreByTouchHelperActivity . ExploreByTouchHelper также используется виджетами платформы, такими как CalendarView , через дочернее представление SimpleMonthView .

Обработка пользовательских событий касания

Настраиваемые элементы управления представлением могут потребовать нестандартного поведения событий касания, как показано в следующих примерах.

Определите действия на основе кликов

Если ваш виджет использует интерфейс OnClickListener или OnLongClickListener , система обрабатывает действия ACTION_CLICK и ACTION_LONG_CLICK за вас. Если ваше приложение использует более настраиваемый виджет, основанный на интерфейсе OnTouchListener , определите пользовательские обработчики для действий доступности на основе кликов. Для этого вызовите метод replaceAccessibilityAction() для каждого действия, как показано в следующем фрагменте кода:

Котлин

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

Ява

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

Создание собственных событий кликов

Пользовательский элемент управления может использовать метод прослушивателя onTouchEvent(MotionEvent) для обнаружения событий ACTION_DOWN и ACTION_UP и запуска специального события щелчка. Чтобы обеспечить совместимость со службами специальных возможностей, код, обрабатывающий это пользовательское событие щелчка, должен выполнять следующее:

  1. Создайте соответствующее AccessibilityEvent для интерпретируемого действия щелчка.
  2. Включите службы специальных возможностей для выполнения настраиваемого действия по щелчку для пользователей, которые не могут использовать сенсорный экран.

Чтобы эффективно обрабатывать эти требования, ваш код должен переопределить метод performClick() , который должен вызвать суперреализацию этого метода, а затем выполнить все действия, необходимые для события щелчка. При обнаружении настраиваемого действия по клику этот код должен вызвать ваш метод performClick() . Следующий пример кода демонстрирует этот шаблон.

Котлин

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

Ява

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

Предыдущий шаблон помогает гарантировать, что настраиваемое событие щелчка совместимо со службами специальных возможностей, используя метод performClick() для создания события доступности и предоставления точки входа для служб специальных возможностей, которые будут действовать от имени пользователя, выполняющего настраиваемое событие щелчка.