Mengembangkan Aplikasi

Materi berikut ini untuk pengembang aplikasi.

Untuk membuat dukungan aplikasi Anda berputar, Anda HARUS:

  1. Tempatkan FocusParkingView di tata letak aktivitas masing-masing.
  2. Pastikan tampilan yang (atau tidak) dapat difokuskan.
  3. Gunakan FocusArea s untuk membungkus semua tampilan yang dapat difokuskan, kecuali FocusParkingView .

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:

  1. Ketuk tiga titik di bagian bawah bilah alat:

    Akses pengontrol putar yang ditiru
    Gambar 1. Akses pengontrol putar yang ditiru
  2. Pilih Putar mobil di jendela kontrol yang diperluas:

    Pilih Putar mobil
    Gambar 2. Pilih Putar mobil

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 atau eng 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 :

  1. 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 sehingga RotaryService dapat memarkir fokus di atasnya untuk menghapus sorotan fokus.
  2. Jika hanya ada satu FocusArea di jendela saat ini, memutar pengontrol di FocusArea menyebabkan RotaryService memindahkan fokus dari tampilan di kanan ke tampilan di kiri (dan sebaliknya). Menambahkan tampilan ini ke setiap jendela dapat memperbaiki masalah. Ketika RotaryService menentukan target fokus adalah FocusParkingView , RotaryService dapat menentukan wrap-around akan terjadi pada titik mana ia menghindari wrap-around dengan tidak memindahkan fokus.
  3. 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:

  1. Untuk menambahkan atribut khusus ke tampilan Anda. Misalnya, untuk menambahkan status kustom state_rotary_enabled ke kelas tampilan CustomView , gunakan:
    <declare-styleable name="CustomView">
        <attr name="state_rotary_enabled" format="boolean" />
    </declare-styleable>
    
  2. 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;
    }
    
  3. 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);
    
  4. 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;
    }
    
  5. 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 .
  6. Untuk membuat tombol tampak dinonaktifkan, di latar belakang tampilan Anda yang dapat digambar, gunakan app:state_rotary_enabled alih-alih android:state_enabled . Jika Anda belum memilikinya, Anda perlu menambahkan:
    xmlns:app="http://schemas.android.com/apk/res-auto"
    
  7. Jika tampilan Anda dinonaktifkan di tata letak apa pun, ganti android:enabled="false" dengan app:state_rotary_enabled="false" lalu tambahkan ruang nama app , seperti di atas.
  8. Jika tampilan Anda dinonaktifkan secara terprogram, ganti panggilan ke setEnabled() dengan panggilan ke setRotaryEnabled() .

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:

  1. Saat menangani tindakan rotate dan nudge, RotaryService mencari instance FocusArea dalam hierarki tampilan.
  2. Saat menerima peristiwa rotasi, RotaryService memindahkan fokus ke Tampilan lain yang dapat mengambil fokus di FocusArea yang sama.
  3. Saat menerima acara nudge, RotaryService memindahkan fokus ke tampilan lain yang dapat mengambil fokus di FocusArea 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, gunakan app:wrapAround (lihat di bawah) untuk membuat loop.
  • android:focusedByDefault memungkinkan pengembang aplikasi untuk menentukan tampilan fokus default di jendela. JANGAN gunakan atribut ini dan app:defaultFocus (lihat di bawah) di FocusArea yang sama.

