İçeriğe geç
academia.sh

Ders 06 / 19

Düzen Bileşenleri

Sütun, satır ve esnek çocuk, kısıtı aşağı geçiren düzen bileşenleridir. Ders sıkı ve gevşek esnek çocuğu ayırır ve esnek bir çocuk olmanın içindeki bileşeni esnetilen yapmadığını ölçerek gösterir.

Yayın Güncelleme

Bu derste

  • Sütunun ve satırın çocuklarına hangi kısıtı verdiğini açıklar
  • Sıkı ve gevşek esnek çocuğun payı nasıl kullandığını ayırt eder
  • Esnek çocuk olmanın içindeki bileşenin esnetilen olması anlamına gelmediğini ölçer
İçindekiler

Önceki derste çizim nesnesi ağacının kısıtı aşağı geçirdiğini gördük. Bu geçişi ağaçta en çok düzen bileşenleri yapar: sütun, satır ve esnek çocuk. Bu bileşenlerin kendileri ekranda bir şey göstermez; çocuklarına hangi aralığın verileceğine karar verirler.

Sütun ve Satır

Sütun, çocuklarını alt alta dizer. Kendisine gelen kısıtı gevşetir ve her çocuğa 0..en büyük aralığını verir. Kendi genişliği, çocuklarının en genişidir.

Satır, çocuklarını yan yana dizer. Burada fark vardır: satırın genişliği çocuklar arasında paylaşılır. Esnek olmayan çocuğa 0..sonsuz verilir, yani çocuk istediği kadar geniş olabilir. Esnek çocuklar ise geriye kalan payı paylaşır. Dersin çekirdeği yine ilk dersin sizing.dart dosyasıdır.

// 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}');
}

DB1. Modelde satır, esnek olmayan çocukları önce yerleştirir, kalan payı sonra esnek çocuklara dağıtır. Çerçevenin ana eksen hizalama ve ikinci eksen seçenekleri modellenmez; model yalnız genişliği ölçer.

Esnek Çocuk

Esnek çocuk, satırın kalan payından bir dilim alan çocuktur. Dilimin büyüklüğünü esneklik çarpanı belirler: çarpanı 2 olan çocuk, çarpanı 1 olandan iki kat pay alır. Çerçevede esnek çocuk iki biçimdedir ve fark, payı nasıl kullandıklarındadır.

// Platform kodu: esnek cocuklar
Row(
  children: [
    const Icon(Icons.person),
    const Expanded(child: Text('Ad Soyad')),
    const Flexible(child: Text('Not')),
  ],
)

Expanded çocuğa payın tamamını sıkı olarak verir: çocuk payı mutlaka doldurur. Flexible ise payı gevşek verir: çocuk payın içinde istediği genişliği seçer ve daha azını alabilir. Modeldeki karşılıkları flex(..., tight: true) ve flex(..., tight: false) çağrılarıdır.

DB2. Modelde esnek çocuk, çarpanı ve sıkı ya da gevşek oluşu dışında hiçbir şey bilmez. Çerçevenin esnek çocuğa uyguladığı başka bir kuralı, örneğin taşma davranışını, model içermez.

Sıkı Esnek Çocuk

Bir satırda soldaki simge sabit 48 genişliğinde, yanındaki ad ise sıkı esnek çocuktur.

// main.dart
import 'sizing.dart';

void main() {
  final tree = pad('Screen', 16, row('Line', [
    fixed('Icon', 48, leaf('Glyph', 48)),
    flex('Name', 1, fill('Text')),
  ]));
  printSizes(measure(tree, 360));
}
dayatilan: /Screen genislik=360 kisit=360..360
dayatilan: /Screen/Line genislik=328 kisit=328..328
secilen: /Screen/Line/Icon genislik=48 kisit=0..sonsuz
dayatilan: /Screen/Line/Icon/Glyph genislik=48 kisit=48..48
dayatilan: /Screen/Line/Name genislik=280 kisit=280..280
dayatilan: /Screen/Line/Name/Text genislik=280 kisit=280..280

Satır önce esnek olmayan çocuğu yerleştirir: Icon 48 ister ve 48’i seçer. Geriye 328 eksi 48, yani 280 kalır. Tek esnek çocuk olan Name payın tamamını sıkı olarak alır. Sıkı bir kısıt, çocuğun isteği ne olursa olsun genişliği belirler; bu yüzden hem Name hem de içindeki Text dayatılandır.

Gevşek Esnek Çocuk

Şimdi aynı satırda ad yerine gevşek bir esnek çocuk koyalım.

// main.dart
import 'sizing.dart';

void main() {
  final tree = pad('Screen', 16, row('Line', [
    fixed('Icon', 48, leaf('Glyph', 48)),
    flex('Note', 1, fill('Text'), tight: false),
  ]));
  printSizes(measure(tree, 360));
}
dayatilan: /Screen genislik=360 kisit=360..360
dayatilan: /Screen/Line genislik=328 kisit=328..328
secilen: /Screen/Line/Icon genislik=48 kisit=0..sonsuz
dayatilan: /Screen/Line/Icon/Glyph genislik=48 kisit=48..48
secilen: /Screen/Line/Note genislik=280 kisit=0..280
esnetilen: /Screen/Line/Note/Text genislik=280 kisit=0..280

