Desenvolver apps

O material a seguir é para desenvolvedores de apps.

Para que seu app tenha suporte ao seletor giratório, você PRECISA:

  1. Coloque uma FocusParkingView no respectivo layout de atividade.
  2. Garanta que as visualizações sejam (ou não) focalizáveis.
  3. Use FocusAreas para envolver todas as visualizações focalizáveis, exceto as FocusParkingView.

Cada uma dessas tarefas está detalhada abaixo. Depois de configurar seu ambiente para desenvolver apps com seletor giratório.

Configurar um controle giratório

Antes de começar a desenvolver apps com seletor giratório, você precisa de um controle desse tipo ou um substituto. As opções estão 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 controle giratório emulado:

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

    Acessar o controle giratório emulado
    Figura 1. Acessar o controle giratório emulado
  2. Selecione Roteiro do carro na janela de controles estendidos:

    Selecionar seletor giratório do carro
    Figura 2. Selecionar seletor giratório do carro

Teclado USB

  • Conecte um teclado USB ao dispositivo com o Android Automotive OS (AAOS). Em alguns casos, isso impede que o teclado na tela apareça.
  • Use um build userdebug ou eng.
  • Ative a filtragem de eventos principais:
    adb shell settings put secure android.car.ROTARY_KEY_EVENT_FILTER 1
    
  • Consulte a tabela abaixo para encontrar a tecla correspondente a cada ação:
    Chave Ação giratória
    Q Girar no sentido anti-horário
    E Girar no sentido horário
    A Deslocar para esquerda
    D Deslocar para a direita
    W Deslocar para cima
    S Deslocar 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 por seletor giratório. Esses comandos pode ser executado em dispositivos com o Android Automotive OS (AAOS) ou em um emulador.

Comandos car_service Entrada por seletor giratório
adb shell cmd car_service inject-rotary Girar no sentido anti-horário
adb shell cmd car_service inject-rotary -c true Girar no sentido horário
adb shell cmd car_service inject-rotary -dt 100 50 Girar várias vezes no sentido anti-horário (100 ms atrás e 50 ms atrás)
adb shell cmd car_service inject-key 282 Deslocar para esquerda
adb shell cmd car_service inject-key 283 Deslocar para a direita
adb shell cmd car_service inject-key 280 Deslocar para cima
adb shell cmd car_service inject-key 281 Deslocar 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"

Controle giratório do OEM

Quando o hardware do controle giratório está em execução, essa é a configuração uma opção realista. Isso é particularmente útil para testar a rotação rápida.

FocusParkingView

FocusParkingView é uma visualização transparente na Biblioteca de interface do carro (car-ui-library). RotaryService o usa para oferecer suporte à navegação pelo controle giratório. FocusParkingView precisa ser a primeira visualização focalizável no layout. Ele precisa ser colocado fora de todos os FocusAreas. Cada janela deve ter um FocusParkingView: Se você já estiver usando o layout base da biblioteca da interface do carro, que contenha um FocusParkingView, não será preciso adicionar outro FocusParkingView: Veja abaixo um exemplo de FocusParkingView em 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>

Confira por que você precisa de um FocusParkingView:

  1. O Android não tira o foco automaticamente quando o foco está definido para outra janela. Se você tentar limpar o foco na janela anterior, o Android muda o foco para uma visualização naquela janela, o que resulta em duas janelas sendo focadas simultaneamente. Como adicionar um FocusParkingView a cada janela pode corrigir esse problema. Esta visualização é transparente, e o foco padrão é destacado é desativado, para que fique invisível para o usuário, independentemente de estar focado ou não. Ela pode tirar o foco para que RotaryService possa parar o foco. para remover o destaque do foco.
  2. Se houver apenas um FocusArea na janela atual, girar o controle no FocusArea faz com que o RotaryService mova o foco. da visualização da direita para a visualização da esquerda (e vice-versa). Como adicionar esta visualização a cada janela pode corrigir o problema. Quando RotaryService determina o foco for um FocusParkingView, ele pode determinar que um encapsulamento ocorre no ponto em que ele evita a volta por não mover o foco.
  3. Quando o controle giratório inicia um app, o Android foca a primeira visualização focalizável, que é sempre FocusParkingView. O FocusParkingView determina a visualização ideal e, em seguida, aplica o foco.

