Rendere più accessibili le visualizzazioni personalizzate

Se la tua applicazione richiede un componente di visualizzazione personalizzato, devi rendere la visualizzazione più accessibile. I seguenti passaggi possono migliorare l'accessibilità della visualizzazione personalizzata, come descritto in questa pagina:

  • Consente di gestire i clic del controller direzionale.
  • Implementare i metodi dell'API Accessibility.
  • Invia AccessibilityEvent oggetti specifici della visualizzazione personalizzata.
  • Compila i campi AccessibilityEvent e AccessibilityNodeInfo per la visualizzazione.

Gestire i clic del controller direzionale

Sulla maggior parte dei dispositivi, se fai clic su una visualizzazione mediante un controller direzionale viene inviato un elemento KeyEvent con KEYCODE_DPAD_CENTER alla visualizzazione attualmente attiva. Tutte le visualizzazioni Android standard gestiscono KEYCODE_DPAD_CENTER in modo appropriato. Quando crei un controllo View personalizzato, assicurati che questo evento abbia lo stesso effetto del tocco della vista sul touchscreen.

Il controllo personalizzato deve considerare l'evento KEYCODE_ENTER come se fosse KEYCODE_DPAD_CENTER. In questo modo le interazioni con una tastiera completa diventano più semplici per gli utenti.

Implementa i metodi dell'API Accessibility

Gli eventi di accessibilità sono messaggi relativi alle interazioni degli utenti con i componenti dell'interfaccia visiva della tua app. Questi messaggi vengono gestiti dai servizi di accessibilità, che utilizzano le informazioni in questi eventi per produrre feedback e prompt supplementari. I metodi di accessibilità fanno parte delle classi View e View.AccessibilityDelegate. I metodi sono i seguenti:

dispatchPopulateAccessibilityEvent()
Il sistema chiama questo metodo quando la visualizzazione personalizzata genera un evento di accessibilità. L'implementazione predefinita di questo metodo chiama onPopulateAccessibilityEvent() per questa vista e poi il metodo dispatchPopulateAccessibilityEvent() per ciascuna vista secondaria.
onInitializeAccessibilityEvent()
Il sistema chiama questo metodo per ottenere ulteriori informazioni sullo stato della visualizzazione oltre ai contenuti testuali. Se la visualizzazione personalizzata offre un controllo interattivo oltre a un semplice TextView o Button, sostituisci questo metodo e imposta le informazioni aggiuntive sulla visualizzazione, ad esempio il tipo di campo password, il tipo di casella di casella di controllo o gli stati che forniscono l'interazione dell'utente o il feedback sull'evento, utilizzando questo metodo. Se esegui l'override di questo metodo, chiama la super-implementazione e modifica solo le proprietà che non sono impostate dalla superclasse.
onInitializeAccessibilityNodeInfo()
Questo metodo fornisce ai servizi di accessibilità informazioni sullo stato della vista. L'implementazione predefinita di View ha un insieme standard di proprietà di visualizzazione, ma se la visualizzazione personalizzata fornisce un controllo interattivo oltre a un semplice TextView o Button, sostituisci questo metodo e imposta le informazioni aggiuntive sulla vista nell'oggetto AccessibilityNodeInfo gestito da questo metodo.
onPopulateAccessibilityEvent()
Questo metodo consente di impostare il prompt di testo pronunciato di AccessibilityEvent per la visualizzazione. Viene chiamata anche se la vista è secondaria di una vista che genera un evento di accessibilità.
onRequestSendAccessibilityEvent()
Il sistema chiama questo metodo quando un elemento secondario della vista genera un AccessibilityEvent. Questo passaggio consente alla vista padre di modificare l'evento di accessibilità con informazioni aggiuntive. Implementa questo metodo solo se la visualizzazione personalizzata può avere viste secondarie e se la vista principale può fornire informazioni di contesto all'evento di accessibilità che è utile per i servizi di accessibilità.
sendAccessibilityEvent()
Il sistema chiama questo metodo quando un utente esegue un'azione su una vista. L'evento è classificato con un tipo di azione dell'utente, come TYPE_VIEW_CLICKED. In generale, devi inviare un AccessibilityEvent ogni volta che i contenuti della visualizzazione personalizzata cambiano.
sendAccessibilityEventUnchecked()
Questo metodo viene utilizzato quando il prefisso telefonico deve controllare direttamente il controllo dell'abilitazione dell'accessibilità sul dispositivo (AccessibilityManager.isEnabled()). Se implementi questo metodo, esegui la chiamata come se l'accessibilità fosse abilitata, indipendentemente dall'impostazione di sistema. In genere, non è necessario implementare questo metodo per una visualizzazione personalizzata.

