Compose'da ConstraintLayout

ConstraintLayout, composable'ları ekrandaki diğer composable'lara göre yerleştirmenize olanak tanıyan bir düzendir. İç içe yerleştirilmiş birden fazla Row, Column, Box ve diğer özel düzen öğelerini kullanmaya alternatif bir yöntemdir.

Görünüm sisteminde, düz görünüm hiyerarşisi performans açısından iç içe yerleştirilmiş görünümlerden daha iyi olduğundan büyük ve karmaşık düzenler oluşturmak için ConstraintLayout öneriliyordu. Ancak bu durum, derin düzen hiyerarşilerini verimli bir şekilde işleyebilen Compose'da bir sorun teşkil etmez. Bu nedenle ConstraintLayout o kadar faydalı değildir.

ConstraintLayout çözümünü kullanmaya başlayın

Compose'da ConstraintLayout kullanmak için bu bağımlılığı build.gradle dosyanıza eklemeniz gerekir (Compose kurulumuna ek olarak):

implementation "androidx.constraintlayout:constraintlayout-compose:$constraintlayout_compose_version"

Compose'da ConstraintLayout, DSL kullanarak aşağıdaki şekilde çalışır:

  • createRefs() veya createRefFor() kullanarak ConstraintLayout içindeki her composable için referans oluşturun.
  • Kısıtlamalar, referansı parametre olarak alan ve kısıtlamalarını gövde lambda'da belirtmenize olanak tanıyan constrainAs() değiştiricisi kullanılarak sağlanır.
  • Kısıtlamalar linkTo() veya diğer faydalı yöntemler kullanılarak belirtilir.
  • parent, ConstraintLayout composable'ının kendisiyle ilgili kısıtlamaları belirtmek için kullanılabilecek mevcut bir referanstır.

ConstraintLayout kullanan bir composable örneği:

@Composable
fun ConstraintLayoutContent() {
    ConstraintLayout {
        // Create references for the composables to constrain
        val (button, text) = createRefs()

        Button(
            onClick = { /* Do something */ },
            // Assign reference "button" to the Button composable
            // and constrain it to the top of the ConstraintLayout
            modifier = Modifier.constrainAs(button) {
                top.linkTo(parent.top, margin = 16.dp)
            }
        ) {
            Text("Button")
        }

        // Assign reference "text" to the Text composable
        // and constrain it to the bottom of the Button composable
        Text(
            "Text",
            Modifier.constrainAs(text) {
                top.linkTo(button.bottom, margin = 16.dp)
            }
        )
    }
}

Bu kod, Button öğesinin üst kısmını 16.dp kenar boşluğuyla üst öğeye, Text öğesini ise Button öğesinin alt kısmına yine 16.dp kenar boşluğuyla kısıtlar.

Düğme, metnin üzerinde görünür.
Şekil 1. ConstraintLayout içinde birbirine kısıtlanmış bir Button ve bir Text composable.

Bağımsız API

ConstraintLayout örneğinde kısıtlamalar, uygulandıkları composable'da bir değiştirici ile satır içinde belirtilir. Ancak kısıtlamaları, uygulandıkları düzenlerden ayırmanın tercih edildiği durumlar da vardır. Örneğin, kısıtlamaları ekran yapılandırmasına göre değiştirmek veya iki kısıtlama grubu arasında animasyon oluşturmak isteyebilirsiniz.

Bu gibi durumlarda ConstraintLayout özelliğini farklı bir şekilde kullanabilirsiniz:

  1. ConstraintLayout için parametre olarak bir ConstraintSet iletin.
  2. ConstraintSet içinde oluşturulan referansları, layoutId değiştiricisini kullanarak composable'lara atayın.

@Composable
fun DecoupledConstraintLayout() {
    BoxWithConstraints {
        val constraints = if (minWidth < 600.dp) {
            decoupledConstraints(margin = 16.dp) // Portrait constraints
        } else {
            decoupledConstraints(margin = 32.dp) // Landscape constraints
        }

        ConstraintLayout(constraints) {
            Button(
                onClick = { /* Do something */ },
                modifier = Modifier.layoutId("button")
            ) {
                Text("Button")
            }

            Text("Text", Modifier.layoutId("text"))
        }
    }
}

