1. مقدمه
Material Components (MDC) به توسعه دهندگان کمک می کند طراحی مواد را پیاده سازی کنند. MDC که توسط تیمی از مهندسان و طراحان UX در Google ایجاد شده است، دارای دهها مؤلفه رابط کاربری زیبا و کاربردی است و برای Android، iOS، وب و Flutter.material.io/develop در دسترس است. |
در Codelab MDC-103 ، رنگ، ارتفاع، تایپوگرافی، و شکل اجزای مواد (MDC) را برای استایل دادن به برنامه خود سفارشی کردید.
یک جزء در سیستم طراحی متریال مجموعه ای از وظایف از پیش تعریف شده را انجام می دهد و ویژگی های خاصی مانند یک دکمه دارد. با این حال، یک دکمه بیشتر از یک روش ساده برای کاربر برای انجام یک عمل است، بلکه بیانی بصری از شکل، اندازه و رنگ است که به کاربر اجازه میدهد بفهمد که تعاملی است و با لمس یا کلیک چیزی اتفاق میافتد.
دستورالعملهای طراحی متریال اجزاء را از دیدگاه طراح توصیف میکنند. آنها طیف گسترده ای از عملکردهای اساسی موجود در پلتفرم ها و عناصر آناتومیکی که هر جزء را تشکیل می دهند را توصیف می کنند. به عنوان مثال، یک پسزمینه حاوی یک لایه پشتی و محتوای آن، لایه جلویی و محتوای آن، قوانین حرکت و گزینههای نمایش است. هر یک از این مؤلفه ها را می توان برای نیازها، موارد استفاده و محتوای هر برنامه سفارشی کرد.
چیزی که خواهی ساخت
در این کد لبه، رابط کاربری برنامه Shrine را به یک ارائه دو سطحی به نام «پسزمینه» تغییر میدهید. پس زمینه شامل منویی است که دسته بندی های قابل انتخابی را فهرست می کند که برای فیلتر کردن محصولات نشان داده شده در شبکه نامتقارن استفاده می شوند. در این کد لبه از موارد زیر استفاده خواهید کرد:
- شکل
- حرکت
- ویجت های فلاتر (که در کدهای قبلی استفاده کرده اید)
اندروید | iOS |
اجزاء و زیرسیستم های Flutter مواد در این آزمایشگاه کد
- شکل
سطح تجربه خود را با توسعه فلاتر چگونه ارزیابی می کنید؟
2. محیط توسعه Flutter خود را تنظیم کنید
برای تکمیل این آزمایشگاه به دو نرم افزار نیاز دارید - Flutter SDK و یک ویرایشگر .
شما می توانید کدلب را با استفاده از هر یک از این دستگاه ها اجرا کنید:
- یک دستگاه فیزیکی Android یا iOS که به رایانه شما متصل شده و روی حالت Developer تنظیم شده است.
- شبیه ساز iOS (نیاز به نصب ابزار Xcode دارد).
- شبیه ساز اندروید (نیاز به نصب در Android Studio دارد).
- یک مرورگر (Chrome برای اشکال زدایی لازم است).
- به عنوان یک برنامه دسکتاپ Windows ، Linux ، یا macOS . شما باید روی پلتفرمی که قصد استقرار در آن را دارید توسعه دهید. بنابراین، اگر می خواهید یک برنامه دسکتاپ ویندوز توسعه دهید، باید در ویندوز توسعه دهید تا به زنجیره ساخت مناسب دسترسی داشته باشید. الزامات خاص سیستم عامل وجود دارد که به طور مفصل در docs.flutter.dev/desktop پوشش داده شده است.
3. برنامه استارتر Codelab را دانلود کنید
از MDC-103 ادامه می دهید؟
اگر MDC-103 را تکمیل کرده اید، کد شما باید برای این کد لبه آماده باشد. رفتن به مرحله: منوی پس زمینه را اضافه کنید .
از صفر شروع کنم؟
برنامه شروع در فهرست راهنمای material-components-flutter-codelabs-104-starter_and_103-complete/mdc_100_series
قرار دارد.
... یا آن را از GitHub شبیه سازی کنید
برای شبیه سازی این کد لبه از GitHub، دستورات زیر را اجرا کنید:
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
پروژه را باز کنید و برنامه را اجرا کنید
- پروژه را در ویرایشگر انتخابی خود باز کنید.
- دستورالعملهای «اجرای برنامه» را در Get Started: Test Drive برای ویرایشگر انتخابی خود دنبال کنید.
موفقیت! شما باید صفحه ورود به حرم را از کد لبه های قبلی دستگاه خود ببینید.
اندروید | iOS |
4. منوی پس زمینه را اضافه کنید
یک پس زمینه در پشت همه محتوا و اجزای دیگر ظاهر می شود. این از دو لایه تشکیل شده است: یک لایه پشتی (که اعمال و فیلترها را نمایش می دهد) و یک لایه جلو (که محتوا را نمایش می دهد). می توانید از یک پس زمینه برای نمایش اطلاعات و اقدامات تعاملی مانند پیمایش یا فیلترهای محتوا استفاده کنید.
نوار برنامه home را بردارید
ویجت HomePage محتوای لایه جلویی ما خواهد بود. در حال حاضر یک نوار برنامه دارد. نوار برنامه را به لایه پشتی منتقل می کنیم و صفحه اصلی فقط شامل AsymmetricView می شود.
در home.dart
، تابع build()
را تغییر دهید تا فقط AsymmetricView را برگرداند:
// TODO: Return an AsymmetricView (104)
return AsymmetricView(products: ProductsRepository.loadProducts(Category.all));
ویجت Backdrop را اضافه کنید
ویجتی به نام Backdrop ایجاد کنید که شامل frontLayer
و backLayer
است.
backLayer
شامل منویی است که به شما امکان می دهد دسته بندی را برای فیلتر کردن لیست انتخاب کنید ( currentCategory
). از آنجایی که میخواهیم انتخاب منو باقی بماند، پس زمینه را به یک ویجت حالت دار تبدیل میکنیم.
یک فایل جدید به /lib
با نام backdrop.dart
اضافه کنید:
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)
توجه داشته باشید که ویژگی های خاصی را required
علامت گذاری می کنیم. این بهترین روش برای خواص در سازنده است که هیچ مقدار پیش فرضی ندارند و نمی توانند null
باشند و بنابراین نباید فراموش شوند.
در تعریف کلاس Backdrop، کلاس _BackdropState را اضافه کنید:
// 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()
یک Scaffold را با یک نوار برنامه برمی گرداند، درست مانند صفحه اصلی. اما بدن Scaffold یک پشته است. فرزندان یک پشته می توانند همپوشانی داشته باشند. اندازه و مکان هر کودک نسبت به والدین پشته مشخص شده است.
اکنون یک نمونه Backdrop را به ShrineApp اضافه کنید.
در app.dart
، backdrop.dart
و model/product.dart
را وارد کنید:
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,
مسیر /
را با برگرداندن Backdrop
که HomePage
بهعنوان frontLayer
دارد، تغییر دهید:
// 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'),
),
پروژه خود را ذخیره کنید، باید ببینید که صفحه اصلی ما و همچنین نوار برنامه نمایش داده می شود:
اندروید | iOS |
BackLayer ناحیه صورتی را در یک لایه جدید در پشت صفحه اصلی frontLayer نشان می دهد.
میتوانید از Flutter Inspector برای تأیید اینکه Stack واقعاً یک کانتینر در پشت صفحه اصلی دارد استفاده کنید. باید شبیه این باشد:
اکنون می توانید طراحی و محتوای لایه ها را تنظیم کنید.
5. یک شکل اضافه کنید
در این مرحله، به لایه جلویی استایل می دهید تا برشی در گوشه بالا سمت چپ اضافه کند.
طراحی متریال به این نوع سفارشی سازی به عنوان یک شکل اشاره می کند. سطوح مواد می توانند اشکال دلخواه داشته باشند. شکل ها به سطوح تاکید و سبک می بخشند و می توانند برای بیان برند استفاده شوند. اشکال مستطیلی معمولی را می توان با گوشه ها و لبه های منحنی یا زاویه دار و هر تعداد ضلع سفارشی کرد. آنها می توانند متقارن یا نامنظم باشند.
یک شکل به لایه جلویی اضافه کنید
لوگوی زاویه دار Shrine الهام بخش داستان شکل اپلیکیشن Shrine بود. شکل داستان استفاده رایج از اشکالی است که در سراسر یک برنامه اعمال می شود. به عنوان مثال، شکل لوگو در عناصر صفحه ورود که دارای شکلی است که روی آنها اعمال شده است، منعکس می شود. در این مرحله، لایه جلویی را با یک برش زاویه دار در گوشه بالا سمت چپ استایل می دهید.
در backdrop.dart
یک کلاس _FrontLayer جدید اضافه کنید:
// 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,
),
],
),
);
}
}
سپس، در تابع _buildStack()
در _BackdropState، لایه جلویی را در یک _FrontLayer بپیچید:
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),
],
);
}
بارگذاری مجدد
اندروید | iOS |
ما به سطح اولیه Shrine شکل سفارشی داده ایم. با این حال، ما می خواهیم این به صورت بصری با نوار برنامه متصل شود.
رنگ نوار برنامه را تغییر دهید
در app.dart
، تابع _buildShrineTheme()
را به صورت زیر تغییر دهید:
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,
),
),
);
}
راه اندازی مجدد داغ. اکنون باید نوار برنامه رنگی جدید ظاهر شود.
اندروید | iOS |
به دلیل این تغییر، کاربران می توانند ببینند که چیزی در پشت لایه سفید جلویی وجود دارد. بیایید حرکت را اضافه کنیم تا کاربران بتوانند لایه پشتی پس زمینه را ببینند.
6. حرکت را اضافه کنید
حرکت راهی برای زنده کردن اپلیکیشن شماست. می تواند بزرگ و دراماتیک، ظریف و مینیمال، یا هر جایی در این بین باشد. اما به یاد داشته باشید که نوع حرکتی که استفاده می کنید باید متناسب با موقعیت باشد. حرکتی که برای اعمال تکراری و منظم اعمال میشود، باید کوچک و ظریف باشد، به طوری که این اقدامات حواس کاربر را پرت نکنند یا زمان زیادی را به طور منظم نگیرند. اما موقعیتهای مناسبی وجود دارد، مانند اولین باری که کاربر یک برنامه را باز میکند، که میتواند چشم نوازتر باشد، و برخی انیمیشنها میتوانند به آموزش کاربر در مورد نحوه استفاده از برنامه شما کمک کنند.
حرکت آشکار را به دکمه منو اضافه کنید
در بالای backdrop.dart
، خارج از محدوده هر کلاس یا تابع، یک ثابت اضافه کنید تا سرعتی را که میخواهیم انیمیشن ما داشته باشد را نشان دهد:
// TODO: Add velocity constant (104)
const double _kFlingVelocity = 2.0;
یک ویجت AnimationController را به _BackdropState اضافه کنید، آن را در تابع initState()
نمونه سازی کنید و آن را در تابع state's dispose()
از بین ببرید:
// 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 انیمیشن ها را هماهنگ می کند و API را برای پخش، معکوس کردن و توقف انیمیشن به شما می دهد. اکنون به توابعی نیاز داریم که باعث حرکت آن شود.
توابعی را اضافه کنید که نمایان بودن لایه جلویی را تعیین و همچنین تغییر می دهد:
// 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);
}
لایه پشتی را در یک ویجت ExcludeSemantics بپیچید. هنگامی که لایه پشتی قابل مشاهده نباشد، این ویجت آیتم های منوی BackLayer را از درخت معنایی حذف می کند.
return Stack(
key: _backdropKey,
children: <Widget>[
// TODO: Wrap backLayer in an ExcludeSemantics widget (104)
ExcludeSemantics(
child: widget.backLayer,
excluding: _frontLayerVisible,
),
...
تابع _buildStack() را برای گرفتن BuildContext و BoxConstraints تغییر دهید. همچنین، یک PositionedTransition که یک انیمیشن RelativeRectTween می گیرد، اضافه کنید:
// 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,
),
),
],
);
}
در نهایت، به جای فراخوانی تابع _buildStack برای بدنه Scaffold، ویجت LayoutBuilder را برگردانید که از _buildStack به عنوان سازنده آن استفاده می کند:
return Scaffold(
appBar: appBar,
// TODO: Return a LayoutBuilder widget (104)
body: LayoutBuilder(builder: _buildStack),
);
ما ساخت پشته لایه جلو/پشت را تا زمان طرح بندی با استفاده از LayoutBuilder به تعویق انداخته ایم تا بتوانیم ارتفاع کلی واقعی پس زمینه را در نظر بگیریم. LayoutBuilder یک ویجت ویژه است که callback سازنده آن محدودیت های اندازه را فراهم می کند.
در تابع build()
آیکون منوی اصلی در نوار برنامه را به یک IconButton تبدیل کنید و از آن برای تغییر نمای لایه جلویی هنگام ضربه زدن روی دکمه استفاده کنید.
// TODO: Replace leading menu icon with IconButton (104)
leading: IconButton(
icon: const Icon(Icons.menu),
onPressed: _toggleBackdropLayerVisibility,
),
دوباره بارگیری کنید سپس روی دکمه منو در شبیه ساز ضربه بزنید.
اندروید | iOS |
لایه جلویی متحرک (اسلاید) به پایین است. اما اگر به پایین نگاه کنید، یک خطای قرمز و یک خطای سرریز وجود دارد. این به این دلیل است که AsymmetricView توسط این انیمیشن فشرده شده و کوچکتر می شود که به نوبه خود فضای کمتری به ستون ها می دهد. در نهایت، ستون ها نمی توانند خود را با فضای داده شده نشان دهند و منجر به خطا می شوند. اگر ستون ها را با ListViews جایگزین کنیم، اندازه ستون باید همانطور که متحرک هستند باقی بماند.
ستون های محصول را در ListView بپیچید
در supplemental/product_columns.dart
، ستون موجود در OneProductCardColumn
را با ListView جایگزین کنید:
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,
),
],
);
}
}
ستون شامل MainAxisAlignment.end
است. برای شروع طرح از پایین، reverse: true
. ترتیب بچه ها برای جبران این تغییر معکوس می شود.
دوباره بارگیری کنید و روی دکمه منو ضربه بزنید.
اندروید | iOS |
هشدار سرریز خاکستری در OneProductCardColumn از بین رفته است! حالا بیایید دیگری را درست کنیم.
در supplemental/product_columns.dart
، نحوه محاسبه imageAspectRatio
را تغییر دهید و ستون TwoProductCardColumn
را با ListView جایگزین کنید:
// 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,
),
),
],
);
ما همچنین مقداری ایمنی را به imageAspectRatio
اضافه کردیم.
بارگذاری مجدد سپس روی دکمه منو ضربه بزنید.
اندروید | iOS |
دیگر سرریز نیست.
7. یک منو در لایه پشتی اضافه کنید
منو لیستی از موارد متنی قابل لمس است که در صورت لمس موارد متنی به شنوندگان اطلاع می دهد. در این مرحله، منوی فیلترینگ دسته را اضافه می کنید.
منو را اضافه کنید
منو را به لایه جلو و دکمه های تعاملی را به لایه پشتی اضافه کنید.
یک فایل جدید به نام lib/category_menu_page.dart
ایجاد کنید:
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()),
),
);
}
}
این یک GestureDetector است که ستونی را میپیچد که فرزندان آن نام دستهها هستند. یک زیر خط برای نشان دادن دسته انتخابی استفاده می شود.
در app.dart
، ویجت ShrineApp را از حالت بدون حالت به حالت حالت تبدیل کنید.
-
ShrineApp.
- بر اساس IDE خود، اقدامات کد را نشان دهید:
- Android Studio: ⌥Enter (macOS) یا alt + enter را فشار دهید
- کد VS: ⌘ را فشار دهید. (macOS) یا Ctrl+.
- "تبدیل به StatefulWidget" را انتخاب کنید.
- کلاس ShrineAppState را به خصوصی (_ShrineAppState) تغییر دهید. روی ShrineAppState راست کلیک کنید و
- Android Studio: Refactor > Rename را انتخاب کنید
- VS Code: تغییر نام نماد را انتخاب کنید
- برای خصوصی کردن کلاس وارد _ShrineAppState شوید.
در app.dart
، یک متغیر به _ShrineAppState برای دسته انتخابی اضافه کنید و وقتی روی آن ضربه زدید، یک تماس پاسخ دهید:
class _ShrineAppState extends State<ShrineApp> {
Category _currentCategory = Category.all;
void _onCategoryTap(Category category) {
setState(() {
_currentCategory = category;
});
}
سپس لایه پشتی را به CategoryMenuPage تغییر دهید.
در app.dart
، CategoryMenuPage را وارد کنید:
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()
فیلد backLayer را به CategoryMenuPage و فیلد currentCategory را برای گرفتن متغیر نمونه تغییر دهید.
'/': (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'),
),
دوباره بارگیری کنید و روی دکمه Menu ضربه بزنید.
اندروید | iOS |
اگر روی یک گزینه منو ضربه بزنید، هیچ اتفاقی نمی افتد...هنوز. بیایید آن را درست کنیم.
در home.dart
، یک متغیر برای Category اضافه کنید و آن را به AsymmetricView ارسال کنید.
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
، _currentCategory
برای frontLayer
: عبور دهید.
// TODO: Pass _currentCategory for frontLayer (104)
frontLayer: HomePage(category: _currentCategory),
بارگذاری مجدد روی دکمه منو در شبیه ساز ضربه بزنید و یک دسته را انتخاب کنید.
اندروید | iOS |
آنها فیلتر شده اند!
بعد از انتخاب منو، لایه جلویی را ببندید
در backdrop.dart
، برای didUpdateWidget()
(هر زمان که پیکربندی ویجت تغییر می کند فراخوانی می شود) در _BackdropState یک جایگزین اضافه کنید:
// 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);
}
}
پروژه خود را ذخیره کنید تا بارگذاری مجدد داغ راه اندازی شود. روی نماد منو ضربه بزنید و یک دسته را انتخاب کنید. منو باید به طور خودکار بسته شود و شما باید دسته بندی موارد انتخاب شده را ببینید. اکنون این قابلیت را به لایه جلویی نیز اضافه خواهید کرد.
لایه جلویی را تغییر دهید
در backdrop.dart
، یک پاسخ تماس با ضربه به لایه پس زمینه اضافه کنید:
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;
سپس یک GestureDetector را به فرزند _FrontLayer اضافه کنید: فرزندان ستون:.
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,
),
],
),
سپس ویژگی جدید onTap
در _BackdropState در تابع _buildStack()
پیاده سازی کنید:
PositionedTransition(
rect: layerAnimation,
child: _FrontLayer(
// TODO: Implement onTap property on _BackdropState (104)
onTap: _toggleBackdropLayerVisibility,
child: widget.frontLayer,
),
),
دوباره بارگیری کنید و روی بالای لایه جلو ضربه بزنید. هر بار که روی لایه جلویی ضربه می زنید، لایه باید باز و بسته شود.
8. یک نماد مارک اضافه کنید
نماد نگاری مارک به نمادهای آشنا نیز گسترش می یابد. بیایید نماد آشکار را سفارشی کنیم و آن را با عنوان خود ادغام کنیم تا ظاهری منحصر به فرد و مارک دار داشته باشیم.
نماد دکمه منو را تغییر دهید
اندروید | iOS |
در backdrop.dart
، یک کلاس _BackdropTitle جدید ایجاد کنید.
// 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
یک ویجت سفارشی است که جایگزین ویجت Text
ساده برای پارامتر title
ویجت AppBar
می شود. دارای یک نماد منوی متحرک و انتقال متحرک بین عناوین جلو و عقب است. نماد منوی متحرک از یک دارایی جدید استفاده می کند. ارجاع به slanted_menu.png
جدید باید به pubspec.yaml
اضافه شود.
assets:
- assets/diamond.png
# TODO: Add slanted menu asset (104)
- assets/slanted_menu.png
- packages/shrine_images/0-0.jpg
ویژگی leading
در سازنده AppBar
را حذف کنید. حذف برای اینکه نماد مارک سفارشی در محل ویجت leading
ارائه شود، ضروری است. انیمیشن listenable
و کنترل کننده onPress
برای نماد مارک دار به _BackdropTitle
منتقل می شوند. frontTitle
و backTitle
نیز ارسال می شوند تا بتوان آنها را در عنوان پس زمینه ارائه کرد. پارامتر title
AppBar
باید به شکل زیر باشد:
// TODO: Create title with _BackdropTitle parameter (104)
title: _BackdropTitle(
listenable: _controller.view,
onPress: _toggleBackdropLayerVisibility,
frontTitle: widget.frontTitle,
backTitle: widget.backTitle,
),
نماد مارک در _BackdropTitle.
این شامل Stack
ای از نمادهای متحرک است: یک منوی کج و یک الماس، که در یک IconButton
پیچیده شده است تا بتوان آن را فشار داد. سپس IconButton
در یک SizedBox
پیچیده می شود تا جایی برای حرکت آیکون افقی ایجاد شود.
معماری "همه چیز یک ویجت است" Flutter اجازه می دهد تا طرح بندی AppBar
پیش فرض را بدون نیاز به ایجاد یک ویجت سفارشی کاملاً جدید AppBar
تغییر دهید. پارامتر title
، که در اصل یک ویجت Text
است، می تواند با یک _BackdropTitle
پیچیده تر جایگزین شود. از آنجایی که _BackdropTitle
شامل نماد سفارشی نیز می شود، جای ویژگی leading
را می گیرد که اکنون می توان آن را حذف کرد. این جایگزینی ساده ویجت بدون تغییر هیچ یک از پارامترهای دیگر، مانند نمادهای عمل، که به تنهایی به کار خود ادامه می دهند، انجام می شود.
یک میانبر به صفحه ورود به سیستم اضافه کنید
در backdrop.dart,
یک میانبر از دو نماد انتهایی در نوار برنامه به صفحه ورود اضافه کنید: برچسب های معنایی نمادها را تغییر دهید تا هدف جدید آنها را منعکس کند.
// 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()),
);
},
),
اگر بارگذاری مجدد را امتحان کنید، با خطا مواجه خواهید شد. برای رفع خطا، login.dart
را وارد کنید:
import 'login.dart';
برنامه را دوباره بارگیری کنید و روی دکمه های جستجو یا تنظیم ضربه بزنید تا به صفحه ورود بازگردید.
9. تبریک می گویم!
در طول این چهار آزمایشگاه، یاد گرفتهاید که چگونه از Material Components برای ایجاد تجربیات کاربر منحصر به فرد و ظریفی استفاده کنید که شخصیت و سبک برند را بیان میکند.
مراحل بعدی
این کد لبه، MDC-104، این توالی از لبه های کد را تکمیل می کند. با مراجعه به کاتالوگ ابزارک های Material Components می توانید حتی اجزای بیشتری را در Material Flutter کاوش کنید.
برای یک هدف طولانی، سعی کنید نماد مارک دار را با یک AnimatedIcon جایگزین کنید که وقتی پس زمینه قابل مشاهده است بین دو نماد متحرک شود.
تعداد زیادی کدهای Flutter دیگر وجود دارد که می توانید بر اساس علایق خود امتحان کنید. ما یکی دیگر از کدهای مخصوص مواد داریم که ممکن است به آن علاقه مند باشید: ساختن انتقال های زیبا با حرکت مواد برای فلوتر .