Odaklanma davranışını değiştirme

Bazen ekranınızdaki öğelerin varsayılan odak davranışını geçersiz kılmanız gerekir. Örneğin, composable'ları gruplandırmak, belirli bir composable'a odaklanmayı önlemek, tek bir composable'a açıkça odaklanmayı istemek, yakalama ya da odağı serbest bırakmak veya giriş ya da çıkışta odağı yeniden yönlendirmek isteyebilirsiniz. Bu bölümde, varsayılan ayarlar ihtiyacınız olmadığında odak davranışının nasıl değiştirileceği açıklanmaktadır.

Odak gruplarıyla tutarlı gezinme sağlama

Bazen Jetpack Compose, sekmeli gezinme için bir sonraki doğru öğeyi hemen tahmin etmez. Özellikle de sekmeler ve listeler gibi karmaşık üst Composables öğeleri devreye girdiğinde.

Odak araması genellikle Composables bildirim sırasını takip etse de hiyerarşideki Composables değerlerinden birinin tam olarak görünmeyen yatay kaydırılabilir olması gibi bazı durumlarda bu mümkün değildir. Bu, aşağıdaki örnekte gösterilmektedir.

Jetpack Compose, tek yönlü gezinme için beklediğiniz yoldan devam etmek yerine aşağıda gösterildiği gibi ekranın başlangıcına en yakın olan bir sonraki öğeye odaklanmaya karar verebilir:

Üst yatay gezinme menüsünü ve altındaki öğelerin listesini gösteren bir uygulama animasyonu.
Şekil 1. Üst yatay gezinme bölmesinin ve alttaki öğelerin listesini gösteren uygulama animasyonu

Bu örnekte, geliştiricilerin odak noktasının Çikolatalar sekmesinden aşağıdaki ilk resme atlamak ve ardından Pastalar sekmesine geri dönmek istemediği açıkça görülmektedir. Bunun yerine, son sekmeye kadar sekmelerde çalışmaya ve daha sonra bu sekmenin içinde yer alan içeriğe odaklanmayı istiyorlardı:

Üst yatay gezinme menüsünü ve altındaki öğelerin listesini gösteren bir uygulama animasyonu.
Şekil 2. Üst yatay gezinme bölmesinin ve alttaki öğelerin listesini gösteren uygulama animasyonu

Bir önceki örnekteki Sekme satırında olduğu gibi, bir composable grubunun sıralı olarak odaklanmasının önemli olduğu durumlarda, Composable öğesini focusGroup() değiştiricisine sahip bir üst öğenin içine kaydırmanız gerekir:

LazyVerticalGrid(columns = GridCells.Fixed(4)) {
    item(span = { GridItemSpan(maxLineSpan) }) {
        Row(modifier = Modifier.focusGroup()) {
            FilterChipA()
            FilterChipB()
            FilterChipC()
        }
    }
    items(chocolates) {
        SweetsCard(sweets = it)
    }
}

İki yönlü gezinme, belirli bir yön için en yakın composable'ı arar. Başka bir gruptaki bir öğe, geçerli grupta tam olarak görünür olmayan bir öğeye daha yakınsa gezinme en yakın olanı seçer. Böyle bir davranıştan kaçınmak için focusGroup() değiştiricisini uygulayabilirsiniz.

FocusGroup, tüm grubun odak açısından tek bir varlık gibi görünmesini sağlar ancak odağı grubun kendisi almaz. Bunun yerine, en yakın alt öğe odaklanır. Bu şekilde, gezinme gruptan ayrılmadan önce tam olarak görünür olmayan öğeye gitmeyi bilir.

Bu durumda üç FilterChip örneğine, SweetsCards kullanıcı tamamen görünürken ve bazı FilterChip gizlenmiş olsa bile SweetsCard öğeden önce odaklanılır. Bunun nedeni, focusGroup değiştiricisinin odak yöneticisine öğelerin odaklanılacağı sırayı ayarlamasını sağlayarak gezinmenin daha kolay ve kullanıcı arayüzü ile daha tutarlı olmasını sağlamasıdır.

focusGroup değiştiricisi kullanılmadığında, FilterChipC görünür olmasaydı odak gezinmesi bu öğeyi en son seçer. Bununla birlikte, böyle bir değiştirici eklemek yalnızca bulunabilir değil, aynı zamanda kullanıcıların beklediği gibi FilterChipB sonrasında odaklanmayı da edinir.

Bir composable'ı odaklanılabilir hale getirme

Düğme veya clickable değiştiricisine sahip bir composable gibi bazı composable'lar tasarım gereği odaklanabilir. Bir composable'a özellikle odaklanılabilir davranış eklemek istiyorsanız focusable değiştiricisini kullanabilirsiniz:

var color by remember { mutableStateOf(Green) }
Box(
    Modifier
        .background(color)
        .onFocusChanged { color = if (it.isFocused) Blue else Green }
        .focusable()
) {
    Text("Focusable 1")
}

