Sviluppa app

Il seguente materiale è destinato agli sviluppatori di app.

Per far sì che la tua app supporti la rotazione, DEVI:

  1. Posiziona un FocusParkingView nel rispettivo layout dell'attività.
  2. Garantire le visualizzazioni che sono (o non sono) focalizzabili.
  3. Utilizza FocusArea s per racchiudere tutte le visualizzazioni focalizzabili, ad eccezione di FocusParkingView .

Ognuna di queste attività è descritta in dettaglio di seguito, dopo aver configurato l'ambiente per sviluppare app abilitate per la rotazione.

Configura un controller rotativo

Prima di poter iniziare a sviluppare app abilitate per la rotazione, è necessario un controller rotante o un supporto. Hai 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 usare aosp_car_x86_64-userdebug .

Per accedere al controller rotativo emulato:

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

    Accedi al controller rotativo emulato
    Figura 1. Accesso al controller rotativo emulato
  2. Seleziona Rotante auto nella finestra dei controlli estesi:

    Seleziona Rotante per auto
    Figura 2. Selezionare Rotante per auto

Tastiera USB

  • Collega una tastiera USB al tuo dispositivo che esegue il sistema operativo Android Automotive (AAOS). In alcuni casi, ciò impedisce la visualizzazione della tastiera su schermo.
  • Utilizzare un userdebug o una build eng .
  • Abilita il filtraggio 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 ciascuna azione:
    Chiave Azione rotatoria
    Q Ruotare in senso antiorario
    E Ruotare in senso orario
    UN Spingi a sinistra
    D Spingi a destra
    W Spingi verso l'alto
    S Spingi verso il basso
    F o virgola Pulsante centrale
    R o Esc Tasto indietro

Comandi dell'ADB

È possibile utilizzare i comandi car_service per inserire eventi di input rotanti. Questi comandi possono essere eseguiti su dispositivi che eseguono il sistema operativo Android Automotive (AAOS) o su un emulatore.

comandi car_service Ingresso rotativo
adb shell cmd car_service inject-rotary Ruotare in senso antiorario
adb shell cmd car_service inject-rotary -c true Ruotare 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 verso l'alto
adb shell cmd car_service inject-key 281 Spingi verso il basso
adb shell cmd car_service inject-key 23 Clic sul pulsante centrale
adb shell input keyevent inject-key 4 Fare clic sul pulsante Indietro

Controller rotativo OEM

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

FocusParkingView

FocusParkingView è una visualizzazione trasparente nella libreria dell'interfaccia utente dell'auto (car-ui-library) . RotaryService lo utilizza per supportare la navigazione del controller rotante. FocusParkingView deve essere la prima vista attivabile nel layout. Deve essere posizionato all'esterno di tutte le FocusArea . Ogni finestra deve avere un FocusParkingView . Se stai già utilizzando il layout di base car-ui-library, che contiene un FocusParkingView , non è necessario aggiungere un altro FocusParkingView . Di seguito è mostrato 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 FocusParkingView :

  1. Android non cancella automaticamente il focus quando il focus è impostato in un'altra finestra. Se provi a cancellare lo stato attivo nella finestra precedente, Android rimette a fuoco una vista in quella finestra, il che si traduce in due finestre focalizzate contemporaneamente. L'aggiunta di FocusParkingView a ciascuna finestra può risolvere questo problema. Questa visualizzazione è trasparente e la sua evidenziazione di messa a fuoco predefinita è disabilitata, in modo che sia invisibile all'utente, indipendentemente dal fatto che sia focalizzata o meno. Può prendere la messa a fuoco in modo che RotaryService possa parcheggiarla su di essa per rimuovere l'evidenziazione della messa a fuoco.
  2. Se è presente una sola FocusArea nella finestra corrente, la rotazione del controller nella FocusArea fa sì che RotaryService sposti il ​​focus dalla vista a destra alla vista a sinistra (e viceversa). L'aggiunta di questa visualizzazione a ciascuna finestra può risolvere il problema. Quando RotaryService determina che l'obiettivo del focus è un FocusParkingView , può determinare che sta per verificarsi un wrap-around e a quel punto evita il wrap-around non spostando il focus.
  3. Quando la manopola avvia un'app, Android mette a fuoco la prima vista focalizzabile, che è sempre FocusParkingView . FocusParkingView determina la visualizzazione ottimale su cui concentrarsi e quindi applica lo stato attivo.