FocusArea juga mendefinisikan beberapa atribut untuk menyesuaikan navigasi putar. Area fokus implisit tidak dapat disesuaikan dengan atribut ini.

  1. ( 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 ke FocusArea ini.
  2. ( Android 11 QPR3, Android 11 Mobil, Android 12 )
    app:defaultFocusOverridesHistory dapat disetel ke true untuk membuat tampilan yang ditentukan di atas menjadi fokus meskipun dengan riwayat untuk menunjukkan tampilan lain di FocusArea ini telah difokuskan.
  3. ( Android 12 )
    Gunakan app:nudgeLeftShortcut , app:nudgeRightShortcut , app:nudgeUpShortcut , dan app: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 dan app:nudgeShortcutDirection hanya mendukung satu pintasan nudge.

  4. ( Android 11 QPR3, Android 11 Mobil, Android 12 )
    Untuk mengaktifkan rotasi untuk membungkus di FocusArea ini, app:wrapAround dapat disetel ke true . Ini paling sering digunakan saat tampilan diatur dalam lingkaran atau oval.
  5. ( Android 11 QPR3, Android 11 Mobil, Android 12 )
    Untuk menyesuaikan padding sorotan di FocusArea ini, gunakan app:highlightPaddingStart , app:highlightPaddingEnd , app:highlightPaddingTop , app:highlightPaddingBottom , app:highlightPaddingHorizontal , dan app:highlightPaddingVertical .
  6. ( Android 11 QPR3, Android 11 Mobil, Android 12 )
    Untuk menyesuaikan batas yang dirasakan dari FocusArea ini untuk menemukan target dorongan, gunakan app:startBoundOffset , app:endBoundOffset , app:topBoundOffset , app:bottomBoundOffset , app:horizontalBoundOffset , dan app:verticalBoundOffset .
  7. ( Android 11 QPR3, Android 11 Mobil, Android 12 )
    Untuk secara eksplisit menentukan ID FocusArea (atau area) yang berdekatan dalam arah yang diberikan, gunakan app:nudgeLeft , app:nudgeRight , app:nudgeUp , dan app: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:

Pintasan dorong
Gambar 3. Pintasan dorong

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:

Nilai default untuk sumber daya tebal
Gambar 4. Nilai default untuk sumber daya tebal

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 :

Warna latar belakang yang solid
  • ( 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 ditekanFokus, ditekan
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 dan AXIS_VSCROLL atau AXIS_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:

  1. ( Android 11 QPR3, Android 11 Car, Android 12 ) HARUS mendengarkan peristiwa KEYCODE_DPAD_CENTER untuk masuk ke mode DM dan mendengarkan peristiwa KEYCODE_BACK untuk keluar dari mode DM, memanggil DirectManipulationHelper.enableDirectManipulationMode() dalam setiap kasus. Untuk mendengarkan acara ini, lakukan salah satu hal berikut:
    • Daftarkan OnKeyListener .
    • atau,
    • Perluas tampilan lalu timpa metode dispatchKeyEvent() -nya.
  2. HARUS mendengarkan acara nudge ( KEYCODE_DPAD_UP , KEYCODE_DPAD_DOWN , KEYCODE_DPAD_LEFT , atau KEYCODE_DPAD_RIGHT ) jika tampilan harus menangani nudges.
  3. HARUS mendengarkan MotionEvent s dan dapatkan jumlah rotasi di AXIS_SCROLL jika tampilan ingin menangani rotasi. Ada beberapa cara untuk melakukannya:
    1. Daftarkan OnGenericMotionListener .
    2. Perluas tampilan dan ganti metode dispatchTouchEvent() -nya.
  4. Untuk menghindari terjebak dalam mode DM, HARUS keluar dari mode DM ketika Fragmen atau Aktivitas milik tampilan tidak interaktif.
  5. 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 )
    Konten ActivityView HARUS berisi FocusParkingView sebagai tampilan pertama yang dapat difokuskan, dan atribut app:shouldRestoreFocus HARUS false .
  • Konten ActivityView seharusnya tidak memiliki tampilan android: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 sebagai true 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.)
  • 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 kustomisasi FocusArea :
    • Membungkus.
    • android:focusedByDefault dan app: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:

  1. Tempatkan FocusParkingView di tata letak aktivitas masing-masing.
  2. Pastikan tampilan yang (atau tidak) dapat difokuskan.
  3. Gunakan FocusArea s untuk membungkus semua tampilan yang dapat difokuskan, kecuali FocusParkingView .

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:

  1. Ketuk tiga titik di bagian bawah bilah alat:

    Akses pengontrol putar yang ditiru
    Gambar 1. Akses pengontrol putar yang ditiru
  2. Pilih Putar mobil di jendela kontrol yang diperluas:

    Pilih Putar mobil
    Gambar 2. Pilih Putar mobil

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 atau eng 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 :

  1. 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 sehingga RotaryService dapat memarkir fokus di atasnya untuk menghapus sorotan fokus.
  2. Jika hanya ada satu FocusArea di jendela saat ini, memutar pengontrol di FocusArea menyebabkan RotaryService memindahkan fokus dari tampilan di kanan ke tampilan di kiri (dan sebaliknya). Menambahkan tampilan ini ke setiap jendela dapat memperbaiki masalah. Ketika RotaryService menentukan target fokus adalah FocusParkingView , RotaryService dapat menentukan wrap-around akan terjadi pada titik mana ia menghindari wrap-around dengan tidak memindahkan fokus.
  3. 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:

  1. Untuk menambahkan atribut khusus ke tampilan Anda. Misalnya, untuk menambahkan status kustom state_rotary_enabled ke kelas tampilan CustomView , gunakan:
    <declare-styleable name="CustomView">
        <attr name="state_rotary_enabled" format="boolean" />
    </declare-styleable>
    
  2. 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;
    }
    
  3. 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);
    
  4. 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;
    }
    
  5. 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 .
  6. Untuk membuat tombol tampak dinonaktifkan, di latar belakang tampilan Anda yang dapat digambar, gunakan app:state_rotary_enabled alih-alih android:state_enabled . Jika Anda belum memilikinya, Anda perlu menambahkan:
    xmlns:app="http://schemas.android.com/apk/res-auto"
    
  7. Jika tampilan Anda dinonaktifkan di tata letak apa pun, ganti android:enabled="false" dengan app:state_rotary_enabled="false" lalu tambahkan ruang nama app , seperti di atas.
  8. Jika tampilan Anda dinonaktifkan secara terprogram, ganti panggilan ke setEnabled() dengan panggilan ke setRotaryEnabled() .

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:

  1. When handling rotate and nudge actions, RotaryService looks for instances of FocusArea in the view hierarchy.
  2. When receiving a rotation event, RotaryService moves focus to another View that can take focus in the same FocusArea .
  3. 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, use app: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 and app:defaultFocus (see below) in the same FocusArea .

