Materi berikut ini untuk pengembang aplikasi.
Untuk membuat dukungan aplikasi Anda berputar, Anda HARUS:
- Tempatkan
FocusParkingView
di tata letak aktivitas masing-masing. - Pastikan tampilan yang (atau tidak) dapat difokuskan.
- Gunakan
FocusArea
s untuk membungkus semua tampilan yang dapat difokuskan, kecualiFocusParkingView
.
Masing-masing tugas ini dirinci di bawah ini, setelah Anda mengatur lingkungan Anda untuk mengembangkan aplikasi yang mendukung putaran.
Siapkan pengontrol putar
Sebelum Anda dapat mulai mengembangkan aplikasi berkemampuan putar, Anda memerlukan pengontrol putar atau stand-in. Anda memiliki opsi yang dijelaskan di bawah ini.
emulator
source build/envsetup.sh && lunch car_x86_64-userdebug m -j emulator -wipe-data -no-snapshot -writable-system
Anda juga dapat menggunakan aosp_car_x86_64-userdebug
.
Untuk mengakses pengontrol putar yang diemulasi:
- Ketuk tiga titik di bagian bawah bilah alat:
- Pilih Putar mobil di jendela kontrol yang diperluas:
Keyboard USB
- Colokkan keyboard USB ke perangkat Anda yang menjalankan Android Automotive OS (AAOS), Dalam beberapa kasus, ini dapat mencegah munculnya keyboard di layar.
- Gunakan
userdebug
ataueng
build. - Aktifkan pemfilteran acara utama:
adb shell settings put secure android.car.ROTARY_KEY_EVENT_FILTER 1
- Lihat tabel di bawah untuk menemukan kunci yang sesuai untuk setiap tindakan:
Kunci Aksi putar Q Putar berlawanan arah jarum jam E Putar searah jarum jam SEBUAH Geser ke kiri D Geser ke kanan W Dorong ke atas S Dorong ke bawah F atau Koma tombol tengah R atau Esc Tombol kembali
perintah ADB
Anda dapat menggunakan perintah car_service
untuk menyuntikkan peristiwa input putar. Perintah ini dapat dijalankan di perangkat yang menjalankan Android Automotive OS (AAOS) atau di emulator.
perintah car_service | Masukan putar |
---|---|
adb shell cmd car_service inject-rotary | Putar berlawanan arah jarum jam |
adb shell cmd car_service inject-rotary -c true | Putar searah jarum jam |
adb shell cmd car_service inject-rotary -dt 100 50 | Putar berlawanan arah jarum jam beberapa kali (100 mdtk lalu dan 50 mdtk lalu) |
adb shell cmd car_service inject-key 282 | Geser ke kiri |
adb shell cmd car_service inject-key 283 | Geser ke kanan |
adb shell cmd car_service inject-key 280 | Dorong ke atas |
adb shell cmd car_service inject-key 281 | Dorong ke bawah |
adb shell cmd car_service inject-key 23 | Klik tombol tengah |
adb shell input keyevent inject-key 4 | Klik tombol kembali |
Pengontrol putar OEM
Saat perangkat keras pengontrol putar Anda aktif dan berjalan, ini adalah opsi yang paling realistis. Ini sangat berguna untuk menguji rotasi cepat.
FocusParkingView
FocusParkingView
adalah tampilan transparan di Car UI Library (car-ui-library) . RotaryService
menggunakannya untuk mendukung navigasi pengontrol putar. FocusParkingView
harus menjadi tampilan fokus pertama dalam tata letak. Itu harus ditempatkan di luar semua FocusArea
s. Setiap jendela harus memiliki satu FocusParkingView
. Jika Anda sudah menggunakan tata letak dasar car-ui-library, yang berisi FocusParkingView
, Anda tidak perlu menambahkan FocusParkingView
lain. Di bawah ini adalah contoh FocusParkingView
di 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>
Berikut adalah alasan Anda membutuhkan FocusParkingView
:
- Android tidak menghapus fokus secara otomatis saat fokus diatur di jendela lain. Jika Anda mencoba menghapus fokus di jendela sebelumnya, Android akan memfokuskan kembali tampilan di jendela itu, yang mengakibatkan dua jendela difokuskan secara bersamaan. Menambahkan
FocusParkingView
ke setiap jendela dapat memperbaiki masalah ini. Tampilan ini transparan dan sorotan fokus default dinonaktifkan, sehingga tidak terlihat oleh pengguna, baik fokus atau tidak. Ini dapat mengambil fokus sehinggaRotaryService
dapat memarkir fokus di atasnya untuk menghapus sorotan fokus. - Jika hanya ada satu
FocusArea
di jendela saat ini, memutar pengontrol diFocusArea
menyebabkanRotaryService
memindahkan fokus dari tampilan di kanan ke tampilan di kiri (dan sebaliknya). Menambahkan tampilan ini ke setiap jendela dapat memperbaiki masalah. KetikaRotaryService
menentukan target fokus adalahFocusParkingView
, RotaryService dapat menentukan wrap-around akan terjadi pada titik mana ia menghindari wrap-around dengan tidak memindahkan fokus. - Saat kontrol putar meluncurkan aplikasi, Android memfokuskan tampilan pertama yang dapat difokuskan, yang selalu berupa
FocusParkingView
.FocusParkingView
menentukan tampilan optimal untuk difokuskan dan kemudian menerapkan fokus.
Pandangan yang dapat difokuskan
RotaryService
dibangun di atas konsep fokus tampilan kerangka kerja Android yang ada , sejak ponsel memiliki keyboard fisik dan D-pad. Atribut android:nextFocusForward
yang ada digunakan ulang untuk rotasi (lihat penyesuaian FocusArea ), tetapi android:nextFocusLeft
, android:nextFocusRight
, android:nextFocusUp
, dan android:nextFocusDown
tidak.
RotaryService
hanya berfokus pada tampilan yang dapat difokuskan. Beberapa tampilan, seperti Button
s, biasanya dapat difokuskan. Lainnya, seperti TextView
s dan ViewGroup
s, biasanya tidak. Tampilan yang dapat diklik secara otomatis dapat difokuskan dan tampilan dapat diklik secara otomatis bila memiliki pendengar klik. Jika logika otomatis ini menghasilkan kemampuan fokus yang diinginkan, Anda tidak perlu secara eksplisit mengatur kemampuan fokus tampilan. Jika logika otomatis tidak menghasilkan kemampuan fokus yang diinginkan, setel atribut android:focusable
ke true
atau false
, atau secara terprogram setel kemampuan fokus tampilan dengan View.setFocusable(boolean)
. Agar RotaryService
fokus padanya, tampilan HARUS memenuhi persyaratan berikut:
- dapat fokus
- Diaktifkan
- Terlihat
- Memiliki nilai bukan nol untuk lebar dan tinggi
Jika tampilan tidak memenuhi semua persyaratan ini, misalnya Tombol yang dapat difokuskan tetapi dinonaktifkan, pengguna tidak dapat menggunakan kontrol putar untuk memfokuskannya. Jika Anda ingin fokus pada tampilan yang dinonaktifkan, pertimbangkan untuk menggunakan status khusus daripada android:state_enabled
untuk mengontrol tampilan tampilan tanpa menunjukkan bahwa Android harus menganggapnya dinonaktifkan. Aplikasi Anda dapat memberi tahu pengguna mengapa tampilan dinonaktifkan saat diketuk. Bagian selanjutnya menjelaskan cara melakukannya.
Status kustom
Untuk menambahkan status khusus:
- Untuk menambahkan atribut khusus ke tampilan Anda. Misalnya, untuk menambahkan status kustom
state_rotary_enabled
ke kelas tampilanCustomView
, gunakan:<declare-styleable name="CustomView"> <attr name="state_rotary_enabled" format="boolean" /> </declare-styleable>
- Untuk melacak status ini, tambahkan variabel instan ke tampilan Anda bersama dengan metode pengakses:
private boolean mRotaryEnabled; public boolean getRotaryEnabled() { return mRotaryEnabled; } public void setRotaryEnabled(boolean rotaryEnabled) { mRotaryEnabled = rotaryEnabled; }
- Untuk membaca nilai atribut Anda saat tampilan Anda dibuat:
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomView); mRotaryEnabled = a.getBoolean(R.styleable.CustomView_state_rotary_enabled);
- Di kelas tampilan Anda, timpa metode
onCreateDrawableState()
lalu tambahkan status kustom, bila perlu. Misalnya:@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; }
- Jadikan penangan klik tampilan Anda bekerja secara berbeda bergantung pada statusnya. Misalnya, pengendali klik mungkin tidak melakukan apa-apa atau mungkin muncul bersulang saat
mRotaryEnabled
false
. - Untuk membuat tombol tampak dinonaktifkan, di latar belakang tampilan Anda yang dapat digambar, gunakan
app:state_rotary_enabled
alih-alihandroid:state_enabled
. Jika Anda belum memilikinya, Anda perlu menambahkan:xmlns:app="http://schemas.android.com/apk/res-auto"
- Jika tampilan Anda dinonaktifkan di tata letak apa pun, ganti
android:enabled="false"
denganapp:state_rotary_enabled="false"
lalu tambahkan ruang namaapp
, seperti di atas. - Jika tampilan Anda dinonaktifkan secara terprogram, ganti panggilan ke
setEnabled()
dengan panggilan kesetRotaryEnabled()
.
Area fokus
Gunakan FocusAreas
untuk mempartisi tampilan yang dapat difokuskan ke dalam blok agar navigasi lebih mudah dan konsisten dengan aplikasi lain. Misalnya, jika aplikasi Anda memiliki toolbar, toolbar harus berada di FocusArea
yang terpisah dari aplikasi Anda yang lain. Bilah tab dan elemen navigasi lainnya juga harus dipisahkan dari aplikasi lainnya. Daftar besar umumnya harus memiliki FocusArea
sendiri. Jika tidak, pengguna harus memutar seluruh daftar untuk mengakses beberapa tampilan.
FocusArea
adalah subkelas LinearLayout
di perpustakaan mobil-ui. Saat fitur ini diaktifkan, FocusArea
akan menarik sorotan saat salah satu turunannya difokuskan. Untuk mempelajari lebih lanjut, lihat Penyesuaian sorotan fokus .
Saat membuat blok navigasi di file tata letak, jika Anda bermaksud menggunakan LinearLayout
sebagai wadah untuk blok itu, gunakan FocusArea
sebagai gantinya. Jika tidak, bungkus blok dalam FocusArea
.
JANGAN menyarangkan FocusArea
di FocusArea
lain. Melakukannya akan menyebabkan perilaku navigasi yang tidak ditentukan. Pastikan bahwa semua tampilan yang dapat difokuskan bersarang di dalam FocusArea
.
Contoh FocusArea
di RotaryPlayground
ditunjukkan di bawah ini:
<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
berfungsi sebagai berikut:
- Saat menangani tindakan rotate dan nudge,
RotaryService
mencari instanceFocusArea
dalam hierarki tampilan. - Saat menerima peristiwa rotasi,
RotaryService
memindahkan fokus ke Tampilan lain yang dapat mengambil fokus diFocusArea
yang sama. - Saat menerima acara nudge,
RotaryService
memindahkan fokus ke tampilan lain yang dapat mengambil fokus diFocusArea
lain (biasanya berdekatan).
Jika Anda tidak menyertakan FocusAreas
apa pun dalam tata letak Anda, tampilan root diperlakukan sebagai area fokus implisit. Pengguna tidak dapat mendorong untuk menavigasi di aplikasi. Sebagai gantinya, mereka akan memutar melalui semua tampilan yang dapat difokuskan, yang mungkin memadai untuk dialog.
Kustomisasi Area Fokus
Dua atribut Tampilan standar dapat digunakan untuk menyesuaikan navigasi putar:
-
android:nextFocusForward
memungkinkan pengembang aplikasi untuk menentukan urutan rotasi di area fokus. Ini adalah atribut yang sama yang digunakan untuk mengontrol urutan Tab untuk navigasi keyboard. JANGAN gunakan atribut ini untuk membuat loop. Sebagai gantinya, gunakanapp:wrapAround
(lihat di bawah) untuk membuat loop. -
android:focusedByDefault
memungkinkan pengembang aplikasi untuk menentukan tampilan fokus default di jendela. JANGAN gunakan atribut ini danapp:defaultFocus
(lihat di bawah) diFocusArea
yang sama.
FocusArea
juga mendefinisikan beberapa atribut untuk menyesuaikan navigasi putar. Area fokus implisit tidak dapat disesuaikan dengan atribut ini.
- ( Android 11 QPR3, Android 11 Mobil, Android 12 )
app:defaultFocus
dapat digunakan untuk menentukan ID tampilan turunan yang dapat difokuskan, yang harus difokuskan saat pengguna mengarahkan keFocusArea
ini. - ( Android 11 QPR3, Android 11 Mobil, Android 12 )
app:defaultFocusOverridesHistory
dapat disetel ketrue
untuk membuat tampilan yang ditentukan di atas menjadi fokus meskipun dengan riwayat untuk menunjukkan tampilan lain diFocusArea
ini telah difokuskan. - ( Android 12 )
Gunakanapp:nudgeLeftShortcut
,app:nudgeRightShortcut
,app:nudgeUpShortcut
, danapp:nudgeDownShortcut
untuk menentukan ID tampilan turunan yang dapat difokuskan, yang harus difokuskan saat pengguna mengarahkan ke arah tertentu. Untuk mempelajari lebih lanjut, lihat konten untuk pintasan nudge di bawah.( Android 11 QPR3, Android 11 Car, tidak digunakan lagi di Android 12 )
app:nudgeShortcut
danapp:nudgeShortcutDirection
hanya mendukung satu pintasan nudge. - ( Android 11 QPR3, Android 11 Mobil, Android 12 )
Untuk mengaktifkan rotasi untuk membungkus diFocusArea
ini,app:wrapAround
dapat disetel ketrue
. Ini paling sering digunakan saat tampilan diatur dalam lingkaran atau oval. - ( Android 11 QPR3, Android 11 Mobil, Android 12 )
Untuk menyesuaikan padding sorotan diFocusArea
ini, gunakanapp:highlightPaddingStart
,app:highlightPaddingEnd
,app:highlightPaddingTop
,app:highlightPaddingBottom
,app:highlightPaddingHorizontal
, danapp:highlightPaddingVertical
. - ( Android 11 QPR3, Android 11 Mobil, Android 12 )
Untuk menyesuaikan batas yang dirasakan dariFocusArea
ini untuk menemukan target dorongan, gunakanapp:startBoundOffset
,app:endBoundOffset
,app:topBoundOffset
,app:bottomBoundOffset
,app:horizontalBoundOffset
, danapp:verticalBoundOffset
. - ( Android 11 QPR3, Android 11 Mobil, Android 12 )
Untuk secara eksplisit menentukan IDFocusArea
(atau area) yang berdekatan dalam arah yang diberikan, gunakanapp:nudgeLeft
,app:nudgeRight
,app:nudgeUp
, danapp:nudgeDown
. Gunakan ini ketika pencarian geometris yang digunakan secara default tidak menemukan target yang diinginkan.
Menyenggol biasanya menavigasi antara FocusArea. Namun dengan pintasan nudge, nudge terkadang menavigasi terlebih dahulu dalam FocusArea
sehingga pengguna mungkin perlu mendorong dua kali untuk menavigasi ke FocusArea
berikutnya. Pintasan dorong berguna saat FocusArea
berisi daftar panjang yang diikuti oleh Tombol Tindakan Mengambang , seperti pada contoh di bawah ini:
Tanpa pintasan nudge, pengguna harus memutar seluruh daftar untuk mencapai FAB.
Kustomisasi sorotan fokus
Seperti disebutkan di atas, RotaryService
dibangun di atas konsep fokus tampilan kerangka kerja Android yang ada. Saat pengguna memutar dan menyenggol, RotaryService
memindahkan fokus, memfokuskan satu tampilan dan tidak memfokuskan yang lain. Di Android, saat tampilan difokuskan, jika tampilan:
- telah menentukan sorotan fokusnya sendiri, Android menggambar sorotan fokus tampilan.
- tidak menentukan sorotan fokus, dan sorotan fokus default tidak dinonaktifkan, Android menggambar sorotan fokus default untuk tampilan.
Aplikasi yang dirancang untuk sentuhan biasanya tidak menentukan sorotan fokus yang sesuai.
Sorotan fokus default disediakan oleh kerangka kerja Android dan dapat diganti oleh OEM. Pengembang aplikasi menerimanya saat tema yang mereka gunakan berasal dari Theme.DeviceDefault
.
Untuk pengalaman pengguna yang konsisten, andalkan sorotan fokus default bila memungkinkan. Jika Anda memerlukan sorotan fokus berbentuk khusus (misalnya, bulat atau pil), atau jika Anda menggunakan tema yang tidak berasal dari Theme.DeviceDefault
, gunakan sumber daya perpustakaan-mobil untuk menentukan sorotan fokus Anda sendiri untuk setiap tampilan.
Untuk menentukan sorotan fokus kustom untuk tampilan, ubah sumber daya dapat digambar untuk latar belakang atau latar depan tampilan menjadi sumber daya dapat digambar yang berbeda saat tampilan difokuskan. Biasanya, Anda akan mengubah latar belakang. Sumber daya dapat digambar berikut, jika digunakan sebagai latar belakang untuk tampilan persegi, menghasilkan sorotan fokus bulat:
<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 ) Referensi sumber daya yang dicetak tebal dalam contoh di atas mengidentifikasi sumber daya yang ditentukan oleh car-ui-library. OEM menimpa ini agar konsisten dengan sorotan fokus default yang mereka tentukan. Ini memastikan bahwa warna sorotan fokus, lebar goresan, dan seterusnya tidak berubah saat pengguna menavigasi antara tampilan dengan sorotan fokus khusus dan tampilan dengan sorotan fokus default. Item terakhir adalah riak yang digunakan untuk sentuhan. Nilai default yang digunakan untuk sumber tebal muncul sebagai berikut:
Selain itu, sorotan fokus khusus dipanggil saat tombol diberi warna latar belakang yang solid untuk menarik perhatian pengguna, seperti dalam contoh di bawah ini. Hal ini dapat membuat sorotan fokus sulit dilihat. Dalam situasi ini, tentukan sorotan fokus kustom menggunakan warna sekunder :
- ( Android 11 QPR3, Android 11 Mobil, 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
Sebagai contoh:
Fokus, tidak ditekan | Fokus, ditekan |
Pengguliran putar
Jika aplikasi Anda menggunakan RecyclerView
s, Anda HARUS menggunakan CarUiRecyclerView
s sebagai gantinya. Ini memastikan bahwa UI Anda konsisten dengan yang lain karena penyesuaian OEM berlaku untuk semua CarUiRecyclerView
s.
Jika elemen dalam daftar Anda semuanya dapat difokuskan, Anda tidak perlu melakukan hal lain. Navigasi putar memindahkan fokus melalui elemen dalam daftar dan daftar menggulir untuk membuat elemen fokus baru terlihat.
( Android 11 QPR3, Android 11 Mobil, Android 12 )
Jika ada campuran elemen yang dapat difokuskan dan tidak dapat difokuskan, atau jika semua elemen tidak dapat difokuskan, Anda dapat mengaktifkan pengguliran putar, yang memungkinkan pengguna menggunakan pengontrol putar untuk menggulir daftar secara bertahap tanpa melewatkan item yang tidak dapat difokuskan. Untuk mengaktifkan pengguliran putar, setel atribut app:rotaryScrollEnabled
ke true
.
( Android 11 QPR3, Android 11 Mobil, Android 12 )
Anda dapat mengaktifkan pengguliran putar dalam tampilan apa pun yang dapat digulir, termasuk av CarUiRecyclerView
, dengan metode setRotaryScrollEnabled()
di CarUiUtils
. Jika Anda melakukannya, Anda perlu:
- Jadikan tampilan yang dapat digulir dapat difokuskan sehingga dapat difokuskan saat tidak ada tampilan turunan yang dapat difokuskan yang terlihat,
- Nonaktifkan sorotan fokus default pada tampilan yang dapat digulir dengan memanggil
setDefaultFocusHighlightEnabled(false)
sehingga tampilan yang dapat digulir tampaknya tidak terfokus, - Pastikan tampilan yang dapat digulir difokuskan sebelum turunannya dengan memanggil
setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS)
. - Dengarkan MotionEvents dengan
SOURCE_ROTARY_ENCODER
danAXIS_VSCROLL
atauAXIS_HSCROLL
untuk menunjukkan jarak untuk menggulir dan arah (melalui tanda).
Saat pengguliran putar diaktifkan pada CarUiRecyclerView
dan pengguna memutar ke area di mana tidak ada tampilan yang dapat difokuskan, bilah gulir berubah dari abu-abu menjadi biru, seolah-olah menunjukkan bahwa bilah gulir difokuskan. Anda dapat menerapkan efek serupa jika Anda mau.
MotionEvents sama dengan yang dihasilkan oleh roda gulir pada mouse, kecuali untuk sumbernya.
Modus manipulasi langsung
Biasanya, dorongan dan rotasi menavigasi melalui antarmuka pengguna, sementara penekanan tombol Tengah mengambil tindakan, meskipun hal ini tidak selalu terjadi. Misalnya, jika pengguna ingin menyesuaikan volume alarm, mereka mungkin menggunakan pengontrol putar untuk menavigasi ke penggeser volume, tekan tombol Tengah, putar pengontrol untuk menyesuaikan volume alarm, lalu tekan tombol Kembali untuk kembali ke navigasi . Ini disebut sebagai mode manipulasi langsung (DM) . Dalam mode ini, pengontrol putar digunakan untuk berinteraksi dengan tampilan secara langsung daripada bernavigasi.
Terapkan DM dengan salah satu dari dua cara. Jika Anda hanya perlu menangani rotasi dan tampilan yang ingin Anda manipulasi merespons ACTION_SCROLL_FORWARD
dan ACTION_SCROLL_BACKWARD
AccessibilityEvent
dengan tepat, gunakan mekanisme sederhana . Jika tidak, gunakan mekanisme lanjutan .
Mekanisme sederhana adalah satu-satunya pilihan di jendela sistem; aplikasi dapat menggunakan salah satu mekanisme.
Mekanisme sederhana
( Android 11 QPR3, Android 11 Mobil, Android 12 )
Aplikasi Anda harus memanggil DirectManipulationHelper.setSupportsRotateDirectly(View view, boolean enable)
. RotaryService
mengenali saat pengguna berada dalam mode DM dan memasuki mode DM saat pengguna menekan tombol Tengah saat tampilan difokuskan. Saat dalam mode DM, rotasi melakukan ACTION_SCROLL_FORWARD
atau ACTION_SCROLL_BACKWARD
dan keluar dari mode DM ketika pengguna menekan tombol Kembali. Mekanisme sederhana mengubah status tampilan yang dipilih saat masuk dan keluar dari mode DM.
Untuk memberikan isyarat visual bahwa pengguna sedang dalam mode DM, buat tampilan Anda tampak berbeda saat dipilih. Misalnya, ubah latar belakang saat android:state_selected
bernilai true
.
Mekanisme tingkat lanjut
Aplikasi menentukan kapan RotaryService
masuk dan keluar dari mode DM. Untuk pengalaman pengguna yang konsisten, menekan tombol Tengah dengan tampilan DM terfokus akan masuk ke mode DM dan tombol Kembali akan keluar dari mode DM. Jika tombol Tengah dan/atau nudge tidak digunakan, itu bisa menjadi cara alternatif untuk keluar dari mode DM. Untuk aplikasi seperti Maps, tombol untuk mewakili DM dapat digunakan untuk masuk ke mode DM.
Untuk mendukung mode DM lanjutan, tampilan:
- ( Android 11 QPR3, Android 11 Car, Android 12 ) HARUS mendengarkan peristiwa
KEYCODE_DPAD_CENTER
untuk masuk ke mode DM dan mendengarkan peristiwaKEYCODE_BACK
untuk keluar dari mode DM, memanggilDirectManipulationHelper.enableDirectManipulationMode()
dalam setiap kasus. Untuk mendengarkan acara ini, lakukan salah satu hal berikut:- Daftarkan
OnKeyListener
. atau, - Perluas tampilan lalu timpa metode
dispatchKeyEvent()
-nya.
- Daftarkan
- HARUS mendengarkan acara nudge (
KEYCODE_DPAD_UP
,KEYCODE_DPAD_DOWN
,KEYCODE_DPAD_LEFT
, atauKEYCODE_DPAD_RIGHT
) jika tampilan harus menangani nudges. - HARUS mendengarkan
MotionEvent
s dan dapatkan jumlah rotasi diAXIS_SCROLL
jika tampilan ingin menangani rotasi. Ada beberapa cara untuk melakukannya:- Daftarkan
OnGenericMotionListener
. - Perluas tampilan dan ganti metode
dispatchTouchEvent()
-nya.
- Daftarkan
- Untuk menghindari terjebak dalam mode DM, HARUS keluar dari mode DM ketika Fragmen atau Aktivitas milik tampilan tidak interaktif.
- HARUS memberikan isyarat visual untuk menunjukkan bahwa tampilan dalam mode DM.
Contoh tampilan kustom yang menggunakan mode DM untuk menggeser dan memperbesar peta disediakan di bawah ini:
/** 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(); }
Lebih banyak contoh dapat ditemukan di proyek RotaryPlayground
.
Tampilan Aktivitas
Saat menggunakan ActivityView:
-
ActivityView
tidak boleh fokus. - ( Android 11 QPR3, Android 11 Car, tidak digunakan lagi di Android 11 )
KontenActivityView
HARUS berisiFocusParkingView
sebagai tampilan pertama yang dapat difokuskan, dan atributapp:shouldRestoreFocus
HARUSfalse
. - Konten
ActivityView
seharusnya tidak memiliki tampilanandroid:focusByDefault
.
Bagi pengguna, ActivityViews seharusnya tidak berpengaruh pada navigasi kecuali bahwa area fokus tidak dapat menjangkau ActivityViews. Dengan kata lain, Anda tidak dapat memiliki satu area fokus yang memiliki konten di dalam dan di luar ActivityView
. Jika Anda tidak menambahkan FocusAreas ke ActivityView
Anda, akar hierarki tampilan di ActivityView
dianggap sebagai area fokus implisit.
Tombol yang beroperasi saat ditekan
Sebagian besar tombol menyebabkan beberapa tindakan saat diklik. Beberapa tombol beroperasi saat ditekan. Misalnya, tombol Fast Forward dan Rewind biasanya beroperasi saat ditekan. Untuk membuat tombol tersebut mendukung putar, dengarkan KEYCODE_DPAD_CENTER
KeyEvents
sebagai berikut:
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; });
Di mana mRunnable
mengambil tindakan (seperti memutar ulang) dan menjadwalkan dirinya sendiri untuk dijalankan setelah penundaan.
Modus sentuh
Pengguna dapat menggunakan rotary controller untuk berinteraksi dengan head unit di dalam mobil dengan dua cara, baik dengan menggunakan rotary controller atau dengan menyentuh layar. Saat menggunakan pengontrol putar, salah satu tampilan yang dapat difokuskan akan disorot. Saat menyentuh layar, tidak ada sorotan fokus yang muncul. Pengguna dapat beralih di antara mode input ini kapan saja:
- Putar → sentuh. Saat pengguna menyentuh layar, sorotan fokus menghilang.
- Sentuh → putar. Saat pengguna menyenggol, memutar, atau menekan tombol Tengah, sorotan fokus muncul.
Tombol Kembali dan Beranda tidak berpengaruh pada mode input.
Dukungan putar pada konsep mode sentuh Android yang ada. Anda dapat menggunakan View.isInTouchMode()
untuk menentukan mode input yang digunakan pengguna. Anda dapat menggunakan OnTouchModeChangeListener
untuk mendengarkan perubahan. Meskipun ini dapat digunakan untuk menyesuaikan antarmuka pengguna Anda untuk mode input saat ini, hindari perubahan besar apa pun karena dapat membingungkan.
Penyelesaian masalah
Dalam aplikasi yang dirancang untuk sentuhan, tidak jarang memiliki tampilan fokus yang bersarang. Misalnya, mungkin ada FrameLayout
di sekitar ImageButton
, yang keduanya dapat difokuskan. Ini tidak membahayakan sentuhan tetapi dapat mengakibatkan pengalaman pengguna yang buruk untuk putar karena pengguna harus memutar pengontrol dua kali untuk pindah ke tampilan interaktif berikutnya. Untuk pengalaman pengguna yang baik, Google menyarankan Anda membuat tampilan luar atau tampilan dalam dapat difokuskan, tetapi tidak keduanya.
Jika tombol atau sakelar kehilangan fokus saat ditekan melalui pengontrol putar, salah satu kondisi berikut mungkin berlaku:
- Tombol atau sakelar sedang dinonaktifkan (singkat atau tanpa batas waktu) karena tombol ditekan. Dalam kedua kasus tersebut, ada dua cara untuk mengatasi hal ini:
- Biarkan status
android:enabled
sebagaitrue
dan gunakan status kustom untuk menghilangkan tombol atau sakelar seperti yang dijelaskan dalam Status Kustom . - Gunakan wadah untuk mengelilingi tombol atau sakelar dan buat wadah dapat difokuskan alih-alih tombol atau sakelar. (Pemroses klik harus ada di penampung.)
- Biarkan status
- Tombol atau sakelar sedang diganti. Misalnya, tindakan yang diambil saat tombol ditekan atau sakelar dialihkan dapat memicu penyegaran tindakan yang tersedia yang menyebabkan tombol baru menggantikan tombol yang ada. Ada dua cara untuk mengatasi hal ini:
- Alih-alih membuat tombol atau sakelar baru, atur ikon dan/atau teks tombol atau sakelar yang ada.
- Seperti di atas, tambahkan wadah yang dapat difokuskan di sekitar tombol atau sakelar.
RotaryPlayground
RotaryPlayground
adalah aplikasi referensi untuk rotary. Gunakan untuk mempelajari cara mengintegrasikan fitur putar ke dalam aplikasi Anda. RotaryPlayground
disertakan dalam build emulator dan build untuk perangkat yang menjalankan Android Automotive OS (AAOS).
- Repositori
RotaryPlayground
:packages/apps/Car/tests/RotaryPlayground/
- Versi: Android 11 QPR3, Android 11 Mobil, dan Android 12
Aplikasi RotaryPlayground
menampilkan tab berikut di sebelah kiri:
- Kartu-kartu. Uji navigasi di sekitar area fokus, lewati elemen yang tidak dapat difokuskan dan input teks.
- Manipulasi Langsung. Uji widget yang mendukung mode manipulasi langsung sederhana dan lanjutan. Tab ini khusus untuk manipulasi langsung di dalam jendela aplikasi.
- Manipulasi Sistem UI. Uji widget yang mendukung manipulasi langsung di jendela sistem di mana hanya mode manipulasi langsung sederhana yang didukung.
- kisi. Uji navigasi putar pola-z dengan menggulir.
- Pemberitahuan. Uji dorongan masuk dan keluar dari pemberitahuan awal.
- Menggulir. Uji pengguliran melalui campuran konten yang dapat difokuskan dan tidak dapat difokuskan.
- Tampilan Web. Uji navigasi melalui tautan di
WebView
. -
FocusArea
Kustom . Uji kustomisasiFocusArea
:- Membungkus.
-
android:focusedByDefault
danapp:defaultFocus
. - Target dorongan eksplisit.
- Pintasan dorong.
-
FocusArea
tanpa tampilan yang dapat difokuskan.
Materi berikut ini untuk pengembang aplikasi.
Untuk membuat dukungan aplikasi Anda berputar, Anda HARUS:
- Tempatkan
FocusParkingView
di tata letak aktivitas masing-masing. - Pastikan tampilan yang (atau tidak) dapat difokuskan.
- Gunakan
FocusArea
s untuk membungkus semua tampilan yang dapat difokuskan, kecualiFocusParkingView
.
Masing-masing tugas ini dirinci di bawah ini, setelah Anda mengatur lingkungan Anda untuk mengembangkan aplikasi yang mendukung putaran.
Siapkan pengontrol putar
Sebelum Anda dapat mulai mengembangkan aplikasi berkemampuan putar, Anda memerlukan pengontrol putar atau stand-in. Anda memiliki opsi yang dijelaskan di bawah ini.
emulator
source build/envsetup.sh && lunch car_x86_64-userdebug m -j emulator -wipe-data -no-snapshot -writable-system
Anda juga dapat menggunakan aosp_car_x86_64-userdebug
.
Untuk mengakses pengontrol putar yang diemulasi:
- Ketuk tiga titik di bagian bawah bilah alat:
- Pilih Putar mobil di jendela kontrol yang diperluas:
Keyboard USB
- Colokkan keyboard USB ke perangkat Anda yang menjalankan Android Automotive OS (AAOS), Dalam beberapa kasus, ini dapat mencegah munculnya keyboard di layar.
- Gunakan
userdebug
ataueng
build. - Aktifkan pemfilteran acara utama:
adb shell settings put secure android.car.ROTARY_KEY_EVENT_FILTER 1
- Lihat tabel di bawah untuk menemukan kunci yang sesuai untuk setiap tindakan:
Kunci Aksi putar Q Putar berlawanan arah jarum jam E Putar searah jarum jam SEBUAH Geser ke kiri D Geser ke kanan W Dorong ke atas S Dorong ke bawah F atau Koma tombol tengah R atau Esc Tombol kembali
perintah ADB
Anda dapat menggunakan perintah car_service
untuk menyuntikkan peristiwa input putar. Perintah ini dapat dijalankan di perangkat yang menjalankan Android Automotive OS (AAOS) atau di emulator.
perintah car_service | Masukan putar |
---|---|
adb shell cmd car_service inject-rotary | Putar berlawanan arah jarum jam |
adb shell cmd car_service inject-rotary -c true | Putar searah jarum jam |
adb shell cmd car_service inject-rotary -dt 100 50 | Putar berlawanan arah jarum jam beberapa kali (100 mdtk lalu dan 50 mdtk lalu) |
adb shell cmd car_service inject-key 282 | Geser ke kiri |
adb shell cmd car_service inject-key 283 | Geser ke kanan |
adb shell cmd car_service inject-key 280 | Dorong ke atas |
adb shell cmd car_service inject-key 281 | Dorong ke bawah |
adb shell cmd car_service inject-key 23 | Klik tombol tengah |
adb shell input keyevent inject-key 4 | Klik tombol kembali |
Pengontrol putar OEM
Saat perangkat keras pengontrol putar Anda aktif dan berjalan, ini adalah opsi yang paling realistis. Ini sangat berguna untuk menguji rotasi cepat.
FocusParkingView
FocusParkingView
adalah tampilan transparan di Car UI Library (car-ui-library) . RotaryService
menggunakannya untuk mendukung navigasi pengontrol putar. FocusParkingView
harus menjadi tampilan fokus pertama dalam tata letak. Itu harus ditempatkan di luar semua FocusArea
s. Setiap jendela harus memiliki satu FocusParkingView
. Jika Anda sudah menggunakan tata letak dasar car-ui-library, yang berisi FocusParkingView
, Anda tidak perlu menambahkan FocusParkingView
lain. Di bawah ini adalah contoh FocusParkingView
di 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>
Berikut adalah alasan Anda membutuhkan FocusParkingView
:
- Android tidak menghapus fokus secara otomatis saat fokus diatur di jendela lain. Jika Anda mencoba menghapus fokus di jendela sebelumnya, Android akan memfokuskan kembali tampilan di jendela itu, yang mengakibatkan dua jendela difokuskan secara bersamaan. Menambahkan
FocusParkingView
ke setiap jendela dapat memperbaiki masalah ini. Tampilan ini transparan dan sorotan fokus default dinonaktifkan, sehingga tidak terlihat oleh pengguna, baik fokus atau tidak. Ini dapat mengambil fokus sehinggaRotaryService
dapat memarkir fokus di atasnya untuk menghapus sorotan fokus. - Jika hanya ada satu
FocusArea
di jendela saat ini, memutar pengontrol diFocusArea
menyebabkanRotaryService
memindahkan fokus dari tampilan di kanan ke tampilan di kiri (dan sebaliknya). Menambahkan tampilan ini ke setiap jendela dapat memperbaiki masalah. KetikaRotaryService
menentukan target fokus adalahFocusParkingView
, RotaryService dapat menentukan wrap-around akan terjadi pada titik mana ia menghindari wrap-around dengan tidak memindahkan fokus. - Saat kontrol putar meluncurkan aplikasi, Android memfokuskan tampilan pertama yang dapat difokuskan, yang selalu berupa
FocusParkingView
.FocusParkingView
menentukan tampilan optimal untuk difokuskan dan kemudian menerapkan fokus.
Pandangan yang dapat difokuskan
RotaryService
dibangun di atas konsep fokus tampilan kerangka kerja Android yang ada , sejak ponsel memiliki keyboard fisik dan D-pad. Atribut android:nextFocusForward
yang ada digunakan ulang untuk rotasi (lihat penyesuaian FocusArea ), tetapi android:nextFocusLeft
, android:nextFocusRight
, android:nextFocusUp
, dan android:nextFocusDown
tidak.
RotaryService
hanya berfokus pada tampilan yang dapat difokuskan. Beberapa tampilan, seperti Button
s, biasanya dapat difokuskan. Lainnya, seperti TextView
s dan ViewGroup
s, biasanya tidak. Tampilan yang dapat diklik secara otomatis dapat difokuskan dan tampilan dapat diklik secara otomatis bila memiliki pendengar klik. Jika logika otomatis ini menghasilkan kemampuan fokus yang diinginkan, Anda tidak perlu secara eksplisit mengatur kemampuan fokus tampilan. Jika logika otomatis tidak menghasilkan kemampuan fokus yang diinginkan, setel atribut android:focusable
ke true
atau false
, atau secara terprogram setel kemampuan fokus tampilan dengan View.setFocusable(boolean)
. Agar RotaryService
fokus padanya, tampilan HARUS memenuhi persyaratan berikut:
- dapat fokus
- Diaktifkan
- Terlihat
- Memiliki nilai bukan nol untuk lebar dan tinggi
Jika tampilan tidak memenuhi semua persyaratan ini, misalnya Tombol yang dapat difokuskan tetapi dinonaktifkan, pengguna tidak dapat menggunakan kontrol putar untuk memfokuskannya. Jika Anda ingin fokus pada tampilan yang dinonaktifkan, pertimbangkan untuk menggunakan status khusus daripada android:state_enabled
untuk mengontrol tampilan tampilan tanpa menunjukkan bahwa Android harus menganggapnya dinonaktifkan. Aplikasi Anda dapat memberi tahu pengguna mengapa tampilan dinonaktifkan saat diketuk. Bagian selanjutnya menjelaskan bagaimana melakukan ini.
Status kustom
Untuk menambahkan status khusus:
- Untuk menambahkan atribut khusus ke tampilan Anda. Misalnya, untuk menambahkan status kustom
state_rotary_enabled
ke kelas tampilanCustomView
, gunakan:<declare-styleable name="CustomView"> <attr name="state_rotary_enabled" format="boolean" /> </declare-styleable>
- Untuk melacak status ini, tambahkan variabel instan ke tampilan Anda bersama dengan metode pengakses:
private boolean mRotaryEnabled; public boolean getRotaryEnabled() { return mRotaryEnabled; } public void setRotaryEnabled(boolean rotaryEnabled) { mRotaryEnabled = rotaryEnabled; }
- Untuk membaca nilai atribut Anda saat tampilan Anda dibuat:
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomView); mRotaryEnabled = a.getBoolean(R.styleable.CustomView_state_rotary_enabled);
- Di kelas tampilan Anda, timpa metode
onCreateDrawableState()
lalu tambahkan status kustom, bila perlu. Misalnya:@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; }
- Jadikan penangan klik tampilan Anda bekerja secara berbeda bergantung pada statusnya. Misalnya, pengendali klik mungkin tidak melakukan apa-apa atau mungkin muncul bersulang saat
mRotaryEnabled
false
. - Untuk membuat tombol tampak dinonaktifkan, di latar belakang tampilan Anda yang dapat digambar, gunakan
app:state_rotary_enabled
alih-alihandroid:state_enabled
. Jika Anda belum memilikinya, Anda perlu menambahkan:xmlns:app="http://schemas.android.com/apk/res-auto"
- Jika tampilan Anda dinonaktifkan di tata letak apa pun, ganti
android:enabled="false"
denganapp:state_rotary_enabled="false"
lalu tambahkan ruang namaapp
, seperti di atas. - Jika tampilan Anda dinonaktifkan secara terprogram, ganti panggilan ke
setEnabled()
dengan panggilan kesetRotaryEnabled()
.
Area fokus
Gunakan FocusAreas
untuk mempartisi tampilan yang dapat difokuskan ke dalam blok agar navigasi lebih mudah dan konsisten dengan aplikasi lain. Misalnya, jika aplikasi Anda memiliki toolbar, toolbar harus berada di FocusArea
yang terpisah dari aplikasi Anda yang lain. Bilah tab dan elemen navigasi lainnya juga harus dipisahkan dari aplikasi lainnya. Daftar besar umumnya harus memiliki FocusArea
sendiri. Jika tidak, pengguna harus memutar seluruh daftar untuk mengakses beberapa tampilan.
FocusArea
adalah subkelas LinearLayout
di perpustakaan mobil-ui. Saat fitur ini diaktifkan, FocusArea
akan menarik sorotan saat salah satu turunannya difokuskan. Untuk mempelajari lebih lanjut, lihat Penyesuaian sorotan fokus .
Saat membuat blok navigasi di file tata letak, jika Anda bermaksud menggunakan LinearLayout
sebagai wadah untuk blok itu, gunakan FocusArea
sebagai gantinya. Jika tidak, bungkus blok dalam FocusArea
.
JANGAN menyarangkan FocusArea
di FocusArea
lain. Melakukannya akan menyebabkan perilaku navigasi yang tidak ditentukan. Pastikan bahwa semua tampilan yang dapat difokuskan bersarang di dalam FocusArea
.
Contoh FocusArea
di RotaryPlayground
ditunjukkan di bawah ini:
<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
berfungsi sebagai berikut:
- When handling rotate and nudge actions,
RotaryService
looks for instances ofFocusArea
in the view hierarchy. - When receiving a rotation event,
RotaryService
moves focus to another View that can take focus in the sameFocusArea
. - When receiving a nudge event,
RotaryService
move focus to another view that can take focus in another (typically adjacent)FocusArea
.
If you don't include any FocusAreas
in your layout, the root view is treated as an implicit focus area. The user can't nudge to navigate in the app. Instead, they'll rotate through all focusable views, which may be adequate for dialogs.
FocusArea customization
Two standard View attributes can be used to customize rotary navigation:
-
android:nextFocusForward
allows app developers to specify the rotation order in a focus area. This is the same attribute used to control the Tab order for keyboard navigation. Do NOT use this attribute to create a loop. Instead, useapp:wrapAround
(see below) to create a loop. -
android:focusedByDefault
allows app developers to specify the default focus view in the window. Do NOT use this attribute andapp:defaultFocus
(see below) in the sameFocusArea
.
FocusArea
also defines some attributes to customize rotary navigation. Implicit focus areas can't be customized with these attributes.
- ( Android 11 QPR3, Android 11 Car, Android 12 )
app:defaultFocus
can be used to specify the ID of a focusable descendant view, which should be focused on when the user nudges to thisFocusArea
. - ( Android 11 QPR3, Android 11 Car, Android 12 )
app:defaultFocusOverridesHistory
can be set totrue
to make the view specified above take focus even if with history to indicate another view in thisFocusArea
had been focused on. - ( Android 12 )
Useapp:nudgeLeftShortcut
,app:nudgeRightShortcut
,app:nudgeUpShortcut
, andapp:nudgeDownShortcut
to specify the ID of a focusable descendant view, which should be focused on when the user nudges in a given direction. To learn more, see the content for nudge shortcuts below.( Android 11 QPR3, Android 11 Car, deprecated in Android 12 )
app:nudgeShortcut
andapp:nudgeShortcutDirection
supported only one nudge shortcut. - ( Android 11 QPR3, Android 11 Car, Android 12 )
To enable rotation to wrap around in thisFocusArea
,app:wrapAround
can be set totrue
. This is most typically used when views are arranged in a circle or oval. - ( Android 11 QPR3, Android 11 Car, Android 12 )
To adjust the padding of the highlight in thisFocusArea
, useapp:highlightPaddingStart
,app:highlightPaddingEnd
,app:highlightPaddingTop
,app:highlightPaddingBottom
,app:highlightPaddingHorizontal
, andapp:highlightPaddingVertical
. - ( Android 11 QPR3, Android 11 Car, Android 12 )
To adjust the perceived bounds of thisFocusArea
to find a nudge target, useapp:startBoundOffset
,app:endBoundOffset
,app:topBoundOffset
,app:bottomBoundOffset
,app:horizontalBoundOffset
, andapp:verticalBoundOffset
. - ( Android 11 QPR3, Android 11 Car, Android 12 )
To explicitly specify the ID of an adjacentFocusArea
(or areas) in the given directions, useapp:nudgeLeft
,app:nudgeRight
,app:nudgeUp
, andapp:nudgeDown
. Use this when the geometric search used by default doesn't find the desired target.
Nudging usually navigates between FocusAreas. But with nudge shortcuts, nudging sometimes first navigates within a FocusArea
so that the user may need to nudge twice to navigate to the next FocusArea
. Nudge shortcuts are useful when a FocusArea
contains a long list followed by a Floating Action Button , as in the example below:
Without the nudge shortcut, the user would have to rotate through the entire list to reach the FAB.
Focus highlight customization
As noted above, RotaryService
builds upon the Android framework's existing concept of view focus. When the user rotates and nudges, RotaryService
moves the focus around, focusing one view and unfocusing another. In Android, when a view is focused, if the view:
- has specified its own focus highlight, Android draws the view's focus highlight.
- doesn't specify a focus highlight, and the default focus highlight is not disabled, Android draws the default focus highlight for the view.
Apps designed for touch usually don't specify the appropriate focus highlights.
The default focus highlight is provided by the Android framework and can be overridden by the OEM. App developers receive it when the theme they're using is derived from Theme.DeviceDefault
.
For a consistent user experience, rely on the default focus highlight whenever possible. If you need a custom-shaped (for example, round or pill-shaped) focus highlight, or if you're using a theme not derived from Theme.DeviceDefault
, use the car-ui-library resources to specify your own focus highlight for each view.
To specify a custom focus highlight for a view, change the background or foreground drawable of the view to a drawable that differs when the view is focused on. Typically, you'd change the background. The following drawable, if used as the background for a square view, produces a round focus highlight:
<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 ) Bold resource references in the sample above identify resources defined by the car-ui-library. The OEM overrides these to be consistent with the default focus highlight they specify. This ensures that the focus highlight color, stroke width, and so on don't change when the user navigates between a view with a custom focus highlight and a view with the default focus highlight. The last item is a ripple used for touch. Default values used for the bold resources appear as follows:
In addition, a custom focus highlight is called for when a button is given a solid background color to bring it to the user's attention, as in the example below. This can make the focus highlight difficult to see. In this situation, specify a custom focus highlight using secondary colors:
- ( 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
Sebagai contoh:
Focused, not pressed | Focused, pressed |
Rotary scrolling
If your app uses RecyclerView
s, you SHOULD use CarUiRecyclerView
s instead. This ensures that your UI is consistent with others because an OEM's customization applies to all CarUiRecyclerView
s.
If the elements in your list are all focusable, you needn't do anything else. Rotary navigation moves the focus through the elements in the list and the list scrolls to make the newly focused element visible.
( Android 11 QPR3, Android 11 Car, Android 12 )
If there is a mix of focusable and unfocusable elements, or if all the elements are unfocusable, you can enable rotary scrolling, which allows the user to use the rotary controller to gradually scroll through the list without skipping unfocusable items. To enable rotary scrolling, set the app:rotaryScrollEnabled
attribute to true
.
( Android 11 QPR3, Android 11 Car, Android 12 )
You can enable rotary scrolling in any scrollable view, including av CarUiRecyclerView
, with the setRotaryScrollEnabled()
method in CarUiUtils
. If you do so, you need to:
- Make the scrollable view focusable so that it can be focused on when none of its focusable descendant views are visible,
- Disable the default focus highlight on the scrollable view by calling
setDefaultFocusHighlightEnabled(false)
so that the scrollable view doesn't appear to be focused, - Ensure that the scrollable view is focused on before its descendants by calling
setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS)
. - Listen for MotionEvents with
SOURCE_ROTARY_ENCODER
and eitherAXIS_VSCROLL
orAXIS_HSCROLL
to indicate the distance to scroll and the direction (through the sign).
When rotary scrolling is enabled on a CarUiRecyclerView
and the user rotates to an area where no focusable views are present, the scrollbar changes from gray to blue, as if to indicate the scrollbar is focused. You can implement a similar effect if you like.
The MotionEvents are the same as those generated by a scroll wheel on a mouse, except for the source.
Direct manipulation mode
Normally, nudges and rotation navigate through the user interface, while Center button presses take action, though this isn't always the case. For example, if a user wants to adjust the alarm volume, they might use the rotary controller to navigate to the volume slider, press the Center button, rotate the controller to adjust the alarm volume, and then press the Back button to return to navigation. This is referred to as direct manipulation (DM) mode. In this mode, the rotary controller is used to interact with the view directly rather than to navigate.
Implement DM in one of two ways. If you only need to handle rotation and the view you want to manipulate responds to ACTION_SCROLL_FORWARD
and ACTION_SCROLL_BACKWARD
AccessibilityEvent
s appropriately, use the simple mechanism. Otherwise, use the advanced mechanism.
The simple mechanism is the only option in system windows; apps can use either mechanism.
Simple mechanism
( Android 11 QPR3, Android 11 Car, Android 12 )
Your app should call DirectManipulationHelper.setSupportsRotateDirectly(View view, boolean enable)
. RotaryService
recognizes when the user is in DM mode and enters DM mode when the user presses the Center button while a view is focused. When in DM mode, rotations perform ACTION_SCROLL_FORWARD
or ACTION_SCROLL_BACKWARD
and exits DM mode when the user presses the Back button. The simple mechanism toggles the selected state of the view when entering and exiting DM mode.
To provide a visual cue that the user is in DM mode, make your view appear different when selected. For example, change the background when android:state_selected
is true
.
Advanced mechanism
The app determines when RotaryService
enters and exits DM mode. For a consistent user experience, pressing the Center button with a DM view focused should enter DM mode and the Back button should exit DM mode. If the Center button and/or nudge aren't used, they can be alternative ways to exit DM mode. For apps such as Maps, a button to represent DM can be used to enter DM mode.
To support advanced DM mode, a view:
- ( Android 11 QPR3, Android 11 Car, Android 12 ) MUST listen for a
KEYCODE_DPAD_CENTER
event to enter DM mode and listen for aKEYCODE_BACK
event to exit DM mode, callingDirectManipulationHelper.enableDirectManipulationMode()
in each case. To listen for these events, do one of the following:- Register an
OnKeyListener
. or, - Extend the view and then override its
dispatchKeyEvent()
method.
- Register an
- SHOULD listen for nudge events (
KEYCODE_DPAD_UP
,KEYCODE_DPAD_DOWN
,KEYCODE_DPAD_LEFT
, orKEYCODE_DPAD_RIGHT
) if the view should handle nudges. - SHOULD listen to
MotionEvent
s and get rotation count inAXIS_SCROLL
if the view wants to handle rotation. There are several ways to do this:- Register an
OnGenericMotionListener
. - Extend the view and override its
dispatchTouchEvent()
method.
- Register an
- To avoid being stuck in DM mode, MUST exit DM mode when the Fragment or Activity the view belongs to is not interactive.
- SHOULD provide a visual cue to indicate that the view is in DM mode.
A sample of a custom view that uses DM mode to pan and zoom a map is provided below:
/** 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(); }
More examples can be found in the RotaryPlayground
project.
ActivityView
When using an ActivityView:
- The
ActivityView
should not be focusable. - ( Android 11 QPR3, Android 11 Car, deprecated in Android 11 )
The contents of theActivityView
MUST contain aFocusParkingView
as the first focusable view, and itsapp:shouldRestoreFocus
attribute MUST befalse
. - The contents of the
ActivityView
should have noandroid:focusByDefault
views.
For the user, ActivityViews should have no effect on navigation except that focus areas can't span ActivityViews. In other words, you can't have a single focus area that has content inside and outside an ActivityView
. If you don't add any FocusAreas to your ActivityView
, the root of the view hierarchy in the ActivityView
is considered an implicit focus area.
Buttons that operate when held down
Most buttons cause some action when clicked. Some buttons operate when held down instead. For example, the Fast Forward and Rewind buttons typically operate when held down. To make such buttons support rotary, listen for KEYCODE_DPAD_CENTER
KeyEvents
as follows:
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; });
In which mRunnable
takes an action (such as rewinding) and schedules itself to be run after a delay.
Touch mode
Users can use a rotary controller to interact with the head unit in a car in two ways, either by using the rotary controller or by touching the screen. When using the rotary controller, one of the focusable views will be highlighted. When touching the screen, no focus highlight appears. The user can switch between these input modes at any time:
- Rotary → touch. When the user touches the screen, the focus highlight disappears.
- Touch → rotary. When the user nudges, rotates, or presses the Center button, the focus highlight appears.
The Back and Home buttons have no effect on the input mode.
Rotary piggybacks on Android's existing concept of touch mode . You can use View.isInTouchMode()
to determine which input mode the user is using. You can use OnTouchModeChangeListener
to listen for changes. While this can be used to customize your user interface for the current input mode, avoid any major changes as they can be disconcerting.
Troubleshooting
In an app designed for touch, it's not uncommon to have nested focusable views. For example, there may be a FrameLayout
around an ImageButton
, both of which are focusable. This does no harm for touch but it can result in a poor user experience for rotary because the user must rotate the controller twice to move to the next interactive view. For a good user experience, Google recommends you make either the outer view or the inner view focusable, but not both.
If a button or switch loses focus when pressed through the rotary controller, one of these conditions may apply:
- The button or switch is being disabled (briefly or indefinitely) due to the button being pressed. In either case, there are two ways to address this:
- Leave the
android:enabled
state astrue
and use a custom state to gray out the button or switch as described in Custom State . - Use a container to surround the button or switch and make the container focusable instead of the button or switch. (The click listener must be on the container.)
- Leave the
- The button or switch is being replaced. For example, the action taken when the button is pressed or the switch is toggled may trigger a refresh of the available actions causing new buttons to replace existing buttons. There are two ways to address this:
- Instead of creating a new button or switch, set the icon and/or text of the existing button or switch.
- As above, add a focusable container around the button or switch.
RotaryPlayground
RotaryPlayground
is a reference app for rotary. Use it to learn how to integrate rotary features into your apps. RotaryPlayground
is included in emulator builds and in builds for devices that run Android Automotive OS (AAOS).
-
RotaryPlayground
repository:packages/apps/Car/tests/RotaryPlayground/
- Versions: Android 11 QPR3, Android 11 Car, and Android 12
The RotaryPlayground
app shows the following tabs on the left:
- Cards. Test navigating around focus areas, skipping unfocusable elements and text input.
- Direct Manipulation. Test widgets that support simple and advanced direct manipulation mode. This tab is specifically for direct manipulation within the app window.
- Sys UI Manipulation. Test widgets that support direct manipulation in system windows where only simple direct manipulation mode is supported.
- Grid. Test z-pattern rotary navigation with scrolling.
- Notification. Test nudging in and out of heads-up notifications.
- Scroll. Test scrolling through a mix of focusable and unfocusable content.
- WebView. Test navigating through links in a
WebView
. - Custom
FocusArea
. TestFocusArea
customization:- Wrap-around.
-
android:focusedByDefault
andapp:defaultFocus
. - Explicit nudge targets.
- Nudge shortcuts.
-
FocusArea
with no focusable views.