Viste focalizzabili

RotaryService si basa sul concetto esistente di visualizzazione focalizzata del framework Android, risalente a quando i telefoni avevano tastiere fisiche e D-pad. L'attributo android:nextFocusForward esistente viene riproposto per Rotary (vedere Personalizzazione FocusArea ), ma android:nextFocusLeft , android:nextFocusRight , android:nextFocusUp e android:nextFocusDown non lo sono.

RotaryService si concentra solo su punti di vista focalizzabili. Alcune visualizzazioni, come Button s, sono generalmente focalizzabili. Altri, come TextView e ViewGroup , di solito non lo sono. Le visualizzazioni selezionabili sono automaticamente selezionabili e le visualizzazioni sono automaticamente selezionabili quando dispongono di un listener di clic. Se questa logica automatica determina la focalizzabilità desiderata, non è necessario impostare esplicitamente la focalizzabilità della vista. Se la logica automatica non produce la focalizzabilità desiderata, imposta l'attributo android:focusable su true o false oppure imposta a livello di codice la focalizzabilità della vista con View.setFocusable(boolean) . Affinché RotaryService possa concentrarsi su questo, una vista DEVE soddisfare i seguenti requisiti:

  • Focalizzabile
  • Abilitato
  • Visibile
  • Avere valori diversi da zero per larghezza e altezza

Se una vista non soddisfa tutti questi requisiti, ad esempio un pulsante attivabile ma disabilitato, l'utente non può utilizzare la manopola per focalizzarla. Se vuoi concentrarti sulle visualizzazioni disabilitate, considera l'utilizzo di uno stato personalizzato anziché android:state_enabled per controllare come appare la visualizzazione senza indicare che Android dovrebbe considerarla disabilitata. La tua app può informare l'utente del motivo per cui la visualizzazione è disabilitata quando viene toccata. La sezione successiva spiega come eseguire questa operazione.

Stato personalizzato

Per aggiungere uno stato personalizzato:

  1. Per aggiungere un attributo personalizzato alla tua vista. Ad esempio, per aggiungere uno stato personalizzato state_rotary_enabled alla classe di visualizzazione CustomView , utilizzare:
    <declare-styleable name="CustomView">
        <attr name="state_rotary_enabled" format="boolean" />
    </declare-styleable>
    
  2. Per tenere traccia di questo stato, aggiungi una variabile di istanza alla tua 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 del tuo 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, sovrascrivi il metodo onCreateDrawableState() e quindi aggiungi lo stato personalizzato, quando appropriato. 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. Fai in modo che il gestore dei clic della tua vista funzioni in modo diverso a seconda del suo stato. Ad esempio, il gestore dei clic potrebbe non fare nulla o potrebbe visualizzare un avviso quando mRotaryEnabled è false .
  6. Per far sì che il pulsante appaia disabilitato, nello sfondo disegnabile della tua vista, usa app:state_rotary_enabled invece di android:state_enabled . Se non lo hai già, dovrai aggiungere:
    xmlns:app="http://schemas.android.com/apk/res-auto"
    
  7. Se la visualizzazione è disabilitata in qualsiasi layout, sostituisci android:enabled="false" con app:state_rotary_enabled="false" e quindi aggiungi lo spazio dei nomi app , come sopra.
  8. Se la vista è disabilitata a livello di codice, sostituisci le chiamate a setEnabled() con le chiamate a setRotaryEnabled() .

Area di messa a fuoco

