Sviluppare app

I seguenti materiali sono destinati agli sviluppatori di app.

Per fare in modo che la tua app supporti il formato rotativo, DEVI:

  1. Inserisci un FocusParkingView nel rispettivo layout dell'attività.
  2. Assicurati che le visualizzazioni siano (o meno) accessibili.
  3. Utilizza FocusArea per avvolgere tutte le visualizzazioni che possono essere messe a fuoco, tranne FocusParkingView.

Ogni una di queste attività è descritta in dettaglio di seguito, dopo aver configurato l'ambiente per sviluppare app con rotazione.

Configurare un selettore rotativo

Prima di poter iniziare a sviluppare app con controllo rotativo, devi avere un controllo rotativo o un sostitutivo. Hai a disposizione le opzioni descritte di seguito.

Emulatore

source build/envsetup.sh && lunch car_x86_64-userdebug
m -j
emulator -wipe-data -no-snapshot -writable-system

Puoi anche utilizzare aosp_car_x86_64-userdebug.

Per accedere al controller rotativo simulato:

  1. Tocca i tre puntini nella parte inferiore della barra degli strumenti:

    Accedere al controller rotativo emulato
    Figura 1. Accedere al controller rotativo simulato
  2. Seleziona Volante dell'auto nella finestra dei controlli avanzati:

    Seleziona Car rotary
    Figura 2. Seleziona Car rotary

Tastiera USB

  • Collega una tastiera USB al dispositivo su cui è installato il sistema operativo Android Automotive (AAOS). In alcuni casi, questo impedisce la visualizzazione della tastiera sullo schermo.
  • Utilizza una build userdebug o eng.
  • Attiva il filtro degli eventi chiave:
    adb shell settings put secure android.car.ROTARY_KEY_EVENT_FILTER 1
    
  • Consulta la tabella seguente per trovare la chiave corrispondente per ogni azione:
    Chiave Azione rotatoria
    D Ruota in senso antiorario
    E Ruota in senso orario
    A Spingi a sinistra
    D Spingi a destra
    W Spingi su
    S Spingi in basso
    F o virgola Pulsante centrale
    R o Esc Pulsante Indietro

Comandi ADB

Puoi utilizzare i comandi car_service per iniettare eventi di input rotatorio. Questi comandi possono essere eseguiti su dispositivi con sistema operativo Android Automotive (AAOS) o su un emulatore.

Comandi car_service Input rotatorio
adb shell cmd car_service inject-rotary Ruota in senso antiorario
adb shell cmd car_service inject-rotary -c true Ruota in senso orario
adb shell cmd car_service inject-rotary -dt 100 50 Ruota in senso antiorario più volte (100 ms fa e 50 ms fa)
adb shell cmd car_service inject-key 282 Spingi a sinistra
adb shell cmd car_service inject-key 283 Spingi a destra
adb shell cmd car_service inject-key 280 Spingi su
adb shell cmd car_service inject-key 281 Spingi in basso
adb shell cmd car_service inject-key 23 Clic sul pulsante centrale
adb shell input keyevent inject-key 4 Clic sul pulsante Indietro

Selettore rotativo OEM

Quando l'hardware del controller rotativo è attivo e funzionante, questa è l'opzione più realistica. È particolarmente utile per testare la rotazione rapida.

FocusParkingView

FocusParkingView è una visualizzazione trasparente nella libreria UI dell'auto (car-ui-library). RotaryService lo utilizza per supportare la navigazione con il controller rotativo. FocusParkingView deve essere la prima vista attivabile nel layout. Deve essere posizionato all'esterno di tutti i FocusArea. Ogni finestra deve avere un FocusParkingView. Se utilizzi già il layout di base della libreria car-ui, che contiene un FocusParkingView, non è necessario aggiungerne un altro. FocusParkingView. Di seguito è riportato un esempio di FocusParkingView in RotaryPlayground.

<FrameLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
   android:layout_height="match_parent">
   <com.android.car.ui.FocusParkingView
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"/>
   <FrameLayout
       android:layout_width="match_parent"
       android:layout_height="match_parent"/>
</FrameLayout>

