- vừa được xem lúc

Flutter: Hiệu Ứng Chuyển Trang của Navigator giữ yên AppBar và BottomNavigationBar

0 0 82

Người đăng: anh le

Theo Viblo Asia

Mở bài

Khi làm việc với Flutter các anh newbie chắc chắn gặp một vấn đề lớn về hiệu ứng Navigation. Sau đây mình xin trình bày một hướng dẫn nhỏ để giúp anh em newbie đỡ bỡ ngỡ và có cái nhìn đơn giản hơn về Navigator trong Flutter. Có rất nhiều bài hướng dẫn làm theo cách sau:

import 'package:flutter/material.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({Key key}) : super(key: key); @override Widget build(BuildContext context) { return const MaterialApp( title: 'Flutter Code Sample', home: MyStatefulWidget(), ); }
} class MyStatefulWidget extends StatefulWidget { const MyStatefulWidget({Key key}) : super(key: key); @override _MyStatefulWidgetState createState() => _MyStatefulWidgetState();
} class _MyStatefulWidgetState extends State<MyStatefulWidget> { int _selectedIndex = 0; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Navigation Sample'), ), body: IndexedStack( children: [ HomePage(), FavoritePage(), ], index: _selectedIndex, ), bottomNavigationBar: BottomNavigationBar( items: [ BottomNavigationBarItem( icon: Icon(Icons.home), label: 'Home', ), BottomNavigationBarItem( icon: Icon(Icons.favorite), label: 'Favorite', ), ], backgroundColor: Colors.blueAccent, currentIndex: _selectedIndex, selectedItemColor: Colors.amber[800], onTap: (value) { return setState(() { _selectedIndex = value; }); }, ), ); }
} class FavoritePage extends StatelessWidget { @override Widget build(BuildContext context) { return Container( color: Colors.greenAccent, alignment: Alignment.center, child: Text('This is favorite page'), ); }
} class HomePage extends StatelessWidget { @override Widget build(BuildContext context) { return Container( color: Colors.yellowAccent, alignment: Alignment.center, child: Text('This is home page'), ); }
}

Trên đây là một cách làm thường thấy, đó là dùng IndexedStack để chứa các màn hình con và thực hiện các hiệu ứng chuyển trang trong IndexedStack. Nhưng đối với mình nó có một nhược điểm lớn, vấn đề nằm ở hàm khởi tạo của các màn hình con. Phần lớn những bạn sử dụng BloC sẽ viết các logic code để gọi api trong hàm khởi tạo của các màn hình con, ngoài ra còn có nhiều logic khác đặt trong hàm khởi tạo nếu chúng ta hay sử dụng kế thừa đến StatelessWidget, vì chỉ có StatefulWidget mới có hàm initState. Để giải quyết nhược điểm này có rất nhiều cách, nhưng trong bài viết này mình sẽ bỏ qua việc giải quyết nhược điểm đó, mà thay vào đó mình sẽ tập trung vào việc thêm hiệu ứng trong khi thực hiện Navigator.push().

Flutter đã cung cấp cho chúng ta một hàm chuyển trang có hiệu ứng sẵn MaterialPageRoute (hãy xem demo bên dưới).

import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Code Sample', home: HomePage(), ); }
} class FavoritePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Navigation Sample'), ), body: Container( color: Colors.greenAccent, alignment: Alignment.center, child: Text('This is favorite page'), ), bottomNavigationBar: BottomNavigationBar( items: [ BottomNavigationBarItem( icon: Icon(Icons.home), label: 'Home', ), BottomNavigationBarItem( icon: Icon(Icons.favorite), label: 'Favorite', ), ], backgroundColor: Colors.blueAccent, selectedItemColor: Colors.amber[800], onTap: (index) { Navigator.push( context, MaterialPageRoute( builder: (context) { Widget widget; if (index == 0) { widget = HomePage(); } if (index == 1) { widget = FavoritePage(); } return widget; }, ), ); }, ), ); }
} class HomePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Navigation Sample'), ), body: Container( color: Colors.yellowAccent, alignment: Alignment.center, child: Text('This is home page'), ), bottomNavigationBar: BottomNavigationBar( items: [ BottomNavigationBarItem( icon: Icon(Icons.home), label: 'Home', ), BottomNavigationBarItem( icon: Icon(Icons.favorite), label: 'Favorite', ), ], backgroundColor: Colors.blueAccent, selectedItemColor: Colors.amber[800], onTap: (index) { Navigator.push( context, MaterialPageRoute( builder: (context) { Widget widget; if (index == 0) { widget = HomePage(); } if (index == 1) { widget = FavoritePage(); } return widget; }, ), ); }, ), ); }
}

