MDC-104 Flutter: Gelişmiş Malzeme Bileşenleri

1. Giriş

logo_components_color_2x_web_96dp.png

Material Components (MDC), geliştiricilerin Materyal Tasarım'ı uygulamasına yardımcı olur. Google'daki bir mühendis ve kullanıcı deneyimi tasarımcısı ekibi tarafından oluşturulan MDC, düzinelerce güzel ve işlevsel kullanıcı arayüzü bileşeni içerir. Android, iOS, web ve Flutter.material.io/develop için kullanılabilir.

MDC-103 adlı codelab'de, uygulamanızı stilize etmek için Material Components'ın (MDC) rengini, yüksekliğini, tipografisini ve şeklini özelleştirmiştiniz.

Materyal Tasarım sistemindeki bir bileşen, önceden tanımlanmış bir dizi görevi yerine getirir ve düğme gibi belirli özelliklere sahiptir. Ancak düğme, kullanıcının bir işlem gerçekleştirmesinin yanı sıra şekil, boyut ve rengin görsel ifadesidir. Kullanıcıya etkileşimli olduğunu ve dokunulduğunda veya tıklandığında bir şey olacağını bildirir.

Materyal Tasarım yönergelerinde bileşenler, tasarımcının bakış açısıyla açıklanır. Bunlar, platformlarda kullanılabilen çok çeşitli temel işlevleri ve her bileşeni oluşturan anatomik öğeleri açıklar. Örneğin, bir arka plan; arka katmanı ve içeriğini, ön katmanı ve içeriğini, hareket kurallarını ve görüntüleme seçeneklerini içerir. Bu bileşenlerin her biri, her uygulamanın ihtiyaçlarına, kullanım alanlarına ve içeriğine göre özelleştirilebilir.

Ne oluşturacaksınız?

Bu codelab'de, Shrine uygulamasındaki kullanıcı arayüzünü "arka plan" adı verilen iki seviyeli bir sunuma dönüştüreceksiniz. Arka planda, asimetrik ızgarada gösterilen ürünleri filtrelemek için kullanılan seçilebilir kategorilerin listelendiği bir menü bulunur. Bu codelab'de şunları kullanacaksınız:

  • Şekil
  • Hareket
  • Flutter widget'ları (önceki codelab'lerde kullandığınız)

Android

iOS

Üst uygulama çubuğu ve ürünlerle dolu, asimetrik, yatay olarak kaydırılabilen bir ızgarası olan pembe ve kahverengi temalı e-ticaret uygulaması

Üst uygulama çubuğu ve ürünlerle dolu, asimetrik, yatay olarak kaydırılabilen bir ızgarası olan pembe ve kahverengi temalı e-ticaret uygulaması

menü listelemesi 4 kategori

menü listelemesi 4 kategori

Bu kod laboratuvarındaki Material Flutter bileşenleri ve alt sistemleri

  • Şekil

Flutter geliştirme deneyiminizi nasıl değerlendirirsiniz?

Başlangıç Orta İleri

2. Flutter geliştirme ortamınızı kurma

Bu laboratuvarı tamamlamak için iki yazılım gerekir: Flutter SDK ve bir düzenleyici.

