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'da mühendislerden ve kullanıcı deneyimi tasarımcılarından oluşan bir ekip tarafından oluşturulan MDC, onlarca güzel ve işlevsel kullanıcı arayüzü bileşeni içerir. Ayrıca Android, iOS, web ve Flutter.material.io/develop'da kullanılabilir.

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. Düğme, kullanıcının bir işlem yapması için yalnızca bir yol olmanın ötesinde, kullanıcıya bir şeyin etkileşimli olduğunu ve dokunulduğunda veya tıklandığında bir şey olacağını anlamasını sağlayan şekil, boyut ve rengin görsel bir ifadesidir.

Materyal Tasarım yönergeleri, bileşenleri bir tasarımcının bakış açısından açıklar. Platformlarda kullanılabilen çok çeşitli temel işlevleri ve her bileşeni oluşturan anatomik öğeleri açıklarlar. Örneğin, bir arka plan, bir arka katmanı ve içeriğini, ön katmanı ve bunun 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 ızgarada gösterilen ürünleri filtrelemek için kullanılan seçilebilir kategorileri listeleyen bir menü bulunur. Bu codelab'de şunları kullanacaksınız:

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

Android

iOS

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

Üst uygulama çubuğu ve ürünlerle dolu asimetrik, yatay olarak kaydırılabilir ızgarası içeren pembe-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 ile ilgili deneyim düzeyinizi nasıl değerlendirirsiniz?

Acemi Orta Yeterli

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ülatörü (Xcode araçlarının yüklenmesini gerektirir).
  • Android Emülatör (Android Studio'da kurulum gerektirir).
  • Tarayıcı (hata ayıklama için Chrome gereklidir).
  • Windows, Linux veya macOS masaüstü uygulaması olarak Uygulamayı dağıtmayı planladığınız platformda gerçekleştirmeniz gerekir. Bu nedenle, bir Windows masaüstü uygulaması geliştirmek istiyorsanız uygun derleme zincirine erişmek için Windows'da geliştirme yapmanız gerekir. İşletim sistemine özgü gereksinimler docs.flutter.dev/desktop sayfasında ayrıntılı olarak açıklanmıştır.

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

MDC-103'ten mi devam ediyorsunuz?

MDC-103 kursunu tamamladıysanız kodunuz bu codelab için hazır olmalıdır. Şu adıma atlayın: Arka plan menüsünü ekleme.

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

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

...veya GitHub'dan klonlayın

Bu codelab'i GitHub'dan klonlamak için şu 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. "Uygulamayı çalıştırma" talimatlarını izleyin Başlarken: Seçtiğiniz düzenleyici için uygulamayı test edin.

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

Android

iOS

Tapınak giriş sayfası

Tapınak giriş sayfası

4. Arka plan menüsü ekleme

Diğer tüm içerik 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 uygulaması çubuğunu kaldırma

HomePage 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 öğesini içerecektir.

home.dart içinde, build() işlevini yalnızca bir 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 adında bir widget oluşturun.

backLayer, listeyi filtrelemek için kategori seçmenize olanak tanıyan bir menü (currentCategory) içerir. Menü seçiminin devam etmesini istediğimizden 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)

Bazı tesisleri required olarak işaretlediğimize dikkat edin. 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ı 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 olan 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, Grubun üst öğesine göre belirtilir.

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

app.dart ürününde backdrop.dart ve model/product.dart verilerini 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:

Android

iOS

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

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

backKatmanlar, pembe alanı önKatman 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. Şuna benzer olmalıdır:

92ed338a15a074bd.png

Artık her iki katmanı da ayarlayabilirsiniz. tasarım ve içerik.

5. Şekil ekleyin

Bu adımda, sol üst köşeye bir kesim eklemek için ön katmanın stilini belirleyeceksiniz.

Materyal Tasarım, bu tür özelleştirmeyi şekil olarak ifade eder. Malzeme yüzeylerinin rastgele şekilleri olabilir. Şekiller yüzeylere vurgu ve stil katar ve markayı göstermek 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

Eğik Shrine logosu, Shrine uygulamasının şekil hikayesine ilham verdi. Şekil hikayesi, bir 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, sol üst köşede açılı bir kesimle ön katmanın stilini belirleyeceksiniz.

backdrop.dart uygulamasında 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ı bir _Frontlayer içine yerleştirin:

  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 tapınak ürün sayfası

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

Mabet'in birincil yüzeyine özel bir şekil verdik. Bununla birlikte, bunun uygulama çubuğuna görsel olarak bağlanmasını istiyoruz.

Uygulama çubuğu rengini değiştirme

app.dart ürününde _buildShrineTheme() işlevini şu ş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 artık görünecektir.

Android

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 nedeniyle, kullanıcılar ön beyaz katmanın hemen arkasında bir şeyler olduğunu görebilir. Kullanıcıların arka planın arka katmanını görebilmesi için hareket ekleyelim.

6. Hareket efekti ekleyin

