İçeriğe geç
academia.sh

Ders 17 / 17

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.

Yayın Güncelleme

Bu derste

  • 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
İçindekiler

Ö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.

// 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.

<!-- 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.

// 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.

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

// 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.

// 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.

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

// 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.

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

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

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ ↵ Esc gezin · aç · kapat