Ecco i motivi per cui hai bisogno di un FocusParkingView:

  1. Android non annulla automaticamente lo stato attivo quando è impostato in un'altra finestra. Se provi a annullare lo stato attivo nella finestra precedente, Android riattiva una visualizzazione in quella finestra, con il risultato che vengono attivate contemporaneamente due finestre. L'aggiunta di un FocusParkingView a ogni finestra può risolvere il problema. Questa visualizzazione è trasparente e l'evidenziazione dell'elemento attivo predefinita è disattivata, pertanto è invisibile all'utente indipendentemente dal fatto che sia attivo o meno. Può acquisire lo stato attivo in modo che RotaryService possa parcheggiare lo stato attivo su di esso per rimuovere l'evidenziazione dello stato attivo.
  2. Se nella finestra corrente è presente un solo FocusArea, la rotazione del controller in FocusArea fa sì che RotaryService sposti lo stato attivo dalla visualizzazione a destra a quella a sinistra (e viceversa). L'aggiunta di questa visualizzazione a ogni finestra può risolvere il problema. Quando RotaryService determina che il target di immissione è un FocusParkingView, può determinare che sta per verificarsi un a capo, a quel punto evita l'a capo non spostando lo stato attivo.
  3. Quando il controllo rotativo avvia un'app, Android mette in primo piano la prima visualizzazione attivabile, che è sempre FocusParkingView. FocusParkingView determina la visualizzazione ottimale da mettere a fuoco e poi applica lo stato attivo.

Viste attivabili

RotaryService si basa sul esistente concetto di messa a fuoco della visualizzazione del framework Android, che risale ai tempi in cui gli smartphone avevano tastiere fisiche e pad direzionali. L'attributo android:nextFocusForward esistente viene riutilizzato per le fotocamere con rotazione (vedi Personalizzazione di FocusArea), ma non per android:nextFocusLeft, android:nextFocusRight,android:nextFocusUp e android:nextFocusDown.

RotaryService si concentra solo sulle visualizzazioni che possono essere messe a fuoco. Alcune visualizzazioni, come le Button, solitamente possono essere messe a fuoco. Altri, come TextView e ViewGroup, solitamente non lo sono. Le visualizzazioni cliccabili sono automaticamente attivabili e sono automaticamente cliccabili se hanno un gestore degli eventi di clic. Se questa logica automatica genera la pulsabilità desiderata, non è necessario impostare esplicitamente la pulsabilità della visualizzazione. Se la logica automatica non consente di ottenere la possibilità di messa a fuoco desiderata, imposta l'attributo android:focusable su true o false oppure imposta la possibilità di messa a fuoco della visualizzazione in modo programmatico con View.setFocusable(boolean). Affinché RotaryService possa concentrarsi su di essa, una visualizzazione DEVE soddisfare i seguenti requisiti:

  • Selezionabile
  • Attivato
  • Visibile
  • Avere valori diversi da zero per larghezza e altezza

Se una visualizzazione non soddisfa tutti questi requisiti, ad esempio un pulsante attivabile ma disattivato, l'utente non può utilizzare il controllo rotatorio per attivarla. Se vuoi concentrarti sulle visualizzazioni disattivate, prendi in considerazione l'utilizzo di uno stato personalizzato anziché android:state_enabled per controllare la modalità di visualizzata della visualizzazione senza indicare ad Android che deve considerarla disattivata. L'app può informare l'utente del motivo per cui la visualizzazione è disattivata quando viene toccata. La sezione successiva spiega come procedere.

Stato personalizzato

Per aggiungere uno stato personalizzato:

  1. Per aggiungere un attributo personalizzato alla visualizzazione. Ad esempio, per aggiungere uno stato personalizzato state_rotary_enabled alla classe di visualizzazione CustomView, utilizza:
    <declare-styleable name="CustomView">
        <attr name="state_rotary_enabled" format="boolean" />
    </declare-styleable>
    
  2. Per monitorare questo stato, aggiungi una variabile di istanza alla vista insieme ai metodi di accesso:
    private boolean mRotaryEnabled;
    public boolean getRotaryEnabled() { return mRotaryEnabled; }
    public void setRotaryEnabled(boolean rotaryEnabled) {
        mRotaryEnabled = rotaryEnabled;
    }
    
  3. Per leggere il valore dell'attributo quando viene creata la vista:
    TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomView);
    mRotaryEnabled = a.getBoolean(R.styleable.CustomView_state_rotary_enabled);
    
  4. Nella classe di visualizzazione, sostituisci il metodo onCreateDrawableState() e poi Aggiungi lo stato personalizzato, se opportuno. Ad esempio:
    @Override
    protected int[] onCreateDrawableState(int extraSpace) {
        if (mRotaryEnabled) extraSpace++;
        int[] drawableState = super.onCreateDrawableState(extraSpace);
        if (mRotaryEnabled) {
            mergeDrawableStates(drawableState, { R.attr.state_rotary_enabled });
        }
        return drawableState;
    }
    
  5. Consenti al gestore dei clic della visualizzazione di funzionare in modo diverso a seconda del suo stato. Ad esempio, il gestore dei clic potrebbe non fare nulla o potrebbe visualizzare un messaggio popup quando mRotaryEnabled è false.
  6. Per fare in modo che il pulsante venga visualizzato come disattivato, utilizza app:state_rotary_enabled anziché android:state_enabled nell'elemento Drawable di sfondo della vista. Se non li hai già, dovrai aggiungere:
    xmlns:app="http://schemas.android.com/apk/res-auto"
    
  7. Se la visualizzazione è disattivata in uno o più layout, sostituisci android:enabled="false" con app:state_rotary_enabled="false" e aggiungi lo spazio dei nomi app, come sopra.
  8. Se la visualizzazione è disattivata tramite programmazione, sostituisci le chiamate a setEnabled() con le chiamate a setRotaryEnabled().

