Ders 07 / 17
Temel Düzenler
Sütun, satır ve kutu düzenlerinde kısıtların, ölçülerin ve konumların arayüz ağacında hangi yöne gittiğinin ölçümü.
Yayın Güncelleme
Bu derste
- Sütun, satır ve kutu düzenlerinin çocuklarını nasıl dizdiğini ayırt eder
- Düzenin tek geçişinde kısıtın aşağı, ölçünün yukarı, konumun aşağı gittiğini ölçer
- Çocukların sırası değişince konumun değiştiğini, yönün değişmediğini gösterir
- Hizalamanın yalnız konum hesabına girdiğini doğrular
İçindekiler
Önceki derste bir değişikliğin sahibine sessizce çıkabildiği ölçüldü. Bu ders, aynı ağaçta ters yöndeki bir veriye bakar: ebeveynin çocuğa verdiği sınır. Bir sütun düzeni kendisine verilen genişliği çocuklarına iletir; çocuklar bu genişliğin içinde kalarak kendi yüksekliklerini bildirir; sütun da bu yüksekliklerden her çocuğun konumunu çıkarır. Ölçü ekseni kurs boyunca aynıdır: bu veri arayüz ağacında hangi yöne gidiyor? Dört yön vardır. Bir veri ebeveynden doğrudan çocuğuna giderse aşağı, çocuktan ebeveyn zincirinin herhangi bir atasına giderse yukarı, bir atadan ebeveyn atlayarak torununa giderse derin gider; uçlardan biri ağaçta değilse dış olur.
Bu dersin sorusu düzen geçişinin üç verisidir: kısıt, ölçü ve konum. Üçü de aynı iki düğüm arasında dolaşır, ama aynı yöne gitmezler. Hangisinin yönü dersin yönünü belirler, bunu son bölümde seçeceğiz. Önce ölçüyü yapan çekirdek:
// Direction.kt — bir verinin arayuz agacinda hangi yone gittigini siniflandirir
class Tree(private val parentOf: Map<String, String>) {
fun contains(node: String): Boolean = node in parentOf.keys || node in parentOf.values
fun parent(node: String): String? = parentOf[node]
fun ancestors(node: String): List<String> {
val chain = mutableListOf<String>()
var current = parentOf[node]
while (current != null) {
chain.add(current)
current = parentOf[current]
}
return chain
}
}
class Move(val label: String, val from: String, val to: String, tree: Tree) {
val direction: String = when {
!tree.contains(from) || !tree.contains(to) -> "dis"
to in tree.ancestors(from) -> "yukari"
tree.parent(to) == from -> "asagi"
from in tree.ancestors(to) -> "derin"
else -> error("akrabasiz: $from ve $to")
}
fun describe() {
println("$label: kimden=$from | kime=$to -> $direction")
}
}
fun countDirections(moves: List<Move>) {
for (name in listOf("asagi", "yukari", "derin", "dis")) {
println("$name: ${moves.count { it.direction == name }}")
}
}
Sütun, Satır ve Kutu
Üç temel düzen işlevi de bir bileşen işlevidir (@Composable) ve çocuklarını bir lambda içinde alır. Column çocuklarını üstten alta, Row başlangıçtan bitişe dizer (soldan sağa yazılan dilde soldan sağa); Box çocuklarını üst üste koyar. Dizilimin gittiği eksene ana eksen, ona dik eksene çapraz eksen denir. Ana eksendeki boşluk dağılımı Arrangement ile, çapraz eksendeki konum Alignment ile verilir.
// Platform kodu: Column, Row ve Box
@Composable
fun Summary() {
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
Text("Title")
Row(horizontalArrangement = Arrangement.SpaceBetween) {
Text("Name")
Text("Total")
}
Box(contentAlignment = Alignment.BottomEnd) {
Text("Note")
}
}
}
Bu parça üç ağaç ilişkisi kurar. Column, Text, Row ve Box işlevlerinin her biri arayüz ağacında bir düğümdür; Text("Title") ile Row ve Box, Column’un çocuklarıdır; Row’un iki Text’i onun çocuğudur. Sıra, kaynakta yazıldığı sıradır. Hizalama ve boşluk parametreleri düğümün çocuklarına dair kuralı taşır; çocuğun kendisine dair bir şey söylemez.
Düzenin Tek Geçişi
Platform bir düzen düğümünü üç adımda işler. Önce ebeveyn, çocuğa kısıtları verir: çocuğun alabileceği en küçük ve en büyük genişlik ile yükseklik. Sonra çocuk bu aralığın içinde kalan bir boyutu seçip bildirir; buna ölçme denir. Son olarak ebeveyn, bildirilen boyutlara bakıp her çocuğun konumunu belirler; buna yerleştirme denir. Bu üç adım tek geçişte olur: ebeveyn bir çocuğa kısıtı verir, çocuk bir kez ölçülür ve bir kez yerleştirilir.
Model, platformun bu kuralını kurar. Model platformun kendisi değildir; yardımcı dosya yalnız sütun kuralını taşır. Girdi, ebeveynin genişliği, çocukların (ad, genişlik, yükseklik) üçlüleri ve çapraz eksendeki hizalamadır. Çıktı, her çocuğun x ve y konumudur. y, öncekilerin yüksekliklerinin toplamıdır; x hizalamaya bağlıdır. Buradaki sayılar modelin hesabıdır, platformun ölçtüğü piksel değildir.
// Layout.kt — sutun duzeninin tek gecisi (modelin hesabi)
class Placed(val node: String, val x: Int, val y: Int)
fun placeColumn(parentWidth: Int, items: List<Triple<String, Int, Int>>, alignment: String): List<Placed> {
var y = 0
return items.map { (node, width, height) ->
val x = when (alignment) {
"center" -> (parentWidth - width) / 2
"end" -> parentWidth - width
else -> 0
}
val placed = Placed(node, x, y)
y += height
placed
}
}
İlk ölçüm tek çocuğun üç verisidir. Ağaç App’in altında Screen, onun altında Column ve Column’un altında iki çocuk kurar. Title ilk çocuktur. Kısıt Column’dan Title’a verilir. Ölçü Title’dan Column’a döner. Konum yine Column’dan Title’a gider; hareketin hedefi, modelin yerleştirdiği düğümün adıdır.
// Main.kt — tek cocugun kisiti, olcusu ve konumu
fun main() {
val tree = Tree(mapOf("Screen" to "App", "Column" to "Screen", "Title" to "Column", "Body" to "Column"))
val placed = placeColumn(360, listOf(Triple("Title", 200, 48), Triple("Body", 300, 120)), "start")
val first = placed[0]
val moves = listOf(
Move("kisit-baslik", "Column", "Title", tree),
Move("olcu-baslik", first.node, "Column", tree),
Move("konum-baslik", "Column", first.node, tree),
)
for (move in moves) {
move.describe()
}
println("${first.node}: x=${first.x} y=${first.y}")
countDirections(moves)
}
kisit-baslik: kimden=Column | kime=Title -> asagi olcu-baslik: kimden=Title | kime=Column -> yukari konum-baslik: kimden=Column | kime=Title -> asagi Title: x=0 y=0 asagi: 2 yukari: 1 derin: 0 dis: 0
kisit-baslik satırının iki ucu da elle yazılmıştır: bu satır kuralı göstermek için yazılmıştır, ölçüm değildir. Ölçüm sayılan olcu-baslik ve konum-baslik satırlarının bir ucu modelin yerleştirdiği düğümden gelir.
Kısıt ve konum aşağı gitti; ölçü yukarı. Title’ın konumu x=0, y=0: sütunun en üstündeki çocuk, hizalama başta olduğu için sol üst köşeye oturur. Aşağı giden iki hareket farklı veri taşır. Kısıt, çocuğun neyi seçebileceğini söyler; konum, seçilen boyutun nereye konacağını. İkisi de ebeveynin elindedir, çünkü çocuk kendi konumunu bilmez.
AD1. Model kısıtı tek bir sayıya, ebeveynin genişliğine indirir. Platformda kısıt, en küçük ve en büyük genişlik ile yükseklik olmak üzere dört sınırdır ve yükseklik kısıtının sınırsız olduğu durum ölçülmedi.
Sıra Konumu Değiştirir, Yönü Değil
Column çocukları kaynakta yazıldığı sırayla dizer. Aynı iki çocuk ters sırayla verilirse konumlar yer değiştirir. Aşağıdaki ölçüm iki çocuğu iki sırada yerleştirir ve her konum hareketini ağaçta sınıflandırır.
// Main.kt — iki sirada konum hareketleri
fun main() {
val tree = Tree(mapOf("Screen" to "App", "Column" to "Screen", "Title" to "Column", "Body" to "Column"))
val items = listOf(Triple("Title", 200, 48), Triple("Body", 300, 120))
val forward = placeColumn(360, items, "start")
val backward = placeColumn(360, items.reversed(), "start")
val moves = mutableListOf<Move>()
for ((name, order) in listOf("duz" to forward, "ters" to backward)) {
for (p in order) {
moves.add(Move("konum-$name-${p.node.lowercase()}", "Column", p.node, tree))
println("$name ${p.node}: y=${p.y}")
}
}
for (move in moves) {
move.describe()
}
countDirections(moves)
}
duz Title: y=0 duz Body: y=48 ters Body: y=0 ters Title: y=120 konum-duz-title: kimden=Column | kime=Title -> asagi konum-duz-body: kimden=Column | kime=Body -> asagi konum-ters-body: kimden=Column | kime=Body -> asagi konum-ters-title: kimden=Column | kime=Title -> asagi asagi: 4 yukari: 0 derin: 0 dis: 0
Title düz sırada y=0, ters sırada y=120; Body ise y=48 ile y=0 arasında yer değiştirdi. Dört konum hareketi de aynı yöne gitti: aşağı. Sıra, hareketin ucundaki değeri değiştirir; hareketin hangi iki düğüm arasında olduğunu değiştirmez. Bu yüzden sırayı değiştirmek bir düzen sonucunu değiştirir, veri akışını değiştirmez.
AD2. Model çocuklar arasında boşluk bırakmaz; Arrangement değerleri ve spacedBy gibi ana eksen boşlukları y hesabına girmedi.
Hizalama Yalnız Konumdur
Column’da horizontalAlignment, çapraz eksendeki konumu seçer; Row’da verticalAlignment aynı işi dikey eksende yapar. Model sütunun çapraz eksenini x olarak kurduğu için aynı ölçüm Row’un çapraz ekseni için de geçerlidir: eksenler yer değiştirir, ilişki aynı kalır. Hizalama hesabı çocuğun genişliği ve ebeveynin genişliği arasındaki farkı kullanır. Bu fark ebeveynde yerinde hesaplanır; çocuğa gönderilen şey hesabın sonucudur.
// Main.kt — uc hizalamanin konumu
fun main() {
val tree = Tree(mapOf("Screen" to "App", "Column" to "Screen", "Title" to "Column"))
val moves = mutableListOf<Move>()
for (alignment in listOf("start", "center", "end")) {
val title = placeColumn(360, listOf(Triple("Title", 200, 48)), alignment)[0]
println("$alignment: x=${title.x}")
moves.add(Move("konum-$alignment", "Column", title.node, tree))
}
for (move in moves) {
move.describe()
}
countDirections(moves)
}
start: x=0 center: x=80 end: x=160 konum-start: kimden=Column | kime=Title -> asagi konum-center: kimden=Column | kime=Title -> asagi konum-end: kimden=Column | kime=Title -> asagi asagi: 3 yukari: 0 derin: 0 dis: 0
Aynı çocuk üç hizalamada x=0, x=80 ve x=160 aldı. Üç hareket de aynı: ebeveynden çocuğa, aşağı. Hizalama bir veri değil, bir hesap parametresidir; ağaçta yeni bir ilişki kurmaz. Box bu ilişkiyi ikinci eksene taşır: çocukları aynı kutuda üst üste durur ve contentAlignment hem yatay hem dikey konumu birlikte seçer.
AD3. Model hizalamayı üç değere indirir. Platformdaki Alignment değerlerinin tamamı, soldan sağa yazılan dilde başlangıç ve bitiş anlamı ve çocuğun ebeveynden büyük olduğu durum ölçülmedi.
Satır ve Kutu Aynı İlişkiyi Kurar
Row, Column’un eksenleri değiştirilmiş hâlidir. Ana eksen yatay olur: çocukların genişlikleri toplanır, her çocuğun x konumu öncekilerin genişliklerinin toplamı olur. Çapraz eksen dikeydir ve y hizalamaya bağlıdır. Hesabın biçimi aynıdır; değişen yalnız hangi ölçünün toplandığı ve hangisinin ebeveynle karşılaştırıldığıdır. Ağaçta ise hiçbir şey değişmez: Row da çocuklarına kısıt verir, onlardan ölçü alır ve onları yerleştirir. Hareketlerin uçları aynıdır, bu yüzden yönleri de aynıdır.
Box farklı davranır, ama ağaç ilişkisi bakımından değil. Çocukların hiçbiri diğerinin konumunu etkilemez; her çocuk kutunun kısıtı içinde bağımsız ölçülür ve contentAlignment’a göre yerleştirilir. Kutunun kendi boyutu, çocukların en büyüğüne uyar. Bu, ölçünün yukarı gittiği bir durumdur ve ebeveynin boyutu çocuklardan çıkar. Ama bir çocuğun ölçüsü, kardeşinin kısıtını değiştirmez. Kardeşler arasında doğrudan bir veri hareketi yoktur; bu sınır sonraki derslerde sık sık görülecek.
Bu üç düzenin ortak noktası, çocuğun kendi konumunu bilmemesidir. Konum, çocuğun parametresi olarak değil, ebeveynin yerleştirme adımının sonucu olarak oluşur. Bir çocuk “ekranın sağına yaslan” demek isterse bunu ebeveynin hizalama parametresiyle ya da kendi kısıtını genişleten bir değiştiriciyle yapar. Değiştirici konusu ikinci dersin işidir.
AD4. Row ve Box için ayrı bir model kurulmadı; bu bölümdeki hesap tarifi sütun modelinden türetilmiş bir anlatımdır ve ölçülmemiştir.
Sınırlayıcı Ölçüm
Dersin yönü, tek bir hareketin değil üç verinin birlikte okunmasıyla seçilir. Aşağıdaki ölçüm dört hareketi birlikte sayar: Screen’in Column’a verdiği kısıt, Column’un Body’ye verdiği kısıt, Body’nin bildirdiği ölçü ve Column’un Body’ye verdiği konum.
// Main.kt — dort hareketin yon dokumu
fun main() {
val tree = Tree(mapOf("Screen" to "App", "Column" to "Screen", "Title" to "Column", "Body" to "Column"))
val placed = placeColumn(360, listOf(Triple("Title", 200, 48), Triple("Body", 300, 120)), "start")
val body = placed[1]
val moves = listOf(
Move("kisit-sutun", "Screen", "Column", tree),
Move("kisit-govde", "Column", "Body", tree),
Move("olcu-govde", body.node, "Column", tree),
Move("konum-govde", "Column", body.node, tree),
)
for (move in moves) {
move.describe()
}
countDirections(moves)
}
kisit-sutun: kimden=Screen | kime=Column -> asagi kisit-govde: kimden=Column | kime=Body -> asagi olcu-govde: kimden=Body | kime=Column -> yukari konum-govde: kimden=Column | kime=Body -> asagi asagi: 3 yukari: 1 derin: 0 dis: 0
kisit-sutun ve kisit-govde satırlarının ikişer ucu elle yazılmıştır: bu satırlar kuralı göstermek için yazılmıştır, ölçüm değildir. Ölçüm sayılan hareketler, bir ucu modelin yerleştirdiği Body düğümünden gelen olcu-govde (yukarı) ve konum-govde (aşağı) satırlarıdır; aşağı üç sayısının ikisi gösterim satırlarına aittir.
Aşağı üç, yukarı bir. Yukarı giden tek veri ölçüdür; kısıt ve konum aşağıdır. Ölçü kısıtsız olmaz, çünkü çocuk ancak kendisine verilen aralığın içinde bir boyut seçebilir; yani yukarı giden her ölçünün öncesinde aşağı giden bir kısıt vardır. Bu yüzden dersin yönü kısıtın yönüdür: aşağı.
AD5. Model dört hareketi bir Column ve onun altındaki tek bir Body için kurar. Çocuk sayısı arttıkça ölçü ve konum hareketlerinin sayısı çocuk sayısıyla artar; bu ölçüm tek çocukla sınırlıdır.
Özet
- Bu dersin yönü: aşağı. Kısıt ve konum ebeveynden çocuğa iner; ölçülen konum hareketi de aşağıdır.
- Düzenin tek geçişi üç veri taşır: kısıt, ölçü ve konum.
- Kısıt ebeveynden çocuğa, konum ebeveynden çocuğa gider; ikisi de aşağıdır.
- Çocuğun bildirdiği ölçü ebeveyne döner; bu hareket yukarıdır.
- Sıra ve hizalama konumun değerini değiştirir, hareketin yönünü değiştirmez.
Sonraki Adım
Kısıtı çocuğa ileten tek düğüm sütun değildir. Bir bileşen işlevinin üstüne zincirleme eklenen her değiştirici de kendi kısıtını ardındakine geçirir. Sıra değiştiğinde aynı kısıt başka bir düğümden geçerek ilerler. Sonraki ders, sırası yönü değiştiren bu zinciri ölçer.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.