Visualizações focalizáveis

O RotaryService se baseia na existentes o conceito de foco de visualização, que remonta à época em que os celulares tinham teclados físicos e botões direcionais. O atributo android:nextFocusForward atual foi reaproveitado para o seletor giratório (consulte Personalização de FocusArea), mas android:nextFocusLeft, android:nextFocusRight. android:nextFocusUp e android:nextFocusDown não.

O RotaryService se concentra apenas em visualizações focalizáveis. Algumas visualizações, como Buttons, costumam ser focalizáveis. Outros, como TextViews e ViewGroups, geralmente não são. As visualizações clicáveis são focalizáveis automaticamente, e as visualizações são automaticamente clicáveis quando têm um listener de clique. Se essa lógica automática resultar na entrega você não precisa definir explicitamente a capacidade de foco da visualização. Se a lógica automática não resultem na capacidade de foco desejada, defina o atributo android:focusable como true ou false, ou definir programaticamente a capacidade de foco da visualização com View.setFocusable(boolean). Para que a RotaryService se concentre nela, uma visualização PRECISA atendem aos seguintes requisitos:

  • Focalizável
  • Ativado
  • Visível
  • têm valores de largura e altura diferentes de zero;

Se uma visualizaçã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 focar em visualizações desativadas, use um estado personalizado em vez de android:state_enabled para controlar a forma como a visualização aparece sem indicar que o Android precisa considerá-la desativada. Seu app pode informar ao usuário por que a visualização é 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 para sua visualização. Por exemplo, para adicionar um estado personalizado state_rotary_enabled ao 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 visualização junto com os métodos do acessador:
    private boolean mRotaryEnabled;
    public boolean getRotaryEnabled() { return mRotaryEnabled; }
    public void setRotaryEnabled(boolean rotaryEnabled) {
        mRotaryEnabled = rotaryEnabled;
    }
    
  3. Para ler o valor do atributo quando a visualização é criada:
    TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomView);
    mRotaryEnabled = a.getBoolean(R.styleable.CustomView_state_rotary_enabled);
    
  4. Na classe de visualização, substitua o método onCreateDrawableState() e, em seguida, adicionar 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 gerenciador de cliques da sua visualização tenha um desempenho diferente dependendo do estado. Por exemplo, o o gerenciador de cliques pode não fazer nada ou pode exibir um aviso quando mRotaryEnabled é false.
  6. Para que o botão apareça desativado, no drawable de segundo plano da visualização, use app:state_rotary_enabled em vez de android:state_enabled. Caso ainda não tenha, adicione:
    xmlns:app="http://schemas.android.com/apk/res-auto"
    
  7. Se a visualização estiver desativada em algum layout, substitua android:enabled="false" por app:state_rotary_enabled="false" e, em seguida, adicione o namespace app. acima.
  8. Se a visualização estiver desativada programaticamente, substitua as chamadas para setEnabled() com chamadas para setRotaryEnabled().

Área de foco

Use FocusAreas para particionar as visualizações focalizáveis em blocos e facilitar a navegação. mais fácil e consistente com outros aplicativos. Por exemplo, se o app tiver uma barra de ferramentas, a barra de ferramentas precisa estar em um FocusArea separado do restante do app. Barras de guias e outros elementos de navegação também precisam ser separados do restante do app. Listas grandes geralmente precisam ter os próprios FocusArea. Caso contrário, os usuários precisam alternar a lista inteira para acessar algumas visualizações.

A FocusArea é uma subclasse do LinearLayout na biblioteca car-ui-library. Quando esse recurso está ativado, o FocusArea desenha um destaque quando um dos em que os descendentes são focados. Para saber mais, consulte Focar na personalização de destaques.

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 FocusArea. Caso contrário, una o bloco em uma FocusArea.