Bir composable'ı odaklanamaz hale getirmek

Öğelerinizden bazılarının odakta yer almaması gereken durumlar olabilir. Bu gibi nadir durumlarda, bir Composable öğesinin odaklanmayı önlemek için canFocus property özelliğinden yararlanabilirsiniz.

var checked by remember { mutableStateOf(false) }

Switch(
    checked = checked,
    onCheckedChange = { checked = it },
    // Prevent component from being focused
    modifier = Modifier
        .focusProperties { canFocus = false }
)

FocusRequester ile klavye odağı iste

Bazı durumlarda, bir kullanıcı etkileşimine yanıt olarak açıkça odaklanma isteğinde bulunmak isteyebilirsiniz. Örneğin, kullanıcıya bir formu doldurmaya yeniden başlamak isteyip istemediğini sorabilir ve "evet"e bastığında bu formun ilk alanına yeniden odaklanmayı isteyebilirsiniz.

Yapmanız gereken ilk şey, bir FocusRequester nesnesini klavye odağını taşımak istediğiniz composable ile ilişkilendirmektir. Aşağıdaki kod snippet'inde FocusRequester nesnesi, Modifier.focusRequester adlı bir değiştirici ayarlanarak TextField ile ilişkilendirilir:

val focusRequester = remember { FocusRequester() }
var text by remember { mutableStateOf("") }

TextField(
    value = text,
    onValueChange = { text = it },
    modifier = Modifier.focusRequester(focusRequester)
)

Gerçek odak istekleri göndermek için FocusRequester'ın requestFocus yöntemini çağırabilirsiniz. Bu yöntemi Composable bağlamı dışında çağırmanız gerekir (aksi takdirde, her yeniden oluşturmada yeniden yürütülür). Aşağıdaki snippet'te, düğme tıklandığında sistemden klavye odağını taşıması için nasıl istekte bulunulacağı gösterilmektedir:

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")
}

Odağı yakalayıp serbest bırakın

Geçerli bir e-posta adresi veya telefon numarası almak gibi, uygulamanızın işlerini yapmak için ihtiyaç duyduğu doğru verileri (ör. geçerli bir e-posta adresi veya telefon numarası almak) sağlama konusunda kullanıcılarınıza rehberlik etmek için odaklanma özelliğinden yararlanabilirsiniz. Hata durumları kullanıcılarınıza ne olduğu konusunda bilgi verse de, hatalı bilgiler içeren alana, sorun düzeltilene kadar odaklanabilmek için ihtiyacınız olabilir.

Odağı yakalamak için captureFocus() yöntemini çağırabilir ve daha sonra aşağıdaki örnekte olduğu gibi freeFocus() yöntemini kullanarak serbest bırakabilirsiniz:

val textField = FocusRequester()

TextField(
    value = text,
    onValueChange = {
        text = it

        if (it.length > 3) {
            textField.captureFocus()
        } else {
            textField.freeFocus()
        }
    },
    modifier = Modifier.focusRequester(textField)
)

Odak değiştiricilerin önceliği

Modifiers, yalnızca bir alt öğesi olan öğeler olarak görülebilir. Bu nedenle, bunları sıraladığınızda soldaki (veya üstte) her bir Modifier, sağda (veya altında) takip eden Modifier öğesini sarar. Bu, ikinci Modifier öğesinin ilkinin içinde bulunduğu anlamına gelir. Böylece iki focusProperties bildirilirken yalnızca en üsttekiler çalışır. Bunun nedeni, aşağıdakilerin en üstte yer almasıdır.

Kavramı daha iyi açıklamak için aşağıdaki koda bakın:

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

Bu durumda, önceki örnekte yer aldığı için item2 doğru odak noktasını gösteren focusProperties kullanılmaz; dolayısıyla, item1 kullanılır.

Ebeveynler, bu yaklaşımdan yararlanarak FocusRequester.Default özelliğini kullanarak bu davranışı varsayılan değere sıfırlayabilir:

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

Üst öğenin aynı değiştirici zincirinin parçası olması gerekmez. Bir üst composable, bir alt composable'ın odak özelliğinin üzerine yazabilir. Örneğin, düğmenin odaklanılamamasına neden olan şu FancyButton öğesini düşünün:

@Composable
fun FancyButton(modifier: Modifier = Modifier) {
    Row(modifier.focusProperties { canFocus = false }) {
        Text("Click me")
        Button(onClick = { }) { Text("OK") }
    }
}

Kullanıcılar, canFocus değerini true olarak ayarlayarak bu düğmeyi tekrar odaklanabilir hale getirebilir:

FancyButton(Modifier.focusProperties { canFocus = true })

