走进Navigator 2.0

image.png

RoutePath

image.png

RouteInformationParser类

image.png

创建页面

image.png

RouteDelegate类

image.png
image.png
image.png

使用

image.png

调用流程

image.png

封装导航框架

hi_navigator.dart

  1. typedef RouteChangeListener(RouteStatusInfo current, RouteStatusInfo pre);
  2. ///创建页面
  3. pageWrap(Widget child) {
  4. return MaterialPage(key: ValueKey(child.hashCode), child: child);
  5. }
  6. ///获取routeStatus在页面栈中的位置
  7. int getPageIndex(List<MaterialPage> pages, RouteStatus routeStatus) {
  8. for (int i = 0; i < pages.length; i++) {
  9. MaterialPage page = pages[i];
  10. if (getStatus(page) == routeStatus) {
  11. return i;
  12. }
  13. }
  14. return -1;
  15. }
  16. ///自定义路由封装,路由状态
  17. enum RouteStatus { login, registration, home, detail, unknown }
  18. ///获取page对应的RouteStatus
  19. RouteStatus getStatus(MaterialPage page) {
  20. if (page.child is LoginPage) {
  21. return RouteStatus.login;
  22. } else if (page.child is RegistrationPage) {
  23. return RouteStatus.registration;
  24. } else if (page.child is BottomNavigator) {
  25. return RouteStatus.home;
  26. } else if (page.child is VideoDetailPage) {
  27. return RouteStatus.detail;
  28. } else {
  29. return RouteStatus.unknown;
  30. }
  31. }
  32. ///路由信息
  33. class RouteStatusInfo {
  34. final RouteStatus routeStatus;
  35. final Widget page;
  36. RouteStatusInfo(this.routeStatus, this.page);
  37. }
  38. ///监听路由页面跳转
  39. ///感知当前页面是否压后台
  40. class HiNavigator extends _RouteJumpListener {
  41. static HiNavigator _instance;
  42. RouteJumpListener _routeJump;
  43. List<RouteChangeListener> _listeners = [];
  44. RouteStatusInfo _current;
  45. //首页底部tab
  46. RouteStatusInfo _bottomTab;
  47. HiNavigator._();
  48. static HiNavigator getInstance() {
  49. if (_instance == null) {
  50. _instance = HiNavigator._();
  51. }
  52. return _instance;
  53. }
  54. ///首页底部tab切换监听
  55. void onBottomTabChange(int index, Widget page) {
  56. _bottomTab = RouteStatusInfo(RouteStatus.home, page);
  57. _notify(_bottomTab);
  58. }
  59. ///注册路由跳转逻辑
  60. void registerRouteJump(RouteJumpListener routeJumpListener) {
  61. this._routeJump = routeJumpListener;
  62. }
  63. ///监听路由页面跳转
  64. void addListener(RouteChangeListener listener) {
  65. if (!_listeners.contains(listener)) {
  66. _listeners.add(listener);
  67. }
  68. }
  69. ///移除监听
  70. void removeListener(RouteChangeListener listener) {
  71. _listeners.remove(listener);
  72. }
  73. @override
  74. void onJumpTo(RouteStatus routeStatus, {Map args}) {
  75. _routeJump.onJumpTo(routeStatus, args: args);
  76. }
  77. ///通知路由页面变化
  78. void notify(List<MaterialPage> currentPages, List<MaterialPage> prePages) {
  79. if (currentPages == prePages) return;
  80. var current =
  81. RouteStatusInfo(getStatus(currentPages.last), currentPages.last.child);
  82. _notify(current);
  83. }
  84. void _notify(RouteStatusInfo current) {
  85. if (current.page is BottomNavigator && _bottomTab != null) {
  86. //如果打开的是首页,则明确到首页具体的tab
  87. current = _bottomTab;
  88. }
  89. print('hi_navigator:current:${current.page}');
  90. print('hi_navigator:pre:${_current?.page}');
  91. _listeners.forEach((listener) {
  92. listener(current, _current);
  93. });
  94. _current = current;
  95. }
  96. }
  97. ///抽象类供HiNavigator实现
  98. abstract class _RouteJumpListener {
  99. void onJumpTo(RouteStatus routeStatus, {Map args});
  100. }
  101. typedef OnJumpTo = void Function(RouteStatus routeStatus, {Map args});
  102. ///定义路由跳转逻辑要实现的功能
  103. class RouteJumpListener {
  104. final OnJumpTo onJumpTo;
  105. RouteJumpListener({this.onJumpTo});
  106. }

main.dart