NÃO aninhe um FocusArea em outro FocusArea. Isso leva a um comportamento de navegação indefinido. Garanta que todas as visualizações focalizáveis sejam aninhadas em uma FocusArea.

Um exemplo de FocusArea em 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 maneira:

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

Se você não incluir FocusAreas no layout, a visualização raiz será tratada. como uma área de foco implícito. O usuário não consegue acionar a navegação no app. Em vez disso, girar todas as visualizações focalizáveis, o que pode ser adequado para caixas de diálogo.

Personalização da área de foco

Dois atributos View padrão podem ser usados para personalizar a navegação por seletor giratório:

  • android:nextFocusForward permite que os desenvolvedores de apps especifiquem a rotação em uma área de foco. É o mesmo atributo usado para controlar a ordem de tabulação de pelo teclado. NÃO use esse atributo para criar uma repetição. Em vez disso, use app:wrapAround (confira abaixo) para criar uma repetição.
  • android:focusedByDefault permite que os desenvolvedores de apps especifiquem o visualização de foco padrão na janela. NÃO use esse atributo e app:defaultFocus (confira abaixo) no mesmo FocusArea.

O FocusArea também define alguns atributos para personalizar a navegação por seletor giratório. As áreas de foco implícitos não podem ser personalizadas com esses atributos.

  1. (Android 11 QPR3, Android 11 Car, Android 12)
    app:defaultFocus pode ser usado para especificar o ID de uma visualização descendente focalizável, que precisa ser focada quando o usuário alertas para este FocusArea.
  2. (Android 11 QPR3, Android 11 Car, Android 12)
    app:defaultFocusOverridesHistory pode ser definido como true para que a visualização especificada acima tenha foco, mesmo se com histórico para indicar que outra visualização neste FocusArea foi focada.
  3. (Android 12)
    Use app:nudgeLeftShortcut, app:nudgeRightShortcut, app:nudgeUpShortcut e app:nudgeDownShortcut para especificar o ID de uma visualização descendente focalizável, que precisa ser focada quando o o usuário alerta para uma determinada direção. Para saber mais, consulte o conteúdo de deslocar atalhos abaixo.

    (Android 11 QPR3, Android 11 Car, descontinuados no Android 12) app:nudgeShortcut e app:nudgeShortcutDirection ofereciam apenas um atalho de alerta.

  4. (Android 11 QPR3, Android 11 Car, Android 12)
    Para permitir que a rotação seja agrupada nessa FocusArea, app:wrapAround pode ser definido como true. Geralmente usado quando as visualizações são organizadas em uma círculo ou oval.
  5. (Android 11 QPR3, Android 11 Car, Android 12)
    Para ajustar o padding do destaque em neste FocusArea, use app:highlightPaddingStart, app:highlightPaddingEnd, app:highlightPaddingTop, app:highlightPaddingBottom, app:highlightPaddingHorizontal, e app:highlightPaddingVertical.
  6. (Android 11 QPR3, Android 11 Car, Android 12)
    Para ajustar os limites percebidos desse FocusArea e encontrar um destino de alerta, usam app:startBoundOffset, app:endBoundOffset, app:topBoundOffset, app:bottomBoundOffset, app:horizontalBoundOffset e app:verticalBoundOffset.
  7. (Android 11 QPR3, Android 11 Car, Android 12)
    Para especificar explicitamente o ID de um FocusArea (ou áreas) adjacentes nas direções indicadas, use app:nudgeLeft, app:nudgeRight, app:nudgeUp e app:nudgeDown. Use quando a pesquisa geométrica usada por padrão não encontrar o destino desejado.

Os deslocamentos geralmente navegam entre FocusAreas. Mas, com os atalhos de alerta, às vezes, o alerta navega primeiro em um FocusArea para que o usuário precise para acionar duas vezes e navegar para a próxima FocusArea. Atalhos de alerta são úteis quando uma FocusArea contém uma lista longa seguida por uma Botão de ação flutuante, como no exemplo abaixo:

Deslocar atalho
Figura 3. Deslocar atalho

Sem o atalho de alerta, o usuário teria que girar por toda a lista para alcançar a FAB.

Focar a personalização de destaques

Como observado acima, RotaryService se baseia no conceito já existente do framework do Android de foco de visualização. Quando o usuário gira e desvia, o RotaryService move o foco. focando uma vista e desfocando outra. No Android, quando uma visualização for focada, se ela:

  • especificar o próprio destaque de foco, o Android desenha o destaque da visualização;
  • Não especifica um destaque de foco, e o destaque de foco padrão não está desativado, Android desenha o destaque de foco padrão para a visualização.

Os apps projetados para toque geralmente não especificam os destaques de foco adequados.

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

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

Para especificar um destaque de foco personalizado para uma visualização, mude o drawable de primeiro ou segundo plano da visualização para um drawable que difere quando o foco da visualização é o foco. Normalmente, você alteraria em segundo plano. O drawable a seguir, se usado como plano de fundo para uma visualização quadrada, produz um destaque 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) Referências de recursos em negrito no exemplo acima identificam os recursos definidos pela biblioteca car-ui-library. O OEM substitui essas diretrizes para manter a consistência com o destaque de foco padrão especificado. Isso garante que a cor de destaque do foco, largura do traço e assim por diante não são alterados quando o usuário navega entre uma visualização com um foco personalizado e uma visualização com o 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 é chamado para quando um botão recebe uma posição sólida cor de fundo para chamar a atenção do usuário, como no exemplo abaixo. Isso pode tornar o destaque do foco é difícil de ver. Nessa situação, especifique um destaque de foco personalizado usando cores secundárias:

Cor de plano de fundo sólida
  • (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

Exemplo:

Foco, não pressionado Foco na pressão
Foco, não pressionado Foco, pressionado

Rolagem por seletor giratório

Se o app usa RecyclerViews, você DEVE usar CarUiRecyclerViews. Isso garante que a interface seja consistente com outros, porque a personalização de um OEM se aplica a todas as CarUiRecyclerViews.

Se todos os elementos da lista puderem ser focados, você não precisará fazer mais nada. A navegação por seletor giratório move o foco pelos elementos na lista, que rola a lista para tornar visível o elemento recém-focado.

(Android 11 QPR3, Android 11 Car, Android 12)
Se houver uma mistura de focalizável e não focal ou, se todos os elementos não puderem ser focados, ative a rolagem por seletor giratório, o usuário pode usar o controle giratório para rolar gradualmente pela lista sem pular itens não focalizáveis. Para ativar a rolagem por seletor giratório, defina o app:rotaryScrollEnabled para true.

(Android 11 QPR3, Android 11 Car, Android 12)
Você pode ativar a rolagem por seletor giratório em qualquer visualização rolável, incluindo avCarUiRecyclerView, com o Método setRotaryScrollEnabled() em CarUiUtils. Se você fizer isso, você precisa:

  • Tornar a visualização rolável focalizável para que ela possa ser focada quando nenhuma das as visualizações descendentes focalizáveis são visíveis,
  • Desative o destaque de foco padrão na visualização rolável chamando setDefaultFocusHighlightEnabled(false) para que a visualização rolável não parece estar focado,
  • Verifique se a visualização rolável está focada antes dos descendentes chamando setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS):
  • Detectar MotionEvents com SOURCE_ROTARY_ENCODER e AXIS_VSCROLL ou AXIS_HSCROLL para indicar a distância a ser rolada e a direção (pela placa).

Quando a rolagem por seletor giratório está ativada em uma CarUiRecyclerView e o usuário gira para uma área em que não há visualizações focalizáveis, a barra de rolagem muda de cinza para azul, como se para indicar que a barra de rolagem está em foco. Você pode implementar um efeito semelhante, se quiser.

Os MotionEvents são os mesmos gerados por uma roda de rolagem no mouse, com exceção da origem.

Modo de manipulação direta