private fun decoupledConstraints(margin: Dp): ConstraintSet {
    return ConstraintSet {
        val button = createRefFor("button")
        val text = createRefFor("text")

        constrain(button) {
            top.linkTo(parent.top, margin = margin)
        }
        constrain(text) {
            top.linkTo(button.bottom, margin)
        }
    }
}

Ardından, kısıtlamaları değiştirmeniz gerektiğinde farklı bir ConstraintSet iletebilirsiniz.

ConstraintLayout kavram

ConstraintLayout, composable'ınızdaki öğeleri konumlandırmanıza yardımcı olabilecek kılavuzlar, engeller ve zincirler gibi kavramlar içerir.

Kurallar

Kılavuz çizgileri, düzen tasarlarken kullanabileceğiniz küçük görsel yardımcılardır. Birleştirilebilir öğeler bir kılavuz çizgiyle sınırlandırılabilir. Kılavuz çizgileri, öğeleri üst composable'da belirli bir dp veya percentage konumuna yerleştirmek için kullanışlıdır.

Dikey ve yatay olmak üzere iki farklı kılavuz çizgi türü vardır. Yatay olanlar top ve bottom, dikey olanlar ise start ve end.

ConstraintLayout {
    // Create guideline from the start of the parent at 10% the width of the Composable
    val startGuideline = createGuidelineFromStart(0.1f)
    // Create guideline from the end of the parent at 10% the width of the Composable
    val endGuideline = createGuidelineFromEnd(0.1f)
    //  Create guideline from 16 dp from the top of the parent
    val topGuideline = createGuidelineFromTop(16.dp)
    //  Create guideline from 16 dp from the bottom of the parent
    val bottomGuideline = createGuidelineFromBottom(16.dp)
}

Kural oluşturmak için createGuidelineFrom* ile birlikte gerekli kural türünü kullanın. Bu işlem, Modifier.constrainAs() bloğunda kullanılabilecek bir referans oluşturur.

Engeller

Engeller, belirtilen taraftaki en uç widget'a göre sanal bir kılavuz oluşturmak için birden fazla composable'a referans verir.

Bariyer oluşturmak için createTopBarrier() (veya: createBottomBarrier(), createEndBarrier(), createStartBarrier()) kullanın ve bariyeri oluşturması gereken referansları sağlayın.

ConstraintLayout {
    val constraintSet = ConstraintSet {
        val button = createRefFor("button")
        val text = createRefFor("text")

        val topBarrier = createTopBarrier(button, text)
    }
}

Ardından bariyer, bir Modifier.constrainAs() bloğunda kullanılabilir.

Zincir

Zincirler, tek bir eksende (yatay veya dikey) grup benzeri davranış sağlar. Diğer eksen bağımsız olarak kısıtlanabilir.

Zincir oluşturmak için createVerticalChain veya createHorizontalChain kullanın:

ConstraintLayout {
    val constraintSet = ConstraintSet {
        val button = createRefFor("button")
        val text = createRefFor("text")

        val verticalChain = createVerticalChain(button, text, chainStyle = ChainStyle.Spread)
        val horizontalChain = createHorizontalChain(button, text)
    }
}

Zincir daha sonra Modifier.constrainAs() bloğunda kullanılabilir.

Zincir, bir composable'ı çevreleyen alanın nasıl ele alınacağını belirleyen farklı ChainStyles ile yapılandırılabilir. Örneğin:

  • ChainStyle.Spread: Alan, ilk composable'dan önceki ve son composable'dan sonraki boş alan da dahil olmak üzere tüm composable'lara eşit olarak dağıtılır.
  • ChainStyle.SpreadInside: Alan, tüm composable'lar arasında eşit olarak dağıtılır. İlk composable'dan önce veya son composable'dan sonra boş alan kalmaz.
  • ChainStyle.Packed: Boşluk, ilk composable'dan önce ve son composable'dan sonra dağıtılır. Composable'lar, aralarında boşluk olmadan bir arada paketlenir.