FocusArea

Utilizza FocusAreas per suddividere le visualizzazioni attivabili in blocchi per semplificare la navigazione e per essere coerente con altre app. Ad esempio, se la tua app ha una barra degli strumenti, questa deve essere in un FocusArea separato dal resto dell'app. Anche le barre delle schede e gli altri elementi di navigazione devono essere separati dal resto dell'app. In genere, gli elenchi di grandi dimensioni devono avere un proprio FocusArea. In caso contrario, gli utenti dovranno scorrere l'intero elenco per accedere ad alcune visualizzazioni.

FocusArea è una sottoclasse di LinearLayout nella libreria car-ui. Quando questa funzionalità è attiva, FocusArea disegna un'evidenziazione quando uno dei suoi decedenti è attivo. Per scoprire di più, consulta la sezione Personalizzazione dell'evidenziazione in primo piano.

Quando crei un blocco di navigazione nel file di layout, se intendi utilizzare un LinearLayout come contenitore per il blocco, utilizza FocusArea. In caso contrario, racchiudi il blocco in un FocusArea.

NON nidificare un FocusArea in un altro FocusArea. Ciò comporta un comportamento di navigazione non definito. Assicurati che tutte le visualizzazioni accessibili siano nidificate all'interno di un FocusArea.

Di seguito è riportato un esempio di FocusArea in RotaryPlayground:

<com.android.car.ui.FocusArea
       android:layout_margin="16dp"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:orientation="vertical">
       <EditText
           android:layout_width="match_parent"
           android:layout_height="wrap_content"
           android:singleLine="true">
       </EditText>
   </com.android.car.ui.FocusArea>

FocusArea funziona nel seguente modo:

  1. Quando gestisce le azioni di rotazione e spostamento, RotaryService cerca istanze di FocusArea nella gerarchia della visualizzazione.
  2. Quando riceve un evento di rotazione, RotaryService sposta lo stato attivo su un'altra visualizzazione che può acquisire lo stato attivo nello stesso FocusArea.
  3. Quando ricevi un evento di nudge, RotaryService sposta lo stato attivo su un'altra visualizzazione che può acquisire lo stato attivo in un altro FocusArea (in genere adiacente).

Se non includi FocusAreas nel layout, la visualizzazione principale viene trattata come un'area di immissione implicita. L'utente non può usare i movimenti laterali per navigare nell'app. Al suo posto, scorrerà tutte le visualizzazioni che possono essere messe a fuoco, il che potrebbe essere sufficiente per le finestre di dialogo.

Personalizzazione di FocusArea

Per personalizzare la navigazione circolare, puoi utilizzare due attributi View standard:

  • android:nextFocusForward consente agli sviluppatori di app di specificare l'ordine di rotazione in un'area di messa a fuoco. Si tratta dello stesso attributo utilizzato per controllare l'ordine di tabulazione per la navigazione da tastiera. NON utilizzare questo attributo per creare un loop. Utilizza invece app:wrapAround (vedi di seguito) per creare un ciclo.
  • android:focusedByDefault consente agli sviluppatori di app di specificare la vista di messa a fuoco predefinita nella finestra. NON utilizzare questo attributo e app:defaultFocus (vedi di seguito) nello stesso FocusArea.

