---
title: 'Klasik Görünüm Sistemi'
source: 'https://academia.sh/tr/kurslar/compose/klasik-gorunum-sistemi'
course: 'Jetpack Compose ile Arayüz'
language: tr
updated: '2026-10-21T05:02:38+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Klasik görünüm hiyerarşisinde ekranın kodunun derindeki görünüme doğrudan ulaştığını ölçer'
    - 'Bir bileşen işlevine konan klasik görünüme giden verinin dış olduğunu gösterir'
    - 'Klasik görünümün içine konan bildirimsel içeriğin sınırı da dış bir hareketle geçtiğini ayırt eder'
    - 'Aynı etiketin yerelde aşağı, sarılı görünümde dış gittiğini açıklar'
---

# Klasik Görünüm Sistemi

Klasik görünüm hiyerarşisi ile bildirimsel ağacın karşılaştırmasının, sarılan bir görünüme giden verinin dış yönünün ve iki ağacın sınırını geçen hareketin ölçümü; kursun on yedi dersinin kapanış tablosu.

Önceki ders birleşen etiketi ekran okuyucuya taşıyarak kursun ağacını ağaç dışındaki bir okuyucuyla kapattı. Bu kursun ağacı bildirimsel arayüzün ağacıdır. Birçok uygulama ise klasik görünüm sistemiyle yazılmış ekranlar taşır: arayüz bir `xml` dosyasında bildirilir, görünümler koşum anında dosyadan kurulur ve ekranın kodu bir görünüme kimliğiyle ulaşıp ona doğrudan değer yazar. İki dünya aynı ekranda bir arada durabilir. Bu dersin sorusu, bir veri ikisinin sınırını geçtiğinde yönün nasıl adlandırıldığıdır. Çekirdek dosya önceki derslerle aynıdır.

```kotlin
// 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 }}")
    }
}
```

## Klasik Hiyerarşi

Klasik görünüm sisteminde ekran bir `xml` dosyasıdır. Dosyadaki iç içe geçme görünüm hiyerarşisini kurar: dıştaki öge ebeveyn, içindekiler çocuktur. Ekranın kodu hiyerarşiyi parametrelerle aşağı doğru beslemez; kimliğiyle bulduğu görünüme doğrudan yazar.

```xml
<!-- Platform kodu: klasik görünüm düzeni, res/layout/screen.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <Button android:id="@+id/pay"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    </FrameLayout>
</LinearLayout>
```

Model dosyanın girintisini düğüm listesi olarak alır ve ondan bir ağaç kurar. Ağaç aynı `Tree` sınıfıdır; yalnız kuruluş biçimi farklıdır. Hareketlerin uçları bu dersin ölçümlerinde elle yazılmaz: kurulan ağaçtan ve düğüm listesinden seçilir. `deepest` listedeki en derin düğümü, `firstAt` verilen girinti düzeyindeki ilk düğümü verir; ekran düğümü de ağacın `ancestors` zincirinin sonundan okunur.

```kotlin
// Inflate.kt — girintili xml dugum listesinden agac kurar ve dugum secer (modelin hesabi)
fun inflate(root: String, nodes: List<Pair<Int, String>>): Tree {
    val parents = linkedMapOf<String, String>()
    val path = mutableListOf(root)
    for ((depth, name) in nodes) {
        while (path.size > depth) path.removeAt(path.size - 1)
        parents[name] = path.last()
        path.add(name)
    }
    return Tree(parents)
}

fun deepest(nodes: List<Pair<Int, String>>): String = nodes.maxByOrNull { it.first }!!.second
fun firstAt(depth: Int, nodes: List<Pair<Int, String>>): String = nodes.first { it.first == depth }.second
```

İlk ölçüm aynı ekranı iki biçimde kurar ve aynı iki veriyi taşır: başlığın metni ve düğmenin tıklama olayı. Klasik tarafta ekranın kodu başlığa ara görünümleri atlayarak yazar. Bildirimsel tarafta metin sütundan geçerek iner.