Utilizza FocusAreas per suddividere le visualizzazioni focalizzabili in blocchi per semplificare la navigazione ed essere coerenti con altre app. Ad esempio, se la tua app ha una barra degli strumenti, la barra degli strumenti dovrebbe trovarsi in una FocusArea separata dal resto della tua app. Anche le barre delle schede e gli altri elementi di navigazione dovrebbero essere separati dal resto dell'app. Gli elenchi di grandi dimensioni dovrebbero generalmente avere una propria FocusArea . In caso contrario, gli utenti dovranno ruotare l'intero elenco per accedere ad alcune visualizzazioni.

FocusArea è una sottoclasse di LinearLayout nella libreria car-ui. Quando questa funzione è abilitata, FocusArea disegna un'evidenziazione quando uno dei suoi discendenti è focalizzato. Per ulteriori informazioni, consulta Personalizzazione dell'evidenziazione del focus .

Quando crei un blocco di navigazione nel file di layout, se intendi utilizzare LinearLayout come contenitore per quel blocco, utilizza invece FocusArea . Altrimenti, avvolgi il blocco in un FocusArea .

NON nidificare una FocusArea in un'altra FocusArea . Ciò porta a un comportamento di navigazione indefinito. Assicurati che tutte le visualizzazioni focalizzabili siano nidificate all'interno di FocusArea .

Di seguito è mostrato 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 come segue:

  1. Durante la gestione delle azioni di rotazione e spostamento, RotaryService cerca istanze di FocusArea nella gerarchia della vista.
  2. Quando si riceve un evento di rotazione, RotaryService sposta lo stato attivo su un'altra vista che può essere attiva nella stessa FocusArea .
  3. Quando si riceve un evento di sollecito, RotaryService sposta lo stato attivo su un'altra vista che può concentrarsi in un'altra FocusArea (in genere adiacente).

Se non includi alcuna FocusAreas nel layout, la vista radice viene trattata come un'area di focus implicita. L'utente non può spostarsi nell'app. Invece, ruoteranno attraverso tutte le visualizzazioni focalizzabili, il che potrebbe essere adeguato per le finestre di dialogo.

Personalizzazione dell'area focus

È possibile utilizzare due attributi di visualizzazione standard per personalizzare la navigazione rotativa:

  • android:nextFocusForward consente agli sviluppatori di app di specificare l'ordine di rotazione in un'area di interesse. Questo è lo 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 sotto) per creare un loop.
  • android:focusedByDefault consente agli sviluppatori di app di specificare la visualizzazione di messa a fuoco predefinita nella finestra. NON utilizzare questo attributo e app:defaultFocus (vedi sotto) nella stessa FocusArea .

FocusArea definisce anche alcuni attributi per personalizzare la navigazione rotativa. Le aree di interesse implicite non possono essere personalizzate con questi attributi.

  1. ( Android 11 QPR3, Android 11 Auto, Android 12 )
    app:defaultFocus può essere utilizzato per specificare l'ID di una vista discendente focalizzabile, su cui dovrebbe essere focalizzata quando l'utente sposta questo FocusArea .
  2. ( Android 11 QPR3, Android 11 Auto, Android 12 )
    app:defaultFocusOverridesHistory può essere impostato su true per mettere a fuoco la vista specificata sopra anche se con la cronologia per indicare che è stata focalizzata un'altra vista in questa FocusArea .
  3. ( Android 12 )
    Utilizza app:nudgeLeftShortcut , app:nudgeRightShortcut , app:nudgeUpShortcut e app:nudgeDownShortcut per specificare l'ID di una vista discendente focalizzabile, su cui dovrebbe essere focalizzata quando l'utente si sposta in una determinata direzione. Per ulteriori informazioni, consulta il contenuto delle scorciatoie per i solleciti di seguito.

    ( Android 11 QPR3, Android 11 Car, obsoleto in Android 12 ) app:nudgeShortcut e app:nudgeShortcutDirection supportavano solo una scorciatoia nudge.

  4. ( Android 11 QPR3, Android 11 Auto, Android 12 )
    Per consentire il ritorno a capo della rotazione in questa FocusArea , app:wrapAround può essere impostato su true . Questo viene in genere utilizzato quando le viste sono disposte in un cerchio o in un ovale.
  5. ( Android 11 QPR3, Android 11 Auto, Android 12 )
    Per regolare la spaziatura dell'evidenziazione in questa FocusArea , utilizza app:highlightPaddingStart , app:highlightPaddingEnd , app:highlightPaddingTop , app:highlightPaddingBottom , app:highlightPaddingHorizontal e app:highlightPaddingVertical .
  6. ( Android 11 QPR3, Android 11 Auto, Android 12 )
    Per regolare i limiti percepiti di questa FocusArea per trovare un obiettivo di spinta, utilizzare app:startBoundOffset , app:endBoundOffset , app:topBoundOffset , app:bottomBoundOffset , app:horizontalBoundOffset e app:verticalBoundOffset .
  7. ( Android 11 QPR3, Android 11 Auto, Android 12 )
    Per specificare esplicitamente l'ID di una FocusArea (o di aree) adiacenti nelle direzioni fornite, utilizzare app:nudgeLeft , app:nudgeRight , app:nudgeUp e app:nudgeDown . Utilizzare questo quando la ricerca geometrica utilizzata per impostazione predefinita non trova il target desiderato.

