İlk Flutter uygulamanız

1. Giriş

Flutter, Google'ın tek bir kod tabanından mobil, web ve masaüstü için uygulamalar oluşturmaya yönelik kullanıcı arayüzü araç setidir. Bu codelab'de aşağıdaki Flutter uygulamasını oluşturacaksınız:

Uygulama, "newstay", "lightstream", "mainbrake" veya "graypine" gibi kulağa hoş gelen adlar oluşturur. Kullanıcı, sonraki adı isteyebilir, mevcut adı favorilere ekleyebilir ve favori adlar listesini ayrı bir sayfada inceleyebilir. Uygulama, farklı ekran boyutlarına duyarlıdır.

Öğrenecekleriniz

  • Flutter'ın işleyiş şekliyle ilgili temel bilgiler
  • Flutter'da düzen oluşturma
  • Kullanıcı etkileşimlerini (ör. düğmeye basma) uygulama davranışına bağlama
  • Flutter kodunuzu düzenli tutma
  • Uygulamanızı duyarlı hale getirme (farklı ekranlar için)
  • Uygulamanızın tutarlı bir görünüm ve tarzda olmasını sağlama

Doğrudan ilginç kısımlara geçebilmeniz için temel bir iskeletle başlarsınız.

e9c6b402cd8003fd.png

Filip, codelab'in tamamını adım adım açıklıyor.

Laboratuvarı başlatmak için İleri'yi tıklayın.

2. Flutter ortamınızı kurma

Düzenleyici

Bu codelab'i olabildiğince basit hale getirmek için geliştirme ortamınız olarak Visual Studio Code'u (VS Code) kullanacağınızı varsayıyoruz. Ücretsizdir ve tüm büyük platformlarda çalışır.

.

Android Studio, diğer IntelliJ IDE'leri, Emacs, Vim veya Notepad++ gibi istediğiniz düzenleyiciyi kullanabilirsiniz. Bunların hepsi Flutter ile çalışır.

Talimatlarda varsayılan olarak VS Code'a özgü kısayollar kullanıldığından bu codelab için VS Code'u kullanmanızı öneririz. "X işlemini yapmak için düzenleyicinizde uygun işlemi yapın" gibi ifadeler yerine "burayı tıklayın" veya "bu tuşa basın" gibi ifadeler kullanmak daha kolaydır.

228c71510a8e868.png

Geliştirme hedefi seçme

Flutter, çok platformlu bir araç setidir. Uygulamanız aşağıdaki işletim sistemlerinden herhangi birinde çalışabilir:

  • iOS
  • Android
  • Windows
  • macOS
  • Linux
  • web

Ancak öncelikli olarak geliştireceğiniz tek bir işletim sistemi seçmek yaygın bir uygulamadır. Bu, "geliştirme hedefiniz"dir. Uygulamanızın geliştirme sırasında üzerinde çalıştığı işletim sistemidir.

16695777c07f18e5.png

Örneğin, Flutter uygulaması geliştirmek için Windows dizüstü bilgisayar kullandığınızı varsayalım. Geliştirme hedefiniz olarak Android'i seçerseniz genellikle bir Android cihazı USB kablosuyla Windows dizüstü bilgisayarınıza bağlarsınız ve geliştirme aşamasındaki uygulamanız bu bağlı Android cihazda çalışır. Ancak geliştirme hedefi olarak Windows'u da seçebilirsiniz. Bu durumda, geliştirme aşamasındaki uygulamanız düzenleyicinizin yanında bir Windows uygulaması olarak çalışır.

Geliştirme hedefiniz olarak web'i seçmek cazip gelebilir. Bu seçimin dezavantajı, Flutter'ın en kullanışlı geliştirme özelliklerinden biri olan Stateful Hot Reload'u kaybetmenizdir. Flutter, web uygulamalarında anında yeniden yükleme yapamaz.

Seçiminizi şimdi yapın. Uygulamanızı daha sonra istediğiniz zaman diğer işletim sistemlerinde çalıştırabileceğinizi unutmayın. Ancak, net bir geliştirme hedefi belirlemek sonraki adımı kolaylaştırır.

Flutter'ı yükleme

Flutter SDK'nın nasıl yükleneceğiyle ilgili en güncel talimatları her zaman docs.flutter.dev adresinde bulabilirsiniz.

Flutter web sitesindeki talimatlar yalnızca SDK'nın yüklenmesini değil, aynı zamanda geliştirme hedefiyle ilgili araçları ve düzenleyici eklentilerini de kapsar. Bu codelab için yalnızca aşağıdakileri yüklemeniz gerektiğini unutmayın:

  1. Flutter SDK'sı
  2. Flutter eklentisiyle Visual Studio Code
  3. Seçtiğiniz geliştirme hedefi için gereken yazılım (örneğin, Windows'u hedeflemek için Visual Studio veya macOS'i hedeflemek için Xcode)

Sonraki bölümde ilk Flutter projenizi oluşturacaksınız.

Şimdiye kadar sorun yaşadıysanız sorun giderme konusunda StackOverflow'daki bazı soru ve yanıtlardan yararlanabilirsiniz.

Sık Sorulan Sorular

3. Proje oluşturma

İlk Flutter projenizi oluşturma

Visual Studio Code'u başlatın ve komut paletini (F1 veya Ctrl+Shift+P veya Shift+Cmd+P ile) açın. "flutter new" yazmaya başlayın. Flutter: New Project (Flutter: Yeni Proje) komutunu seçin.

Ardından Uygulama'yı ve projenizi oluşturacağınız klasörü seçin. Bu, ana dizininiz veya C:\src\ gibi bir şey olabilir.

Son olarak, projenize ad verin. namer_app veya my_awesome_namer gibi bir şey.

260a7d97f9678005.png

Flutter artık proje klasörünüzü oluşturur ve VS Code bu klasörü açar.

Artık 3 dosyanın içeriğinin üzerine uygulamanın temel iskeletini yazacaksınız.

İlk uygulamayı kopyalayıp yapıştırma

VS Code'un sol bölmesinde Explorer'ın (Gezgin) seçili olduğundan emin olun ve pubspec.yaml dosyasını açın.

e2a5bab0be07f4f7.png

Bu dosyanın içeriğini aşağıdakiyle değiştirin:

pubspec.yaml

name: namer_app
description: "A new Flutter project."
publish_to: "none"
version: 0.1.0

environment:
  sdk: ^3.9.0

dependencies:
  flutter:
    sdk: flutter
  english_words: ^4.0.0
  provider: ^6.1.5

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^6.0.0

flutter:
  uses-material-design: true

pubspec.yaml dosyası, uygulamanızla ilgili temel bilgileri (ör. mevcut sürümü, bağımlılıkları ve birlikte gönderileceği öğeler) belirtir.

Ardından, projede başka bir yapılandırma dosyası açın (analysis_options.yaml).

a781f218093be8e0.png

İçeriğini aşağıdakiyle değiştirin:

analysis_options.yaml

include: package:flutter_lints/flutter.yaml

linter:
  rules:
    avoid_print: false
    prefer_const_constructors_in_immutables: false
    prefer_const_constructors: false
    prefer_const_literals_to_create_immutables: false
    prefer_final_fields: false
    unnecessary_breaks: true
    use_key_in_widget_constructors: false

Bu dosya, Flutter'ın kodunuzu analiz ederken ne kadar katı olması gerektiğini belirler. Bu, Flutter'a ilk girişiniz olduğundan analizciye rahat davranmasını söylüyorsunuz. Bu ayarı daha sonra istediğiniz zaman değiştirebilirsiniz. Hatta gerçek bir üretim uygulaması yayınlamaya yaklaştıkça analiz aracını bu değerden daha katı hale getirmek isteyeceksiniz.

