1. Pengantar
Desain Material adalah sistem untuk mem-build produk digital yang menarik dan indah. Dengan menyatukan gaya, branding, interaksi, dan gerakan di bawah kumpulan prinsip dan komponen yang konsisten, tim produk dapat mewujudkan potensi desain terbesar mereka.
Komponen Material (MDC) membantu developer menerapkan Desain Material. Dibuat oleh tim engineer dan desainer UX di Google, MDC memiliki banyak komponen UI yang indah dan fungsional serta tersedia untuk Android, iOS, web, dan Flutter.material.io/develop |
Apa itu sistem gerakan Material untuk Flutter?
Sistem gerakan Material untuk Flutter adalah pola transisi dalam paket animasi yang dapat membantu pengguna memahami dan membuka aplikasi, sebagaimana dijelaskan dalam pedoman Desain Material.
Keempat pola transisi Material utama adalah sebagai berikut:
- Transformasi Container: transisi antarelemen UI yang menyertakan container; membuat hubungan yang terlihat antara dua elemen UI yang berbeda dengan mengubah satu elemen menjadi elemen lainnya secara lancar.
- Sumbu Merata: transisi antarelemen UI yang memiliki hubungan spasial atau navigasi; menggunakan transformasi yang sama pada sumbu x, y, atau z untuk memperkuat hubungan antarelemen.
- Memudar: transisi antarelemen UI yang tidak memiliki hubungan kuat satu sama lain; menggunakan transisi memudar (jelas ke buram) dan makin jelas (buram ke jelas) yang berurutan, dengan skala elemen yang masuk.
- Memperjelas: digunakan untuk elemen UI yang masuk atau keluar dalam batasan layar.
Paket animasi menawarkan transisi widget untuk pola ini yang dibuat dari library animasi Flutter (flutter/animation.dart
) dan library material Flutter (flutter/material.dart
):
Dalam codelab ini, Anda akan menggunakan transisi Material yang dibuat dari framework Flutter dan library Material, yang berarti Anda akan menangani widget. :)
Yang akan Anda buat
Codelab ini akan memandu Anda mem-build beberapa transisi menjadi aplikasi email Flutter contoh yang disebut Reply, menggunakan Dart, untuk mendemonstrasikan cara Anda menggunakan transisi dari paket animasi untuk menyesuaikan tampilan dan nuansa aplikasi Anda.
Kode awal aplikasi Reply akan diberikan, dan Anda akan menyertakan transisi Material berikut ke dalam aplikasi, yang dapat dilihat di GIF codelab yang selesai di bawah:
- Transisi Transformasi Container dari daftar email menjadi halaman detail email
- Transisi Transformasi Container dari FAB menjadi halaman tulis email
- Transisi Sumbu Z Merata dari ikon penelusuran menjadi halaman tampilan penelusuran
- Transisi Memudar antara halaman kotak surat
- Transisi Memudar antara FAB tulis dan balas
- Transisi Memudar antara judul kotak surat yang menghilang
- Transisi Memudar antartindakan panel aplikasi bawah
Yang Anda butuhkan
- Pengetahuan dasar tentang pengembangan Flutter dan Dart
- Editor kode
- Emulator atau perangkat Android/iOS
- Kode contoh (lihat langkah berikutnya)
Bagaimana Anda menilai tingkat pengalaman Anda mem-build aplikasi Flutter?
Apa yang ingin Anda pelajari dari codelab ini?
2. Menyiapkan lingkungan pengembangan Flutter Anda
Anda memerlukan dua software untuk menyelesaikan lab ini—Flutter SDK dan editor.
Anda dapat menjalankan codelab menggunakan salah satu perangkat berikut:
- Perangkat Android atau iOS fisik yang terhubung ke komputer dan disetel ke mode Developer.
- Simulator iOS (perlu menginstal alat Xcode).
- Android Emulator (memerlukan penyiapan di Android Studio).
- Browser (Chrome diperlukan untuk proses debug).
- Aplikasi desktop Windows, Linux, atau macOS. Anda harus melakukan pengembangan di platform tempat Anda berencana men-deploy aplikasi. Jadi, jika ingin mengembangkan aplikasi desktop Windows, Anda harus mengembangkannya di Windows untuk mengakses rantai build yang sesuai. Ada persyaratan spesifik per sistem operasi yang dibahas secara mendetail di docs.flutter.dev/desktop.
3. Mendownload aplikasi awal codelab
Opsi 1: Meng-clone aplikasi codelab awal dari GitHub
Untuk meng-clone codelab ini dari GitHub, jalankan perintah berikut:
git clone https://github.com/material-components/material-components-flutter-motion-codelab.git cd material-components-flutter-motion-codelab
Opsi 2: Mendownload file ZIP aplikasi codelab awal
Aplikasi awal terletak di direktori material-components-flutter-motion-codelab-starter
.
Memverifikasi dependensi project
Project ini bergantung pada paket animasi. Di pubspec.yaml
, perhatikan bahwa bagian dependencies
mencakup kode berikut:
animations: ^2.0.0
Membuka project dan menjalankan aplikasi
- Buka project di editor pilihan Anda.
- Ikuti petunjuk untuk "Run the app" (Jalankan aplikasi) di Get Started: Test drive pada editor pilihan Anda.
Berhasil! Kode awal untuk halaman beranda Reply akan berjalan di perangkat/emulator Anda. Anda akan melihat Kotak masuk yang berisi daftar email.
Opsional: Memperlambat animasi perangkat
Karena codelab ini mencakup transisi yang cepat, tetapi belum sempurna, ada baiknya jika Anda memperlambat animasi perangkat untuk mengamati detail transisinya saat menerapkannya. Tindakan ini dapat dilakukan melalui setelan dalam aplikasi, yang dapat diakses dengan mengetuk ikon setelan saat panel samping bawah terbuka. Jangan khawatir, metode memperlambat animasi perangkat ini tidak akan memengaruhi animasi pada perangkat di luar aplikasi Reply.
Opsional: Mode Gelap
Jika tema terang Reply membuat mata Anda sakit, mode ini cocok untuk Anda. Terdapat setelan dalam aplikasi yang memungkinkan Anda mengubah tema aplikasi menjadi mode gelap agar tidak sakit di mata. Setelan ini dapat diakses dengan mengetuk ikon setelan saat panel samping bawah terbuka.
4. Memahami kode aplikasi contoh
Mari kita lihat kodenya. Kita telah menyediakan aplikasi yang menggunakan paket animasi untuk transisi antara berbagai layar di aplikasi.
- HomePage: menampilkan kotak surat tertentu
- InboxPage: menampilkan daftar email
- MailPreviewCard: menampilkan pratinjau email
- MailViewPage: menampilkan satu email lengkap
- ComposePage: memungkinkan penulisan email baru
- SearchPage: menampilkan tampilan penelusuran
router.dart
Pertama, untuk memahami cara penyiapan navigasi root aplikasi, buka router.dart
di direktori 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);
}
}
Ini adalah navigator root kita, dan navigator ini menangani layar aplikasi kita yang menggunakan seluruh kanvas, seperti HomePage
dan SearchPage
. Navigator memproses status aplikasi kita, memeriksa apakah kita telah menetapkan rute ke ReplySearchPath
. Jika kita telah menetapkannya, navigator akan di-build ulang dengan SearchPage
di bagian atas stack. Perhatikan bahwa layar kita digabungkan dalam CustomTransitionPage
tanpa transisi yang ditentukan. Hal ini menunjukkan satu cara untuk melakukan navigasi antara layar tanpa transisi kustom.
home.dart
Kita menetapkan rute ke ReplySearchPath
di status aplikasi kita dengan melakukan hal berikut di dalam _BottomAppBarActionItems
di home.dart
:
Align(
alignment: AlignmentDirectional.bottomEnd,
child: IconButton(
icon: const Icon(Icons.search),
color: ReplyColors.white50,
onPressed: () {
Provider.of<RouterProvider>(
context,
listen: false,
).routePath = const ReplySearchPath();
},
),
);
Di parameter onPressed
, kita mengakses RouterProvider
kita dan menetapkan routePath
-nya ke ReplySearchPath
. RouterProvider
kita melacak status navigator root kita.
mail_view_router.dart
Sekarang mari kita lihat cara navigasi bagian dalam aplikasi kita disiapkan, buka mail_view_router.dart
di direktori lib
. Anda akan melihat navigator yang serupa dengan yang di atas:
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,
),
)
],
);
},
);
}
...
}
Ini adalah navigator bagian dalam kita. Ini menangani layar bagian dalam aplikasi kita yang hanya memakai isi kanvas, seperti InboxPage
. InboxPage
menampilkan daftar email bergantung pada kotak surat saat ini di status aplikasi kita. Navigator di-build ulang dengan InboxPage
yang benar di atas, kapan pun terdapat perubahan pada properti currentlySelectedInbox
dari status aplikasi kita.
home.dart
Kita menetapkan kotak surat saat ini status aplikasi kita dengan melakukan hal berikut dalam _HomePageState
di 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(() {});
}
Di fungsi _onDestinationSelected
, kita mengakses EmailStore
dan menetapkan currentlySelectedInbox
-nya ke tujuan yang dipilih. EmailStore
kita melacak status navigator bagian dalam.
home.dart
Terakhir, untuk melihat contoh perutean navigasi yang sedang digunakan, buka home.dart
di direktori lib
. Temukan class _ReplyFabState
, di dalam properti onTap
widget InkWell
, yang akan terlihat seperti ini:
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();
},
),
);
},
Ini menunjukkan cara Anda menuju halaman tulis email, tanpa transisi kustom apa pun. Selama codelab ini, Anda akan mempelajari kode Reply untuk menyiapkan transisi Material yang berfungsi bersama-sama dengan berbagai tindakan navigasi di seluruh aplikasi.
Setelah Anda memahami kode awal, sekarang mari kita implementasikan transisi pertama kita.
5. Menambahkan transisi Transformasi Container dari daftar email ke halaman detail email
Untuk memulai, Anda akan menambahkan transisi dengan mengklik email. Pola transformasi container sangatlah cocok untuk perubahan navigasi ini, karena pola ini didesain untuk transisi antara elemen UI yang menyertakan container. Pola ini menghasilkan hubungan yang terlihat di antara dua elemen UI.
Sebelum menambahkan kode, coba jalankan aplikasi Reply dan klik email. Aplikasi akan menampilkan animasi melompat-memotong sederhana, yang berarti layar diganti tanpa transisi:
Sebelum
Mulai dengan menambahkan impor untuk paket animasi di atas mail_card_preview.dart
seperti yang ditampilkan di cuplikan berikut:
mail_card_preview.dart
import 'package:animations/animations.dart';
Setelah Anda memiliki impor untuk paket animasi, kita dapat mulai menambahkan transisi yang indah ke aplikasi. Mari kita mulai dengan membuat class StatelessWidget
yang akan menjadi rumah untuk widget OpenContainer
.
Di mail_card_preview.dart
, tambahkan cuplikan kode berikut setelah definisi class 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,
);
},
);
}
}
Sekarang mari kita gunakan wrapper baru kita. Di dalam definisi class MailPreviewCard
, kita akan menggabungkan widget Material
dari fungsi build()
kita dengan _OpenContainerWrapper
baru:
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
kita memiliki widget InkWell
dan properti warna OpenContainer
menentukan warna container yang ditutupnya. Oleh karena itu, kita dapat menghapus widget Material dan Inkwell. Kode yang dihasilkan akan terlihat seperti berikut:
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,
),
);
Pada tahap ini, Anda akan memiliki transformasi container yang berfungsi sepenuhnya. Mengklik email akan meluaskan item daftar menjadi layar detail sembari menyurutkan daftar email. Menekan tombol kembali akan menutup layar detail email kembali menjadi daftar item sembari meningkatkan skala di daftar email.
Setelah
6. Menambahkan transisi Transformasi Container dari FAB ke halaman tulis email
Mari kita lanjutkan dengan transformasi container dan kita tambahkan transisi dari Tombol Tindakan Mengambang ke ComposePage
yang meluaskan FAB ke email baru yang akan ditulis oleh pengguna. Pertama-tama, jalankan ulang aplikasi klik FAB untuk melihat bahwa tidak ada transisi ketika meluncurkan layar tulis email.
Sebelum
Cara mengonfigurasi transisi ini akan sangat mirip dengan cara kita melakukannya di langkah sebelumnya, karena kita menggunakan class widget yang sama, OpenContainer
.
Di home.dart
, mari kita impor package:animations/animations.dart
di bagian paling atas file, dan kita ubah metode _ReplyFabState
build()
. Selanjutnya, kita gabungkan widget Material
yang ditampilkan dengan widget OpenContainer
:
home.dart
// 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,
...
Selain parameter yang digunakan untuk mengonfigurasi widget OpenContainer
kita sebelumnya, onClosed
kini juga ditetapkan. onClosed
adalah ClosedCallback
yang dipanggil ketika rute OpenContainer
telah muncul atau telah ditampilkan ke status tertutup. Nilai pengembalian transaksi tersebut diteruskan ke fungsi ini sebagai argumen. Kita menggunakan Callback
ini untuk memberi tahu penyedia aplikasi Anda bahwa kita telah meninggalkan rute ComposePage
, sehingga penyedia aplikasi dapat memberi tahu semua pemroses.
Sama halnya dengan yang kita lakukan di langkah sebelumnya, kita akan menghapus widget Material
dari widget kita karena widget OpenContainer
menangani warna widget yang ditampilkan oleh closedBuilder
dengan closedColor
. Kita juga akan menghapus panggilan Navigator.push()
di dalam onTap
widget InkWell, dan menggantinya dengan openContainer() Callback
yang diberikan oleh closedBuilder
widget OpenContainer
, karena kini widget OpenContainer
menangani peruteannya sendiri.
Kode yang dihasilkan adalah sebagai berikut:
home.dart
// 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,
),
),
),
);
},
);
Sekarang untuk membersihkan beberapa kode lama. Karena widget OpenContainer
kini menangangi pemberitahuan kepada penyedia aplikasi kita bahwa kita tidak lagi di ComposePage
melalui onClosed ClosedCallback
, kita dapat menghapus implementasi kita sebelumnya di 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);
Itu saja untuk langkah ini! Anda harus memiliki transisi dari FAB ke layar tulis yang terlihat seperti berikut:
Setelah
7. Menambahkan transisi Sumbu Z Merata dari ikon penelusuran ke halaman tampilan penelusuran
Di langkah ini, kita akan menambahkan transisi dari ikon penelusuran ke tampilan penelusuran layar penuh. Karena tidak ada container yang dilibatkan dalam perubahan navigasi ini, kita dapat menggunakan transisi Sumbu Z Merata untuk memperkuat hubungan spasial antara dua layar dan menunjukkan pemindahan satu tingkat ke atas di hierarki aplikasi.
Sebelum menambahkan kode tambahan apa pun, coba jalankan aplikasi dan ketuk ikon penelusuran di pojok kanan bawah layar. Tindakan ini akan memunculkan layar tampilan penelusuran tanpa transisi.
Sebelum
Untuk memulai, mari kita buka file router.dart
kita. Setelah definisi class ReplySearchPath
, tambahkan cuplikan berikut:
router.dart
// 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;
});
}
}
Sekarang, mari kita gunakan SharedAxisTransitionPageWrapper
baru kita untuk mencapai transisi yang diinginkan. Di dalam definisi class ReplyRouterDelegate
, di bagian properti pages
, mari kita gabungkan layar penelusuran kita dengan SharedAxisTransitionPageWrapper
, bukan CustomTransitionPage
:
router.dart
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(),
),
],
);
Sekarang, coba jalankan ulang aplikasinya.
Transisinya mulai terlihat baik. Saat Anda mengklik ikon penelusuran di panel aplikasi bawah, transisi sumbu merata menskalakan halaman penelusuran menjadi terlihat. Namun, perhatikan bagaimana halaman utama tidak diskalakan dan tetap statis saat halaman penelusuran diskalakan di atasnya. Selain itu, saat menekan tombol kembali, halaman utama tidak diskalakan menjadi terlihat, melainkan tetap statis saat halaman penelusuran diskalakan ke luar tampilan. Kita belum selesai.
Mari kita perbaiki kedua masalah itu dengan menggabungkan juga HomePage
dengan SharedAxisTransitionWrapper
kita, bukan dengan CustomTransitionPage
:
router.dart
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(),
),
],
);
Selesai. Sekarang coba jalankan ulang aplikasi dan ketuk ikon penelusuran. Layar utama dan tampilan penelusuran akan secara bersamaan memudar dan diskalakan searah sumbu Z ke bagian dalam, membuat efek yang mulus antara dua layar.
Setelah
8. Menambahkan transisi Memudar antarhalaman kotak surat
Di langkah ini, kita akan menambahkan transisi di antara kotak surat yang berbeda. Karena kita tidak ingin menekankan hubungan spasial atau hierarki, kita akan menggunakan transisi memudar untuk melakukan "pertukaran" sederhana antara daftar email.
Sebelum menambahkan kode tambahan, coba jalankan aplikasi, ketuk logo Reply di Panel Aplikasi Bawah, dan beralihlah antara kotak surat. Daftar email akan berubah tanpa transisi.
Sebelum
Untuk memulai, mari kita buka file mail_view_router.dart
kita. Setelah definisi class MailViewRouterDelegate
kita, tambahkan cuplikan berikut:
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;
});
}
}
Sama seperti langkah sebelumnya, mari kita gunakan FadeThroughTransitionPageWrapper
baru kita untuk mencapai transisi yang diinginkan. Di dalam definisi class MailViewRouterDelegate
, di bagian properti pages
, alih-alih menggabungkan layar kotak surat kita dengan CustomTransitionPage
, gunakan 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),
),
],
);
Jalankan ulang aplikasi. Saat Anda membuka panel navigasi bawah dan mengubah kotak surat, daftar email yang ada saat ini akan diskalakan dan memudar, sementara daftar baru akan diskalakan dan makin jelas. Bagus!
Setelah
9. Menambahkan transisi Memudar antara FAB tulis dan balas
Di langkah ini, kita akan menambahkan transisi di antara ikon FAB yang berbeda. Karena kita tidak ingin menekankan hubungan spasial atau hierarki, kita akan menggunakan transisi memudar untuk melakukan "swap" sederhana antara ikon di FAB.
Sebelum menambahkan kode tambahan apa pun, cobalah menjalankan aplikasi, mengetuk email, dan membuka tampilan email. Ikon FAB akan berubah tanpa transisi.
Sebelum
Kita akan bekerja dalam home.dart
di sisa codelab ini, jadi Anda tidak perlu khawatir mengenai penambahan impor untuk paket animasi, karena kita sudah melakukannya untuk home.dart
di langkah 2.
Cara kita mengonfigurasi beberapa transisi berikutnya akan sangat mirip, karena semua transisi tersebut akan memanfaatkan class yang dapat digunakan kembali, _FadeThroughTransitionSwitcher
.
Di home.dart
, mari kita tambahkan cuplikan berikut di bawah _ReplyFabState
:
home.dart
// 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,
);
}
}
Sekarang, di _ReplyFabState
kita, cari widget fabSwitcher
. fabSwitcher
menampilkan ikon yang berbeda berdasarkan apakah itu tampilan email atau bukan. Mari kita gabungkan dengan _FadeThroughTransitionSwitcher
kita:
home.dart
// 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,
),
);
...
Kita memberikan fillColor
transparan ke _FadeThroughTransitionSwitcher
, sehingga tidak ada latar belakang antarelemen saat bertransisi. Kita juga membuat UniqueKey
dan menetapkannya ke salah satu ikon.
Sekarang, di langkah ini, Anda akan memiliki FAB kontekstual yang memiliki animasi penuh. Membuka tampilan email menyebabkan ikon FAB yang lama diskalakan dan memudar, sementara yang baru diskalan dan makin jelas.
Setelah
10. Menambahkan transisi Memudar antara judul kotak surat yang menghilang
Di langkah ini, kita akan menambahkan transisi memudar, untuk memudarkan judul kotak surat antara status terlihat dan tidak terlihat saat berada di tampilan email. Karena kita tidak ingin menekankan hubungan spasial atau hierarki, kita akan menggunakan memudar untuk melakukan "pertukaran" sederhana antara widget Text
yang mencakup judul kotak surat, dan SizedBox
kosong.
Sebelum menambahkan kode tambahan apa pun, cobalah menjalankan aplikasi, mengetuk email, dan membuka tampilan email. Judul kotak surat akan menghilang tanpa transisi.
Sebelum
Bagian yang tersisa di codelab ini tidak akan memerlukan banyak waktu karena kita sudah melakukan sebagian besar pekerjaan di _FadeThroughTransitionSwitcher
pada langkah terakhir.
Sekarang, mari kita buka class _AnimatedBottomAppBar
di home.dart
untuk menambahkan transisi. Kita akan menggunakan kembali _FadeThroughTransitionSwitcher
dari langkah sebelumnya, dan menggabungkan onMailView
bersyarat, yang menampilkan SizedBox
kosong, atau judul kotak surat yang memudar bersamaan dengan panel samping bawah:
home.dart
...
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
.bodyText1!
.copyWith(
color: ReplyColors.white50,
),
);
},
),
),
),
Selesai, itu saja yang harus dilakukan di langkah ini.
Jalankan ulang aplikasi. Ketika Anda membuka email dan diarahkan ke tampilan email, judul kotak surat di panel aplikasi bawah akan diskalakan dan memudar. Keren!
Setelah
11. Menambahkan transisi Memudar antartindakan panel aplikasi bawah
Di langkah ini, kita akan menambahkan transisi memudar, untuk memudarkan tindakan panel aplikasi bawah berdasarkan konteks aplikasi. Karena kita tidak ingin menekankan hubungan spasial atau hierarki, kita akan menggunakan memudar untuk melakukan "pertukaran" sederhana antara tindakan panel aplikasi bawah di HomePage, ketika panel samping bawah terlihat, dan ketika sedang berada di tampilan email.
Sebelum menambahkan kode tambahan apa pun, cobalah menjalankan aplikasi, mengetuk email, dan membuka tampilan email. Anda juga dapat mencoba mengetuk logo Reply. Tindakan panel aplikasi bawah akan berubah tanpa transisi.
Sebelum
Mirip dengan langkah sebelumnya, kita akan memanfaatkan _FadeThroughTransitionSwitcher
kita lagi. Untuk mencapai transisi yang diinginkan, buka definisi class _BottomAppBarActionItems
dan gabungkan widget kembali dari fungsi build()
kita dengan _FadeThroughTransitionSwitcher
:
home.dart
// 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
...
Sekarang, mari kita coba. Saat Anda membuka email dan diarahkan ke tampilan email, panel aplikasi bawah yang lama akan diskalakan dan memudar sementara tindakan baru akan diskalakan dan makin jelas. Bagus!
Setelah
12. Selamat!
Dengan menggunakan kurang dari 100 baris kode Dart, paket animasi telah membantu Anda membuat transisi yang indah di aplikasi yang sudah ada yang mematuhi pedoman Desain Material, dan terlihat serta berfungsi secara konsisten di seluruh perangkat.
Langkah berikutnya
Untuk informasi selengkapnya mengenai sistem gerakan Material, pastikan untuk memeriksa spek dan dokumentasi developer lengkap, dan coba tambahkan beberapa transisi Material ke aplikasi Anda.
Terima kasih telah mencoba gerakan Material. Kami harap Anda menikmati codelab ini.
Saya dapat menyelesaikan codelab ini dengan upaya dan dalam durasi waktu yang wajar
Saya ingin terus menggunakan sistem gerakan Material pada masa mendatang
Lihat Flutter Gallery
Untuk demo lainnya terkait cara menggunakan widget yang disediakan oleh library Flutter Material serta framework Flutter, pastikan untuk mengunjungi Flutter Gallery. |