Ders 07 / 19
Platform Bileşen Aileleri
Çerçeve iki platformun tasarım dili için ayrı bileşen kümeleri sunar. Ders ailelerin farkını, platforma göre seçimi ve aynı eylem düğmesinin iki ailede farklı istek bildirmesine rağmen sabit bir yuvada aynı genişliği almasını ölçer.
Yayın Güncelleme
Bu derste
- İki platformun tasarım diline ait bileşen kümelerini ve uyarlanır bileşeni açıklar
- Platforma göre aile seçiminin kodda nasıl yapıldığını gösterir
- Ailelerin farklı istek bildirdiği bir düğmenin sabit yuvada aynı genişliği aldığını ölçer
İçindekiler
Önceki derslerde bileşenin değişmez bir tarif olduğunu, kısıtın aşağı indiğini ve düzen bileşenlerinin payı paylaştırdığını gördük. Bu son derste bileşenlerin hangi kümeden geldiğine bakıyoruz. Çerçeve, iki platformun tasarım diline ait ayrı bileşen kümeleri sunar. Bir eylem düğmesinin bir kümedeki biçimi ile diğerindeki biçimi farklı görünür, farklı içeriğe ve farklı isteğe sahip olabilir.
İki Tasarım Dili, İki Küme
Birinci küme Material tasarım dilini uygular ve Android’de varsayılan görünümdür; ikinci küme iOS’un tasarım dilini uygular. İki kümede de düğme, anahtar, iletişim kutusu ve gezinme çubuğu gibi bileşenlerin karşılığı vardır, ama her biri kendi platformunun alışkanlıklarını taşır: boşluklar, köşe yuvarlaklığı ve dokunma davranışı küme başına farklıdır.
Bu fark boyuta da yansır. Aynı iş için kurulan iki düğme, genişlik olarak farklı bir istek bildirebilir. 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 aile, düğmenin bildirdiği istek olarak temsil edilir: bir ailede 88, diğerinde 64. Düğmenin biçimi, rengi ve dokunma davranışı modellenmez; yalnız genişlik isteği ölçülür.
Platforma Göre Seçim
Bir arayüzü iki platformda da doğal göstermek için uygulama hangi kümeyi kullanacağını çalışma anında seçer. Çerçeve bu seçimi iki yolla destekler. İlki, platformu sorup uygun bileşeni döndürmektir. İkincisi, iki kümeyi birlikte sarmalayan uyarlanır bileşenlerdir: bu bileşenler platforma göre uygun kümenin karşılığını kendileri üretir.
// Platform kodu: platforma gore secim
Widget buildAction(BuildContext context) {
final platform = Theme.of(context).platform;
return switch (platform) {
TargetPlatform.iOS => CupertinoButton(onPressed: () {}, child: const Text('Kaydet')),
_ => ElevatedButton(onPressed: () {}, child: const Text('Kaydet')),
};
}
Seçim bir koşuldur, bir sınıf hiyerarşisi değildir: iOS ise ikinci kümeden, değilse birinci kümeden bileşen döner. Modelde karşılığı, platform adını alıp aile adını döndüren sıradan bir işlevdir.
// main.dart
String familyFor(String platform) => platform == 'ios' ? 'ios' : 'android';
void main() {
for (final platform in ['android', 'ios', 'linux']) {
print('$platform: ${familyFor(platform)}');
}
}
android: android ios: ios linux: android
Tanınmayan bir platform için varsayılan aile android’dir; işlev her platform adına bir aile döndürür. Seçim kodu ölçüm yapmaz, yalnız kimin bileşeninin kullanılacağını söyler.
Ortak kodun ve platform kodunun nasıl paylaşıldığı Çapraz Platform Yaklaşımları kursunda sorulur: bu özelliğin kodu platformlar arasında nasıl paylaşılıyor. Bu derste soru başkadır. Aile, aynı kısıtta başka bir istek bildirebilir; boyutlanma ise isteğin sonuca yansıyıp yansımadığını, yani genişliği kimin verdiğini sınıflar. Ailenin farkı, ancak bir şey bu farkı sınırlamıyorsa genişlikte görünür.
Yuva Genişliği Farkı Siler
Eylem düğmesini önce sabit genişlikli bir yuvanın içine koyalım. Yuva, fixed ile kurulan 120 genişliğinde bir hücredir ve düğme onun çocuğudur.
// main.dart
import 'sizing.dart';
Part buildBar(String family) {
final requested = family == 'android' ? 88 : 64;
return pad('Screen', 16, column('Bar', [
fixed('Action', 120, leaf('Button', requested)),
]));
}
void main() {
for (final family in ['android', 'ios']) {
print('aile: $family');
printSizes(measure(buildBar(family), 360));
}
}
aile: android dayatilan: /Screen genislik=360 kisit=360..360 dayatilan: /Screen/Bar genislik=328 kisit=328..328 secilen: /Screen/Bar/Action genislik=120 kisit=0..328 dayatilan: /Screen/Bar/Action/Button genislik=120 kisit=120..120 aile: ios dayatilan: /Screen genislik=360 kisit=360..360 dayatilan: /Screen/Bar genislik=328 kisit=328..328 secilen: /Screen/Bar/Action genislik=120 kisit=0..328 dayatilan: /Screen/Bar/Action/Button genislik=120 kisit=120..120
İki ailenin düğmesi farklı isteklerle gelir, 88 ve 64. Yuva ise çocuğuna kendi genişliğini sıkı olarak verir: Button her iki ailede de 120..120 alır ve genişliği dayatılır. İstekteki fark genişliğe yansımaz.
Yuvayı kaldırırsak durum değişir.
// main.dart
import 'sizing.dart';
Part buildBar(String family) {
final requested = family == 'android' ? 88 : 64;
return pad('Screen', 16, column('Bar', [leaf('Button', requested)]));
}
void main() {
for (final family in ['android', 'ios']) {
print('aile: $family');
printSizes(measure(buildBar(family), 360));
}
}
aile: android dayatilan: /Screen genislik=360 kisit=360..360 dayatilan: /Screen/Bar genislik=328 kisit=328..328 secilen: /Screen/Bar/Button genislik=88 kisit=0..328 aile: ios dayatilan: /Screen genislik=360 kisit=360..360 dayatilan: /Screen/Bar genislik=328 kisit=328..328 secilen: /Screen/Bar/Button genislik=64 kisit=0..328
Yuva yokken sütun kısıtı gevşetir, düğme kendi isteğini bildirir ve iki aile iki ayrı seçilen genişlik verir: 88 ve 64. Aile farkı artık genişlikte görünür. Yuva, bu farkı silen sıkı kısıttır.
DB2. Yuvanın 120 olması bir tasarım kararıdır; model bu sayıyı açıklamaz. Ölçülen tek şey, sıkı bir kısıtın isteği genişlikten silmesidir.
Sınırlayıcı Ölçüm
Odak düğüm yuvanın içindeki düğmedir: /Screen/Bar/Action/Button. Yuvayla kurulmuş çubuğu iki ekran genişliğinde ve iki ailede ölçelim.
// main.dart
import 'sizing.dart';
Part buildBar(String family) {
final requested = family == 'android' ? 88 : 64;
return pad('Screen', 16, column('Bar', [
fixed('Action', 120, leaf('Button', requested)),
]));
}
void main() {
for (final width in [480, 320]) {
for (final family in ['android', 'ios']) {
print('ekran: $width, aile: $family');
final log = measure(buildBar(family), width);
print('${log[3].sizing}: ${log[3].path} genislik=${log[3].width}');
}
}
}
ekran: 480, aile: android dayatilan: /Screen/Bar/Action/Button genislik=120 ekran: 480, aile: ios dayatilan: /Screen/Bar/Action/Button genislik=120 ekran: 320, aile: android dayatilan: /Screen/Bar/Action/Button genislik=120 ekran: 320, aile: ios dayatilan: /Screen/Bar/Action/Button genislik=120
Bu dersin boyutlanması: dayatılan. /Screen/Bar/Action/Button iki ailede ve iki ekran genişliğinde 120’ye dayatılır. Düğmenin 88 ya da 64 istemesi sonucu değiştirmez; genişliği yuva verir. Ailenin seçimi yalnız yuva olmadığında genişliği belirler.
Özet
- Bu dersin boyutlanması: dayatılan.
/Screen/Bar/Action/Buttoniki ailede ve her ekran genişliğinde 120’ye dayatılır; isteğin farkı genişliğe yansımaz. - Çerçeve iki platformun tasarım diline ait ayrı bileşen kümeleri sunar; uyarlanır bileşenler uygun kümeyi platforma göre kendileri seçer.
- Platforma göre seçim bir koşuldur: platform sorulur, uygun kümeden bileşen döner.
- Aileler aynı kısıtta farklı istek bildirebilir: yuva olmadan 88 ve 64, iki ayrı seçilen genişlik.
- Sabit bir yuva sıkı kısıt verir ve ailenin istek farkını siler.
Sonraki Adım
Bu konu Dart dilinden başlayıp bileşenlerin ağaç içindeki yerine ve kümelerine kadar geldi. Sıradaki konu, bileşenlerin durumunun uygulama genelinde nasıl yönetildiğini ele alır. Durum Yönetimi Yaklaşımları dersi, sağlayıcı, olay tabanlı ve tepkisel modelleri karşılaştırır.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.