```kotlin
// Main.kt — ayni ekran: xml hiyerarsisi ve bildirimsel agac
fun main() {
    val classicNodes = listOf(1 to "LinearLayout", 2 to "Title", 2 to "Frame", 3 to "PayButton")
    val classic = inflate("Screen", classicNodes)
    val button = deepest(classicNodes)
    val screen = classic.ancestors(button).last()
    val declarative = Tree(mapOf("Screen" to "App", "Column" to "Screen", "Title" to "Column", "PayButton" to "Column"))
    val column = declarative.parent("Title")!!
    val declarativeScreen = declarative.parent(column)!!
    val classicMoves = listOf(
        Move("baslik-yaz", screen, firstAt(2, classicNodes), classic),
        Move("dugme-dinleyici", button, screen, classic),
    )
    val declarativeMoves = listOf(
        Move("baslik-yol-1", declarativeScreen, column, declarative),
        Move("baslik-yol-2", column, "Title", declarative),
        Move("dugme-olayi", "PayButton", declarativeScreen, declarative),
    )
    println("klasik:")
    classicMoves.forEach { it.describe() }
    countDirections(classicMoves)
    println("bildirimsel:")
    declarativeMoves.forEach { it.describe() }
    countDirections(declarativeMoves)
}
```

```text
klasik:
baslik-yaz: kimden=Screen | kime=Title -> derin
dugme-dinleyici: kimden=PayButton | kime=Screen -> yukari
asagi: 0
yukari: 1
derin: 1
dis: 0
bildirimsel:
baslik-yol-1: kimden=Screen | kime=Column -> asagi
baslik-yol-2: kimden=Column | kime=Title -> asagi
dugme-olayi: kimden=PayButton | kime=Screen -> yukari
asagi: 2
yukari: 1
derin: 0
dis: 0
```

Klasik ekranın kodu başlığa tek hareketle, derin gider. Bildirimsel ekranda aynı metin iki aşağı hareketle iner. Düğmenin olayı iki tarafta da yukarıdır. Fark kanaldadır: kimlikle erişim aradaki düğümleri atlar, parametre zinciri her düğümden geçer. Kural kanalı görmez; iki hareketin geometrisini sınıflar.

**AD6.** Model xml dosyasını girintili bir düğüm listesi olarak alır. Özniteliklerin, stillerin, kimlikle aramanın maliyetinin ve görünümlerin ölçülüp yerleştirildiği geçişin modelde karşılığı yoktur.

## Bileşen İşlevi İçindeki Görünüm

Klasik bir görünümü bildirimsel ağaca koymanın yolu `AndroidView`'dir. Bileşen işlevi, görünümün kurulmasını, ardından kurulduğunda ve güncelleme bloğunun okuduğu durum değiştiğinde güncellenmesini ister.

```kotlin
// Platform kodu: klasik bir görünümü bileşen işlevinin içine koymak
@Composable
fun LabelHost(caption: String) {
    AndroidView(
        factory = { context -> TextView(context) },
        update = { view -> view.text = caption },
    )
}
```

`LabelHost` ağaçta bir düğümdür. İçindeki `TextView` ise değildir: bildirimsel ağaç onu çocuk olarak bilmez, platformun görünüm hiyerarşisi ona sahiptir. Modelde sarılan görünüm, ağaçta bulunmayan bir ad olarak kurulur.

```kotlin
// Bridge.kt — sarilan gorunum agacin disinda kalir (modelin hesabi)
class ClassicHost(val node: String, val view: String, tree: Tree) {
    val viewInTree: Boolean = tree.contains(view)
}
```

Ölçüm, veriyi sarmalayıcıya, sarılı görünüme ve görünümden geriye doğru izler.

```kotlin
// Main.kt — sarilan gorunume giden ve ondan donen veri
fun main() {
    val tree = Tree(mapOf("Screen" to "App", "Header" to "Screen", "LabelHost" to "Screen"))
    val host = ClassicHost("LabelHost", "LabelView", tree)
    println("sarilan gorunum agacta: ${host.viewInTree}")
    val moves = listOf(
        Move("sarmalayici", "Screen", host.node, tree),
        Move("guncelleme", host.node, host.view, tree),
        Move("dogrudan", "Screen", host.view, tree),
        Move("dokunus", host.view, host.node, tree),
    )
    moves.forEach { it.describe() }
    countDirections(moves)
}
```

```text
sarilan gorunum agacta: false
sarmalayici: kimden=Screen | kime=LabelHost -> asagi
guncelleme: kimden=LabelHost | kime=LabelView -> dis
dogrudan: kimden=Screen | kime=LabelView -> dis
dokunus: kimden=LabelView | kime=LabelHost -> dis
asagi: 1
yukari: 0
derin: 0
dis: 3
```

Sarmalayıcıya giden veri aşağıdır: ebeveyn çocuğuna verir. Sarılı görünüme ulaşan her hareket dış, görünümden dönen dokunuş da dış. Sarmalayıcı üzerinden gitmek ile ekrandan doğrudan gitmek aynı sonucu verir, çünkü ağaçta olmayan bir uca giden hareketin yolu kuralı değiştirmez.

