Desenvolvendo aplicativos

O material a seguir é para desenvolvedores de aplicativos.

Para fazer com que seu aplicativo seja rotativo, você DEVE:

  1. Coloque um FocusParkingView no respectivo layout de atividade.
  2. Garanta as exibições que são (ou não) focalizáveis.
  3. Use FocusArea s para agrupar todas as suas visualizações focalizáveis, exceto FocusParkingView .

Cada uma dessas tarefas é detalhada abaixo, depois que você configurar seu ambiente para desenvolver aplicativos habilitados para rotação.

Configurar um controlador rotativo

Antes de começar a desenvolver aplicativos habilitados para rotação, você precisa de um controlador rotativo ou de um substituto. Você tem as opções descritas abaixo.

emulador

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

Você também pode usar aosp_car_x86_64-userdebug .

Para acessar o controlador rotativo emulado:

  1. Toque nos três pontos na parte inferior da barra de ferramentas:

    Acesse o controlador rotativo emulado
    Figura 1. Controlador rotativo emulado de acesso
  2. Selecione Carro giratório na janela de controles estendidos:

    Selecione o carro rotativo
    Figura 2. Selecione o carro rotativo

Teclado USB

  • Conecte um teclado USB ao seu dispositivo que executa o Android Automotive OS (AAOS). Em alguns casos, isso pode impedir que o teclado na tela apareça.
  • Use um userdebug ou compilação eng .
  • Habilitar filtragem de eventos chave:
    adb shell settings put secure android.car.ROTARY_KEY_EVENT_FILTER 1
    
  • Consulte a tabela abaixo para encontrar a chave correspondente para cada ação:
    Chave ação rotativa
    Q Girar no sentido anti-horário
    E Rode no sentido dos ponteiros do relógio
    A Deslocar para a esquerda
    D Deslocar para a direita
    C Empurrar para cima
    S Empurrar para baixo
    F ou Vírgula botão central
    R ou Esc Botão "voltar

Comandos ADB

Você pode usar comandos car_service para injetar eventos de entrada rotativa. Esses comandos podem ser executados em dispositivos que executam o Android Automotive OS (AAOS) ou em um emulador.

comandos car_service entrada rotativa
adb shell cmd car_service inject-rotary Girar no sentido anti-horário
adb shell cmd car_service inject-rotary -c true Rode no sentido dos ponteiros do relógio
adb shell cmd car_service inject-rotary -dt 100 50 Gire no sentido anti-horário várias vezes (100 ms atrás e 50 ms atrás)
adb shell cmd car_service inject-key 282 Deslocar para a esquerda
adb shell cmd car_service inject-key 283 Deslocar para a direita
adb shell cmd car_service inject-key 280 Empurrar para cima
adb shell cmd car_service inject-key 281 Empurrar para baixo
adb shell cmd car_service inject-key 23 Clique no botão central
adb shell input keyevent inject-key 4 Clique no botão Voltar

controlador rotativo OEM

Quando o hardware do controlador rotativo estiver funcionando, essa é a opção mais realista. É particularmente útil para testar a rotação rápida.

FocusParkingView

FocusParkingView é uma visão transparente na Car UI Library (car-ui-library) . RotaryService o usa para oferecer suporte à navegação do controlador rotativo. FocusParkingView deve ser a primeira exibição focalizável no layout. Ele deve ser colocado fora de todos FocusArea s. Cada janela deve ter um FocusParkingView . Se você já estiver usando o layout base car-ui-library, que contém um FocusParkingView , não será necessário adicionar outro FocusParkingView . Veja abaixo um exemplo de FocusParkingView no 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>