Her Modifier gibi odakla ilgili olanlar da bildirdiğiniz sıraya göre farklı şekilde davranır. Örneğin, aşağıdaki gibi kod, Box öğesini odaklanılabilir hale getirir ancak FocusRequester, odaklanılabilir öğeden sonra bildirildiği için bu odaklanılabilir ile ilişkilendirilmez.

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

focusRequester öğesinin, hiyerarşide altında bulunan ilk odaklanılan öğeyle ilişkilendirildiği unutulmamalıdır. Dolayısıyla bu focusRequester, odaklanılabilir ilk alt öğeyi gösterir. Kullanılabilir alan yoksa herhangi bir yere işaret etmez. Bununla birlikte, Box odaklanılabilir olduğundan (focusable() değiştiricisi sayesinde) iki yönlü gezinme kullanarak bu öğeye gidebilirsiniz.

Başka bir örnek vermek gerekirse onFocusChanged() değiştiricisi, focusable() veya focusTarget() değiştiricilerinden sonra görünen ilk odaklanılabilir öğeye karşılık geldiğinden aşağıdakilerden biri işe yarar.

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

Giriş veya çıkışta odağı yönlendir

Bazen, aşağıdaki animasyonda gösterilene benzer çok spesifik bir gezinme türü sağlamanız gerekir:

Yan yana yerleştirilmiş iki düğme sütununu gösteren ve odağı bir sütundan diğerine yönlendiren bir ekran animasyonu.
Şekil 3. Yan yana yerleştirilmiş iki düğme sütununu gösteren ve odağı bir sütundan diğerine yönlendiren bir ekran animasyonu

Bunu nasıl oluşturabileceğinize geçmeden önce, odak aramasının varsayılan davranışını anlamak önemlidir. Herhangi bir değişiklik yapılmadan, odak araması Clickable 3 öğesine ulaştığında d-pad'de DOWN tuşuna (veya eşdeğer ok tuşuna) basıldığında odak, Column öğesinin altında görüntülenen öğeye taşınır, gruptan çıkılır ve sağdaki öğe yoksayılır. Odaklanabilir öğe yoksa odak herhangi bir yere taşınmaz ancak Clickable 3 üzerinde kalır.

Bu davranışı değiştirmek ve amaçlanan gezinmeyi sağlamak için, odak araması Composable öğesine girdiğinde veya buradan çıktığında ne olacağını yönetmenize yardımcı olan focusProperties değiştiricisini kullanabilirsiniz:

val otherComposable = remember { FocusRequester() }

Modifier.focusProperties {
    exit = { focusDirection ->
        when (focusDirection) {
            Right -> Cancel
            Down -> otherComposable
            else -> Default
        }
    }
}

Odağı, hiyerarşinin belirli bir bölümüne girdiğinde veya belirli bir bölümünden çıktığında belirli bir Composable öğesine yönlendirmek mümkündür. Örneğin, kullanıcı arayüzünüzde iki sütun olduğunda ve ilk sütun işlenirken odak ikinciye geçtiğinden emin olmak istediğinizde:

Yan yana yerleştirilmiş iki düğme sütununu gösteren ve odağı bir sütundan diğerine yönlendiren bir ekran animasyonu.
Şekil 4. Yan yana yerleştirilmiş iki düğme sütununu gösteren ve odağı bir sütundan diğerine yönlendiren bir ekran animasyonu

Bu GIF'te, odak Column 1 öğesinde Clickable 3 Composable değerine ulaştığında, odaklanılan bir sonraki öğe başka bir Column öğesinde Clickable 4 olur. Bu davranış, focusProperties değiştiricisinin içindeki focusDirection ile enter ve exit değerleri birleştirilerek elde edilebilir. Her ikisinin de odağın geldiği yönü ve FocusRequester döndüren bir lambdaya ihtiyacı vardır. Bu lambda üç farklı şekilde davranabilir: FocusRequester.Cancel döndürülmesi odağın devam etmesini durdurur, ancak FocusRequester.Default davranışını değiştirmez. Bunun yerine, başka bir Composable öğesine ekli FocusRequester sağlamak, odağı ilgili Composable öğesine atlar.

Odaklanma yönünü değiştirme

Odağı bir sonraki öğeye veya belirli bir yöne doğru ilerletmek için onPreviewKey değiştiricisinden yararlanabilir ve moveFocus Değiştirici ile odağı ilerletmek için LocalFocusManager işaretini ima edebilirsiniz.

Aşağıdaki örnekte odak mekanizmasının varsayılan davranışı gösterilmektedir: tab tuşuna basıldığında odak, odak listesindeki bir sonraki öğeye ilerler. Bu, normalde yapılandırmanız gerekmese de, varsayılan çalışma biçimini değiştirebilmek için sistemin iç işleyişini bilmek önemlidir.

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
        }
    }
)

Bu örnekte focusManager.moveFocus() işlevi, odağı belirtilen öğeye veya işlev parametresinde belirtilen yöne ilerletir.