Per supportare l'accessibilità, esegui l'override e l'implementazione dei metodi di accessibilità precedenti direttamente nella classe delle visualizzazioni personalizzate.

Implementa almeno i seguenti metodi di accessibilità per la classe visualizzazione personalizzata:

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

Per ulteriori informazioni sull'implementazione di questi metodi, consulta la sezione relativa alla compilazione degli eventi di accessibilità.

Invia eventi di accessibilità

A seconda delle specifiche della visualizzazione personalizzata, potrebbe essere necessario inviare AccessibilityEvent oggetti in momenti diversi o per eventi non gestiti dall'implementazione predefinita. La classe View fornisce un'implementazione predefinita per questi tipi di eventi:

In generale, devi inviare un AccessibilityEvent ogni volta che i contenuti della visualizzazione personalizzata cambiano. Ad esempio, se stai implementando una barra di scorrimento personalizzata che consente all'utente di selezionare un valore numerico premendo il tasto Freccia sinistra o destra, la visualizzazione personalizzata deve emettere un evento di TYPE_VIEW_TEXT_CHANGED ogni volta che il valore del dispositivo di scorrimento cambia. Il seguente esempio di codice mostra l'utilizzo del metodo sendAccessibilityEvent() per segnalare questo evento.

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

Completa gli eventi di accessibilità

Ogni AccessibilityEvent ha un insieme di proprietà obbligatorie che descrivono lo stato attuale della vista. Queste proprietà includono elementi come il nome della classe della vista, la descrizione dei contenuti e lo stato selezionato. Le proprietà specifiche richieste per ogni tipo di evento sono descritte nella documentazione di riferimento AccessibilityEvent.

L'implementazione View fornisce valori predefiniti per queste proprietà obbligatorie. Molti di questi valori, inclusi il nome della classe e il timestamp dell'evento, vengono forniti automaticamente. Se crei un componente di una visualizzazione personalizzata, devi fornire informazioni sui contenuti e sulle caratteristiche della vista. Queste informazioni possono essere semplici come l'etichetta di un pulsante e includere informazioni aggiuntive sullo stato da aggiungere all'evento.

Utilizza i metodi onPopulateAccessibilityEvent() e onInitializeAccessibilityEvent() per inserire o modificare le informazioni in un AccessibilityEvent. Utilizza il metodo onPopulateAccessibilityEvent() specificamente per aggiungere o modificare i contenuti testuali dell'evento, che viene trasformato in prompt udibili dai servizi di accessibilità come TalkBack. Utilizza il metodo onInitializeAccessibilityEvent() per inserire ulteriori informazioni sull'evento, ad esempio lo stato di selezione della vista.

Inoltre, implementa il metodo onInitializeAccessibilityNodeInfo(). I servizi di accessibilità utilizzano gli oggetti AccessibilityNodeInfo compilati con questo metodo per esaminare la gerarchia delle visualizzazioni che genera un evento di accessibilità dopo la ricezione e fornire un feedback appropriato agli utenti.

Il seguente esempio di codice mostra come eseguire l'override di questi tre metodi nella vista:

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

Puoi implementare questi metodi direttamente nella classe Visualizzazione personalizzata.

Fornisci un contesto personalizzato per l'accessibilità

I servizi di accessibilità possono esaminare la gerarchia delle viste contenitore di un componente dell'interfaccia utente che genera un evento di accessibilità. Ciò consente ai servizi di accessibilità di fornire informazioni contestuali più complete per aiutare gli utenti.

In alcuni casi i servizi di accessibilità non riescono a ottenere informazioni adeguate dalla gerarchia delle viste. Un esempio è costituito da un controllo dell'interfaccia personalizzata con due o più aree selezionabili separatamente, come il controllo del calendario. In questo caso, i servizi non possono ricevere informazioni adeguate perché le sottosezioni cliccabili non fanno parte della gerarchia delle visualizzazioni.

Figura 1. Una visualizzazione personalizzata del calendario con elementi giornalieri selezionabili.

Nell'esempio nella figura 1, l'intero calendario è implementato come un'unica visualizzazione, pertanto i servizi di accessibilità non ricevono informazioni sufficienti sui contenuti della visualizzazione e sulla selezione da parte dell'utente all'interno della visualizzazione a meno che lo sviluppatore non fornisca informazioni aggiuntive. Ad esempio, se un utente fa clic sul giorno 17, il framework di accessibilità riceve solo le informazioni della descrizione per l'intero controllo del calendario. In questo caso, il servizio di accessibilità TalkBack annuncia "Calendar" o "Calendario di aprile" e l'utente non sa quale giorno viene selezionato.