image.png

  1. class BiliRouteDelegate extends RouterDelegate<BiliRoutePath>
  2. with ChangeNotifier, PopNavigatorRouterDelegateMixin<BiliRoutePath> {
  3. final GlobalKey<NavigatorState> navigatorKey;
  4. //为Navigator设置一个key,必要的时候可以通过navigatorKey.currentState来获取到NavigatorState对象
  5. BiliRouteDelegate() : navigatorKey = GlobalKey<NavigatorState>() {
  6. //实现路由跳转逻辑
  7. HiNavigator.getInstance().registerRouteJump(
  8. RouteJumpListener(onJumpTo: (RouteStatus routeStatus, {Map args}) {
  9. _routeStatus = routeStatus;
  10. if (routeStatus == RouteStatus.detail) {
  11. this.videoModel = args['videoMo'];
  12. }
  13. notifyListeners();
  14. }));
  15. /*
  16. //设置网络错误拦截器
  17. HiNet.getInstance().setErrorInterceptor((error) {
  18. if (error is NeedLogin) {
  19. //清空失效的登录令牌
  20. HiCache.getInstance().setString(LoginDao.BOARDING_PASS, null);
  21. //拉起登录
  22. HiNavigator.getInstance().onJumpTo(RouteStatus.login);
  23. }
  24. });
  25. */
  26. }
  27. RouteStatus _routeStatus = RouteStatus.home;
  28. List<MaterialPage> pages = [];
  29. VideoModel videoModel;
  30. @override
  31. Widget build(BuildContext context) {
  32. var index = getPageIndex(pages, routeStatus);
  33. List<MaterialPage> tempPages = pages;
  34. if (index != -1) {
  35. //要打开的页面在栈中已存在,则将该页面和它上面的所有页面进行出栈
  36. //tips 具体规则可以根据需要进行调整,这里要求栈中只允许有一个同样的页面的实例
  37. tempPages = tempPages.sublist(0, index);
  38. }
  39. var page;
  40. if (routeStatus == RouteStatus.home) {
  41. //跳转首页时将栈中其它页面进行出栈,因为首页不可回退
  42. pages.clear();
  43. page = pageWrap(BottomNavigator());
  44. } else if (routeStatus == RouteStatus.detail) {
  45. page = pageWrap(VideoDetailPage(videoModel));
  46. } else if (routeStatus == RouteStatus.registration) {
  47. page = pageWrap(RegistrationPage());
  48. } else if (routeStatus == RouteStatus.login) {
  49. page = pageWrap(LoginPage());
  50. }
  51. //重新创建一个数组,否则pages因引用没有改变路由不会生效
  52. tempPages = [...tempPages, page];
  53. //通知路由发生变化
  54. HiNavigator.getInstance().notify(tempPages, pages);
  55. pages = tempPages;
  56. return WillPopScope(
  57. //fix Android物理返回键,无法返回上一页问题@https://github.com/flutter/flutter/issues/66349
  58. onWillPop: () async => !await navigatorKey.currentState.maybePop(),
  59. child: Navigator(
  60. key: navigatorKey,
  61. pages: pages,
  62. onPopPage: (route, result) {
  63. if (route.settings is MaterialPage) {
  64. //登录页未登录返回拦截
  65. if ((route.settings as MaterialPage).child is LoginPage) {
  66. if (!hasLogin) {
  67. showWarnToast("请先登录");
  68. return false;
  69. }
  70. }
  71. }
  72. //执行返回操作
  73. if (!route.didPop(result)) {
  74. return false;
  75. }
  76. var tempPages = [...pages];
  77. pages.removeLast();
  78. //通知路由发生变化
  79. HiNavigator.getInstance().noify(pages, tempPages);
  80. return true;
  81. },
  82. ),
  83. );
  84. }
  85. RouteStatus get routeStatus {
  86. if (_routeStatus != RouteStatus.registration && !hasLogin) {
  87. return _routeStatus = RouteStatus.login;
  88. } else if (videoModel != null) {
  89. return _routeStatus = RouteStatus.detail;
  90. } else {
  91. return _routeStatus;
  92. }
  93. }
  94. bool get hasLogin => LoginDao.getBoardingPass() != null;
  95. @override
  96. Future<void> setNewRoutePath(BiliRoutePath path) async {}
  97. }
  98. ///定义路由数据,path
  99. class BiliRoutePath {
  100. final String location;
  101. BiliRoutePath.home() : location = "/";
  102. BiliRoutePath.detail() : location = "/detail";
  103. }

