Terkadang, Anda perlu mengganti perilaku fokus default elemen di layar. Misalnya, Anda mungkin ingin mengelompokkan composable, mencegah fokus pada composable tertentu, secara eksplisit meminta fokus pada composable, mengambil atau melepaskan fokus, atau mengalihkan fokus ke composable masuk atau keluar. Bagian ini menjelaskan cara mengubah perilaku fokus jika default tidak yang Anda butuhkan.
Menyediakan navigasi yang koheren dengan grup fokus
Terkadang, Jetpack Compose tidak langsung menebak item berikutnya yang benar untuk
navigasi tab, terutama saat Composables
induk yang kompleks seperti tab dan
daftar berperan.
Meskipun penelusuran fokus biasanya mengikuti urutan deklarasi Composables
,
hal ini tidak mungkin dilakukan dalam beberapa kasus, seperti saat salah satu Composables
dalam
hierarki adalah dapat di-scroll horizontal yang tidak sepenuhnya terlihat. Hal ini ditunjukkan dalam
contoh di bawah ini.
Jetpack Compose dapat memutuskan untuk memfokuskan item berikutnya yang paling dekat dengan awal layar, seperti yang ditunjukkan di bawah, alih-alih melanjutkan jalur yang Anda harapkan untuk navigasi satu arah:
Dalam contoh ini, jelas bahwa developer tidak bermaksud untuk melompat dari tab Cokelat ke gambar pertama di bawah, lalu kembali ke tab Pastries. Sebaliknya, mereka ingin fokus berlanjut pada tab hingga tab terakhir, lalu berfokus pada konten dalam:
Dalam situasi ketika sekelompok composable harus mendapatkan fokus
secara berurutan, seperti di baris Tab dari contoh sebelumnya, Anda perlu menggabungkan
Composable
dalam induk yang memiliki pengubah focusGroup()
:
LazyVerticalGrid(columns = GridCells.Fixed(4)) { item(span = { GridItemSpan(maxLineSpan) }) { Row(modifier = Modifier.focusGroup()) { FilterChipA() FilterChipB() FilterChipC() } } items(chocolates) { SweetsCard(sweets = it) } }
Navigasi dua arah mencari composable terdekat untuk arah
yang diberikan— jika elemen dari grup lain lebih dekat daripada item yang tidak terlihat
sepenuhnya dalam grup saat ini, navigasi akan memilih elemen terdekat. Untuk menghindari perilaku
ini, Anda dapat menerapkan pengubah focusGroup()
.
FocusGroup
membuat seluruh grup muncul seperti entitas tunggal dalam hal fokus,
tetapi grup itu sendiri tidak akan mendapatkan fokus— sebagai gantinya, turunan terdekat akan
mendapatkan fokus. Dengan cara ini, navigasi tahu cara menuju ke item yang tidak terlihat
sepenuhnya sebelum keluar dari grup.
Dalam hal ini, tiga instance FilterChip
akan difokuskan sebelum
item SweetsCard
, bahkan saat SweetsCards
sepenuhnya terlihat oleh
pengguna dan beberapa FilterChip
mungkin tersembunyi. Hal ini terjadi karena
pengubah focusGroup
memberi tahu pengelola fokus untuk menyesuaikan urutan item
difokus sehingga navigasi lebih mudah dan lebih koheren dengan UI.
Tanpa pengubah focusGroup
, jika FilterChipC
tidak terlihat, navigasi
fokus akan mengambilnya terakhir kali. Namun, menambahkan pengubah semacam itu tidak
hanya dapat ditemukan, tetapi juga akan memperoleh fokus tepat setelah FilterChipB
, seperti
yang diharapkan pengguna.
Membuat composable dapat difokuskan
Beberapa composable dapat difokuskan secara desain, seperti Tombol atau composable dengan
pengubah clickable
yang terpasang padanya. Jika Anda ingin secara khusus menambahkan
perilaku yang dapat difokuskan ke composable, gunakan pengubah focusable
:
var color by remember { mutableStateOf(Green) } Box( Modifier .background(color) .onFocusChanged { color = if (it.isFocused) Blue else Green } .focusable() ) { Text("Focusable 1") }
Membuat composable tidak dapat difokuskan
Mungkin ada situasi saat beberapa elemen Anda tidak boleh berpartisipasi
dalam fokus. Dalam kasus yang jarang terjadi ini, Anda dapat memanfaatkan canFocus property
untuk mengecualikan Composable
agar tidak dapat difokuskan.
var checked by remember { mutableStateOf(false) } Switch( checked = checked, onCheckedChange = { checked = it }, // Prevent component from being focused modifier = Modifier .focusProperties { canFocus = false } )
Minta fokus keyboard dengan FocusRequester
Dalam beberapa kasus, Anda mungkin ingin secara eksplisit meminta fokus sebagai respons terhadap interaksi pengguna. Misalnya, Anda dapat bertanya kepada pengguna apakah mereka ingin memulai ulang pengisian formulir, dan jika mereka menekan "ya", Anda ingin memfokuskan ulang kolom pertama formulir tersebut.
Hal pertama yang harus dilakukan adalah mengaitkan objek FocusRequester
dengan
composable yang ingin Anda pindahkan fokus keyboard. Dalam cuplikan
kode berikut, objek FocusRequester
dikaitkan dengan TextField
dengan menyetel
pengubah yang disebut Modifier.focusRequester
:
val focusRequester = remember { FocusRequester() } var text by remember { mutableStateOf("") } TextField( value = text, onValueChange = { text = it }, modifier = Modifier.focusRequester(focusRequester) )
Anda dapat memanggil metode requestFocus
FocusRequester untuk mengirim permintaan fokus yang sebenarnya. Anda harus memanggil metode ini di luar konteks Composable
(jika tidak, metode ini akan dieksekusi ulang pada setiap rekomposisi). Cuplikan berikut
menunjukkan cara meminta sistem untuk memindahkan fokus keyboard saat tombol
diklik:
val focusRequester = remember { FocusRequester() } var text by remember { mutableStateOf("") } TextField( value = text, onValueChange = { text = it }, modifier = Modifier.focusRequester(focusRequester) ) Button(onClick = { focusRequester.requestFocus() }) { Text("Request focus on TextField") }
Mengambil dan melepaskan fokus
Anda dapat memanfaatkan fokus untuk memandu pengguna memberikan data yang tepat yang diperlukan aplikasi Anda untuk menjalankan tugasnya—misalnya, mendapatkan alamat email atau nomor telepon yang valid. Meskipun status error memberi tahu pengguna tentang apa yang terjadi, Anda mungkin memerlukan kolom dengan informasi yang salah agar tetap fokus hingga diperbaiki.
Untuk mengambil fokus, Anda dapat memanggil metode captureFocus()
, dan
melepasnya setelahnya dengan metode freeFocus()
, seperti pada contoh
berikut:
val textField = FocusRequester() TextField( value = text, onValueChange = { text = it if (it.length > 3) { textField.captureFocus() } else { textField.freeFocus() } }, modifier = Modifier.focusRequester(textField) )
Prioritas pengubah fokus
Modifiers
dapat dilihat sebagai elemen yang hanya memiliki satu turunan, jadi saat Anda mengantrekannya, setiap Modifier
di sebelah kiri (atau atas) menggabungkan Modifier
yang mengikuti di sebelah kanan (atau di bawahnya). Ini berarti Modifier
kedua dimuat di dalam
yang pertama, sehingga saat mendeklarasikan dua focusProperties
, hanya yang paling
teratas yang berfungsi, karena yang berikut berada di bagian paling atas.
Untuk memperjelas konsepnya lebih lanjut, lihat kode berikut:
Modifier .focusProperties { right = item1 } .focusProperties { right = item2 } .focusable()
Dalam hal ini, focusProperties
yang menunjukkan item2
sebagai fokus yang tepat tidak akan
digunakan, seperti yang terdapat dalam fokus sebelumnya; sehingga item1
akan menjadi
yang digunakan.
Dengan memanfaatkan pendekatan ini, induk juga dapat mereset perilaku ke default menggunakan FocusRequester.Default
:
Modifier .focusProperties { right = Default } .focusProperties { right = item1 } .focusProperties { right = item2 } .focusable()
Induk tidak harus menjadi bagian dari rantai pengubah yang sama. Composable
induk dapat menimpa properti fokus composable turunan. Misalnya,
perhatikan FancyButton
ini yang membuat tombol tidak dapat difokuskan:
@Composable fun FancyButton(modifier: Modifier = Modifier) { Row(modifier.focusProperties { canFocus = false }) { Text("Click me") Button(onClick = { }) { Text("OK") } } }
Pengguna dapat membuat tombol ini dapat difokuskan lagi dengan menyetel canFocus
ke true
:
FancyButton(Modifier.focusProperties { canFocus = true })
Seperti setiap Modifier
, yang terkait fokus memiliki perilaku yang berbeda berdasarkan urutan
Anda mendeklarasikannya. Misalnya, kode seperti berikut membuat Box
dapat difokuskan, tetapi FocusRequester
tidak terkait dengan dapat difokuskan karena
dideklarasikan setelah focusable.
Box( Modifier .focusable() .focusRequester(Default) .onFocusChanged {} )
Penting untuk diingat bahwa focusRequester
dikaitkan dengan elemen pertama
yang dapat difokuskan di bawahnya dalam hierarki, sehingga focusRequester
ini mengarah ke
turunan pertama yang dapat difokuskan. Jika tidak ada yang tersedia, ikon tidak akan menunjuk ke apa pun.
Namun, karena Box
dapat difokuskan (berkat pengubah focusable()
),
Anda dapat menavigasi ke dalamnya menggunakan navigasi dua arah.
Sebagai contoh lainnya, salah satu dari hal berikut akan berfungsi, karena pengubah onFocusChanged()
merujuk pada elemen pertama yang dapat difokuskan yang muncul setelah
pengubah focusable()
atau focusTarget()
.
Box( Modifier .onFocusChanged {} .focusRequester(Default) .focusable() ) |
Box( Modifier .focusRequester(Default) .onFocusChanged {} .focusable() ) |
Alihkan fokus saat masuk atau keluar
Terkadang, Anda perlu menyediakan jenis navigasi yang sangat spesifik, seperti yang ditunjukkan pada animasi di bawah ini:
Sebelum mempelajari cara membuatnya, penting untuk memahami perilaku
default penelusuran fokus. Tanpa modifikasi apa pun, setelah penelusuran fokus
mencapai item Clickable 3
, menekan DOWN
pada D-Pad (atau tombol panah
yang setara) akan memindahkan fokus ke apa pun yang ditampilkan di bawah Column
,
keluar dari grup dan mengabaikan yang di sebelah kanan. Jika tidak ada
item yang dapat difokuskan, fokus tidak akan berpindah ke mana pun, tetapi tetap pada
Clickable 3
.
Untuk mengubah perilaku ini dan menyediakan navigasi yang diinginkan, Anda dapat memanfaatkan
pengubah focusProperties
, yang membantu Anda mengelola hal yang terjadi saat penelusuran
fokus memasuki atau keluar dari Composable
:
val otherComposable = remember { FocusRequester() } Modifier.focusProperties { exit = { focusDirection -> when (focusDirection) { Right -> Cancel Down -> otherComposable else -> Default } } }
Anda dapat mengarahkan fokus ke Composable
tertentu setiap kali memasuki
atau keluar dari bagian hierarki tertentu—misalnya, saat UI memiliki dua
kolom dan Anda ingin memastikan bahwa setiap kali kolom pertama diproses,
fokus beralih ke kolom kedua:
Dalam gif ini, setelah fokus mencapai Clickable 3 Composable
di Column
1,
item berikutnya yang difokuskan adalah Clickable 4
di Column
lainnya. Perilaku ini
dapat dicapai dengan menggabungkan focusDirection
dengan nilai enter
dan exit
di dalam pengubah focusProperties
. Keduanya memerlukan lambda yang menggunakan
parameter, arah asal fokus, dan menampilkan
FocusRequester
. Lambda ini dapat berperilaku dengan tiga cara berbeda: menampilkan
FocusRequester.Cancel
akan menghentikan fokus agar tidak dilanjutkan, sedangkan
FocusRequester.Default
tidak mengubah perilakunya. Sebagai gantinya, memberikan
FocusRequester
yang dilampirkan ke Composable
lain akan membuat fokus melompat ke
Composable
khusus tersebut.
Mengubah arah memajukan fokus
Untuk memajukan fokus ke item berikutnya atau ke arah yang tepat, Anda dapat
memanfaatkan pengubah onPreviewKey
dan menyiratkan LocalFocusManager
untuk
memajukan fokus dengan Pengubah moveFocus
.
Contoh berikut menunjukkan perilaku default mekanisme fokus: saat
penekanan tombol tab
terdeteksi, fokus akan maju ke elemen berikutnya dalam daftar
fokus. Meskipun ini bukan sesuatu yang biasanya perlu Anda konfigurasi, penting
untuk mengetahui cara kerja bagian dalam sistem agar dapat mengubah perilaku
default.
val focusManager = LocalFocusManager.current var text by remember { mutableStateOf("") } TextField( value = text, onValueChange = { text = it }, modifier = Modifier.onPreviewKeyEvent { when { KeyEventType.KeyUp == it.type && Key.Tab == it.key -> { focusManager.moveFocus(FocusDirection.Next) true } else -> false } } )
Dalam contoh ini, fungsi focusManager.moveFocus()
memajukan fokus ke
item yang ditentukan, atau ke arah yang tersirat dalam parameter fungsi.
Direkomendasikan untuk Anda
- Catatan: teks link ditampilkan saat JavaScript nonaktif
- Bereaksi terhadap fokus
- Fokus di Compose
- Mengubah urutan traversal fokus