Son olarak, lib/ dizinindeki main.dart dosyasını açın.

e54c671c9bb4d23d.png

Bu dosyanın içeriğini aşağıdakiyle değiştirin:

lib/main.dart

import 'package:english_words/english_words.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (context) => MyAppState(),
      child: MaterialApp(
        title: 'Namer App',
        theme: ThemeData(
          colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepOrange),
        ),
        home: MyHomePage(),
      ),
    );
  }
}

class MyAppState extends ChangeNotifier {
  var current = WordPair.random();
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var appState = context.watch<MyAppState>();

    return Scaffold(
      body: Column(
        children: [Text('A random idea:'), Text(appState.current.asLowerCase)],
      ),
    );
  }
}

Bu 50 satırlık kod, uygulamanın şu ana kadarki tamamını oluşturuyor.

Sonraki bölümde, uygulamayı hata ayıklama modunda çalıştırın ve geliştirmeye başlayın.

4. Düğme ekleme

Bu adım, yeni bir kelime eşleşmesi oluşturmak için Sonraki düğmesini ekler.

Uygulamayı başlatma

Öncelikle lib/main.dart uygulamasını açın ve hedef cihazınızın seçili olduğundan emin olun. VS Code'un sağ alt köşesinde, mevcut hedef cihazı gösteren bir düğme bulunur. Değiştirmek için tıklayın.

lib/main.dart açıkken VS Code penceresinin sağ üst köşesindeki "oynat" b0a5d0200af5985d.png düğmesini bulup tıklayın.

Yaklaşık bir dakika sonra uygulamanız hata ayıklama modunda başlatılır. Henüz çok fazla şey görünmüyor:

f96e7dfb0937d7f4.png

İlk sıcak yeniden yükleme

lib/main.dart bölümünün en altına, ilk Text nesnesindeki dizeye bir şey ekleyin ve dosyayı kaydedin (Ctrl+S veya Cmd+S ile). Örneğin:

lib/main.dart

// ...

    return Scaffold(
      body: Column(
        children: [
          Text('A random AWESOME idea:'),  // ← Example change.
          Text(appState.current.asLowerCase),
        ],
      ),
    );

// ...

Uygulamanın hemen değiştiğini ancak rastgele kelimenin aynı kaldığını fark edin. Bu, Flutter'ın ünlü durumlu sıcak yeniden yükleme özelliğinin işleyişidir. Kaynak dosyada değişiklikleri kaydettiğinizde anında yeniden yükleme tetiklenir.

Sık Sorulan Sorular

Düğme ekleme

Ardından, ikinci Text örneğinin hemen altına, Column öğesinin en altına bir düğme ekleyin.

lib/main.dart

// ...

    return Scaffold(
      body: Column(
        children: [
          Text('A random AWESOME idea:'),
          Text(appState.current.asLowerCase),

          // ↓ Add this.
          ElevatedButton(
            onPressed: () {
              print('button pressed!');
            },
            child: Text('Next'),
          ),

        ],
      ),
    );

// ...

Değişikliği kaydettiğinizde uygulama tekrar güncellenir: Bir düğme görünür ve bu düğmeyi tıkladığınızda VS Code'daki Debug Console'da button pressed! (düğmeye basıldı!) mesajı gösterilir.

5 dakikada Flutter hızlandırılmış kursu

Hata Ayıklama Konsolu'nu izlemek eğlenceli olsa da düğmenin daha anlamlı bir işlem yapmasını istersiniz. Ancak bu konuya geçmeden önce, lib/main.dart bölümündeki kodu daha yakından inceleyerek nasıl çalıştığını anlayın.

lib/main.dart

// ...

void main() {
  runApp(MyApp());
}

// ...

Dosyanın en üstünde main() işlevini bulabilirsiniz. Mevcut haliyle yalnızca Flutter'a MyApp içinde tanımlanan uygulamayı çalıştırmasını söyler.

lib/main.dart

// ...

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (context) => MyAppState(),
      child: MaterialApp(
        title: 'Namer App',
        theme: ThemeData(
          colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepOrange),
        ),
        home: MyHomePage(),
      ),
    );
  }
}

// ...

MyApp sınıfı, StatelessWidget sınıfını genişletir. Widget'lar, her Flutter uygulamasını oluştururken kullandığınız öğelerdir. Gördüğünüz gibi, uygulamanın kendisi bile bir widget'tır.

MyApp içindeki kod, uygulamanın tamamını kurar. Uygulama genelinde durumu oluşturur (bu konuyla ilgili daha fazla bilgiyi sonraki bölümlerde bulabilirsiniz), uygulamayı adlandırır, görsel temayı tanımlar ve uygulamanızın başlangıç noktası olan "ana sayfa" widget'ını ayarlar.

lib/main.dart

// ...

class MyAppState extends ChangeNotifier {
  var current = WordPair.random();
}

// ...

Ardından, MyAppState sınıfı uygulamanın durumunu tanımlar. Bu, Flutter'a ilk adımınız olduğu için bu codelab'de basit ve odaklanmış bir yaklaşım izleyeceğiz. Flutter'da uygulama durumunu yönetmenin birçok etkili yolu vardır. En kolay açıklanabilenlerden biri, bu uygulamanın kullandığı yaklaşım olan ChangeNotifier'dır.

  • MyAppState Uygulamanın çalışması için gereken verileri tanımlar. Şu anda yalnızca mevcut rastgele kelime çiftini içeren tek bir değişken içerir. Bunu daha sonra ekleyeceksiniz.
  • Durum sınıfı ChangeNotifier sınıfını genişletir. Bu nedenle, kendi değişiklikleri hakkında diğerlerini bilgilendirebilir. Örneğin, mevcut kelime çifti değişirse uygulamadaki bazı widget'ların bunu bilmesi gerekir.
  • Durum, ChangeNotifierProvider kullanılarak oluşturulur ve uygulamanın tamamına sağlanır (MyApp bölümündeki yukarıdaki koda bakın). Bu sayede, uygulamadaki herhangi bir widget durumu elde edebilir.

d9b6ecac5494a6ff.png

lib/main.dart

// ...

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {           //  1
    var appState = context.watch<MyAppState>();  //  2

    return Scaffold(                             //  3
      body: Column(                              //  4
        children: [
          Text('A random AWESOME idea:'),        //  5
          Text(appState.current.asLowerCase),    //  6
          ElevatedButton(
            onPressed: () {
              print('button pressed!');
            },
            child: Text('Next'),
          ),
        ],                                       //  7
      ),
    );
  }
}

// ...