Nhưng phần lớn chúng ta sẽ không sử dụng hiệu ứng chuyển trang của MaterialPageRoute mà sẽ viết lại hiệu ứng bằng PageRouteBuilder (đã có rất nhiều bài viết hướng dẫn cách thay đổi hiệu ứng chuyển trang tại đây). Nhưng vấn đề tiếp tục gặp phải, đó là khi chúng ta thực hiện Navigator.push() với PageRouteBuilder và hiệu ứng của chúng ta, thì toàn bộ màn hình của chúng ta sẽ nhận hiệu ứng (từ AppBar,body,bottomNavigationBar... có trên màn hình là nhận). Vậy có những trường hợp mình chỉ muốn thay đổi một phần của màn hình. Ví dụ: Hãy xem các ứng dụng Zalo, Shopee, Lazada. Mình chỉ muốn áp dụng hiệu ứng trượt sang phải cho body trong thẻ Scaffold thôi, mình muốn AppBarbottomNavigationBar phải đứng yên, chỉ thay đổi nội dung và hiệu ứng trượt cho body. Dưới đây là một trick nhỏ để làm việc đó.

import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Code Sample', home: HomePage(), ); }
} class FavoritePage extends StatelessWidget { final Animation<double> animation; FavoritePage({Key key, this.animation}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Navigation Sample'), ), body: AnimatedBuilder( child: Container( color: Colors.greenAccent, alignment: Alignment.center, child: Text('This is favorite page'), ), animation: animation, builder: (context, child) { return SlideTransition( position: Tween<Offset>( begin: Offset(1, 0), end: Offset(0, 0), ).animate(animation), child: child, ); }, ), bottomNavigationBar: BottomNavigationBar( items: [ BottomNavigationBarItem( icon: Icon(Icons.home), label: 'Home', ), BottomNavigationBarItem( icon: Icon(Icons.favorite), label: 'Favorite', ), ], backgroundColor: Colors.blueAccent, selectedItemColor: Colors.amber[800], onTap: (index) { Navigator.push( context, PageRouteBuilder( pageBuilder: (context, animation, secondaryAnimation) { Widget widget; if (index == 0) { widget = HomePage(); } if (index == 1) { widget = FavoritePage( animation: animation, ); } return widget; }, ), ); }, ), ); }
} class HomePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Navigation Sample'), ), body: Container( color: Colors.yellowAccent, alignment: Alignment.center, child: Text('This is home page'), ), bottomNavigationBar: BottomNavigationBar( items: [ BottomNavigationBarItem( icon: Icon(Icons.home), label: 'Home', ), BottomNavigationBarItem( icon: Icon(Icons.favorite), label: 'Favorite', ), ], backgroundColor: Colors.blueAccent, selectedItemColor: Colors.amber[800], onTap: (index) { Navigator.push( context, PageRouteBuilder( pageBuilder: (context, animation, secondaryAnimation) { Widget widget; if (index == 0) { widget = HomePage(); } if (index == 1) { widget = FavoritePage( animation: animation, ); } return widget; }, ), ); }, ), ); }
}

Hãy tập trung vào class FavoritePage lúc này body đã nhận được hiệu ứng trượt, và chỉ body được nhận. Với cách làm trên, chúng ta sẽ tự viết code trong hàm khởi tạo của màn hình bất kể khi nào chúng ta muốn, chúng ta có thể truyền params mà không sợ trường hợp không biết params là gì khi khởi tạo với IndexStack.

Mình xin chấm dứt bài viết tại đây. Các bạn hãy chạy thử các đoạn code trên để hiểu rõ hơn. Cảm ơn các bạn đã theo dõi. Bài viết có gì sai sót rất mong nhận được sự đóng góp của các bạn và các tiền bối. Bài viết tham khảo tại đây

Bình luận

Bài viết tương tự

- vừa được xem lúc

Học Flutter từ cơ bản đến nâng cao. Phần 1: Làm quen cô nàng Flutter

Lời mở đầu. Gần đây, Flutter nổi lên và được Google PR như một xu thế của lập trình di động vậy.

0 0 281

- vừa được xem lúc

Học Flutter từ cơ bản đến nâng cao. Phần 3: Lột trần cô nàng Flutter, BuildContext là gì?

Lời mở đầu. Màn làm quen cô nàng FLutter ở Phần 1 đã gieo rắc vào đầu chúng ta quá nhiều điều bí ẩn về nàng Flutter.

0 0 206

- vừa được xem lúc

Flutter Animation: Creating medium’s clap animation in flutte Part II

Trong phần 1 mình đã giới thiệu với các bạn cơ bản về Animation trong Flutter. Score Widget Size Animation.

0 0 64

- vừa được xem lúc

Flutter - GetX - Using GetConnect to handle API request (Part 4)

Giới thiệu. Xin chào các bạn, lại là mình với series về GetX và Flutter.

0 0 351

- vừa được xem lúc

StatefulWidget và StatelessWidget trong Flutter

I. Mở đầu. Khi các bạn build một ứng dụng với Flutter thì Widgets là thứ không thể thiếu đúng không ạ. Và 2 loại Widget không thể thiếu đó là StatefullWidget và StatelessWidget.

0 0 143

- vừa được xem lúc

Tìm hiểu về Riverpod - Provider nhưng không hắn :v

Trong Flutter có rất nhiều các quản lý state: Provider, Bloc, GetX, Redux,... khó mà nói cái nào tốt hơn cái nào. Tuy nhiên nếu bạn đã làm quen với Provider thì không ngại để tìm hiểu thêm về Riverpod. Một bản nâng cấp của Provider. Nếu bạn để ý thì cái tên "Riverpod" là các chữ cái của "Provider" đ

0 0 67