1. مقدمه
متریال دیزاین سیستمی برای ساخت محصولات دیجیتال جسورانه و زیبا است. با متحد کردن سبک، نام تجاری، تعامل و حرکت تحت مجموعهای از اصول و اجزای ثابت، تیمهای محصول میتوانند بزرگترین پتانسیل طراحی خود را محقق کنند.
Material Components (MDC) به توسعه دهندگان کمک می کند طراحی مواد را پیاده سازی کنند. MDC که توسط تیمی از مهندسان و طراحان UX در Google ایجاد شده است، دارای دهها مؤلفه رابط کاربری زیبا و کاربردی است و برای Android، iOS، وب و Flutter.material.io/develop در دسترس است. |
سیستم حرکت مواد برای فلاتر چیست؟
سیستم حرکت مواد برای فلاتر مجموعهای از الگوهای انتقال در بسته انیمیشنها است که میتواند به کاربران کمک کند تا یک برنامه را بفهمند و حرکت کنند، همانطور که در دستورالعملهای طراحی مواد توضیح داده شده است.
چهار الگوی اصلی انتقال مواد به شرح زیر است:
- Container Transform: انتقال بین عناصر UI که شامل یک ظرف است. با تبدیل یکپارچه یک عنصر به عنصر دیگر، یک ارتباط قابل مشاهده بین دو عنصر UI مجزا ایجاد می کند.
- محور مشترک: انتقال بین عناصر UI که یک رابطه فضایی یا ناوبری دارند. از یک تبدیل مشترک در محور x، y یا z برای تقویت رابطه بین عناصر استفاده می کند.
- Fade Through: انتقال بین عناصر UI که رابطه قوی با یکدیگر ندارند. از محو شدن و محو شدن متوالی با مقیاس عنصر ورودی استفاده می کند.
- محو شدن: برای عناصر رابط کاربری که در محدوده صفحه وارد یا خارج می شوند استفاده می شود.
بسته انیمیشنها ویجتهای انتقالی را برای این الگوها ارائه میکند که بر روی کتابخانه انیمیشنهای Flutter ( flutter/animation.dart
) و کتابخانه مواد Flutter ( flutter/material.dart
) ساخته شدهاند:
در این کد لبه شما از انتقال مواد ساخته شده در بالای چارچوب Flutter و کتابخانه Material استفاده می کنید، به این معنی که با ویجت ها سر و کار دارید. :)
چیزی که خواهی ساخت
این لبه کد شما را از طریق ایجاد برخی انتقالها در یک برنامه ایمیل Flutter به نام Reply با استفاده از Dart راهنمایی میکند تا نشان دهد چگونه میتوانید از انتقالهای بسته انیمیشنها برای سفارشی کردن ظاهر و احساس برنامه خود استفاده کنید.
کد شروع برای برنامه Reply ارائه میشود، و شما جابجاییهای Material زیر را در برنامه قرار میدهید، که میتوانید در GIF کد لبه تکمیلشده زیر مشاهده کنید:
- تبدیل کانتینر از لیست ایمیل به صفحه جزئیات ایمیل
- تبدیل کانتینر از FAB به نوشتن صفحه ایمیل
- انتقال مشترک Z-Axis از نماد جستجو به صفحه مشاهده جستجو
- محو شدن از طریق انتقال بین صفحات صندوق پستی
- محو شدن از طریق انتقال بین نوشتن و پاسخ FAB
- محو شدن از طریق انتقال بین عنوان صندوق پستی ناپدید شده
- محو شدن از طریق انتقال بین عملکردهای نوار برنامه پایین
آنچه شما نیاز دارید
- دانش پایه توسعه فلوتر و دارت
- یک ویرایشگر کد
- شبیه ساز یا دستگاه Android/iOS
- کد نمونه (مرحله بعدی را ببینید)
سطح تجربه خود را در ساخت برنامه های Flutter چگونه ارزیابی می کنید؟
دوست دارید از این کد لبه چه چیزی یاد بگیرید؟
2. محیط توسعه Flutter خود را تنظیم کنید
برای تکمیل این آزمایشگاه به دو نرم افزار نیاز دارید - Flutter SDK و یک ویرایشگر .
شما می توانید کدلب را با استفاده از هر یک از این دستگاه ها اجرا کنید:
- یک دستگاه فیزیکی Android یا iOS که به رایانه شما متصل شده و روی حالت Developer تنظیم شده است.
- شبیه ساز iOS (نیاز به نصب ابزار Xcode دارد).
- شبیه ساز اندروید (نیاز به نصب در Android Studio دارد).
- یک مرورگر (Chrome برای اشکال زدایی لازم است).
- به عنوان یک برنامه دسکتاپ Windows ، Linux ، یا macOS . شما باید روی پلتفرمی که قصد استقرار در آن را دارید توسعه دهید. بنابراین، اگر می خواهید یک برنامه دسکتاپ ویندوز توسعه دهید، باید در ویندوز توسعه دهید تا به زنجیره ساخت مناسب دسترسی داشته باشید. الزامات خاص سیستم عامل وجود دارد که به طور مفصل در docs.flutter.dev/desktop پوشش داده شده است.
3. برنامه استارتر Codelab را دانلود کنید
گزینه 1: برنامه codelab starter را از GitHub کلون کنید
برای شبیه سازی این کد لبه از GitHub، دستورات زیر را اجرا کنید:
git clone https://github.com/material-components/material-components-flutter-motion-codelab.git cd material-components-flutter-motion-codelab
گزینه 2: فایل فشرده برنامه codelab starter را دانلود کنید
برنامه شروع در دایرکتوری material-components-flutter-motion-codelab-starter
قرار دارد.
وابستگی های پروژه را تأیید کنید
پروژه به بسته انیمیشن بستگی دارد. در pubspec.yaml
، توجه داشته باشید که بخش dependencies
شامل موارد زیر است:
animations: ^2.0.0
پروژه را باز کنید و برنامه را اجرا کنید
- پروژه را در ویرایشگر انتخابی خود باز کنید.
- دستورالعملهای «اجرای برنامه» را در Get Started: Test Drive برای ویرایشگر انتخابی خود دنبال کنید.
موفقیت! کد شروع برای صفحه اصلی Reply باید روی دستگاه/شبیه ساز شما اجرا شود. باید صندوق ورودی حاوی لیستی از ایمیل ها را ببینید.
اختیاری: انیمیشن های دستگاه را کاهش دهید
از آنجایی که این آزمایشگاه کد شامل انتقالهای سریع و در عین حال صیقلی است، کاهش سرعت انیمیشنهای دستگاه برای مشاهده جزئیات دقیق تر انتقالها در حین پیادهسازی میتواند مفید باشد. این کار را می توان از طریق تنظیمات درون برنامه ای انجام داد، زمانی که کشوی پایین باز است، از طریق ضربه زدن روی نماد تنظیمات قابل دسترسی است. نگران نباشید، این روش کاهش سرعت انیمیشنهای دستگاه روی انیمیشنهای دستگاه خارج از برنامه Reply تأثیری نخواهد گذاشت.
اختیاری: حالت تاریک
اگر موضوع روشن Reply چشمان شما را آزار می دهد، دیگر به آن نگاه نکنید. تنظیمات درونبرنامهای وجود دارد که به شما امکان میدهد تم برنامه را به حالت تاریک تغییر دهید تا با چشمهایتان بهتر باشد. هنگامی که کشوی پایین باز است، این تنظیم با ضربه زدن روی نماد تنظیمات قابل دسترسی است.
4. با نمونه کد برنامه آشنا شوید
بیایید به کد نگاه کنیم. ما برنامهای ارائه کردهایم که از بسته انیمیشنها برای انتقال بین صفحههای مختلف در برنامه استفاده میکند.
- صفحه اصلی: صندوق پستی انتخاب شده را نمایش می دهد
- InboxPage : فهرستی از ایمیل ها را نمایش می دهد
- MailPreviewCard : پیش نمایش یک ایمیل را نمایش می دهد
- MailViewPage: یک ایمیل منفرد و کامل را نمایش می دهد
- ComposePage: امکان ترکیب یک ایمیل جدید را فراهم می کند
- SearchPage: نمای جستجو را نمایش می دهد
روتر.دارت
ابتدا، برای درک نحوه تنظیم مسیریابی ریشه برنامه، router.dart
را در دایرکتوری lib
باز کنید:
class ReplyRouterDelegate extends RouterDelegate<ReplyRoutePath>
with ChangeNotifier, PopNavigatorRouterDelegateMixin<ReplyRoutePath> {
ReplyRouterDelegate({required this.replyState})
: navigatorKey = GlobalObjectKey<NavigatorState>(replyState) {
replyState.addListener(() {
notifyListeners();
});
}
@override
final GlobalKey<NavigatorState> navigatorKey;
RouterProvider replyState;
@override
void dispose() {
replyState.removeListener(notifyListeners);
super.dispose();
}
@override
ReplyRoutePath get currentConfiguration => replyState.routePath!;
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider<RouterProvider>.value(value: replyState),
],
child: Selector<RouterProvider, ReplyRoutePath?>(
selector: (context, routerProvider) => routerProvider.routePath,
builder: (context, routePath, child) {
return Navigator(
key: navigatorKey,
onPopPage: _handlePopPage,
pages: [
// TODO: Add Shared Z-Axis transition from search icon to search view page (Motion)
const CustomTransitionPage(
transitionKey: ValueKey('Home'),
screen: HomePage(),
),
if (routePath is ReplySearchPath)
const CustomTransitionPage(
transitionKey: ValueKey('Search'),
screen: SearchPage(),
),
],
);
},
),
);
}
bool _handlePopPage(Route<dynamic> route, dynamic result) {
// _handlePopPage should not be called on the home page because the
// PopNavigatorRouterDelegateMixin will bubble up the pop to the
// SystemNavigator if there is only one route in the navigator.
assert(route.willHandlePopInternally ||
replyState.routePath is ReplySearchPath);
final bool didPop = route.didPop(result);
if (didPop) replyState.routePath = const ReplyHomePath();
return didPop;
}
@override
Future<void> setNewRoutePath(ReplyRoutePath configuration) {
replyState.routePath = configuration;
return SynchronousFuture<void>(null);
}
}
این ناوبر اصلی ما است و صفحههای برنامه ما را کنترل میکند که کل بوم را مصرف میکنند، مانند HomePage
و SearchPage
. به وضعیت برنامه ما گوش می دهد تا بررسی کند که آیا مسیر را روی ReplySearchPath
تنظیم کرده ایم. اگر داشته باشیم، ناوبر ما را با SearchPage
در بالای پشته بازسازی می کند. توجه داشته باشید که صفحات ما در یک CustomTransitionPage
بدون هیچ انتقالی تعریف شده اند. این یک راه را برای پیمایش بین صفحهها بدون هیچ گونه انتقال سفارشی به شما نشان میدهد.
خانه.دارت
با انجام کارهای زیر در داخل _BottomAppBarActionItems
در home.dart
، مسیر خود را به ReplySearchPath
در وضعیت برنامه خود تنظیم کردیم:
Align(
alignment: AlignmentDirectional.bottomEnd,
child: IconButton(
icon: const Icon(Icons.search),
color: ReplyColors.white50,
onPressed: () {
Provider.of<RouterProvider>(
context,
listen: false,
).routePath = const ReplySearchPath();
},
),
);
در پارامتر onPressed
خود، به RouterProvider
خود دسترسی پیدا می کنیم و routePath
آن را روی ReplySearchPath
قرار می دهیم. RouterProvider
ما وضعیت ناوبر اصلی ما را پیگیری می کند.
mail_view_router.dart
اکنون، بیایید ببینیم که ناوبری داخلی برنامه ما چگونه تنظیم شده است، mail_view_router.dart
را در دایرکتوری lib
باز کنید. یک ناوبر مشابه تصویر بالا خواهید دید:
class MailViewRouterDelegate extends RouterDelegate<void>
with ChangeNotifier, PopNavigatorRouterDelegateMixin {
MailViewRouterDelegate({required this.drawerController});
final AnimationController drawerController;
@override
Widget build(BuildContext context) {
bool _handlePopPage(Route<dynamic> route, dynamic result) {
return false;
}
return Selector<EmailStore, String>(
selector: (context, emailStore) => emailStore.currentlySelectedInbox,
builder: (context, currentlySelectedInbox, child) {
return Navigator(
key: navigatorKey,
onPopPage: _handlePopPage,
pages: [
// TODO: Add Fade through transition between mailbox pages (Motion)
CustomTransitionPage(
transitionKey: ValueKey(currentlySelectedInbox),
screen: InboxPage(
destination: currentlySelectedInbox,
),
)
],
);
},
);
}
...
}
این ناوبر درونی ماست. این صفحه نمایش های داخلی برنامه ما را کنترل می کند که فقط بدنه بوم را مصرف می کنند، مانند InboxPage
. InboxPage
لیستی از ایمیل ها را بسته به اینکه صندوق پست فعلی در وضعیت برنامه ما قرار دارد، نمایش می دهد. هر زمان که تغییری در ویژگی currentlySelectedInbox
وضعیت برنامه ما ایجاد شود، ناوبری با InboxPage
صحیح در بالای پشته بازسازی میشود.
خانه.دارت
با انجام کارهای زیر در داخل _HomePageState
در home.dart
، صندوق پستی فعلی خود را در وضعیت برنامه خود قرار می دهیم:
void _onDestinationSelected(String destination) {
var emailStore = Provider.of<EmailStore>(
context,
listen: false,
);
if (emailStore.onMailView) {
emailStore.currentlySelectedEmailId = -1;
}
if (emailStore.currentlySelectedInbox != destination) {
emailStore.currentlySelectedInbox = destination;
}
setState(() {});
}
در تابع _onDestinationSelected
خود، به EmailStore
خود دسترسی پیدا می کنیم و currentlySelectedInbox
SelectedInbox آن را روی مقصد انتخابی تنظیم می کنیم. EmailStore
ما وضعیت ناوبری داخلی ما را پیگیری می کند.
خانه.دارت
در نهایت، برای دیدن نمونهای از مسیریابی مسیریابی استفاده شده، home.dart
در فهرست lib
باز کنید. کلاس _ReplyFabState
را در داخل ویژگی onTap
ویجت InkWell
قرار دهید، که باید به شکل زیر باشد:
onTap: () {
Provider.of<EmailStore>(
context,
listen: false,
).onCompose = true;
Navigator.of(context).push(
PageRouteBuilder(
pageBuilder: (
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
) {
return const ComposePage();
},
),
);
},
این نشان می دهد که چگونه می توانید بدون هیچ گونه انتقال سفارشی به صفحه نوشتن ایمیل بروید. در طول این کد، به کد Reply میروید تا انتقالهای Material را تنظیم کنید که همزمان با اقدامات پیمایش مختلف در سراسر برنامه کار کنند.
اکنون که با کد شروع آشنا شدید، اجازه دهید اولین انتقال خود را پیاده سازی کنیم.
5. انتقال Container Transform را از لیست ایمیل به صفحه جزئیات ایمیل اضافه کنید
برای شروع، هنگام کلیک کردن بر روی ایمیل، یک انتقال اضافه خواهید کرد. برای این تغییر مسیریابی، الگوی تبدیل کانتینر به خوبی مناسب است، زیرا برای انتقال بین عناصر UI که شامل یک ظرف است، طراحی شده است. این الگو یک ارتباط قابل مشاهده بین دو عنصر UI ایجاد می کند.
قبل از افزودن هر کدی، برنامه Reply را اجرا کرده و روی ایمیل کلیک کنید. این باید یک پرش ساده انجام دهد، به این معنی که صفحه بدون تغییر جایگزین می شود:
قبل از
همانطور که در قطعه زیر نشان داده شده است، با افزودن یک import برای بسته انیمیشن در بالای mail_card_preview.dart
شروع کنید:
mail_card_preview.dart
import 'package:animations/animations.dart';
اکنون که یک واردات برای بسته انیمیشن دارید، میتوانیم شروع به اضافه کردن انتقالهای زیبا به برنامه شما کنیم. بیایید با ایجاد یک کلاس StatelessWidget
شروع کنیم که ویجت OpenContainer
ما را در خود جای دهد.
در mail_card_preview.dart
، قطعه کد زیر را بعد از تعریف کلاس MailPreviewCard
اضافه کنید:
mail_card_preview.dart
// TODO: Add Container Transform transition from email list to email detail page (Motion)
class _OpenContainerWrapper extends StatelessWidget {
const _OpenContainerWrapper({
required this.id,
required this.email,
required this.closedChild,
});
final int id;
final Email email;
final Widget closedChild;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return OpenContainer(
openBuilder: (context, closedContainer) {
return MailViewPage(id: id, email: email);
},
openColor: theme.cardColor,
closedShape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(0)),
),
closedElevation: 0,
closedColor: theme.cardColor,
closedBuilder: (context, openContainer) {
return InkWell(
onTap: () {
Provider.of<EmailStore>(
context,
listen: false,
).currentlySelectedEmailId = id;
openContainer();
},
child: closedChild,
);
},
);
}
}
حالا بیایید از لفاف جدیدمان استفاده کنیم. در داخل تعریف کلاس MailPreviewCard
، ویجت Material
را از تابع build()
با _OpenContainerWrapper
جدید خود قرار می دهیم:
mail_card_preview.dart
// TODO: Add Container Transform transition from email list to email detail page (Motion)
return _OpenContainerWrapper(
id: id,
email: email,
closedChild: Material(
...
_OpenContainerWrapper
ما دارای یک ویجت InkWell
است و ویژگی های رنگ OpenContainer
رنگ ظرفی را که محصور می کند مشخص می کند. بنابراین، میتوانیم ویجتهای Material و Inkwell را حذف کنیم. کد حاصل به صورت زیر است:
mail_card_preview.dart
// TODO: Add Container Transform transition from email list to email detail page (Motion)
return _OpenContainerWrapper(
id: id,
email: email,
closedChild: Dismissible(
key: ObjectKey(email),
dismissThresholds: const {
DismissDirection.startToEnd: 0.8,
DismissDirection.endToStart: 0.4,
},
onDismissed: (direction) {
switch (direction) {
case DismissDirection.endToStart:
if (onStarredInbox) {
onStar();
}
break;
case DismissDirection.startToEnd:
onDelete();
break;
default:
}
},
background: _DismissibleContainer(
icon: 'twotone_delete',
backgroundColor: colorScheme.primary,
iconColor: ReplyColors.blue50,
alignment: Alignment.centerLeft,
padding: const EdgeInsetsDirectional.only(start: 20),
),
confirmDismiss: (direction) async {
if (direction == DismissDirection.endToStart) {
if (onStarredInbox) {
return true;
}
onStar();
return false;
} else {
return true;
}
},
secondaryBackground: _DismissibleContainer(
icon: 'twotone_star',
backgroundColor: currentEmailStarred
? colorScheme.secondary
: theme.scaffoldBackgroundColor,
iconColor: currentEmailStarred
? colorScheme.onSecondary
: colorScheme.onBackground,
alignment: Alignment.centerRight,
padding: const EdgeInsetsDirectional.only(end: 20),
),
child: mailPreview,
),
);
در این مرحله، شما باید یک تبدیل ظرف کاملاً در حال کار داشته باشید. با کلیک بر روی یک ایمیل، آیتم لیست را در صفحه جزئیات گسترش می دهد و در عین حال لیست ایمیل ها را کاهش می دهد. با فشار دادن به عقب، صفحه جزئیات ایمیل در حالی که در لیست ایمیلها افزایش مییابد، به یک آیتم فهرست باز میگردد.
بعد از
6. انتقال Container Transform از FAB به نوشتن صفحه ایمیل را اضافه کنید
بیایید تبدیل ظرف را ادامه دهیم و یک انتقال از دکمه اقدام شناور به ComposePage
اضافه کنیم و FAB را به ایمیل جدیدی که کاربر نوشته است گسترش دهد. ابتدا برنامه را دوباره اجرا کنید و روی FAB کلیک کنید تا ببینید که هنگام راهاندازی صفحه نوشتن ایمیل هیچ انتقالی وجود ندارد.
قبل از
روشی که ما این انتقال را پیکربندی می کنیم بسیار شبیه به روشی است که در مرحله آخر انجام دادیم، زیرا از همان کلاس ویجت، OpenContainer
استفاده می کنیم.
در home.dart
، بیایید package:animations/animations.dart
در بالای فایل وارد کنیم و متد build()
_ReplyFabState
را تغییر دهیم. بیایید ویجت Material
بازگشتی را با ویجت OpenContainer
بپیچیم:
خانه.دارت
// TODO: Add Container Transform from FAB to compose email page (Motion)
return OpenContainer(
openBuilder: (context, closedContainer) {
return const ComposePage();
},
openColor: theme.cardColor,
onClosed: (success) {
Provider.of<EmailStore>(
context,
listen: false,
).onCompose = false;
},
closedShape: circleFabBorder,
closedColor: theme.colorScheme.secondary,
closedElevation: 6,
closedBuilder: (context, openContainer) {
return Material(
color: theme.colorScheme.secondary,
...
علاوه بر پارامترهای مورد استفاده برای پیکربندی ویجت OpenContainer
قبلی، onClosed
هم اکنون در حال تنظیم است. onClosed
یک ClosedCallback
است که زمانی فراخوانی میشود که مسیر OpenContainer
ظاهر شود یا به حالت بسته بازگردد. مقدار بازگشتی آن تراکنش به عنوان آرگومان به این تابع ارسال می شود. ما از این Callback
استفاده میکنیم تا به ارائهدهنده برنامه خود اطلاع دهیم که مسیر ComposePage
را ترک کردهایم تا بتواند همه شنوندگان را مطلع کند.
مشابه کاری که برای آخرین مرحله خود انجام دادیم، ویجت Material
را از ویجت خود حذف خواهیم کرد زیرا ویجت OpenContainer
رنگ ویجت بازگردانده شده توسط closedBuilder
را با closedColor
کنترل می کند. ما همچنین فراخوانی Navigator.push()
خود را در داخل onTap
ویجت InkWell خود حذف خواهیم کرد و آن را با openContainer() Callback
که توسط ویجت OpenContainer
closedBuilder
شده است جایگزین می کنیم، زیرا اکنون ویجت OpenContainer
مسیریابی خود را انجام می دهد.
کد حاصل به صورت زیر است:
خانه.دارت
// TODO: Add Container Transform from FAB to compose email page (Motion)
return OpenContainer(
openBuilder: (context, closedContainer) {
return const ComposePage();
},
openColor: theme.cardColor,
onClosed: (success) {
Provider.of<EmailStore>(
context,
listen: false,
).onCompose = false;
},
closedShape: circleFabBorder,
closedColor: theme.colorScheme.secondary,
closedElevation: 6,
closedBuilder: (context, openContainer) {
return Tooltip(
message: tooltip,
child: InkWell(
customBorder: circleFabBorder,
onTap: () {
Provider.of<EmailStore>(
context,
listen: false,
).onCompose = true;
openContainer();
},
child: SizedBox(
height: _mobileFabDimension,
width: _mobileFabDimension,
child: Center(
child: fabSwitcher,
),
),
),
);
},
);
حالا برای پاک کردن کدهای قدیمی. از آنجایی که ویجت OpenContainer
ما اکنون از طریق onClosed ClosedCallback
به ارائهدهنده برنامهمان اطلاع میدهد که دیگر در ComposePage
نیستیم، میتوانیم اجرای قبلی خود را در mail_view_router.dart
حذف کنیم:
mail_view_router.dart
// TODO: Add Container Transform from FAB to compose email page (Motion)
emailStore.onCompose = false; /// delete this line
return SynchronousFuture<bool>(true);
برای این مرحله تمام شد! شما باید یک انتقال از FAB به صفحه نوشتن داشته باشید که به شکل زیر باشد:
بعد از
7. انتقال مشترک Z-Axis را از نماد جستجو به صفحه مشاهده جستجو اضافه کنید
در این مرحله، یک انتقال از نماد جستجو به نمای جستجوی تمام صفحه اضافه می کنیم. از آنجایی که هیچ محفظه دائمی در این تغییر ناوبری وجود ندارد، میتوانیم از انتقال مشترک Z-Axis برای تقویت رابطه فضایی بین دو صفحه و نشان دادن حرکت یک سطح به سمت بالا در سلسله مراتب برنامه استفاده کنیم.
قبل از افزودن کد اضافی، برنامه را اجرا کنید و روی نماد جستجو در گوشه سمت راست پایین صفحه ضربه بزنید. این باید صفحه نمایش جستجو را بدون انتقال ظاهر کند.
قبل از
برای شروع، اجازه دهید به فایل router.dart
خود برویم. پس از تعریف کلاس ReplySearchPath
، قطعه زیر را اضافه کنید:
روتر.دارت
// TODO: Add Shared Z-Axis transition from search icon to search view page (Motion)
class SharedAxisTransitionPageWrapper extends Page {
const SharedAxisTransitionPageWrapper(
{required this.screen, required this.transitionKey})
: super(key: transitionKey);
final Widget screen;
final ValueKey transitionKey;
@override
Route createRoute(BuildContext context) {
return PageRouteBuilder(
settings: this,
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return SharedAxisTransition(
fillColor: Theme.of(context).cardColor,
animation: animation,
secondaryAnimation: secondaryAnimation,
transitionType: SharedAxisTransitionType.scaled,
child: child,
);
},
pageBuilder: (context, animation, secondaryAnimation) {
return screen;
});
}
}
اکنون، اجازه دهید از SharedAxisTransitionPageWrapper
جدید خود برای دستیابی به انتقال مورد نظر خود استفاده کنیم. در داخل تعریف کلاس ReplyRouterDelegate
، در زیر ویژگی pages
، اجازه دهید صفحه جستجوی خود را به جای CustomTransitionPage
با SharedAxisTransitionPageWrapper
بپیچیم:
روتر.دارت
return Navigator(
key: navigatorKey,
onPopPage: _handlePopPage,
pages: [
// TODO: Add Shared Z-Axis transition from search icon to search view page (Motion)
const CustomTransitionPage(
transitionKey: ValueKey('Home'),
screen: HomePage(),
),
if (routePath is ReplySearchPath)
const SharedAxisTransitionPageWrapper(
transitionKey: ValueKey('Search'),
screen: SearchPage(),
),
],
);
حالا سعی کنید برنامه را دوباره اجرا کنید.
همه چیز شروع به عالی شدن کرده است! هنگامی که روی نماد جستجو در نوار برنامه پایین کلیک میکنید، یک انتقال محور مشترک صفحه جستجو را به نمایش در میآورد. با این حال، توجه کنید که چگونه صفحه اصلی کوچک نمی شود و در عوض ثابت می ماند که صفحه جستجو در آن بزرگ می شود. بعلاوه، هنگام فشار دادن دکمه بازگشت، صفحه اصلی در حالت نمایش قرار نمی گیرد، در عوض زمانی که صفحه جستجو از دید خارج می شود، ثابت می ماند. پس هنوز کارمان تمام نشده است.
بیایید هر دو مشکل را با قرار دادن HomePage
با SharedAxisTransitionWrapper
به جای CustomTransitionPage
حل کنیم:
روتر.دارت
return Navigator(
key: navigatorKey,
onPopPage: _handlePopPage,
pages: [
// TODO: Add Shared Z-Axis transition from search icon to search view page (Motion)
const SharedAxisTransitionPageWrapper(
transitionKey: ValueKey('home'),
screen: HomePage(),
),
if (routePath is ReplySearchPath)
const SharedAxisTransitionPageWrapper(
transitionKey: ValueKey('search'),
screen: SearchPage(),
),
],
);
همین! حالا سعی کنید برنامه را دوباره اجرا کنید و روی نماد جستجو ضربه بزنید. صفحه نمایش خانه و صفحه نمایش جستجو باید به طور همزمان محو شوند و در امتداد محور Z در عمق بزرگ شوند و یک جلوه یکپارچه بین دو صفحه ایجاد کنند.
بعد از
8. انتقال Fade Through بین صفحات صندوق پست را اضافه کنید
در این مرحله، یک انتقال بین صندوق های پستی مختلف اضافه می کنیم. از آنجایی که نمیخواهیم بر یک رابطه فضایی یا سلسله مراتبی تأکید کنیم، از fade through برای انجام یک «تبادل» ساده بین فهرستهای ایمیل استفاده میکنیم.
قبل از افزودن کد اضافی، برنامه را اجرا کنید، روی نشانواره Reply در نوار برنامه پایین ضربه بزنید و صندوقهای پستی را تغییر دهید. لیست ایمیل ها باید بدون انتقال تغییر کند.
قبل از
برای شروع، اجازه دهید به فایل mail_view_router.dart
خود برویم. پس از تعریف کلاس MailViewRouterDelegate
قطعه زیر را اضافه کنید:
mail_view_router.dart
// TODO: Add Fade through transition between mailbox pages (Motion)
class FadeThroughTransitionPageWrapper extends Page {
const FadeThroughTransitionPageWrapper({
required this.mailbox,
required this.transitionKey,
}) : super(key: transitionKey);
final Widget mailbox;
final ValueKey transitionKey;
@override
Route createRoute(BuildContext context) {
return PageRouteBuilder(
settings: this,
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return FadeThroughTransition(
fillColor: Theme.of(context).scaffoldBackgroundColor,
animation: animation,
secondaryAnimation: secondaryAnimation,
child: child,
);
},
pageBuilder: (context, animation, secondaryAnimation) {
return mailbox;
});
}
}
مانند آخرین مرحله ما، بیایید از FadeThroughTransitionPageWrapper
جدید خود برای رسیدن به انتقال مورد نظر خود استفاده کنیم. در داخل تعریف کلاس MailViewRouterDelegate
، در زیر ویژگی pages
، به جای اینکه صفحه صندوق پستی خود را با CustomTransitionPage
بپیچید، به جای آن از FadeThroughTransitionPageWrapper
استفاده کنید:
mail_view_router.dart
return Navigator(
key: navigatorKey,
onPopPage: _handlePopPage,
pages: [
// TODO: Add Fade through transition between mailbox pages (Motion)
FadeThroughTransitionPageWrapper(
mailbox: InboxPage(destination: currentlySelectedInbox),
transitionKey: ValueKey(currentlySelectedInbox),
),
],
);
برنامه را دوباره اجرا کنید. وقتی کشوی پیمایش پایینی را باز میکنید و صندوقهای پستی را تغییر میدهید، فهرست فعلی ایمیلها باید محو و کوچک شود در حالی که فهرست جدید محو میشود. خوب است!
بعد از
9. انتقال Fade Through بین نوشتن و پاسخ FAB را اضافه کنید
در این مرحله، یک انتقال بین آیکون های مختلف FAB اضافه می کنیم. از آنجایی که نمیخواهیم بر یک رابطه فضایی یا سلسله مراتبی تأکید کنیم، از fade through برای انجام یک «تبادل» ساده بین نمادها در FAB استفاده میکنیم.
قبل از افزودن کد اضافی، برنامه را اجرا کنید، روی یک ایمیل ضربه بزنید و نمای ایمیل را باز کنید. نماد FAB باید بدون انتقال تغییر کند.
قبل از
ما در home.dart
برای بقیه کدلب کار خواهیم کرد، بنابراین نگران اضافه کردن واردات برای بسته انیمیشنها نباشید، زیرا قبلاً برای home.dart
در مرحله 2 برگشتیم.
روشی که ما دو ترانزیشن بعدی را پیکربندی می کنیم بسیار شبیه خواهد بود، زیرا همه آنها از یک کلاس قابل استفاده مجدد، _FadeThroughTransitionSwitcher
استفاده می کنند.
در home.dart
بیایید قطعه زیر را در زیر _ReplyFabState
اضافه کنیم:
خانه.دارت
// TODO: Add Fade through transition between compose and reply FAB (Motion)
class _FadeThroughTransitionSwitcher extends StatelessWidget {
const _FadeThroughTransitionSwitcher({
required this.fillColor,
required this.child,
});
final Widget child;
final Color fillColor;
@override
Widget build(BuildContext context) {
return PageTransitionSwitcher(
transitionBuilder: (child, animation, secondaryAnimation) {
return FadeThroughTransition(
fillColor: fillColor,
child: child,
animation: animation,
secondaryAnimation: secondaryAnimation,
);
},
child: child,
);
}
}
اکنون، در _ReplyFabState
ما، به دنبال ویجت fabSwitcher
بگردید. fabSwitcher
یک نماد متفاوت را بر اساس اینکه آیا در نمای ایمیل است یا نه، برمی گرداند. بیایید آن را با _FadeThroughTransitionSwitcher
خود ببندیم:
خانه.دارت
// TODO: Add Fade through transition between compose and reply FAB (Motion)
static final fabKey = UniqueKey();
static const double _mobileFabDimension = 56;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final circleFabBorder = const CircleBorder();
return Selector<EmailStore, bool>(
selector: (context, emailStore) => emailStore.onMailView,
builder: (context, onMailView, child) {
// TODO: Add Fade through transition between compose and reply FAB (Motion)
final fabSwitcher = _FadeThroughTransitionSwitcher(
fillColor: Colors.transparent,
child: onMailView
? Icon(
Icons.reply_all,
key: fabKey,
color: Colors.black,
)
: const Icon(
Icons.create,
color: Colors.black,
),
);
...
ما به _FadeThroughTransitionSwitcher
خود یک fillColor
شفاف می دهیم، بنابراین هیچ پس زمینه ای بین عناصر هنگام انتقال وجود ندارد. همچنین یک UniqueKey
ایجاد می کنیم و آن را به یکی از آیکون ها اختصاص می دهیم.
اکنون، در این مرحله، باید یک FAB متنی کاملا متحرک داشته باشید. رفتن به نمای ایمیل باعث می شود که نماد FAB قدیمی محو و کوچک شود در حالی که نماد جدید محو و کوچک می شود.
بعد از
10. انتقال Fade Through بین عنوان صندوق پستی ناپدید شده را اضافه کنید
در این مرحله، یک Fade through Transition اضافه میکنیم، تا از طریق عنوان صندوق پستی بین حالت قابل مشاهده و نامرئی هنگام نمایش ایمیل محو شود. از آنجایی که نمیخواهیم بر یک رابطه فضایی یا سلسله مراتبی تأکید کنیم، از یک fade through برای انجام یک "تبادل" ساده بین ویجت Text
که عنوان صندوق پستی را در بر میگیرد و یک SizedBox
خالی استفاده میکنیم.
قبل از افزودن کد اضافی، برنامه را اجرا کنید، روی یک ایمیل ضربه بزنید و نمای ایمیل را باز کنید. عنوان صندوق پستی باید بدون انتقال ناپدید شود.
قبل از
بقیه این کد لبه سریع خواهد بود زیرا ما قبلاً اکثر کارها را در _FadeThroughTransitionSwitcher
خود در آخرین مرحله خود انجام داده ایم.
حالا بیایید به کلاس _AnimatedBottomAppBar
خود در home.dart
برویم تا انتقال خود را اضافه کنیم. ما از آخرین مرحله از _FadeThroughTransitionSwitcher
مجددا استفاده خواهیم کرد و onMailView
خود را به صورت شرطی بسته بندی می کنیم، که یا یک SizedBox
خالی برمی گرداند، یا یک عنوان صندوق پستی که با کشوی پایینی محو می شود:
خانه.دارت
...
const _ReplyLogo(),
const SizedBox(width: 10),
// TODO: Add Fade through transition between disappearing mailbox title (Motion)
_FadeThroughTransitionSwitcher(
fillColor: Colors.transparent,
child: onMailView
? const SizedBox(width: 48)
: FadeTransition(
opacity: fadeOut,
child: Selector<EmailStore, String>(
selector: (context, emailStore) =>
emailStore.currentlySelectedInbox,
builder: (
context,
currentlySelectedInbox,
child,
) {
return Text(
currentlySelectedInbox,
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(
color: ReplyColors.white50,
),
);
},
),
),
),
تمام شد، این مرحله را تمام کردیم!
برنامه را دوباره اجرا کنید. وقتی ایمیلی را باز میکنید و به نمای ایمیل منتقل میشوید، عنوان صندوق پستی در نوار برنامه پایین باید محو و کوچک شود. عالی!
بعد از
11. انتقال Fade Through را بین اقدامات نوار برنامه پایین اضافه کنید
در این مرحله، یک Fade through Transition اضافه می کنیم، تا از طریق اعمال نوار برنامه پایین بر اساس زمینه برنامه ها محو شوند. از آنجایی که نمیخواهیم بر یک رابطه فضایی یا سلسله مراتبی تأکید کنیم، وقتی برنامه در صفحه اصلی است، زمانی که کشوی پایینی قابل مشاهده است، از یک fade through برای انجام یک «تبادل» ساده بین اعمال نوار برنامه پایین استفاده میکنیم. وقتی در نمای ایمیل هستیم.
قبل از افزودن کد اضافی، برنامه را اجرا کنید، روی یک ایمیل ضربه بزنید و نمای ایمیل را باز کنید. همچنین میتوانید روی نشانواره Reply ضربه بزنید. اقدامات نوار برنامه پایین باید بدون انتقال تغییر کند.
قبل از
مشابه مرحله آخر، ما دوباره از _FadeThroughTransitionSwitcher
خود استفاده خواهیم کرد. برای دستیابی به انتقال مورد نظر، به تعریف کلاس _BottomAppBarActionItems
خود بروید و ویجت بازگشتی تابع build()
خود را با یک _FadeThroughTransitionSwitcher
بپیچید:
خانه.دارت
// TODO: Add Fade through transition between bottom app bar actions (Motion)
return _FadeThroughTransitionSwitcher(
fillColor: Colors.transparent,
child: drawerVisible
? Align(
key: UniqueKey(),
alignment: AlignmentDirectional.bottomEnd,
child: IconButton(
icon: const Icon(Icons.settings),
color: ReplyColors.white50,
onPressed: () async {
drawerController.reverse();
showModalBottomSheet(
context: context,
shape: RoundedRectangleBorder(
borderRadius: modalBorder,
),
builder: (context) => const SettingsBottomSheet(),
);
},
),
)
: onMailView
...
حالا بیایید آن را امتحان کنیم! وقتی ایمیلی را باز میکنید و به نمای ایمیل هدایت میشوید، عملکردهای نوار پایین برنامه قدیمی باید محو و کوچک شوند در حالی که اقدامات جدید محو میشوند و بزرگ میشوند. آفرین!
بعد از
12. تبریک می گویم!
با استفاده از کمتر از 100 خط کد دارت، بسته انیمیشنها به شما کمک کرده است که انتقالهای زیبایی را در یک برنامه موجود ایجاد کنید که با دستورالعملهای طراحی متریال مطابقت دارد، و همچنین در همه دستگاهها بهطور ثابت ظاهر و رفتار میکند.
مراحل بعدی
برای اطلاعات بیشتر در مورد سیستم حرکت مواد، حتما دستورالعملها و مستندات کامل توسعهدهنده را بررسی کنید و سعی کنید چند انتقال مواد را به برنامه خود اضافه کنید!
از اینکه حرکت Material را امتحان کردید متشکریم. امیدواریم از این کد لبه لذت برده باشید!
من توانستم با صرف زمان و تلاش معقول این کد لبه را تکمیل کنم
من می خواهم در آینده از سیستم حرکت مواد استفاده کنم
از گالری فلاتر دیدن کنید
برای نمایش های بیشتر در مورد نحوه استفاده از ویجت های ارائه شده توسط کتابخانه Material Flutter، و همچنین چارچوب Flutter، حتما از گالری Flutter دیدن کنید. |