Son olarak, MyHomePage, yani daha önce değiştirdiğiniz widget'ı inceleyelim. Aşağıdaki her numaralı satır, yukarıdaki kodda bir satır numarası yorumuyla eşlenir:

  1. Her widget, widget'ın koşulları her değiştiğinde otomatik olarak çağrılan bir build() yöntemi tanımlar. Böylece widget her zaman güncel kalır.
  2. MyHomePage yöntemiyle uygulamanın mevcut durumundaki değişiklikleri izler.watch
  3. Her build yöntemi bir widget veya (daha yaygın olarak) iç içe yerleştirilmiş bir widget ağacı döndürmelidir. Bu örnekte üst düzey widget Scaffold'dır. Bu codelab'de Scaffold ile çalışmayacaksınız ancak bu widget, gerçek dünyadaki Flutter uygulamalarının büyük çoğunluğunda bulunan faydalı bir widget'tır.
  4. Column, Flutter'daki en temel düzen widget'larından biridir. İstenen sayıda çocuğu alır ve yukarıdan aşağıya doğru bir sütuna yerleştirir. Sütun, varsayılan olarak alt öğelerini görsel olarak en üste yerleştirir. Yakında bu sütunu ortalayacak şekilde değiştireceksiniz.
  5. İlk adımda bu Text widget'ını değiştirdiniz.
  6. Bu ikinci Text widget'ı appState alır ve bu sınıfın tek üyesi olan current'ye (WordPair olan) erişir. WordPair, asPascalCase veya asSnakeCase gibi çeşitli yararlı alıcılar sağlar. Burada asLowerCase kullanıyoruz ancak alternatiflerden birini tercih ederseniz bunu şimdi değiştirebilirsiniz.
  7. Flutter kodunda sondaki virgüllerin yoğun olarak kullanıldığını fark etmişsinizdir. children, bu Column parametre listesinin son (ve aynı zamanda tek) üyesi olduğundan bu virgülün burada olması gerekmez. Ancak genellikle sondaki virgülleri kullanmak iyi bir fikirdir: Bu virgüller, daha fazla üye eklemeyi kolaylaştırır ve Dart'ın otomatik biçimlendiricisinin oraya yeni bir satır eklemesi için ipucu görevi görür. Daha fazla bilgi için Kod biçimlendirme konusuna bakın.

Ardından, düğmeyi duruma bağlayacaksınız.

İlk davranışınız

MyAppState bölümüne gidin ve getNext yöntemi ekleyin.

lib/main.dart

// ...

class MyAppState extends ChangeNotifier {
  var current = WordPair.random();

  //  Add this.
  void getNext() {
    current = WordPair.random();
    notifyListeners();
  }
}

// ...

Yeni getNext() yöntemi, current değerini yeni bir rastgele WordPair ile yeniden atar. Ayrıca, notifyListeners()(MyAppState izleyen herkesin bilgilendirilmesini sağlayan bir ChangeNotifier) yöntemi) olarak da bilinir.

Geriye yalnızca düğmenin geri çağırma işlevinden getNext yöntemini çağırmak kalır.

lib/main.dart

// ...

    ElevatedButton(
      onPressed: () {
        appState.getNext();  // ← This instead of print().
      },
      child: Text('Next'),
    ),

// ...

Kaydedin ve uygulamayı hemen deneyin. Sonraki düğmesine her bastığınızda yeni bir rastgele kelime çifti oluşturulmalıdır.

Bir sonraki bölümde kullanıcı arayüzünü daha güzel hale getireceksiniz.

5. Uygulamayı daha güzel hale getirme

Uygulama şu anda bu şekilde görünüyor.

3dd8a9d8653bdc56.png

Çok iyi değil. Uygulamanın merkezinde yer alan, rastgele oluşturulmuş kelime çifti daha görünür olmalıdır. Sonuçta kullanıcılarımızın bu uygulamayı kullanmasının temel nedeni bu! Ayrıca, uygulama içerikleri tuhaf bir şekilde ortalanmamış ve uygulamanın tamamı sıkıcı bir şekilde siyah beyaz.

Bu bölüm, uygulamanın tasarımı üzerinde çalışarak bu sorunları ele alır. Bu bölümün nihai hedefi şuna benzer:

2bbee054d81a3127.png

Widget'ı ayıklama

Mevcut kelime çiftini göstermekten sorumlu satır artık şu şekilde görünüyor: Text(appState.current.asLowerCase). Bu satırı daha karmaşık bir hale getirmek için ayrı bir widget'a çıkarmanız önerilir. Kullanıcı arayüzünüzün ayrı mantıksal bölümleri için ayrı widget'lar kullanmak, Flutter'da karmaşıklığı yönetmenin önemli bir yoludur.

Flutter, widget'ları ayıklamak için yeniden düzenleme yardımcı programı sunar. Ancak bu yardımcı programı kullanmadan önce, ayıklanan satırın yalnızca ihtiyacı olanlara eriştiğinden emin olun. Şu anda satır appState öğesine erişiyor ancak aslında yalnızca mevcut kelime çiftinin ne olduğunu bilmesi gerekiyor.

Bu nedenle, MyHomePage widget'ını aşağıdaki gibi yeniden yazın:

lib/main.dart

// ...

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var appState = context.watch<MyAppState>();
    var pair = appState.current;                 //  Add this.

    return Scaffold(
      body: Column(
        children: [
          Text('A random AWESOME idea:'),
          Text(pair.asLowerCase),                //  Change to this.
          ElevatedButton(
            onPressed: () {
              appState.getNext();
            },
            child: Text('Next'),
          ),
        ],
      ),
    );
  }
}

// ...

Güzel. Text widget'ı artık appState'ın tamamını ifade etmiyor.

Şimdi Refactor (Yeniden düzenle) menüsünü açın. VS Code'da bunu iki şekilde yapabilirsiniz:

  1. Yeniden düzenlemek istediğiniz kod parçasını (bu örnekte Text) sağ tıklayın ve açılır menüden Yeniden düzenle...'yi seçin.

VEYA

  1. İmlecinizi yeniden düzenlemek istediğiniz parça koduna (bu örnekte Text) taşıyın ve Ctrl+. (Win/Linux) veya Cmd+. (Mac) tuşuna basın.

Refactor (Yeniden düzenle) menüsünde Extract Widget'ı (Widget'ı ayıkla) seçin. BigCard gibi bir ad atayın ve Enter simgesini tıklayın.

Bu işlem, geçerli dosyanın sonuna otomatik olarak yeni bir sınıf (BigCard) oluşturur. Sınıf aşağıdaki gibi görünür:

lib/main.dart

// ...

class BigCard extends StatelessWidget {
  const BigCard({super.key, required this.pair});

  final WordPair pair;

  @override
  Widget build(BuildContext context) {
    return Text(pair.asLowerCase);
  }
}

// ...

Uygulamanın bu yeniden düzenleme işlemine rağmen çalışmaya devam ettiğine dikkat edin.

Kart ekleme

Şimdi bu yeni widget'ı, bu bölümün başında hayal ettiğimiz cesur kullanıcı arayüzü parçası haline getirme zamanı.

BigCard sınıfını ve bu sınıfın içindeki build() yöntemini bulun. Daha önce olduğu gibi, Text widget'ında Refactor (Yeniden düzenle) menüsünü açın. Ancak bu kez widget'ı ayıklamayacaksınız.

Bunun yerine Dolguyla Sar'ı seçin. Bu işlem, Text widget'ı etrafında Padding adlı yeni bir üst widget oluşturur. Kaydettikten sonra, rastgele kelimenin daha fazla boşluk içerdiğini görürsünüz.

Dolgu değerini varsayılan 8.0 değerinden artırın. Örneğin, daha geniş bir dolgu için 20 gibi bir karakter kullanın.

Ardından, bir üst seviyeye gidin. İmlecinizi Padding widget'ının üzerine getirin, Refactor (Yeniden düzenle) menüsünü açın ve Wrap with widget... (Widget ile sarmala...) seçeneğini belirleyin.

Bu, üst widget'ı belirtmenize olanak tanır. "Kart" yazıp Enter tuşuna basın.

Bu, Padding widget'ını ve dolayısıyla Text widget'ını Card widget'ı ile sarmalar.

lib/main.dart

// ...

class BigCard extends StatelessWidget {
  const BigCard({super.key, required this.pair});

  final WordPair pair;

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(20),
        child: Text(pair.asLowerCase),
      ),
    );
  }
}

// ...