FocusArea also defines some attributes to customize rotary navigation. Implicit focus areas can't be customized with these attributes.

  1. ( 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 this FocusArea .
  2. ( Android 11 QPR3, Android 11 Car, Android 12 )
    app:defaultFocusOverridesHistory can be set to true to make the view specified above take focus even if with history to indicate another view in this FocusArea had been focused on.
  3. ( Android 12 )
    Use app:nudgeLeftShortcut , app:nudgeRightShortcut , app:nudgeUpShortcut , and app: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 and app:nudgeShortcutDirection supported only one nudge shortcut.

  4. ( Android 11 QPR3, Android 11 Car, Android 12 )
    To enable rotation to wrap around in this FocusArea , app:wrapAround can be set to true . This is most typically used when views are arranged in a circle or oval.
  5. ( Android 11 QPR3, Android 11 Car, Android 12 )
    To adjust the padding of the highlight in this FocusArea , use app:highlightPaddingStart , app:highlightPaddingEnd , app:highlightPaddingTop , app:highlightPaddingBottom , app:highlightPaddingHorizontal , and app:highlightPaddingVertical .
  6. ( Android 11 QPR3, Android 11 Car, Android 12 )
    To adjust the perceived bounds of this FocusArea to find a nudge target, use app:startBoundOffset , app:endBoundOffset , app:topBoundOffset , app:bottomBoundOffset , app:horizontalBoundOffset , and app:verticalBoundOffset .
  7. ( Android 11 QPR3, Android 11 Car, Android 12 )
    To explicitly specify the ID of an adjacent FocusArea (or areas) in the given directions, use app:nudgeLeft , app:nudgeRight , app:nudgeUp , and app: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:

Nudge shortcut
Figure 3. Nudge shortcut

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:

Default values for bold resources
Figure 4. Default values for bold resources

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:

Solid background color
  • ( 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 pressedFocused, pressed
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 either AXIS_VSCROLL or AXIS_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:

  1. ( Android 11 QPR3, Android 11 Car, Android 12 ) MUST listen for a KEYCODE_DPAD_CENTER event to enter DM mode and listen for a KEYCODE_BACK event to exit DM mode, calling DirectManipulationHelper.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.
  2. SHOULD listen for nudge events ( KEYCODE_DPAD_UP , KEYCODE_DPAD_DOWN , KEYCODE_DPAD_LEFT , or KEYCODE_DPAD_RIGHT ) if the view should handle nudges.
  3. SHOULD listen to MotionEvent s and get rotation count in AXIS_SCROLL if the view wants to handle rotation. There are several ways to do this:
    1. Register an OnGenericMotionListener .
    2. Extend the view and override its dispatchTouchEvent() method.
  4. To avoid being stuck in DM mode, MUST exit DM mode when the Fragment or Activity the view belongs to is not interactive.
  5. 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 the ActivityView MUST contain a FocusParkingView as the first focusable view, and its app:shouldRestoreFocus attribute MUST be false .
  • The contents of the ActivityView should have no android: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 as true 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.)
  • 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 . Test FocusArea customization:
    • Wrap-around.
    • android:focusedByDefault and app:defaultFocus
    • .
    • Explicit nudge targets.
    • Nudge shortcuts.
    • FocusArea with no focusable views.