Codelab'i aşağıdaki cihazlardan herhangi birini kullanarak çalıştırabilirsiniz:

  • Bilgisayarınıza bağlı ve Geliştirici moduna ayarlanmış fiziksel bir Android veya iOS cihaz.
  • iOS simülatörü (Xcode araçlarının yüklenmesi gerekir).
  • Android Emulator (Android Studio'da kurulum gerektirir).
  • Tarayıcı (hata ayıklama için Chrome gereklidir).
  • Windows, Linux veya macOS masaüstü uygulaması olarak. Dağıtmayı planladığınız platformda geliştirme yapmanız gerekir. Bu nedenle, bir Windows masaüstü uygulaması geliştirmek istiyorsanız uygun derleme zincirine erişmek için Windows'ta geliştirme yapmanız gerekir. docs.flutter.dev/desktop adresinde ayrıntılı olarak ele alınan işletim sistemine özgü gereksinimler vardır.

3. Codelab başlangıç uygulamasını indirin

MDC-103'ten devam mı ediyorsunuz?

MDC-103'ü tamamladıysanız kodunuz bu codelab'e hazırdır. Arka plan menüsünü ekleme adımına gidin.

Sıfırdan mı başlıyorsunuz?

Başlangıç uygulaması material-components-flutter-codelabs-104-starter_and_103-complete/mdc_100_series dizininde bulunur.

...veya GitHub'dan kopyalayın

Bu codelab'i GitHub'dan klonlamak için aşağıdaki komutları çalıştırın:

git clone https://github.com/material-components/material-components-flutter-codelabs.git
cd material-components-flutter-codelabs/mdc_100_series
git checkout 104-starter_and_103-complete

Projeyi açıp uygulamayı çalıştırın.

  1. Projeyi tercih ettiğiniz düzenleyicide açın.
  2. Seçtiğiniz düzenleyici için Başlarken: Test sürüşü bölümündeki "Uygulamayı çalıştırma" talimatlarını uygulayın.

Başarılı aktarım Cihazınızda önceki codelab'lerdeki Shrine giriş sayfasını görmeniz gerekir.

Android

iOS

Shrine giriş sayfası

Shrine giriş sayfası

4. Arka plan menüsünü ekleme

Diğer tüm içeriklerin ve bileşenlerin arkasında bir arka plan görünür. İki katmandan oluşur: arka katman (işlemleri ve filtreleri gösterir) ve ön katman (içeriği gösterir). Gezinme veya içerik filtreleri gibi etkileşimli bilgileri ve işlemleri göstermek için arka plan kullanabilirsiniz.

Ev uygulaması çubuğunu kaldırma

AnaSayfa widget'ı, ön katmanımızın içeriği olacaktır. Şu anda bir uygulama çubuğu var. Uygulama çubuğunu arka katmana taşıyacağız ve HomePage yalnızca AsymmetricView'u içerecek.

home.dart içinde, build() işlevini yalnızca AsymmetricView döndürecek şekilde değiştirin:

// TODO: Return an AsymmetricView (104)
return AsymmetricView(products: ProductsRepository.loadProducts(Category.all));

Arka Plan widget'ını ekleme

frontLayer ve backLayer simgelerini içeren Arka Plan adlı bir widget oluşturun.

backLayer, listeyi filtrelemek için bir kategori seçmenize olanak tanıyan bir menü içerir (currentCategory). Menü seçiminin kalıcı olmasını istediğimiz için Arka plan'ı durum bilgili bir widget yapacağız.

/lib adlı backdrop.dart çalışma alanına yeni bir dosya ekleyin:

import 'package:flutter/material.dart';

import 'model/product.dart';

// TODO: Add velocity constant (104)

class Backdrop extends StatefulWidget {
  final Category currentCategory;
  final Widget frontLayer;
  final Widget backLayer;
  final Widget frontTitle;
  final Widget backTitle;

  const Backdrop({
    required this.currentCategory,
    required this.frontLayer,
    required this.backLayer,
    required this.frontTitle,
    required this.backTitle,
    Key? key,
  }) : super(key: key);

  @override
  _BackdropState createState() => _BackdropState();
}

// TODO: Add _FrontLayer class (104)
// TODO: Add _BackdropTitle class (104)
// TODO: Add _BackdropState class (104)

Belirli tesisleri required ile işaretlediğimizi unutmayın. Bu, oluşturucuda varsayılan değeri olmayan ve null olamayan özellikler için en iyi uygulamadır. Bu nedenle unutulmamalıdır.

Arka plan sınıfı tanımının altına _BackdropState sınıfını ekleyin:

// TODO: Add _BackdropState class (104)
class _BackdropState extends State<Backdrop>
    with SingleTickerProviderStateMixin {
  final GlobalKey _backdropKey = GlobalKey(debugLabel: 'Backdrop');

  // TODO: Add AnimationController widget (104)

  // TODO: Add BuildContext and BoxConstraints parameters to _buildStack (104)
  Widget _buildStack() {
    return Stack(
    key: _backdropKey,
      children: <Widget>[
        // TODO: Wrap backLayer in an ExcludeSemantics widget (104)
        widget.backLayer,
        widget.frontLayer,
      ],
    );
  }

  @override
  Widget build(BuildContext context) {
    var appBar = AppBar(
      elevation: 0.0,
      titleSpacing: 0.0,
      // TODO: Replace leading menu icon with IconButton (104)
      // TODO: Remove leading property (104)
      // TODO: Create title with _BackdropTitle parameter (104)
      leading: Icon(Icons.menu),
      title: Text('SHRINE'),
      actions: <Widget>[
        // TODO: Add shortcut to login screen from trailing icons (104)
        IconButton(
          icon: Icon(
            Icons.search,
            semanticLabel: 'search',
          ),
          onPressed: () {
          // TODO: Add open login (104)
          },
        ),
        IconButton(
          icon: Icon(
            Icons.tune,
            semanticLabel: 'filter',
          ),
          onPressed: () {
          // TODO: Add open login (104)
          },
        ),
      ],
    );
    return Scaffold(
      appBar: appBar,
      // TODO: Return a LayoutBuilder widget (104)
      body: _buildStack(),
    );
  }
}

build() işlevi, tıpkı HomePage'in yaptığı gibi bir uygulama çubuğu içeren bir Scaffold döndürür. Ancak Scaffold'un gövdesi bir Stack'tir. Bir Stack'in alt öğeleri çakışabilir. Her alt öğenin boyutu ve konumu, yığının üst öğesine göre belirtilir.

Şimdi ShrineApp'e bir Backdrop örneği ekleyin.

app.dart içinde backdrop.dart ve model/product.dart dosyalarını içe aktarın:

import 'backdrop.dart'; // New code
import 'colors.dart';
import 'home.dart';
import 'login.dart';
import 'model/product.dart'; // New code
import 'supplemental/cut_corners_border.dart';

app.dart, içinde, / rotasını frontLayer olarak HomePage içeren bir Backdrop döndürerek değiştirin:

// TODO: Change to a Backdrop with a HomePage frontLayer (104)
'/': (BuildContext context) => Backdrop(
     // TODO: Make currentCategory field take _currentCategory (104)
     currentCategory: Category.all,
     // TODO: Pass _currentCategory for frontLayer (104)
     frontLayer: HomePage(),
     // TODO: Change backLayer field value to CategoryMenuPage (104)
     backLayer: Container(color: kShrinePink100),
     frontTitle: Text('SHRINE'),
     backTitle: Text('MENU'),
),

Projenizi kaydedin. Ana sayfamızın ve uygulama çubuğunun göründüğünü görmelisiniz:

Android

iOS

Pembe arka planlı Shrine ürün sayfası

Pembe arka planlı Shrine ürün sayfası

backLayer, ön katman ana sayfasının arkasındaki yeni bir katmanda pembe alanı gösterir.

Stack'in gerçekten bir HomePage'in arkasında bir Container'a sahip olduğunu doğrulamak için Flutter Inspector'ı kullanabilirsiniz. Şuna benzer olmalıdır:

92ed338a15a074bd.png

Artık her iki katmanın da tasarımını ve içeriğini ayarlayabilirsiniz.

5. Şekil ekleme

Bu adımda, ön katmana stil uygulayarak sol üst köşeye bir kesik ekleyeceksiniz.

Materyal Tasarım, bu tür özelleştirmeyi şekil olarak adlandırır. Malzeme yüzeyleri rastgele şekillere sahip olabilir. Şekiller, yüzeylere vurgu ve stil katar. Markalama için kullanılabilir. Sıradan dikdörtgen şekiller, kavisli veya açılı köşeler ve kenarlarla ve istenen sayıda kenarla özelleştirilebilir. Simetrik veya düzensiz olabilirler.

Ön katmana şekil ekleme

Açılı Shrine logosu, Shrine uygulamasının şekil hikayesine ilham verdi. Şekil hikayesi, bir uygulama genelinde kullanılan şekillerin ortak kullanımıdır. Örneğin, logo şekli, şekil uygulanmış giriş sayfası öğelerinde tekrarlanır. Bu adımda, ön katmanı sol üst köşede açılı bir kesimle şekillendireceksiniz.

backdrop.dart bölümünde yeni bir _FrontLayer sınıfı ekleyin:

// TODO: Add _FrontLayer class (104)
class _FrontLayer extends StatelessWidget {
  // TODO: Add on-tap callback (104)
  const _FrontLayer({
    Key? key,
    required this.child,
  }) : super(key: key);

  final Widget child;

  @override
  Widget build(BuildContext context) {
    return Material(
      elevation: 16.0,
      shape: const BeveledRectangleBorder(
        borderRadius: BorderRadius.only(topLeft: Radius.circular(46.0)),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: <Widget>[
          // TODO: Add a GestureDetector (104)
          Expanded(
            child: child,
          ),
        ],
      ),
    );
  }
}

Ardından, _BackdropState'in _buildStack() işlevinde ön katmanı _FrontLayer ile sarmalayın:

  Widget _buildStack() {
    // TODO: Create a RelativeRectTween Animation (104)

    return Stack(
    key: _backdropKey,
      children: <Widget>[
        // TODO: Wrap backLayer in an ExcludeSemantics widget (104)
        widget.backLayer,
        // TODO: Add a PositionedTransition (104)
        // TODO: Wrap front layer in _FrontLayer (104)
          _FrontLayer(child: widget.frontLayer),
      ],
    );
  }

Yeniden yükleyin.

Android

iOS

Özel şekilli Shrine ürün sayfası

Özel şekilli Shrine ürün sayfası

Shrine'ın birincil yüzeyine özel bir şekil verdik. Ancak bunun görsel olarak uygulama çubuğuyla bağlantılı olmasını istiyoruz.

Uygulama çubuğu rengini değiştirme

app.dart içinde _buildShrineTheme() işlevini aşağıdaki gibi değiştirin:

ThemeData _buildShrineTheme() {
  final ThemeData base = ThemeData.light(useMaterial3: true);
  return base.copyWith(
    colorScheme: base.colorScheme.copyWith(
      primary: kShrinePink100,
      onPrimary: kShrineBrown900,
      secondary: kShrineBrown900,
      error: kShrineErrorRed,
    ),
    textTheme: _buildShrineTextTheme(base.textTheme),
    textSelectionTheme: const TextSelectionThemeData(
      selectionColor: kShrinePink100,
    ),
    appBarTheme: const AppBarTheme(
      foregroundColor: kShrineBrown900,
      backgroundColor: kShrinePink100,
    ),
    inputDecorationTheme: const InputDecorationTheme(
      border: CutCornersBorder(),
      focusedBorder: CutCornersBorder(
        borderSide: BorderSide(
          width: 2.0,
          color: kShrineBrown900,
        ),
      ),
      floatingLabelStyle: TextStyle(
        color: kShrineBrown900,
      ),
    ),
  );
}

Çalışır durumda yeniden başlatma. Yeni renkli uygulama çubuğu görünür.

Android

iOS

Renkli uygulama çubuğu içeren Shrine ürün sayfası

Renkli uygulama çubuğu içeren Shrine ürün sayfası

Bu değişiklik nedeniyle kullanıcılar, ön taraftaki beyaz katmanın hemen arkasında bir şey olduğunu görebilir. Kullanıcıların arka planın arka katmanını görebilmesi için hareket ekleyelim.

6. Hareket ekleme

Hareket, uygulamanıza hayat vermenin bir yoludur. Büyük ve çarpıcı, ince ve minimalist veya bu ikisinin arasında bir yerde olabilir. Ancak, kullandığınız hareket türünün duruma uygun olması gerektiğini unutmayın. Tekrarlanan ve düzenli işlemlere uygulanan hareketler küçük ve ince olmalıdır. Böylece işlemler, kullanıcının dikkatini dağıtmaz veya düzenli olarak çok fazla zaman almaz. Ancak, kullanıcının bir uygulamayı ilk kez açması gibi uygun durumlarda daha dikkat çekici olabilir ve bazı animasyonlar, kullanıcının uygulamanızı nasıl kullanacağı konusunda bilgi edinmesine yardımcı olabilir.

Menü düğmesine ortaya çıkarma hareketi ekleme

backdrop.dart dosyasının en üstünde, herhangi bir sınıf veya işlevin kapsamı dışında, animasyonumuzun sahip olmasını istediğimiz hızı temsil eden bir sabit ekleyin:

// TODO: Add velocity constant (104)
const double _kFlingVelocity = 2.0;

_BackdropState'e bir AnimationController widget'ı ekleyin, initState() işlevinde örneklendirin ve durumun dispose() işlevinde kaldırın:

  // TODO: Add AnimationController widget (104)
  late AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(milliseconds: 300),
      value: 1.0,
      vsync: this,
    );
  }

  // TODO: Add override for didUpdateWidget (104)

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  // TODO: Add functions to get and change front layer visibility (104)