Uygulama artık aşağıdaki gibi görünecektir:

6031adbc0a11e16b.png

Tema ve stil

Kartın daha fazla öne çıkması için daha canlı bir renkle boyayın. Tutarlı bir renk düzeni kullanmak her zaman iyi bir fikir olduğundan rengi seçmek için uygulamanın Theme özelliğini kullanın.

BigCard'nın build() yönteminde aşağıdaki değişiklikleri yapın.

lib/main.dart

// ...

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);       //  Add this.

    return Card(
      color: theme.colorScheme.primary,    //  And also this.
      child: Padding(
        padding: const EdgeInsets.all(20),
        child: Text(pair.asLowerCase),
      ),
    );
  }

// ...

Bu iki yeni satır çok işe yarar:

  • Öncelikle kod, Theme.of(context) ile uygulamanın mevcut temasını ister.
  • Ardından, kod kartın rengini temanın colorScheme özelliğiyle aynı olacak şekilde tanımlar. Renk şeması birçok renk içeriyor ve primary, uygulamanın en belirgin ve tanımlayıcı rengi.

Kart artık uygulamanın birincil rengiyle boyanır:

a136f7682c204ea1.png

Bu rengi ve uygulamanın tamamının renk şemasını değiştirmek için MyApp simgesine kaydırıp ColorScheme simgesinin temel rengini değiştirin.

Rengin nasıl sorunsuz bir şekilde canlandığına dikkat edin. Buna örtülü animasyon denir. Birçok Flutter widget'ı, kullanıcı arayüzünün durumlar arasında "atlamaması" için değerler arasında sorunsuz bir şekilde enterpolasyon yapar.

Kartın altındaki yükseltilmiş düğme de renk değiştirir. Değerleri sabit kodlamak yerine uygulama genelinde Theme kullanmanın avantajı budur.

TextTheme

Kartta hâlâ sorun var: Metin çok küçük ve rengi zor okunuyor. Bu sorunu düzeltmek için BigCard'nın build() yönteminde aşağıdaki değişiklikleri yapın.

lib/main.dart

// ...

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    //  Add this.
    final style = theme.textTheme.displayMedium!.copyWith(
      color: theme.colorScheme.onPrimary,
    );

    return Card(
      color: theme.colorScheme.primary,
      child: Padding(
        padding: const EdgeInsets.all(20),
        //  Change this line.
        child: Text(pair.asLowerCase, style: style),
      ),
    );
  }

// ...

Bu değişikliğin nedeni:

  • theme.textTheme, özelliğini kullanarak uygulamanın yazı tipi temasına erişebilirsiniz. Bu sınıfta bodyMedium (orta boyuttaki standart metinler için), caption (resimlerin altyazıları için) veya headlineLarge (büyük başlıklar için) gibi üyeler bulunur.
  • displayMedium özelliği, metin görüntülemek için tasarlanmış büyük bir stildir. Görüntü kelimesi burada görüntü yazı tipi gibi tipografik anlamda kullanılmaktadır. displayMedium dokümanlarında "görüntüleme stilleri kısa ve önemli metinler için ayrılmıştır" ifadesi yer alıyor. Bu ifade, kullanım alanımızı tam olarak tanımlıyor.
  • Temanın displayMedium özelliği teorik olarak null olabilir. Bu uygulamayı yazdığınız programlama dili olan Dart, null güvenlidir. Bu nedenle, potansiyel olarak null olan nesnelerin yöntemlerini çağırmanıza izin vermez. Ancak bu durumda, ne yaptığınızı Dart'a bildirmek için ! operatörünü ("bang operatörü") kullanabilirsiniz. (displayMedium bu durumda kesinlikle boş değildir. Bunu neden bildiğimiz bu codelab'in kapsamı dışındadır.)
  • displayMedium üzerinde copyWith() işlevini çağırmak, tanımladığınız değişiklikleri içeren metin stilinin kopyasını döndürür. Bu durumda yalnızca metnin rengini değiştiriyorsunuz.
  • Yeni rengi almak için uygulamanın temasına tekrar erişin. Renk düzeninin onPrimary özelliği, uygulamanın birincil rengi üzerinde kullanıma uygun bir renk tanımlar.

Uygulama artık aşağıdaki gibi görünmelidir:

2405e9342d28c193.png

İsterseniz kartı daha fazla değiştirebilirsiniz. Aşağıdaki önerilerden yararlanabilirsiniz:

  • copyWith() simgesi, metin stilinde renkten çok daha fazla şeyi değiştirmenize olanak tanır. Değiştirebileceğiniz özelliklerin tam listesini görmek için imlecinizi copyWith() parantezlerinin içine yerleştirin ve Ctrl+Shift+Space (Win/Linux) veya Cmd+Shift+Space (Mac) tuşuna basın.
  • Benzer şekilde, Card widget'ı hakkında daha fazla değişiklik yapabilirsiniz. Örneğin, elevation parametresinin değerini artırarak kartın gölgesini büyütebilirsiniz.
  • Renklerle deneme yapmayı deneyin. theme.colorScheme.primary dışında .secondary, .surface ve daha pek çok sanatçı var. Bu renklerin hepsinin onPrimary karşılığı vardır.

Erişilebilirliği artırma

Flutter, uygulamaları varsayılan olarak erişilebilir hale getirir. Örneğin, her Flutter uygulaması, uygulamadaki tüm metinleri ve etkileşimli öğeleri TalkBack ve VoiceOver gibi ekran okuyuculara doğru şekilde gösterir.

d1fad7944fb890ea.png

Ancak bazen biraz çalışmanız gerekir. Bu uygulamada, ekran okuyucu bazı oluşturulan kelime çiftlerini telaffuz etmekte sorun yaşayabilir. İnsanlar cheaphead kelimesindeki iki kelimeyi tanımlamakta sorun yaşamazken ekran okuyucu, kelimenin ortasındaki ph harflerini f olarak telaffuz edebilir.

Çözüm olarak pair.asLowerCase yerine "${pair.first} ${pair.second}" kullanabilirsiniz. İkincisi, pair içinde bulunan iki kelimeden bir dize (ör. "cheap head") oluşturmak için dize enterpolasyonu kullanır. Birleşik kelime yerine iki ayrı kelime kullanmak, ekran okuyucuların bunları uygun şekilde tanımlamasını sağlar ve görme engelli kullanıcılara daha iyi bir deneyim sunar.

Ancak, pair.asLowerCase görselinin basitliğini korumak isteyebilirsiniz. Metin widget'ının görsel içeriğini, ekran okuyucular için daha uygun olan semantik bir içerikle geçersiz kılmak için Text'ın semanticsLabel özelliğini kullanın:

lib/main.dart

// ...

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    final style = theme.textTheme.displayMedium!.copyWith(
      color: theme.colorScheme.onPrimary,
    );

    return Card(
      color: theme.colorScheme.primary,
      child: Padding(
        padding: const EdgeInsets.all(20),

        //  Make the following change.
        child: Text(
          pair.asLowerCase,
          style: style,
          semanticsLabel: "${pair.first} ${pair.second}",
        ),
      ),
    );
  }

// ...

Artık ekran okuyucular, oluşturulan her kelime çiftini doğru şekilde telaffuz ediyor ancak kullanıcı arayüzü aynı kalıyor. Cihazınızda ekran okuyucu kullanarak bu özelliği deneyin.

Kullanıcı arayüzünü ortalama

Rastgele kelime çifti yeterli görsel öğeyle sunulduğuna göre artık bunu uygulamanın penceresinin/ekranının ortasına yerleştirme zamanı.