Aqui estão as razões pelas quais você precisa de um FocusParkingView :

  1. O Android não limpa o foco automaticamente quando o foco é definido em outra janela. Se você tentar limpar o foco na janela anterior, o Android refocalizará uma exibição nessa janela, o que resultará em duas janelas sendo focadas simultaneamente. Adicionar um FocusParkingView a cada janela pode corrigir esse problema. Essa visualização é transparente e seu destaque de foco padrão está desabilitado, de modo que fica invisível para o usuário, independentemente de estar focado ou não. Pode levar o foco para que RotaryService possa estacionar o foco nele para remover o destaque do foco.
  2. Se houver apenas uma FocusArea na janela atual, girar o controlador na FocusArea faz com que RotaryService mova o foco da visualização à direita para a visualização à esquerda (e vice-versa). Adicionar essa exibição a cada janela pode corrigir o problema. Quando RotaryService determina que o alvo do foco é um FocusParkingView , ele pode determinar que um contorno está prestes a ocorrer, no ponto em que evita o contorno ao não mover o foco.
  3. Quando o controle giratório inicia um aplicativo, o Android focaliza a primeira visualização focalizável, que é sempre a FocusParkingView . O FocusParkingView determina a exibição ideal para focar e, em seguida, aplica o foco.

Visualizações focalizáveis

RotaryService se baseia no conceito existente de foco de exibição da estrutura do Android, que remonta a quando os telefones tinham teclados físicos e D-pads. O atributo android:nextFocusForward existente foi reaproveitado para rotação (consulte personalização FocusArea ), mas android:nextFocusLeft , android:nextFocusRight , android:nextFocusUp e android:nextFocusDown não são.

RotaryService se concentra apenas em exibições que podem ser focadas. Algumas exibições, como Button s, geralmente podem ser focalizadas. Outros, como TextView s e ViewGroup s, geralmente não são. As exibições clicáveis ​​são automaticamente focadas e as exibições são clicáveis ​​automaticamente quando têm um ouvinte de clique. Se essa lógica automática resultar na capacidade de foco desejada, você não precisará definir explicitamente a capacidade de foco da exibição. Se a lógica automática não resultar na capacidade de foco desejada, defina o atributo android:focusable como true ou false ou defina programaticamente a capacidade de foco da exibição com View.setFocusable(boolean) . Para que RotaryService se concentre nisso, uma visualização DEVE atender aos seguintes requisitos:

  • Focável
  • Habilitado
  • Visível
  • Ter valores diferentes de zero para largura e altura

Se uma exibição não atender a todos esses requisitos, por exemplo, um botão focalizável, mas desativado, o usuário não poderá usar o controle giratório para focar nele. Se você quiser se concentrar em visualizações desativadas, considere usar um estado personalizado em vez de android:state_enabled para controlar como a visualização aparece sem indicar que o Android deve considerá-la desativada. Seu aplicativo pode informar ao usuário por que a visualização está desativada quando tocada. A próxima seção explica como fazer isso.

Estado personalizado

Para adicionar um estado personalizado:

  1. Para adicionar um atributo personalizado à sua visualização. Por exemplo, para adicionar um estado personalizado state_rotary_enabled à classe de exibição CustomView , use:
    <declare-styleable name="CustomView">
        <attr name="state_rotary_enabled" format="boolean" />
    </declare-styleable>
    
  2. Para rastrear esse estado, adicione uma variável de instância à sua exibição junto com métodos de acesso:
    private boolean mRotaryEnabled;
    public boolean getRotaryEnabled() { return mRotaryEnabled; }
    public void setRotaryEnabled(boolean rotaryEnabled) {
        mRotaryEnabled = rotaryEnabled;
    }
    
  3. Para ler o valor do seu atributo quando sua visualização é criada:
    TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomView);
    mRotaryEnabled = a.getBoolean(R.styleable.CustomView_state_rotary_enabled);
    
  4. Em sua classe de exibição, substitua o método onCreateDrawableState() e adicione o estado personalizado, quando apropriado. Por exemplo:
    @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. Faça com que o manipulador de cliques de sua exibição funcione de maneira diferente, dependendo de seu estado. Por exemplo, o manipulador de cliques pode não fazer nada ou pode exibir um brinde quando mRotaryEnabled for false .
  6. Para fazer com que o botão apareça desabilitado, use app:state_rotary_enabled em vez de android:state_enabled . Se você ainda não o tiver, precisará adicionar:
    xmlns:app="http://schemas.android.com/apk/res-auto"
    
  7. Se sua visualização estiver desativada em qualquer layout, substitua android:enabled="false" por app:state_rotary_enabled="false" e adicione o namespace app , como acima.
  8. Se sua visualização estiver desativada programaticamente, substitua as chamadas para setEnabled() por chamadas para setRotaryEnabled() .