Il nudging solitamente consente di spostarsi tra le FocusArea. Tuttavia, con le scorciatoie di spostamento, lo spostamento a volte consente di navigare prima all'interno di una FocusArea in modo che l'utente potrebbe dover eseguire due spostamenti per passare alla successiva FocusArea . Le scorciatoie di spinta sono utili quando FocusArea contiene un lungo elenco seguito da un pulsante di azione mobile , come nell'esempio seguente:

Scorciatoia spinta
Figura 3. Scorciatoia per lo spostamento

Senza la scorciatoia di spinta, l'utente dovrebbe ruotare l'intero elenco per raggiungere il FAB.

Personalizzazione dell'evidenziazione del focus

Come notato in precedenza, RotaryService si basa sul concetto esistente di visualizzazione focalizzata del framework Android. Quando l'utente ruota e sposta, RotaryService sposta l'attenzione, focalizzando una vista e sfocandone un'altra. In Android, quando una vista è focalizzata, se la vista:

  • Ha specificato il proprio focus evidenziato, Android disegna l'evidenziazione del focus della vista.
  • Non specifica un'evidenziazione dello stato attivo e l'evidenziazione dello stato attivo predefinita non è disabilitata, Android disegna l'evidenziazione dello stato attivo predefinita per la vista.

Le app progettate per il tocco in genere non specificano le evidenziazioni di messa a fuoco appropriate.

L'evidenziazione dello stato attivo predefinita è fornita dal framework Android e può essere sovrascritta dall'OEM. Gli sviluppatori di app lo ricevono quando il tema che stanno utilizzando deriva da Theme.DeviceDefault .

Per un'esperienza utente coerente, affidati all'evidenziazione dello stato attivo predefinito quando possibile. Se hai bisogno di un'evidenziazione di messa a fuoco di forma personalizzata (ad esempio, rotonda o a forma di pillola) o se stai utilizzando un tema non derivato da Theme.DeviceDefault , utilizza le risorse car-ui-library per specificare la tua evidenziazione di messa a fuoco personalizzata per ogni vista.

Per specificare un'evidenziazione di messa a fuoco personalizzata per una vista, modificare il disegno di sfondo o di primo piano della vista in un disegno che differisce quando viene messa a fuoco la vista. In genere, cambieresti lo sfondo. Il seguente elemento disegnabile, se utilizzato come sfondo per una vista quadrata, produce un'evidenziazione del fuoco circolare:

<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 precedente identificano le risorse definite dalla libreria car-ui. L'OEM li sovrascrive per essere coerenti con l'evidenziazione di messa a fuoco predefinita specificata. Ciò garantisce che il colore dell'evidenziazione dello stato attivo, la larghezza del tratto e così via non cambino quando l'utente passa da una vista con un'evidenziazione dello stato attivo personalizzata a una vista con l'evidenziazione dello stato attivo predefinita. L'ultimo elemento è un'increspatura utilizzata per il tatto. I valori predefiniti utilizzati per le risorse in grassetto vengono visualizzati come segue:

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