Öncelikle, BigCard öğesinin Column öğesinin bir parçası olduğunu unutmayın. Sütunlar varsayılan olarak alt öğelerini en üste toplar ancak bu durumu geçersiz kılabiliriz. MyHomePage'nın build() yöntemine gidin ve aşağıdaki değişikliği yapın:

lib/main.dart

// ...

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var appState = context.watch<MyAppState>();
    var pair = appState.current;

    return Scaffold(
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,  //  Add this.
        children: [
          Text('A random AWESOME idea:'),
          BigCard(pair: pair),
          ElevatedButton(
            onPressed: () {
              appState.getNext();
            },
            child: Text('Next'),
          ),
        ],
      ),
    );
  }
}

// ...

Bu, alt öğeleri ana (dikey) ekseni boyunca Column içinde ortalar.

b555d4c7f5000edf.png

Çocuklar, sütunun çapraz ekseni boyunca zaten ortalanmıştır (diğer bir deyişle, yatay olarak zaten ortalanmışlardır). Ancak Column kendisi Scaffold içinde ortalanmamıştır. Bu durumu Widget İnceleyici'yi kullanarak doğrulayabiliriz.

Widget Inspector'ın kendisi bu codelab'in kapsamı dışındadır ancak Column vurgulandığında uygulamanın tüm genişliğini kaplamadığını görebilirsiniz. Yalnızca alt öğelerinin ihtiyaç duyduğu yatay alanı kaplar.

Yalnızca sütunu ortalayabilirsiniz. İmlecinizi Column üzerine getirin, Yeniden düzenle menüsünü (Ctrl+. veya Cmd+. ile) açın ve Wrap with Center'ı (Ortayla sarmala) seçin.

Uygulama artık aşağıdaki gibi görünmelidir:

455688d93c30d154.png

İsterseniz bu ayarı biraz daha değiştirebilirsiniz.

  • Text widget'ını BigCard bölümünden kaldırabilirsiniz. Kullanıcı arayüzü, açıklayıcı metin olmadan bile anlamlı olduğundan bu metne ("Rastgele bir HARİKA fikir:") artık gerek olmadığı söylenebilir. Bu şekilde daha temiz olur.
  • SizedBox(height: 10) ile ElevatedButton arasına BigCard widget'ı da ekleyebilirsiniz. Bu şekilde, iki widget arasında biraz daha fazla ayrım olur. SizedBox widget'ı yalnızca yer kaplar ve kendi başına hiçbir şey oluşturmaz. Genellikle görsel "boşluklar" oluşturmak için kullanılır.

İsteğe bağlı değişikliklerle birlikte MyHomePage şu kodu içerir:

lib/main.dart

// ...

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var appState = context.watch<MyAppState>();
    var pair = appState.current;

    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            BigCard(pair: pair),
            SizedBox(height: 10),
            ElevatedButton(
              onPressed: () {
                appState.getNext();
              },
              child: Text('Next'),
            ),
          ],
        ),
      ),
    );
  }
}

// ...

Uygulama aşağıdaki gibi görünür:

3d53d2b071e2f372.png

Sonraki bölümde, oluşturulan kelimeleri favorilere ekleme (veya "beğenme") özelliğini ekleyeceksiniz.

6. İşlev ekleme

Uygulama çalışıyor ve bazen ilginç kelime çiftleri bile sunuyor. Ancak kullanıcı İleri'yi tıkladığında her kelime çifti kalıcı olarak kaybolur. En iyi önerileri "hatırlamak" için bir yol (ör. "Beğen" düğmesi) olması daha iyi olurdu.

e6b01a8c90df8ffa.png

İş mantığını ekleme

MyAppState bölümüne gidin ve aşağıdaki kodu ekleyin:

lib/main.dart

// ...

class MyAppState extends ChangeNotifier {
  var current = WordPair.random();

  void getNext() {
    current = WordPair.random();
    notifyListeners();
  }

  //  Add the code below.
  var favorites = <WordPair>[];

  void toggleFavorite() {
    if (favorites.contains(current)) {
      favorites.remove(current);
    } else {
      favorites.add(current);
    }
    notifyListeners();
  }
}

// ...

Değişiklikleri inceleyin:

  • MyAppState adlı mülke favorites adlı yeni bir mülk eklediniz. Bu özellik boş bir liste ile başlatılıyor: [].
  • Ayrıca, jenerikler kullanarak listenin yalnızca kelime çiftleri içerebileceğini de belirttiniz: <WordPair>[]. Bu, uygulamanızı daha sağlam hale getirir. Dart, WordPair dışında bir şey eklemeye çalışırsanız uygulamanızı çalıştırmayı bile reddeder. Böylece, favorites listesinde hiçbir zaman istenmeyen nesnelerin (ör. null) gizlenemeyeceğini bilerek bu listeyi kullanabilirsiniz.
  • Ayrıca, mevcut kelime çiftini favoriler listesinden kaldıran (zaten listede varsa) veya listeye ekleyen (henüz listede yoksa) yeni bir yöntem olan toggleFavorite()'yı da ekledik. Her iki durumda da kod daha sonra notifyListeners(); işlevini çağırır.

Düğmeyi ekleme

"İş mantığı" tamamlandığına göre kullanıcı arayüzü üzerinde tekrar çalışmanın zamanı geldi. "Beğen" düğmesini "Sonraki" düğmesinin soluna yerleştirmek için Row gerekir. Row widget'ı, daha önce gördüğünüz Column widget'ının yatay versiyonudur.

Öncelikle mevcut düğmeyi Row ile sarmalayın. MyHomePage's build() yöntemine gidin, imlecinizi ElevatedButton üzerine getirin, Ctrl+. veya Cmd+. ile Yeniden düzenle menüsünü açın ve Satırla sarmala'yı seçin.

Kaydettiğinizde Row öğesinin Column öğesine benzer şekilde davrandığını (varsayılan olarak alt öğelerini sola yığdığını) görürsünüz. (Column alt öğelerini en üste topladı.) Bu sorunu düzeltmek için öncekiyle aynı yaklaşımı kullanabilirsiniz ancak bu kez mainAxisAlignment ile. Ancak didaktik (öğrenme) amaçlarla mainAxisSize kullanın. Bu, Row öğesine mevcut yatay alanın tamamını kullanmaması gerektiğini söyler.

Aşağıdaki değişikliği yapın:

lib/main.dart

