Ders 03 / 19
Bileşen Kavramı
Her şeyin bileşen olduğu bir arayüz modelinde iç boşluk da bir bileşendir. Ders bileşenin değişmez bir tarif olduğunu ve bir kartın aynı isteğe rağmen konumuna göre neden başka bir sahiple karşılaştığını ölçer.
Yayın Güncelleme
Bu derste
- Bileşenin değişmez bir tarif olduğunu ve iç boşluk, hizalama ve boyutun da birer bileşen olduğunu açıklar
- Kısıtın aşağı indiğini, boyutun yukarı çıktığını bir ağaç üzerinde ölçer
- Aynı bileşenin sıkı bir kısıtta dayatılan, gevşek bir kısıtta seçilen olduğunu gösterir
İçindekiler
Önceki iki ders bileşeni bir ağaç düğümü olarak kullandı: pad, column ve leaf çağrıları iç içe geçip bir ekran kurdu. Bu ders o düğümün ne olduğunu söyler. Bu arayüz modelinde ekranda gördüğünüz hemen her şey bir bileşendir: metin, görsel, düğme; ama aynı zamanda iç boşluk, hizalama, sabit boyut ve sütun da. Bileşenler başka bileşenleri çocuk olarak alır ve ağaç bu iç içe geçişten oluşur.
Hemen Her Şey Bir Bileşendir
Birçok arayüz aracında iç boşluk bir bileşenin özelliğidir: kartın margin ya da padding ayarı vardır. Burada ise iç boşluk kendi başına bir bileşendir ve çocuğunu sarar. Hizalama ve sabit boyut da öyledir. Önce dersin çekirdeği gelir; bu, ilk dersin sizing.dart dosyasıdır ve hiç değişmemiştir.
// sizing.dart — bir bilesenin genisligini kimin belirledigini siniflandirir
const sizings = ['dayatilan', 'secilen', 'kirpilan', 'esnetilen'];
class Limit {
final int min;
final int? max; // null: sinirsiz
const Limit(this.min, this.max);
bool get tight => max == min;
Limit loosen() => Limit(0, max);
Limit deflate(int by) => Limit(_floor(min - by), max == null ? null : _floor(max! - by));
String bounds() => '$min..${max ?? 'sonsuz'}';
}
int _floor(int value) => value < 0 ? 0 : value;
class Part {
final String kind;
final String name;
final int? width; // null: alabildigi kadar genis
final List<Part> children;
final int factor;
final bool tightFlex;
const Part(this.kind, this.name, this.width, this.children,
{this.factor = 0, this.tightFlex = true});
}
Part leaf(String name, int width) => Part('leaf', name, width, const []);
Part fill(String name) => Part('leaf', name, null, const []);
Part pad(String name, int padding, Part child) => Part('pad', name, padding, [child]);
Part center(String name, Part child) => Part('center', name, null, [child]);
Part fixed(String name, int width, Part child) => Part('fixed', name, width, [child]);
Part column(String name, List<Part> children) => Part('column', name, null, children);
Part row(String name, List<Part> children) => Part('row', name, null, children);
Part flex(String name, int factor, Part child, {bool tight = true}) =>
Part('flex', name, null, [child], factor: factor, tightFlex: tight);
class Sizing {
final String sizing;
final String path;
final int width;
final Limit limit;
const Sizing(this.sizing, this.path, this.width, this.limit);
}
Sizing classify(String path, int? desire, Limit limit) {
if (limit.tight) return Sizing('dayatilan', path, limit.min, limit);
if (desire == null) {
return limit.max == null
? Sizing('kirpilan', path, limit.min, limit)
: Sizing('esnetilen', path, limit.max!, limit);
}
if (desire < limit.min) return Sizing('kirpilan', path, limit.min, limit);
if (limit.max != null && desire > limit.max!) {
return Sizing('kirpilan', path, limit.max!, limit);
}
return Sizing('secilen', path, desire, limit);
}
int layout(Part part, Limit limit, List<Sizing> log, [String path = '']) {
final here = '$path/${part.name}';
final below = <Sizing>[];
if (part.kind == 'fixed') {
final own = classify(here, part.width, limit);
layout(part.children.first, Limit(own.width, own.width), below, here);
log..add(own)..addAll(below);
return own.width;
}
int? desire;
switch (part.kind) {
case 'leaf':
desire = part.width;
case 'pad':
final space = 2 * part.width!;
desire = layout(part.children.first, limit.deflate(space), below, here) + space;
case 'center':
final inner = layout(part.children.first, limit.loosen(), below, here);
desire = limit.max == null ? inner : null;
case 'flex':
desire = layout(part.children.first, limit, below, here);
case 'column':
var widest = 0;
for (final child in part.children) {
final width = layout(child, limit.loosen(), below, here);
if (width > widest) widest = width;
}
desire = widest;
case 'row':
final logs = [for (final _ in part.children) <Sizing>[]];
final bounded = limit.max != null;
var used = 0;
var factors = 0;
for (var i = 0; i < part.children.length; i++) {
final child = part.children[i];
if (child.kind == 'flex' && bounded) {
factors += child.factor;
} else {
used += layout(child, const Limit(0, null), logs[i], here);
}
}
final left = bounded ? _floor(limit.max! - used) : 0;
var given = 0;
var seen = 0;
for (var i = 0; i < part.children.length; i++) {
final child = part.children[i];
if (child.kind != 'flex' || !bounded) continue;
seen += child.factor;
final share = (seen == factors ? left : left * seen ~/ factors) - given;
given += share;
final room = child.tightFlex ? Limit(share, share) : Limit(0, share);
used += layout(child, room, logs[i], here);
}
for (final entries in logs) {
below.addAll(entries);
}
desire = bounded ? null : used;
}
final own = classify(here, desire, limit);
log..add(own)..addAll(below);
return own.width;
}
List<Sizing> measure(Part root, int screen) {
final log = <Sizing>[];
layout(root, Limit(screen, screen), log);
return log;
}
void printSizes(List<Sizing> log) {
for (final entry in log) {
print('${entry.sizing}: ${entry.path} genislik=${entry.width} kisit=${entry.limit.bounds()}');
}
}
void countSizings(List<Sizing> log) {
for (final name in sizings) {
print('$name: ${log.where((entry) => entry.sizing == name).length}');
}
print('ebeveyn: ${log.where((entry) => entry.sizing != 'secilen').length}');
}
Aşağıdaki parça bir kartı iç boşluk bileşeninin içine koyar. Parça ölçülmez; model çerçevenin kodunu çalıştırmaz.
// Platform kodu: ic bosluk bileseni
Widget buildCard() {
return const Padding(
padding: EdgeInsets.all(16),
child: Card(child: Text('Ozet')),
);
}
Padding kartı sarar ve her yandan 16 birim boşluk bırakır; kart Padding’in child parametresidir. Modelde bu parçanın karşılığı pad('Screen', 16, leaf('Card', 200)) çağrısıdır.
DB1. Model iç boşluğu, hizalamayı ve sabit boyutu birer düğüm olarak kurar (pad, center, fixed). Çerçevenin bileşen listesinin tamamını sunmaz; model çerçevenin kendisi değildir.
DB2. Card modelde yalnız genişlik isteyen bir yapraktır. Kartın içeriği, gölgesi ve köşeleri yoktur; geriye ölçülebilen tek şey olan 200 birimlik istek kalır.
İç boşluk bir bileşen olduğundan üst üste konabilir. Aşağıdaki blok iki iç boşluğu iç içe koyar ve her birinin kartın kısıtını ne kadar daralttığını gösterir.
// main.dart
import 'sizing.dart';
void main() {
final tree = pad('Outer', 8, pad('Inner', 8, leaf('Card', 200)));
printSizes(measure(tree, 360));
}
dayatilan: /Outer genislik=360 kisit=360..360 dayatilan: /Outer/Inner genislik=344 kisit=344..344 dayatilan: /Outer/Inner/Card genislik=328 kisit=328..328
Her pad iki yandan kendi değerini çıkarır: 360 sıkı kısıttan 344, ondan 328 çıkar. Bileşenlerin hiçbiri kendi başına bir şey “hesaplamaz”; her biri kısıtı alır, çocuğa değiştirilmiş hâlini verir ve çocuğun cevabını işler.
Bileşen Bir Tariftir
Bir bileşen nesnesi ekranda duran bir şeyin kendisi değil, onun tarifidir. Nesne bir kez kurulur ve değişmez: alanları final, kurucusu const. Bir şey değişecekse yeni bir bileşen kurulur ve eskisi atılır. Bu seçim iki sonuç doğurur.
Birincisi, bileşenin kuruluşu ucuzdur. Tarif ekranın kendisini taşımadığı için bir ekranın tarifini yeniden kurmak, ekranı yeniden çizmek anlamına gelmez. İkincisi, aynı bileşen nesnesi birden çok yerde durabilir. Nesne hiçbir konum bilgisi taşımaz; konumu ağaçtaki yeri verir ve her yerde o yerin kısıtı ona iner. Aşağıdaki ölçümler bu ikinci sonucu gösterir.
Kısıt Aşağı, Boyut Yukarı
Bir ağaçta iki akış vardır ve ters yönlere gider. Kısıtlar aşağı iner: ebeveyn, çocuğa kabul edeceği aralığı verir. Boyutlar yukarı çıkar: çocuk o aralıkta bir genişlik seçer ve ebeveyne bildirir. Ebeveyn bu boyutu alır, kendi genişliğini ona göre belirler ve çocuğu yerleştirir.
// main.dart
import 'sizing.dart';
void main() {
final tree = pad('Screen', 16, center('Center', column('Body', [
leaf('A', 100),
leaf('B', 250),
])));
printSizes(measure(tree, 360));
}
dayatilan: /Screen genislik=360 kisit=360..360 dayatilan: /Screen/Center genislik=328 kisit=328..328 secilen: /Screen/Center/Body genislik=250 kisit=0..328 secilen: /Screen/Center/Body/A genislik=100 kisit=0..328 secilen: /Screen/Center/Body/B genislik=250 kisit=0..328
Screen’den Center’a 328 sıkı kısıt iner. Center bu kısıtı gevşetir ve Body’ye 0..328 verir; A ve B‘ye de aynı gevşek aralık ulaşır. Yukarı çıkan boyutlar şunlardır: A 100, B 250 bildirir; sütun bunların en genişini, 250’yi kendi genişliği yapar. Kısıt aşağı yönde azalarak, boyut yukarı yönde büyüyerek ilerlemiştir.
Jetpack Compose ile Arayüz kursunda “bu veri arayüz ağacında hangi yöne gidiyor?” diye sorulur ve yön, sorunun cevabıdır. Bu kursta yön sabittir: kısıt aşağı, boyut yukarı gider. Yön her ağaçta aynı olduğundan düğümleri birbirinden ayırmaz; boyutlanma ise ayırır. Boyutlanma, o iki akışın sonunda genişliği kimin belirlediğini sınıflar. Yukarıdaki çıktıda Body 250 genişliğindedir, çünkü çocuklarının en genişi onu seçmiştir; Center ise 328’dir, çünkü ebeveyni ona 328’i dayatmıştır.
Sınırlayıcı Ölçüm
Odak düğüm, iç boşluğun içindeki karttır: /Screen/Card. Kart 200 ister. Screen 360’lık sıkı kısıtı alır; iç boşluk bunu iki yandan 16 daraltır ve sıkı kalacak biçimde çocuğa iletir.
// main.dart
import 'sizing.dart';
void main() {
final tree = pad('Screen', 16, leaf('Card', 200));
printSizes(measure(tree, 360));
}
dayatilan: /Screen genislik=360 kisit=360..360 dayatilan: /Screen/Card genislik=328 kisit=328..328
Bu dersin boyutlanması: dayatılan. /Screen/Card 200 ister ve 328..328 alır. Kısıt sıkıdır, yani en küçük ve en büyük genişlik aynıdır; kartın isteği sonucu değiştirmez. Kart 200 değil, 328 genişliğindedir ve bu genişliği ona Screen ile iç boşluk vermiştir.
Aynı kartı bir sütunun içine koyalım. Sütun kısıtı gevşetir ve kart ilk kez kendi isteğini söyleyebilir.
// main.dart
import 'sizing.dart';
void main() {
final tree = pad('Screen', 16, column('Body', [leaf('Card', 200)]));
printSizes(measure(tree, 360));
}
dayatilan: /Screen genislik=360 kisit=360..360 dayatilan: /Screen/Body genislik=328 kisit=328..328 secilen: /Screen/Body/Card genislik=200 kisit=0..328
Kart aynı kart, isteği aynı 200’dür. Body sütunu 328 sıkı kısıtı alır ama çocuğuna 0..328 verir; bu aralıkta 200 geçerli bir seçimdir ve genişliği Card seçer. Aynı bileşen, aynı istek, iki farklı sonuç sahibi: konum sonucu belirlemiştir.
Özet
- Bu dersin boyutlanması: dayatılan.
/Screen/Card200 ister, iç boşluk sıkı kısıtı 328..328 olarak iletir ve genişliği kısıt belirler. - Bu arayüz modelinde iç boşluk, hizalama ve sabit boyut birer bileşendir; ağaç bileşenlerin iç içe geçmesiyle kurulur.
- Bileşen değişmez bir tariftir: alanları
final, kurucusuconst. Aynı nesne birden çok yerde durabilir ve her yerde o yerin kısıtını alır. - Kısıtlar aşağı iner, boyutlar yukarı çıkar. Boyutlanma yönü değil, bu iki akışın sonunda genişliğin sahibini sınıflar.
- Aynı kart, iç boşluğun içinde dayatılan, sütunun içinde seçilen olur: sonucu bileşenin isteği değil, konumu belirler.
Sonraki Adım
Bileşen değişmez bir tariftir; peki ekranda değişen bir şey nasıl gösterilir? Durumsuz ve Durumlu Bileşenler dersi, tarifin hiç değişmediği bileşeni ve kendi durumunu taşıyan bileşeni ayırır; durum değişince bir arama kutusunun genişliğini kimin verdiğini ölçer.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.