Inoltre, viene richiesto un focus personalizzato quando a un pulsante viene assegnato un colore di sfondo a tinta unita per portarlo all'attenzione dell'utente, come nell'esempio seguente. Ciò può rendere difficile vedere l'evidenziazione della messa a fuoco. In questa situazione, specifica un'evidenziazione della messa a fuoco personalizzata utilizzando i colori secondari :

Colore di sfondo solido
  • ( Android 11 QPR3, Android 11 Auto, 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

Per esempio:

Concentrato, non pressatoConcentrato, pressato
Concentrato, non pressato Concentrato, pressato

Scorrimento rotatorio

Se la tua app utilizza RecyclerView , DOVREBBE utilizzare invece CarUiRecyclerView . Ciò garantisce che la tua interfaccia utente sia coerente con le altre perché la personalizzazione di un OEM si applica a tutti i CarUiRecyclerView .

Se gli elementi nella tua lista sono tutti focalizzabili, non devi fare nient'altro. La navigazione rotativa sposta il focus sugli elementi nell'elenco e l'elenco scorre per rendere visibile l'elemento appena focalizzato.

( Android 11 QPR3, Android 11 Auto, Android 12 )
Se è presente una combinazione di elementi focalizzabili e non focalizzabili, o se tutti gli elementi non sono focalizzabili, è possibile abilitare lo scorrimento rotatorio, che consente all'utente di utilizzare la manopola per scorrere gradualmente l'elenco senza saltare gli elementi non focalizzabili. Per abilitare lo scorrimento rotatorio, imposta l'attributo app:rotaryScrollEnabled su true .

( Android 11 QPR3, Android 11 Auto, Android 12 )
Puoi abilitare lo scorrimento rotatorio in qualsiasi vista scorrevole, incluso av CarUiRecyclerView , con il metodo setRotaryScrollEnabled() in CarUiUtils . Se lo fai, devi:

  • Rendere focalizzabile la vista scorrevole in modo che possa essere focalizzata quando nessuna delle sue viste discendenti focalizzabili è visibile,
  • Disabilita l'evidenziazione dello stato attivo predefinito sulla vista scorrevole chiamando setDefaultFocusHighlightEnabled(false) in modo che la vista scorrevole non sembri essere focalizzata,
  • Assicurati che la vista scorrevole sia focalizzata prima dei suoi 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 (attraverso il segno).

Quando lo scorrimento rotante è abilitato su un CarUiRecyclerView e l'utente ruota in un'area in cui non sono presenti visualizzazioni focalizzabili, la barra di scorrimento cambia da grigia a blu, come per indicare che la barra di scorrimento è focalizzata. Se lo desideri, puoi implementare un effetto simile.

I MotionEvents sono gli stessi generati dalla rotella di scorrimento del mouse, ad eccezione della sorgente.

Modalità di manipolazione diretta

Normalmente, gli spostamenti e la rotazione navigano attraverso l'interfaccia utente, mentre la pressione del pulsante centrale agisce, anche se non è sempre così. Ad esempio, se un utente desidera regolare il volume della sveglia, potrebbe utilizzare la manopola di controllo per spostarsi sul cursore del volume, premere il pulsante centrale, ruotare il controller per regolare il volume della sveglia, quindi premere il pulsante Indietro per tornare alla navigazione. . Questa viene definita modalità di manipolazione diretta (DM) . In questa modalità, la manopola viene utilizzata per interagire direttamente con la vista anziché per navigare.

Implementare DM in due modi. Se è necessario gestire solo la rotazione e la vista che si desidera manipolare risponde in modo appropriato agli AccessibilityEvent ACTION_SCROLL_FORWARD e ACTION_SCROLL_BACKWARD , utilizzare il meccanismo semplice . Altrimenti utilizza il meccanismo avanzato .

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

Meccanismo semplice

( Android 11 QPR3, Android 11 Auto, Android 12 )
La tua app dovrebbe chiamare DirectManipulationHelper.setSupportsRotateDirectly(View view, boolean enable) . RotaryService riconosce quando l'utente è in modalità DM e accede alla modalità DM quando l'utente preme il pulsante centrale mentre è focalizzata una vista. In modalità DM, le rotazioni eseguono ACTION_SCROLL_FORWARD o ACTION_SCROLL_BACKWARD e escono dalla modalità DM quando l'utente preme il pulsante Indietro. Il semplice meccanismo alterna lo stato selezionato della vista quando si entra e si esce dalla modalità DM.

Per fornire un segnale visivo che l'utente è in modalità DM, fai in modo che la tua 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, premendo il pulsante centrale con una vista DM focalizzata si dovrebbe entrare in modalità DM e il pulsante Indietro dovrebbe uscire dalla modalità DM. Se il pulsante centrale e/o la spinta non vengono utilizzati, possono rappresentare modi alternativi per uscire dalla modalità DM. Per app come Mappe, è possibile utilizzare un pulsante per rappresentare DM per accedere alla modalità DM.

Per supportare la modalità DM avanzata, una visualizzazione:

  1. ( Android 11 QPR3, Android 11 Car, Android 12 ) DEVE attendere un evento KEYCODE_DPAD_CENTER per accedere alla modalità DM e attendere un evento KEYCODE_BACK per uscire dalla modalità DM, chiamando DirectManipulationHelper.enableDirectManipulationMode() in ogni caso. Per ascoltare questi eventi, effettuare una delle seguenti operazioni:
    • Registra un OnKeyListener .
    • O,
    • Estendi la vista e quindi sovrascrivi 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 vista deve gestire i solleciti.
  3. DOVREBBE ascoltare MotionEvent e ottenere il conteggio delle rotazioni in AXIS_SCROLL se la vista desidera gestire la rotazione. Esistono diversi modi per farlo:
    1. Registra un OnGenericMotionListener .
    2. Estendi la visualizzazione e sovrascrivi il relativo metodo dispatchTouchEvent() .
  4. Per evitare di rimanere bloccati in modalità DM, DEVE uscire dalla modalità DM quando il frammento o l'attività a cui appartiene la vista non è interattivo.
  5. DOVREBBE fornire un segnale visivo per indicare che la vista è in modalità DM.

Di seguito viene fornito 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(); }

