Poniższy materiał jest przeznaczony dla twórców aplikacji.
Aby Twoja aplikacja obsługiwała rotację, MUSISZ:
- Umieść
FocusParkingView
w odpowiednim układzie aktywności. - Upewnij się, że widoki można (lub nie można) skupić.
- Użyj
FocusArea
s, aby objąć wszystkie widoki, na których można się skupić, z wyjątkiemFocusParkingView
.
Każde z tych zadań opisano szczegółowo poniżej po skonfigurowaniu środowiska do tworzenia aplikacji obsługujących technologię rotacyjną.
Skonfiguruj kontroler obrotowy
Zanim zaczniesz tworzyć aplikacje obsługujące funkcję obrotową, potrzebujesz kontrolera obrotowego lub modułu zastępczego. Masz opcje opisane poniżej.
Emulator
source build/envsetup.sh && lunch car_x86_64-userdebug m -j emulator -wipe-data -no-snapshot -writable-system
Możesz także użyć aosp_car_x86_64-userdebug
.
Aby uzyskać dostęp do emulowanego kontrolera obrotowego:
- Kliknij trzy kropki na dole paska narzędzi:
- Wybierz opcję Car obrotowy w rozszerzonym oknie sterowania:
Klawiatura USB
- Podłącz klawiaturę USB do urządzenia z systemem Android Automotive OS (AAOS). W niektórych przypadkach uniemożliwia to wyświetlenie klawiatury ekranowej.
- Użyj
userdebug
lub kompilacjieng
. - Włącz filtrowanie kluczowych zdarzeń:
adb shell settings put secure android.car.ROTARY_KEY_EVENT_FILTER 1
- W poniższej tabeli znajdziesz odpowiedni klucz dla każdej akcji:
Klucz Akcja obrotowa Q Obróć przeciwnie do ruchu wskazówek zegara mi Obróć zgodnie z ruchem wskazówek zegara A Rusz w lewo D Przesuń w prawo W Podnieś się S Przesuń w dół F lub przecinek Środkowy przycisk R lub Esc Przycisk powrotu
Polecenia ADB
Za pomocą poleceń car_service
można wstrzykiwać obrotowe zdarzenia wejściowe. Polecenia te można uruchamiać na urządzeniach z systemem Android Automotive OS (AAOS) lub na emulatorze.
polecenia car_service | Wejście obrotowe |
---|---|
adb shell cmd car_service inject-rotary | Obróć przeciwnie do ruchu wskazówek zegara |
adb shell cmd car_service inject-rotary -c true | Obróć zgodnie z ruchem wskazówek zegara |
adb shell cmd car_service inject-rotary -dt 100 50 | Obróć wielokrotnie w kierunku przeciwnym do ruchu wskazówek zegara (100 ms temu i 50 ms temu) |
adb shell cmd car_service inject-key 282 | Rusz w lewo |
adb shell cmd car_service inject-key 283 | Przesuń w prawo |
adb shell cmd car_service inject-key 280 | Podnieś się |
adb shell cmd car_service inject-key 281 | Przesuń w dół |
adb shell cmd car_service inject-key 23 | Kliknięcie środkowego przycisku |
adb shell input keyevent inject-key 4 | Kliknij przycisk Wstecz |
Sterownik obrotowy OEM
Jest to najbardziej realistyczna opcja, gdy sprzęt kontrolera obrotowego jest uruchomiony. Jest to szczególnie przydatne do testowania szybkiego obrotu.
FocusParkingView
FocusParkingView
to przezroczysty widok w bibliotece Car UI (car-ui-library) . RotaryService
używa go do obsługi nawigacji za pomocą kontrolera obrotowego. FocusParkingView
musi być pierwszym widokiem w układzie, na którym można ustawić fokus. Musi być umieszczony poza wszystkimi obszarami FocusArea
. Każde okno musi mieć jeden FocusParkingView
. Jeśli już korzystasz z układu podstawowego car-ui-library, który zawiera FocusParkingView
, nie musisz dodawać kolejnego FocusParkingView
. Poniżej pokazano przykład FocusParkingView
w 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>
Oto powody, dla których potrzebujesz FocusParkingView
:
- Android nie usuwa automatycznie fokusu, gdy fokus jest ustawiony w innym oknie. Jeśli spróbujesz usunąć fokus w poprzednim oknie, Android ponownie ustawi ostrość widoku w tym oknie, co spowoduje jednoczesne skupienie się na dwóch oknach. Dodanie
FocusParkingView
do każdego okna może rozwiązać ten problem. Ten widok jest przezroczysty, a jego domyślne podświetlenie fokusu jest wyłączone, dzięki czemu jest niewidoczne dla użytkownika, niezależnie od tego, czy jest on skupiony, czy nie. Może przejąć fokus, dzięki czemuRotaryService
może ustawić na nim fokus w celu usunięcia podświetlenia fokusu. - Jeśli w bieżącym oknie znajduje się tylko jeden
FocusArea
, obrócenie kontrolera wFocusArea
powoduje, żeRotaryService
przenosi fokus z widoku po prawej stronie do widoku po lewej stronie (i odwrotnie). Dodanie tego widoku do każdego okna może rozwiązać problem. GdyRotaryService
ustali, że celem fokusu jestFocusParkingView
, może określić, że wkrótce nastąpi zawijanie i w którym momencie unika zawijania, nie przesuwając fokusu. - Gdy pokrętło uruchamia aplikację, system Android ustawia ostrość na pierwszym widoku, który można ustawić, czyli zawsze na widoku
FocusParkingView
.FocusParkingView
określa optymalny widok, na którym należy się skupić, a następnie ustawia ostrość.
Możliwość skupienia widoków
RotaryService
opiera się na istniejącej koncepcji skupienia widoku w środowisku Android, której początki sięgają czasów, gdy telefony miały fizyczne klawiatury i pady kierunkowe. Istniejący atrybut android:nextFocusForward
został ponownie przystosowany do obsługi obrotowej (zobacz Dostosowywanie FocusArea ), ale android:nextFocusLeft
, android:nextFocusRight
, android:nextFocusUp
i android:nextFocusDown
nie są.
RotaryService
skupia się wyłącznie na widokach, na których można się skupić. Niektóre widoki, takie jak Button
s, zwykle można ustawić jako aktywne. Inne, takie jak TextView
i ViewGroup
, zwykle nie są. Klikalne widoki są automatycznie ustawiane jako aktywne, a widoki są automatycznie klikalne, gdy mają odbiornik kliknięcia. Jeśli ta automatyczna logika skutkuje żądaną możliwością skupienia, nie trzeba jawnie ustawiać możliwości skupiania widoku. Jeśli logika automatyczna nie zapewnia pożądanej możliwości skupienia, ustaw atrybut android:focusable
na true
lub false
albo programowo ustaw możliwość skupienia widoku za pomocą View.setFocusable(boolean)
. Aby RotaryService
mogło się na tym skupić, widok MUSI spełniać następujące wymagania:
- Możliwość ustawienia ostrości
- Włączony
- Widoczny
- Mają niezerowe wartości szerokości i wysokości
Jeśli widok nie spełnia wszystkich tych wymagań, na przykład przycisk, na którym można ustawić fokus, ale jest wyłączony, użytkownik nie może użyć pokrętła, aby się na nim skupić. Jeśli chcesz skupić się na wyłączonych widokach, rozważ użycie stanu niestandardowego zamiast android:state_enabled
aby kontrolować sposób wyświetlania widoku bez wskazywania, że Android powinien uważać go za wyłączony. Twoja aplikacja może poinformować użytkownika, dlaczego po dotknięciu widok jest wyłączony. W następnej sekcji wyjaśniono, jak to zrobić.
Stan niestandardowy
Aby dodać stan niestandardowy:
- Aby dodać atrybut niestandardowy do widoku. Na przykład, aby dodać niestandardowy stan
state_rotary_enabled
do klasy widokuCustomView
, użyj:<declare-styleable name="CustomView"> <attr name="state_rotary_enabled" format="boolean" /> </declare-styleable>
- Aby śledzić ten stan, dodaj zmienną instancji do swojego widoku wraz z metodami dostępu:
private boolean mRotaryEnabled; public boolean getRotaryEnabled() { return mRotaryEnabled; } public void setRotaryEnabled(boolean rotaryEnabled) { mRotaryEnabled = rotaryEnabled; }
- Aby odczytać wartość atrybutu podczas tworzenia widoku:
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomView); mRotaryEnabled = a.getBoolean(R.styleable.CustomView_state_rotary_enabled);
- W klasie widoku zastąp metodę
onCreateDrawableState()
, a następnie dodaj stan niestandardowy, jeśli to konieczne. Na przykład:@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; }
- Spraw, aby moduł obsługi kliknięć w widoku działał inaczej w zależności od jego stanu. Na przykład moduł obsługi kliknięcia może nic nie robić lub może wyświetlić toast, gdy
mRotaryEnabled
mafalse
. - Aby przycisk wyglądał na wyłączony, w tle widoku możesz rysować, użyj
app:state_rotary_enabled
zamiastandroid:state_enabled
. Jeśli jeszcze go nie masz, musisz dodać:xmlns:app="http://schemas.android.com/apk/res-auto"
- Jeśli widok jest wyłączony w którymkolwiek układzie, zamień
android:enabled="false"
naapp:state_rotary_enabled="false"
, a następnie dodaj przestrzeń nazwapp
, jak powyżej. - Jeśli Twój widok jest programowo wyłączony, zamień wywołania
setEnabled()
na wywołaniasetRotaryEnabled()
.
Strefa zainteresowania
Użyj FocusAreas
aby podzielić aktywne widoki na bloki, aby ułatwić nawigację i zachować spójność z innymi aplikacjami. Na przykład, jeśli aplikacja ma pasek narzędzi, powinien on znajdować się w oddzielnym obszarze FocusArea
od reszty aplikacji. Paski zakładek i inne elementy nawigacyjne również powinny być oddzielone od reszty aplikacji. Duże listy powinny generalnie mieć swój własny FocusArea
. Jeśli nie, użytkownicy muszą przeglądać całą listę, aby uzyskać dostęp do niektórych widoków.
FocusArea
jest podklasą LinearLayout
w bibliotece car-ui. Gdy ta funkcja jest włączona, FocusArea
rysuje podświetlenie, gdy jeden z jego elementów podrzędnych jest skupiony. Aby dowiedzieć się więcej, zobacz Dostosowywanie podświetlenia fokusu .
Jeśli podczas tworzenia bloku nawigacyjnego w pliku układu zamierzasz używać LinearLayout
jako kontenera dla tego bloku, zamiast tego użyj FocusArea
. W przeciwnym razie zawiń blok w FocusArea
.
NIE zagnieżdżaj FocusArea
w innym FocusArea
. Prowadzi to do niezdefiniowanego zachowania nawigacji. Upewnij się, że wszystkie widoki, na których można się skupić, są zagnieżdżone w FocusArea
.
Przykład FocusArea
w RotaryPlayground
pokazano poniżej:
<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
działa w następujący sposób:
- Podczas obsługi akcji obracania i szturchania
RotaryService
szuka wystąpieńFocusArea
w hierarchii widoków. - Po otrzymaniu zdarzenia rotacji
RotaryService
przenosi fokus do innego widoku, który może skupić się na tym samymFocusArea
. - Po otrzymaniu zdarzenia szturchnięcia
RotaryService
przenosi fokus do innego widoku, który może skupić się na innym (zazwyczaj sąsiadującym)FocusArea
.
Jeśli w układzie nie uwzględnisz żadnych FocusAreas
, widok główny będzie traktowany jako ukryty obszar fokusu. Użytkownik nie może naciskać, aby poruszać się po aplikacji. Zamiast tego będą się one obracać po wszystkich widokach, na których można się skupić, co może być odpowiednie w przypadku okien dialogowych.
Personalizacja obszaru FocusArea
Do dostosowania nawigacji obrotowej można użyć dwóch standardowych atrybutów widoku:
-
android:nextFocusForward
umożliwia twórcom aplikacji określenie kolejności rotacji w obszarze fokusu. Jest to ten sam atrybut, który służy do kontrolowania kolejności tabulacji podczas nawigacji za pomocą klawiatury. NIE używaj tego atrybutu do tworzenia pętli. Zamiast tego użyjapp:wrapAround
(patrz poniżej), aby utworzyć pętlę. -
android:focusedByDefault
umożliwia twórcom aplikacji określenie domyślnego widoku fokusu w oknie. NIE używaj tego atrybutu iapp:defaultFocus
(patrz poniżej) w tym samymFocusArea
.
FocusArea
definiuje również pewne atrybuty umożliwiające dostosowanie nawigacji obrotowej. Za pomocą tych atrybutów nie można dostosowywać ukrytych obszarów fokusu.
- ( Android 11 QPR3, Android 11 Samochód, Android 12 )
app:defaultFocus
może służyć do określenia identyfikatora widoku potomnego, na którym można ustawić fokus, na którym należy się skupić, gdy użytkownik przejdzie do tegoFocusArea
. - ( Android 11 QPR3, Android 11 Samochód, Android 12 )
app:defaultFocusOverridesHistory
można ustawić natrue
, aby widok określony powyżej był aktywny, nawet jeśli z historią wskazującą, że skupiono się na innym widoku w tymFocusArea
. - ( Android 12 )
Użyjapp:nudgeLeftShortcut
,app:nudgeRightShortcut
,app:nudgeUpShortcut
iapp:nudgeDownShortcut
, aby określić identyfikator widoku potomnego, na którym można ustawić fokus, na którym powinien się skupiać, gdy użytkownik przesuwa w danym kierunku. Aby dowiedzieć się więcej, zapoznaj się z treścią skrótów przesuwania poniżej.( Android 11 QPR3, Android 11 Car, przestarzałe w Androidzie 12 )
app:nudgeShortcut
iapp:nudgeShortcutDirection
obsługiwały tylko jeden skrót szturchnięcia. - ( Android 11 QPR3, Android 11 Samochód, Android 12 )
Aby umożliwić obrót w tymFocusArea
,app:wrapAround
można ustawić natrue
. Jest to najczęściej używane, gdy widoki są ułożone w okrąg lub owal. - ( Android 11 QPR3, Android 11 Samochód, Android 12 )
Aby dostosować dopełnienie podświetlenia w tymFocusArea
, użyjapp:highlightPaddingStart
,app:highlightPaddingEnd
,app:highlightPaddingTop
,app:highlightPaddingBottom
,app:highlightPaddingHorizontal
iapp:highlightPaddingVertical
. - ( Android 11 QPR3, Android 11 Samochód, Android 12 )
Aby dostosować postrzegane granice tegoFocusArea
w celu znalezienia celu szturchnięcia, użyjapp:startBoundOffset
,app:endBoundOffset
,app:topBoundOffset
,app:bottomBoundOffset
,app:horizontalBoundOffset
iapp:verticalBoundOffset
. - ( Android 11 QPR3, Android 11 Samochód, Android 12 )
Aby jawnie określić identyfikator sąsiadującegoFocusArea
(lub obszarów) w podanych kierunkach, użyjapp:nudgeLeft
,app:nudgeRight
,app:nudgeUp
iapp:nudgeDown
. Użyj tej opcji, jeśli domyślnie używane wyszukiwanie geometryczne nie pozwala znaleźć żądanego celu.
Podsuwanie zwykle powoduje nawigację pomiędzy obszarami ostrości. Jednak w przypadku skrótów przesuwania przesuwanie czasami najpierw powoduje nawigację w obrębie FocusArea
, więc może być konieczne dwukrotne szturchnięcie, aby przejść do następnego FocusArea
. Skróty przesuwania są przydatne, gdy FocusArea
zawiera długą listę, po której następuje pływający przycisk akcji , jak w poniższym przykładzie:
Bez skrótu szturchnięcia użytkownik musiałby przeglądać całą listę, aby dotrzeć do FAB.
Dostosowywanie podświetlenia ostrości
Jak wspomniano powyżej, RotaryService
opiera się na istniejącej koncepcji skupienia widoku w środowisku Android. Kiedy użytkownik obraca się i szturcha, RotaryService
przesuwa fokus, skupiając jeden widok i tracąc ostrość na innym. W systemie Android, gdy widok jest skupiony, jeśli widok:
- Określił własne wyróżnienie fokusu, Android rysuje wyróżnienie fokusu widoku.
- Nie określa podświetlenia fokusu, a domyślne wyróżnienie fokusu nie jest wyłączone, system Android rysuje domyślne wyróżnienie fokusu dla widoku.
Aplikacje przeznaczone do obsługi dotyku zwykle nie określają odpowiednich podświetleń.
Domyślne podświetlenie fokusu jest zapewniane przez platformę Android i może zostać zastąpione przez producenta OEM. Twórcy aplikacji otrzymują go, gdy motyw, którego używają, pochodzi z Theme.DeviceDefault
.
Aby zapewnić spójne wrażenia użytkownika, w miarę możliwości polegaj na domyślnym podświetleniu fokusu. Jeśli potrzebujesz podświetlenia o niestandardowym kształcie (na przykład okrągłego lub pigułkowego) lub jeśli używasz motywu niepochodzącego z Theme.DeviceDefault
, użyj zasobów car-ui-library, aby określić własne wyróżnienie fokusu każdy widok.
Aby określić niestandardowe podświetlenie widoku, zmień tło lub element rysunkowy pierwszego planu widoku na rysowalny, który różni się, gdy widok jest skupiony. Zwykle zmieniasz tło. Poniższy rysunek, jeśli zostanie użyty jako tło dla widoku kwadratowego, tworzy okrągłe podświetlenie:
<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 ) Pogrubione odniesienia do zasobów w powyższym przykładzie identyfikują zasoby zdefiniowane przez bibliotekę car-ui. Producent OEM zastępuje je, aby zachować zgodność z określonym przez nich domyślnym podświetleniem fokusu. Dzięki temu kolor podświetlenia fokusu, szerokość obrysu itd. nie zmienią się, gdy użytkownik będzie przechodzić między widokiem z niestandardowym podświetleniem fokusu a widokiem z domyślnym podświetleniem fokusu. Ostatnim elementem jest zmarszczka służąca do dotyku. Domyślne wartości użyte dla zasobów pogrubionych wyglądają następująco:
Ponadto niestandardowe wyróżnienie fokusu jest wymagane, gdy przycisk ma jednolity kolor tła, aby zwrócić na niego uwagę użytkownika, jak w przykładzie poniżej. Może to sprawić, że podkreślenie ostrości będzie trudne do zauważenia. W tej sytuacji określ niestandardowe wyróżnienie fokusu za pomocą kolorów dodatkowych :
- ( Android 11 QPR3, Android 11 Samochód, 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
Na przykład:
Skoncentrowany, a nie przyciśnięty | Skoncentrowany, przyciśnięty |
Przewijanie obrotowe
Jeśli Twoja aplikacja używa RecyclerView
s, zamiast tego powinieneś używać CarUiRecyclerView
s. Gwarantuje to, że Twój interfejs użytkownika będzie spójny z innymi, ponieważ dostosowanie OEM dotyczy wszystkich CarUiRecyclerView
.
Jeśli na wszystkich elementach listy można ustawić fokus, nie musisz robić nic więcej. Nawigacja obrotowa przesuwa fokus pomiędzy elementami na liście, a lista przewija się, aby nowo wybrany element był widoczny.
( Android 11 QPR3, Android 11 Samochód, Android 12 )
Jeśli występuje mieszanka elementów, na których można ustawić fokus i na których nie można ustawić ostrości, lub jeśli na wszystkich elementach nie można ustawić ostrości, można włączyć przewijanie obrotowe, co pozwala użytkownikowi używać kontrolera obrotowego do stopniowego przewijania listy bez pomijania elementów, na których nie można ustawić ostrości. Aby włączyć przewijanie obrotowe, ustaw atrybut app:rotaryScrollEnabled
na true
.
( Android 11 QPR3, Android 11 Samochód, Android 12 )
Możesz włączyć przewijanie obrotowe w dowolnym przewijanym widoku, w tym av CarUiRecyclerView
, za pomocą metody setRotaryScrollEnabled()
w CarUiUtils
. Jeśli to zrobisz, musisz:
- Skoncentruj przewijalny widok, aby można było się na nim skupić, gdy żaden z jego możliwych do skupienia widoków potomnych nie jest widoczny,
- Wyłącz domyślne wyróżnienie fokusu w przewijanym widoku, wywołując
setDefaultFocusHighlightEnabled(false)
, aby przewijany widok nie wydawał się skupiony, - Upewnij się, że przewijany widok jest skupiony przed jego potomkami, wywołując
setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS)
. - Nasłuchuj zdarzeń MotionEvents za pomocą
SOURCE_ROTARY_ENCODER
iAXIS_VSCROLL
lubAXIS_HSCROLL
, aby wskazać odległość do przewinięcia i kierunek (przez znak).
Gdy przewijanie obrotowe jest włączone w CarUiRecyclerView
, a użytkownik obraca się do obszaru, w którym nie ma widoków, na których można ustawić fokus, pasek przewijania zmienia się z szarego na niebieski, jakby wskazywał, że pasek przewijania jest aktywny. Jeśli chcesz, możesz zastosować podobny efekt.
Zdarzenia MotionEvents są takie same, jak te generowane przez kółko przewijania myszy, z wyjątkiem źródła.
Tryb bezpośredniej manipulacji
Zwykle przesuwanie i obrót umożliwiają poruszanie się po interfejsie użytkownika, podczas gdy naciśnięcie środkowego przycisku powoduje wykonanie akcji, chociaż nie zawsze tak się dzieje. Na przykład, jeśli użytkownik chce dostosować głośność alarmu, może użyć kontrolera obrotowego, aby przejść do suwaka głośności, nacisnąć środkowy przycisk, obrócić kontroler, aby dostosować głośność alarmu, a następnie nacisnąć przycisk Wstecz, aby powrócić do nawigacji . Nazywa się to trybem bezpośredniej manipulacji (DM) . W tym trybie pokrętło służy do bezpośredniej interakcji z widokiem, a nie do nawigacji.
Zaimplementuj DM na jeden z dwóch sposobów. Jeśli potrzebujesz tylko obsłużyć obrót, a widok, którym chcesz manipulować, odpowiednio reaguje na AccessibilityEvent
ACTION_SCROLL_FORWARD
i ACTION_SCROLL_BACKWARD
, użyj prostego mechanizmu. W przeciwnym razie skorzystaj z zaawansowanego mechanizmu.
Prosty mechanizm jest jedyną opcją w oknach systemowych; aplikacje mogą korzystać z obu mechanizmów.
Prosty mechanizm
( Android 11 QPR3, Android 11 Samochód, Android 12 )
Twoja aplikacja powinna wywołać DirectManipulationHelper.setSupportsRotateDirectly(View view, boolean enable)
. RotaryService
rozpoznaje, kiedy użytkownik znajduje się w trybie DM i przechodzi do trybu DM, gdy użytkownik naciśnie środkowy przycisk, gdy widok jest aktywny. W trybie DM obroty wykonują ACTION_SCROLL_FORWARD
lub ACTION_SCROLL_BACKWARD
i opuszczają tryb DM, gdy użytkownik naciśnie przycisk Wstecz. Prosty mechanizm przełącza wybrany stan widoku podczas wchodzenia i wychodzenia z trybu DM.
Aby zapewnić wizualną wskazówkę, że użytkownik znajduje się w trybie DM, po wybraniu zmień wygląd widoku. Na przykład zmień tło, gdy android:state_selected
ma true
.
Zaawansowany mechanizm
Aplikacja określa, kiedy RotaryService
wchodzi i wychodzi z trybu DM. Aby zapewnić spójne doświadczenie użytkownika, naciśnięcie środkowego przycisku przy widoku DM powinno spowodować przejście do trybu DM, a przycisk Wstecz powinien zakończyć tryb DM. Jeśli środkowy przycisk i/lub szturchnięcie nie są używane, mogą to być alternatywne sposoby wyjścia z trybu DM. W przypadku aplikacji takich jak Mapy do przejścia do trybu DM można użyć przycisku reprezentującego DM.
Aby obsługiwać zaawansowany tryb DM, widok:
- ( Android 11 QPR3, Android 11 Car, Android 12 ) MUSI nasłuchiwać zdarzenia
KEYCODE_DPAD_CENTER
, aby wejść w tryb DM i nasłuchiwać zdarzeniaKEYCODE_BACK
, aby wyjść z trybu DM, wywołując w każdym przypadkuDirectManipulationHelper.enableDirectManipulationMode()
. Aby nasłuchiwać tych zdarzeń, wykonaj jedną z następujących czynności:- Zarejestruj
OnKeyListener
. Lub, - Rozszerz widok, a następnie zastąp jego metodę
dispatchKeyEvent()
.
- Zarejestruj
- POWINNO nasłuchiwać zdarzeń szturchnięć (
KEYCODE_DPAD_UP
,KEYCODE_DPAD_DOWN
,KEYCODE_DPAD_LEFT
lubKEYCODE_DPAD_RIGHT
), jeśli widok powinien obsługiwać szturchnięcia. - POWINNO nasłuchiwać zdarzeń
MotionEvent
i uzyskać liczbę rotacji wAXIS_SCROLL
, jeśli widok chce obsłużyć obrót. Można to zrobić na kilka sposobów:- Zarejestruj
OnGenericMotionListener
. - Rozszerz widok i zastąp jego metodę
dispatchTouchEvent()
.
- Zarejestruj
- Aby uniknąć utknięcia w trybie DM, MUSISZ wyjść z trybu DM, gdy fragment lub działanie, do którego należy widok, nie jest interaktywne.
- POWINNO zapewniać wizualną wskazówkę wskazującą, że widok jest w trybie DM.
Poniżej znajduje się przykład widoku niestandardowego, który wykorzystuje tryb DM do przesuwania i powiększania mapy:
/** 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(); }
Więcej przykładów można znaleźć w projekcie RotaryPlayground
.
Widok aktywności
Podczas korzystania z ActivityView:
-
ActivityView
nie powinien umożliwiać fokusu. - ( Android 11 QPR3, Android 11 Car, przestarzałe w Androidzie 11 )
ZawartośćActivityView
MUSI zawieraćFocusParkingView
jako pierwszy widok, na którym można się skupić, a jegoapp:shouldRestoreFocus
MUSI mieć wartośćfalse
. - Zawartość
ActivityView
nie powinna zawierać widokówandroid:focusByDefault
.
Dla użytkownika Widoki Aktywności nie powinny mieć żadnego wpływu na nawigację, z wyjątkiem tego, że obszary fokusu nie mogą obejmować Widoków Aktywności. Innymi słowy, nie można mieć jednego obszaru skupienia zawierającego treść wewnątrz i na zewnątrz ActivityView
. Jeśli nie dodasz żadnego FocusAreas do ActivityView
, katalog główny hierarchii widoków w ActivityView
będzie uważany za niejawny obszar fokusu.
Przyciski działające po przytrzymaniu
Większość przycisków powoduje pewne działanie po kliknięciu. Zamiast tego niektóre przyciski działają po ich przytrzymaniu. Na przykład przyciski szybkiego przewijania do przodu i przewijania do tyłu zazwyczaj działają, gdy są przytrzymywane. Aby takie przyciski obsługiwały obroty, nasłuchuj KeyEvents
KEYCODE_DPAD_CENTER
w następujący sposób:
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; });
W którym mRunnable
podejmuje akcję (taką jak przewijanie) i planuje uruchomienie się z opóźnieniem.
Tryb dotykowy
Użytkownicy mogą używać kontrolera obrotowego do interakcji z radioodtwarzaczem w samochodzie na dwa sposoby: za pomocą kontrolera obrotowego lub dotykając ekranu. Podczas korzystania z pokrętła podświetlony jest jeden z widoków, na których można ustawić ostrość. Po dotknięciu ekranu nie pojawia się podświetlenie ostrości. Użytkownik może w dowolnym momencie przełączać się pomiędzy trybami wprowadzania danych:
- Obróć → dotknij. Gdy użytkownik dotknie ekranu, podświetlenie ostrości znika.
- Dotknij → obrotowy. Gdy użytkownik szturchnie, obróci lub naciśnie środkowy przycisk, pojawi się podświetlenie fokusu.
Przyciski Wstecz i Strona główna nie mają wpływu na tryb wprowadzania.
Rotary opiera się na istniejącej koncepcji trybu dotykowego Androida. Za pomocą View.isInTouchMode()
można określić, jakiego trybu wprowadzania danych używa użytkownik. Do nasłuchiwania zmian możesz użyć OnTouchModeChangeListener
. Chociaż można to wykorzystać do dostosowania interfejsu użytkownika do bieżącego trybu wprowadzania, należy unikać większych zmian, ponieważ mogą one wprowadzać w błąd.
Rozwiązywanie problemów
W aplikacjach przeznaczonych do obsługi dotyku często stosuje się zagnieżdżone widoki, na których można się skupiać. Na przykład wokół ImageButton
może znajdować się FrameLayout
, z których oba można ustawić. Nie szkodzi to dotykowi, ale może powodować pogorszenie komfortu korzystania z funkcji obrotowej, ponieważ użytkownik musi dwukrotnie obrócić kontroler, aby przejść do następnego widoku interaktywnego. Aby zapewnić wygodę użytkownika, Google zaleca ustawienie skupienia widoku zewnętrznego lub widoku wewnętrznego, ale nie obu.
Jeśli przycisk lub przełącznik straci ostrość po naciśnięciu za pomocą pokrętła, może mieć zastosowanie jeden z poniższych warunków:
- Przycisk lub przełącznik jest blokowany (na krótko lub na czas nieokreślony) w wyniku naciśnięcia przycisku. W obu przypadkach można temu zaradzić na dwa sposoby:
- Pozostaw stan
android:enabled
jakotrue
i użyj stanu niestandardowego, aby wyszarzić przycisk lub przełączyć, jak opisano w Custom State . - Użyj kontenera, aby otoczyć przycisk lub przełącznik i ustawić kontener, na którym można się skupić zamiast przycisku lub przełącznika. (Odbiornik kliknięcia musi znajdować się w kontenerze.)
- Pozostaw stan
- Przycisk lub przełącznik jest wymieniany. Na przykład akcja wykonywana po naciśnięciu przycisku lub przełączeniu przełącznika może spowodować odświeżenie dostępnych akcji, powodując zastąpienie istniejących przycisków nowymi. Można temu zaradzić na dwa sposoby:
- Zamiast tworzyć nowy przycisk lub przełącznik, ustaw ikonę i/lub tekst istniejącego przycisku lub przełącznika.
- Jak powyżej, dodaj możliwy do skupienia kontener wokół przycisku lub przełącznika.
Obrotowy plac zabaw
RotaryPlayground
to aplikacja referencyjna dla urządzeń obrotowych. Skorzystaj z niego, aby dowiedzieć się, jak zintegrować funkcje obrotowe ze swoimi aplikacjami. RotaryPlayground
jest zawarty w kompilacjach emulatorów i kompilacjach dla urządzeń z systemem operacyjnym Android Automotive OS (AAOS).
- Repozytorium
RotaryPlayground
:packages/apps/Car/tests/RotaryPlayground/
- Wersje: Android 11 QPR3, Android 11 Car i Android 12
Aplikacja RotaryPlayground
wyświetla po lewej stronie następujące karty:
- Karty. Przetestuj poruszanie się po obszarach fokusu, pomijanie elementów, na których nie można ustawić ostrości, oraz wprowadzanie tekstu.
- Bezpośrednia manipulacja. Testuj widżety obsługujące prosty i zaawansowany tryb bezpośredniej manipulacji. Ta zakładka służy specjalnie do bezpośredniej manipulacji w oknie aplikacji.
- Manipulacja interfejsem użytkownika systemu. Testuj widżety obsługujące bezpośrednią manipulację w oknach systemowych, w których obsługiwany jest tylko prosty tryb bezpośredniej manipulacji.
- Siatka. Przetestuj nawigację obrotową według wzoru Z z przewijaniem.
- Powiadomienie. Przetestuj włączanie i wyłączanie powiadomień heads-up.
- Zwój. Przetestuj przewijanie mieszanki treści, na których można się skupić i których nie można skupić.
- Widok sieciowy. Przetestuj nawigację po łączach w
WebView
. - Niestandardowy
FocusArea
. Przetestuj dostosowywanieFocusArea
:- Owinąć.
-
android:focusedByDefault
iapp:defaultFocus
. - Wyraźne cele szturchania.
- Poruszaj skrótami.
-
FocusArea
bez widoków, na których można się skupić.
Poniższy materiał jest przeznaczony dla twórców aplikacji.
Aby Twoja aplikacja obsługiwała rotację, MUSISZ:
- Umieść
FocusParkingView
w odpowiednim układzie aktywności. - Upewnij się, że widoki można (lub nie można) skupić.
- Użyj
FocusArea
s, aby objąć wszystkie widoki, na których można się skupić, z wyjątkiemFocusParkingView
.
Każde z tych zadań opisano szczegółowo poniżej po skonfigurowaniu środowiska do tworzenia aplikacji obsługujących technologię rotacyjną.
Skonfiguruj kontroler obrotowy
Zanim zaczniesz tworzyć aplikacje obsługujące funkcję obrotową, potrzebujesz kontrolera obrotowego lub modułu zastępczego. Masz opcje opisane poniżej.
Emulator
source build/envsetup.sh && lunch car_x86_64-userdebug m -j emulator -wipe-data -no-snapshot -writable-system
Możesz także użyć aosp_car_x86_64-userdebug
.
Aby uzyskać dostęp do emulowanego kontrolera obrotowego:
- Kliknij trzy kropki na dole paska narzędzi:
- Wybierz opcję Car obrotowy w rozszerzonym oknie sterowania:
Klawiatura USB
- Podłącz klawiaturę USB do urządzenia z systemem Android Automotive OS (AAOS). W niektórych przypadkach uniemożliwia to wyświetlenie klawiatury ekranowej.
- Użyj
userdebug
lub kompilacjieng
. - Włącz filtrowanie kluczowych zdarzeń:
adb shell settings put secure android.car.ROTARY_KEY_EVENT_FILTER 1
- W poniższej tabeli znajdziesz odpowiedni klucz dla każdej akcji:
Klucz Akcja obrotowa Q Obróć przeciwnie do ruchu wskazówek zegara mi Obróć zgodnie z ruchem wskazówek zegara A Rusz w lewo D Przesuń w prawo W Podnieś się S Przesuń w dół F lub przecinek Środkowy przycisk R lub Esc Przycisk powrotu
Polecenia ADB
Za pomocą poleceń car_service
można wstrzykiwać obrotowe zdarzenia wejściowe. Polecenia te można uruchamiać na urządzeniach z systemem Android Automotive OS (AAOS) lub na emulatorze.
polecenia car_service | Wejście obrotowe |
---|---|
adb shell cmd car_service inject-rotary | Obróć przeciwnie do ruchu wskazówek zegara |
adb shell cmd car_service inject-rotary -c true | Obróć zgodnie z ruchem wskazówek zegara |
adb shell cmd car_service inject-rotary -dt 100 50 | Obróć wielokrotnie w kierunku przeciwnym do ruchu wskazówek zegara (100 ms temu i 50 ms temu) |
adb shell cmd car_service inject-key 282 | Rusz w lewo |
adb shell cmd car_service inject-key 283 | Przesuń w prawo |
adb shell cmd car_service inject-key 280 | Podnieś się |
adb shell cmd car_service inject-key 281 | Przesuń w dół |
adb shell cmd car_service inject-key 23 | Kliknięcie środkowego przycisku |
adb shell input keyevent inject-key 4 | Kliknij przycisk Wstecz |
Sterownik obrotowy OEM
Jest to najbardziej realistyczna opcja, gdy sprzęt kontrolera obrotowego jest uruchomiony. Jest to szczególnie przydatne do testowania szybkiego obrotu.
FocusParkingView
FocusParkingView
to przezroczysty widok w bibliotece Car UI (car-ui-library) . RotaryService
używa go do obsługi nawigacji za pomocą kontrolera obrotowego. FocusParkingView
musi być pierwszym widokiem w układzie, na którym można ustawić fokus. Musi być umieszczony poza wszystkimi obszarami FocusArea
. Każde okno musi mieć jeden FocusParkingView
. Jeśli już korzystasz z układu podstawowego car-ui-library, który zawiera FocusParkingView
, nie musisz dodawać kolejnego FocusParkingView
. Poniżej pokazano przykład FocusParkingView
w 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>
Oto powody, dla których potrzebujesz FocusParkingView
:
- Android nie usuwa automatycznie fokusu, gdy fokus jest ustawiony w innym oknie. Jeśli spróbujesz usunąć fokus w poprzednim oknie, Android ponownie ustawi ostrość widoku w tym oknie, co spowoduje jednoczesne skupienie się na dwóch oknach. Dodanie
FocusParkingView
do każdego okna może rozwiązać ten problem. Ten widok jest przezroczysty, a jego domyślne podświetlenie fokusu jest wyłączone, dzięki czemu jest niewidoczne dla użytkownika, niezależnie od tego, czy jest on skupiony, czy nie. Może przejąć fokus, dzięki czemuRotaryService
może ustawić na nim fokus w celu usunięcia podświetlenia fokusu. - Jeśli w bieżącym oknie znajduje się tylko jeden
FocusArea
, obrócenie kontrolera wFocusArea
powoduje, żeRotaryService
przenosi fokus z widoku po prawej stronie do widoku po lewej stronie (i odwrotnie). Dodanie tego widoku do każdego okna może rozwiązać problem. GdyRotaryService
ustali, że celem fokusu jestFocusParkingView
, może określić, że wkrótce nastąpi zawijanie i w którym momencie unika zawijania, nie przesuwając fokusu. - Gdy pokrętło uruchamia aplikację, system Android ustawia ostrość na pierwszym widoku, który można ustawić, czyli zawsze na widoku
FocusParkingView
.FocusParkingView
określa optymalny widok, na którym należy się skupić, a następnie ustawia ostrość.
Możliwość skupienia widoków
RotaryService
opiera się na istniejącej koncepcji skupienia widoku w środowisku Android, której początki sięgają czasów, gdy telefony miały fizyczne klawiatury i pady kierunkowe. Istniejący atrybut android:nextFocusForward
został ponownie przystosowany do obsługi obrotowej (zobacz Dostosowywanie FocusArea ), ale android:nextFocusLeft
, android:nextFocusRight
, android:nextFocusUp
i android:nextFocusDown
nie są.
RotaryService
skupia się wyłącznie na widokach, na których można się skupić. Niektóre widoki, takie jak Button
s, zwykle można ustawić jako aktywne. Inne, takie jak TextView
i ViewGroup
, zwykle nie są. Klikalne widoki są automatycznie ustawiane jako aktywne, a widoki są automatycznie klikalne, gdy mają odbiornik kliknięcia. Jeśli ta automatyczna logika skutkuje żądaną możliwością skupienia, nie trzeba jawnie ustawiać możliwości skupiania widoku. Jeśli logika automatyczna nie zapewnia pożądanej możliwości skupienia, ustaw atrybut android:focusable
na true
lub false
albo programowo ustaw możliwość skupienia widoku za pomocą View.setFocusable(boolean)
. Aby RotaryService
mogło się na tym skupić, widok MUSI spełniać następujące wymagania:
- Możliwość ustawienia ostrości
- Włączony
- Widoczny
- Mają niezerowe wartości szerokości i wysokości
Jeśli widok nie spełnia wszystkich tych wymagań, na przykład przycisk, na którym można ustawić fokus, ale jest wyłączony, użytkownik nie może użyć pokrętła, aby się na nim skupić. Jeśli chcesz skupić się na wyłączonych widokach, rozważ użycie stanu niestandardowego zamiast android:state_enabled
aby kontrolować sposób wyświetlania widoku bez wskazywania, że Android powinien uważać go za wyłączony. Twoja aplikacja może poinformować użytkownika, dlaczego po dotknięciu widok jest wyłączony. W następnej sekcji wyjaśniono, jak to zrobić.
Stan niestandardowy
Aby dodać stan niestandardowy:
- Aby dodać atrybut niestandardowy do widoku. Na przykład, aby dodać niestandardowy stan
state_rotary_enabled
do klasy widokuCustomView
, użyj:<declare-styleable name="CustomView"> <attr name="state_rotary_enabled" format="boolean" /> </declare-styleable>
- Aby śledzić ten stan, dodaj zmienną instancji do swojego widoku wraz z metodami dostępu:
private boolean mRotaryEnabled; public boolean getRotaryEnabled() { return mRotaryEnabled; } public void setRotaryEnabled(boolean rotaryEnabled) { mRotaryEnabled = rotaryEnabled; }
- Aby odczytać wartość atrybutu podczas tworzenia widoku:
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomView); mRotaryEnabled = a.getBoolean(R.styleable.CustomView_state_rotary_enabled);
- W klasie widoku zastąp metodę
onCreateDrawableState()
, a następnie dodaj stan niestandardowy, jeśli to konieczne. Na przykład:@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; }
- Spraw, aby moduł obsługi kliknięć w widoku działał inaczej w zależności od jego stanu. Na przykład moduł obsługi kliknięcia może nic nie robić lub może wyświetlić toast, gdy
mRotaryEnabled
mafalse
. - Aby przycisk wyglądał na wyłączony, w tle widoku możesz rysować, użyj
app:state_rotary_enabled
zamiastandroid:state_enabled
. Jeśli jeszcze go nie masz, musisz dodać:xmlns:app="http://schemas.android.com/apk/res-auto"
- Jeśli widok jest wyłączony w którymkolwiek układzie, zamień
android:enabled="false"
naapp:state_rotary_enabled="false"
, a następnie dodaj przestrzeń nazwapp
, jak powyżej. - Jeśli Twój widok jest programowo wyłączony, zamień wywołania
setEnabled()
na wywołaniasetRotaryEnabled()
.
Strefa zainteresowania
Użyj FocusAreas
aby podzielić aktywne widoki na bloki, aby ułatwić nawigację i zachować spójność z innymi aplikacjami. Na przykład, jeśli aplikacja ma pasek narzędzi, powinien on znajdować się w oddzielnym obszarze FocusArea
od reszty aplikacji. Paski zakładek i inne elementy nawigacyjne również powinny być oddzielone od reszty aplikacji. Duże listy powinny generalnie mieć swój własny FocusArea
. Jeśli nie, użytkownicy muszą przeglądać całą listę, aby uzyskać dostęp do niektórych widoków.
FocusArea
jest podklasą LinearLayout
w bibliotece car-ui. Gdy ta funkcja jest włączona, FocusArea
rysuje podświetlenie, gdy jeden z jego elementów podrzędnych jest skupiony. Aby dowiedzieć się więcej, zobacz Dostosowywanie podświetlenia fokusu .
Jeśli podczas tworzenia bloku nawigacyjnego w pliku układu zamierzasz używać LinearLayout
jako kontenera dla tego bloku, zamiast tego użyj FocusArea
. W przeciwnym razie zawiń blok w FocusArea
.
NIE zagnieżdżaj FocusArea
w innym FocusArea
. Prowadzi to do niezdefiniowanego zachowania nawigacji. Upewnij się, że wszystkie widoki, na których można się skupić, są zagnieżdżone w FocusArea
.
Przykład FocusArea
w RotaryPlayground
pokazano poniżej:
<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
działa w następujący sposób:
- Podczas obsługi akcji obracania i szturchania
RotaryService
szuka wystąpieńFocusArea
w hierarchii widoków. - Po otrzymaniu zdarzenia rotacji
RotaryService
przenosi fokus do innego widoku, który może skupić się na tym samymFocusArea
. - Po otrzymaniu zdarzenia szturchnięcia
RotaryService
przenosi fokus do innego widoku, który może skupić się na innym (zazwyczaj sąsiadującym)FocusArea
.
Jeśli w układzie nie uwzględnisz żadnych FocusAreas
, widok główny będzie traktowany jako ukryty obszar fokusu. Użytkownik nie może naciskać, aby poruszać się po aplikacji. Zamiast tego będą się one obracać po wszystkich widokach, na których można się skupić, co może być odpowiednie w przypadku okien dialogowych.
Personalizacja obszaru FocusArea
Do dostosowania nawigacji obrotowej można użyć dwóch standardowych atrybutów widoku:
-
android:nextFocusForward
umożliwia twórcom aplikacji określenie kolejności rotacji w obszarze fokusu. Jest to ten sam atrybut, który służy do kontrolowania kolejności tabulacji podczas nawigacji za pomocą klawiatury. NIE używaj tego atrybutu do tworzenia pętli. Zamiast tego użyjapp:wrapAround
(patrz poniżej), aby utworzyć pętlę. -
android:focusedByDefault
umożliwia twórcom aplikacji określenie domyślnego widoku fokusu w oknie. NIE używaj tego atrybutu iapp:defaultFocus
(patrz poniżej) w tym samymFocusArea
.
FocusArea
definiuje również pewne atrybuty umożliwiające dostosowanie nawigacji obrotowej. Za pomocą tych atrybutów nie można dostosowywać ukrytych obszarów fokusu.
- ( Android 11 QPR3, Android 11 Samochód, Android 12 )
app:defaultFocus
może służyć do określenia identyfikatora widoku potomnego, na którym można ustawić fokus, na którym należy się skupić, gdy użytkownik przejdzie do tegoFocusArea
. - ( Android 11 QPR3, Android 11 Samochód, Android 12 )
app:defaultFocusOverridesHistory
można ustawić natrue
, aby widok określony powyżej był aktywny, nawet jeśli z historią wskazującą, że skupiono się na innym widoku w tymFocusArea
. - ( Android 12 )
Użyjapp:nudgeLeftShortcut
,app:nudgeRightShortcut
,app:nudgeUpShortcut
iapp:nudgeDownShortcut
, aby określić identyfikator widoku potomnego, na którym można ustawić fokus, na którym powinien się skupiać, gdy użytkownik przesuwa w danym kierunku. Aby dowiedzieć się więcej, zapoznaj się z treścią skrótów przesuwania poniżej.( Android 11 QPR3, Android 11 Car, przestarzałe w Androidzie 12 )
app:nudgeShortcut
iapp:nudgeShortcutDirection
obsługiwały tylko jeden skrót szturchnięcia. - ( Android 11 QPR3, Android 11 Samochód, Android 12 )
Aby umożliwić obrót w tymFocusArea
,app:wrapAround
można ustawić natrue
. Jest to najczęściej używane, gdy widoki są ułożone w okrąg lub owal. - ( Android 11 QPR3, Android 11 Samochód, Android 12 )
Aby dostosować dopełnienie podświetlenia w tymFocusArea
, użyjapp:highlightPaddingStart
,app:highlightPaddingEnd
,app:highlightPaddingTop
,app:highlightPaddingBottom
,app:highlightPaddingHorizontal
iapp:highlightPaddingVertical
. - ( Android 11 QPR3, Android 11 Samochód, Android 12 )
Aby dostosować postrzegane granice tegoFocusArea
w celu znalezienia celu szturchnięcia, użyjapp:startBoundOffset
,app:endBoundOffset
,app:topBoundOffset
,app:bottomBoundOffset
,app:horizontalBoundOffset
iapp:verticalBoundOffset
. - ( Android 11 QPR3, Android 11 Samochód, Android 12 )
Aby jawnie określić identyfikator sąsiadującegoFocusArea
(lub obszarów) w podanych kierunkach, użyjapp:nudgeLeft
,app:nudgeRight
,app:nudgeUp
iapp:nudgeDown
. Użyj tej opcji, jeśli domyślnie używane wyszukiwanie geometryczne nie pozwala znaleźć żądanego celu.
Podsuwanie zwykle powoduje nawigację pomiędzy obszarami ostrości. Jednak w przypadku skrótów przesuwania przesuwanie czasami najpierw powoduje nawigację w obrębie FocusArea
, więc może być konieczne dwukrotne szturchnięcie, aby przejść do następnego FocusArea
. Skróty przesuwania są przydatne, gdy FocusArea
zawiera długą listę, po której następuje pływający przycisk akcji , jak w poniższym przykładzie:
Bez skrótu szturchnięcia użytkownik musiałby przeglądać całą listę, aby dotrzeć do FAB.
Dostosowywanie podświetlenia ostrości
Jak wspomniano powyżej, RotaryService
opiera się na istniejącej koncepcji skupienia widoku w środowisku Android. Kiedy użytkownik obraca się i szturcha, RotaryService
przesuwa fokus, skupiając jeden widok i tracąc ostrość na innym. W systemie Android, gdy widok jest skupiony, jeśli widok:
- Określił własne wyróżnienie fokusu, Android rysuje wyróżnienie fokusu widoku.
- Nie określa podświetlenia fokusu, a domyślne wyróżnienie fokusu nie jest wyłączone, system Android rysuje domyślne wyróżnienie fokusu dla widoku.
Aplikacje przeznaczone do obsługi dotyku zwykle nie określają odpowiednich podświetleń.
Domyślne podświetlenie fokusu jest zapewniane przez platformę Android i może zostać zastąpione przez producenta OEM. Twórcy aplikacji otrzymują go, gdy motyw, którego używają, pochodzi z Theme.DeviceDefault
.
Aby zapewnić spójne wrażenia użytkownika, w miarę możliwości polegaj na domyślnym podświetleniu fokusu. Jeśli potrzebujesz podświetlenia o niestandardowym kształcie (na przykład okrągłego lub pigułkowego) lub jeśli używasz motywu niepochodzącego z Theme.DeviceDefault
, użyj zasobów car-ui-library, aby określić własne wyróżnienie fokusu każdy widok.
Aby określić niestandardowe podświetlenie widoku, zmień tło lub element rysunkowy pierwszego planu widoku na rysowalny, który różni się, gdy widok jest skupiony. Zwykle zmieniasz tło. Poniższy rysunek, jeśli zostanie użyty jako tło dla widoku kwadratowego, tworzy okrągłe podświetlenie:
<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 ) Pogrubione odniesienia do zasobów w powyższym przykładzie identyfikują zasoby zdefiniowane przez bibliotekę car-ui. Producent OEM zastępuje je, aby zachować zgodność z określonym przez nich domyślnym podświetleniem fokusu. Dzięki temu kolor podświetlenia fokusu, szerokość obrysu itd. nie zmienią się, gdy użytkownik będzie przechodzić między widokiem z niestandardowym podświetleniem fokusu a widokiem z domyślnym podświetleniem fokusu. Ostatnim elementem jest zmarszczka służąca do dotyku. Domyślne wartości użyte dla zasobów pogrubionych wyglądają następująco:
Ponadto niestandardowe wyróżnienie fokusu jest wymagane, gdy przycisk ma jednolity kolor tła, aby zwrócić na niego uwagę użytkownika, jak w przykładzie poniżej. Może to sprawić, że podkreślenie ostrości będzie trudne do zauważenia. W tej sytuacji określ niestandardowe wyróżnienie fokusu za pomocą kolorów dodatkowych :
- ( Android 11 QPR3, Android 11 Samochód, 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
Na przykład:
Skoncentrowany, a nie przyciśnięty | Skoncentrowany, przyciśnięty |
Przewijanie obrotowe
Jeśli Twoja aplikacja używa RecyclerView
s, zamiast tego powinieneś używać CarUiRecyclerView
s. Gwarantuje to, że Twój interfejs użytkownika będzie spójny z innymi, ponieważ dostosowanie OEM dotyczy wszystkich CarUiRecyclerView
.
Jeśli na wszystkich elementach listy można ustawić fokus, nie musisz robić nic więcej. Nawigacja obrotowa przesuwa fokus pomiędzy elementami na liście, a lista przewija się, aby nowo wybrany element był widoczny.
( Android 11 QPR3, Android 11 Samochód, Android 12 )
Jeśli występuje mieszanka elementów, na których można ustawić fokus i na których nie można ustawić ostrości, lub jeśli na wszystkich elementach nie można ustawić ostrości, można włączyć przewijanie obrotowe, co pozwala użytkownikowi używać kontrolera obrotowego do stopniowego przewijania listy bez pomijania elementów, na których nie można ustawić ostrości. Aby włączyć przewijanie obrotowe, ustaw atrybut app:rotaryScrollEnabled
na true
.
( Android 11 QPR3, Android 11 Samochód, Android 12 )
Możesz włączyć przewijanie obrotowe w dowolnym przewijanym widoku, w tym av CarUiRecyclerView
, za pomocą metody setRotaryScrollEnabled()
w CarUiUtils
. Jeśli to zrobisz, musisz:
- Skoncentruj przewijalny widok, aby można było się na nim skupić, gdy żaden z jego możliwych do skupienia widoków potomnych nie jest widoczny,
- Wyłącz domyślne wyróżnienie fokusu w przewijanym widoku, wywołując
setDefaultFocusHighlightEnabled(false)
, aby przewijany widok nie wydawał się skupiony, - Upewnij się, że przewijany widok jest skupiony przed jego potomkami, wywołując
setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS)
. - Nasłuchuj zdarzeń MotionEvents za pomocą
SOURCE_ROTARY_ENCODER
iAXIS_VSCROLL
lubAXIS_HSCROLL
, aby wskazać odległość do przewinięcia i kierunek (przez znak).
Gdy przewijanie obrotowe jest włączone w CarUiRecyclerView
, a użytkownik obraca się do obszaru, w którym nie ma widoków, na których można ustawić fokus, pasek przewijania zmienia się z szarego na niebieski, jakby wskazywał, że pasek przewijania jest aktywny. Jeśli chcesz, możesz zastosować podobny efekt.
Zdarzenia MotionEvents są takie same, jak te generowane przez kółko przewijania myszy, z wyjątkiem źródła.
Tryb bezpośredniej manipulacji
Zwykle przesuwanie i obrót umożliwiają poruszanie się po interfejsie użytkownika, podczas gdy naciśnięcie środkowego przycisku powoduje wykonanie akcji, chociaż nie zawsze tak się dzieje. Na przykład, jeśli użytkownik chce dostosować głośność alarmu, może użyć kontrolera obrotowego, aby przejść do suwaka głośności, nacisnąć środkowy przycisk, obrócić kontroler, aby dostosować głośność alarmu, a następnie nacisnąć przycisk Wstecz, aby powrócić do nawigacji . Nazywa się to trybem bezpośredniej manipulacji (DM) . W tym trybie pokrętło służy do bezpośredniej interakcji z widokiem, a nie do nawigacji.
Zaimplementuj DM na jeden z dwóch sposobów. Jeśli potrzebujesz tylko obsłużyć obrót, a widok, którym chcesz manipulować, odpowiednio reaguje na AccessibilityEvent
ACTION_SCROLL_FORWARD
i ACTION_SCROLL_BACKWARD
, użyj prostego mechanizmu. W przeciwnym razie skorzystaj z zaawansowanego mechanizmu.
Prosty mechanizm jest jedyną opcją w oknach systemowych; aplikacje mogą korzystać z obu mechanizmów.
Prosty mechanizm
( Android 11 QPR3, Android 11 Samochód, Android 12 )
Twoja aplikacja powinna wywołać DirectManipulationHelper.setSupportsRotateDirectly(View view, boolean enable)
. RotaryService
rozpoznaje, kiedy użytkownik znajduje się w trybie DM i przechodzi do trybu DM, gdy użytkownik naciśnie środkowy przycisk, gdy widok jest aktywny. W trybie DM obroty wykonują ACTION_SCROLL_FORWARD
lub ACTION_SCROLL_BACKWARD
i opuszczają tryb DM, gdy użytkownik naciśnie przycisk Wstecz. Prosty mechanizm przełącza wybrany stan widoku podczas wchodzenia i wychodzenia z trybu DM.
Aby zapewnić wizualną wskazówkę, że użytkownik znajduje się w trybie DM, po wybraniu zmień wygląd widoku. Na przykład zmień tło, gdy android:state_selected
ma true
.
Zaawansowany mechanizm
Aplikacja określa, kiedy RotaryService
wchodzi i wychodzi z trybu DM. Aby zapewnić spójne doświadczenie użytkownika, naciśnięcie środkowego przycisku przy widoku DM powinno spowodować przejście do trybu DM, a przycisk Wstecz powinien zakończyć tryb DM. Jeśli środkowy przycisk i/lub szturchnięcie nie są używane, mogą to być alternatywne sposoby wyjścia z trybu DM. W przypadku aplikacji takich jak Mapy do przejścia do trybu DM można użyć przycisku reprezentującego DM.
Aby obsługiwać zaawansowany tryb DM, widok:
- ( Android 11 QPR3, Android 11 Car, Android 12 ) MUSI nasłuchiwać zdarzenia
KEYCODE_DPAD_CENTER
, aby wejść w tryb DM i nasłuchiwać zdarzeniaKEYCODE_BACK
, aby wyjść z trybu DM, wywołując w każdym przypadkuDirectManipulationHelper.enableDirectManipulationMode()
. Aby nasłuchiwać tych zdarzeń, wykonaj jedną z następujących czynności:- Zarejestruj
OnKeyListener
. Lub, - Rozszerz widok, a następnie zastąp jego metodę
dispatchKeyEvent()
.
- Zarejestruj
- POWINNO nasłuchiwać zdarzeń szturchnięć (
KEYCODE_DPAD_UP
,KEYCODE_DPAD_DOWN
,KEYCODE_DPAD_LEFT
lubKEYCODE_DPAD_RIGHT
), jeśli widok powinien obsługiwać szturchnięcia. - POWINNO nasłuchiwać zdarzeń
MotionEvent
i uzyskać liczbę rotacji wAXIS_SCROLL
, jeśli widok chce obsłużyć obrót. Można to zrobić na kilka sposobów:- Zarejestruj
OnGenericMotionListener
. - Rozszerz widok i zastąp jego metodę
dispatchTouchEvent()
.
- Zarejestruj
- Aby uniknąć utknięcia w trybie DM, MUSISZ wyjść z trybu DM, gdy fragment lub działanie, do którego należy widok, nie jest interaktywne.
- POWINNO zapewniać wizualną wskazówkę wskazującą, że widok jest w trybie DM.
Poniżej znajduje się przykład widoku niestandardowego, który wykorzystuje tryb DM do przesuwania i powiększania mapy:
/** 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(); }
Więcej przykładów można znaleźć w projekcie RotaryPlayground
.
Widok aktywności
Podczas korzystania z ActivityView:
-
ActivityView
nie powinien umożliwiać fokusu. - ( Android 11 QPR3, Android 11 Car, przestarzałe w Androidzie 11 )
ZawartośćActivityView
MUSI zawieraćFocusParkingView
jako pierwszy widok, na którym można się skupić, a jegoapp:shouldRestoreFocus
MUSI mieć wartośćfalse
. - Zawartość
ActivityView
nie powinna zawierać widokówandroid:focusByDefault
.
Dla użytkownika Widoki Aktywności nie powinny mieć żadnego wpływu na nawigację, z wyjątkiem tego, że obszary fokusu nie mogą obejmować Widoków Aktywności. Innymi słowy, nie można mieć jednego obszaru skupienia zawierającego treść wewnątrz i na zewnątrz ActivityView
. Jeśli nie dodasz żadnego FocusAreas do ActivityView
, katalog główny hierarchii widoków w ActivityView
będzie uważany za niejawny obszar fokusu.
Przyciski działające po przytrzymaniu
Większość przycisków powoduje pewne działanie po kliknięciu. Zamiast tego niektóre przyciski działają po ich przytrzymaniu. Na przykład przyciski szybkiego przewijania do przodu i przewijania do tyłu zazwyczaj działają, gdy są przytrzymywane. Aby takie przyciski obsługiwały obroty, nasłuchuj KeyEvents
KEYCODE_DPAD_CENTER
w następujący sposób:
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; });
W którym mRunnable
podejmuje akcję (taką jak przewijanie) i planuje uruchomienie się z opóźnieniem.
Tryb dotykowy
Użytkownicy mogą używać kontrolera obrotowego do interakcji z radioodtwarzaczem w samochodzie na dwa sposoby: za pomocą kontrolera obrotowego lub dotykając ekranu. Podczas korzystania z pokrętła podświetlony jest jeden z widoków, na których można ustawić ostrość. Po dotknięciu ekranu nie pojawia się podświetlenie ostrości. Użytkownik może w dowolnym momencie przełączać się pomiędzy trybami wprowadzania danych:
- Obróć → dotknij. Gdy użytkownik dotknie ekranu, podświetlenie ostrości znika.
- Dotknij → obrotowy. Gdy użytkownik szturchnie, obróci lub naciśnie środkowy przycisk, pojawi się podświetlenie fokusu.
Przyciski Wstecz i Strona główna nie mają wpływu na tryb wprowadzania.
Rotary opiera się na istniejącej koncepcji trybu dotykowego Androida. Za pomocą View.isInTouchMode()
można określić, jakiego trybu wprowadzania danych używa użytkownik. Do nasłuchiwania zmian możesz użyć OnTouchModeChangeListener
. Chociaż można to wykorzystać do dostosowania interfejsu użytkownika do bieżącego trybu wprowadzania, należy unikać większych zmian, ponieważ mogą one wprowadzać w błąd.
Rozwiązywanie problemów
W aplikacjach przeznaczonych do obsługi dotyku często stosuje się zagnieżdżone widoki, na których można się skupiać. Na przykład wokół ImageButton
może znajdować się FrameLayout
, z których oba można ustawić. Nie szkodzi to dotykowi, ale może powodować pogorszenie komfortu korzystania z funkcji obrotowej, ponieważ użytkownik musi dwukrotnie obrócić kontroler, aby przejść do następnego widoku interaktywnego. Aby zapewnić wygodę użytkownika, Google zaleca ustawienie skupienia widoku zewnętrznego lub widoku wewnętrznego, ale nie obu.
Jeśli przycisk lub przełącznik straci ostrość po naciśnięciu za pomocą pokrętła, może mieć zastosowanie jeden z poniższych warunków:
- Przycisk lub przełącznik jest blokowany (na krótko lub na czas nieokreślony) w wyniku naciśnięcia przycisku. W obu przypadkach można temu zaradzić na dwa sposoby:
- Pozostaw stan
android:enabled
jakotrue
i użyj stanu niestandardowego, aby wyszarzić przycisk lub przełączyć, jak opisano w Custom State . - Użyj kontenera, aby otoczyć przycisk lub przełącznik i ustawić kontener, na którym można się skupić zamiast przycisku lub przełącznika. (Odbiornik kliknięcia musi znajdować się w kontenerze.)
- Pozostaw stan
- Przycisk lub przełącznik jest wymieniany. Na przykład akcja wykonywana po naciśnięciu przycisku lub przełączeniu przełącznika może spowodować odświeżenie dostępnych akcji, powodując zastąpienie istniejących przycisków nowymi. Można temu zaradzić na dwa sposoby:
- Zamiast tworzyć nowy przycisk lub przełącznik, ustaw ikonę i/lub tekst istniejącego przycisku lub przełącznika.
- Jak powyżej, dodaj możliwy do skupienia kontener wokół przycisku lub przełącznika.
Obrotowy plac zabaw
RotaryPlayground
to aplikacja referencyjna dla urządzeń obrotowych. Skorzystaj z niego, aby dowiedzieć się, jak zintegrować funkcje obrotowe ze swoimi aplikacjami. RotaryPlayground
jest zawarty w kompilacjach emulatorów i kompilacjach dla urządzeń z systemem operacyjnym Android Automotive OS (AAOS).
- Repozytorium
RotaryPlayground
:packages/apps/Car/tests/RotaryPlayground/
- Wersje: Android 11 QPR3, Android 11 Car i Android 12
Aplikacja RotaryPlayground
wyświetla po lewej stronie następujące karty:
- Karty. Przetestuj poruszanie się po obszarach fokusu, pomijanie elementów, na których nie można ustawić ostrości, oraz wprowadzanie tekstu.
- Bezpośrednia manipulacja. Testuj widżety obsługujące prosty i zaawansowany tryb bezpośredniej manipulacji. Ta zakładka służy specjalnie do bezpośredniej manipulacji w oknie aplikacji.
- Manipulacja interfejsem użytkownika systemu. Testuj widżety obsługujące bezpośrednią manipulację w oknach systemowych, w których obsługiwany jest tylko prosty tryb bezpośredniej manipulacji.
- Siatka. Przetestuj nawigację obrotową według wzoru Z z przewijaniem.
- Powiadomienie. Przetestuj włączanie i wyłączanie powiadomień heads-up.
- Zwój. Przetestuj przewijanie mieszanki treści, na których można się skupić i których nie można skupić.
- Widok sieciowy. Przetestuj nawigację po łączach w
WebView
. - Niestandardowy
FocusArea
. Przetestuj dostosowywanieFocusArea
:- Owinąć.
-
android:focusedByDefault
iapp:defaultFocus
. - Wyraźne cele szturchania.
- Poruszaj skrótami.
-
FocusArea
bez widoków, na których można się skupić.