**AD7.** Model sarılan görünümü ağaçta olmayan bir ad olarak kurar. `factory` ile `update` ayrımı, güncellemenin hangi yeniden oluşturmada çalıştığı ve görünümün yaşam döngüsü modelde yoktur.

## Görünüm İçindeki Bildirimsel İçerik

Ters yön de mümkündür: klasik bir hiyerarşinin içine `ComposeView` konur ve bildirimsel içerik onun içinde oluşturulur.

```kotlin
// Platform kodu: bildirimsel içeriği klasik görünüm hiyerarşisine koymak
val composeView = ComposeView(context).apply {
    setContent {
        Panel { Label(text = "Sepet") }
    }
}
frame.addView(composeView)
```

Bu durumda iki ağaç vardır. Klasik hiyerarşide `ComposeView` bir yapraktır. İçindeki `Panel` ve `Label` ise ikinci bir bildirimsel ağaca aittir. Ölçüm iki ağacı ayrı kurar ve sınırı geçen hareketi iki taraftan da sınıflar.

```kotlin
// Main.kt — iki agacin siniri
fun main() {
    val classicNodes = listOf(1 to "LinearLayout", 2 to "Frame", 3 to "ComposeView")
    val classic = inflate("Screen", classicNodes)
    val host = deepest(classicNodes)
    val screen = classic.ancestors(host).last()
    val inner = Tree(mapOf("Label" to "Panel"))
    val panel = inner.parent("Label")!!
    val moves = listOf(
        Move("duzen-veri", screen, host, classic),
        Move("sinir-klasik", host, panel, classic),
        Move("sinir-bildirimsel", host, panel, inner),
        Move("icerde", panel, "Label", inner),
    )
    moves.forEach { it.describe() }
    countDirections(moves)
}
```

```text
duzen-veri: kimden=Screen | kime=ComposeView -> derin
sinir-klasik: kimden=ComposeView | kime=Panel -> dis
sinir-bildirimsel: kimden=ComposeView | kime=Panel -> dis
icerde: kimden=Panel | kime=Label -> asagi
asagi: 1
yukari: 0
derin: 1
dis: 2
```

Klasik ağaçta ekrandan `ComposeView`'e giden veri derindir. Sınırın ötesine geçen hareket ise hangi ağaçtan bakılırsa bakılsın dıştır: kaynak ya da hedef, bakılan ağaçta yoktur. Ağaç içi hareket ancak `Panel`'den `Label`'a, yani ikinci ağacın içinde aşağıdır.

**AD8.** Model iki ağacı iki ayrı `Tree` olarak kurar. `ComposeView`'in yaşam döngüsü sahibine bağlanması, yeniden oluşturma kapsamının sınırı ve iki ağacın tek pencerede nasıl bütünleştiği ölçülmedi.

## Sınırlayıcı Ölçüm

Dersin yönü, bir uç ağaçta olmadığında dış adının verilmesinden gelir. Kural, ögenin klasik mi bildirimsel mi olduğunu görmez; hangi ağaçta bulunduğunu görür. Aynı etiket iki biçimde kurulsun: yerel bir bileşen işleviyle ve `AndroidView` ile sarılı bir görünümle. Sarılı görünümün kendi iç hiyerarşisi ayrı bir ağaç olarak ölçülebilir.

```kotlin
// Main.kt — ayni etiket iki bicimde
fun main() {
    val tree = Tree(mapOf("Screen" to "App", "Header" to "Screen", "Label" to "Screen", "LabelHost" to "Screen"))
    val wrappedNodes = listOf(1 to "Glyphs")
    val wrapped = inflate("LabelView", wrappedNodes)
    val glyphs = deepest(wrappedNodes)
    val labelView = wrapped.parent(glyphs)!!
    val screen = tree.parent("Label")!!
    val moves = listOf(
        Move("yerel-etiket", screen, "Label", tree),
        Move("sarili-etiket", screen, labelView, tree),
        Move("sarili-ici", labelView, glyphs, wrapped),
    )
    moves.forEach { it.describe() }
    countDirections(moves)
}
```

```text
yerel-etiket: kimden=Screen | kime=Label -> asagi
sarili-etiket: kimden=Screen | kime=LabelView -> dis
sarili-ici: kimden=LabelView | kime=Glyphs -> asagi
asagi: 2
yukari: 0
derin: 0
dis: 1
```

Aynı etiket yerelde aşağı, sarılı görünümde dış gider. Sarılı görünümün içinde de bir hiyerarşi vardır ve o kendi ağacında aşağıdır; ama bildirimsel ağaç bu hiyerarşiyi göremez. Dersin yönü: **dış**.

