O material a seguir é destinado a desenvolvedores de apps.
Para que o app ofereça suporte a controles rotativos, é PRECISO:
- Coloque uma
FocusParkingView
no layout da atividade correspondente. - Verifique se as visualizações podem ou não ser focalizadas.
- Use
FocusArea
s para agrupar todas as visualizações com foco, excetoFocusParkingView
.
Cada uma dessas tarefas é detalhada abaixo, depois que você configurar seu ambiente para desenvolver apps com botão giratório.
Configurar um controle giratório
Antes de começar a desenvolver apps com controle giratório, você precisa de um controle giratório ou um substituto. Confira as opções descritas abaixo.
Emulador
source build/envsetup.sh && lunch car_x86_64-userdebug m -j emulator -wipe-data -no-snapshot -writable-system
Também é possível usar aosp_car_x86_64-userdebug
.
Para acessar o controle giratório emulado:
- Toque nos três pontos na parte de baixo da barra de ferramentas:
Figura 1. Acessar o controle giratório emulado - Selecione Car rotary na janela de controles estendidos:
Figura 2. Selecione o botão rotativo Car
Teclado USB
- Conecte um teclado USB ao dispositivo que executa o Android Automotive OS (AAOS). Em alguns casos, isso impede que o teclado na tela apareça.
- Use um build
userdebug
oueng
. - Ativar a filtragem de eventos principais:
adb shell settings put secure android.car.ROTARY_KEY_EVENT_FILTER 1
- Consulte a tabela abaixo para encontrar a chave correspondente a cada ação:
Chave Ação de rotação P 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
É possível usar comandos car_service
para injetar eventos de entrada por seletor giratório. Esses comandos
podem ser executados 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 no sentido anti-horário várias vezes (há 100 ms e 50 ms) |
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 OEM
Quando o hardware do controlador rotativo estiver em funcionamento, essa é a opção mais realista. É especialmente útil para testar a rotação rápida.
FocusParkingView
FocusParkingView
é uma visualização transparente na
biblioteca de interface do carro (car-ui-library).
O RotaryService
usa essa API para oferecer suporte à navegação com o controle giratório.
FocusParkingView
precisa ser a primeira visualização focada
no layout. Ele precisa ser colocado fora de todos os FocusArea
s. Cada janela precisa ter um
FocusParkingView
. Se você já estiver usando o layout base da car-ui-library,
que contém um FocusParkingView
, não será necessário adicionar outro
FocusParkingView
. Confira 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 os motivos para usar um FocusParkingView
:
- O Android não limpa o foco automaticamente quando ele é definido em outra janela. Se você
tentar limpar o foco na janela anterior, o Android vai redirecionar uma visualização nessa janela, o que
resulta em duas janelas com foco ao mesmo tempo. Adicionar um
FocusParkingView
a cada janela pode corrigir esse problema. Essa visualização é transparente, e o destaque de foco padrão é desativado, para que fique invisível para o usuário, independentemente de estar com foco ou não. Ele pode receber o foco para queRotaryService
possa estacionar o foco nele para remover o destaque de foco. - Se houver apenas um
FocusArea
na janela atual, girar o controle noFocusArea
fará com queRotaryService
mova o foco da visualização à direita para a visualização à esquerda (e vice-versa). Adicionar essa visualização a cada janela pode corrigir o problema. QuandoRotaryService
determina que o destino de foco é umFocusParkingView
, ele pode determinar que uma contagem regressiva está prestes a ocorrer, momento em que ele evita a contagem regressiva sem mover o foco. - Quando o controle giratório inicia um app, o Android foca a primeira visualização focalizável,
que é sempre a
FocusParkingView
. OFocusParkingView
determina a visualização ideal para aplicar o foco.
Visualizações focalizáveis
O RotaryService
se baseia no conceito de
focus de visualização
do framework do Android, que remonta à época em que os smartphones tinham teclados físicos e direcionais D-pad.
O atributo android:nextFocusForward
existente foi modificado para rotary
(consulte Personalização de FocusArea), mas
android:nextFocusLeft
, android:nextFocusRight
,
android:nextFocusUp
e android:nextFocusDown
não foram.
RotaryService
se concentra apenas em visualizações que podem ser focalizadas. Algumas visualizações,
como Button
s,
geralmente podem ser focalizadas. Outros, como TextView
s e ViewGroup
s,
geralmente não são. As visualizações clicáveis são automaticamente focalizáveis e clicáveis quando têm um listener de cliques. Se essa lógica automática resultar na capacidade de foco
desejada, não será necessário definir explicitamente a capacidade de foco da visualizaçã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 a capacidade de foco da visualização de forma programática com
View.setFocusable(boolean)
. Para que RotaryService
se concentre nele, uma visualização PRECISA
atender aos seguintes requisitos:
- Focalizável
- Ativado
- Visível
- Ter valores diferentes de zero para largura e altura
Se uma visualização não atender a todos esses requisitos, por exemplo, um botão com foco, 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 como
a visualização aparece sem indicar que o Android precisa considerá-la desativada. O app pode informar
ao usuário por que a visualização está desativada quando ele toca nela. A próxima seção explica como fazer isso.
Estado personalizado
Para adicionar um estado personalizado:
- Para adicionar um atributo personalizado
à visualização. Por exemplo, para adicionar um estado personalizado
state_rotary_enabled
à classe de visualizaçãoCustomView
, use:<declare-styleable name="CustomView"> <attr name="state_rotary_enabled" format="boolean" /> </declare-styleable>
- Para acompanhar esse estado, adicione uma variável de instância à visualização com métodos de acesso:
private boolean mRotaryEnabled; public boolean getRotaryEnabled() { return mRotaryEnabled; } public void setRotaryEnabled(boolean rotaryEnabled) { mRotaryEnabled = rotaryEnabled; }
- Para ler o valor do atributo quando a vista é criada:
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomView); mRotaryEnabled = a.getBoolean(R.styleable.CustomView_state_rotary_enabled);
- Na classe de visualização, substitua o método
onCreateDrawableState()
e adicione o estado personalizado, quando apropriado. 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; }
- Faça com que o gerenciador de cliques da visualização funcione de maneira diferente dependendo do estado dela. Por exemplo, o
gerenciador de cliques pode não fazer nada ou exibir uma mensagem pop-up quando
mRotaryEnabled
forfalse
. - Para fazer com que o botão apareça desativado, no drawable de plano de fundo da sua visualização, use
app:state_rotary_enabled
em vez deandroid:state_enabled
. Se você ainda não tiver, adicione:xmlns:app="http://schemas.android.com/apk/res-auto"
- Se a visualização estiver desativada em qualquer layout, substitua
android:enabled="false"
porapp:state_rotary_enabled="false"
e adicione o namespaceapp
, conforme mostrado acima. - Se a visualização estiver desativada programaticamente, substitua as chamadas para
setEnabled()
por chamadas parasetRotaryEnabled()
.
FocusArea
Use FocusAreas
para dividir as visualizações com foco em blocos para facilitar a navegação
e manter a consistência com outros apps. Por exemplo, se o app tiver uma barra de ferramentas, ela
precisa estar em uma FocusArea
separada do restante do app. As barras de guias e
outros elementos de navegação também precisam ser separados do restante do app. Listas grandes
geralmente têm uma FocusArea
própria. Caso contrário, os usuários precisam percorrer
toda a lista para acessar algumas visualizações.
FocusArea
é uma subclasse de LinearLayout
na biblioteca car-ui-library.
Quando esse recurso está ativado, FocusArea
mostra um destaque quando um dos
descendentes está em foco. Para saber mais, consulte
Personalização do destaque 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 FocusArea
.
Caso contrário, encapsule o bloco em um FocusArea
.
NÃO aninhe um FocusArea
em outro FocusArea
.
Isso leva a um comportamento de navegação indefinido. Verifique se todas as visualizações com foco estão
aninhadas em um FocusArea
.
Confira abaixo um exemplo de FocusArea
em
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
funciona da seguinte maneira:
- Ao processar ações de rotação e empurrão,
RotaryService
procura instâncias deFocusArea
na hierarquia de visualização. - Ao receber um evento de rotação,
RotaryService
move o foco para outra visualização que pode receber o foco no mesmoFocusArea
. - Ao receber um evento de lembrete, o
RotaryService
move o foco para outra visualização que pode receber o foco em outraFocusArea
(geralmente adjacente).
Se você não incluir FocusAreas
no layout, a visualização raiz será tratada
como uma área de foco implícita. O usuário não pode tocar para navegar no app. Em vez disso, ele
vai alternar entre todas as visualizações com foco, o que pode ser adequado para caixas de diálogo.
Personalização de FocusArea
Dois atributos padrão da visualização podem ser usados para personalizar a navegação por botão giratório:
android:nextFocusForward
permite que os desenvolvedores de apps especifiquem a ordem de rotação em uma área de foco. Esse é o mesmo atributo usado para controlar a ordem de tabulação para a navegação pelo teclado. NÃO use esse atributo para criar um loop. Em vez disso, useapp:wrapAround
(confira abaixo) para criar um loop.android:focusedByDefault
permite que os desenvolvedores de apps especifiquem a visualização de foco padrão na janela. NÃO use esse atributo eapp:defaultFocus
(consulte abaixo) na mesmaFocusArea
.
FocusArea
também define alguns atributos para personalizar a navegação por botão giratório.
As áreas de foco implícitas não podem ser personalizadas com esses atributos.
- (Android 11 QPR3, Android 11 Car,
Android 12)
app:defaultFocus
pode ser usado para especificar o ID de uma visualização de descendente com foco, que precisa ser focada quando o usuário toca nela.FocusArea
- (Android 11 QPR3, Android 11 Car,
Android 12)
app:defaultFocusOverridesHistory
pode ser definido comotrue
para que a visualização especificada acima receba o foco, mesmo que com histórico para indicar que outra visualização emFocusArea
foi focada. - (Android 12)
Useapp:nudgeLeftShortcut
,app:nudgeRightShortcut
,app:nudgeUpShortcut
eapp:nudgeDownShortcut
para especificar o ID de uma visualização descendente com foco, que precisa ser focada quando o usuário desliza em uma determinada direção. Para saber mais, consulte o conteúdo sobre atalhos de sugestão abaixo.(Android 11 QPR3, Android 11 Car, descontinuado no Android 12)
app:nudgeShortcut
eapp:nudgeShortcutDirection
tinham suporte apenas a um atalho de toque. - (Android 11 QPR3, Android 11 Car,
Android 12)
Para permitir que a rotação seja aplicada a esteFocusArea
, aapp:wrapAround
pode ser definida comotrue
. Isso é mais comum quando as visualizações são organizadas em um círculo ou oval. - (Android 11 QPR3, Android 11 Car,
Android 12)
Para ajustar o padding do destaque nesteFocusArea
, useapp:highlightPaddingStart
,app:highlightPaddingEnd
,app:highlightPaddingTop
,app:highlightPaddingBottom
,app:highlightPaddingHorizontal
eapp:highlightPaddingVertical
. - (Android 11 QPR3, Android 11 Car,
Android 12)
Para ajustar os limites percebidos dessaFocusArea
e encontrar um alvo de empurrão, useapp:startBoundOffset
,app:endBoundOffset
,app:topBoundOffset
,app:bottomBoundOffset
,app:horizontalBoundOffset
eapp:verticalBoundOffset
. - (Android 11 QPR3, Android 11 Car,
Android 12)
Para especificar explicitamente o ID de umFocusArea
adjacente (ou áreas) nas direções fornecidas, useapp:nudgeLeft
,app:nudgeRight
,app:nudgeUp
eapp:nudgeDown
. Use essa opção quando a pesquisa geométrica usada por padrão não encontrar o destino desejado.
O nudge geralmente navega entre as áreas de foco. No entanto, com atalhos de nudge,
às vezes o nudge primeiro navega dentro de um FocusArea
, de modo que o usuário pode precisar
fazer o nudge duas vezes para navegar até o próximo FocusArea
. Os atalhos de empurrão são úteis
quando um FocusArea
contém uma lista longa seguida por um
botão de ação flutuante,
como no exemplo abaixo:

Sem o atalho de sugestão, o usuário teria que percorrer toda a lista para chegar ao FAB.
Personalização do destaque de foco
Como observado acima, o RotaryService
é baseado no conceito de
foco de visualização do framework do Android. Quando o usuário gira e move, RotaryService
move o foco,
focando uma visualização e desfocando outra. No Android, quando uma visualização está em foco, se ela:
- Especificou o próprio destaque de foco. O Android desenha o destaque de foco da visualização.
- Não especifica um destaque de foco e o destaque de foco padrão não está desativado. O Android mostra 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 recebem esse valor quando o tema que estão usando é derivado de
Theme.DeviceDefault
.
Para uma experiência consistente do usuário, use o destaque de foco padrão sempre que possível.
Se você precisar de um destaque de foco com forma personalizada (por exemplo, redondo ou em forma de pílula) ou se estiver
usando um tema não derivado de Theme.DeviceDefault
, use os recursos da car-ui-library
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 plano de fundo ou de primeiro plano da visualização para um drawable diferente quando a visualização estiver em foco. Normalmente, você muda o plano de fundo. 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) As referências de recursos em negrito no exemplo acima identificam recursos definidos pela car-ui-library. O OEM substitui esses valores para que eles sejam consistentes com o destaque de foco padrão especificado. Isso garante que a cor do destaque de foco, a largura do traço e assim por diante não mudem quando o usuário navega entre uma visualização com um destaque de 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 maneira:

Além disso, um destaque de foco personalizado é chamado quando um botão recebe uma cor de plano de fundo sólida para chamar a atenção do usuário, como no exemplo abaixo. Isso pode dificultar a visualização do destaque de foco. Nesse caso, especifique um destaque de foco personalizado usando cores secundárias:
![]() |
- (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:
![]() |
![]() |
|
Em foco, não pressionado | Foco, pressionado |
Rolagem por seletor giratório
Se o app usa RecyclerView
s, use
CarUiRecyclerView
s. Isso garante que sua interface seja consistente com
outras, porque a personalização de um OEM é aplicada a todos os CarUiRecyclerView
s.
Se todos os elementos da lista puderem ser focados, não será necessário fazer mais nada. A navegação rotativa move o foco pelos elementos na lista, e a lista rola para tornar o elemento recém-focado visível.
(Android 11 QPR3, Android 11 Car,
Android 12)
Se houver uma mistura de elementos com e sem foco, ou se todos os elementos não tiverem foco, será possível ativar a rolagem por botão giratório, que permite
que o usuário use o controle giratório para rolar gradualmente pela lista sem pular
itens sem foco. Para ativar a rolagem giratória, defina o atributo app:rotaryScrollEnabled
como true
.
(Android 11 QPR3, Android 11 Car,
Android 12)
É possível ativar a rolagem giratória em qualquer
visualização rolável, incluindo avCarUiRecyclerView
, com o
método setRotaryScrollEnabled()
em CarUiUtils
. Se você fizer isso,
faça o seguinte:
- Tornar a visualização rolável focalizável para que ela possa ser focada quando nenhuma das visualizações filhas focalizáveis estiverem visíveis.
- Desativar o destaque de foco padrão na visualização rolável chamando
setDefaultFocusHighlightEnabled(false)
para que a visualização rolável não pareça estar em foco. - Chame
setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS)
para garantir que a visualização rolável seja focada antes dos descendentes. - Detecte MotionEvents com
SOURCE_ROTARY_ENCODER
eAXIS_VSCROLL
ouAXIS_HSCROLL
para indicar a distância de rolagem e a direção (pelo sinal).
Quando a rolagem giratória é ativada em um CarUiRecyclerView
e o usuário gira
para uma área em que não há visualizações focadas, a barra de rolagem muda de cinza para azul, como se
para indicar que a barra de rolagem está focada. Você pode implementar um efeito semelhante, se quiser.
Os MotionEvents são iguais aos gerados por uma roda de rolagem em um mouse, exceto pela origem.
Modo de manipulação direta
Normalmente, os empurrões e a rotação navegam pela interface do usuário, enquanto as pressões no botão central executam uma ação, embora nem sempre seja assim. Por exemplo, se um usuário quiser ajustar o volume do alarme, ele poderá usar o controle giratório para navegar até o controle deslizante de volume, pressionar o botão central, girar o controle para ajustar o volume do alarme e pressionar o botão "Voltar" para retornar à navegação. Isso é chamado de modo de manipulação direta (DM). Nesse modo, o controle giratório é usado para interagir com a visualização diretamente, em vez de navegar.
Implemente a DM de duas maneiras. Se você só precisa processar a rotação e a visualização que quer
manipular responde corretamente a AccessibilityEvent
s ACTION_SCROLL_FORWARD
e
ACTION_SCROLL_BACKWARD
, 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)
O app precisa 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. No modo DM, as rotações executam
ACTION_SCROLL_FORWARD
ou ACTION_SCROLL_BACKWARD
e saem 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 dica visual de que o usuário está no modo de DM, faça com que a visualização apareça de forma diferente
quando selecionada. Por exemplo, mude o plano de fundo quando
android:state_selected
for true
.
Mecanismo avançado
O app determina quando RotaryService
entra e sai do modo DM. Para uma experiência
consistente do usuário, pressionar o botão central com uma visualização de DM em foco deve entrar no modo DM
e o botão "Voltar" deve sair do modo DM. Se o botão Centralizar e/ou o toque não forem usados,
eles podem ser maneiras alternativas de sair do modo DM. Para apps como o Maps, um botão para representar
a DM pode ser usado para entrar no modo DM.
Para oferecer suporte ao modo avançado de DM, uma visualização:
- (Android 11 QPR3, Android 11 Car,
Android 12) É PRECISO detectar um evento
KEYCODE_DPAD_CENTER
para entrar no modo DM e detectar um eventoKEYCODE_BACK
para sair do modo DM, chamandoDirectManipulationHelper.enableDirectManipulationMode()
em cada caso. Para detectar esses eventos, siga um destes procedimentos:- Registre um
OnKeyListener
.
ou
- Estenda a visualização e substitua o método
dispatchKeyEvent()
dela.
- Registre um
- DEVE detectar eventos de alerta (
KEYCODE_DPAD_UP
,KEYCODE_DPAD_DOWN
,KEYCODE_DPAD_LEFT
ouKEYCODE_DPAD_RIGHT
) se a visualização precisar processar alertas. - DEVE detectar
MotionEvent
s e receber a contagem de rotações emAXIS_SCROLL
se a visualização quiser processar a rotação. Há várias maneiras de fazer isso:- Registre um
OnGenericMotionListener
. - Estenda a visualização e substitua o método
dispatchTouchEvent()
dela.
- Registre um
- Para evitar ficar preso no modo DM, é PRECISO sair do modo DM quando o fragmento ou a atividade a que a visualização pertence não é interativa.
- DEVE fornecer uma indicação visual para indicar que a visualização está no modo DM.
Confira abaixo um exemplo de visualização personalizada que usa o modo DM para mover e aplicar zoom em um mapa:
/** 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 pode ser focado. - (Android 11 QPR3, Android 11 Car,
descontinuado no Android 11)
O conteúdo daActivityView
PRECISA conter umaFocusParkingView
como a primeira visualização com foco, e o atributoapp:shouldRestoreFocus
PRECISA serfalse
. - O conteúdo do
ActivityView
não pode ter visualizaçõesandroid:focusByDefault
.
Para o usuário, as ActivityViews não devem ter efeito na navegação, exceto que as áreas de foco
não podem abranger ActivityViews. Em outras palavras, não é possível ter uma única área de foco com
conteúdo dentro e fora de um ActivityView
. Se você não adicionar
nenhuma FocusArea ao ActivityView
, a raiz da hierarquia de visualizaçã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 são pressionados.
Por exemplo, os botões de avanço rápido e retrocesso normalmente funcionam quando são pressionados. Para que esses
botões ofereçam suporte a rotary, 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
realiza 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 controle rotativo, uma das visualizações com foco é destacada. Ao tocar na tela, nenhum destaque de foco aparece. O usuário pode alternar entre esses modos de entrada a qualquer momento:
- Rotativa → toque. Quando o usuário toca na tela, o destaque de foco desaparece.
- Toque em → rotativo. Quando o usuário empurra, gira ou pressiona o botão Central, o destaque de foco aparece.
Os botões "Voltar" e "Início" não afetam o modo de entrada.
O Rotary usa o conceito existente do
modo de toque do Android.
É possível usar
View.isInTouchMode()
para determinar qual modo de entrada o usuário está usando. Você pode usar
OnTouchModeChangeListener
para detectar mudanças. Embora isso possa ser usado para personalizar a interface do usuário para o modo de entrada
atual, evite mudanças importantes, pois elas podem ser perturbadoras.
Solução de problemas
Em um app projetado para toque, é comum ter visualizações focadas aninhadas.
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
do usuário ruim para o seletor giratório, porque o usuário precisa girar o controle duas vezes para passar para
a próxima visualização interativa. Para uma boa experiência do usuário, o Google recomenda que você torne
a visualização externa ou interna focada, mas não ambas.
Se um botão ou chave perder o foco ao ser pressionado pelo controle rotativo, uma destas condições poderá ser aplicada:
- O botão ou chave está sendo desativado (brevemente ou indefinidamente) porque está sendo pressionado. Em ambos os casos, há duas maneiras de resolver isso:
- Deixe o estado
android:enabled
comotrue
e use um estado personalizado para deixar o botão ou a chave cinza, conforme descrito em Estado personalizado. - Use um contêiner para cercar o botão ou o interruptor e tornar o contêiner focalizável em vez do botão ou do interruptor. O listener de clique precisa estar no contêiner.
- Deixe o estado
- O botão ou a chave está sendo substituído. Por exemplo, a ação realizada quando o botão
é pressionado ou o interruptor é acionado pode acionar uma atualização das ações disponíveis,
fazendo com que novos botões substituam os botões atuais. Há duas maneiras de resolver isso:
- Em vez de criar um novo botão ou interruptor, defina o ícone e/ou o texto do item atual.
- Como acima, adicione um contêiner com foco ao redor do botão ou do interruptor.
RotaryPlayground
RotaryPlayground
é um app de referência para seletor giratório. Use-o para aprender a integrar
recursos rotativos aos seus apps. RotaryPlayground
está incluído em builds de emulador e em
builds 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 app RotaryPlayground
mostra as seguintes guias à esquerda:
- Cards. Teste a navegação pelas áreas de foco, pulando elementos não focados e entradas de texto.
- Manipulação direta. Teste widgets que oferecem suporte ao modo de manipulação direta simples e avançada. Essa guia é específica para manipulação direta na janela do app.
- Manipulação da interface do Sys. Teste widgets que oferecem suporte à manipulação direta em janelas do sistema em que apenas o modo de manipulação direta simples é aceito.
- Grade. Teste a navegação por seletor giratório com rolagem.
- Notificação. Teste a ativação e desativação das notificações de informações básicas.
- Role a tela. Teste a rolagem com uma combinação de conteúdo com e sem foco.
- WebView. Teste a navegação pelos links em um
WebView
. FocusArea
personalizado. Teste a personalizaçãoFocusArea
:- Wrap-around.
android:focusedByDefault
eapp:defaultFocus
.
- Alvos de estímulo explícito.
- Atalhos de lembretes.
FocusArea
sem visualizações focalizáveis.