1. Sebelum memulai
Tidak termasuk dalam cakupan
- Panduan tentang cara membuat aplikasi media (audio - misalnya musik, radio, podcast) untuk Android Auto dan Android Automotive OS. Lihat Membangun aplikasi media untuk mobil guna mengetahui detail tentang cara membangun aplikasi serupa.
Yang akan Anda butuhkan
- Android Studio terbaru.
- Pengalaman dengan Kotlin dasar.
- Pengalaman membuat Perangkat Virtual Android dan menjalankannya di Android Emulator.
- Pengetahuan dasar tentang Jetpack Compose.
- Pemahaman tentang Efek samping
- Pemahaman dasar tentang inset jendela.
Yang akan Anda bangun
Dalam codelab ini, Anda akan mempelajari cara memigrasikan aplikasi seluler streaming video yang ada, Road Reels, ke Android Automotive OS.
Versi titik awal aplikasi yang berjalan di ponsel | Versi lengkap aplikasi yang berjalan di emulator Android Automotive OS dengan potongan layar. |
Yang akan Anda pelajari
- Cara menggunakan emulator Android Automotive OS.
- Cara membuat perubahan yang diperlukan untuk membuat build Android Automotive OS.
- Asumsi umum yang dibuat ketika mengembangkan aplikasi untuk perangkat seluler yang mungkin rusak saat aplikasi berjalan di Android Automotive OS.
- Berbagai tingkat kualitas untuk aplikasi di mobil.
- Cara menggunakan sesi media untuk memungkinkan aplikasi lain mengontrol pemutaran aplikasi Anda.
- Perbedaan UI sistem dan inset jendela di perangkat Android Automotive OS dibandingkan dengan perangkat seluler.
2. Memulai persiapan
Mendapatkan kode
- Kode untuk codelab ini dapat ditemukan di direktori
build-a-parked-app
dalam repositori GitHubcar-codelabs
. Untuk membuat clone kode ini, jalankan perintah berikut:
git clone https://github.com/android/car-codelabs.git
- Atau, Anda dapat mendownload repositori sebagai file ZIP:
Membuka project
- Setelah memulai Android Studio, impor project dengan memilih direktori
build-a-parked-app/start
saja. Direktoribuild-a-parked-app/end
berisi kode solusi yang dapat Anda rujuk kapan saja jika Anda mengalami kebuntuan atau hanya ingin melihat project lengkap.
Memahami kode
- Setelah membuka project di Android Studio, luangkan waktu untuk memeriksa kode awal.
3. Mempelajari aplikasi parkir untuk Android Automotive OS
Aplikasi parkir merupakan subkumpulan kategori aplikasi yang didukung oleh Android Automotive OS. Pada saat penulisan codelab ini, aplikasi tersebut terdiri dari aplikasi streaming video, browser web, dan game. Aplikasi ini sangat cocok untuk mobil, mengingat hardware yang ada di kendaraan yang dilengkapi Google dan meningkatnya prevalensi kendaraan listrik. Waktu pengisian daya memberikan peluang besar bagi pengemudi dan penumpang untuk berinteraksi dengan jenis aplikasi ini.
Dalam banyak hal, mobil mirip dengan perangkat layar besar lainnya seperti tablet dan perangkat foldable. Mobil memiliki layar sentuh dengan ukuran, resolusi, dan rasio aspek yang serupa, dan bisa saja dalam orientasi potret atau lanskap (meskipun, tidak seperti tablet, orientasi layar mobil tetap). Mobil juga merupakan perangkat terhubung yang dapat masuk dan keluar dari koneksi jaringan. Dengan semua hal itu, tidak mengherankan jika aplikasi yang sudah adaptif sering kali memerlukan sedikit saja upaya untuk menghadirkan pengalaman pengguna yang sangat baik di mobil.
Serupa dengan perangkat layar besar, ada juga tingkat kualitas aplikasi untuk aplikasi di mobil:
- Tingkat 3 - Siap digunakan di mobil: Aplikasi Anda kompatibel dengan perangkat layar besar dan dapat digunakan saat mobil diparkir. Meskipun aplikasi ini mungkin tidak memiliki fitur yang dioptimalkan untuk mobil, pengguna dapat menikmati aplikasi ini seperti yang mereka lakukan di perangkat Android layar besar lainnya. Aplikasi seluler yang memenuhi persyaratan ini layak didistribusikan ke mobil secara apa adanya melalui program aplikasi seluler siap digunakan untuk mobil.
- Tingkat 2 - Dioptimalkan untuk mobil: Aplikasi Anda memberikan pengalaman yang sangat baik pada tampilan stack tengah mobil. Agar dapat digunakan di mobil, aplikasi Anda harus melalui beberapa rekayasa khusus mobil untuk menyertakan kemampuan yang dapat digunakan di seluruh mode mengemudi atau parkir, bergantung pada kategori aplikasi Anda.
- Tingkat 1 - Dirancang khusus untuk mobil: Aplikasi Anda dibuat agar berfungsi di berbagai hardware pada mobil dan dapat menyesuaikan pengalamannya di seluruh mode mengemudi dan parkir. Aplikasi ini pada tingkat ini memberikan pengalaman pengguna terbaik yang didesain untuk berbagai layar di mobil, seperti konsol tengah, cluster instrumen, dan layar tambahan - seperti tampilan panorama yang terlihat di banyak mobil premium.
4. Menjalankan aplikasi di emulator Android Automotive OS
Menginstal Image Sistem Automotive with Play Store
- Pertama, buka SDK Manager di Android Studio, lalu pilih tab SDK Platforms jika belum dipilih. Di pojok kanan bawah jendela SDK Manager, pastikan kotak Show package details dicentang.
- Instal image emulator Android Automotive dengan Google API menggunakan API 33 yang tercantum di Menambahkan Generic System Image (GSI). Image hanya dapat berjalan di komputer dengan arsitektur yang sama (x86/ARM) dengan image itu sendiri.
Membuat Perangkat Virtual Android untuk Android Automotive OS
- Setelah membuka Pengelola Perangkat, pilih Automotive di kolom Category di sisi kiri jendela. Kemudian, pilih profil hardware gabungan Automotive (1408p landscape) dari daftar, lalu klik Next.
- Di halaman berikutnya, pilih image sistem dari langkah sebelumnya. Klik Next dan pilih opsi lanjutan yang Anda inginkan sebelum akhirnya membuat AVD dengan mengklik Finish. Catatan: jika Anda memilih image API 30, image tersebut mungkin berada di tab selain tab Recommended.
Menjalankan aplikasi
Jalankan aplikasi di emulator yang baru saja Anda buat menggunakan konfigurasi run app
yang ada. Coba gunakan aplikasi di berbagai layar dan bandingkan perilakunya dengan ketika dijalankan di emulator ponsel atau tablet.
5. Memperbarui manifes untuk mendeklarasikan dukungan Android Automotive OS
Meskipun aplikasi "berfungsi", ada beberapa perubahan kecil yang perlu dilakukan agar aplikasi berfungsi dengan baik di Android Automotive OS dan memenuhi persyaratan agar dapat dipublikasikan di Play Store. Perubahan ini dapat dilakukan sedemikian rupa sehingga APK atau App Bundle yang sama dapat mendukung perangkat seluler dan Android Automotive OS. Kumpulan perubahan pertama adalah memperbarui file AndroidManifest.xml
untuk menunjukkan bahwa aplikasi mendukung perangkat Android Automotive OS dan merupakan aplikasi video.
Mendeklarasikan fitur hardware otomotif
Untuk menunjukkan bahwa aplikasi Anda mendukung perangkat Android Automotive OS, tambahkan elemen <uses-feature>
berikut dalam file AndroidManifest.xml:
AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
...
<uses-feature
android:name="android.hardware.type.automotive"
android:required="false" />
...
</manifest>
Menggunakan nilai false
untuk atribut android:required
memungkinkan APK atau App Bundle yang dihasilkan didistribusikan ke perangkat Android Automotive OS dan perangkat seluler. Lihat Memilih jenis jalur untuk Android Automotive OS guna mengetahui informasi selengkapnya.
Menandai aplikasi sebagai aplikasi video
Bagian terakhir metadata yang perlu ditambahkan adalah file automotive_app_desc.xml
. File ini digunakan untuk mendeklarasikan kategori aplikasi Anda dalam konteks Android untuk Mobil, dan tidak bergantung pada kategori yang Anda pilih untuk aplikasi di Konsol Play.
- Klik kanan modul
app
dan pilih opsi New > Android Resource File, lalu masukkan nilai berikut sebelum mengklik OK:
- Nama file:
automotive_app_desc.xml
- Jenis resource:
XML
- Elemen root:
automotiveApp
- Set sumber:
main
- Nama direktori:
xml
- Dalam file tersebut, tambahkan elemen
<uses>
berikut untuk mendeklarasikan bahwa aplikasi Anda adalah aplikasi video.
automotive_app_desc.xml
<?xml version="1.0" encoding="utf-8"?>
<automotiveApp xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses
name="video"
tools:ignore="InvalidUsesTagAttribute" />
</automotiveApp>
- Dalam elemen
<application>
yang ada, tambahkan elemen<meta-data>
berikut yang mereferensikan fileautomotive_app_desc.xml
yang baru saja Anda buat.
AndroidManifest.xml
<application ...>
<meta-data
android:name="com.android.automotive"
android:resource="@xml/automotive_app_desc" />
</application>
Dengan demikian, Anda telah membuat semua perubahan yang diperlukan untuk mendeklarasikan dukungan Android Automotive OS.
6. Memenuhi persyaratan kualitas Android Automotive OS: Kemampuan Navigasi
Meskipun mendeklarasikan dukungan Android Automotive OS adalah salah satu bagian dari menghadirkan aplikasi Anda di mobil, Anda tetap perlu memastikan aplikasi dapat digunakan dan aman digunakan.
Menambahkan kemampuan navigasi
Saat menjalankan aplikasi di emulator Android Automotive OS, Anda mungkin melihat bahwa kembali dari layar detail ke layar utama atau dari layar pemutar ke layar detail tidak mungkin dilakukan. Tidak seperti faktor bentuk lainnya, yang mungkin memerlukan tombol kembali atau gestur sentuh untuk mengaktifkan navigasi kembali, perangkat Android Automotive OS tidak memiliki persyaratan semacam itu. Dengan demikian, aplikasi harus menyediakan kemampuan navigasi di UI-nya untuk memastikan pengguna dapat melakukan navigasi tanpa terhenti di layar dalam aplikasi. Persyaratan ini dikodifikasi sebagai pedoman kualitas AN-1.
Untuk mendukung navigasi kembali dari layar detail ke layar utama, tambahkan parameter navigationIcon
tambahan untuk CenterAlignedTopAppBar
layar detail sebagai berikut:
RoadReelsApp.kt
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
...
} else if (route?.startsWith(Screen.Detail.name) == true) {
CenterAlignedTopAppBar(
title = { Text(stringResource(R.string.bbb_title)) },
navigationIcon = {
IconButton(onClick = { navController.popBackStack() }) {
Icon(
Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = null
)
}
}
)
}
Untuk mendukung navigasi kembali dari layar pemutar ke layar utama:
- Perbarui composable
TopControls
untuk mengambil parameter callback bernamaonClose
dan tambahkanIconButton
yang memanggilnya saat diklik.
PlayerControls.kt
import androidx.compose.material.icons.twotone.Close
...
@Composable
fun TopControls(
title: String?,
onClose: () -> Unit,
modifier: Modifier = Modifier
) {
Box(modifier) {
IconButton(
modifier = Modifier
.align(Alignment.TopStart),
onClick = onClose
) {
Icon(
Icons.TwoTone.Close,
contentDescription = "Close player",
tint = Color.White
)
}
if (title != null) { ... }
}
}
- Perbarui composable
PlayerControls
untuk mengambil juga parameter callbackonClose
dan meneruskannya keTopControls
PlayerControls.kt
fun PlayerControls(
uiState: PlayerUiState,
onClose: () -> Unit,
onPlayPause: () -> Unit,
onSeek: (seekToMillis: Long) -> Unit,
modifier: Modifier = Modifier,
) {
AnimatedVisibility(
visible = uiState.isShowingControls,
enter = fadeIn(),
exit = fadeOut()
) {
Box(modifier = modifier.background(Color.Black.copy(alpha = .5f))) {
TopControls(
modifier = Modifier
.fillMaxWidth()
.padding(dimensionResource(R.dimen.screen_edge_padding))
.align(Alignment.TopCenter),
title = uiState.mediaMetadata.title?.toString(),
onClose = onClose
)
...
}
}
}
- Selanjutnya, perbarui composable
PlayerScreen
untuk mengambil parameter yang sama, dan teruskan kePlayerControls
-nya.
PlayerScreen.kt
@Composable
fun PlayerScreen(
onClose: () -> Unit,
modifier: Modifier = Modifier,
viewModel: PlayerViewModel = viewModel()
) {
...
PlayerControls(
modifier = Modifier
.fillMaxSize(),
uiState = playerUiState,
onClose = onClose,
onPlayPause = { if (playerUiState.isPlaying) viewModel.pause() else viewModel.play() },
onSeek = viewModel::seekTo
)
}
- Terakhir, di
RoadReelsNavHost
, berikan implementasi yang akan diteruskan kePlayerScreen
:
RoadReelsNavHost.kt
composable(route = Screen.Player.name, ...) {
PlayerScreen(onClose = { navController.popBackStack() })
}
Sekarang pengguna dapat berpindah antarlayar tanpa terhenti. Selain itu, pengalaman pengguna mungkin juga lebih baik pada faktor bentuk lainnya – misalnya, pada ponsel yang tinggi ketika tangan pengguna sudah berada di dekat bagian atas layar, mereka dapat dengan lebih mudah menavigasi aplikasi tanpa perlu memindahkan perangkat di tangan mereka.
Menyesuaikan dengan dukungan orientasi layar
Tidak seperti kebanyakan perangkat seluler, sebagian besar mobil memiliki orientasi tetap. Artinya, mobil tersebut mendukung orientasi lanskap atau potret, tetapi tidak keduanya, karena layarnya tidak dapat diputar. Oleh karena itu, aplikasi harus menghindari asumsi bahwa kedua orientasi didukung.
Dalam Create an Android Automotive OS manifest, Anda menambahkan dua elemen <uses-feature>
untuk fitur android.hardware.screen.portrait
dan android.hardware.screen.landscape
dengan atribut required
yang ditetapkan ke false
. Tindakan tersebut memastikan bahwa tidak ada dependensi fitur yang implisit pada salah satu orientasi layar yang dapat mencegah aplikasi didistribusikan ke mobil. Akan tetapi, elemen manifes tersebut tidak mengubah perilaku aplikasi, hanya cara pendistribusiannya.
Saat ini, aplikasi memiliki fitur berguna yang otomatis menetapkan orientasi aktivitas ke lanskap saat pemutar video terbuka, sehingga pengguna ponsel tidak perlu mengutak-atik perangkat mereka untuk mengubah orientasinya jika belum berupa lanskap.
Sayangnya, perilaku yang sama dapat menyebabkan loop berkedip atau tampilan lebar di perangkat dengan orientasi potret tetap, seperti yang dimiliki banyak mobil laik jalan saat ini.
Untuk memperbaikinya, Anda dapat menambahkan pemeriksaan berdasarkan orientasi layar yang didukung perangkat saat ini.
- Untuk menyederhanakan implementasinya, tambahkan terlebih dahulu kode berikut di
Extensions.kt
:
Extensions.kt
import android.content.Context
import android.content.pm.PackageManager
...
enum class SupportedOrientation {
Landscape,
Portrait,
}
fun Context.supportedOrientations(): List<SupportedOrientation> {
return when (Pair(
packageManager.hasSystemFeature(PackageManager.FEATURE_SCREEN_LANDSCAPE),
packageManager.hasSystemFeature(PackageManager.FEATURE_SCREEN_PORTRAIT)
)) {
Pair(true, false) -> listOf(SupportedOrientation.Landscape)
Pair(false, true) -> listOf(SupportedOrientation.Portrait)
// For backwards compat, if neither feature is declared, both can be assumed to be supported
//
else -> listOf(SupportedOrientation.Landscape, SupportedOrientation.Portrait)
}
}
- Kemudian, simpan panggilan untuk menetapkan orientasi yang diminta. Karena aplikasi dapat mengalami masalah serupa dalam mode multi-aplikasi di perangkat seluler, Anda juga dapat menyertakan pemeriksaan agar tidak menyetel orientasi secara dinamis dalam kasus tersebut.
PlayerScreen.kt
import com.example.android.cars.roadreels.SupportedOrientation
import com.example.android.cars.roadreels.supportedOrientations
...
DisposableEffect(Unit) {
...
// Only automatically set the orientation to landscape if the device supports landscape.
// On devices that are portrait only, the activity may enter a compat mode and won't get to
// use the full window available if so. The same applies if the app's window is portrait
// in multi-window mode.
if (activity.supportedOrientations().contains(SupportedOrientation.Landscape)
&& !activity.isInMultiWindowMode
) {
activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
}
...
}
Layar pemutar memasuki loop berkedip pada emulator Polestar 2 sebelum menambahkan pemeriksaan (saat aktivitas tidak menangani perubahan konfigurasi | Layar pemutar memiliki tampilan lebar pada emulator Polestar 2 sebelum menambahkan pemeriksaan (saat aktivitas menangani perubahan konfigurasi | Layar pemutar tidak memiliki tampilan lebar pada emulator Polestar 2 setelah menambahkan pemeriksaan |
Karena tempat ini adalah satu-satunya lokasi di aplikasi yang menetapkan orientasi layar, aplikasi kini menghindari tampilan lebar. Di aplikasi Anda sendiri, periksa apakah ada atribut screenOrientation
atau panggilan setRequestedOrientation
yang hanya ditujukan untuk orientasi lanskap atau potret (termasuk sensor
, reverse
, dan varian user
), lalu hapus atau simpan jika diperlukan untuk membatasi tampilan lebar. Untuk mengetahui detail selengkapnya, lihat Mode kompatibilitas perangkat.
Menyesuaikan dengan kemampuan kontrol kolom sistem
Sayangnya, meskipun perubahan sebelumnya memastikan aplikasi tidak memasuki loop berkedip atau membuat tampilan lebar, perubahan ini juga mengekspos asumsi lain yang rusak – yaitu bahwa kolom sistem selalu dapat disembunyikan. Karena pengguna memiliki kebutuhan yang berbeda saat menggunakan mobil (dibandingkan saat menggunakan ponsel atau tablet), OEM memiliki opsi untuk mencegah aplikasi menyembunyikan kolom sistem guna memastikan bahwa kontrol kendaraan, seperti pengontrol kondisi udara, selalu dapat diakses di layar.
Akibatnya, ada potensi bagi aplikasi untuk merender di belakang kolom sistem saat aplikasi tersebut merender dalam mode imersif dan menganggap bahwa kolom tersebut dapat disembunyikan. Anda dapat melihatnya di langkah sebelumnya, karena kontrol pemutar atas dan bawah tidak lagi terlihat saat aplikasi tidak dijadikan tampilan lebar. Dalam contoh khusus ini, aplikasi tidak lagi dapat dijelajahi karena tombol untuk menutup pemutar terhalang dan fungsinya terhambat karena bilah geser tidak dapat digunakan.
Perbaikan yang termudah adalah menerapkan padding inset jendela systemBars
ke pemutar seperti berikut:
PlayerScreen.kt
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.layout.windowInsetsPadding
...
Box(
modifier = Modifier
.fillMaxSize()
.windowInsetsPadding(WindowInsets.systemBars)
) {
PlayerView(...)
PlayerControls(...)
}
Namun, solusi ini tidak ideal karena menyebabkan elemen UI berpindah-pindah ketika kolom sistem bergerak.
Untuk meningkatkan pengalaman pengguna, Anda dapat mengupdate aplikasi guna melacak inset mana yang dapat dikontrol dan menerapkan padding hanya untuk inset yang tidak dapat dikontrol.
- Karena layar lain dalam aplikasi mungkin ingin mengontrol inset jendela, sebaiknya teruskan inset yang dapat dikontrol sebagai
CompositionLocal
. Buat file baru,LocalControllableInsets.kt
, di paketcom.example.android.cars.roadreels
dan tambahkan kode berikut:
LocalControllableInsets.kt
import androidx.compose.runtime.compositionLocalOf
// Assume that no insets can be controlled by default
const val DEFAULT_CONTROLLABLE_INSETS = 0
val LocalControllableInsets = compositionLocalOf { DEFAULT_CONTROLLABLE_INSETS }
- Siapkan
OnControllableInsetsChangedListener
untuk memproses perubahan.
MainActivity.kt
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsControllerCompat.OnControllableInsetsChangedListener
...
class MainActivity : ComponentActivity() {
private lateinit var onControllableInsetsChangedListener: OnControllableInsetsChangedListener
@OptIn(ExperimentalMaterial3WindowSizeClassApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
var controllableInsetsTypeMask by remember { mutableIntStateOf(DEFAULT_CONTROLLABLE_INSETS) }
onControllableInsetsChangedListener =
OnControllableInsetsChangedListener { _, typeMask ->
if (controllableInsetsTypeMask != typeMask) {
controllableInsetsTypeMask = typeMask
}
}
WindowCompat.getInsetsController(window, window.decorView)
.addOnControllableInsetsChangedListener(onControllableInsetsChangedListener)
RoadReelsTheme {
RoadReelsApp(calculateWindowSizeClass(this))
}
}
}
override fun onDestroy() {
super.onDestroy()
WindowCompat.getInsetsController(window, window.decorView)
.removeOnControllableInsetsChangedListener(onControllableInsetsChangedListener)
}
}
- Tambahkan
CompositionLocalProvider
tingkat teratas yang berisi composable tema dan aplikasi, juga yang mengikat nilai keLocalControllableInsets
.
MainActivity.kt
import androidx.compose.runtime.CompositionLocalProvider
...
CompositionLocalProvider(LocalControllableInsets provides controllableInsetsTypeMask) {
RoadReelsTheme {
RoadReelsApp(calculateWindowSizeClass(this))
}
}
- Pada pemutar, baca nilai saat ini dan gunakan untuk menentukan inset untuk disembunyikan dan yang akan digunakan untuk padding.
PlayerScreen.kt
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.union
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.unit.dp
import com.example.android.cars.roadreels.LocalControllableInsets
...
val controllableInsetsTypeMask by rememberUpdatedState(LocalControllableInsets.current)
DisposableEffect(Unit) {
...
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars().and(controllableInsetsTypeMask))
...
}
...
// When the system bars can be hidden, ignore them when applying padding to the player and
// controls so they don't jump around as the system bars disappear. If they can't be hidden
// include them so nothing renders behind the system bars
var windowInsetsForPadding = WindowInsets(0.dp)
if (controllableInsetsTypeMask.and(WindowInsetsCompat.Type.statusBars()) == 0) {
windowInsetsForPadding = windowInsetsForPadding.union(WindowInsets.statusBars)
}
if (controllableInsetsTypeMask.and(WindowInsetsCompat.Type.navigationBars()) == 0) {
windowInsetsForPadding = windowInsetsForPadding.union(WindowInsets.navigationBars)
}
Box(
modifier = Modifier
.fillMaxSize()
.windowInsetsPadding(windowInsetsForPadding)
) {
PlayerView(...)
PlayerControls(...)
}
Konten tidak berpindah-pindah ketika kolom sistem dapat disembunyikan | Konten tetap terlihat ketika kolom sistem tidak dapat disembunyikan |
Konten tampak lebih bagus dan tidak akan berpindah-pindah. Selain itu, kontrol sepenuhnya terlihat, bahkan di mobil yang kolom sistemnya tidak dapat dikontrol.
7. Memenuhi persyaratan kualitas Android Automotive OS: Gangguan bagi pengemudi
Terakhir, ada satu perbedaan utama antara mobil dan faktor bentuk lainnya, yaitu bahwa mobil digunakan untuk mengemudi. Oleh karena itu, membatasi gangguan saat berkendara sangatlah penting. Semua aplikasi parkir untuk Android Automotive OS harus menjeda pemutaran saat batasan pengalaman pengguna aktif dan mencegah kelanjutan pemutaran saat batasan pengalaman pengguna aktif. Overlay sistem muncul saat batasan pengalaman pengguna aktif, dan pada gilirannya, peristiwa siklus proses onPause
dipanggil untuk aplikasi yang ditempatkan. Selama panggilan ini, aplikasi harus menjeda pemutaran.
Simulasi mengemudi
Buka tampilan pemutar di emulator dan mulai memutar konten. Kemudian, ikuti langkah-langkah untuk simulasi mengemudi dan perhatikan bahwa saat UI aplikasi terhalang oleh sistem, pemutaran tidak dijeda. Hal ini melanggar pedoman kualitas aplikasi mobil DD-2.
Menjeda pemutaran ketika mulai mengemudi
- Tambahkan dependensi pada artefak
androidx.lifecycle:lifecycle-runtime-compose
, yang berisiLifecycleEventEffect
yang membantu menjalankan kode pada peristiwa siklus proses.
libs.version.toml
androidx-lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "lifecycle" }
build.gradle.kts (Module :app)
implementation(libs.androidx.lifecycle.runtime.compose)
- Setelah menyinkronkan project untuk mendownload dependensi, tambahkan
LifecycleEventEffect
yang berjalan di peristiwaON_PAUSE
untuk menjeda pemutaran (dan secara opsional di peristiwaON_RESUME
untuk melanjutkan pemutaran).
PlayerScreen.kt
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.LifecycleEventEffect
...
@Composable
fun PlayerScreen(...) {
...
LifecycleEventEffect(Lifecycle.Event.ON_PAUSE) {
viewModel.pause()
}
LifecycleEventEffect(Lifecycle.Event.ON_RESUME) {
viewModel.play()
}
...
}
Setelah perbaikan diterapkan, ikuti langkah yang sama seperti yang Anda lakukan sebelumnya untuk simulasi mengemudi selama pemutaran aktif, dan perhatikan bahwa pemutaran berhenti, yang memenuhi persyaratan DD-2.
8. Menguji aplikasi di emulator distant display
Konfigurasi baru yang mulai muncul di mobil adalah penyiapan dua layar dengan layar utama di konsol tengah dan layar sekunder tinggi di dasbor, dekat kaca depan. Aplikasi dapat dipindahkan dari layar tengah ke layar sekunder, lalu kembali lagi untuk memberikan lebih banyak opsi kepada pengemudi dan penumpang.
Menginstal image Automotive Distant Display
- Pertama, buka SDK Manager di Pratinjau Android Studio, lalu pilih tab SDK Platforms jika belum dipilih. Di pojok kanan bawah jendela SDK Manager, pastikan kotak Show package details dicentang.
- Instal image emulator Automotive Distant Display with Google Play menggunakan API 33 untuk arsitektur komputer Anda (x86/ARM).
Membuat Perangkat Virtual Android untuk Android Automotive OS
- Setelah membuka Pengelola Perangkat, pilih Automotive di kolom Category di sisi kiri jendela. Kemudian, pilih profil hardware paket Automotive Distant Display with Google Play dari daftar, lalu klik Next.
- Di halaman berikutnya, pilih image sistem dari langkah sebelumnya. Klik Next dan pilih opsi lanjutan yang Anda inginkan sebelum akhirnya membuat AVD dengan mengklik Finish.
Menjalankan aplikasi
Jalankan aplikasi di emulator yang baru saja Anda buat menggunakan konfigurasi run app
yang ada. Ikuti petunjuk di Menggunakan emulator distant display untuk memindahkan aplikasi ke dan dari distant display. Uji pemindahan aplikasi saat berada di layar utama/detail dan saat berada di layar pemutar, serta saat mencoba berinteraksi dengan aplikasi di kedua layar.
9. Meningkatkan pengalaman aplikasi di distant display
Saat menggunakan aplikasi di distant display, Anda mungkin memperhatikan dua hal:
- Pemutaran terganggu saat aplikasi dipindahkan ke dan dari distant display.
- Anda tidak dapat berinteraksi dengan aplikasi saat berada di distant display, termasuk mengubah status pemutaran.
Meningkatkan kontinuitas aplikasi
Gangguan pemutaran disebabkan oleh aktivitas yang dibuat ulang karena perubahan konfigurasi. Karena aplikasi ditulis menggunakan Compose dan konfigurasi yang berubah terkait dengan ukuran, Compose dapat menangani perubahan konfigurasi untuk Anda dengan membatasi pembuatan ulang aktivitas untuk perubahan konfigurasi berbasis ukuran. Hal ini membuat transisi antarlayar berjalan lancar, tanpa penghentian dalam pemutaran atau pemuatan ulang karena pembuatan ulang aktivitas.
AndroidManifest.xml
<activity
android:name="com.example.android.cars.roadreels.MainActivity"
...
android:configChanges="screenSize|smallestScreenSize|orientation|screenLayout|density">
...
</activity>
Menerapkan kontrol pemutaran
Untuk memperbaiki masalah ketika aplikasi tidak dapat dikontrol saat berada di distant display, Anda dapat menerapkan MediaSession
. Sesi media menyediakan cara universal untuk berinteraksi dengan pemutar audio atau video. Untuk informasi selengkapnya, lihat Mengontrol dan memberitahukan pemutaran menggunakan MediaSession.
- Menambahkan dependensi pada artefak
androidx.media3:media3-session
libs.version.toml
androidx-media3-mediasession = { group = "androidx.media3", name = "media3-session", version.ref = "media3" }
build.gradle.kts (Module :app)
implementation(libs.androidx.media3.mediasession)
- Di
PlayerViewModel
, tambahkan variabel untuk menyimpan sesi media dan buatMediaSession
menggunakan builder-nya.
PlayerViewModel.kt
import androidx.media3.session.MediaSession
...
class PlayerViewModel(...) {
...
private var mediaSession: MediaSession? = null
init {
viewModelScope.launch {
_player.onEach { player ->
playerUiStateUpdateJob?.cancel()
mediaSession?.release()
if (player != null) {
initializePlayer(player)
mediaSession = MediaSession.Builder(application, player).build()
playerUiStateUpdateJob = viewModelScope.launch {... }
}
}.collect()
}
}
}
- Kemudian, tambahkan baris tambahan di metode
onCleared
untuk merilisMediaSession
saatPlayerViewModel
tidak lagi diperlukan.
PlayerViewModel.kt
override fun onCleared() {
super.onCleared()
mediaSession?.release()
_player.value?.release()
}
- Terakhir, saat berada di layar pemutar (dengan aplikasi di layar utama atau distant display), Anda dapat menguji kontrol media menggunakan perintah
adb shell cmd media_session dispatch
# To play content
adb shell cmd media_session dispatch play
# To pause content
adb shell cmd media_session dispatch pause
# To toggle the playing state
adb shell cmd media_session dispatch play-pause
Membatasi kelanjutan pemutaran
Meskipun mendukung MediaSession
memungkinkan kontrol pemutaran saat aplikasi berada dalam distant display, hal ini menimbulkan satu masalah baru. Yaitu, pemutaran dapat dilanjutkan saat batasan pengalaman pengguna diterapkan, yang melanggar batasan pedoman kualitas DD-2 (lagi!). Untuk mengujinya sendiri:
- Mulai pemutaran
- Simulasikan mengemudi
- Gunakan perintah
media_session dispatch
. Perhatikan bahwa pemutaran dilanjutkan meskipun aplikasi disamarkan.
Untuk memperbaikinya, Anda dapat memproses batasan pengalaman pengguna perangkat dan hanya mengizinkan kelanjutan pemutaran saat aplikasi aktif. Hal ini bahkan dapat dilakukan dengan logika yang sama yang digunakan untuk Android Automotive OS dan perangkat seluler.
- Dalam file
build.gradle
modulapp
, tambahkan kode berikut untuk menyertakan Library Android Automotive dan lakukan sinkronisasi Gradle setelahnya:
build.gradle.kts
android {
...
useLibrary("android.car")
}
- Klik kanan paket
com.example.android.cars.roadreels
, lalu pilih New > Kotlin Class/File. MasukkanRoadReelsPlayer
sebagai nama dan klik jenis Class. - Dalam file yang baru saja Anda buat, tambahkan implementasi awal class berikut. Dengan memperluas
ForwardingSimpleBasePlayer
, Anda dapat dengan mudah mengubah perintah dan interaksi yang didukung untuk pemutar yang digabungkan dengan mengganti metodegetState()
.
RoadReelsPlayer.kt
import android.content.Context
import androidx.media3.common.ForwardingSimpleBasePlayer
import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.ExoPlayer
@UnstableApi
class RoadReelsPlayer(context: Context) :
ForwardingSimpleBasePlayer(ExoPlayer.Builder(context).build()) {
private var shouldPreventPlay = false
override fun getState(): State {
val state = super.getState()
return state.buildUpon()
.setAvailableCommands(
state.availableCommands.buildUpon().removeIf(COMMAND_PLAY_PAUSE, shouldPreventPlay)
.build()
).build()
}
}
- Di
PlayerViewModel.kt
, perbarui deklarasi variabel pemain untuk menggunakan instanceRoadReelsPlayer
, bukanExoPlayer
. Saat ini, perilakunya akan sama persis seperti sebelumnya karenashouldPreventPlay
tidak pernah diperbarui dari nilai defaultnya, yaitufalse
.
PlayerViewModel.kt
init {
...
_player.update { RoadReelsPlayer(application) }
}
- Untuk mulai melacak batasan pengalaman pengguna, tambahkan blok
init
dan implementasihandleRelease
berikut:
RoadReelsPlayer.kt
import android.car.Car
import android.car.drivingstate.CarUxRestrictions
import android.car.drivingstate.CarUxRestrictionsManager
import android.content.pm.PackageManager
import com.google.common.util.concurrent.ListenableFuture
...
@UnstableApi
class RoadReelsPlayer(context: Context) :
ForwardingSimpleBasePlayer(ExoPlayer.Builder(context).build()) {
...
private var pausedByUxRestrictions = false
private lateinit var carUxRestrictionsManager: CarUxRestrictionsManager
init {
with(context) {
// Only listen to UX restrictions if the device is running Android Automotive OS
if (packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
val car = Car.createCar(context)
carUxRestrictionsManager =
car.getCarManager(Car.CAR_UX_RESTRICTION_SERVICE) as CarUxRestrictionsManager
// Get the initial UX restrictions and update the player state
shouldPreventPlay =
carUxRestrictionsManager.currentCarUxRestrictions.isRequiresDistractionOptimization
invalidateState()
// Register a listener to update the player state as the UX restrictions change
carUxRestrictionsManager.registerListener { carUxRestrictions: CarUxRestrictions ->
shouldPreventPlay = carUxRestrictions.isRequiresDistractionOptimization
if (!shouldPreventPlay && pausedByUxRestrictions) {
handleSetPlayWhenReady(true)
invalidateState()
} else if (shouldPreventPlay && isPlaying) {
pausedByUxRestrictions = true
handleSetPlayWhenReady(false)
invalidateState()
}
}
}
addListener(object : Player.Listener {
override fun onEvents(player: Player, events: Player.Events) {
if (events.contains(EVENT_IS_PLAYING_CHANGED) && isPlaying) {
pausedByUxRestrictions = false
}
}
})
}
}
...
override fun handleRelease(): ListenableFuture<*> {
if (::carUxRestrictionsManager.isInitialized) {
carUxRestrictionsManager.unregisterListener()
}
return super.handleRelease()
}
}
Ada beberapa hal yang perlu diperhatikan di sini:
CarUxRestrictionsManager
disimpan sebagai variabellateinit
karena tidak dibuat instance-nya atau digunakan di perangkat non-Android Automotive OS, tetapi pemrosesnya harus dihapus saat pemutar dirilis.- Hanya nilai
isRequiresDistractionOptimization
yang dirujuk saat menentukan status batasan UX. Meskipun classCarUxRestrictions
berisi detail tambahan tentang pembatasan yang aktif, Anda tidak perlu mereferensikannya karena hanya ditujukan untuk digunakan oleh aplikasi distraksi dioptimalkan (seperti aplikasi navigasi), karena aplikasi tersebut akan terus terlihat saat batasan aktif. - Setelah pembaruan pada variabel
shouldPreventPlay
,invalidateState()
dipanggil untuk memberi tahu konsumen tentang status perubahan pemutar. - Di pemroses itu sendiri, pemutaran otomatis dijeda atau dilanjutkan dengan memanggil
handleSetPlayWhenReady
dengan nilai yang sesuai.
- Sekarang, uji lanjutkan pemutaran saat menyimulasikan mengemudi seperti yang dijelaskan di awal bagian ini dan perhatikan bahwa pemutaran tidak dilanjutkan.
- Terakhir, karena menjeda pemutaran saat batasan pengalaman pengguna aktif ditangani oleh
RoadReelsPlayer
,LifecycleEventEffect
tidak perlu menjeda pemutar selamaON_PAUSE
. Sebagai gantinya, hal ini dapat diubah menjadiON_STOP
, sehingga pemutaran berhenti saat pengguna keluar dari aplikasi untuk membuka peluncur atau membuka aplikasi lain.
PlayerScreen.kt
LifecycleEventEffect(Lifecycle.Event.ON_START) {
viewModel.play()
}
LifecycleEventEffect(Lifecycle.Event.ON_STOP) {
viewModel.pause()
}
Rangkuman
Dengan demikian, aplikasi ini berfungsi jauh lebih baik di mobil, baik dengan maupun tanpa distant display. Namun lebih dari itu, aplikasi juga berfungsi lebih baik pada faktor bentuk lainnya. Pada perangkat yang dapat memutar layar atau memungkinkan pengguna mengubah ukuran jendela aplikasi, aplikasi kini juga beradaptasi dengan lancar dalam situasi tersebut.
Selain itu, berkat integrasi sesi media, pemutaran aplikasi dapat dikontrol tidak hanya oleh kontrol hardware dan software di mobil, tetapi juga oleh sumber lain, seperti kueri Asisten Google atau tombol jeda pada perangkat headphone, sehingga pengguna memiliki lebih banyak opsi untuk mengontrol aplikasi di berbagai faktor bentuk.
10. Menguji aplikasi dengan konfigurasi sistem yang berbeda
Karena aplikasi berfungsi dengan baik di layar utama dan distant display, hal terakhir yang harus diperiksa adalah cara aplikasi menangani konfigurasi kolom sistem dan potongan layar yang berbeda. Seperti dijelaskan dalam Menggunakan inset jendela dan potongan layar, perangkat Android Automotive OS mungkin memiliki konfigurasi yang merusak asumsi yang umumnya berlaku pada faktor bentuk seluler.
Di bagian ini, Anda akan mempelajari cara mengonfigurasi emulator agar memiliki kolom sistem sebelah kiri, dan menguji aplikasi dalam konfigurasi tersebut.
Mengonfigurasi kolom sistem samping
Seperti dijelaskan dalam Pengujian menggunakan emulator yang dapat dikonfigurasi, ada berbagai opsi untuk mengemulasi berbagai konfigurasi sistem yang ada di mobil.
Untuk tujuan codelab ini, com.android.systemui.rro.left
dapat digunakan untuk menguji berbagai konfigurasi kolom sistem. Untuk mengaktifkan kode tersebut, gunakan perintah berikut:
adb shell cmd overlay enable --user 0 com.android.systemui.rro.left
Karena aplikasi menggunakan pengubah systemBars
sebagai contentWindowInsets
dalam Scaffold
, konten sudah digambar di area yang aman di kolom sistem. Untuk melihat apa yang akan terjadi jika aplikasi mengasumsikan bahwa kolom sistem hanya muncul di bagian atas dan bawah layar, ubah parameter tersebut menjadi berikut:
RoadReelsApp.kt
contentWindowInsets = if (route?.equals(Screen.Player.name) == true) WindowInsets(0.dp) else WindowInsets.systemBars.only(WindowInsetsSides.Vertical)
Maaf. Layar daftar dan detail dirender di belakang kolom sistem. Berkat pekerjaan sebelumnya, layar pemutar akan berfungsi dengan baik, meskipun kolom sistem tidak dapat dikontrol setelahnya.
Sebelum melanjutkan ke bagian berikutnya, pastikan untuk mengembalikan perubahan yang baru saja Anda buat pada parameter windowContentPadding
.
11. Menggunakan potongan layar
Terakhir, beberapa mobil memiliki layar dengan potongan layar yang sangat berbeda jika dibandingkan dengan yang terlihat di perangkat seluler. Sebagai ganti potongan kamera pinhole atau notch, beberapa kendaraan Android Automotive OS memiliki layar melengkung yang membuat layar menjadi non-persegi panjang.
Untuk melihat bagaimana perilaku aplikasi ketika potongan layar seperti itu ada, pertama-tama aktifkan potongan layar menggunakan perintah berikut:
adb shell cmd overlay enable --user 0 com.android.internal.display.cutout.emulation.top_and_right
Untuk benar-benar menguji seberapa baik perilaku aplikasi, aktifkan juga kolom sistem sebelah kiri yang digunakan di bagian terakhir, jika belum diaktifkan:
adb shell cmd overlay enable --user 0 com.android.systemui.rro.left
Sama halnya, aplikasi tidak merender ke potongan layar (bentuk persis potongan tersebut sulit diketahui saat ini, tetapi akan menjadi jelas di langkah berikutnya). Hal ini tidak menjadi masalah dan memberikan pengalaman yang lebih baik daripada aplikasi yang merender ke potongan, tetapi tidak beradaptasi dengan hati-hati.
Merender ke potongan layar
Untuk memberi pengalaman yang paling imersif kepada pengguna, Anda dapat memanfaatkan ruang layar yang lebih luas dengan merender ke potongan layar.
- Untuk merender ke potongan layar, buat file
integers.xml
guna menyimpan penggantian khusus untuk mobil. Untuk melakukannya, gunakan penentu mode UI dengan nilai Dok Mobil (nama ini adalah peninggalan dari saat hanya Android Auto yang ada, tetapi kini juga digunakan Android Automotive OS). Selain itu, karena nilai yang akan Anda gunakan,LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
, diperkenalkan di Android R, tambahkan juga penentu Versi Android dengan nilai 30. Lihat Menggunakan resource alternatif untuk mengetahui detail selengkapnya.
- Dalam file yang baru saja Anda buat (
res/values-car-v30/integers.xml
), tambahkan kode berikut:
integers.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="windowLayoutInDisplayCutoutMode">3</integer>
</resources>
Nilai bilangan bulat 3
sesuai dengan LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
dan menggantikan nilai default 0
dari res/values/integers.xml
, yang sesuai dengan LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
. Nilai bilangan bulat ini sudah direferensikan di MainActivity.kt
untuk mengganti mode yang ditetapkan oleh enableEdgeToEdge()
. Untuk mengetahui informasi selengkapnya tentang atribut ini, lihat dokumentasi referensi.
Sekarang, saat Anda menjalankan aplikasi, perhatikan bahwa konten meluas ke potongan dan terlihat sangat imersif. Namun, panel aplikasi atas dan beberapa konten terhalang sebagian oleh potongan layar, menyebabkan masalah yang mirip dengan yang terjadi saat aplikasi menganggap kolom sistem hanya akan muncul di bagian atas dan bawah.
Memperbaiki panel aplikasi atas
Untuk memperbaiki panel aplikasi atas, Anda dapat menambahkan parameter windowInsets
berikut ke Composable CenterAlignedTopAppBar
:
RoadReelsApp.kt
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.safeDrawing
...
windowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal + WindowInsetsSides.Top)
Karena safeDrawing
terdiri dari inset displayCutout
dan systemBars
, hal ini meningkatkan parameter windowInsets
default, yang hanya menggunakan systemBars
saat memosisikan panel aplikasi atas.
Selain itu, karena panel aplikasi atas diposisikan di bagian atas jendela, Anda tidak boleh menyertakan komponen bawah inset safeDrawing
karena hal ini berpotensi menambahkan padding yang tidak perlu.
Memperbaiki layar utama
Salah satu opsi untuk memperbaiki konten di layar utama dan layar detail adalah menggunakan safeDrawing
, bukan systemBars
untuk contentWindowInsets
dari Scaffold
. Namun, aplikasi terlihat kurang imersif menggunakan opsi tersebut, dengan konten yang tiba-tiba terpotong saat potongan layar dimulai – tidak lebih baik dibandingkan jika aplikasi tidak dirender ke potongan layar sama sekali.
Untuk antarmuka pengguna yang lebih imersif, Anda dapat menangani inset pada setiap komponen dalam layar.
- Perbarui
contentWindowInsets
dariScaffold
agar selalu menjadi 0 dp (bukan hanya untukPlayerScreen
). Hal ini memungkinkan setiap layar dan/atau komponen dalam layar menentukan perilakunya terkait inset.
RoadReelsApp.kt
Scaffold(
...,
contentWindowInsets = WindowInsets(0.dp)
) { ... }
- Setel
windowInsetsPadding
dari composableText
header baris untuk menggunakan komponen horizontal insetsafeDrawing
. Komponen atas inset ini ditangani oleh panel aplikasi atas, dan komponen bawah akan ditangani nanti.
MainScreen.kt
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.layout.windowInsetsPadding
...
LazyColumn(
contentPadding = PaddingValues(bottom = dimensionResource(R.dimen.screen_edge_padding))
) {
items(NUM_ROWS) { rowIndex: Int ->
Text(
"Row $rowIndex",
style = MaterialTheme.typography.headlineSmall,
modifier = Modifier
.padding(
horizontal = dimensionResource(R.dimen.screen_edge_padding),
vertical = dimensionResource(R.dimen.row_header_vertical_padding)
)
.windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Horizontal))
)
...
}
- Hapus parameter
contentPadding
dariLazyRow
. Kemudian, di awal dan akhir setiapLazyRow
, tambahkanSpacer
selebar komponensafeDrawing
yang sesuai untuk memastikan semua thumbnail dapat dilihat sepenuhnya. Gunakan pengubahwidthIn
untuk memastikan pengatur jarak ini setidaknya selebar padding konten. Tanpa elemen ini, item di awal dan akhir baris mungkin akan terhalang di belakang kolom sistem dan/atau potongan layar, bahkan saat digeser sepenuhnya ke awal/akhir baris.
MainScreen.kt
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.layout.windowInsetsEndWidth
import androidx.compose.foundation.layout.windowInsetsStartWidth
...
LazyRow(
horizontalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.list_item_spacing)),
) {
item {
Spacer(
Modifier
.windowInsetsStartWidth(WindowInsets.safeDrawing)
.widthIn(min = dimensionResource(R.dimen.screen_edge_padding))
)
}
items(NUM_ITEMS_PER_ROW) { ... }
item {
Spacer(
Modifier
.windowInsetsEndWidth(WindowInsets.safeDrawing)
.widthIn(min = dimensionResource(R.dimen.screen_edge_padding))
)
}
}
- Terakhir, tambahkan
Spacer
di akhirLazyColumn
untuk memperhitungkan setiap kolom sistem atau inset potongan layar yang potensial di bagian bawah layar. Pengatur jarak yang setara di bagian atasLazyColumn
tidak diperlukan karena panel aplikasi atas akan menanganinya. Jika aplikasi menggunakan panel aplikasi bawah, bukan panel aplikasi atas, Anda perlu menambahkanSpacer
di awal daftar menggunakan pengubahwindowInsetsTopHeight
. Dan jika aplikasi menggunakan panel aplikasi atas dan bawah, tidak ada pengatur jarak yang diperlukan.
MainScreen.kt
import androidx.compose.foundation.layout.windowInsetsBottomHeight
...
LazyColumn(...){
items(NUM_ROWS) { ... }
item {
Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.safeDrawing))
}
}
Panel aplikasi atas sepenuhnya terlihat dan, saat men-scroll ke akhir baris, Anda kini dapat melihat semua thumbnail secara keseluruhan.
Memperbaiki layar detail
Layar detail tidak terlalu buruk, tetapi konten masih terpotong.
Karena layar detail tidak memiliki konten yang dapat di-scroll, yang diperlukan untuk memperbaikinya adalah menambahkan pengubah windowInsetsPadding
pada Box
tingkat atas.
DetailScreen.kt
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.layout.windowInsetsPadding
...
Box(
modifier = modifier
.padding(dimensionResource(R.dimen.screen_edge_padding))
.windowInsetsPadding(WindowInsets.safeDrawing)
) { ... }
Memperbaiki layar pemutar
Meskipun PlayerScreen
sudah menerapkan padding untuk beberapa atau semua inset jendela kolom sistem di bagian Memenuhi persyaratan kualitas Android Automotive OS: Kemampuan Navigasi, hal itu tidak cukup untuk memastikan bahwa sekarang layar pemutar tidak tertutup sehingga aplikasi dirender ke potongan layar. Pada perangkat seluler, potongan layar hampir selalu sepenuhnya berada dalam kolom sistem. Namun, di mobil, potongan layar mungkin jauh melampaui kolom sistem, sehingga melanggar asumsi.
Untuk memperbaikinya, cukup ubah nilai awal variabel windowInsetsForPadding
dari nilai nol menjadi displayCutout
:
PlayerScreen.kt
import androidx.compose.foundation.layout.displayCutout
...
var windowInsetsForPadding = WindowInsets(WindowInsets.displayCutout)
Bagus, aplikasi ini benar-benar mengoptimalkan layar sekaligus tetap dapat digunakan.
Selain itu, jika Anda menjalankan aplikasi di perangkat seluler, aplikasi tersebut juga akan lebih imersif. Item daftar dirender hingga ke tepi layar, termasuk di belakang menu navigasi.
12. Selamat
Anda berhasil memigrasikan dan mengoptimalkan aplikasi parkir pertama Anda. Sekarang saatnya menggunakan yang telah Anda pelajari dan menerapkannya ke aplikasi Anda sendiri.
Untuk dicoba
- Ganti beberapa nilai resource dimensi untuk menambah ukuran elemen saat dijalankan di mobil
- Coba lebih banyak konfigurasi emulator yang dapat dikonfigurasi
- Uji aplikasi menggunakan beberapa image emulator OEM yang tersedia
Bacaan lebih lanjut
- Membangun aplikasi parkir untuk Android Automotive OS
- Membangun aplikasi video untuk Android Automotive OS
- Membangun game untuk Android Automotive OS
- Membangun browser untuk Android Automotive OS
- Halaman Kualitas aplikasi Android untuk mobil menjelaskan kriteria yang harus dipenuhi aplikasi Anda untuk menciptakan pengalaman pengguna yang sangat baik dan lulus peninjauan Play Store. Pastikan Anda memfilter kategori aplikasi.