Ders 09 / 19
Gezinme ve Yönlendirme
Sayfa hangi rotayla açılırsa açılsın genişliğini gezinme kabı belirler. Ders üç rotayı ve üç derin bağlantıyı ölçer ve sayfanın isteğinin sonuca girmediğini gösterir.
Yayın Güncelleme
Bu derste
- Rota tanımını, rota yığınını ve derin bağlantıyı birbirinden ayırır
- Gezinme kabının sayfaya ekran genişliğinde sıkı bir kısıt verdiğini ölçer
- Rota değişse de sayfanın boyutlanmasının dayatılan kaldığını gösterir
- Derin bağlantıyla açılan sayfanın aynı kısıtı aldığını gösterir
İçindekiler
Önceki ders, paylaşılan bir sayaçtan beslenen rozetin genişliğini ölçtü ve durumun isteği değiştirdiğini, kısıtı değiştirmediğini gösterdi. Bu ders ağacın bir üst katmanına çıkar: uygulamanın hangi ekranı gösterdiğine karar veren gezinmeye. Bir sayfa rota yığınına konur, yığından çıkar ya da dışarıdan gelen bir bağlantıyla doğrudan açılır. Kursun sorusu yine aynıdır: bu bileşenin genişliğini kim belirliyor? Ders yanıtı, sayfayı saran gezinme kabında arar.
Rota, Yığın ve Derin Bağlantı
Uygulamanın her ekranı bir rota (route) ile adlandırılır. Rota bir ad ve o adın açacağı sayfayı üreten bir işlevdir. Açık rotalar bir yığında tutulur: yeni rota yığının tepesine konur, geri dönüş tepeyi çıkarır ve ekranda her zaman tepedeki rotanın sayfası görünür. Yönlendirme kitaplıkları rota tanımını ve yığın yönetimini bildirimsel hâle getirir; kitaplık adı bu derste yazılmaz, işleviyle anılır.
Derin bağlantı (deep link) uygulamayı dışarıdan, bir adresle açar. Adresin yolu bir rota adına çözülür ve çözülen rota yığına konur. Kullanıcı sayfaya ana ekrandan gelmemiştir, ama sayfanın ekranda durduğu yer değişmez.
Gezinmenin çerçeve tarafı, rota yığınını yöneten bir kap ve o kabın içine konan sayfalardır.
Ç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 bir rotanın yığına konmasını gösterir. Parça ölçülmez; model çerçevenin kodunu çalıştırmaz.
// Platform kodu: rota yigini
Future<void> openOrders(BuildContext context) {
return Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (context) => const OrdersPage(),
),
);
}
void closeOrders(BuildContext context) {
Navigator.of(context).pop();
}
push yığının tepesine bir rota koyar, pop tepeyi çıkarır. Sayfayı kuran builder işlevidir; gezinme kabı onu çağırırken sayfaya bir kısıt verir. Bu dersin sorusu o kısıtın nereden geldiği ve sayfanın kendi isteğinin ona ne yaptığıdır.
Rotadan Sayfaya
Modelin gezinme kabı, ekran genişliğinde sabit bir kaptır: fixed('Navigator', screen, page). Sayfa kabın tek çocuğudur ve çocuğa kabın genişliği sıkı kısıt olarak iner. Her rotanın sayfasının bir isteği vardır; istekler bir yardımcı dosyada durur. Yardımcı dosya ayrıca rota yığınını ve bir adresi rota adına çeviren işlevi taşır. Üçü de çıktıya hiçbir şey basmaz.
// routes.dart — rota yigini, sayfa istekleri ve baglanti cozumu
const pageRequests = {'Orders': 200, 'Profile': 480, 'Settings': 120};
const _linkRoutes = {'orders': 'Orders', 'profile': 'Profile', 'settings': 'Settings'};
class RouteStack {
final List<String> _names;
RouteStack(String first) : _names = [first];
String get top => _names.last;
int get depth => _names.length;
void push(String name) => _names.add(name);
void pop() {
if (_names.length > 1) {
_names.removeLast();
}
}
}
String routeFromLink(String link) {
final segments = Uri.parse(link).pathSegments;
return _linkRoutes[segments.first]!;
}
buildRoute yığının tepesindeki rotanın adını alır ve kabın içine o adın sayfasını koyar. İlk ölçüm Orders sayfasını açar.
// main.dart
import 'sizing.dart';
import 'routes.dart';
Part buildRoute(String name, {int screen = 360}) {
final page = leaf(name, pageRequests[name]!);
return fixed('Navigator', screen, page);
}
void main() {
final stack = RouteStack('Orders');
final log = measure(buildRoute(stack.top), 360);
printSizes(log);
countSizings(log);
}
dayatilan: /Navigator genislik=360 kisit=360..360 dayatilan: /Navigator/Orders genislik=360 kisit=360..360 dayatilan: 2 secilen: 0 kirpilan: 0 esnetilen: 0 ebeveyn: 2
Orders sayfası 200 birim ister, ama /Navigator/Orders kaydı dayatılandır: genişliği 360, kısıtı 360..360. Gezinme kabı sayfaya ekranın tamamını sıkı bir kısıt olarak verir; sayfanın isteği kayda girer girmez aralığın tek noktasına eşitlenir. Kap da ekranın verdiği kısıtla dayatılandır. Genişliği ne sayfa ne kap kendisi seçmiştir: ikisi de dışarıdan almıştır.
Rota Değiştikçe
Şimdi yığına üç rota koyalım ve her konuşta sayfanın kaydına bakalım. Sonra iki kez geri dönelim. Yığın derinliği düzyazıda anlam taşır; ölçümün girdisi yalnız tepedeki rotanın adıdır.
// main.dart
import 'sizing.dart';
import 'routes.dart';
Part buildRoute(String name, {int screen = 360}) {
final page = leaf(name, pageRequests[name]!);
return fixed('Navigator', screen, page);
}
void show(RouteStack stack) {
final log = measure(buildRoute(stack.top), 360);
final page = log.firstWhere((entry) => entry.path == '/Navigator/${stack.top}');
print('${stack.top} derinlik=${stack.depth} istek=${pageRequests[stack.top]} ${page.sizing} genislik=${page.width}');
}
void main() {
final stack = RouteStack('Orders');
show(stack);
stack.push('Profile');
show(stack);
stack.push('Settings');
show(stack);
stack.pop();
show(stack);
stack.pop();
show(stack);
}
Orders derinlik=1 istek=200 dayatilan genislik=360 Profile derinlik=2 istek=480 dayatilan genislik=360 Settings derinlik=3 istek=120 dayatilan genislik=360 Profile derinlik=2 istek=480 dayatilan genislik=360 Orders derinlik=1 istek=200 dayatilan genislik=360
Üç sayfanın istekleri 200, 480 ve 120’dir ve üçü de aynı genişlikte, 360’ta durur. Her satırda boyutlanma dayatilandır. Profile ekrandan geniş bir sayfa ister; yine de kap onu 360’a indirir. Settings yalnız 120 ister; yine de 360’ı kaplar. Geri dönüşte Profile ve Orders aynı genişlikle geri gelir. Sayfa yığında ne kadar kalırsa kalsın, yığından çıkıp yeniden açılsa da genişliğini sayfa vermez; her açılışta kap yeniden verir.
Ömür nerede bitiyor sorusu bir değerin yaşam süresinin sonunu arar: sayfa yığından çıkınca sayfanın değerleri biter. Bu ders onu sormaz. Rota sayfasının ömrü ne olursa olsun, genişliğini kap dayatır; ölçüm ömrün başladığı ya da bittiği anı değil, sayfanın bir kez ekrandayken genişliğinin sahibini sınıflar.
Sınırlayıcı Ölçüm
Dışarıdan gelen bir bağlantı, sayfaya farklı bir yol açar ama kısıtı değiştirmez. Aşağıdaki ölçüm üç bağlantıyı rota adına çevirir, her birini iki ekran genişliğinde ölçer. Birinci genişlik 360, ikincisi 280’dir. Sayfaların istekleri yine 200, 480 ve 120’dir.
// main.dart
import 'sizing.dart';
import 'routes.dart';
Part buildRoute(String name, {int screen = 360}) {
final page = leaf(name, pageRequests[name]!);
return fixed('Navigator', screen, page);
}
void main() {
final links = [
'app://shop/orders/42',
'app://shop/profile',
'app://shop/settings/theme',
];
for (final screen in [360, 280]) {
for (final link in links) {
final name = routeFromLink(link);
final log = measure(buildRoute(name, screen: screen), screen);
final page = log.firstWhere((entry) => entry.path == '/Navigator/$name');
print('ekran=$screen $name istek=${pageRequests[name]} ${page.sizing} genislik=${page.width} kisit=${page.limit.bounds()}');
}
}
}
ekran=360 Orders istek=200 dayatilan genislik=360 kisit=360..360 ekran=360 Profile istek=480 dayatilan genislik=360 kisit=360..360 ekran=360 Settings istek=120 dayatilan genislik=360 kisit=360..360 ekran=280 Orders istek=200 dayatilan genislik=280 kisit=280..280 ekran=280 Profile istek=480 dayatilan genislik=280 kisit=280..280 ekran=280 Settings istek=120 dayatilan genislik=280 kisit=280..280
Odak düğüm bağlantıyla açılan sayfadır: /Navigator/Orders, /Navigator/Profile ve /Navigator/Settings. Altı satırın altısında da boyutlanma dayatilan, genişlik ekran genişliğine, kısıt ekran..ekran aralığına eşittir. Sayfanın isteği kayda girmez; çıktıdaki istek değerini ders satırı ayrıca basar. 480 isteyen Profile 280’lik ekranda 280’e iner. Derin bağlantı sayfaya farklı bir kısıt vermez: kap hangi yoldan gelinirse gelinsin ekran genişliğinde sabit kaldığı için yığına nasıl girildiği genişliğe yansımaz.
UY1. Model yalnız genişliği ölçer. Sayfanın yüksekliği, geçiş animasyonu, kabın üst çubuğu ve geri dönüşte durumun korunması modelde yoktur. Model, bu boyutlanmanın kuralını kurar; model çerçevenin kendisi değildir. Gezinme kabını ekran genişliğinde sabit saymak dersin varsayımıdır; gerçek bir kap, yığındaki bir sayfaya kendi kısıtını farklı verebilir.
Bu sonuç sayfa yazarı için bir sözleşmedir: sayfa kendi içindeki bileşenler için bir genişlik bekleyebilir, ama kendi genişliğini kap verdiği için sayfa kendini ekrandan geniş ya da dar isteyerek ayarlayamaz. Sayfanın içinde isteğin anlam taşıdığı yer, sayfanın çocuklarıdır; onlar sayfanın verdiği kısıtla ölçülür.
Özet
- Bu dersin boyutlanması: dayatılan. Odak düğüm
/Navigator/Orders’tır ve ekranın verdiği360..360kısıtıyla sayfanın isteği 200 olsa da 360 alır. - Rota yığına konur ya da yığından çıkar; sayfanın genişliği her açılışta gezinme kabından gelir ve rotaya göre değişmez.
- Üç sayfa 200, 480 ve 120 istedi ve üçü de 360’ta dayatılan çıktı.
- Derin bağlantıyla açılan sayfa, yığına ana ekrandan konmuş sayfayla aynı kısıtı aldı; ekran 280’e inince genişlik 280’e indi.
- Kural sayfanın ömrünü değil, genişliğinin sahibini görür: sayfa kaç kez açılıp kapanırsa kapansın kayıt aynıdır.
Sonraki Adım
Bu derste sayfanın genişliğini kap dayattı ve sayfanın isteği sonuca girmedi. Sıradaki ders, Ağ ve Dizileştirme, isteğin sunucudan geldiği durumu ölçer: dışarıdan gelen bir genişlik aralığın dışına çıkınca kuralın o isteğe ne yaptığına bakar.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.