O material a seguir é destinado a desenvolvedores de apps.
Para que o app ofereça suporte a controles rotativos, é PRECISO:
- Coloque uma
FocusParkingViewno layout da atividade correspondente. - Verifique se as visualizações podem ou não ser focalizadas.
- Use
FocusAreas 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
userdebugoueng. - 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 FocusAreas. 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
FocusParkingViewa 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 queRotaryServicepossa estacionar o foco nele para remover o destaque de foco. - Se houver apenas um
FocusAreana janela atual, girar o controle noFocusAreafará com queRotaryServicemova 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. QuandoRotaryServicedetermina 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. OFocusParkingViewdetermina 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 Buttons,
geralmente podem ser focalizadas. Outros, como TextViews e ViewGroups,
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
mRotaryEnabledforfalse. - Para fazer com que o botão apareça desativado, no drawable de plano de fundo da sua visualização, use
app:state_rotary_enabledem 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,
RotaryServiceprocura instâncias deFocusAreana hierarquia de visualização. - Ao receber um evento de rotação,
RotaryServicemove o foco para outra visualização que pode receber o foco no mesmoFocusArea. - Ao receber um evento de lembrete, o
RotaryServicemove 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:nextFocusForwardpermite 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:focusedByDefaultpermite 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:defaultFocuspode 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:defaultFocusOverridesHistorypode ser definido comotruepara que a visualização especificada acima receba o foco, mesmo que com histórico para indicar que outra visualização emFocusAreafoi focada. - (Android 12)
Useapp:nudgeLeftShortcut,app:nudgeRightShortcut,app:nudgeUpShortcuteapp:nudgeDownShortcutpara 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:nudgeShortcuteapp:nudgeShortcutDirectiontinham 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:wrapAroundpode 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:highlightPaddingHorizontaleapp:highlightPaddingVertical. - (Android 11 QPR3, Android 11 Car,
Android 12)
Para ajustar os limites percebidos dessaFocusAreae encontrar um alvo de empurrão, useapp:startBoundOffset,app:endBoundOffset,app:topBoundOffset,app:bottomBoundOffset,app:horizontalBoundOffseteapp:verticalBoundOffset. - (Android 11 QPR3, Android 11 Car,
Android 12)
Para especificar explicitamente o ID de umFocusAreaadjacente (ou áreas) nas direções fornecidas, useapp:nudgeLeft,app:nudgeRight,app:nudgeUpeapp: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 RecyclerViews, use
CarUiRecyclerViews. Isso garante que sua interface seja consistente com
outras, porque a personalização de um OEM é aplicada a todos os CarUiRecyclerViews.
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_ENCODEReAXIS_VSCROLLouAXIS_HSCROLLpara 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 AccessibilityEvents 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_CENTERpara entrar no modo DM e detectar um eventoKEYCODE_BACKpara 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_LEFTouKEYCODE_DPAD_RIGHT) se a visualização precisar processar alertas. - DEVE detectar
MotionEvents e receber a contagem de rotações emAXIS_SCROLLse 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
ActivityViewnão pode ser focado. - (Android 11 QPR3, Android 11 Car,
descontinuado no Android 11)
O conteúdo daActivityViewPRECISA conter umaFocusParkingViewcomo a primeira visualização com foco, e o atributoapp:shouldRestoreFocusPRECISA serfalse. - O conteúdo do
ActivityViewnã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:enabledcomotruee 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. FocusAreapersonalizado. Teste a personalizaçãoFocusArea:- Wrap-around.
android:focusedByDefaulteapp:defaultFocus.
- Alvos de estímulo explícito.
- Atalhos de lembretes.
FocusAreasem visualizações focalizáveis.