Área de foco

Use FocusAreas para particionar as exibições focalizáveis ​​em blocos para facilitar a navegação e ser consistente com outros aplicativos. Por exemplo, se seu aplicativo tiver uma barra de ferramentas, a barra de ferramentas deverá estar em uma FocusArea separada do restante do aplicativo. Barras de guias e outros elementos de navegação também devem ser separados do restante do aplicativo. Listas grandes geralmente devem ter seu próprio FocusArea . Caso contrário, os usuários devem girar por toda a lista para acessar algumas exibições.

FocusArea é uma subclasse de LinearLayout na biblioteca car-ui-library. Quando esse recurso estiver ativado, um FocusArea desenhará um destaque quando um de seus descendentes estiver em foco. Para saber mais, consulte Personalização de realce de foco .

Ao criar um bloco de navegação no arquivo de layout, se você pretende usar um LinearLayout como um contêiner para esse bloco, use um FocusArea . Caso contrário, envolva o bloco em um FocusArea .

NÃO aninhe uma FocusArea em outra FocusArea . Fazer isso levará a um comportamento de navegação indefinido. Certifique-se de que todas as visualizações focalizáveis ​​estejam aninhadas em uma FocusArea .

Um exemplo de FocusArea no RotaryPlayground é mostrado abaixo:

<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 funciona da seguinte forma:

  1. Ao lidar com ações de rotação e deslocamento, RotaryService procura instâncias de FocusArea na hierarquia de visualização.
  2. Ao receber um evento de rotação, RotaryService move o foco para outra View que pode ter o foco na mesma FocusArea .
  3. Ao receber um evento nudge, RotaryService move o foco para outra visualização que pode ter o foco em outra FocusArea (normalmente adjacente).

Se você não incluir nenhum FocusAreas em seu layout, a exibição raiz será tratada como uma área de foco implícita. O usuário não pode cutucar para navegar no aplicativo. Em vez disso, eles giram em todas as exibições focalizáveis, o que pode ser adequado para caixas de diálogo.

Personalização FocusArea

Dois atributos de visualização padrão podem ser usados ​​para personalizar a navegação rotativa:

  • android:nextFocusForward permite que os desenvolvedores de aplicativos especifiquem a ordem de rotação em uma área de foco. Este é o mesmo atributo usado para controlar a ordem de tabulação para navegação pelo teclado. NÃO use este atributo para criar um loop. Em vez disso, use app:wrapAround (veja abaixo) para criar um loop.
  • android:focusedByDefault permite que os desenvolvedores de aplicativos especifiquem a exibição de foco padrão na janela. NÃO use este atributo e app:defaultFocus (veja abaixo) no mesmo FocusArea .