// ...

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var appState = context.watch<MyAppState>();
    var pair = appState.current;

    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            BigCard(pair: pair),
            SizedBox(height: 10),
            Row(
              mainAxisSize: MainAxisSize.min,   //  Add this.
              children: [
                ElevatedButton(
                  onPressed: () {
                    appState.getNext();
                  },
                  child: Text('Next'),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

// ...

Kullanıcı arayüzü eski haline döner.

3d53d2b071e2f372.png

Ardından, Beğen düğmesini ekleyin ve toggleFavorite()'ye bağlayın. Kendinizi test etmek için önce aşağıdaki kod bloğuna bakmadan bunu kendi başınıza yapmaya çalışın.

e6b01a8c90df8ffa.png

Aşağıdakiyle tam olarak aynı şekilde yapmamanızda bir sakınca yoktur. Hatta büyük bir zorluk istemiyorsanız kalp simgesiyle ilgili endişelenmeyin.

Başarısız olmak da tamamen normaldir. Sonuçta Flutter ile ilk saatinizdesiniz.

252f7c4a212c94d2.png

İkinci düğmeyi MyHomePage'ya eklemenin bir yolu aşağıda açıklanmıştır. Bu kez, simge içeren bir düğme oluşturmak için ElevatedButton.icon() oluşturucusunu kullanın. Ayrıca build yönteminin üst kısmında, mevcut kelime çiftinin favorilerde olup olmamasına bağlı olarak uygun simgeyi seçin. Ayrıca, iki düğmeyi biraz ayrı tutmak için SizedBox karakterinin tekrar kullanıldığını unutmayın.

lib/main.dart

// ...

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var appState = context.watch<MyAppState>();
    var pair = appState.current;

    //  Add this.
    IconData icon;
    if (appState.favorites.contains(pair)) {
      icon = Icons.favorite;
    } else {
      icon = Icons.favorite_border;
    }

    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            BigCard(pair: pair),
            SizedBox(height: 10),
            Row(
              mainAxisSize: MainAxisSize.min,
              children: [

                //  And this.
                ElevatedButton.icon(
                  onPressed: () {
                    appState.toggleFavorite();
                  },
                  icon: Icon(icon),
                  label: Text('Like'),
                ),
                SizedBox(width: 10),

                ElevatedButton(
                  onPressed: () {
                    appState.getNext();
                  },
                  child: Text('Next'),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

// ...

Uygulama aşağıdaki gibi görünmelidir:

Maalesef kullanıcı, favorileri göremez. Uygulamamıza tamamen ayrı bir ekran eklemenin zamanı geldi. Bir sonraki bölümde görüşmek üzere.

7. Gezinme sütunu ekleme

Çoğu uygulama, her şeyi tek bir ekrana sığdıramaz. Bu uygulama muhtemelen yapabilir ancak didaktik amaçlarla kullanıcının favorileri için ayrı bir ekran oluşturacaksınız. İki ekran arasında geçiş yapmak için ilk StatefulWidget öğenizi uygulayacaksınız.

f62c54f5401a187.png

Bu adımın özüne en kısa sürede ulaşmak için MyHomePage öğesini 2 ayrı widget'a bölün.

MyHomePage öğesinin tamamını seçin, silin ve aşağıdaki kodla değiştirin:

lib/main.dart

// ...

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Row(
        children: [
          SafeArea(
            child: NavigationRail(
              extended: false,
              destinations: [
                NavigationRailDestination(
                  icon: Icon(Icons.home),
                  label: Text('Home'),
                ),
                NavigationRailDestination(
                  icon: Icon(Icons.favorite),
                  label: Text('Favorites'),
                ),
              ],
              selectedIndex: 0,
              onDestinationSelected: (value) {
                print('selected: $value');
              },
            ),
          ),
          Expanded(
            child: Container(
              color: Theme.of(context).colorScheme.primaryContainer,
              child: GeneratorPage(),
            ),
          ),
        ],
      ),
    );
  }
}

class GeneratorPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var appState = context.watch<MyAppState>();
    var pair = appState.current;

    IconData icon;
    if (appState.favorites.contains(pair)) {
      icon = Icons.favorite;
    } else {
      icon = Icons.favorite_border;
    }

    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          BigCard(pair: pair),
          SizedBox(height: 10),
          Row(
            mainAxisSize: MainAxisSize.min,
            children: [
              ElevatedButton.icon(
                onPressed: () {
                  appState.toggleFavorite();
                },
                icon: Icon(icon),
                label: Text('Like'),
              ),
              SizedBox(width: 10),
              ElevatedButton(
                onPressed: () {
                  appState.getNext();
                },
                child: Text('Next'),
              ),
            ],
          ),
        ],
      ),
    );
  }
}

// ...

Kaydedildiğinde, kullanıcı arayüzünün görsel tarafının hazır olduğunu görürsünüz ancak bu kısım çalışmaz. Gezinme çubuğunda ♥︎ (kalp) simgesini tıkladığınızda hiçbir şey olmaz.

388bc25fe198c54a.png

Değişiklikleri inceleyin.

  • Öncelikle, MyHomePage içindeki tüm içeriklerin GeneratorPage adlı yeni bir widget'a çıkarıldığını fark edin. Eski MyHomePage widget'ının çıkarılmayan tek kısmı Scaffold.
  • Yeni MyHomePage öğesinde iki çocuklu bir Row bulunuyor. Birinci widget SafeArea, ikinci widget ise Expanded widget'ıdır.
  • SafeArea, alt öğesinin donanım çentiği veya durum çubuğu tarafından gizlenmemesini sağlar. Bu uygulamada, gezinme düğmelerinin örneğin bir mobil durum çubuğu tarafından gizlenmesini önlemek için widget NavigationRail öğesini sarar.
  • extended: false satırını NavigationRail bölümünde true olarak değiştirebilirsiniz. Bu ayar, simgelerin yanında etiketleri gösterir. Gelecekteki bir adımda, uygulama yeterli yatay alana sahip olduğunda bunu otomatik olarak nasıl yapacağınızı öğreneceksiniz.
  • Gezinme çubuğunda, ilgili simgeleri ve etiketleriyle birlikte iki hedef (Ana Sayfa ve Favoriler) bulunur. Ayrıca geçerli selectedIndex değerini de tanımlar. Sıfır seçili dizini ilk hedefi, bir seçili dizini ikinci hedefi vb. seçer. Şimdilik sıfır olarak kodlanmıştır.
  • Gezinme çubuğu, kullanıcının onDestinationSelected ile hedeflerden birini seçtiğinde ne olacağını da tanımlar. Şu anda uygulama, yalnızca print() ile istenen dizin değerini çıkış olarak veriyor.
  • Row öğesinin ikinci alt öğesi Expanded widget'ıdır. Genişletilmiş widget'lar satır ve sütunlarda son derece kullanışlıdır. Bazı öğelerin yalnızca ihtiyaç duyduğu kadar alan kapladığı (bu örnekte SafeArea) ve diğer widget'ların kalan alanın mümkün olduğunca çoğunu kapladığı (bu örnekte Expanded) düzenler oluşturmanıza olanak tanır. Expanded widget'ları "açgözlü" olarak düşünebilirsiniz. Bu widget'ın rolünü daha iyi anlamak istiyorsanız SafeArea widget'ını başka bir Expanded ile sarmayı deneyin. Elde edilen düzen aşağıdaki gibi görünür:

6bbda6c1835a1ae.png

  • Gezinme çubuğunun aslında solda küçük bir alana ihtiyacı olmasına rağmen iki Expanded widget'ı, mevcut tüm yatay alanı kendi aralarında bölüyor.
  • Expanded widget'ının içinde renkli bir Container, kapsayıcının içinde ise GeneratorPage bulunur.

Durum bilgisiz ve durum bilgili widget'lar

Şimdiye kadar MyAppState, eyaletinizdeki tüm ihtiyaçlarınızı karşılıyordu. Bu nedenle, şimdiye kadar yazdığınız tüm widget'lar durumsuzdur. Kendi değişebilir durumlarını içermezler. Widget'ların hiçbiri kendi kendini değiştiremez. Değişiklikler MyAppState üzerinden yapılmalıdır.

Bu durum değişmek üzere.

Gezinme çubuğunun selectedIndex değerini tutmak için bir yöntem gerekir. Ayrıca, bu değeri onDestinationSelected geri çağırma işlevinden değiştirebilmek istiyorsunuz.

selectedIndex öğesini MyAppState öğesinin başka bir özelliği olarak ekleyebilirsiniz. Bu yöntem işe yarayacaktır. Ancak her widget değerlerini bu durumda saklarsa uygulama durumunun kısa sürede mantıksız bir şekilde büyüyeceğini tahmin edebilirsiniz.

e52d9c0937cc0823.jpeg

