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

1. Giriş

logo_components_color_2x_web_96dp.png

Materyal Bileşenleri (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, onlarca güzel ve işlevsel kullanıcı arayüzü bileşenine sahiptir ve Android, iOS, web ve Flutter'da kullanılabilir.material.io/develop

MDC-103 codelab'inde, Materyal Bileşenlerinin (MDC) rengini, yüksekliğini, tipografisini ve şeklini özelleştirip uygulamanıza stil kazandırdınız.

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

Materyal Tasarım yönergeleri, bileşenleri bir tasarımcının bakış açısından açıklar. Bu dokümanlar, platformlar genelinde kullanılabilen çok çeşitli temel işlevleri ve her bileşeni oluşturan anatomik öğeleri açıklar. Örneğin, 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.

Oluşturacaklarınız

Bu codelab'de Shrine uygulamasındaki kullanıcı arayüzünü, "arka plan" adı verilen iki düzeyli bir sunumla değiştireceksiniz. Arka planda, asimetrik ızgaradaki ü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ızlar)

Yapay Zeka

iOS

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

Üstte uygulama çubuğu ve ürünlerle dolu asimetrik, yatay kaydırılabilir bir ızgara bulunan pembe ve kahverengi temalı e-ticaret uygulaması

menü listeleme 4 kategori

menü listeleme 4 kategori

Bu codelab'deki Material Flutter bileşenleri ve alt sistemleri

  • Şekil

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

Acemi Orta Uzman

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

Bu laboratuvarı tamamlamak için iki yazılıma ihtiyacınız vardır: Flutter SDK'sı ve düzenleyici.

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

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

3. codelab başlangıç uygulamasını indirme

MDC-103'ten mi devam ediyorsunuz?

MDC-103'ü tamamladıysanız kodunuz bu codelab için hazır olmalıdır. Arka plan menüsünü ekleme adımına atlayın.

Sıfırdan mı başlayacaksınız?

Başlatıcı uygulama material-components-flutter-codelabs-104-starter_and_103-complete/mdc_100_series dizininde bulunur.

...veya GitHub'dan kopyalayın

Bu kod laboratuvarını GitHub'dan kopyalamak 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çın ve uygulamayı çalıştırın

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

Başarıyla gerçekleştirildi. Cihazınızdaki önceki codelab'lerde bulunan Tapınak giriş sayfasını göreceksiniz.

Yapay Zeka

iOS

Shrine giriş sayfası

Tapınak giriş sayfası

4. Arka plan menüsü ekleme

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

Ana ekran uygulama çubuğunu kaldırma

Ana Sayfa 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 AnaSayfa yalnızca AsimetrikGörünüm'ü 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 öğelerini içeren Arka Plan adlı bir widget oluşturun.

backLayer, listeyi filtrelemek için bir kategori seçmenize olanak tanıyan bir menü (currentCategory) içerir. Menü seçiminin değişmesini istediğimiz için Arka Plan'ı durum bilgili bir widget haline getireceğiz.

/lib hedefine backdrop.dart adlı 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 mülkleri required ile işaretlediğimizi unutmayın. Bu, oluşturucuda varsayılan değeri olmayan ve null olamayacak ve dolayısıyla unutulmaması gereken özellikler için en iyi uygulamadı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, HomePage'in eskiden olduğu gibi uygulama çubuğu içeren bir İskele döndürür. Ancak, İskele'nin gövdesi bir Yığındır. Bir Grubun alt öğeleri çakışabilir. Her bir alt öğenin boyutu ve konumu, Yığının üst öğesine göre belirtilir.

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

app.dart'te 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, frontLayer değeri HomePage olan bir Backdrop döndürerek / rotasını 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ü fark edeceksiniz:

Yapay Zeka

iOS

Pembe arka planlı tapınak ürün sayfası

Pembe arka planlı tapınak ürün sayfası

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

Grubun gerçekten bir Ana Sayfanın arkasında bir Kapsayıcı olduğunu doğrulamak için Flutter Denetleyicisi'ni kullanabilirsiniz. Bu, aşağıdakine benzer şekilde görünmelidir:

92ed338a15a074bd.png

Artık hem katmanların tasarımını hem de 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 özelleştirme türünü şekil olarak ifade eder. Malzeme yüzeylerinin rastgele şekilleri olabilir. Şekiller, yüzeylere vurgu ve stil ekler ve markayı ifade etmek için kullanılabilir. Sıradan dikdörtgen şekiller, kavisli veya açılı köşeler ve kenarlarla ve istenilen 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, uygulama genelinde uygulanan şekillerin yaygın bir şekilde kullanılmasıdır. Örneğin, logonun şekli, kendilerine şekil uygulanmış giriş sayfası öğelerinde yansıtılıyor. Bu adımda, ön katmana sol üst köşede açılı bir kesim uygulayacaksınız.

backdrop.dart alanına _ÖnKatman adlı yeni bir 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ı bir _FrontLayer içine sarı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.

Yapay Zeka

iOS

Özel şekilli tapınak ürün sayfası

Özel şekilli tapınak ürün sayfası

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

Uygulama çubuğu rengini değiştirme

app.dart dosyasında _buildShrineTheme() işlevini aşağıdaki şekilde 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österilir.

Yapay Zeka

iOS

Renkli uygulama çubuğu bulunan tapınak ürün sayfası

Renkli uygulama çubuğu bulunan tapınak ürün sayfası

Bu değişiklik sayesinde kullanıcılar, öndeki 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 etkileyici bir üslup, incelikli ve minimal olabileceği gibi, arada herhangi bir yerde de olabilir. Ancak kullandığınız hareket türünün duruma uygun olması gerektiğini unutmayın. Yinelenen ve düzenli olarak yapılan işlemlere uygulanan hareketler, kullanıcının dikkatini dağıtmaması veya düzenli olarak çok fazla zaman almaması için küçük ve belirsiz olmalıdır. Ancak kullanıcının bir uygulamayı ilk kez açtığı gibi daha dikkat çekici olabilecek uygun durumlar vardır ve bazı animasyonlar kullanıcıya uygulamanızı nasıl kullanacağı konusunda bilgi verebilir.

Menü düğmesine gösterme hareketi ekleme

backdrop.dart öğesinin üst kısmına, herhangi bir sınıfın veya işlevin kapsamı dışında, animasyonumuzun sahip olmasını istediğimiz hızı temsil edecek bir sabit değer ekleyin:

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

_BackdropState için bir AnimationController widget'ı ekleyin, initState() işlevinde bu widget'ı ö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 için koordinasyon sağlar ve animasyonu oynatma, tersine çevirme ve durdurma API'sini sunar. Şimdi de 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);
  }