Normalmente, os alertas e a rotação navegam pela interface do usuário, enquanto o pressionamento do botão central realizar ações, embora nem sempre seja o caso. Por exemplo, se um usuário quiser ajustar volume do alarme, podem usar o controle giratório para navegar até o controle deslizante de volume, pressionar o Botão central, gire o controle para ajustar o volume do alarme e depois pressione o botão "Voltar" para voltar à navegação. Isso é conhecido como modo de manipulação direta (DM). Neste o controle giratório é usado para interagir diretamente com a visualização, em vez de navegar.

Implemente as mensagens diretas de duas maneiras. Se você só precisa processar a rotação e a visualização desejada para manipular responde a ACTION_SCROLL_FORWARD e ACTION_SCROLL_BACKWARD AccessibilityEvents corretamente, use o mecanismo simples. Caso contrário, use o mecanismo avançado.

O mecanismo simples é a única opção nas janelas do sistema. os apps podem usar qualquer um dos mecanismos.

Mecanismo simples

(Android 11 QPR3, Android 11 Car, Android 12)
Seu app deve chamar DirectManipulationHelper.setSupportsRotateDirectly(View view, boolean enable). RotaryService reconhece quando o usuário está no modo de mensagem direta e entra nesse modo quando pressiona o botão "Centro" enquanto uma visualização está em foco. No modo DM, as rotações são realizadas ACTION_SCROLL_FORWARD ou ACTION_SCROLL_BACKWARD e sai do modo mensagem direta quando o usuário pressiona o botão "Voltar". O mecanismo simples alterna o estado selecionado de a visualização ao entrar e sair do modo DM.

Para indicar visualmente que o usuário está no modo de mensagem direta, faça com que sua visualização pareça diferente. quando selecionado. Por exemplo, mude o plano de fundo quando android:state_selected é true.

Mecanismo avançado

O app determina quando RotaryService entra e sai do modo de mensagem direta. Para uma abordagem consistente do usuário, pressionar o botão central com a visualização em foco vai entrar no modo de mensagens diretas e o botão "Voltar" para sair do modo DM. Se o botão central e/ou o alerta não forem usados, são formas alternativas de sair desse modo. Em apps como o Maps, um botão para representar É possível usar a mensagem direta para entrar no modo de mensagem direta.

Para oferecer suporte ao modo de mensagem direta avançada, uma visualização:

  1. (Android 11 QPR3, Android 11 Car, Android 12) PRECISA detectar um KEYCODE_DPAD_CENTER. para entrar no modo de mensagem direta e detectar um evento KEYCODE_BACK para sair desse modo, chamando DirectManipulationHelper.enableDirectManipulationMode() em cada caso. Para detectar esses eventos, siga um destes procedimentos:
    • Registre um OnKeyListener.
    • ou
    • Estenda a visualização e modifique o método dispatchKeyEvent().
  2. Você deve ouvir eventos de alerta (KEYCODE_DPAD_UP, KEYCODE_DPAD_DOWN, KEYCODE_DPAD_LEFT ou KEYCODE_DPAD_RIGHT) se a visualização e lidar com alertas.
  3. DEVE detectar MotionEvents e receber a contagem de rotação em AXIS_SCROLL se a visualização quiser lidar com a rotação. Há várias maneiras de fazer isso:
    1. Registre um OnGenericMotionListener.
    2. Estenda a visualização e modifique o método dispatchTouchEvent().
  4. Para não ficar preso no modo de mensagem direta, PRECISA sair desse modo quando o fragmento ou a atividade abrir a visualização. não é interativo.
  5. DEVE fornecer uma indicação visual para indicar que a visualização está no modo DM.

Um exemplo de visualização personalizada que usa o modo DM para movimentar e aplicar zoom a um mapa é fornecido 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 na RotaryPlayground.

Visualização de atividade

Ao usar uma ActivityView:

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

