Flutter上線專案實戰——路由篇

callme大帥發表於2019-06-30

1. 應用場景

開發中經常遇到

  • 路由跳轉時拿不到context怎麼辦,eg: token失效/異地登入跳轉登入頁面。
  • 獲取不到當前路由名稱怎麼辦,eg: 點選push推送跳轉指定路由,如果已經在當前頁面就replace,如果不在就push。
  • 註冊監聽路由跳轉,做一些想做的事情 ,eg:不同路由,顯示不同狀態列顏色。
  • 等等...

2. 解決方案

解決思路:

  1. MaterialApp 的routes屬性賦值路由陣列,navigatorObservers屬性賦值路由監聽物件NavigatorManager。
  2. 在NavigatorManager裡實現NavigatorObserver的didPush/didReplace/didPop/didRemove,並記錄到路由棧 List _mRoutes中。
  3. 將實時記錄的路由跳轉,用stream發一個廣播,哪裡需要哪裡註冊。

3. 具體實現

main.dart

MaterialApp(
    navigatorObservers: [NavigatorManager.getInstance()],
    routes: NavigatorManager.configRoutes,
    ...
)
複製程式碼

navigator_manager.dart

class NavigatorManager extends NavigatorObserver {
  /* 配置routes */
  static Map<String, WidgetBuilder> configRoutes = {
  PackageInfoPage.sName: (context) =>
    SplashPage.sName: (context) => SplashPage(),
    LoginPage.sName: (context) => SplashPage()),
    MainPage.sName: (context) => SplashPage(),
    //...
  }
  // 當前路由棧
  static List<Route> _mRoutes;
  List<Route> get routes => _mRoutes;
  // 當前路由
  Route get currentRoute => _mRoutes[_mRoutes.length - 1];
  // stream相關
  static StreamController _streamController;
  StreamController get streamController=> _streamController;
  // 用來路由跳轉
  static NavigatorState navigator;
  
  /* 單例給出NavigatorManager */
  static NavigatorManager navigatorManager;
  static NavigatorManager getInstance() {
    if (navigatorManager == null) {
      navigatorManager = new NavigatorManager();
      _streamController = StreamController.broadcast();
    }
    return navigatorManager;
  }
  
  // replace 頁面
  pushReplacementNamed(String routeName, [WidgetBuilder builder]) {
    return navigator.pushReplacement(
      CupertinoPageRoute(
        builder: builder ?? configRoutes[routeName],
        settings: RouteSettings(name: routeName),
      ),
    );
  }
  
  // push 頁面
  pushNamed(String routeName, [WidgetBuilder builder]) {
    return navigator.push(
      CupertinoPageRoute(
        builder: builder ?? configRoutes[routeName],
        settings: RouteSettings(name: routeName),
      ),
    );
  }
  
  // pop 頁面
  pop<T extends Object>([T result]) {
    navigator.pop(result);
  }
  
  // push一個頁面, 移除該頁面下面所有頁面
  pushNamedAndRemoveUntil(String newRouteName) {
    return navigator.pushNamedAndRemoveUntil(newRouteName, (Route<dynamic> route) => false);
  }
  
  // 當呼叫Navigator.push時回撥
  @override
  void didPush(Route route, Route previousRoute) {
    super.didPush(route, previousRoute);
    if (_mRoutes == null) {
      _mRoutes = new List<Route>();
    }
    // 這裡過濾調push的是dialog的情況
    if (route is CupertinoPageRoute || route is MaterialPageRoute) {
      _mRoutes.add(route);
      routeObserver();
    }
  }
  
  // 當呼叫Navigator.replace時回撥
  @override
  void didReplace({Route newRoute, Route oldRoute}) {
    super.didReplace();
    if (newRoute is CupertinoPageRoute || newRoute is MaterialPageRoute) {
      _mRoutes.remove(oldRoute);
      _mRoutes.add(newRoute);
      routeObserver();
    }
  }
  
  // 當呼叫Navigator.pop時回撥
  @override
  void didPop(Route route, Route previousRoute) {
    super.didPop(route, previousRoute);
    if (route is CupertinoPageRoute || route is MaterialPageRoute) {
      _mRoutes.remove(route);
      routeObserver();
    }
  }
  
  @override
  void didRemove(Route removedRoute, Route oldRoute) {
    super.didRemove(removedRoute, oldRoute);
    if (removedRoute is CupertinoPageRoute || removedRoute is MaterialPageRoute) {
      _mRoutes.remove(removedRoute);
      routeObserver();
    }
  }
  
  void routeObserver() {
    LogUtil.i(sName, '&&路由棧&&');
    LogUtil.i(sName, _mRoutes);
    LogUtil.i(sName, '&&當前路由&&');
    LogUtil.i(sName, _mRoutes[_mRoutes.length - 1]);
    // 當前頁面的navigator,用來路由跳轉
    navigator = _mRoutes[_mRoutes.length - 1].navigator;
    streamController.sink.add(_mRoutes);
  }
}
複製程式碼

4. 如何使用

token失效跳轉

case 401:
    ToastUtil.showRed('登入失效,請重新登陸');
    UserDao.clearAll();
    NavigatorManager.getInstance().pushNamedAndRemoveUntil(LoginPage.sName);
    break;
複製程式碼

點選push推送跳轉

static jumpPage(String pageName, [WidgetBuilder builder]) {
    String currentRouteName = NavigatorManager.getInstance().currentRoute.settings.name;
    // 如果是未登入,不跳轉
    if (NavigatorManager.getInstance().routes[0].settings.name != MainPage.sName) {
      return;
    }

    // 如果已經是當前頁面就replace
    if (currentRouteName == pageName) {
      NavigatorManager.getInstance().pushReplacementNamed(pageName, builder);
    } else {
      NavigatorManager.getInstance().pushNamed(pageName, builder);
    }
}
複製程式碼

監聽路由改變狀態列顏色

class StatusBarUtil {
      static List<String> lightRouteNameList = [
        TaskhallPage.sName,
        //...
      ];
      static List darkRoutNameList = [
        SplashPage.sName,
        LoginPage.sName,
        MainPage.sName,
        //...
      ];
      
      static init() {
        NavigatorManager.getInstance().streamController.stream.listen((state) {
            setupStatusBar(state[state.length - 1]);
        })
      }
    
      setupStatusBar(Route currentRoute) {
        if (lightRouteNameList.contains(currentRoute.settings.name)) {
          setLight();
        } else if (darkRoutNameList.contains(currentRoute.settings.name)) {
          setDart();
        }
      }
}
複製程式碼

完結,撒花?

相關文章