O material a seguir é para desenvolvedores de aplicativos.
Para tornar o suporte do seu aplicativo rotativo, você DEVE:
- Coloque um
FocusParkingView
no respectivo layout de atividade. - Garanta que as visualizações sejam (ou não) focáveis.
- Use
FocusArea
s para envolver todas as suas visualizações focáveis, excetoFocusParkingView
.
Cada uma dessas tarefas é detalhada abaixo, depois de configurar seu ambiente para desenvolver aplicativos habilitados para rotação.
Configure um controlador rotativo
Antes de começar a desenvolver aplicativos habilitados para rotação, você precisa de um controlador rotativo ou de um substituto. Você tem as opções descritas abaixo.
Emulador
source build/envsetup.sh && lunch car_x86_64-userdebug m -j emulator -wipe-data -no-snapshot -writable-system
Você também pode usar aosp_car_x86_64-userdebug
.
Para acessar o controlador rotativo emulado:
- Toque nos três pontos na parte inferior da barra de ferramentas:
- Selecione Rotativo do carro na janela de controles estendidos:
Teclado USB
- Conecte um teclado USB ao seu dispositivo que executa o Android Automotive OS (AAOS). Em alguns casos, isso impede que o teclado na tela apareça.
- Use um
userdebug
ou uma compilaçãoeng
. - Habilite 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 para cada ação:
Chave Ação rotativa P Girar no sentido anti-horário E Rode no sentido dos ponteiros do relógio A Deslocar para a esquerda D Deslocar para a direita C Empurre para cima S Empurre para baixo F ou Vírgula Botão central R ou Esc Botão "voltar
Comandos ADB
Você pode usar comandos car_service
para injetar eventos de entrada rotativa. Esses comandos podem ser executados em dispositivos que executam o Android Automotive OS (AAOS) ou em um emulador.
comandos car_service | Entrada rotativa |
---|---|
adb shell cmd car_service inject-rotary | Girar no sentido anti-horário |
adb shell cmd car_service inject-rotary -c true | Rode no sentido dos ponteiros do relógio |
adb shell cmd car_service inject-rotary -dt 100 50 | Gire no sentido anti-horário várias vezes (100 ms atrás e 50 ms atrás) |
adb shell cmd car_service inject-key 282 | Deslocar para a esquerda |
adb shell cmd car_service inject-key 283 | Deslocar para a direita |
adb shell cmd car_service inject-key 280 | Empurre para cima |
adb shell cmd car_service inject-key 281 | Empurre para baixo |
adb shell cmd car_service inject-key 23 | Clique no botão central |
adb shell input keyevent inject-key 4 | Clique no botão Voltar |
Controlador rotativo OEM
Quando o hardware do controlador rotativo estiver instalado e funcionando, esta é a opção mais realista. É particularmente útil para testar rotação rápida.
FocusParkingView
FocusParkingView
é uma visualização transparente na Car UI Library (car-ui-library) . RotaryService
o utiliza para oferecer suporte à navegação do controlador rotativo. FocusParkingView
deve ser a primeira visualização focável no layout. Deve ser colocado fora de todos FocusArea
s. Cada janela deve ter um FocusParkingView
. Se você já estiver usando o layout base car-ui-library, que contém um FocusParkingView
, não será necessário adicionar outro FocusParkingView
. Abaixo é mostrado um exemplo de FocusParkingView
no RotaryPlayground
.
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <com.android.car.ui.FocusParkingView android:layout_width="wrap_content" android:layout_height="wrap_content"/> <FrameLayout android:layout_width="match_parent" android:layout_height="match_parent"/> </FrameLayout>
Aqui estão os motivos pelos quais você precisa de um FocusParkingView
:
- O Android não limpa o foco automaticamente quando o foco é definido em outra janela. Se você tentar limpar o foco na janela anterior, o Android redirecionará uma visualização nessa janela, o que resultará no foco simultâneo de duas janelas. Adicionar um
FocusParkingView
a cada janela pode resolver esse problema. Esta visualização é transparente e seu realce de foco padrão está desabilitado, de modo que fica invisível para o usuário, independentemente de estar focado ou não. Ele pode receber foco para queRotaryService
possa estacionar o foco nele para remover o destaque do foco. - Se houver apenas uma
FocusArea
na janela atual, girar o controlador naFocusArea
fará com queRotaryService
mova o foco da visualização à direita para a visualização à esquerda (e vice-versa). Adicionar esta visualização a cada janela pode resolver o problema. QuandoRotaryService
determina que o alvo do foco é umFocusParkingView
, ele pode determinar que um contorno está prestes a ocorrer e nesse ponto ele evita o contorno ao não mover o foco. - Quando o controle rotativo inicia um aplicativo, o Android foca a primeira visualização focável, que é sempre
FocusParkingView
. OFocusParkingView
determina a visualização ideal para focar e então aplica o foco.
Visualizações focáveis
RotaryService
se baseia no conceito existente de foco de visualização da estrutura Android, que remonta à época em que os telefones tinham teclados físicos e D-pads. O atributo android:nextFocusForward
existente é reaproveitado para rotativo (consulte Personalização FocusArea ), mas android:nextFocusLeft
, android:nextFocusRight
, android:nextFocusUp
e android:nextFocusDown
não são.
RotaryService
se concentra apenas nas opiniões que podem ser focadas. Algumas visualizações, como Button
s, geralmente são focáveis. Outros, como TextView
se ViewGroup
s, geralmente não são. As visualizações clicáveis são automaticamente focalizáveis e as visualizações são automaticamente clicáveis quando possuem um ouvinte de clique. Se essa lógica automática resultar na capacidade de foco desejada, você não precisará definir explicitamente a capacidade de foco da 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 programaticamente a capacidade de foco da visualização com View.setFocusable(boolean)
. Para que RotaryService
se concentre nisso, uma visão DEVE atender aos seguintes requisitos:
- Focável
- Habilitado
- Visível
- Tenha valores diferentes de zero para largura e altura
Se uma visualização não atender a todos esses requisitos, por exemplo, um botão focável, mas desabilitado, o usuário não poderá usar o controle giratório para focar nela. Se você quiser focar nas visualizações desativadas, considere usar um estado personalizado em vez de android:state_enabled
para controlar como a visualização aparece sem indicar que o Android deve considerá-la desativada. Seu aplicativo pode informar ao usuário por que a visualização está desativada quando tocada. A próxima seção explica como fazer isso.
Estado personalizado
Para adicionar um estado personalizado:
- Para adicionar um atributo personalizado à sua 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 rastrear esse estado, adicione uma variável de instância à sua visualização junto com os métodos acessadores:
private boolean mRotaryEnabled; public boolean getRotaryEnabled() { return mRotaryEnabled; } public void setRotaryEnabled(boolean rotaryEnabled) { mRotaryEnabled = rotaryEnabled; }
- Para ler o valor do seu atributo quando sua visualização for criada:
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomView); mRotaryEnabled = a.getBoolean(R.styleable.CustomView_state_rotary_enabled);
- Na sua classe de visualização, substitua o método
onCreateDrawableState()
e adicione o estado personalizado, quando apropriado. Por exemplo:@Override protected int[] onCreateDrawableState(int extraSpace) { if (mRotaryEnabled) extraSpace++; int[] drawableState = super.onCreateDrawableState(extraSpace); if (mRotaryEnabled) { mergeDrawableStates(drawableState, { R.attr.state_rotary_enabled }); } return drawableState; }
- Faça com que o manipulador de cliques da sua visualização tenha um desempenho diferente dependendo do seu estado. Por exemplo, o manipulador de cliques pode não fazer nada ou pode exibir um brinde quando
mRotaryEnabled
forfalse
. - Para fazer o botão parecer desabilitado, no drawable de fundo da sua visualização, use
app:state_rotary_enabled
em vez deandroid:state_enabled
. Se ainda não o tiver, você precisará adicionar:xmlns:app="http://schemas.android.com/apk/res-auto"
- Se sua visualização estiver desabilitada em algum layout, substitua
android:enabled="false"
porapp:state_rotary_enabled="false"
e adicione o namespaceapp
, como acima. - Se sua visualização estiver desabilitada programaticamente, substitua as chamadas para
setEnabled()
por chamadas parasetRotaryEnabled()
.
Área de foco
Use FocusAreas
para particionar as visualizações focáveis em blocos para facilitar a navegação e ser consistente com outros aplicativos. Por exemplo, se seu aplicativo tiver uma barra de ferramentas, ela deverá estar em uma FocusArea
separada do restante do aplicativo. Barras de guias e outros elementos de navegação também devem ser separados do restante do aplicativo. Listas grandes geralmente devem ter seu próprio FocusArea
. Caso contrário, os usuários deverão percorrer toda a lista para acessar algumas visualizações.
FocusArea
é uma subclasse de LinearLayout
na car-ui-library. Quando esse recurso está habilitado, FocusArea
desenha um destaque quando um de seus descendentes está em foco. Para saber mais, consulte Personalização de destaque de foco .
Ao criar um bloco de navegação no arquivo de layout, se você pretende usar um LinearLayout
como contêiner para esse bloco, use FocusArea
. Caso contrário, envolva o bloco em FocusArea
.
NÃO aninhe um FocusArea
em outro FocusArea
. Fazer isso leva a um comportamento de navegação indefinido. Certifique-se de que todas as visualizações focáveis estejam aninhadas em uma FocusArea
.
Um exemplo de FocusArea
no RotaryPlayground
é mostrado abaixo:
<com.android.car.ui.FocusArea android:layout_margin="16dp" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:singleLine="true"> </EditText> </com.android.car.ui.FocusArea>
FocusArea
funciona da seguinte maneira:
- Ao lidar com ações de rotação e deslocamento,
RotaryService
procura instâncias deFocusArea
na hierarquia de visualizações. - Ao receber um evento de rotação,
RotaryService
move o foco para outra View que pode ter foco na mesmaFocusArea
. - Ao receber um evento de nudge,
RotaryService
move o foco para outra visualização que pode receber o foco em outraFocusArea
(normalmente adjacente).
Se você não incluir nenhuma FocusAreas
em seu layout, a visualização raiz será tratada como uma área de foco implícita. O usuário não pode empurrar para navegar no aplicativo. Em vez disso, eles girarão por todas as visualizações focáveis, o que pode ser adequado para diálogos.
Personalização da FocusArea
Dois atributos de visualização padrão podem ser usados para personalizar a navegação rotativa:
-
android:nextFocusForward
permite que os desenvolvedores de aplicativos especifiquem a ordem de rotação em uma área de foco. Este é o mesmo atributo usado para controlar a ordem de tabulação para navegação pelo teclado. NÃO use este atributo para criar um loop. Em vez disso, useapp:wrapAround
(veja abaixo) para criar um loop. -
android:focusedByDefault
permite que os desenvolvedores de aplicativos especifiquem a visualização de foco padrão na janela. NÃO use este atributo eapp:defaultFocus
(veja abaixo) na mesmaFocusArea
.
FocusArea
também define alguns atributos para personalizar a navegação rotativa. As áreas de foco implícitas não podem ser customizadas com esses atributos.
- ( Android 11 QPR3, carro Android 11, Android 12 )
app:defaultFocus
pode ser usado para especificar o ID de uma visualização descendente focável, que deve ser focada quando o usuário avança para estaFocusArea
. - ( Android 11 QPR3, carro Android 11, Android 12 )
app:defaultFocusOverridesHistory
pode ser definido comotrue
para fazer com que a visualização especificada acima fique em foco, mesmo que o histórico indique que outra visualização nestaFocusArea
foi focada. - ( Android 12 )
Useapp:nudgeLeftShortcut
,app:nudgeRightShortcut
,app:nudgeUpShortcut
eapp:nudgeDownShortcut
para especificar o ID de uma visualização descendente focável, que deve ser focada quando o usuário move em uma determinada direção. Para saber mais, consulte o conteúdo dos atalhos de deslocamento abaixo.( Android 11 QPR3, Android 11 Car, obsoleto no Android 12 )
app:nudgeShortcut
eapp:nudgeShortcutDirection
suportavam apenas um atalho de deslocamento. - ( Android 11 QPR3, carro Android 11, Android 12 )
Para permitir que a rotação seja contornada nesteFocusArea
,app:wrapAround
pode ser definido comotrue
. Isso é mais comumente usado quando as visualizações são organizadas em círculo ou oval. - ( Android 11 QPR3, carro Android 11, Android 12 )
Para ajustar o preenchimento do destaque nesteFocusArea
, useapp:highlightPaddingStart
,app:highlightPaddingEnd
,app:highlightPaddingTop
,app:highlightPaddingBottom
,app:highlightPaddingHorizontal
eapp:highlightPaddingVertical
. - ( Android 11 QPR3, carro Android 11, Android 12 )
Para ajustar os limites percebidos desteFocusArea
para encontrar um alvo de deslocamento, useapp:startBoundOffset
,app:endBoundOffset
,app:topBoundOffset
,app:bottomBoundOffset
,app:horizontalBoundOffset
eapp:verticalBoundOffset
. - ( Android 11 QPR3, carro Android 11, Android 12 )
Para especificar explicitamente o ID de umaFocusArea
(ou áreas) adjacente nas direções fornecidas, useapp:nudgeLeft
,app:nudgeRight
,app:nudgeUp
eapp:nudgeDown
. Use isto quando a pesquisa geométrica usada por padrão não encontrar o alvo desejado.
O cutucão geralmente navega entre FocusAreas. Mas com atalhos de deslocamento, às vezes o deslocamento navega primeiro dentro de um FocusArea
para que o usuário possa precisar mover duas vezes para navegar para o próximo FocusArea
. Os atalhos de deslocamento são úteis quando um FocusArea
contém uma longa lista seguida por um Floating Action Button , como no exemplo abaixo:
Sem o atalho de deslocamento, o usuário teria que percorrer toda a lista para chegar ao FAB.
Personalização de destaque de foco
Conforme mencionado acima, RotaryService
baseia-se no conceito existente de foco de visualização da estrutura Android. Quando o usuário gira e empurra, RotaryService
move o foco, focando uma visualização e desfocando outra. No Android, quando uma visualização está em foco, se a visualização:
- Especificou seu 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 desenha o destaque de foco padrão para a visualização.
Os aplicativos projetados para toque geralmente não especificam os destaques de foco apropriados.
O destaque de foco padrão é fornecido pela estrutura do Android e pode ser substituído pelo OEM. Os desenvolvedores de aplicativos o recebem quando o tema que estão usando é derivado de Theme.DeviceDefault
.
Para uma experiência de usuário consistente, conte com o destaque de foco padrão sempre que possível. Se você precisar de um realce de foco com formato personalizado (por exemplo, redondo ou em forma de pílula), ou se estiver usando um tema não derivado de Theme.DeviceDefault
, use os recursos car-ui-library para especificar seu próprio realce de foco para cada visualização.
Para especificar um realce de foco personalizado para uma visualização, altere o drawable de fundo ou de primeiro plano da visualização para um drawable que seja diferente quando a visualização estiver focada. Normalmente, você mudaria 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 os recursos definidos pela car-ui-library. O OEM os substitui para serem consistentes com o destaque de foco padrão especificado. Isso garante que a cor de realce do foco, a largura do traço e assim por diante não sejam alteradas quando o usuário navegar entre uma visualização com realce de foco personalizado e uma visualização com realce de foco padrão. O último item é uma ondulação usada para toque. Os valores padrão usados para os recursos em negrito aparecem como segue:
Além disso, um destaque de foco personalizado é necessário quando um botão recebe uma cor de fundo sólida para chamar a atenção do usuário, como no exemplo abaixo. Isso pode dificultar a visualização do realce do foco. Nessa situação, especifique um realce de foco personalizado usando cores secundárias :
- ( Android 11 QPR3, carro Android 11, 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
Por exemplo:
Focado, não pressionado | Focado, pressionado |
Rolagem rotativa
Se seu aplicativo usa RecyclerView
s, você DEVE usar CarUiRecyclerView
s. Isso garante que sua UI seja consistente com outras porque a personalização de um OEM se aplica a todos os CarUiRecyclerView
s.
Se todos os elementos da sua lista puderem ser focados, você não precisará fazer mais nada. A navegação rotativa move o foco pelos elementos da lista e a lista rola para tornar visível o elemento recém-focado.
( Android 11 QPR3, carro Android 11, Android 12 )
Se houver uma mistura de elementos focalizáveis e não focalizáveis, ou se todos os elementos estiverem fora de foco, você poderá ativar a rolagem rotativa, que permite ao usuário usar o controlador giratório para rolar gradualmente pela lista sem pular os itens não focalizáveis. Para ativar a rolagem rotativa, defina o atributo app:rotaryScrollEnabled
como true
.
( Android 11 QPR3, carro Android 11, Android 12 )
Você pode ativar a rolagem rotativa em qualquer visualização rolável, incluindo av CarUiRecyclerView
, com o método setRotaryScrollEnabled()
em CarUiUtils
. Se você fizer isso, precisará:
- Torne a visualização rolável focável para que ela possa ser focada quando nenhuma de suas visualizações descendentes focáveis estiver visível,
- Desative 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 focada, - Certifique-se de que a visualização rolável esteja focada antes de seus descendentes chamando
setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS)
. - Ouça MotionEvents com
SOURCE_ROTARY_ENCODER
eAXIS_VSCROLL
ouAXIS_HSCROLL
para indicar a distância a ser rolada e a direção (através do sinal).
Quando a rolagem rotativa está habilitada em um CarUiRecyclerView
e o usuário gira para uma área onde não há visualizações focáveis, 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 desejar.
Os MotionEvents são iguais aos gerados por uma roda de rolagem de um mouse, exceto pela fonte.
Modo de manipulação direta
Normalmente, os toques e a rotação navegam pela interface do usuário, enquanto os pressionamentos do botão central executam uma ação, embora nem sempre seja o caso. Por exemplo, se um usuário quiser ajustar o volume do alarme, ele poderá usar o controlador giratório para navegar até o controle deslizante de volume, pressionar o botão central, girar o controlador para ajustar o volume do alarme e, em seguida, pressionar o botão Voltar para retornar à navegação . Isso é conhecido como modo de manipulação direta (DM) . Neste modo, o controlador rotativo é usado para interagir diretamente com a visualização, em vez de navegar.
Implemente o DM de duas maneiras. Se você precisar apenas manipular a rotação e a visualização que deseja manipular responder a ACTION_SCROLL_FORWARD
e ACTION_SCROLL_BACKWARD
AccessibilityEvent
s adequadamente, use o mecanismo simples . Caso contrário, use o mecanismo avançado .
O mecanismo simples é a única opção nas janelas do sistema; os aplicativos podem usar qualquer um dos mecanismos.
Mecanismo simples
( Android 11 QPR3, carro Android 11, Android 12 )
Seu aplicativo deve chamar DirectManipulationHelper.setSupportsRotateDirectly(View view, boolean enable)
. RotaryService
reconhece quando o usuário está no modo DM e entra no modo DM quando o usuário pressiona o botão central enquanto uma visualização está focada. Quando no modo DM, as rotações executam ACTION_SCROLL_FORWARD
ou ACTION_SCROLL_BACKWARD
e sai do modo DM quando o usuário pressiona o botão Voltar. O mecanismo simples alterna o estado selecionado da visualização ao entrar e sair do modo DM.
Para fornecer uma indicação visual de que o usuário está no modo DM, faça com que sua visualização pareça diferente quando selecionada. Por exemplo, altere o plano de fundo quando android:state_selected
for true
.
Mecanismo avançado
O aplicativo determina quando RotaryService
entra e sai do modo DM. Para uma experiência de usuário consistente, pressionar o botão Central com uma visualização DM focada deve entrar no modo DM e o botão Voltar deve sair do modo DM. Se o botão central e/ou o empurrão não forem usados, eles podem ser formas alternativas de sair do modo DM. Para aplicativos como o Maps, um botão para representar o DM pode ser usado para entrar no modo DM.
Para oferecer suporte ao modo DM avançado, uma visualização:
- ( Android 11 QPR3, Android 11 Car, Android 12 ) DEVE ouvir um evento
KEYCODE_DPAD_CENTER
para entrar no modo DM e ouvir um eventoKEYCODE_BACK
para sair do modo DM, chamandoDirectManipulationHelper.enableDirectManipulationMode()
em cada caso. Para ouvir esses eventos, siga um destes procedimentos:- Registre um
OnKeyListener
. ou, - Estenda a visualização e substitua seu método
dispatchKeyEvent()
.
- Registre um
- DEVE ouvir eventos de nudge (
KEYCODE_DPAD_UP
,KEYCODE_DPAD_DOWN
,KEYCODE_DPAD_LEFT
ouKEYCODE_DPAD_RIGHT
) se a visualização deve lidar com nudges. - DEVE ouvir
MotionEvent
se obter a contagem de rotação emAXIS_SCROLL
se a visualização quiser lidar com a rotação. Existem várias maneiras de fazer isso:- Registre um
OnGenericMotionListener
. - Estenda a visualização e substitua seu método
dispatchTouchEvent()
.
- Registre um
- Para evitar ficar preso no modo DM, DEVE sair do modo DM quando o Fragmento ou Atividade ao qual a visualização pertence não for interativo.
- DEVE fornecer uma dica visual para indicar que a visualização está no modo DM.
Um exemplo de visualização personalizada que usa o modo DM para deslocar e ampliar 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 no projeto RotaryPlayground
.
ActivityView
Ao usar um ActivityView:
- O
ActivityView
não deve ser focalizável. - ( Android 11 QPR3, Android 11 Car, obsoleto no Android 11 )
O conteúdo doActivityView
DEVE conter umFocusParkingView
como a primeira visualização focável e seu atributoapp:shouldRestoreFocus
DEVE serfalse
. - O conteúdo do
ActivityView
não deve ter visualizaçõesandroid:focusByDefault
.
Para o usuário, ActivityViews não devem ter efeito na navegação, exceto que as áreas de foco não podem abranger ActivityViews. Em outras palavras, você não pode ter uma única área de foco com conteúdo dentro e fora de ActivityView
. Se você não adicionar FocusAreas ao seu 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 pressionados. Por exemplo, os botões Avançar e Retroceder normalmente funcionam quando mantidos pressionados. Para fazer com que esses botões suportem rotação, ouça KEYCODE_DPAD_CENTER
KeyEvents
da seguinte maneira:
mButton.setOnKeyListener((v, keyCode, event) -> { if (keyCode != KEYCODE_DPAD_CENTER) { return false; } if (event.getAction() == ACTION_DOWN) { mButton.setPressed(true); mHandler.post(mRunnable); } else { mButton.setPressed(false); mHandler.removeCallbacks(mRunnable); } return true; });
Em que mRunnable
executa uma ação (como retroceder) e se programa para ser executado após um atraso.
Modo de toque
Os usuários podem usar um controlador rotativo para interagir com a unidade principal de um carro de duas maneiras: usando o controlador rotativo ou tocando na tela. Ao usar o controlador rotativo, uma das visualizações focáveis é destacada. Ao tocar na tela, nenhum destaque de foco aparece. O usuário pode alternar entre estes modos de entrada a qualquer momento:
- Girar → tocar. Quando o usuário toca na tela, o destaque do foco desaparece.
- Toque em → giratório. 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 pega carona no conceito existente de modo de toque do Android" . Você pode usar View.isInTouchMode()
para determinar qual modo de entrada o usuário está usando. Você pode usar OnTouchModeChangeListener
para ouvir alterações. Embora isso possa ser usado para personalizar sua interface de usuário para o modo de entrada atual, evite quaisquer alterações importantes, pois elas podem ser desconcertantes.
Solução de problemas
Em um aplicativo projetado para toque, é comum ter visualizações aninhadas e focáveis. Por exemplo, pode haver um FrameLayout
em torno de um ImageButton
, ambos focáveis. Isso não prejudica o toque, mas pode resultar em uma experiência de usuário ruim para rotação porque o usuário deve girar o controlador 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 visão externa ou a interna focável, mas não ambas.
Se um botão ou interruptor perder o foco quando pressionado através do controlador rotativo, uma destas condições poderá ser aplicada:
- O botão ou interruptor está sendo desativado (brevemente ou indefinidamente) devido ao botão ser pressionado. Em ambos os casos, existem duas maneiras de resolver isso:
- Deixe o estado
android:enabled
comotrue
e use um estado personalizado para esmaecer o botão ou alternância conforme descrito em Estado personalizado . - Use um contêiner para cercar o botão ou switch e torne o contêiner focável em vez do botão ou switch. (O ouvinte de clique deve estar no contêiner.)
- Deixe o estado
- O botão ou interruptor está sendo substituído. Por exemplo, a ação realizada quando o botão é pressionado ou o interruptor é alternado pode desencadear uma atualização das ações disponíveis, fazendo com que novos botões substituam os botões existentes. Existem duas maneiras de resolver isso:
- Em vez de criar um novo botão ou switch, defina o ícone e/ou texto do botão ou switch existente.
- Como acima, adicione um contêiner focalizável ao redor do botão ou switch.
Rotary Playground
RotaryPlayground
é um aplicativo de referência para rotativo. Use-o para aprender como integrar recursos rotativos em seus aplicativos. RotaryPlayground
está incluído em versões de emulador e em versões para dispositivos que executam o Android Automotive OS (AAOS).
- Repositório
RotaryPlayground
:packages/apps/Car/tests/RotaryPlayground/
- Versões: Android 11 QPR3, Android 11 Car e Android 12
O aplicativo RotaryPlayground
mostra as seguintes guias à esquerda:
- Cartões. Teste a navegação pelas áreas de foco, ignorando elementos fora de foco e entrada de texto.
- Manipulação direta. Teste widgets que suportam modo de manipulação direta simples e avançado. Esta guia é especificamente para manipulação direta na janela do aplicativo.
- Manipulação da UI do sistema. Teste widgets que suportam manipulação direta em janelas do sistema onde apenas o modo de manipulação direta simples é suportado.
- Grade. Teste a navegação rotativa em padrão z com rolagem.
- Notificação. Teste a entrada e saída de notificações de alerta.
- Rolagem. Teste a rolagem por uma mistura de conteúdo focalizável e não focalizável.
- WebView. Teste a navegação por links em um
WebView
. -
FocusArea
personalizada. Teste a personalizaçãoFocusArea
:- Envolver em torno.
-
android:focusedByDefault
eapp:defaultFocus
. - Alvos de cutucada explícitos.
- Deslize os atalhos.
-
FocusArea
sem visualizações focáveis.
O material a seguir é para desenvolvedores de aplicativos.
Para tornar o suporte do seu aplicativo rotativo, você DEVE:
- Coloque um
FocusParkingView
no respectivo layout de atividade. - Garanta que as visualizações sejam (ou não) focáveis.
- Use
FocusArea
s para envolver todas as suas visualizações focáveis, excetoFocusParkingView
.
Cada uma dessas tarefas é detalhada abaixo, depois de configurar seu ambiente para desenvolver aplicativos habilitados para rotação.
Configure um controlador rotativo
Antes de começar a desenvolver aplicativos habilitados para rotação, você precisa de um controlador rotativo ou de um substituto. Você tem as opções descritas abaixo.
Emulador
source build/envsetup.sh && lunch car_x86_64-userdebug m -j emulator -wipe-data -no-snapshot -writable-system
Você também pode usar aosp_car_x86_64-userdebug
.
Para acessar o controlador rotativo emulado:
- Toque nos três pontos na parte inferior da barra de ferramentas:
- Selecione Rotativo do carro na janela de controles estendidos:
Teclado USB
- Conecte um teclado USB ao seu dispositivo que executa o Android Automotive OS (AAOS). Em alguns casos, isso impede que o teclado na tela apareça.
- Use um
userdebug
ou uma compilaçãoeng
. - Habilite 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 para cada ação:
Chave Ação rotativa P Girar no sentido anti-horário E Rode no sentido dos ponteiros do relógio A Deslocar para a esquerda D Deslocar para a direita C Empurre para cima S Empurre para baixo F ou Vírgula Botão central R ou Esc Botão "voltar
Comandos ADB
Você pode usar comandos car_service
para injetar eventos de entrada rotativa. Esses comandos podem ser executados em dispositivos que executam o Android Automotive OS (AAOS) ou em um emulador.
comandos car_service | Entrada rotativa |
---|---|
adb shell cmd car_service inject-rotary | Girar no sentido anti-horário |
adb shell cmd car_service inject-rotary -c true | Rode no sentido dos ponteiros do relógio |
adb shell cmd car_service inject-rotary -dt 100 50 | Gire no sentido anti-horário várias vezes (100 ms atrás e 50 ms atrás) |
adb shell cmd car_service inject-key 282 | Deslocar para a esquerda |
adb shell cmd car_service inject-key 283 | Deslocar para a direita |
adb shell cmd car_service inject-key 280 | Empurre para cima |
adb shell cmd car_service inject-key 281 | Empurre para baixo |
adb shell cmd car_service inject-key 23 | Clique no botão central |
adb shell input keyevent inject-key 4 | Clique no botão Voltar |
Controlador rotativo OEM
Quando o hardware do controlador rotativo estiver instalado e funcionando, esta é a opção mais realista. É particularmente útil para testar rotação rápida.
FocusParkingView
FocusParkingView
é uma visualização transparente na Car UI Library (car-ui-library) . RotaryService
o utiliza para oferecer suporte à navegação do controlador rotativo. FocusParkingView
deve ser a primeira visualização focável no layout. Deve ser colocado fora de todos FocusArea
s. Cada janela deve ter um FocusParkingView
. Se você já estiver usando o layout base car-ui-library, que contém um FocusParkingView
, não será necessário adicionar outro FocusParkingView
. Abaixo é mostrado um exemplo de FocusParkingView
no RotaryPlayground
.
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <com.android.car.ui.FocusParkingView android:layout_width="wrap_content" android:layout_height="wrap_content"/> <FrameLayout android:layout_width="match_parent" android:layout_height="match_parent"/> </FrameLayout>
Aqui estão os motivos pelos quais você precisa de um FocusParkingView
:
- O Android não limpa o foco automaticamente quando o foco é definido em outra janela. Se você tentar limpar o foco na janela anterior, o Android redirecionará uma visualização nessa janela, o que resultará no foco simultâneo de duas janelas. Adicionar um
FocusParkingView
a cada janela pode resolver esse problema. Esta visualização é transparente e seu realce de foco padrão está desabilitado, de modo que fica invisível para o usuário, independentemente de estar focado ou não. Ele pode receber foco para queRotaryService
possa estacionar o foco nele para remover o destaque do foco. - Se houver apenas uma
FocusArea
na janela atual, girar o controlador naFocusArea
fará com queRotaryService
mova o foco da visualização à direita para a visualização à esquerda (e vice-versa). Adicionar esta visualização a cada janela pode resolver o problema. QuandoRotaryService
determina que o alvo do foco é umFocusParkingView
, ele pode determinar que um contorno está prestes a ocorrer e nesse ponto ele evita o contorno ao não mover o foco. - Quando o controle rotativo inicia um aplicativo, o Android foca a primeira visualização focável, que é sempre
FocusParkingView
. OFocusParkingView
determina a visualização ideal para focar e então aplica o foco.
Visualizações focáveis
RotaryService
se baseia no conceito existente de foco de visualização da estrutura Android, que remonta à época em que os telefones tinham teclados físicos e D-pads. O atributo android:nextFocusForward
existente é reaproveitado para rotativo (consulte Personalização FocusArea ), mas android:nextFocusLeft
, android:nextFocusRight
, android:nextFocusUp
e android:nextFocusDown
não são.
RotaryService
se concentra apenas nas opiniões que podem ser focadas. Algumas visualizações, como Button
s, geralmente são focáveis. Outros, como TextView
se ViewGroup
s, geralmente não são. As visualizações clicáveis são automaticamente focalizáveis e as visualizações são automaticamente clicáveis quando possuem um ouvinte de clique. Se essa lógica automática resultar na capacidade de foco desejada, você não precisará definir explicitamente a capacidade de foco da 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 programaticamente a capacidade de foco da visualização com View.setFocusable(boolean)
. Para que RotaryService
se concentre nisso, uma visão DEVE atender aos seguintes requisitos:
- Focável
- Habilitado
- Visível
- Tenha valores diferentes de zero para largura e altura
Se uma visualização não atender a todos esses requisitos, por exemplo, um botão focável, mas desabilitado, o usuário não poderá usar o controle giratório para focar nela. Se você quiser focar nas visualizações desativadas, considere usar um estado personalizado em vez de android:state_enabled
para controlar como a visualização aparece sem indicar que o Android deve considerá-la desativada. Seu aplicativo pode informar ao usuário por que a visualização está desativada quando tocada. A próxima seção explica como fazer isso.
Estado personalizado
Para adicionar um estado personalizado:
- Para adicionar um atributo personalizado à sua 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 rastrear esse estado, adicione uma variável de instância à sua visualização junto com os métodos acessadores:
private boolean mRotaryEnabled; public boolean getRotaryEnabled() { return mRotaryEnabled; } public void setRotaryEnabled(boolean rotaryEnabled) { mRotaryEnabled = rotaryEnabled; }
- Para ler o valor do seu atributo quando sua visualização for criada:
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomView); mRotaryEnabled = a.getBoolean(R.styleable.CustomView_state_rotary_enabled);
- Na sua classe de visualização, substitua o método
onCreateDrawableState()
e adicione o estado personalizado, quando apropriado. Por exemplo:@Override protected int[] onCreateDrawableState(int extraSpace) { if (mRotaryEnabled) extraSpace++; int[] drawableState = super.onCreateDrawableState(extraSpace); if (mRotaryEnabled) { mergeDrawableStates(drawableState, { R.attr.state_rotary_enabled }); } return drawableState; }
- Faça com que o manipulador de cliques da sua visualização tenha um desempenho diferente dependendo do seu estado. Por exemplo, o manipulador de cliques pode não fazer nada ou pode exibir um brinde quando
mRotaryEnabled
forfalse
. - Para fazer o botão parecer desabilitado, no drawable de fundo da sua visualização, use
app:state_rotary_enabled
em vez deandroid:state_enabled
. Se ainda não o tiver, você precisará adicionar:xmlns:app="http://schemas.android.com/apk/res-auto"
- Se sua visualização estiver desabilitada em algum layout, substitua
android:enabled="false"
porapp:state_rotary_enabled="false"
e adicione o namespaceapp
, como acima. - Se sua visualização estiver desabilitada programaticamente, substitua as chamadas para
setEnabled()
por chamadas parasetRotaryEnabled()
.
Área de foco
Use FocusAreas
para particionar as visualizações focáveis em blocos para facilitar a navegação e ser consistente com outros aplicativos. Por exemplo, se seu aplicativo tiver uma barra de ferramentas, ela deverá estar em uma FocusArea
separada do restante do aplicativo. Barras de guias e outros elementos de navegação também devem ser separados do restante do aplicativo. Listas grandes geralmente devem ter seu próprio FocusArea
. Caso contrário, os usuários deverão percorrer toda a lista para acessar algumas visualizações.
FocusArea
é uma subclasse de LinearLayout
na car-ui-library. Quando esse recurso está habilitado, FocusArea
desenha um destaque quando um de seus descendentes está em foco. Para saber mais, consulte Personalização de destaque de foco .
Ao criar um bloco de navegação no arquivo de layout, se você pretende usar um LinearLayout
como contêiner para esse bloco, use FocusArea
. Caso contrário, envolva o bloco em FocusArea
.
NÃO aninhe um FocusArea
em outro FocusArea
. Fazer isso leva a um comportamento de navegação indefinido. Certifique-se de que todas as visualizações focáveis estejam aninhadas em uma FocusArea
.
Um exemplo de FocusArea
no RotaryPlayground
é mostrado abaixo:
<com.android.car.ui.FocusArea android:layout_margin="16dp" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:singleLine="true"> </EditText> </com.android.car.ui.FocusArea>
FocusArea
funciona da seguinte maneira:
- Ao lidar com ações de rotação e deslocamento,
RotaryService
procura instâncias deFocusArea
na hierarquia de visualizações. - Ao receber um evento de rotação,
RotaryService
move o foco para outra View que pode ter foco na mesmaFocusArea
. - Ao receber um evento de empurrão,
FocusArea
RotaryService
Move se concentra em outra visão que pode se concentrar em outro foco (normalmente adjacente).
Se você não incluir nenhum FocusAreas
no seu layout, a visão raiz será tratada como uma área de foco implícita. O usuário não pode cutucar para navegar no aplicativo. Em vez disso, eles giram em todas as visualizações focáveis, que podem ser adequadas para diálogos.
Personalização do foco
Dois atributos de visualização padrão podem ser usados para personalizar a navegação rotativa:
-
android:nextFocusForward
permite que os desenvolvedores de aplicativos especifiquem a ordem de rotação em uma área de foco. Este é o mesmo atributo usado para controlar a ordem da guia para navegação no teclado. Não use este atributo para criar um loop. Em vez disso, useapp:wrapAround
(veja abaixo) para criar um loop. -
android:focusedByDefault
permite que os desenvolvedores de aplicativos especifiquem a visualização de foco padrão na janela. Não use este atributo eapp:defaultFocus
(veja abaixo) na mesmaFocusArea
.
FocusArea
também define alguns atributos para personalizar a navegação rotativa. As áreas de foco implícito 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 visão descendente focalizável, que deve ser focada em quando o usuário cutuca esseFocusArea
. - ( Android 11 QPR3, Android 11 Car, Android 12 )
app:defaultFocusOverridesHistory
pode ser definido comotrue
para fazer com que a exibição especificada acima faça foco, mesmo que com o histórico para indicar outra visão nessaFocusArea
em que se concentrava. - ( Android 12 )
Useapp:nudgeLeftShortcut
,app:nudgeRightShortcut
,app:nudgeUpShortcut
eapp:nudgeDownShortcut
para especificar o ID de uma visão descendente focalizável, que deve ser focada em quando o usuário cutuca em uma determinada direção. Para saber mais, consulte o conteúdo para atalhos de cutucadas abaixo.( Android 11 QPR3, Android 11 Car, depreciados no Android 12 )
app:nudgeShortcut
eapp:nudgeShortcutDirection
suportou apenas um atalho de empurrão. - ( Android 11 QPR3, Android 11 Car, Android 12 )
Para permitir a rotação para envolver nesseFocusArea
,app:wrapAround
pode ser definido comotrue
. Isso é mais normalmente usado quando as vistas são organizadas em círculo ou oval. - ( Android 11 QPR3, Android 11 Car, Android 12 )
Para ajustar o preenchimento 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 desseFocusArea
para encontrar um alvo de cutucada, 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 umaFocusArea
adjacente (ou áreas) nas direções dadas, useapp:nudgeLeft
,app:nudgeRight
,app:nudgeUp
eapp:nudgeDown
. Use isso quando a pesquisa geométrica usada por padrão não encontrar o destino desejado.
A cutucada geralmente navega entre o foco. Mas com os atalhos de cutucadas, a cutucação às vezes navega primeiro dentro de uma FocusArea
, para que o usuário precise cutucá -lo duas vezes para navegar para o próximo FocusArea
. Os atalhos de cutucadas são úteis quando um FocusArea
contém uma longa lista seguida por um botão de ação flutuante , como no exemplo abaixo:
Sem o atalho de empurrão, o usuário teria que girar em toda a lista para alcançar o FAB.
Foco destaque a personalização
Como observado acima, RotaryService
se baseia no foco do conceito de visão da estrutura do Android. Quando o usuário gira e cutuca, RotaryService
move o foco, concentrando uma visão e desfocando outra. No Android, quando uma visão é focada, se a visualização:
- Especificou seu próprio destaque de foco, o Android desenha o destaque do foco da vista.
- Não especifica um destaque do foco, e o destaque do foco padrão não está desativado, o Android desenha o destaque do foco padrão para a visualização.
Os aplicativos projetados para o toque geralmente não especificam os destaques apropriados do foco.
O destaque padrão do foco é fornecido pela estrutura do Android e pode ser substituído pelo OEM. Os desenvolvedores de aplicativos o recebem quando o tema que eles estão usando é derivado do Theme.DeviceDefault
.
Para uma experiência consistente do usuário, confie no destaque do foco padrão sempre que possível. Se você precisar de um destaque de foco em formato de personagem (por exemplo, redondo ou em forma de pílula), ou se estiver usando um tema não derivado do Theme.DeviceDefault
, use os recursos car-ui-bibliotecas para especificar seu próprio destaque para foco para o destaque para cada visualização.
Para especificar um destaque de foco personalizado para uma visualização, altere o plano de fundo ou o primeiro plano desenhado da vista para um desenhado que difere quando a visualização estiver focada. Normalmente, você mudaria o plano de fundo. O seguinte desenhado, se usado como plano de fundo para uma visã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 na amostra acima identificam recursos definidos pela biblioteca CAR-UI. O OEM substitui estes para serem consistentes com o foco padrão destacando que eles especificam. Isso garante que o foco destaque a cor, a largura do AVC e assim por diante, não mude quando o usuário navega entre uma visualização com um destaque de foco personalizado e uma visão com o destaque padrão do foco. O último item é uma ondulação usada para toque. Os valores padrão usados para os recursos em negrito aparecem da seguinte forma:
Além disso, um destaque de foco personalizado é necessário para quando um botão recebe uma cor sólida de fundo para chamá -lo à atenção do usuário, como no exemplo abaixo. Isso pode dificultar o destaque do foco. Nesta situação, 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
Por exemplo:
Focado, não pressionado | Focado, pressionado |
Rolagem rotativa
Se o seu aplicativo usar RecyclerView
S, você deverá usar CarUiRecyclerView
s. Isso garante que sua interface do usuário seja consistente com os outros, porque a personalização de um OEM se aplica a todos os CarUiRecyclerView
s.
Se os elementos da sua lista forem focais, você não precisa fazer mais nada. A navegação rotativa move o foco através dos elementos da lista e da lista rola para tornar visível o elemento recém -focado.
( Android 11 QPR3, Android 11 Car, Android 12 )
Se houver uma mistura de elementos focalizáveis e sem foco, ou se todos os elementos forem focados, você poderá permitir a rolagem rotativa, o que permite ao usuário usar o controlador rotativo para rolar gradualmente pela lista sem pular itens não focáveis. Para ativar a rolagem rotativa, defina o app:rotaryScrollEnabled
para true
.
( Android 11 QPR3, Android 11 Car, Android 12 )
Você pode ativar a rolagem rotativa em qualquer visualização rolável, incluindo Av CarUiRecyclerView
, com o método setRotaryScrollEnabled()
em CarUiUtils
. Se você fizer isso, você precisa:
- Torne a visualização rolável focada para que possa ser focada quando nenhuma de suas visões descendentes focalizáveis forem visíveis,
- Desative o destaque do foco padrão na visualização rolável chamando
setDefaultFocusHighlightEnabled(false)
para que a visualização rolável não pareça focada, - Certifique -se de que a visualização rolável esteja focada antes de seus descendentes chamando
setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS)
. - Ouça o MotionEvents com
SOURCE_ROTARY_ENCODER
eAXIS_VSCROLL
ouAXIS_HSCROLL
para indicar a distância para rolar e a direção (através do sinal).
Quando a rolagem rotativa é ativada em um CarUiRecyclerView
e o usuário gira para uma área em que nenhuma visualização focada está presente, a barra de rolagem muda de cinza para azul, como se indique que a barra de rolagem está focada. Você pode implementar um efeito semelhante, se quiser.
Os MotionEvents são os mesmos que os gerados por uma roda de rolagem em um mouse, exceto a fonte.
Modo de manipulação direta
Normalmente, cutucadas e rotação navegam pela interface do usuário, enquanto o botão central pressiona agir, embora esse nem sempre seja o caso. Por exemplo, se um usuário quiser ajustar o volume de alarme, ele poderá usar o controlador rotativo para navegar até o controle deslizante do volume, pressionar o botão central, girar o controlador para ajustar o volume de alarme e pressione o botão Voltar para retornar à navegação . Isso é referido como modo de manipulação direta (DM) . Nesse modo, o controlador rotativo é usado para interagir diretamente com a exibição, em vez de navegar.
Implemente o DM de uma de duas maneiras. Se você precisar apenas lidar com a rotação e a visualização que deseja manipular responde a ACTION_SCROLL_FORWARD
e ACTION_SCROLL_BACKWARD
AccessibilityEvent
S adequadamente, use o mecanismo simples . Caso contrário, use o mecanismo avançado .
O mecanismo simples é a única opção nas janelas do sistema; Os aplicativos podem usar qualquer mecanismo.
Mecanismo simples
( Android 11 QPR3, Android 11 Car, Android 12 )
Seu aplicativo deve ligar para 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 exibição é focada. Quando no modo DM, as rotações executam ACTION_SCROLL_FORWARD
ou ACTION_SCROLL_BACKWARD
e sai do modo DM quando o usuário pressiona o botão Voltar. O mecanismo simples alterna o estado selecionado da visualização ao entrar e sair do modo DM.
Para fornecer uma sugestão visual de que o usuário está no modo DM, faça com que sua visualização pareça diferente quando selecionada. Por exemplo, altere o plano de fundo quando android:state_selected
é true
.
Mecanismo avançado
O aplicativo 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 DM focada deve entrar no modo DM e o botão de volta deve sair do modo DM. Se o botão central e/ou cutucada não forem usados, eles podem ser maneiras alternativas de sair do modo DM. Para aplicativos como mapas, um botão para representar o DM pode ser usado para inserir o modo DM.
Para apoiar o modo DM avançado, uma visualização:
- ( Android 11 QPR3, Android 11 Car, Android 12 ) deve ouvir um evento
KEYCODE_DPAD_CENTER
para inserir o modo DM e ouvir um eventoKEYCODE_BACK
para sair do modo DM, chamando oDirectManipulationHelper.enableDirectManipulationMode()
em cada caso. Para ouvir esses eventos, faça um dos seguintes:- Registre um
OnKeyListener
. ou, - Estenda a visualização e substitua seu método
dispatchKeyEvent()
.
- Registre um
- Deve ouvir eventos de nudge (
KEYCODE_DPAD_UP
,KEYCODE_DPAD_DOWN
,KEYCODE_DPAD_LEFT
ouKEYCODE_DPAD_RIGHT
) se a visualização deve lidar com nudges. - Deve ouvir o
MotionEvent
e obter a contagem de rotação noAXIS_SCROLL
se a exibição quiser lidar com a rotação. Existem várias maneiras de fazer isso:- Registre um
OnGenericMotionListener
. - Estenda a visualização e substitua seu método
dispatchTouchEvent()
.
- Registre um
- Para evitar ficar preso no modo DM, deve sair do modo DM quando o fragmento ou atividade à qual a exibição pertence não for interativa.
- Deve fornecer uma sugestão visual para indicar que a visualização está no modo DM.
Uma amostra de uma visualização personalizada que usa o modo DM para pan e zoom 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 no projeto RotaryPlayground
.
Atividade View
Ao usar uma visão de atividade:
- O
ActivityView
não deve ser focado. - ( Android 11 QPR3, Android 11 Car, depreciado no Android 11 )
O conteúdo daActivityView
deve conter umaFocusParkingView
como a primeira visualização focalizável e seuapp:shouldRestoreFocus
deve serfalse
. - O conteúdo do
ActivityView
não deve ter visualizaçõesandroid:focusByDefault
.
Para o usuário, o ActivityViews não deve ter efeito na navegação, exceto que as áreas de foco não podem abranger vistas de atividades. Em outras palavras, você não pode ter uma única área de foco que tenha conteúdo dentro e fora de uma ActivityView
. Se você não adicionar nenhuma foco à sua ActivityView
, a raiz da hierarquia de visualizações na ActivityView
é considerada uma área de foco implícita.
Botões que operam quando segurados
A maioria dos botões causa alguma ação quando clicada. Alguns botões operam quando segurados. Por exemplo, os botões de avanço rápido e rebobinar normalmente operam quando mantidos. Para fazer com que esses botões suportem o Rotary, ouça 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 mRunnable
toma uma ação (como rebobinar) e os programas para serem executados após um atraso.
Modo de toque
Os usuários podem usar um controlador rotativo para interagir com a unidade principal em um carro de duas maneiras, usando o controlador rotativo ou tocando na tela. Ao usar o controlador rotativo, uma das visualizações focalizáveis é destacada. Ao tocar na tela, nenhum destaque de foco aparece. O usuário pode alternar entre esses modos de entrada a qualquer momento:
- Rotary → Touch. Quando o usuário toca na tela, o destaque do foco desaparece.
- Toque → Rotary. Quando o usuário cutuca, gira ou pressiona o botão central, o destaque do foco aparece.
Os botões traseiros e home não têm efeito no modo de entrada.
Piggybacks rotativos no conceito de toque existente do Android. Você pode usar View.isInTouchMode()
para determinar qual modo de entrada o usuário está usando. Você pode usar OnTouchModeChangeListener
para ouvir alterações. Embora isso possa ser usado para personalizar sua interface do usuário para o modo de entrada atual, evite grandes alterações, pois elas podem ser desconcertantes.
Solução de problemas
Em um aplicativo projetado para o toque, é comum ter visões focadas aninhadas. Por exemplo, pode haver um FrameLayout
em torno de um ImageButton
, ambos focalizados. Isso não causa danos ao toque, mas pode resultar em uma experiência de usuário ruim para o Rotary, porque o usuário deve girar o controlador 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 a visualização interna focada, mas não ambos.
Se um botão ou interruptor perder o foco quando pressionado pelo controlador rotativo, uma dessas condições poderá se aplicar:
- O botão ou interruptor está sendo desativado (brevemente ou indefinidamente) devido ao botão ser pressionado. Em ambos os casos, existem duas maneiras de abordar isso:
- Deixe o estado
android:enabled
comotrue
e use um estado personalizado para acordar o botão ou alternar conforme descrito no estado personalizado . - Use um contêiner para envolver o botão ou alternar e torne o contêiner focado em vez do botão ou interruptor. (O ouvinte de clique deve estar no contêiner.)
- Deixe o estado
- O botão ou interruptor está sendo substituído. Por exemplo, a ação tomada quando o botão é pressionado ou o interruptor é alterado pode desencadear uma atualização das ações disponíveis, fazendo com que novos botões substituam os botões existentes. Existem duas maneiras de abordar isso:
- Em vez de criar um novo botão ou comutador, defina o ícone e/ou texto do botão ou interruptor existente.
- Como acima, adicione um contêiner focalizado ao redor do botão ou do comutador.
ROTÁRIOPROMENTO
RotaryPlayground
é um aplicativo de referência para o Rotary. Use -o para aprender a integrar recursos rotativos em seus aplicativos. RotaryPlayground
está incluído nas compilações do emulador e nas compilações para dispositivos que executam o sistema operacional Android Automotive (AAOS).
- Repositório
RotaryPlayground
:packages/apps/Car/tests/RotaryPlayground/
- Versões: Android 11 QPR3, Android 11 Car e Android 12
O aplicativo RotaryPlayground
mostra as seguintes guias à esquerda:
- Cartões. Teste Navegando em torno de áreas de foco, pulando elementos sem foco e entrada de texto.
- Manipulação direta. Widgets de teste que suportam o modo de manipulação direta simples e avançado. Esta guia é especificamente para manipulação direta na janela do aplicativo.
- Manipulação de Ui SYS. Widgets de teste que suportam manipulação direta nas janelas do sistema, onde apenas o modo de manipulação direta simples é suportado.
- Grade. Teste a navegação rotativa com padrão z com rolagem.
- Notificação. Teste as notificações dentro e fora das notificações de heads-up.
- Rolagem. Teste a rolagem de uma mistura de conteúdo focável e sem foco.
- WebView. Teste Navegando através de links em um
WebView
. -
FocusArea
personalizado. Teste a personalizaçãoFocusArea
:- Envolver em torno.
-
android:focusedByDefault
eapp:defaultFocus
. - Nudge explícito alvos.
- Atalhos de cutucadas.
-
FocusArea
sem visões focais.