Para o usuário, ActivityViews não devem afetar a navegação, exceto o foco áreas não podem abranger ActivityViews. Em outras palavras, não é possível ter uma única área de foco tem conteúdo dentro e fora de uma ActivityView. Se você não adicionar qualquer FocusAreas à ActivityView, a raiz da hierarquia de visualização na ActivityView é considerada uma área de foco implícito.

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 "Avançar" e "Retroceder" geralmente funcionam quando pressionados. Para tornar essas Os botões têm suporte ao seletor giratório. Detecte KEYCODE_DPAD_CENTER KeyEvents da seguinte forma:

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 o mRunnable realiza uma ação (como voltar) 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 controle giratório, uma das visualizações focalizáveis é destacada. Sem foco ao tocar na tela 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 → seletor giratório. Quando o usuário movimenta, gira ou pressiona o botão central, o o destaque do foco é exibido.

Os botões Voltar e Início não afetam o modo de entrada.

Embarques rotativos no conceito existente de Android modo de toque. Você pode usar View.isInTouchMode() para determinar qual modo de entrada o usuário está usando. Você pode usar OnTouchModeChangeListener para detectar alterações. Embora isso possa ser usado para personalizar a interface do usuário para o modo de entrada, evite grandes alterações, pois elas podem ser desconcertantes.

Solução de problemas

Em um app projetado para toque, é comum ter visualizações focalizáveis aninhadas. Por exemplo, pode haver um FrameLayout em torno de um ImageButton, sendo que ambos são focalizáveis. Isso não prejudica o toque, mas pode resultar em uma experiência do usuário para o seletor giratório porque o usuário precisa girar o controle duas vezes para mover para a próxima visualização interativa. Para uma boa experiência do usuário, o Google recomenda que você faça a visualização externa ou a interna são focalizáveis, mas não ambas.

Se um botão ou interruptor perder o foco quando pressionado no controle giratório, um dos estas condições podem ser aplicáveis:

  • O botão ou interruptor está sendo desativado (brevemente ou indefinidamente) devido à o botão sendo pressionado. Em ambos os casos, há duas maneiras de resolver isso:
    • Deixe o estado android:enabled como true e use um para esmaecer o botão ou o botão, conforme descrito em Estado personalizado.
    • Use um contêiner para cercar o botão ou interruptor e torná-lo focalizável em vez de um botão ou um interruptor. O listener de clique precisa estar no contêiner.
  • O botão ou interruptor está sendo substituído. Por exemplo, a ação realizada quando o botão for pressionada ou se o interruptor estiver alternado, as ações disponíveis serão atualizadas fazendo com que os novos botões substituam os existentes. Há duas maneiras de lidar com isso:
    • Em vez de criar um novo botão ou interruptor, defina o ícone e/ou o texto do botão ou interruptor existente.
    • Como acima, adicione um contêiner focalizável ao redor do botão ou interruptor.

RotaryPlayground

O RotaryPlayground é um app de referência para o seletor giratório. Use para aprender a integrar os recursos giratórios nos seus apps. RotaryPlayground está incluído em builds do emulador e nos para dispositivos com o Android Automotive OS (AAOS).

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

O app RotaryPlayground mostra as seguintes guias à esquerda:

  • Cards. Testar a navegação pelas áreas de foco, pulando elementos que não podem ser focados e entrada de texto.
  • Manipulação direta. Testar widgets que oferecem suporte a widgets simples e avançados o modo de manipulação direta. Essa guia serve especificamente para manipulação direta do janela do app.
  • Manipulação de interface do Sys. Testar widgets que oferecem suporte à manipulação direta em janelas do sistema em que apenas o modo de manipulação direta simples é suportado.
  • Grade. Testar a navegação por seletor giratório do padrão z com rolagem.
  • Notificação. Tente ativar e desativar as notificações de alerta.
  • Role. Teste a rolagem por uma combinação de focalizável e não focalizável conteúdo.
  • WebView. Teste a navegação por links em um WebView.
  • FocusArea personalizado. Teste a personalização de FocusArea:
    • Encerramento.
    • android:focusedByDefault e app:defaultFocus
    • .
    • Destinos de alertas explícitos.
    • Deslocar atalhos.
    • FocusArea sem visualizações focalizáveis.