home_page.dart

  1. class HomePage extends StatefulWidget {
  2. const HomePage({Key key}) : super(key: key);
  3. @override
  4. _HomePageState createState() => _HomePageState();
  5. }
  6. class _HomePageState extends State<HomePage>
  7. with AutomaticKeepAliveClientMixin, TickerProviderStateMixin {
  8. var listener;
  9. TabController _controller;
  10. var tabs = ["推荐", "热门", "追播", "影视", "搞笑", "日常", "综合", "手机游戏", "短片·手书·配音"];
  11. @override
  12. void initState() {
  13. super.initState();
  14. _controller = TabController(length: tabs.length, vsync: this);
  15. HiNavigator.getInstance().addListener(this.listener = (current, pre) {
  16. print('home:current:${current.page}');
  17. print('home:pre:${pre.page}');
  18. if (widget == current.page || current.page is HomePage) {
  19. print('打开了首页:onResume');
  20. } else if (widget == pre?.page || pre?.page is HomePage) {
  21. print('首页:onPause');
  22. }
  23. });
  24. }
  25. @override
  26. void dispose() {
  27. HiNavigator.getInstance().removeListener(this.listener);
  28. super.dispose();
  29. }
  30. @override
  31. Widget build(BuildContext context) {
  32. super.build(context);
  33. return Scaffold(
  34. body: Column(
  35. children: [
  36. Container(
  37. color: Colors.white,
  38. padding: EdgeInsets.only(top: 30),
  39. child: _tabBar(),
  40. ),
  41. Flexible(
  42. child: TabBarView(
  43. controller: _controller,
  44. children: tabs.map((tab) {
  45. return HomeTabPage(name: tab);
  46. }).toList()))
  47. ],
  48. ),
  49. );
  50. }
  51. @override
  52. bool get wantKeepAlive => true;
  53. ///自定义顶部tab
  54. _tabBar() {
  55. return TabBar(
  56. controller: _controller,
  57. isScrollable: true,
  58. labelColor: Colors.black,
  59. indicator: UnderlineIndicator(
  60. strokeCap: StrokeCap.round,
  61. borderSide: BorderSide(color: primary, width: 3),
  62. insets: EdgeInsets.only(left: 15, right: 15)),
  63. tabs: tabs.map<Tab>((tab) {
  64. return Tab(
  65. child: Padding(
  66. padding: EdgeInsets.only(left: 5, right: 5),
  67. child: Text(
  68. tab,
  69. style: TextStyle(fontSize: 16),
  70. ),
  71. ));
  72. }).toList());
  73. }
  74. }
  75. class HomeTabPage extends StatefulWidget {
  76. final String name;
  77. const HomeTabPage({Key key, this.name}) : super(key: key);
  78. @override
  79. _HomeTabPageState createState() => _HomeTabPageState();
  80. }
  81. class _HomeTabPageState extends State<HomeTabPage> {
  82. @override
  83. Widget build(BuildContext context) {
  84. return Container(
  85. child: Text(widget.name),
  86. );
  87. }
  88. }

底部导航:PageView + BottomNavigationBar

  1. ///底部导航
  2. class BottomNavigator extends StatefulWidget {
  3. @override
  4. _BottomNavigatorState createState() => _BottomNavigatorState();
  5. }
  6. class _BottomNavigatorState extends State<BottomNavigator> {
  7. final _defaultColor = Colors.grey;
  8. final _activeColor = primary;
  9. int _currentIndex = 0;
  10. static int initialPage = 0;
  11. final PageController _controller = PageController(initialPage: initialPage);
  12. List<Widget> _pages;
  13. bool _hasBuild = false;
  14. @override
  15. Widget build(BuildContext context) {
  16. _pages = [HomePage(), RankingPage(), FavoritePage(), ProfilePage()];
  17. if (!_hasBuild) {
  18. //页面第一次打开时通知打开的是那个tab
  19. HiNavigator.getInstance()
  20. .onBottomTabChange(initialPage, _pages[initialPage]);
  21. _hasBuild = true;
  22. }
  23. return Scaffold(
  24. body: PageView(
  25. controller: _controller,
  26. children: _pages,
  27. onPageChanged: (index) => _onJumpTo(index, pageChange: true),
  28. physics: NeverScrollableScrollPhysics(),
  29. ),
  30. bottomNavigationBar: BottomNavigationBar(
  31. currentIndex: _currentIndex,
  32. onTap: (index) => _onJumpTo(index),
  33. type: BottomNavigationBarType.fixed,
  34. selectedItemColor: _activeColor,
  35. items: [
  36. _bottomItem('首页', Icons.home, 0),
  37. _bottomItem('排行', Icons.local_fire_department, 1),
  38. _bottomItem('收藏', Icons.favorite, 2),
  39. _bottomItem('我的', Icons.live_tv, 3),
  40. ],
  41. ),
  42. );
  43. }
  44. _bottomItem(String title, IconData icon, int index) {
  45. return BottomNavigationBarItem(
  46. icon: Icon(icon, color: _defaultColor),
  47. activeIcon: Icon(icon, color: _activeColor),
  48. label: title);
  49. }
  50. void _onJumpTo(int index, {pageChange = false}) {
  51. if (!pageChange) {
  52. //让PageView展示对应tab
  53. _controller.jumpToPage(index);
  54. } else {
  55. HiNavigator.getInstance().onBottomTabChange(index, _pages[index]);
  56. }
  57. setState(() {
  58. //控制选中第一个tab
  59. _currentIndex = index;
  60. });
  61. }
  62. }