Ulteriori esempi possono essere trovati nel progetto RotaryPlayground .

Visualizzazione attività

Quando si utilizza ActivityView:

  • ActivityView non dovrebbe essere focalizzabile.
  • ( Android 11 QPR3, Android 11 Car, obsoleto in Android 11 )
    Il contenuto di ActivityView DEVE contenere FocusParkingView come prima vista focalizzabile e il suo attributo app:shouldRestoreFocus DEVE essere false .
  • Il contenuto di ActivityView non deve avere visualizzazioni android:focusByDefault .

Per l'utente, ActivityViews non dovrebbe avere alcun effetto sulla navigazione, tranne per il fatto che le aree di interesse non possono estendersi su ActivityViews. In altre parole, non è possibile avere un'unica area di interesse con contenuto all'interno e all'esterno di ActivityView . Se non aggiungi FocusAreas al tuo ActivityView , la radice della gerarchia di visualizzazione in ActivityView è considerata un'area di interesse implicita.

Pulsanti che funzionano quando vengono tenuti premuti

La maggior parte dei pulsanti provocano un'azione quando vengono cliccati. Alcuni pulsanti invece funzionano se tenuti premuti. Ad esempio, i pulsanti Avanzamento rapido e Riavvolgimento in genere funzionano quando vengono tenuti premuti. Per fare in modo che tali pulsanti supportino la rotazione, 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 (come il riavvolgimento) e pianifica l'esecuzione dopo un ritardo.

Modalità tocco