FocusArea definisce anche alcuni attributi per personalizzare la navigazione con il selettore rotativo. Le aree di messa a fuoco implicite non possono essere personalizzate con questi attributi.

  1. (Android 11 QPR3, Android 11 Car, Android 12)
    app:defaultFocus può essere utilizzato per specificare l'ID di una visualizzazione discendente attivabile, su cui deve essere attivato il fuoco quando l'utente esegue un nudge su questo FocusArea.
  2. (Android 11 QPR3, Android 11 Car, Android 12)
    app:defaultFocusOverridesHistory può essere impostato su true per dare il focus alla visualizzazione specificata sopra anche se con la cronologia per indicare che un'altra visualizzazione in questo FocusArea era stata messa a fuoco.
  3. (Android 12)
    Utilizza app:nudgeLeftShortcut, app:nudgeRightShortcut, app:nudgeUpShortcut e app:nudgeDownShortcut per specificare l'ID di una visualizzazione discendente attivabile, che deve essere attivata quando l'utente spinge in una determinata direzione. Per scoprire di più, consulta i contenuti relativi alle scorciatoie per i suggerimenti di seguito.

    (Android 11 QPR3, Android 11 Car, deprecato in Android 12) app:nudgeShortcut e app:nudgeShortcutDirection supportavano una sola scorciatoia per le sollecitazioni.

  4. (Android 11 QPR3, Android 11 Car, Android 12)
    Per attivare la rotazione con a capo in questo FocusArea, app:wrapAround può essere impostato su true. Questo tipo di visualizzazione viene utilizzato più comunemente quando le viste sono disposte in un cerchio o un ovale.
  5. (Android 11 QPR3, Android 11 Car, Android 12)
    Per regolare la spaziatura interna dell'evidenziazione in questo FocusArea, utilizza app:highlightPaddingStart, app:highlightPaddingEnd, app:highlightPaddingTop, app:highlightPaddingBottom, app:highlightPaddingHorizontal, e app:highlightPaddingVertical.
  6. (Android 11 QPR3, Android 11 Car, Android 12)
    Per regolare i limiti percepiti di questo FocusArea in modo da trovare un target di nudge, utilizza app:startBoundOffset, app:endBoundOffset, app:topBoundOffset, app:bottomBoundOffset, app:horizontalBoundOffset e app:verticalBoundOffset.
  7. (Android 11 QPR3, Android 11 Car, Android 12)
    Per specificare esplicitamente l'ID di un FocusArea (o aree) adiacente nelle indicazioni fornite, utilizza app:nudgeLeft, app:nudgeRight, app:nudgeUp e app:nudgeDown. Utilizzala quando la ricerca geometrica utilizzata per impostazione predefinita non trova il target desiderato.

I nudge di solito consentono di spostarsi tra le aree di attenzione. Tuttavia, con le scorciatoie per i solleciti, talvolta il sollecito consente prima di navigare all'interno di un FocusArea, pertanto l'utente potrebbe dover fare un sollecito due volte per passare al FocusArea successivo. Le scorciatoie per i movimenti sono utili quando un FocusArea contiene un lungo elenco seguito da un pulsante di azione mobile, come nell'esempio seguente:

Scorciatoia per spostare leggermente
Figura 3. Scorciatoia per spostare leggermente

Senza la scorciatoia per la notifica, l'utente dovrebbe scorrere l'intero elenco per raggiungere il pulsante flottante.

Personalizzazione dell'evidenziazione dell'elemento attivo

Come indicato sopra, RotaryService si basa sul concetto esistente di messa a fuoco della vista del framework Android. Quando l'utente ruota e sposta leggermente, RotaryService sposta lo stato attivo, attivando una visualizzazione e disattivandone un'altra. In Android, quando una visualizzazione è attiva, se la visualizzazione:

  • Ha specificato il proprio punto di messa a fuoco, Android disegna il punto di messa a fuoco della visualizzazione.
  • Non specifica un'evidenziazione della messa a fuoco e l'evidenziazione della messa a fuoco predefinita non è disattivata. Android disegna l'evidenziazione della messa a fuoco predefinita per la visualizzazione.

Le app progettate per il tocco in genere non specificano gli elementi in evidenza appropriati.

L'evidenziazione della messa a fuoco predefinita è fornita dal framework Android e può essere sostituita dall'OEM. Gli sviluppatori di app lo ricevono quando il tema che utilizzano è derivato daTheme.DeviceDefault.

Per un'esperienza utente coerente, utilizza l'evidenziazione dell'elemento attivo predefinito, se possibile. Se hai bisogno di un'evidenziazione della messa a fuoco di forma personalizzata (ad esempio rotonda o a forma di pillola) o se utilizzi un tema non derivato da Theme.DeviceDefault, utilizza le risorse della libreria car-ui per specificare la tua evidenziazione della messa a fuoco per ogni visualizzazione.

Per specificare un'evidenziazione dell'attenzione personalizzata per una vista, modifica l'elemento drawable di sfondo o primo piano della vista con un drawable diverso quando la vista è attiva. In genere, cambieresti lo sfondo. Il seguente drawable, se utilizzato come sfondo per una visualizzazione quadrata, produce un'evidenziazione della messa a fuoco rotonda:

<selector xmlns:android="http://schemas.android.com/apk/res/android">
   <item android:state_focused="true" android:state_pressed="true">
      <shape android:shape="oval">
         <solid android:color="@color/car_ui_rotary_focus_pressed_fill_color"/>
         <stroke
            android:width="@dimen/car_ui_rotary_focus_pressed_stroke_width"
            android:color="@color/car_ui_rotary_focus_pressed_stroke_color"/>
      </shape>
   </item>
   <item android:state_focused="true">
      <shape android:shape="oval">
         <solid android:color="@color/car_ui_rotary_focus_fill_color"/>
         <stroke
            android:width="@dimen/car_ui_rotary_focus_stroke_width"
            android:color="@color/car_ui_rotary_focus_stroke_color"/>
      </shape>
   </item>
   <item>
      <ripple...>
         ...
      </ripple>
   </item>
</selector>

(Android 11 QPR3, Android 11 Car, Android 12) I riferimenti alle risorse in grassetto nell'esempio sopra identificano le risorse definite dalla raccolta car-ui-library. L'OEM li sostituisce per essere coerente con l'evidenziazione dell'elemento attivo predefinita specificata. In questo modo, il colore dell'evidenziazione della messa a fuoco, la larghezza del tratto e così via non cambiano quando l'utente passa da una visualizzazione con un'evidenziazione della messa a fuoco personalizzata a una con l'evidenziazione della messa a fuoco predefinita. L'ultimo elemento è un'eco utilizzata per il tocco. I valori predefiniti utilizzati per le risorse in grassetto sono i seguenti:

Valori predefiniti per le risorse in grassetto
Figura 4. Valori predefiniti per le risorse in grassetto

Inoltre, viene richiamato un'evidenziazione dell'attenzione personalizzata quando a un pulsante viene assegnato un colore di sfondo uniforme per attirare l'attenzione dell'utente, come nell'esempio seguente. In questo modo, l'evidenziazione dell'area di messa a fuoco può essere difficile da vedere. In questa situazione, specifica un'evidenziazione personalizzata dell'attenzione utilizzando i colori secondari:

Colore di sfondo a tinta unita
  • (Android 11 QPR3, Android 11 Car, Android 12)
    car_ui_rotary_focus_fill_secondary_color
    car_ui_rotary_focus_stroke_secondary_color
  • (Android 12)
    car_ui_rotary_focus_pressed_fill_secondary_color
    car_ui_rotary_focus_pressed_stroke_secondary_color

Ad esempio:

Messa a fuoco, non premuto Concentrato, premuto
In primo piano, non premuto Concentrato, premuto

Scorrimento rotatorio

Se la tua app utilizza i RecyclerView, DOVREBBE utilizzare i CarUiRecyclerView. In questo modo, la tua UI sarà coerente con altre perché la personalizzazione di un OEM si applica a tutti i CarUiRecyclerView.

Se tutti gli elementi dell'elenco sono accessibili, non devi fare altro. La navigazione con il controllo rotativo sposta lo stato attivo tra gli elementi dell'elenco e l'elenco scorre per rendere visibile l'elemento appena selezionato.

(Android 11 QPR3, Android 11 Car, Android 12)
Se sono presenti elementi che possono essere attivati e disattivati o se tutti gli elementi non possono essere attivati, puoi attivare lo scorrimento rotatorio, che consente all'utente di utilizzare il controllo rotatorio per scorrere gradualmente l'elenco senza saltare gli elementi non attivabili. Per attivare lo scorrimento rotatorio, imposta l'attributo app:rotaryScrollEnabled su true.

(Android 11 QPR3, Android 11 Car, Android 12)
Puoi attivare lo scorrimento rotatorio in qualsiasi visualizzazione scorrevole, inclusa avCarUiRecyclerView, con il metodo setRotaryScrollEnabled() in CarUiUtils. In questo caso, devi:

  • Rendi la visualizzazione scorrevole attivabile in modo che possa essere attivata quando nessuna delle sue visualizzazioni discendenti attivabili è visibile.
  • Disattiva l'evidenziazione dell'elemento attivo predefinita nella visualizzazione scorrevole chiamando setDefaultFocusHighlightEnabled(false) in modo che la visualizzazione scorrevole non sembri essere attiva.
  • Assicurati che la visualizzazione scorrevole sia attiva prima dei relativi discendenti chiamando setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS).
  • Ascolta MotionEvents con SOURCE_ROTARY_ENCODER e AXIS_VSCROLL o AXIS_HSCROLL per indicare la distanza da scorrere e la direzione (tramite il segnale).