FocusArea também define alguns atributos para personalizar a navegação rotativa. Áreas de foco implícitas não podem ser personalizadas com esses atributos.

  1. ( Android 11 QPR3, Android 11 Carro, Android 12 )
    app:defaultFocus pode ser usado para especificar o ID de uma visualização descendente focalizável, que deve ser focada quando o usuário for direcionado para esta FocusArea .
  2. ( Android 11 QPR3, Android 11 Carro, Android 12 )
    app:defaultFocusOverridesHistory pode ser definido como true para fazer com que a exibição especificada acima tenha o foco, mesmo que com o histórico para indicar que outra exibição nesta FocusArea foi focada.
  3. ( Andróide 12 )
    Use app:nudgeLeftShortcut , app:nudgeRightShortcut , app:nudgeUpShortcut e app:nudgeDownShortcut para especificar o ID de uma exibição descendente focalizável, que deve ser focada quando o usuário move em uma determinada direção. Para saber mais, consulte o conteúdo dos atalhos de deslocamento abaixo.

    ( Android 11 QPR3, Android 11 Car, obsoleto no Android 12 ) app:nudgeShortcut e app:nudgeShortcutDirection suportavam apenas um atalho de nudge.

  4. ( Android 11 QPR3, Android 11 Carro, Android 12 )
    Para permitir que a rotação seja agrupada nesta FocusArea , app:wrapAround pode ser definido como true . Isso é mais usado quando as visualizações são organizadas em um círculo ou oval.
  5. ( Android 11 QPR3, Android 11 Carro, Android 12 )
    Para ajustar o preenchimento do destaque nesta FocusArea , use app:highlightPaddingStart , app:highlightPaddingEnd , app:highlightPaddingTop , app:highlightPaddingBottom , app:highlightPaddingHorizontal e app:highlightPaddingVertical .
  6. ( Android 11 QPR3, Android 11 Carro, Android 12 )
    Para ajustar os limites percebidos desta FocusArea para localizar um alvo de deslocamento, use app:startBoundOffset , app:endBoundOffset , app:topBoundOffset , app:bottomBoundOffset , app:horizontalBoundOffset e app:verticalBoundOffset .
  7. ( Android 11 QPR3, Android 11 Carro, Android 12 )
    Para especificar explicitamente o ID de uma FocusArea adjacente (ou áreas) nas direções fornecidas, use app:nudgeLeft , app:nudgeRight , app:nudgeUp e app:nudgeDown . Use isso quando a busca geométrica usada por padrão não encontrar o alvo desejado.

Nudging geralmente navega entre FocusAreas. Mas com atalhos de deslocamento, às vezes, o deslocamento primeiro navega dentro de uma FocusArea , de modo que o usuário pode precisar mover duas vezes para navegar para a próxima FocusArea . Atalhos de deslocamento são úteis quando uma FocusArea contém uma longa lista seguida por um Floating Action Button , como no exemplo abaixo:

Deslocar atalho
Figura 3. Atalho de deslocamento

Sem o atalho nudge, o usuário teria que rodar toda a lista para chegar ao FAB.

Personalização de destaque de foco

Conforme observado acima, RotaryService se baseia no conceito existente de foco de exibição da estrutura do Android. Quando o usuário gira e cutuca, RotaryService move o foco, focalizando uma visualização e tirando o foco de outra. No Android, quando uma visualização é focada, se a visualização:

  • tiver especificado seu próprio destaque de foco, o Android desenha o destaque de foco da exibição.
  • não especifica um destaque de foco e o destaque de foco padrão não está desativado, o Android desenha o destaque de foco padrão para a exibição.

Os aplicativos projetados para toque geralmente não especificam os destaques de foco apropriados.

O destaque de foco padrão é fornecido pela estrutura do Android e pode ser substituído pelo OEM. Os desenvolvedores de aplicativos o recebem quando o tema que estão usando é derivado de Theme.DeviceDefault .

Para uma experiência de usuário consistente, conte com o destaque de foco padrão sempre que possível. Se você precisar de um destaque de foco de formato personalizado (por exemplo, redondo ou em forma de pílula) ou se estiver usando um tema não derivado de Theme.DeviceDefault , use os recursos car-ui-library para especificar seu próprio destaque de foco para cada vista.

Para especificar um realce de foco personalizado para uma exibição, altere o desenhável de fundo ou primeiro plano da exibição para um desenhável que difere quando o foco é o da exibição. Normalmente, você mudaria o plano de fundo. O desenhável a seguir, se usado como plano de fundo para uma vista quadrada, produz um realce de foco redondo:

<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 ) As referências de recursos em negrito no exemplo acima identificam os recursos definidos pela biblioteca car-ui-library. O OEM os substitui para serem consistentes com o realce de foco padrão que eles especificam. Isso garante que a cor de destaque do foco, a largura do traçado e assim por diante não sejam alteradas quando o usuário navegar entre uma exibição com destaque de foco personalizado e uma exibição com destaque de foco padrão. O último item é uma ondulação usada para toque. Os valores padrão usados ​​para os recursos em negrito aparecem da seguinte forma:

Valores padrão para recursos em negrito
Figura 4. Valores padrão para recursos em negrito