BackKatman'ı bir ExcludeSemantics widget'ta sarmalayın. Bu widget, arka katman görünür olmadığındabackKatman'ın menü öğelerini anlam ağacından hariç tutar.

    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 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, İskele'nin gövdesi için _buildStack işlevini çağırmak yerine, derleyici olarak _buildStack adlı 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 edebilmemiz için ön/arka katman yığınının oluşturulmasını LayoutBuilder'ı kullanarak düzen zamanına kadar erteledik. LayoutBuilder, oluşturucu geri çağırmasının boyut kısıtlamaları sağladığı özel bir widget'tır.

build() işlevinde, uygulama çubuğundaki ana menü simgesini bir 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ülatörde menü düğmesine dokunun.

Yapay Zeka

iOS

İki hata içeren boş tapınak menüsü

İki hata içeren boş tapınak menüsü

Ön katman aşağı doğru hareket eder (kaydırılır). Ancak aşağıya bakarsanız kırmızı bir hata ve bir taşma hatası var. Bunun nedeni, AsymmetricView'un sıkıştırılması ve bu animasyonla küçülmesi nedeniyle Sütunlara daha az yer açılmasıdır. Sonunda sütunlar, verilen alanla kendilerini düzenleyemez ve hata oluşur. Sütunları ListViews ile değiştirirsek, sütun boyutu canlandırıldığı sırada kalmalıdır.

ListView'da ürün sütunlarını sarmalama

supplemental/product_columns.dart dosyasında, 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çeriyor. Sayfayı alttan başlatmak için reverse: true simgesini işaretleyin. Alt yayıncının sıralaması, değişikliği telafi etmek için tersine çevrilir.

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

Yapay Zeka

iOS

Bir hatayla boş tapınak menüsü

Bir hatayla boş tapınak menüsü

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

supplemental/product_columns.dart içinde imageAspectRatio değerinin hesaplanmasını 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çine bazı güvenlik özellikleri de ekledik.

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

Yapay Zeka

iOS

Boş tapınak menüsü

Boş Tapınak menüsü

Artık taşma yok.

7. Arka katmana menü ekleme

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

Menüyü ekleme

Menüyü ön katmana ve etkileşimli düğmeleri 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()),
      ),
    );
  }
}

Alt öğeleri kategori adları olan bir Sütunu sarmalayan bir GestureDetector'dür. Seçilen kategori altı çizili olarak gösterilir.

app.dart içinde ShrineApp widget'ını durum bilgisizden durum bilgiliye dönüştürün.

  1. Şunu vurgula: ShrineApp.
  2. IDE'nize bağlı olarak 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çin.
  6. ShrineAppState sınıfını gizli (_ShrineAppState) olarak değiştirin. ShrineAppState'i sağ tıklayın ve
  7. Android Studio: Yeniden Düzenleme > Yeniden Adlandır'ı seçin
  8. VS Kodu: Sembolü Yeniden Adlandır'ı seçin
  9. Sınıfı gizli hale getirmek için _ShrineAppState değerini girin.