Quando lo scorrimento rotatorio è attivato su un CarUiRecyclerView e l'utente ruota in un'area in cui non sono presenti visualizzazioni attivabili, la barra di scorrimento diventa da grigia a blu, come se indicasse che la barra di scorrimento è attivata. Se vuoi, puoi implementare un effetto simile.

Gli eventi Motion sono gli stessi generati dalla rotellina di scorrimento di un mouse, ad eccezione dell'origine.

Modalità di manipolazione diretta

In genere, le pressioni e la rotazione consentono di navigare nell'interfaccia utente, mentre le pressioni del pulsante Centro consentono di eseguire un'azione, anche se non sempre è così. Ad esempio, se un utente vuole regolare il volume della sveglia, può utilizzare il controllo rotatorio per accedere al cursore del volume, premere il pulsante centrale, ruotare il controllo per regolare il volume della sveglia e poi premere il pulsante Indietro per tornare alla navigazione. Questa modalità è nota come manipolazione diretta (DM). In questa modalità, il controllo rotativo viene utilizzato per interagire direttamente con la visualizzazione anziché per navigare.

Puoi implementare la gestione dei messaggi in due modi. Se devi gestire solo la rotazione e la visualizzazione che vuoi manipolare risponde in modo appropriato a ACTION_SCROLL_FORWARD e ACTION_SCROLL_BACKWARD AccessibilityEvent, utilizza il meccanismo semplice. In caso contrario, utilizza il meccanismo advanced.

Il meccanismo semplice è l'unica opzione nelle finestre di sistema; le app possono utilizzare entrambi i meccanismi.

Meccanismo semplice

(Android 11 QPR3, Android 11 Car, Android 12)
La tua app deve chiamare DirectManipulationHelper.setSupportsRotateDirectly(View view, boolean enable). RotaryService riconosce quando l'utente è in modalità DM e accede a questa modalità quando l'utente preme il pulsante Centrale mentre una visualizzazione è attiva. In modalità DM, le rotazioni vengono eseguite con ACTION_SCROLL_FORWARD o ACTION_SCROLL_BACKWARD e la modalità DM viene disattivata quando l'utente preme il pulsante Indietro. Il semplice meccanismo attiva/disattiva lo stato selezionato della visualizzazione quando si entra e si esce dalla modalità DM.

Per fornire un'indicazione visiva che l'utente è in modalità DM, fai in modo che la visualizzazione appaia diversa quando è selezionata. Ad esempio, cambia lo sfondo quando android:state_selected è true.

Meccanismo avanzato

L'app determina quando RotaryService entra ed esce dalla modalità DM. Per un'esperienza utente coerente, se premi il tasto centrale con una visualizzazione della scheda DM attiva, dovresti attivare la modalità DM e il tasto Indietro dovrebbe farti uscire da questa modalità. Se il pulsante centrale e/o il nudge non vengono utilizzati, possono essere modi alternativi per uscire dalla modalità DM. Per app come Maps, è possibile utilizzare un pulsante per rappresentare il messaggio diretto per accedere alla modalità DM.

Per supportare la modalità avanzata del modulo, una visualizzazione:

  1. (Android 11 QPR3, Android 11 Car, Android 12) DEVE attendere un evento KEYCODE_DPAD_CENTER per entrare in modalità DM e un evento KEYCODE_BACK per uscirne, chiamando DirectManipulationHelper.enableDirectManipulationMode() in ogni caso. Per ascoltare questi eventi, esegui una delle seguenti operazioni:
    • Registra un OnKeyListener.
    • oppure
    • Espandi la visualizzazione e poi sostituisci il relativo metodo dispatchKeyEvent().
  2. DOVREBBE ascoltare gli eventi di sollecito (KEYCODE_DPAD_UP, KEYCODE_DPAD_DOWN, KEYCODE_DPAD_LEFT o KEYCODE_DPAD_RIGHT) se la visualizzazione deve gestire i solleciti.
  3. DOVREBBE ascoltare i messaggi MotionEvent e ottenere il conteggio delle rotazioni in AXIS_SCROLL se la visualizzazione vuole gestire la rotazione. Esistono diversi modi per farlo:
    1. Registra un OnGenericMotionListener.
    2. Espandi la visualizzazione e sostituisci il relativo metodo dispatchTouchEvent().
  4. Per evitare di rimanere bloccati in modalità DM, devi uscire dalla modalità DM quando il frammento o l'attività a cui appartiene la visualizzazione non è interattivo.
  5. DOVREBBE fornire un indicatore visivo per indicare che la visualizzazione è in modalità DM.

