Mengubah perilaku fokus

Terkadang perlu untuk mengganti perilaku fokus default dari elemen di layar Anda. Misalnya, Anda mungkin ingin mengelompokkan composable, mencegah berfokus pada composable tertentu, secara eksplisit meminta fokus pada satu composable, ambil atau rilis fokus, atau alihkan fokus saat masuk atau keluar. Ini menjelaskan cara mengubah perilaku fokus jika setelan defaultnya tidak sesuai dengan butuhkan.

Menyediakan navigasi yang koheren dengan grup fokus

Terkadang, Jetpack Compose tidak langsung menebak item berikutnya yang benar untuk navigasi tab, khususnya saat induk yang kompleks Composables seperti tab dan penggunaan daftar.

Sementara penelusuran fokus biasanya mengikuti urutan deklarasi Composables, hal ini tidak mungkin dilakukan dalam kasus tertentu, seperti jika salah satu dari Composables dalam hierarki adalah horizontal yang dapat di-scroll dan tidak sepenuhnya terlihat. Hal ini ditunjukkan di contoh di bawah ini.

Jetpack Compose dapat memutuskan untuk memfokuskan item berikutnya yang paling dekat dengan awal seperti yang ditunjukkan di bawah ini, daripada melanjutkan di jalur yang Anda harapkan navigasi satu arah:

Animasi aplikasi yang menampilkan navigasi horizontal atas dan daftar item di bawah.
Gambar 1. Animasi aplikasi yang menampilkan navigasi horizontal atas dan daftar item di bawah

Dalam contoh ini, jelas bahwa pengembang tidak bermaksud untuk fokus pada dari tab Cokelat ke gambar pertama di bawah ini, lalu kembali ke Tab Pastri. Sebaliknya, mereka ingin fokus berlanjut pada tab hingga tab terakhir, lalu fokus pada konten dalam:

Animasi aplikasi yang menampilkan navigasi horizontal atas dan daftar item di bawah.
Gambar 2. Animasi aplikasi yang menampilkan navigasi horizontal atas dan daftar item di bawah

Dalam situasi ketika grup composable harus mendapatkan fokus secara berurutan, seperti di baris Tab dari contoh sebelumnya, Anda harus 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 elemen yang ditentukan arah— jika suatu elemen dari kelompok lain lebih dekat dari elemen yang tidak terlihat sepenuhnya dalam grup saat ini, navigasi akan memilih yang terdekat. Untuk menghindari hal ini yang sama, Anda dapat menerapkan pengubah focusGroup().

FocusGroup membuat seluruh grup tampak seperti satu entitas dalam hal fokus, tetapi grup itu sendiri tidak akan mendapatkan fokus— sebaliknya, turunan terdekat akan mendapatkan fokus. Dengan cara ini, navigasi tahu harus menuju ke tempat yang tidak sebelum meninggalkan grup.

Dalam hal ini, tiga instance FilterChip akan difokuskan sebelum SweetsCard item, meskipun SweetsCards benar-benar terlihat oleh pengguna dan beberapa FilterChip mungkin disembunyikan. Hal ini terjadi karena Pengubah focusGroup memberi tahu pengelola fokus untuk menyesuaikan urutan item terfokus sehingga navigasi lebih mudah dan lebih koheren dengan UI.

Tanpa pengubah focusGroup, jika FilterChipC tidak terlihat, fokus navigasi akan mengambilnya terakhir. Namun, menambahkan pengubah ini tidak hanya dapat ditemukan, tetapi juga akan memperoleh fokus tepat setelah FilterChipB, karena diharapkan pengguna.

Membuat composable dapat difokuskan

Beberapa composable dapat difokuskan secara desain, seperti Tombol atau composable dengan pengubah clickable yang dilampirkan. Jika Anda secara khusus ingin menambahkan perilaku yang dapat difokuskan ke composable, Anda menggunakan 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 dapat 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 }
)

Meminta fokus keyboard dengan FocusRequester

Dalam beberapa kasus, Anda mungkin ingin secara eksplisit meminta fokus sebagai respons terhadap interaksi pengguna. Misalnya, Anda mungkin bertanya kepada pengguna apakah mereka ingin memulai ulang komputer. mengisi formulir, dan jika mereka menekan "ya" Anda ingin memfokuskan ulang bidang pertama dari bentuk tersebut.

Hal pertama yang harus dilakukan adalah mengaitkan objek FocusRequester dengan composable yang ingin dijadikan tujuan pemindahan fokus keyboard. Dalam kode berikut , objek FocusRequester akan 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 requestFocusFocusRequester untuk mengirim permintaan fokus yang sebenarnya. Anda harus memanggil metode ini di luar konteks Composable (jika tidak, akan dijalankan kembali di 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")
}

Ambil dan lepaskan fokus

Anda bisa memanfaatkan fokus untuk memandu pengguna agar menyediakan data yang tepat untuk aplikasi Anda diperlukan untuk menjalankan tugasnya— misalnya, mendapatkan alamat email atau nomor telepon yang valid angka Meskipun status {i>error<i} memberi tahu pengguna tentang apa yang sedang terjadi, Anda mungkin membutuhkan bidang dengan informasi yang salah untuk tetap fokus sampai diperbaiki.

Untuk mengambil fokus, Anda dapat memanggil metode captureFocus(), dan rilis setelahnya dengan metode freeFocus(), seperti dalam contoh:

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 ketika Anda mengantrekan mereka, masing-masing Modifier di kiri (atau atas) menggabungkan Modifier yang mengikuti sebelah kanan (atau di bawahnya). Ini berarti Modifier kedua berada di dalam yang pertama, sehingga saat mendeklarasikan dua focusProperties, hanya elemen yang satu berfungsi, karena yang berikut ini berada di paling atas.