Bu ayrım iki başka soruyla karıştırılmaz. M31/K02'nin sorusu, bir isteği platformun nasıl karşıladığıdır; burada istenenle verilen karşılaştırılmaz. M30/K04'ün sorusu, bir ekranın modelle uyuşup uyuşmadığıdır; burada ekranın uyumu hiçbir yerde sorulmaz. İkisi de kategori ya da ölçü adı yapılmaz: ölçü baştan sona verinin ağaçtaki yönüdür.

**AD9.** Model sarılı görünümün iç yapısını tek bir çocukla kurar. Gerçek bir görünümün çocuk sayısı, kendi içindeki yeniden çizim ve ölçüm sırası ölçülmedi.

## Özet

- Bu dersin yönü: dış. Sarılan görünüm ağaçta olmayan bir uçtur ve bir ucu ağaçta olmayan her hareket, ölçüm `inflate` dönüşünden seçilen düğümlerle kurulduğunda da dış çıkar.
- Klasik görünüm hiyerarşisi xml dosyasından kurulur; ekranın kodu derindeki görünüme kimliğiyle doğrudan ulaşır ve bu hareket derindir.
- Bir bileşen işlevine konan klasik görünüm bildirimsel ağaçta değildir; ona giden ve ondan dönen her hareket dıştır.
- Klasik hiyerarşinin içine konan bildirimsel içerik ikinci bir ağaçtır; sınırı geçen hareket iki taraftan da dıştır.
- Aynı etiket yerelde aşağı, sarılı görünümde dış gider; kural öge türünü değil, ağaç üyeliğini görür.

## Kurs Kapanışı

Kurs, tek bir soruyla on yedi dersi ölçtü: bu veri arayüz ağacında hangi yöne gidiyor? Her dersin ölçülen hareketi, yönü ve sınırlayıcı ölçümü aşağıdadır. Yön hücreleri kaynak dersin özetinden alınmıştır.

| Ders | Ölçülen hareket | Yön | Sınırlayıcı ölçüm |
|---|---|---|---|
| Bileşen İşlevleri | parametre ve sağlayıcı değeri | aşağı | aynı sağlayıcı: doğrudan çocuğa aşağı, torununa derin |
| Durum ve Hatırlama | sahibinden okuyucuya değer | aşağı | yerel durum hiçbir harekete girmez |
| Durum Kaldırma | olay sahibe, değer okuyucuya | yukarı | App'e kaldırınca aşağı 3, yukarı 2 |
| Yeniden Oluşturma | değişimin okuyucuya ulaşması | derin | parametre iki aşağı, ertelenmiş okuma tek derin |
| Yan Etkiler | etkiden servise istek | dış | iç yazı yukarı, yanıt da dış |
| Değişmezlik ve Kararlılık | sahibe giden değişiklik | yukarı | iki kanal aynı yön, gözlenme farklı |
| Temel Düzenler | kısıt, ölçü, konum | aşağı | ölçü yukarı, kısıt ve konum aşağı |
| Değiştiriciler | kısıt, zincirin sırası | aşağı | sıra değişince kısıt derin |
| Kısıt Tabanlı Düzen | kardeş kısıtı | yukarı | kardeş hareketi akrabasız, iki harekete bölünür |
| Tembel Listeler | kaydırma konumu | yukarı | görünmeyen satıra giden veri dış |
| Hazır Arayüz Bileşenleri | içerik rengi | derin | doğrudan çocuğa aşağı, sarılınca derin |
| İskele Yapısı | iç boşluk | aşağı | yakalanan durum iskeleyi atlar, derin |
| Gezinme ve Rotalar | gezinme öncesi argüman | dış | ağaca girince aşağı, geri sonuç dış |
| Tema ve Tipografi | kökten derine tema | derin | alt ağaç teması doğrudan çocuğa aşağı |
| Animasyonlar | kare başına ara değer | dış | hedef aşağı, bitiş yukarı |
| Erişilebilirlik | etiketlerin ataya birleşmesi | yukarı | birleşen etiket okuyucuya dış |
| Klasik Görünüm Sistemi | sarılı görünüme giden veri | dış | aynı etiket yerelde aşağı, sarılıda dış |

On yedi dersin dağılımı: 5 aşağı, 5 yukarı, 3 derin, 4 dış. Çekirdeğin 3/2/1/2 dağılımı ayrı bir sayımdır: çekirdek açılış dersindeki sekiz hareketi sayar, tablo ise on yedi dersin yönünü sayar; ikisi aynı şey değildir.

Sıradaki kurs (M31/K04) katmanlı mimariyi konu alır. Bu kursun ölçü ekseni orada sürmez; K04 kendi sorusunu kendisi sorar.
