Ders 12 / 19
Varlıklar ve Yazı Tipleri
Yazı tipi ölçeği bir düğmenin isteğini büyütür ve büyüyen istek aralığı aşınca düğme kırpılır. Ders ölçeği 1, 2 ve 3 verir ve aralığı genişletmenin sonucu nasıl değiştirdiğini ölçer.
Yayın Güncelleme
Bu derste
- Varlık bildirimini, görsel çözünürlük çeşitlerini ve yazı tipi ölçeğini birbirinden ayırır
- Yazı tipi ölçeğinin bir düğmenin isteğini büyüttüğünü ölçer
- Ölçek 1'de düğmenin seçilen, ölçek 2'de kırpılan olduğunu gösterir
- Aralık genişleyince aynı isteğin yeniden seçilen olabildiğini gösterir
İçindekiler
Önceki ders, kayıtlı bir tercihin bir kartın isteğini kaldırdığını ve kartı esnetilen yaptığını gösterdi. Bu ders tersini yapan bir girdiyi ölçer: isteği büyüten bir girdiyi. Kullanıcı sistem ayarlarından yazı boyutunu büyüttüğünde uygulamanın her yazısı ve yazıyı taşıyan her düğme büyür. Kursun sorusu yine aynıdır: bu bileşenin genişliğini kim belirliyor? Ders yanıtı, ölçekle büyüyen bir düğmenin üzerinden arar.
Varlıklar, Çözünürlük Çeşitleri ve Yazı Tipleri
Bu derste varlık (asset) uygulamayla birlikte paketlenen bir dosyadır: bir görsel, bir yazı tipi dosyası ya da bir ses. Veri modelindeki varlık (entity) ile ilgisi yoktur. Uygulama yapılandırmasında varlıkların hangi dosyalar olduğu bildirilir; bildirilmeyen dosya pakete girmez.
Görseller ekranın yoğunluğuna göre çözünürlük çeşitleriyle gelir: aynı görselin 1.0x, 2.0x gibi büyütülmüş dosyaları yan yana durur ve çerçeve ekrana uygun olanı seçer. Yazı tipleri de varlıktır; bir aile adı altında bildirilir.
Çekirdek
Her ders aynı çekirdeği kullanır. measure ağacı ekran genişliğinde sıkı bir kısıtla dolaşır, her düğüm için bir kayıt üretir ve kayıttaki boyutlanmayı bir kural verir: kısıt sıkıysa dayatılan, isteğin aralık içinde kaldığı durumda seçilen, istek aralığın dışındaysa ya da yoksa ve üst sınır yoksa kırpılan, istek yok ve üst sınır sonluysa esnetilen. Çıktıda dört ad ASCII yazılır: dayatilan, secilen, kirpilan, esnetilen. ebeveyn: satırı dayatılan, kırpılan ve esnetilenin toplamıdır; genişliği bileşenin kendi isteği olmayan kayıtları sayar.
// 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}');
}
Çerçeve Tarafı
Aşağıdaki parça yapılandırmayı gösterir. Paket adı kurgusaldır ve parça ölçülmez.
# Platform kodu: varlik bildirimi name: demo_shop flutter: assets: - assets/images/logo.png fonts: - family: Body fonts: - asset: assets/fonts/body-regular.ttf
assets görsel dosyalarını, fonts yazı tipi ailesini ve dosyasını bildirir. Çözünürlük çeşitleri assets/images/2.0x/logo.png gibi bir alt dizinde yan yana durur ve ayrıca listelenmez. Bildirimden sonra kod, yazı tipinin ölçeğini kullanıcının ayarından okur.
// Platform kodu: yazi tipi olcegi
double labelSize(BuildContext context) {
final scaler = MediaQuery.textScalerOf(context);
return scaler.scale(14);
}
textScalerOf kullanıcının ayarladığı ölçeği verir ve scale temel boyutu o ölçekle çarpar. Yazı büyüyünce yazıyı taşıyan düğmenin isteği de büyür. Bu dersin sorusu o büyümenin aralıkla buluştuğu yerdir.
Ölçekten İsteğe
Modelin düğmesi bir etiketin genişliğini taşır ve etraftaki çerçeve payını ekler. Etiketin genişliği ölçekle çarpılan bir tamsayıdır: temel genişlik 160 birimse ölçek 1’de etiket 160, ölçek 2’de 320 birim genişliğindedir. Çerçeve payı sabit 24 birimdir. İsteği hesaplayan işlev ve temel genişlikler bir yardımcı dosyada durur; yardımcı dosya çıktıya hiçbir şey basmaz.
// fonts.dart — etiket genisligi ve olcege bagli istek
const labelBase = {'Kaydet': 160, 'Tamam': 80};
int frameWidth(int base, int scale) => base * scale + 24;
buildForm ölçeği alır ve düğmeyi bu istekle kurar. Ağaç ekranın içinde bir sütun, sütunun içinde başlık ile düğmedir. İlk ölçüm ölçek 1’dedir.
// main.dart
import 'sizing.dart';
import 'fonts.dart';
Part buildForm(String label, int scale) {
return pad('Screen', 16, column('Body', [
leaf('Title', 120),
leaf('Button', frameWidth(labelBase[label]!, scale)),
]));
}
void main() {
final log = measure(buildForm('Kaydet', 1), 360);
printSizes(log);
countSizings(log);
}
dayatilan: /Screen genislik=360 kisit=360..360 dayatilan: /Screen/Body genislik=328 kisit=328..328 secilen: /Screen/Body/Title genislik=120 kisit=0..328 secilen: /Screen/Body/Button genislik=184 kisit=0..328 dayatilan: 2 secilen: 2 kirpilan: 0 esnetilen: 0 ebeveyn: 2
Ölçek 1’de düğme 184 ister; aralık 0..328 olduğu için istek aralığın içinde kalır ve /Screen/Body/Button seçilendir. Başlık da seçilendir. Sayım iki dayatılan, iki seçilen gösterir; kırpılan ve esnetilen yoktur, ebeveyn ikidir.
Ölçek Büyüdükçe
Şimdi ölçeği 1’den 3’e artıralım ve iki etiketi karşılaştıralım. Kaydet uzun bir etikettir; Tamam kısadır.
// main.dart
import 'sizing.dart';
import 'fonts.dart';
Part buildForm(String label, int scale) {
return pad('Screen', 16, column('Body', [
leaf('Title', 120),
leaf('Button', frameWidth(labelBase[label]!, scale)),
]));
}
void main() {
for (final label in ['Kaydet', 'Tamam']) {
for (final scale in [1, 2, 3]) {
final log = measure(buildForm(label, scale), 360);
final button = log.firstWhere((entry) => entry.path == '/Screen/Body/Button');
print('$label olcek=$scale istek=${frameWidth(labelBase[label]!, scale)} ${button.sizing} genislik=${button.width}');
}
}
}
Kaydet olcek=1 istek=184 secilen genislik=184 Kaydet olcek=2 istek=344 kirpilan genislik=328 Kaydet olcek=3 istek=504 kirpilan genislik=328 Tamam olcek=1 istek=104 secilen genislik=104 Tamam olcek=2 istek=184 secilen genislik=184 Tamam olcek=3 istek=264 secilen genislik=264
Kaydet ölçek 1’de 184 ister ve seçilen kalır. Ölçek 2’de istek 344’e çıkar, 328’i aşar ve kayıt kirpilan olur; genişlik 328’dir. Ölçek 3’te istek 504’tür ve kayıt yine kırpılan, genişlik yine 328’dir. Tamam üç ölçekte de 104, 184 ve 264 ister ve üçünde de seçilen kalır. Ölçek tek başına boyutlanmayı belirlemez: ölçek isteği büyütür, boyutlanmayı isteğin aralıkla ilişkisi belirler.
Bu işlem yinelenirse ya da sırası değişirse sonuç değişir mi sorusu bir işlemin duyarlılığını arar. Bu ders onu sormaz. Yazı tipi ölçeği bir girdidir ve isteği büyütür; boyutlanma sonucun yinelemeye dayanıklılığını değil, büyüyen isteği aralığa kimin sığdırdığını sınıflar.
Sınırlayıcı Ölçüm
Ölçek 2’de Kaydet kırpılandı. Aralık genişlerse aynı istek sığabilir mi? Aynı ağacı iki ekran genişliğinde ölçelim: 360’ta aralık 0..328, 480’de 0..448‘dir. İstek 344’te sabit kalır.
// main.dart
import 'sizing.dart';
import 'fonts.dart';
Part buildForm(String label, int scale) {
return pad('Screen', 16, column('Body', [
leaf('Title', 120),
leaf('Button', frameWidth(labelBase[label]!, scale)),
]));
}
void main() {
for (final screen in [360, 480]) {
final log = measure(buildForm('Kaydet', 2), screen);
final button = log.firstWhere((entry) => entry.path == '/Screen/Body/Button');
print('ekran=$screen olcek=2 istek=344 ${button.sizing} genislik=${button.width} kisit=${button.limit.bounds()}');
}
}
ekran=360 olcek=2 istek=344 kirpilan genislik=328 kisit=0..328 ekran=480 olcek=2 istek=344 secilen genislik=344 kisit=0..448
Odak düğüm, ölçek 2’deki /Screen/Body/Button‘dır. 360’lık ekranda istek 344, aralık 0..328 olduğundan kayıt kirpilan, genişlik 328’dir. 480’lik ekranda aralık 0..448 olur, istek içinde kalır ve kayıt secilen, genişlik 344’tür. Ekran ya da ölçek değiştiğinde sonuç değişebilir; sonucu değiştiren yazı tipi ölçeğinin kendisi değil, büyüyen isteğin aralığa sığıp sığmadığıdır.
UY1. Model yalnız genişliği ölçer. Etiketin satıra kırılması, taşan metnin görüntüsü, düğmenin yüksekliği ve yazı tipinin gerçek harf ölçüleri modelde yoktur. Model, bu boyutlanmanın kuralını kurar; model çerçevenin kendisi değildir. İsteğin ölçekle doğrusal büyümesi ve 24 birimlik sabit çerçeve payı dersin varsayımlarıdır; gerçek bir etiketin genişliği yazı tipinin ölçülerinden gelir.
Sonuç şudur: büyük yazı ayarı olan bir kullanıcıda düğme kırpılıyorsa çözüm ölçeği bastırmak değildir. Seçenekler iki yöndedir: etiketi kısaltarak isteği küçültmek ya da düğmeyi aralığı geniş bir yere taşıyarak aralığı genişletmek.
Özet
- Bu dersin boyutlanması: kırpılan. Odak düğüm ölçek 2’deki
/Screen/Body/Button‘dır: 344 istek,0..328aralığını aştığı için 328’e çekilir. - Varlık bildirimi paketlenecek dosyaları söyler; yazı tipi ölçeği ise kullanıcının ayarından okunur ve etiketin genişliğini büyütür.
Kaydetölçek 1’de 184 ile seçilen, ölçek 2 ve 3’te 344 ve 504 ile kırpılan çıktı;Tamamüç ölçekte de seçilen kaldı.- Ekran 480’e çıkınca aralık
0..448oldu ve 344 isteği yeniden seçilen çıktı; genişliği ölçek değil, istekle aralığın ilişkisi belirledi. - Kural ölçeğin kendisini değil, büyüyen isteğin sığıp sığmadığını görür.
Sonraki Adım
Bu derste isteği ölçek büyüttü ve sonuç iki ölçüm arasında değişti. Sıradaki ders, Animasyonlar, isteğin zamanla küçük adımlarla büyüdüğü durumu ölçer: her karede ağaç yeniden ölçülür ve her karede genişliğin sahibine bakılır.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.