Hareket, uygulamanıza hayat vermenin bir yoludur. Büyük ve dramatik, incelikli ve minimal olabileceği gibi, ikisi de arasında herhangi bir yerde olabilir. Ancak kullandığınız hareket türünün duruma uygun olması gerektiğini unutmayın. Tekrarlanan düzenli eylemlere uygulanan hareket, kullanıcının dikkatini dağıtmaması veya düzenli olarak çok fazla zaman gerektirmemesi için küçük ve incelikli olmalıdır. Ancak uygun durumlar (ör. bir kullanıcının uygulamayı ilk kez açtığı zaman) daha dikkat çekici olabilir ve bazı animasyonlar kullanıcıyı uygulamanızı nasıl kullanacağı konusunda bilgilendirmeye yardımcı olabilir.

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ı koordine eder ve animasyonu oynatmak, tersine çevirmek ve durdurmak için size API sunar. Şimdi onu hareket ettiren 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,
        ),
      ...

BuildContext ve BoxConstraints alması için _buildStack() işlevini değiştirin. Ayrıca, ComparisonRectTween 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 edebilmek için LayoutBuilder'ı kullanarak ön/arka katman yığınının oluşturulmasını düzen 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 ana menü simgesini bir iconButton'a dönüştürün ve düğmeye dokunduğunuzda ön katmanın görünürlüğünü açıp kapatmak için bunu 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.

Android

iOS

İki hata içeren boş Tapına menüsü

İki hata içeren boş Tapına menüsü

Ön katman animasyon oluşturur (aşağı 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. Sonuç olarak, sütunlar verilen alanla yerleşemez 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 içinde, OneProductCardColumn konumundaki 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. Düzeni alttan başlatmak için reverse: true işaretini 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.

Android

iOS

Bir hatayla boş tapınak menüsü

Bir hatayla boş tapınak menüsü

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

supplemental/product_columns.dart ürününde imageAspectRatio metriğinin hesaplanma yöntemini 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. Sonra menü düğmesine dokun.

Android

iOS

Boş tapınak menüsü

Boş tapınak menüsü

Artık taşma yok.

7. Arka katmana bir menü ekleyin

Menü, metin öğelerine dokunulduğunda dinleyicileri bilgilendiren, dokunulabilir metin öğelerinin bir listesidir. Bu adımda bir 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. Altı çizili, seçilen kategoriyi belirtmek için kullanılır.

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 Kod: ⌘ tuşuna basın. (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
  8. VS Kodu: Sembolü Yeniden Adlandır'ı seçin
  9. Sınıfı gizli yapmak için _ShrineAppState yazın.

app.dart içinde, seçilen kategori için _ShrineAppState öğesine bir değişken ve dokunulduğunda bir geri çağırma 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 içinde, 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 backKatmanlar alanını CategoryMenüPage, currentCategory alanını da 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 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örde menü düğmesine dokunup bir Kategori seçin.

Android

iOS

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

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

Filtrelenmişler.

Menü seçiminden sonra ön katmanı kapatma

backdrop.dart ürününde, _BackdropState işlevinde didUpdateWidget() işlevi için bir geçersiz kılma (Widget yapılandırması her değiştiğinde çağrılır) 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 öğe kategorisinin seçili olduğunu görürsünüz. Şimdi bu işlevselliği ön katmana da ekleyeceksiniz.

Ön katmanı aç/kapat

backdrop.dart uygulamasında, arka plan katmanına dokunarak bir geri çağırma 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 alt öğesine: Sütunun alt öğelerine bir HareketDetector ekleyin:.

      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,
          ),
        ],
      ),

Daha sonra, yeni onTap özelliğini _buildStack() işlevindeki _BackdropState üzerinde 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 ekleyin

Markalı ikonlar, bilinen simgeleri de kapsıyor. Markalı, benzersiz bir görünüm elde etmek için açıklama simgesini özel yapıp başlığımızla birleştirelim.

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

Android

iOS

Markalı simgenin yer aldığı tapınak ürün sayfası

Markalı simgenin yer aldığı 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. Orijinal leading widget'ının yerine özel markalı simgenin oluşturulması için kaldırma işlemi gerekir. Markalı simge için listenable animasyonu ve onPress işleyicisi _BackdropTitle öğesine iletilir. Arka plan başlığında oluşturulabilmeleri için frontTitle ve backTitle de iletilir. AppBar için 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. biçiminde oluşturulur. Animasyonlu simgelerden oluşan Stack içerir: eğimli bir menü ve basılabilmesi 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'tır" mimari, tamamen yeni bir özel AppBar widget'ı oluşturmaya gerek kalmadan varsayılan AppBar 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şikliği, kendiliğinden çalışmaya devam eden işlem simgeleri gibi diğer parametreler değiştirilmeden gerçekleştirilir.

Giriş ekranına bir kısayol ekleyin

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 ayarlama düğmelerine dokunun.

9. Tebrikler!

Bu dört codelab'de, 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 zorlayıcı bir hedef için, arka plan görünür hale geldiğinde markalı simgeyi iki simge arasında hareket eden bir AnimatedIcon ile değiştirmeyi deneyin.

İlgi alanlarınıza göre deneyebileceğiniz başka birçok Flutter codelab'i (daha fazla kod laboratuvarı) mevcut. İlginizi çekebilecek başka bir Malzemeye özel codelab'imiz daha var: Building Beautiful Transitions with Material Motion for Flutter.

Bu codelab'i makul bir zaman ve çabayla tamamlayabildim

Kesinlikle katılıyorum Katılıyorum Ne memnunum ne değilim Katılmıyorum Kesinlikle katılmıyorum

Gelecekte Materyal Bileşenleri kullanmaya devam etmek istiyorum

Kesinlikle katılıyorum Katılıyorum Ne memnunum ne değilim Katılmıyorum Kesinlikle katılmıyorum