Além disso, um destaque de foco personalizado é solicitado quando um botão recebe uma cor de fundo sólida para chamar a atenção do usuário, como no exemplo abaixo. Isso pode dificultar a visualização do realce do foco. Nessa situação, especifique um destaque de foco personalizado usando cores secundárias :

Cor de fundo sólida
  • ( Android 11 QPR3, Android 11 Carro, Android 12 )
    car_ui_rotary_focus_fill_secondary_color
    car_ui_rotary_focus_stroke_secondary_color
  • ( Andróide 12 )
    car_ui_rotary_focus_pressed_fill_secondary_color
    car_ui_rotary_focus_pressed_stroke_secondary_color

Por exemplo:

Focado, não pressionadoFocado, pressionado
Focado, não pressionado Focado, pressionado

Rolagem rotativa

Se seu aplicativo usa RecyclerView s, você DEVE usar CarUiRecyclerView s. Isso garante que sua interface do usuário seja consistente com outras porque a personalização de um OEM se aplica a todos os CarUiRecyclerView s.

Se todos os elementos da sua lista forem focalizáveis, você não precisará fazer mais nada. A navegação giratória move o foco pelos elementos na lista e a lista rola para tornar visível o elemento recém-focado.

( Android 11 QPR3, Android 11 Carro, Android 12 )
Se houver uma mistura de elementos focalizáveis ​​e não focalizáveis, ou se todos os elementos estiverem fora de foco, você pode habilitar a rolagem rotativa, que permite ao usuário usar o controlador giratório para percorrer gradualmente a lista sem pular os itens não focalizáveis. Para ativar a rolagem rotativa, defina o atributo app:rotaryScrollEnabled como true .

( Android 11 QPR3, Android 11 Carro, Android 12 )
Você pode ativar a rolagem rotativa em qualquer visualização rolável, incluindo av CarUiRecyclerView , com o método setRotaryScrollEnabled() em CarUiUtils . Se você fizer isso, você precisa:

  • Torne a exibição rolável focalizável para que ela possa ser focada quando nenhuma de suas exibições descendentes focalizáveis ​​estiver visível,
  • Desative o realce de foco padrão na exibição rolável chamando setDefaultFocusHighlightEnabled(false) para que a exibição rolável não pareça estar focada,
  • Certifique-se de que a visualização rolável esteja focada antes de seus descendentes chamando setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS) .
  • Ouça MotionEvents com SOURCE_ROTARY_ENCODER e AXIS_VSCROLL ou AXIS_HSCROLL para indicar a distância para rolar e a direção (através do sinal).

Quando a rolagem rotativa está habilitada em um CarUiRecyclerView e o usuário gira para uma área onde não há exibições focalizáveis, a barra de rolagem muda de cinza para azul, como se indicasse que a barra de rolagem está focada. Você pode implementar um efeito semelhante, se quiser.

Os MotionEvents são os mesmos gerados por uma roda de rolagem em um mouse, exceto pela fonte.

Modo de manipulação direta

Normalmente, os toques e a rotação navegam pela interface do usuário, enquanto os pressionamentos do botão central entram em ação, embora nem sempre seja o caso. Por exemplo, se um usuário deseja ajustar o volume do alarme, ele pode usar o controle giratório para navegar até o controle deslizante de volume, pressionar o botão Central, girar o controlador para ajustar o volume do alarme e, em seguida, pressionar o botão Voltar para retornar à navegação . Isso é conhecido como modo de manipulação direta (DM) . Nesse modo, o controlador giratório é usado para interagir diretamente com a visualização, em vez de navegar.

Implemente o DM de uma das duas maneiras. Se você só precisa manipular a rotação e a exibição que deseja manipular responde a ACTION_SCROLL_FORWARD e ACTION_SCROLL_BACKWARD AccessibilityEvent s adequadamente, use o mecanismo simples . Caso contrário, use o mecanismo avançado .

O mecanismo simples é a única opção nas janelas do sistema; os aplicativos podem usar qualquer mecanismo.

Mecanismo simples