Per fornire informazioni di contesto adeguate per i servizi di accessibilità in situazioni come questa, il framework fornisce un modo per specificare una gerarchia di visualizzazioni virtuali. Una gerarchia di visualizzazioni virtuali è un modo in cui gli sviluppatori di app possono fornire una gerarchia di viste complementare ai servizi di accessibilità che corrisponda più strettamente alle informazioni sullo schermo. Questo approccio consente ai servizi di accessibilità di fornire informazioni contestuali più utili agli utenti.

Un'altra situazione in cui potrebbe essere necessaria una gerarchia di visualizzazioni virtuali è un'interfaccia utente contenente un insieme di controlli View con funzioni strettamente correlate, dove un'azione su un controllo influisce sui contenuti di uno o più elementi, ad esempio un selettore di numeri con pulsanti Su e Giù separati. In questo caso, i servizi di accessibilità non possono ricevere informazioni adeguate perché un'azione su un controllo cambia i contenuti in un altro e la relazione di questi controlli potrebbe non essere chiara al servizio.

Per gestire questa situazione, raggruppa i controlli correlati con una vista contenitore e fornisci una gerarchia di visualizzazioni virtuali da questo container per rappresentare chiaramente le informazioni e il comportamento forniti dai controlli.

Per fornire una gerarchia di visualizzazioni virtuali per una vista, esegui l'override del metodo getAccessibilityNodeProvider() nel gruppo di visualizzazioni o visualizzazioni personalizzate e restituisci un'implementazione di AccessibilityNodeProvider. Puoi implementare una gerarchia di visualizzazioni virtuali utilizzando la Support Library con il metodo ViewCompat.getAccessibilityNodeProvider() e fornire un'implementazione con AccessibilityNodeProviderCompat.

Per semplificare il compito di fornire informazioni ai servizi di accessibilità e gestire l'obiettivo dell'accessibilità, puoi implementare ExploreByTouchHelper. Fornisce un AccessibilityNodeProviderCompat e può essere allegato come AccessibilityDelegateCompat di una vista chiamando setAccessibilityDelegate. Per un esempio, vedi ExploreByTouchHelperActivity. ExploreByTouchHelper è utilizzato anche da widget framework come CalendarView, attraverso la sua vista secondaria SimpleMonthView.

Gestire gli eventi tocco personalizzati

I controlli delle visualizzazioni personalizzate potrebbero richiedere un comportamento degli eventi touch non standard, come dimostrato nei seguenti esempi.

Definisci azioni basate sui clic

Se il widget utilizza l'interfaccia OnClickListener o OnLongClickListener, il sistema gestisce per te le azioni ACTION_CLICK e ACTION_LONG_CLICK. Se la tua app utilizza un widget più personalizzato che si basa sull'interfaccia OnTouchListener, definisci gestori personalizzati per le azioni di accessibilità basate sui clic. A questo scopo, chiama il metodo replaceAccessibilityAction() per ogni azione, come mostrato nel seguente snippet di codice:

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

Creare eventi di clic personalizzati

Un controllo personalizzato può utilizzare il metodo di ascolto onTouchEvent(MotionEvent) per rilevare gli eventi ACTION_DOWN e ACTION_UP e attivare un evento di clic speciale. Per mantenere la compatibilità con i servizi di accessibilità, il codice che gestisce questo evento di clic personalizzato deve:

  1. Genera un valore AccessibilityEvent appropriato per l'azione di clic interpretata.
  2. Attiva i servizi di accessibilità per eseguire l'azione di clic personalizzata per gli utenti che non sono in grado di utilizzare un touchscreen.

Per gestire questi requisiti in modo efficiente, il codice deve sostituire il metodo performClick(), che deve chiamare la super-implementazione di questo metodo ed eseguire le azioni richieste dall'evento clic. Quando viene rilevata l'azione di clic personalizzata, questo codice deve chiamare il tuo metodo performClick(). L'esempio di codice riportato di seguito illustra questo pattern.

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

Il pattern precedente aiuta a garantire che l'evento di clic personalizzato sia compatibile con i servizi di accessibilità utilizzando il metodo performClick() per generare un evento di accessibilità e fornire un punto di ingresso affinché i servizi di accessibilità agiscano per conto di un utente che esegue l'evento di clic personalizzato.