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

Концепции и реализация Jetpack Compose

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

  • Обрабатывать нажатия кнопок управления направлением.
  • Реализуйте методы 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
        }
        ...
    }
}

Java

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

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

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

Предоставьте персонализированный контекст доступности.

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

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

Рисунок 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()
    }
}

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

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

Пользовательский элемент управления может использовать метод обработчика событий 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
    }
}

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

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