( Android 11 QPR3, Android 11 Carro, Android 12 )
Seu aplicativo deve chamar DirectManipulationHelper.setSupportsRotateDirectly(View view, boolean enable) . RotaryService reconhece quando o usuário está no modo DM e entra no modo DM quando o usuário pressiona o botão central enquanto uma visualização está em foco. Quando no modo DM, as rotações executam ACTION_SCROLL_FORWARD ou ACTION_SCROLL_BACKWARD e sai do modo DM quando o usuário pressiona o botão Voltar. O mecanismo simples alterna o estado selecionado da visualização ao entrar e sair do modo DM.

Para fornecer uma indicação visual de que o usuário está no modo DM, faça com que sua visualização pareça diferente quando selecionada. Por exemplo, altere o plano de fundo quando android:state_selected for true .

Mecanismo avançado

O aplicativo determina quando RotaryService entra e sai do modo DM. Para uma experiência de usuário consistente, pressionar o botão Central com uma visão DM focada deve entrar no modo DM e o botão Voltar deve sair do modo DM. Se o botão central e/ou nudge não forem usados, eles podem ser formas alternativas de sair do modo DM. Para aplicativos como o Maps, um botão para representar DM pode ser usado para entrar no modo DM.

Para suportar o modo DM avançado, uma visualização:

  1. ( Android 11 QPR3, Android 11 Car, Android 12 ) DEVE ouvir um evento KEYCODE_DPAD_CENTER para entrar no modo DM e ouvir um evento KEYCODE_BACK para sair do modo DM, chamando DirectManipulationHelper.enableDirectManipulationMode() em cada caso. Para ouvir esses eventos, siga um destes procedimentos:
    • Registre um OnKeyListener .
    • ou,
    • Estenda a exibição e substitua seu método dispatchKeyEvent() .
  2. DEVE ouvir eventos de nudge ( KEYCODE_DPAD_UP , KEYCODE_DPAD_DOWN , KEYCODE_DPAD_LEFT ou KEYCODE_DPAD_RIGHT ) se a exibição deve lidar com nudges.
  3. DEVE ouvir MotionEvent s e obter a contagem de rotação em AXIS_SCROLL se a exibição quiser manipular a rotação. Existem várias maneiras de fazer isso:
    1. Registre um OnGenericMotionListener .
    2. Estenda a exibição e substitua seu método dispatchTouchEvent() .
  4. Para evitar ficar preso no modo DM, DEVE sair do modo DM quando o fragmento ou a atividade à qual a visualização pertence não for interativa.
  5. DEVE fornecer uma indicação visual para indicar que a visualização está no modo DM.

Uma amostra de uma exibição personalizada que usa o modo DM para panorâmica e zoom em um mapa é fornecida abaixo:

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

Mais exemplos podem ser encontrados no projeto RotaryPlayground .

ActivityView

Ao usar um ActivityView:

  • O ActivityView não deve ser focalizável.
  • ( Android 11 QPR3, Android 11 Car, obsoleto no Android 11 )
    O conteúdo do ActivityView DEVE conter um FocusParkingView como a primeira exibição focalizável e seu atributo app:shouldRestoreFocus DEVE ser false .
  • O conteúdo do ActivityView não deve ter exibições android:focusByDefault .

Para o usuário, ActivityViews não devem ter nenhum efeito na navegação, exceto que as áreas de foco não podem abranger ActivityViews. Em outras palavras, você não pode ter uma única área de foco com conteúdo dentro e fora de um ActivityView . Se você não adicionar nenhum FocusAreas ao seu ActivityView , a raiz da hierarquia de exibição no ActivityView será considerada uma área de foco implícita.

Botões que funcionam quando pressionados

A maioria dos botões causa alguma ação quando clicados. Alguns botões funcionam quando pressionados. Por exemplo, os botões Fast Forward e Rewind normalmente funcionam quando pressionados. Para fazer com que esses botões suportem rotação, ouça KEYCODE_DPAD_CENTER KeyEvents da seguinte maneira:

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

Em que mRunnable executa uma ação (como retroceder) e se programa para ser executado após um atraso.

Modo de toque