Di seguito è riportato un esempio di visualizzazione personalizzata che utilizza la modalità DM per eseguire la panoramica e lo zoom di una mappa:

/** Whether this view is in DM mode. */
private boolean mInDirectManipulationMode;

/** Initializes the view. Called by the constructors. */ private void init() { setOnKeyListener((view, keyCode, keyEvent) -> { boolean isActionUp = keyEvent.getAction() == KeyEvent.ACTION_UP; switch (keyCode) { // Always consume KEYCODE_DPAD_CENTER and KEYCODE_BACK events. case KeyEvent.KEYCODE_DPAD_CENTER: if (!mInDirectManipulationMode && isActionUp) { mInDirectManipulationMode = true; DirectManipulationHelper.enableDirectManipulationMode(this, true); setSelected(true); // visually indicate DM mode } return true; case KeyEvent.KEYCODE_BACK: if (mInDirectManipulationMode && isActionUp) { mInDirectManipulationMode = false; DirectManipulationHelper.enableDirectManipulationMode(this, false); setSelected(false); } return true; // Consume controller nudge events only when in DM mode. // When in DM mode, nudges pan the map. case KeyEvent.KEYCODE_DPAD_UP: if (!mInDirectManipulationMode) return false; if (isActionUp) pan(0f, -10f); return true; case KeyEvent.KEYCODE_DPAD_DOWN: if (!mInDirectManipulationMode) return false; if (isActionUp) pan(0f, 10f); return true; case KeyEvent.KEYCODE_DPAD_LEFT: if (!mInDirectManipulationMode) return false; if (isActionUp) pan(-10f, 0f); return true; case KeyEvent.KEYCODE_DPAD_RIGHT: if (!mInDirectManipulationMode) return false; if (isActionUp) pan(10f, 0f); return true; // Don't consume other key events. default: return false; } });
// When in DM mode, rotation zooms the map. setOnGenericMotionListener(((view, motionEvent) -> { if (!mInDirectManipulationMode) return false; float scroll = motionEvent.getAxisValue(MotionEvent.AXIS_SCROLL); zoom(10 * scroll); return true; })); }
@Override public void onPause() { if (mInDirectManipulationMode) { // To ensure that the user doesn't get stuck in DM mode, disable DM mode // when the fragment is not interactive (e.g., a dialog shows up). mInDirectManipulationMode = false; DirectManipulationHelper.enableDirectManipulationMode(this, false); } super.onPause(); }

Puoi trovare altri esempi nel progetto RotaryPlayground.

ActivityView

Quando utilizzi un'attività ActivityView:

  • ActivityView non deve essere selezionabile.
  • (Android 11 QPR3, Android 11 Car, dismesso in Android 11)
    I contenuti di ActivityView DEVONO contenere un FocusParkingView come prima visualizzazione attivabile e il relativo attributo app:shouldRestoreFocus DEVE essere false.
  • I contenuti del ActivityView non devono avere android:focusByDefault visualizzazioni.

Per l'utente, le visualizzazioni attività non dovrebbero avere alcun effetto sulla navigazione, tranne per il fatto che le aree di messa a fuoco non possono estendersi alle visualizzazioni attività. In altre parole, non puoi avere un'unica area di messa a fuoco con contenuti all'interno e all'esterno di un ActivityView. Se non aggiungi alcune aree di messa a fuoco al tuo ActivityView, la radice della gerarchia delle visualizzazioni nel ActivityView è considerata un'area di messa a fuoco implicita.

Pulsanti che si attivano quando vengono premuti

La maggior parte dei pulsanti provoca un'azione quando viene fatto clic. Alcuni pulsanti invece funzionano se vengono premuti a lungo. Ad esempio, i pulsanti Avanti veloce e Riavvolgi in genere funzionano quando vengono tenuti premuti. Per fare in modo che questi pulsanti supportino il controllo rotativo, ascolta KEYCODE_DPAD_CENTER KeyEvents come segue:

mButton.setOnKeyListener((v, keyCode, event) ->
{
    if (keyCode != KEYCODE_DPAD_CENTER) {
        return false;
    }
    if (event.getAction() == ACTION_DOWN) {
        mButton.setPressed(true);
        mHandler.post(mRunnable);
    } else {
        mButton.setPressed(false);
        mHandler.removeCallbacks(mRunnable);
    }
    return true;
});

In cui mRunnable esegue un'azione (ad esempio riavvolgere) e si programma per essere eseguita dopo un ritardo.

Modalità tocco

Gli utenti possono utilizzare un controllo rotativo per interagire con l'unità principale di un'auto in due modi: con il controllo rotativo o toccando lo schermo. Quando utilizzi il controllo rotativo, viene evidenziata una delle visualizzazioni che possono essere messe a fuoco. Quando tocchi lo schermo, non viene visualizzato alcun sfondo evidenziato. L'utente può passare da una modalità di inserimento all'altra in qualsiasi momento:

  • Manopola → tocco. Quando l'utente tocca lo schermo, l'evidenziazione dello stato attivo scompare.
  • Tocca → rotativo. Quando l'utente spinge, ruota o preme il pulsante centrale, viene visualizzato l'evidenziazione dello stato attivo.

I pulsanti Indietro e Home non influiscono sulla modalità di immissione.

Il controllo rotativo si basa sul concetto esistente di modalità tocco di Android. Puoi utilizzare View.isInTouchMode() per determinare quale modalità di inserimento sta utilizzando l'utente. Puoi utilizzare OnTouchModeChangeListener per rilevare le modifiche. Sebbene questa opzione possa essere utilizzata per personalizzare l'interfaccia utente per la modalità di inserimento corrente, evita modifiche sostanziali perché potrebbero essere disorientanti.

Risoluzione dei problemi

In un'app progettata per il tocco, è comune avere visualizzazioni attivabili nidificate. Ad esempio, potresti avere un FrameLayout attorno a un ImageButton, entrambi attivabili. Questo non causa alcun danno per il tocco, ma può comportare una cattiva esperienza utente per il controllo rotativo perché l'utente deve ruotare il controller due volte per passare alla visualizzazione interattiva successiva. Per un'esperienza utente positiva, Google consiglia di rendere possibile mettere in primo piano la visualizzazione esterna o quella interna, ma non entrambe.

Se un pulsante o un interruttore perde lo stato attivo quando viene premuto tramite il controllo rotativo, potrebbe essere valida una di queste condizioni:

  • Il pulsante o l'opzione viene disattivato (temporaneamente o indefinitamente) a causa della pressione del pulsante. In entrambi i casi, esistono due modi per risolvere il problema:
    • Lascia lo stato android:enabled come true e utilizza uno stato personalizzato per disattivare il pulsante o l'opzione come descritto in Stato personalizzato.
    • Utilizza un contenitore per circondare il pulsante o l'opzione e impostalo in modo che sia attivabile anziché il pulsante o l'opzione. (L'ascoltatore dei clic deve trovarsi nel contenitore).
  • Il pulsante o l'interruttore viene sostituito. Ad esempio, l'azione eseguita quando il pulsante viene premuto o l'opzione attivata potrebbe attivare un aggiornamento delle azioni disponibili, facendo sì che i nuovi pulsanti sostituiscano quelli esistenti. Esistono due modi per risolvere il problema:
    • Anziché creare un nuovo pulsante o un nuovo pulsante di attivazione/disattivazione, imposta l'icona e/o il testo del pulsante o del pulsante di attivazione/disattivazione esistente.
    • Come sopra, aggiungi un contenitore attivabile attorno al pulsante o all'opzione.

RotaryPlayground

RotaryPlayground è un'app di riferimento per le rotazioni. Utilizzalo per scoprire come integrare le funzionalità di rotazione nelle tue app. RotaryPlayground è incluso nelle build dell'emulatore e nelle build per i dispositivi con Android Automotive OS (AAOS).

  • RotaryPlayground repository: packages/apps/Car/tests/RotaryPlayground/
  • Versioni: Android 11 QPR3, Android 11 Car e Android 12

L'app RotaryPlayground mostra le seguenti schede a sinistra:

  • Schede. Testa la navigazione nelle aree di messa a fuoco, saltando gli elementi non selezionabili e l'input di testo.
  • Manipolazione diretta. Testa i widget che supportano la modalità di manipolazione diretta semplice e avanzata. Questa scheda è specifica per la manipolazione diretta all'interno della finestra dell'app.
  • Manipolazione dell'interfaccia utente di sistema. Prova i widget che supportano la manipolazione diretta nelle finestre di sistema in cui è supportata solo la modalità di manipolazione diretta semplice.
  • Griglia. Testa la navigazione circolare a Z con scorrimento.
  • Notifica. Prova a attivare e disattivare le notifiche di avviso.
  • Scorri. Testa lo scorrimento di una combinazione di contenuti attivabili e non attivabili.
  • WebView. Prova a navigare tra i link in un WebView.
  • Personalizzato FocusArea. Testa la personalizzazione di FocusArea:
    • Wrap-around.
    • android:focusedByDefault e app:defaultFocus
    • .
    • Target di nudge espliciti.
    • Scorciatoie per i movimenti.
    • FocusArea senza viste attivabili.