Gli utenti possono utilizzare un controller rotante per interagire con l'unità principale di un'auto in due modi, utilizzando il controller rotante o toccando lo schermo. Quando si utilizza la manopola di controllo, viene evidenziata una delle visualizzazioni focalizzabili. Quando si tocca lo schermo, non viene visualizzata alcuna evidenziazione della messa a fuoco. L'utente può passare da una modalità di input all'altra in qualsiasi momento:

  • Rotazione → toccare. Quando l'utente tocca lo schermo, l'evidenziazione della messa a fuoco scompare.
  • Toccate → rotante. Quando l'utente sposta, ruota o preme il pulsante centrale, viene visualizzata l'evidenziazione della messa a fuoco.

I pulsanti Indietro e Home non hanno alcun effetto sulla modalità di input.

Il Rotary si basa sul concetto esistente di modalità touch di Android. È possibile utilizzare View.isInTouchMode() per determinare quale modalità di input sta utilizzando l'utente. È possibile utilizzare OnTouchModeChangeListener per ascoltare le modifiche. Anche se questo può essere utilizzato per personalizzare l'interfaccia utente per la modalità di input corrente, evita eventuali modifiche importanti poiché possono essere sconcertanti.

Risoluzione dei problemi

In un'app progettata per il tocco, è normale avere visualizzazioni focalizzabili nidificate. Ad esempio, potrebbe esserci un FrameLayout attorno a un ImageButton , entrambi focalizzabili. Ciò non danneggia il tocco, ma può comportare un'esperienza utente scadente per la rotazione perché l'utente deve ruotare il controller due volte per passare alla visualizzazione interattiva successiva. Per una buona esperienza utente, Google consiglia di rendere focalizzabile la vista esterna o quella interna, ma non entrambe.

Se un pulsante o un interruttore perde la messa a fuoco quando viene premuto tramite la manopola, potrebbe verificarsi una delle seguenti condizioni:

  • Il pulsante o l'interruttore è stato disabilitato (brevemente o indefinitamente) perché è stato premuto il pulsante. In entrambi i casi, ci sono due modi per risolvere questo problema:
    • Lascia lo stato android:enabled su true e utilizza uno stato personalizzato per disattivare il pulsante o passare come descritto in Custom State .
    • Utilizza un contenitore per circondare il pulsante o l'interruttore e rendere il contenitore attivabile anziché il pulsante o l'interruttore. (Il listener di clic deve trovarsi nel contenitore.)
  • È in corso la sostituzione del pulsante o dell'interruttore. Ad esempio, l'azione eseguita quando si preme il pulsante o si attiva l'interruttore potrebbe attivare un aggiornamento delle azioni disponibili facendo sì che nuovi pulsanti sostituiscano quelli esistenti. Esistono due modi per risolvere questo problema:
    • Invece di creare un nuovo pulsante o interruttore, imposta l'icona e/o il testo del pulsante o interruttore esistente.
    • Come sopra, aggiungi un contenitore focalizzabile attorno al pulsante o all'interruttore.

RotaryPlayground

RotaryPlayground è un'app di riferimento per il Rotary. Usalo per scoprire come integrare le funzionalità rotanti nelle tue app. RotaryPlayground è incluso nelle build dell'emulatore e nelle build per dispositivi che eseguono il sistema operativo Android Automotive (AAOS).

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

L'app RotaryPlayground mostra le seguenti schede sulla sinistra:

  • Carte. Prova a navigare nelle aree di interesse, saltando gli elementi non focalizzabili e l'input di testo.
  • Manipolazione diretta. Testare 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 del sistema. Testare i widget che supportano la manipolazione diretta nelle finestre di sistema in cui è supportata solo la modalità di manipolazione diretta semplice.
  • Griglia. Prova la navigazione rotativa con motivo Z con scorrimento.
  • Notifica. Prova a inserire e uscire dalle notifiche heads-up.
  • Scorrere. Prova a scorrere un mix di contenuti focalizzabili e non focalizzabili.
  • WebView. Prova la navigazione attraverso i collegamenti in un WebView .
  • FocusArea personalizzata. Prova la personalizzazione FocusArea :
    • Arrotolare.
    • android:focusedByDefault e app:defaultFocus
    • .
    • Obiettivi di spinta espliciti.
    • Scorciatoie di spinta.
    • FocusArea senza viste focalizzabili.