Untuk memperjelas konsep ini lebih lanjut, lihat kode berikut:

Modifier
    .focusProperties { right = item1 }
    .focusProperties { right = item2 }
    .focusable()

Dalam hal ini, focusProperties yang menunjukkan item2 sebagai fokus yang tepat akan tidak digunakan, seperti yang tercantum dalam pembahasan sebelumnya; jadi, item1 akan menjadi satu yang digunakan.

Dengan memanfaatkan pendekatan ini, induk juga dapat mereset perilaku tersebut ke default dengan menggunakan FocusRequester.Default:

Modifier
    .focusProperties { right = Default }
    .focusProperties { right = item1 }
    .focusProperties { right = item2 }
    .focusable()

Induk tidak harus menjadi bagian dari rantai pengubah yang sama. Orang tua dapat menimpa properti fokus composable turunan. Misalnya, pertimbangkan FancyButton berikut 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, fungsi yang terkait fokus akan berperilaku berbeda berdasarkan urutannya Anda mendeklarasikannya. Misalnya, kode seperti berikut membuat Box dapat difokuskan, tetapi FocusRequester tidak terkait dengan objek yang dapat difokuskan ini karena dideklarasikan setelah focusable.

Box(
    Modifier
        .focusable()
        .focusRequester(Default)
        .onFocusChanged {}
)

Perlu diingat bahwa focusRequester dikaitkan dengan dapat difokuskan di bawahnya dalam hierarki, jadi focusRequester ini menunjuk ke turunan pertama yang dapat difokuskan. Jika tidak ada yang tersedia, ikon tidak akan mengarah ke apa pun. Namun, karena Box dapat difokuskan (berkat pengubah focusable()), Anda dapat menavigasi ke sana menggunakan navigasi dua arah.

Sebagai contoh lain, salah satu dari berikut ini akan berfungsi, karena onFocusChanged() pengubah mengacu 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()
)

Mengalihkan fokus saat masuk atau keluar

Terkadang, Anda perlu menyediakan jenis navigasi yang sangat spesifik, seperti yang yang ditampilkan dalam animasi di bawah ini:

Animasi layar yang menampilkan dua kolom tombol yang ditempatkan berdampingan dan menganimasikan fokus dari satu kolom ke kolom lainnya.
Gambar 3. Animasi layar yang menampilkan dua kolom tombol yang ditempatkan berdampingan dan menganimasikan fokus dari satu kolom ke kolom lainnya

Sebelum mempelajari cara membuatnya, penting untuk memahami perilaku penelusuran fokus. Tanpa modifikasi apa pun, setelah penelusuran fokus mencapai item Clickable 3, menekan DOWN pada D-Pad (atau metode yang tombol panah) akan memindahkan fokus ke apa pun yang ditampilkan di bawah Column, keluar dari grup dan mengabaikan grup di sebelah kanan. Jika tidak ada item yang dapat difokuskan tersedia, fokus tidak akan berpindah ke mana pun, tetapi tetap aktif Clickable 3.

Untuk mengubah perilaku ini dan menyediakan navigasi yang diinginkan, Anda bisa memanfaatkan Pengubah focusProperties, yang membantu Anda mengelola apa yang terjadi saat fokus penelusuran 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 masuk atau keluar dari bagian hierarki tertentu— misalnya, saat UI Anda memiliki dua kolom dan Anda ingin memastikan bahwa kapan pun yang pertama diproses, fokus akan beralih ke yang kedua:

Animasi layar yang menampilkan dua kolom tombol yang ditempatkan berdampingan dan menganimasikan fokus dari satu kolom ke kolom lainnya.
Gambar 4. Animasi layar yang menampilkan dua kolom tombol yang ditempatkan berdampingan dan menganimasikan fokus dari satu kolom ke kolom lainnya

Dalam gif ini, setelah fokus mencapai Clickable 3 Composable dalam Column 1, item berikutnya yang difokuskan adalah Clickable 4 di Column lainnya. Perilaku ini dapat dicapai dengan menggabungkan focusDirection dengan enter dan exit nilai di dalam pengubah focusProperties. Keduanya membutuhkan lambda yang mengambil sebagai parameter, arah asal fokus dan mengembalikan FocusRequester. Lambda ini dapat berperilaku dengan tiga cara berbeda: menampilkan FocusRequester.Cancel menghentikan fokus agar tidak berlanjut, sementara FocusRequester.Default tidak mengubah perilakunya. Sebagai gantinya, menyediakan FocusRequester yang dilampirkan ke Composable lain akan membuat fokus melompat ke elemen tersebut Composable spesifik.

Ubah arah kemajuan fokus

Untuk memajukan fokus ke item berikutnya atau menuju arah yang tepat, Anda dapat manfaatkan pengubah onPreviewKey dan menyiratkan LocalFocusManager untuk majukan fokus dengan Pengubah moveFocus.

Contoh berikut menunjukkan perilaku default mekanisme fokus: ketika sebuah Penekanan tombol tab terdeteksi, fokus akan berpindah ke elemen berikutnya dalam fokus daftar. Meskipun ini bukan sesuatu yang biasanya perlu Anda konfigurasi, hal ini penting mengetahui cara kerja bagian dalam sistem agar dapat mengubah {i>default<i} perilaku model.

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 arah yang tersirat dalam parameter fungsi.