1. Giriş
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 |
Bu codelab'deki Material Flutter bileşenleri ve alt sistemleri
- Şekil
Flutter geliştirme ile ilgili deneyim düzeyinizi nasıl değerlendirirsiniz?
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
- Projeyi istediğiniz düzenleyicide açın.
- "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 |
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 |
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:
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 |
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 |
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 |
Ö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 |
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 |
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.
- Şunu vurgula:
ShrineApp.
- IDE'nize bağlı olarak kod işlemlerini gösterin:
- Android Studio: ⌥Enter (macOS) veya alt + enter tuşlarına basın
- VS Kod: ⌘ tuşuna basın. (macOS) veya Ctrl+ tuşlarına basın.
- "Convert to StatefulWidget"ı (StatefulWidget'a dönüştür) seçin.
- ShrineAppState sınıfını gizli (_ShrineAppState) olarak değiştirin. ShrineAppState'i sağ tıklayın ve
- Android Studio: Yeniden düzenleme > Yeniden adlandır
- VS Kodu: Sembolü Yeniden Adlandır'ı seçin
- 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 |
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 |
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 |
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.