Bazı durumlar yalnızca tek bir widget ile alakalıdır ve bu nedenle ilgili widget'ta kalmalıdır.

StatefulWidget değerini girin. Bu, State içeren bir widget türüdür. Öncelikle MyHomePage öğesini durumlu bir widget'a dönüştürün.

İmlecinizi MyHomePage'nın ilk satırına (class MyHomePage... ile başlayan) getirin ve Ctrl+. veya Cmd+. tuşlarını kullanarak Yeniden düzenle menüsünü açın. Ardından Convert to StatefulWidget'ı (Durumlu widget'a dönüştür) seçin.

IDE, sizin için yeni bir sınıf oluşturur: _MyHomePageState. Bu sınıf State sınıfını genişletir ve bu nedenle kendi değerlerini yönetebilir. (Kendiliğinden değişebilir.) Ayrıca, eski durum bilgisi içermeyen widget'taki build yönteminin widget'ta kalmak yerine _MyHomePageState'a taşındığını da unutmayın. Bu yöntem, kelimesi kelimesine taşındı. build yöntemi içinde hiçbir şey değişmedi. Artık yalnızca başka bir yerde yaşıyor.

setState

Yeni durumlu widget'ın yalnızca bir değişkeni izlemesi gerekir: selectedIndex. _MyHomePageState üzerinde aşağıdaki 3 değişikliği yapın:

lib/main.dart

// ...

class _MyHomePageState extends State<MyHomePage> {

  var selectedIndex = 0;     //  Add this property.

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Row(
        children: [
          SafeArea(
            child: NavigationRail(
              extended: false,
              destinations: [
                NavigationRailDestination(
                  icon: Icon(Icons.home),
                  label: Text('Home'),
                ),
                NavigationRailDestination(
                  icon: Icon(Icons.favorite),
                  label: Text('Favorites'),
                ),
              ],
              selectedIndex: selectedIndex,    //  Change to this.
              onDestinationSelected: (value) {

                //  Replace print with this.
                setState(() {
                  selectedIndex = value;
                });

              },
            ),
          ),
          Expanded(
            child: Container(
              color: Theme.of(context).colorScheme.primaryContainer,
              child: GeneratorPage(),
            ),
          ),
        ],
      ),
    );
  }
}

// ...

Değişiklikleri inceleyin:

  1. Yeni bir değişken olan selectedIndex'yı tanıtıp 0 olarak başlatıyorsunuz.
  2. Bu yeni değişkeni, NavigationRail tanımında şimdiye kadar kullanılan sabit kodlu 0 yerine kullanırsınız.
  3. onDestinationSelected geri çağırma işlevi çağrıldığında, yeni değeri yalnızca konsola yazdırmak yerine setState() çağrısı içinde selectedIndex öğesine atarsınız. Bu çağrı, daha önce kullanılan notifyListeners() yöntemine benzer. Kullanıcı arayüzünün güncellenmesini sağlar.

Gezinme çubuğu artık kullanıcı etkileşimine yanıt veriyor. Ancak sağdaki genişletilmiş alan aynı kalır. Bunun nedeni, kodun hangi ekranın görüntüleneceğini belirlemek için selectedIndex kullanmamasıdır.

selectedIndex özelliğini kullanma

Aşağıdaki kodu _MyHomePageState yönteminin en üstüne, return Scaffold'den hemen önce yerleştirin:build

lib/main.dart

// ...

Widget page;
switch (selectedIndex) {
  case 0:
    page = GeneratorPage();
    break;
  case 1:
    page = Placeholder();
    break;
  default:
    throw UnimplementedError('no widget for $selectedIndex');
}

// ...

Şu kodu inceleyin:

  1. Kod, page türünde yeni bir değişken (Widget) bildirir.
  2. Ardından, bir switch ifadesi, selectedIndex içindeki geçerli değere göre page öğesine bir ekran atar.
  3. Henüz FavoritesPage olmadığı için Placeholder simgesini kullanın. Bu kullanışlı widget, yerleştirdiğiniz her yere çapraz bir dikdörtgen çizer ve kullanıcı arayüzünün bu bölümünü tamamlanmamış olarak işaretler.

5685cf886047f6ec.png

  1. Hızlı hata ilkesini uygulayan switch ifadesi, selectedIndex 0 veya 1 değilse de hata vermeyi sağlar. Bu sayede ileride hataların oluşması önlenir. Gezinme çubuğuna yeni bir hedef ekleyip bu kodu güncellemeyi unutursanız program, geliştirme aşamasında kilitlenir (bu durumda, neden çalışmadığını tahmin etmenize veya hatalı kodu üretime yayınlamanıza izin verilmez).

page artık sağda göstermek istediğiniz widget'ı içerdiğine göre, başka hangi değişikliğin yapılması gerektiğini tahmin edebilirsiniz.

Tek bir değişiklik kaldıktan sonraki _MyHomePageState:

lib/main.dart

// ...

class _MyHomePageState extends State<MyHomePage> {
  var selectedIndex = 0;

  @override
  Widget build(BuildContext context) {
    Widget page;
    switch (selectedIndex) {
      case 0:
        page = GeneratorPage();
        break;
      case 1:
        page = Placeholder();
        break;
      default:
        throw UnimplementedError('no widget for $selectedIndex');
    }

    return Scaffold(
      body: Row(
        children: [
          SafeArea(
            child: NavigationRail(
              extended: false,
              destinations: [
                NavigationRailDestination(
                  icon: Icon(Icons.home),
                  label: Text('Home'),
                ),
                NavigationRailDestination(
                  icon: Icon(Icons.favorite),
                  label: Text('Favorites'),
                ),
              ],
              selectedIndex: selectedIndex,
              onDestinationSelected: (value) {
                setState(() {
                  selectedIndex = value;
                });
              },
            ),
          ),
          Expanded(
            child: Container(
              color: Theme.of(context).colorScheme.primaryContainer,
              child: page,  //  Here.
            ),
          ),
        ],
      ),
    );
  }
}

// ...

Uygulama artık GeneratorPage ile yakında Favoriler sayfası olacak yer tutucu arasında geçiş yapıyor.

Yanıt verme

Ardından, gezinme rayını duyarlı hale getirin. Yani, yeterli alan olduğunda etiketlerin otomatik olarak gösterilmesini sağlayın (extended: true kullanarak).

a8873894c32e0d0b.png

Flutter, uygulamalarınızı otomatik olarak duyarlı hale getirmenize yardımcı olan çeşitli widget'lar sunar. Örneğin, Wrap, yeterli dikey veya yatay alan olmadığında alt öğeleri otomatik olarak bir sonraki "satıra" (çalıştırma olarak adlandırılır) kaydıran Row veya Column'a benzer bir widget'tır. FittedBox adlı bir widget vardır. Bu widget, çocuğunuza ait öğeleri, belirttiğiniz özelliklere göre mevcut alana otomatik olarak yerleştirir.

Ancak NavigationRail, her bağlamda neyin yeterli alan olduğunu bilemediği için yeterli alan olduğunda etiketleri otomatik olarak göstermez. Bu kararı geliştirici olarak siz verirsiniz.

Örneğin, etiketleri yalnızca MyHomePage en az 600 piksel genişliğinde olduğunda göstermeye karar verdiğinizi varsayalım.

Bu örnekte kullanılacak widget LayoutBuilder. Bu özellik, widget ağacınızı kullanılabilir alanınıza göre değiştirmenize olanak tanır.