Pay yine 280’dir ama bu kez Note‘a 0..280 olarak verilir. Text istek bildirmediği için üst sınırı doldurur ve esnetilen olur. Note ise çocuğunun bildirdiği 280’i aralığın içinde seçilmiş bir değer olarak alır: seçilen. Esnek çocuk olan Note’un kendisi esnetilen değildir; esnetilen, onun içindeki Text’tir.

Gevşek çocuğun içi istek bildiren bir yaprak olursa durum değişir.

// main.dart
import 'sizing.dart';

void main() {
  final tree = pad('Screen', 16, row('Line', [
    fixed('Icon', 48, leaf('Glyph', 48)),
    flex('Note', 1, leaf('Text', 120), tight: false),
  ]));
  printSizes(measure(tree, 360));
}
dayatilan: /Screen genislik=360 kisit=360..360
dayatilan: /Screen/Line genislik=328 kisit=328..328
secilen: /Screen/Line/Icon genislik=48 kisit=0..sonsuz
dayatilan: /Screen/Line/Icon/Glyph genislik=48 kisit=48..48
secilen: /Screen/Line/Note genislik=120 kisit=0..280
secilen: /Screen/Line/Note/Text genislik=120 kisit=0..280

Text 120 ister ve 0..280 aralığında 120’yi seçer; Note da çocuğu kadar, yani 120 kadar yer alır. Pay 280 olmasına rağmen ikisi de 120’de kalır. Gevşek esnek çocuk payı kullanma hakkı verir, kullanma zorunluluğu getirmez.

Bu üç ölçüm bir ayrımı gösterir: esnek çocuk olmak ile esnetilen olmak aynı şey değildir. Esnek çocuk, satırın bir yerleşim rolüdür. Esnetilen ise bir düğümün genişliğini nasıl aldığını anlatan bir sınıftır: istek yok, üst sınır sonlu ve düğüm onu dolduruyor. Sıkı esnek çocuk dayatılan, gevşek esnek çocuk seçilen çıktı; esnetilen olan, gevşek çocuğun içindeki isteksiz yapraktı.

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

Odak düğüm, gevşek esnek çocuğun içindeki metindir: /Screen/Line/Note/Text. Aynı satırı iki ekran genişliğinde ölçelim.

// main.dart
import 'sizing.dart';

void main() {
  for (final width in [480, 320]) {
    print('ekran: $width');
    final tree = pad('Screen', 16, row('Line', [
      fixed('Icon', 48, leaf('Glyph', 48)),
      flex('Note', 1, fill('Text'), tight: false),
    ]));
    printSizes(measure(tree, width));
  }
}
ekran: 480
dayatilan: /Screen genislik=480 kisit=480..480
dayatilan: /Screen/Line genislik=448 kisit=448..448
secilen: /Screen/Line/Icon genislik=48 kisit=0..sonsuz
dayatilan: /Screen/Line/Icon/Glyph genislik=48 kisit=48..48
secilen: /Screen/Line/Note genislik=400 kisit=0..400
esnetilen: /Screen/Line/Note/Text genislik=400 kisit=0..400
ekran: 320
dayatilan: /Screen genislik=320 kisit=320..320
dayatilan: /Screen/Line genislik=288 kisit=288..288
secilen: /Screen/Line/Icon genislik=48 kisit=0..sonsuz
dayatilan: /Screen/Line/Icon/Glyph genislik=48 kisit=48..48
secilen: /Screen/Line/Note genislik=240 kisit=0..240
esnetilen: /Screen/Line/Note/Text genislik=240 kisit=0..240

Bu dersin boyutlanması: esnetilen. /Screen/Line/Note/Text istek bildirmez, üst sınırı sonludur ve onu doldurur: 480 ekranda 400, 320 ekranda 240. Genişliği ne Text’in isteği ne de kendi seçimi belirler; ebeveyninin payı belirler. Note ise ikisinde de seçilendir, çünkü çocuğunun bildirdiği genişlik aralığın içinde kalır.

Özet

  • Bu dersin boyutlanması: esnetilen. Gevşek esnek çocuğun içindeki metin istek bildirmez ve payı doldurur: 360 ekranda 280, 480 ekranda 400, 320 ekranda 240.
  • Sütun kısıtı gevşetir ve çocuklarının en genişini kendi genişliği yapar; satır esnek olmayan çocuklara 0..sonsuz verir, kalan payı esnek çocuklara dağıtır.
  • Sıkı esnek çocuk payın tamamını sıkı kısıt olarak alır ve dayatılan olur; gevşek esnek çocuk payı üst sınır olarak alır.
  • Gevşek esnek çocuğun içi sabit bir yaprak olursa pay boşta kalır; çocuk seçilen olur.
  • Esnek çocuk olmak bir yerleşim rolüdür; esnetilen olmak ise genişliğin nasıl alındığının sınıfıdır. İkisi aynı şey değildir.

Sonraki Adım

Bu konunun son dersi, aynı arayüzü farklı platform bileşen aileleriyle kurmayı ele alır. Platform Bileşen Aileleri dersi, iki tasarım dilinin bileşen kümelerini ve bir eylem düğmesinin genişliğini sabit bir yuvanın nasıl koruduğunu ölçer.

İ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