app.dart içinde, seçili kategori için _ShrineAppState değişkeni ve bu kategoriye dokunulduğunda geri çağırma işlevi ekleyin:

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

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

Sonra, arka katmanı bir CategoryMenüPage olarak değiştirin.

app.dart ürününde CategoryMenüPage'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, örnek değişkenini almak için backKatman alanını CategoryMenüPage ve currentCategory alanını 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.

Yapay Zeka

iOS

4 kategori içeren tapınak menüsü

4 kategorili tapınak menüsü

Bir menü seçeneğine dokunursanız henüz hiçbir şey olmuyor... Gelin bu sorunu çözelim.

home.dart ürününde 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 doğru.

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

Yeniden yükleyin. Simülatördeki menü düğmesine dokunup bir kategori seçin.

Yapay Zeka

iOS

Tapınak filtrelenmiş ürün sayfası

Tapınak için filtrelenmiş ürün sayfası

Filtrelendi.

Menü seçiminin ardından ön katmanı kapatma

backdrop.dart içinde, _BackdropState işlevindeki didUpdateWidget() (widget yapılandırması değiştiğinde çağrılır) işlevi için bir 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);
    }
  }

Çalışır durumda bir 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şlevselliği ön katmana da ekleyeceksiniz.

Ön katmanı aç/kapat

backdrop.dart içinde, arka plan katmanına dokunma üzerine 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 çocuğuna bir GestureDetector ekleyin: Sütun'un çocukları:.

      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 özelliğine yeni onTap mülkünü 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ı ikonlar, bilinen simgeler için de geçerlidir. Benzersiz ve markalı bir görünüm için gösterme simgesini özelleştirip başlığımızla birleştirelim.

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

Yapay Zeka

iOS

Marka simgesi içeren tapınak ürün sayfası

Marka simgesi içeren tapınak ürün sayfası

backdrop.dart uygulamasında 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ü simgesine ve ön ve arka başlıklar arasında animasyonlu geçişlere sahip. Animasyonlu menü simgesi yeni bir öğe kullanacak. Yeni slanted_menu.png referansı, pubspec.yaml öğesine 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 özelliğini kaldırın. Özel markalı simgesinin orijinal leading widget'ının yerine oluşturulması için kaldırma işleminin yapılması gerekir. Marka simgesi için listenable animasyonu ve onPress işleyicisi _BackdropTitle'ye iletilir. frontTitle ve backTitle, arka plan başlığında oluşturulabilmeleri için de iletilir. AppBar öğesinin title parametresi şöyle 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. Stack animasyonlu simge içerir: Eğik bir menü ve basılması için IconButton içine yerleştirilmiş bir elmas. Daha sonra IconButton, yatay simge hareketine yer açmak için bir SizedBox içine sarmalanır.

Flutter'ın "her şey bir widget" mimarisi, tamamen yeni bir özel AppBar widget oluşturmak zorunda kalmadan varsayılan AppBar'ın düzeninin 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. Bu özellik artık atlanabilir. Bu basit widget değişimi, işlem simgeleri gibi diğer parametrelerin hiçbiri değiştirilmeden gerçekleştirilir. İşlem simgeleri kendi kendilerine çalışmaya devam eder.

Giriş ekranına geri dönen bir kısayol ekleme

backdrop.dart, ürününde, uygulama çubuğunun sonundaki iki simgeden giriş ekranına kısayol ekleyin: Simgelerin anlamsal 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 dosyasını içe aktarın:

import 'login.dart';

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

9. Tebrikler!

Bu dört codelab süresince marka kişiliğini ve stilini yansıtan benzersiz, şık kullanıcı deneyimleri oluşturmak için Materyal Bileşenleri nasıl kullanacağınızı öğrendiniz.

Sonraki adımlar

Bu codelab'de (MDC-104) kod laboratuvarları dizisi tamamlanır. Malzeme Bileşenleri widget kataloğunu ziyaret ederek Material Flutter'da daha da fazla bileşeni keşfedebilirsiniz.

Daha iddialı bir hedef için markalı simgeyi, arka plan görünür hale getirildiğinde iki simge arasında animasyonlu geçiş yapan bir AnimatedIcon ile değiştirmeyi deneyin.

İlgi alanlarınıza göre denemeniz için birçok başka Flutter codelab de mevcuttur. İlginizi çekebilecek başka bir Materyal'e özel codelab'imiz de var: Flutter için Materyal Hareket ile Güzel Geçişler Oluşturma.

Bu codelab'i makul bir zaman 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'i kullanmaya devam etmek istiyorum

Kesinlikle katılıyorum Katılıyorum Nötr Katılmıyorum Kesinlikle katılmıyorum