Gerekli değişiklikleri yapmak için VS Code'da Flutter'ın Refactor (Yeniden düzenle) menüsünü kullanın. Ancak bu sefer durum biraz daha karmaşık:

  1. _MyHomePageState'nın build yönteminde imlecinizi Scaffold'nin üzerine getirin.
  2. Ctrl+. (Windows/Linux) veya Cmd+. (Mac) ile Refactor (Yeniden Düzenle) menüsünü açın.
  3. Wrap with Builder'ı (Oluşturucu ile Sar) seçin ve Enter tuşuna basın.
  4. Yeni eklenen Builder öğesinin adını LayoutBuilder olarak değiştirin.
  5. Geri çağırma parametresi listesini (context) olarak değiştirin.(context, constraints)

LayoutBuilder'nın builder geri çağırma işlevi, kısıtlamalar her değiştiğinde çağrılır. Bu durum örneğin aşağıdaki durumlarda ortaya çıkar:

  • Kullanıcı, uygulamanın penceresini yeniden boyutlandırırsa
  • Kullanıcının telefonunu dikey moddan yatay moda veya yatay moddan dikey moda döndürmesi
  • MyHomePage'nın yanındaki bir widget'ın boyutu büyüdüğünde MyHomePage'nın kısıtlamaları küçülüyor.

Artık kodunuz, mevcut constraints sorgulanarak etiketin gösterilip gösterilmeyeceğine karar verebilir. _MyHomePageState'nın build yönteminde aşağıdaki tek satırlık değişikliği yapın:

lib/main.dart

// ...

class _MyHomePageState extends State<MyHomePage> {
  var selectedIndex = 0;

  @override
  Widget build(BuildContext context) {
    Widget page;
    switch (selectedIndex) {
      case 0:
        page = GeneratorPage();
        break;
      case 1:
        page = Placeholder();
        break;
      default:
        throw UnimplementedError('no widget for $selectedIndex');
    }

    return LayoutBuilder(builder: (context, constraints) {
      return Scaffold(
        body: Row(
          children: [
            SafeArea(
              child: NavigationRail(
                extended: constraints.maxWidth >= 600,  //  Here.
                destinations: [
                  NavigationRailDestination(
                    icon: Icon(Icons.home),
                    label: Text('Home'),
                  ),
                  NavigationRailDestination(
                    icon: Icon(Icons.favorite),
                    label: Text('Favorites'),
                  ),
                ],
                selectedIndex: selectedIndex,
                onDestinationSelected: (value) {
                  setState(() {
                    selectedIndex = value;
                  });
                },
              ),
            ),
            Expanded(
              child: Container(
                color: Theme.of(context).colorScheme.primaryContainer,
                child: page,
              ),
            ),
          ],
        ),
      );
    });
  }
}


// ...

Artık uygulamanız ekran boyutu, yönü ve platform gibi ortamına yanıt veriyor. Başka bir deyişle, duyarlıdır.

Geriye kalan tek iş, bu Placeholder simgesini gerçek bir Favoriler ekranıyla değiştirmektir. Bu konu bir sonraki bölümde ele alınmaktadır.

8. Yeni sayfa ekle

Favoriler sayfası yerine kullandığımız Placeholder widget'ını hatırlıyor musunuz?

Bu sorunu düzeltme zamanı.

Kendinize güveniyorsanız bu adımı kendi başınıza yapmayı deneyin. Amacınız, favorites listesini yeni bir durumsuz widget'ta (FavoritesPage) göstermek ve ardından Placeholder yerine bu widget'ı göstermektir.

Aşağıda birkaç ipucu verilmiştir:

  • Kaydırılan bir Column istediğinizde ListView widget'ını kullanın.
  • MyAppState örneğine context.watch<MyAppState>() kullanarak herhangi bir widget'tan erişebileceğinizi unutmayın.
  • Yeni bir widget da denemek istiyorsanız ListTile, title (genellikle metin için), leading (simgeler veya avatarlar için) ve onTap (etkileşimler için) gibi özellikler sunar. Ancak, bildiğiniz widget'larla benzer efektler elde edebilirsiniz.
  • Dart, koleksiyon değişmezleri içinde for döngülerinin kullanılmasına izin verir. Örneğin, messages dizesi bir liste içeriyorsa aşağıdaki gibi bir kodunuz olabilir:

f0444bba08f205aa.png

Öte yandan, işlevsel programlamaya daha aşina iseniz Dart, messages.map((m) => Text(m)).toList() gibi kodlar yazmanıza da olanak tanır. Elbette, her zaman bir widget listesi oluşturabilir ve build yöntemi içinde bu listeye zorunlu olarak eklemeler yapabilirsiniz.

Favoriler sayfasını kendiniz eklemenin avantajı, kendi kararlarınızı vererek daha fazla şey öğrenmenizdir. Dezavantajı, henüz kendi başınıza çözemeyeceğiniz sorunlarla karşılaşabilirsiniz. Unutmayın: Başarısızlık, öğrenmenin en önemli unsurlarından biridir. Kimse sizden ilk saatte Flutter geliştirmeyi öğrenmenizi beklemez. Siz de kendinizden bunu beklememelisiniz.

252f7c4a212c94d2.png

Aşağıda, favoriler sayfasını uygulamanın bir yolu verilmiştir. Uygulama şekli, kodu denemeniz, kullanıcı arayüzünü iyileştirmeniz ve kendi tarzınıza göre düzenlemeniz için size ilham verecektir.

Yeni FavoritesPage sınıfı:

lib/main.dart

// ...

class FavoritesPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var appState = context.watch<MyAppState>();

    if (appState.favorites.isEmpty) {
      return Center(
        child: Text('No favorites yet.'),
      );
    }

    return ListView(
      children: [
        Padding(
          padding: const EdgeInsets.all(20),
          child: Text('You have '
              '${appState.favorites.length} favorites:'),
        ),
        for (var pair in appState.favorites)
          ListTile(
            leading: Icon(Icons.favorite),
            title: Text(pair.asLowerCase),
          ),
      ],
    );
  }
}

Widget'ın işlevi:

  • Uygulamanın mevcut durumunu alır.
  • Favoriler listesi boşsa ortalanmış bir mesaj gösterilir: Henüz favori eklenmedi.
  • Aksi takdirde, (kaydırılabilir) bir liste gösterilir.
  • Liste, bir özetle başlar (örneğin, 5 favoriniz var.).
  • Ardından kod, tüm favoriler arasında gezinir ve her biri için bir ListTile widget'ı oluşturur.

Şimdi yapmanız gereken tek şey Placeholder widget'ını FavoritesPage ile değiştirmek. İşte bu kadar.

Bu uygulamanın nihai kodunu GitHub'daki codelab deposunda bulabilirsiniz.

9. Sonraki adımlar

Tebrikler!

Harikasın! Column ve iki Text widget'ı içeren işlevsel olmayan bir iskeleti alıp duyarlı ve keyifli küçük bir uygulamaya dönüştürdünüz.

d6e3d5f736411f13.png

İşlediğimiz konular

  • Flutter'ın işleyiş şekliyle ilgili temel bilgiler
  • Flutter'da düzen oluşturma
  • Kullanıcı etkileşimlerini (ör. düğmeye basma) uygulama davranışına bağlama
  • Flutter kodunuzu düzenli tutma
  • Uygulamanızı duyarlı hale getirme
  • Uygulamanızın tutarlı bir görünüm ve tarzda olmasını sağlama

Sonra ne olur?

  • Bu laboratuvar sırasında yazdığınız uygulamayla daha fazla deneme yapın.
  • Animasyonlu listeler, gradyanlar, çapraz geçişler ve daha fazlasını nasıl ekleyebileceğinizi görmek için aynı uygulamanın bu gelişmiş sürümünün koduna göz atın.