AnimationController, animasyonları koordine eder ve animasyonu oynatmak, tersine çevirmek ve durdurmak için API sağlar. Şimdi hareket etmesini sağlayacak işlevlere ihtiyacımız var.

Ön katmanın görünürlüğünü belirleyen ve değiştiren işlevler ekleyin:

  // TODO: Add functions to get and change front layer visibility (104)
  bool get _frontLayerVisible {
    final AnimationStatus status = _controller.status;
    return status == AnimationStatus.completed ||
        status == AnimationStatus.forward;
  }

  void _toggleBackdropLayerVisibility() {
    _controller.fling(
        velocity: _frontLayerVisible ? -_kFlingVelocity : _kFlingVelocity);
  }

backLayer'ı ExcludeSemantics widget'ına sarın. Bu widget, arka katman görünür olmadığında arka katmanın menü öğelerini semantik ağacından çıkarır.

    return Stack(
      key: _backdropKey,
      children: <Widget>[
        // TODO: Wrap backLayer in an ExcludeSemantics widget (104)
        ExcludeSemantics(
          child: widget.backLayer,
          excluding: _frontLayerVisible,
        ),
      ...

_buildStack() işlevini, BuildContext ve BoxConstraints alacak şekilde değiştirin. Ayrıca, RelativeRectTween animasyonu alan bir PositionedTransition da ekleyin:

  // TODO: Add BuildContext and BoxConstraints parameters to _buildStack (104)
  Widget _buildStack(BuildContext context, BoxConstraints constraints) {
    const double layerTitleHeight = 48.0;
    final Size layerSize = constraints.biggest;
    final double layerTop = layerSize.height - layerTitleHeight;

    // TODO: Create a RelativeRectTween Animation (104)
    Animation<RelativeRect> layerAnimation = RelativeRectTween(
      begin: RelativeRect.fromLTRB(
          0.0, layerTop, 0.0, layerTop - layerSize.height),
      end: const RelativeRect.fromLTRB(0.0, 0.0, 0.0, 0.0),
    ).animate(_controller.view);

    return Stack(
      key: _backdropKey,
      children: <Widget>[
        // TODO: Wrap backLayer in an ExcludeSemantics widget (104)
        ExcludeSemantics(
          child: widget.backLayer,
          excluding: _frontLayerVisible,
        ),
        // TODO: Add a PositionedTransition (104)
        PositionedTransition(
          rect: layerAnimation,
          child: _FrontLayer(
            // TODO: Implement onTap property on _BackdropState (104)
            child: widget.frontLayer,
          ),
        ),
      ],
    );
  }

Son olarak, Scaffold'un gövdesi için _buildStack işlevini çağırmak yerine, oluşturucu olarak _buildStack'i kullanan bir LayoutBuilder widget'ı döndürün:

    return Scaffold(
      appBar: appBar,
      // TODO: Return a LayoutBuilder widget (104)
      body: LayoutBuilder(builder: _buildStack),
    );

Arka planın gerçek toplam yüksekliğini dahil edebilmek için LayoutBuilder'ı kullanarak ön/arka katman yığınının oluşturulmasını düzenleme zamanına kadar erteledik. LayoutBuilder, oluşturucu geri çağırması boyut kısıtlamaları sağlayan özel bir widget'tır.

build() işlevinde, uygulama çubuğundaki önde gelen menü simgesini IconButton'a dönüştürün ve düğmeye dokunulduğunda ön katmanın görünürlüğünü değiştirmek için kullanın.

      // TODO: Replace leading menu icon with IconButton (104)
      leading: IconButton(
        icon: const Icon(Icons.menu),
        onPressed: _toggleBackdropLayerVisibility,
      ),

Yeniden yükleyin ve simülasyon aracında menü düğmesine dokunun.

Android

iOS

İki hatanın bulunduğu boş Shrine menüsü

İki hatanın bulunduğu boş Shrine menüsü

Ön katman aşağı doğru animasyonla (kayarak) hareket eder. Ancak aşağı baktığınızda kırmızı bir hata ve taşma hatası olduğunu görürsünüz. Bunun nedeni, AsymmetricView'in bu animasyonla sıkıştırılıp küçülmesi ve dolayısıyla sütunlara daha az yer kalmasıdır. Sonunda, sütunlar kendilerine verilen alanla yerleştirilemez ve hata oluşur. Sütunları ListView'lerle değiştirirsek sütun boyutu animasyon sırasında aynı kalmalıdır.

Ürün sütunlarını ListView'a sarmalama

supplemental/product_columns.dart içinde, OneProductCardColumn içindeki sütunu bir ListView ile değiştirin:

class OneProductCardColumn extends StatelessWidget {
  const OneProductCardColumn({required this.product, Key? key}) : super(key: key);

  final Product product;

  @override
  Widget build(BuildContext context) {
    // TODO: Replace Column with a ListView (104)
    return ListView(
      physics: const ClampingScrollPhysics(),
      reverse: true,
      children: <Widget>[
        ConstrainedBox(
          constraints: const BoxConstraints(
            maxWidth: 550,
          ),
          child: ProductCard(
            product: product,
          ),
        ),
        const SizedBox(
          height: 40.0,
        ),

      ],
    );
  }
}

Sütun MainAxisAlignment.end içerir. Düzeni alttan başlatmak için reverse: true işaretini koyun. Değişikliği telafi etmek için çocukların sıralaması tersine çevrilir.

Yeniden yükleyin ve menü düğmesine dokunun.

Android

iOS

Bir hatanın olduğu boş Shrine menüsü

Bir hatanın olduğu boş Shrine menüsü

OneProductCardColumn'daki gri taşma uyarısı kaldırıldı. Şimdi diğerini düzeltelim.

supplemental/product_columns.dart içinde imageAspectRatio hesaplanma şeklini değiştirin ve TwoProductCardColumn içindeki sütunu bir ListView ile değiştirin:

      // TODO: Change imageAspectRatio calculation (104)
      double imageAspectRatio = heightOfImages >= 0.0
          ? constraints.biggest.width / heightOfImages
          : 49.0 / 33.0;
      // TODO: Replace Column with a ListView (104)
      return ListView(
        physics: const ClampingScrollPhysics(),
        children: <Widget>[
          Padding(
            padding: const EdgeInsetsDirectional.only(start: 28.0),
            child: top != null
                ? ProductCard(
                    imageAspectRatio: imageAspectRatio,
                    product: top!,
                  )
                : SizedBox(
                    height: heightOfCards,
                  ),
          ),
          const SizedBox(height: spacerHeight),
          Padding(
            padding: const EdgeInsetsDirectional.only(end: 28.0),
            child: ProductCard(
              imageAspectRatio: imageAspectRatio,
              product: bottom,
            ),
          ),
        ],
      );

Ayrıca imageAspectRatio için bazı güvenlik önlemleri ekledik.

Yeniden yükleyin. Ardından menü düğmesine dokunun.

Android

iOS

Boş Shrine menüsü

Boş Shrine menüsü

Artık taşma yok.

7. Arka katmana menü ekleme

Menü, dinleyicileri metin öğelerine dokunulduğunda bilgilendiren, dokunulabilir metin öğelerinin listesidir. Bu adımda, bir kategori filtreleme menüsü ekleyeceksiniz.

Menüyü ekleme

Menüyü ön katmana, etkileşimli düğmeleri ise arka katmana ekleyin.

lib/category_menu_page.dart adlı yeni bir dosya oluşturun:

import 'package:flutter/material.dart';

import 'colors.dart';
import 'model/product.dart';

class CategoryMenuPage extends StatelessWidget {
  final Category currentCategory;
  final ValueChanged<Category> onCategoryTap;
  final List<Category> _categories = Category.values;

  const CategoryMenuPage({
    Key? key,
    required this.currentCategory,
    required this.onCategoryTap,
  }) : super(key: key);

  Widget _buildCategory(Category category, BuildContext context) {
    final categoryString =
        category.toString().replaceAll('Category.', '').toUpperCase();
    final ThemeData theme = Theme.of(context);

    return GestureDetector(
      onTap: () => onCategoryTap(category),
      child: category == currentCategory
        ? Column(
            children: <Widget>[
              const SizedBox(height: 16.0),
              Text(
                categoryString,
                style: theme.textTheme.bodyLarge,
                textAlign: TextAlign.center,
              ),
              const SizedBox(height: 14.0),
              Container(
                width: 70.0,
                height: 2.0,
                color: kShrinePink400,
              ),
            ],
          )
      : Padding(
        padding: const EdgeInsets.symmetric(vertical: 16.0),
        child: Text(
          categoryString,
          style: theme.textTheme.bodyLarge!.copyWith(
              color: kShrineBrown900.withAlpha(153)
            ),
          textAlign: TextAlign.center,
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        padding: const EdgeInsets.only(top: 40.0),
        color: kShrinePink100,
        child: ListView(
          children: _categories
            .map((Category c) => _buildCategory(c, context))
            .toList()),
      ),
    );
  }
}

Bu, alt öğeleri kategori adları olan bir Column'u sarmalayan bir GestureDetector'dır. Seçilen kategori, altı çizili olarak gösterilir.

app.dart içinde ShrineApp widget'ını durumsuzdan durumluya dönüştürün.

  1. Şunu vurgula: ShrineApp.
  2. IDE'nize göre kod işlemlerini gösterin:
  3. Android Studio: ⌥Enter (macOS) veya alt + enter tuşlarına basın.
  4. VS Code: ⌘ (macOS) veya Ctrl+. tuşlarına basın.
  5. "Convert to StatefulWidget" (StatefulWidget'a dönüştür) seçeneğini belirleyin.
  6. ShrineAppState sınıfını özel (_ShrineAppState) olarak değiştirin. ShrineAppState'i sağ tıklayın ve
  7. Android Studio: Refactor > Rename'i (Yeniden düzenle > Yeniden adlandır) seçin.
  8. VS Code: Sembolü Yeniden Adlandır'ı seçin.
  9. Sınıfı özel yapmak için _ShrineAppState girin.

app.dart içinde, seçilen Kategori için _ShrineAppState'e bir değişken ve dokunulduğunda geri çağırma ekleyin:

class _ShrineAppState extends State<ShrineApp> {
  Category _currentCategory = Category.all;

  void _onCategoryTap(Category category) {
    setState(() {
      _currentCategory = category;
    });
  }

Ardından arka katmanı CategoryMenuPage olarak değiştirin.

app.dart içinde CategoryMenuPage'i içe aktarın:

import 'backdrop.dart';
import 'category_menu_page.dart';
import 'colors.dart';
import 'home.dart';
import 'login.dart';
import 'model/product.dart';
import 'supplemental/cut_corners_border.dart';

build() işlevinde, backLayer alanını CategoryMenuPage olarak, currentCategory alanını ise örnek değişkeni alacak şekilde değiştirin.

'/': (BuildContext context) => Backdrop(
              // TODO: Make currentCategory field take _currentCategory (104)
              currentCategory: _currentCategory,
              // TODO: Pass _currentCategory for frontLayer (104)
              frontLayer: HomePage(),
              // TODO: Change backLayer field value to CategoryMenuPage (104)
              backLayer: CategoryMenuPage(
                currentCategory: _currentCategory,
                onCategoryTap: _onCategoryTap,
              ),
              frontTitle: const Text('SHRINE'),
              backTitle: const Text('MENU'),
            ),

Yeniden yükleyin ve Menü düğmesine dokunun.

Android

iOS

4 kategorili tapınak menüsü

4 kategorili tapınak menüsü

Bir menü seçeneğine dokunduğunuzda henüz hiçbir şey olmaz. Gelin bu sorunu çözelim.

home.dart içinde Kategori için bir değişken ekleyin ve bunu AsymmetricView'a iletin.

import 'package:flutter/material.dart';

import 'model/product.dart';
import 'model/products_repository.dart';
import 'supplemental/asymmetric_view.dart';

class HomePage extends StatelessWidget {
  // TODO: Add a variable for Category (104)
  final Category category;

  const HomePage({this.category = Category.all, Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    // TODO: Pass Category variable to AsymmetricView (104)
    return AsymmetricView(
      products: ProductsRepository.loadProducts(category),
    );
  }
}

app.dart içinde frontLayer için _currentCategory sınavını geçin.

// TODO: Pass _currentCategory for frontLayer (104)
frontLayer: HomePage(category: _currentCategory),

Yeniden yükleyin. Simülatördeki menü düğmesine dokunun ve bir Kategori seçin.

Android

iOS

Filtrelenmiş ürünlerin yer aldığı marka sayfası

Filtrelenmiş ürünlerin yer aldığı marka sayfası

Filtrelenirler.

Menü seçimi yapıldıktan sonra ön katmanı kapatma

backdrop.dart içinde, _BackdropState'teki didUpdateWidget() (widget yapılandırması her değiştiğinde çağrılır) işlevi için geçersiz kılma ekleyin:

  // TODO: Add override for didUpdateWidget() (104)
  @override
  void didUpdateWidget(Backdrop old) {
    super.didUpdateWidget(old);

    if (widget.currentCategory != old.currentCategory) {
      _toggleBackdropLayerVisibility();
    } else if (!_frontLayerVisible) {
      _controller.fling(velocity: _kFlingVelocity);
    }
  }

Anında yeniden yüklemeyi tetiklemek için projenizi kaydedin. Menü simgesine dokunup bir kategori seçin. Menü otomatik olarak kapanır ve seçilen öğelerin kategorisini görürsünüz. Şimdi bu işlevi ön katmana da ekleyeceksiniz.

Ön katmanı açma/kapatma

backdrop.dart içinde, arka plan katmanına dokunulduğunda geri çağırma işlevi ekleyin:

class _FrontLayer extends StatelessWidget {
  // TODO: Add on-tap callback (104)
  const _FrontLayer({
    Key? key,
    this.onTap, // New code
    required this.child,
  }) : super(key: key);
 
  final VoidCallback? onTap; // New code
  final Widget child;

Ardından, _FrontLayer'ın alt öğesine bir GestureDetector ekleyin: Column's children:.

      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: <Widget>[
          // TODO: Add a GestureDetector (104)
          GestureDetector(
            behavior: HitTestBehavior.opaque,
            onTap: onTap,
            child: Container(
              height: 40.0,
              alignment: AlignmentDirectional.centerStart,
            ),
          ),
          Expanded(
            child: child,
          ),
        ],
      ),

Ardından, _buildStack() işlevindeki _BackdropState üzerinde yeni onTap özelliğini uygulayın:

          PositionedTransition(
            rect: layerAnimation,
            child: _FrontLayer(
              // TODO: Implement onTap property on _BackdropState (104)
              onTap: _toggleBackdropLayerVisibility,
              child: widget.frontLayer,
            ),
          ),

Yeniden yükleyin ve ön katmanın üst kısmına dokunun. Ön katmanın üst kısmına her dokunduğunuzda katman açılıp kapanmalıdır.

8. Markalı simge ekleme

Markalı simgeler, tanıdık simgeleri de kapsar. Açıklama simgesini özelleştirelim ve benzersiz bir marka görünümü için başlığımızla birleştirelim.

Menü düğmesi simgesini değiştirme

Android

iOS

Markalı simge içeren Shrine ürün sayfası

Markalı simge içeren Shrine ürün sayfası

backdrop.dart içinde yeni bir _BackdropTitle sınıfı oluşturun.

// TODO: Add _BackdropTitle class (104)
class _BackdropTitle extends AnimatedWidget {
  final void Function() onPress;
  final Widget frontTitle;
  final Widget backTitle;

  const _BackdropTitle({
    Key? key,
    required Animation<double> listenable,
    required this.onPress,
    required this.frontTitle,
    required this.backTitle,
  }) : _listenable = listenable, 
       super(key: key, listenable: listenable);

  final Animation<double> _listenable;

  @override
  Widget build(BuildContext context) {
    final Animation<double> animation = _listenable;

    return DefaultTextStyle(
      style: Theme.of(context).textTheme.titleLarge!,
      softWrap: false,
      overflow: TextOverflow.ellipsis,
      child: Row(children: <Widget>[
        // branded icon
        SizedBox(
          width: 72.0,
          child: IconButton(
            padding: const EdgeInsets.only(right: 8.0),
            onPressed: this.onPress,
            icon: Stack(children: <Widget>[
              Opacity(
                opacity: animation.value,
                child: const ImageIcon(AssetImage('assets/slanted_menu.png')),
              ),
              FractionalTranslation(
                translation: Tween<Offset>(
                  begin: Offset.zero,
                  end: const Offset(1.0, 0.0),
                ).evaluate(animation),
                child: const ImageIcon(AssetImage('assets/diamond.png')),
              )]),
          ),
        ),
        // Here, we do a custom cross fade between backTitle and frontTitle.
        // This makes a smooth animation between the two texts.
        Stack(
          children: <Widget>[
            Opacity(
              opacity: CurvedAnimation(
                parent: ReverseAnimation(animation),
                curve: const Interval(0.5, 1.0),
              ).value,
              child: FractionalTranslation(
                translation: Tween<Offset>(
                  begin: Offset.zero,
                  end: const Offset(0.5, 0.0),
                ).evaluate(animation),
                child: backTitle,
              ),
            ),
            Opacity(
              opacity: CurvedAnimation(
                parent: animation,
                curve: const Interval(0.5, 1.0),
              ).value,
              child: FractionalTranslation(
                translation: Tween<Offset>(
                  begin: const Offset(-0.25, 0.0),
                  end: Offset.zero,
                ).evaluate(animation),
                child: frontTitle,
              ),
            ),
          ],
        )
      ]),
    );
  }
}

_BackdropTitle, AppBar widget'ının title parametresi için düz Text widget'ının yerini alacak özel bir widget'tır. Animasyonlu bir menü simgesi ve ön ile arka başlıklar arasında animasyonlu geçişler içerir. Animasyonlu menü simgesi yeni bir öğe kullanacak. Yeni slanted_menu.png ile ilgili referans, pubspec.yaml'ye eklenmelidir.

assets:
    - assets/diamond.png
    # TODO: Add slanted menu asset (104)
    - assets/slanted_menu.png
    - packages/shrine_images/0-0.jpg

AppBar oluşturucuda leading mülkünü kaldırın. Özel markalı simgenin orijinal leading widget'ının yerinde oluşturulması için kaldırma işlemi gereklidir. Markalı simge için animasyon listenable ve onPress işleyici, _BackdropTitle öğesine iletilir. Ayrıca, frontTitle ve backTitle da aktarılır. Böylece, arka plan başlığında oluşturulabilirler. AppBar parametresinin title parametresi aşağıdaki gibi görünmelidir:

// TODO: Create title with _BackdropTitle parameter (104)
title: _BackdropTitle(
  listenable: _controller.view,
  onPress: _toggleBackdropLayerVisibility,
  frontTitle: widget.frontTitle,
  backTitle: widget.backTitle,
),

Markalı simge, _BackdropTitle. içinde oluşturulur. Animasyonlu simgelerden oluşan bir Stack içerir: eğik bir menü ve IconButton içine sarılmış bir elmas. Elmas, basılabilmesi için IconButton içine sarılmıştır. Ardından, yatay simge hareketine yer açmak için IconButton, SizedBox içine yerleştirilir.

Flutter'ın "her şey bir widget'tır" mimarisi, varsayılan AppBar düzeninin tamamen yeni bir özel AppBar widget oluşturmak zorunda kalmadan değiştirilmesine olanak tanır. Başlangıçta Text widget'ı olan title parametresi, daha karmaşık bir _BackdropTitle ile değiştirilebilir. _BackdropTitle, özel simgeyi de içerdiğinden leading özelliğinin yerini alır ve artık atlanabilir. Bu basit widget değiştirme işlemi, diğer parametrelerde (ör. kendi başlarına çalışmaya devam eden işlem simgeleri) herhangi bir değişiklik yapılmadan gerçekleştirilir.

Oturum açma ekranına geri dönme kısayolu ekleme

backdrop.dart,Uygulama çubuğundaki sondaki iki simgeden giriş ekranına geri dönmek için kısayol ekleyin: Simgelerin anlam etiketlerini yeni amaçlarını yansıtacak şekilde değiştirin.

        // TODO: Add shortcut to login screen from trailing icons (104)
        IconButton(
          icon: const Icon(
            Icons.search,
            semanticLabel: 'login', // New code
          ),
          onPressed: () {
            // TODO: Add open login (104)
            Navigator.push(
              context,
              MaterialPageRoute(
                builder: (BuildContext context) => LoginPage()),
            );
          },
        ),
        IconButton(
          icon: const Icon(
            Icons.tune,
            semanticLabel: 'login', // New code
          ),
          onPressed: () {
            // TODO: Add open login (104)
            Navigator.push(
              context,
              MaterialPageRoute(
                builder: (BuildContext context) => LoginPage()),
            );
          },
        ),

Yeniden yüklemeyi denerseniz hata alırsınız. Hatayı düzeltmek için login.dart içe aktarın:

import 'login.dart';

Uygulamayı yeniden yükleyin ve giriş ekranına dönmek için arama veya ayarlama düğmelerine dokunun.

9. Tebrikler!

Bu dört kod laboratuvarı boyunca, marka kişiliğini ve stilini yansıtan benzersiz ve zarif kullanıcı deneyimleri oluşturmak için Material Components'ı nasıl kullanacağınızı öğrendiniz.

Sonraki adımlar

Bu codelab (MDC-104), codelab dizisini tamamlar. Material Components widget kataloğunu ziyaret ederek Material Flutter'da daha da fazla bileşeni keşfedebilirsiniz.

Daha da ileri gitmek için markalı simgeyi, arka plan görünür hale geldiğinde iki simge arasında animasyon oluşturan bir AnimatedIcon ile değiştirmeyi deneyin.

İlgi alanlarınıza göre deneyebileceğiniz başka birçok Flutter codelab'i vardır. İlginizi çekebilecek başka bir Material'a özel codelab'imiz var: Flutter için Material Motion ile Güzel Geçişler Oluşturma.

Bu codelab'i makul bir süre ve çabayla tamamlayabildim.

Kesinlikle katılıyorum Katılıyorum Ne katılıyorum ne katılmıyorum Katılmıyorum Kesinlikle katılmıyorum

Gelecekte Material Components'ı kullanmaya devam etmek istiyorum

Kesinlikle katılıyorum Katılıyorum Ne katılıyorum ne katılmıyorum Katılmıyorum Kesinlikle katılmıyorum