Os usuários podem usar um controle giratório para interagir com a unidade principal em um carro de duas maneiras: usando o controle giratório ou tocando na tela. Ao usar o controlador giratório, uma das visualizações focalizáveis ​​será destacada. Ao tocar na tela, nenhum destaque de foco aparece. O usuário pode alternar entre esses modos de entrada a qualquer momento:

  • Rotativo → toque. Quando o usuário toca na tela, o destaque do foco desaparece.
  • Toque em → rotativo. Quando o usuário cutuca, gira ou pressiona o botão central, o destaque do foco aparece.

Os botões Voltar e Início não têm efeito no modo de entrada.

O Rotary pega carona no conceito existente de modo de toque do Android. Você pode usar View.isInTouchMode() para determinar qual modo de entrada o usuário está usando. Você pode usar OnTouchModeChangeListener para ouvir as alterações. Embora isso possa ser usado para personalizar sua interface de usuário para o modo de entrada atual, evite alterações importantes, pois elas podem ser desconcertantes.

Solução de problemas

Em um aplicativo projetado para toque, não é incomum ter visualizações aninhadas com foco. Por exemplo, pode haver um FrameLayout em torno de um ImageButton , ambos com foco. Isso não prejudica o toque, mas pode resultar em uma experiência de usuário insatisfatória para rotação porque o usuário deve girar o controlador duas vezes para mover para a próxima exibição interativa. Para uma boa experiência do usuário, o Google recomenda que você torne a visão externa ou a visão interna focalizáveis, mas não ambas.

Se um botão ou chave perder o foco quando pressionado por meio do controle giratório, uma destas condições pode se aplicar:

  • O botão ou interruptor está sendo desabilitado (brevemente ou indefinidamente) devido ao botão ter sido pressionado. Em ambos os casos, há duas maneiras de resolver isso:
    • Deixe o estado android:enabled como true e use um estado personalizado para esmaecer o botão ou alternar conforme descrito em Estado personalizado .
    • Use um recipiente para cercar o botão ou interruptor e torne o recipiente focalizável em vez do botão ou interruptor. (O ouvinte de clique deve estar no contêiner.)
  • O botão ou interruptor está sendo substituído. Por exemplo, a ação executada quando o botão é pressionado ou o botão é alternado pode acionar uma atualização das ações disponíveis, fazendo com que novos botões substituam os botões existentes. Existem duas maneiras de abordar isso:
    • Em vez de criar um novo botão ou switch, defina o ícone e/ou o texto do botão ou switch existente.
    • Como acima, adicione um contêiner focalizável ao redor do botão ou switch.

RotaryPlayground

RotaryPlayground é um aplicativo de referência para rotary. Use-o para aprender a integrar recursos rotativos em seus aplicativos. RotaryPlayground está incluído em compilações de emuladores e em compilações para dispositivos que executam o Android Automotive OS (AAOS).

  • Repositório RotaryPlayground : packages/apps/Car/tests/RotaryPlayground/
  • Versões: Android 11 QPR3, Android 11 Car e Android 12

O aplicativo RotaryPlayground mostra as seguintes guias à esquerda:

  • Cartões. Teste a navegação pelas áreas de foco, ignorando elementos fora de foco e entrada de texto.
  • Manipulação direta. Widgets de teste que suportam o modo de manipulação direta simples e avançado. Esta guia é especificamente para manipulação direta na janela do aplicativo.
  • Manipulação da interface do usuário do sistema. Widgets de teste que suportam manipulação direta em janelas do sistema onde apenas o modo de manipulação direta simples é suportado.
  • Grade. Teste a navegação rotativa de padrão z com rolagem.
  • Notificação. Teste cutucando dentro e fora das notificações heads-up.
  • Rolagem. Teste a rolagem em uma mistura de conteúdo focalizável e não focalizável.
  • WebView. Teste a navegação por links em um WebView .
  • FocusArea personalizado. Teste a personalização FocusArea :
    • Envolver em torno.
    • android:focusedByDefault e app:defaultFocus
    • .
    • Alvos de nudge explícitos.
    • Deslocar atalhos.
    • FocusArea sem exibições focalizáveis.