完整代码

需求

在开发中,关于页面路由,咱们一般会遇到这样的一个需求:顺次翻开A页面→B页面→C页面,然后在某个业务场景下需求封闭中心的B页面,C页面点击回来时回到A页面。本文将讨论如何在Flutter中完成这种页面导航形式,以及如何提供更好的用户体会。

探索

Flutter中咱们一般运用路由表进行开发,经过装备路由称号和对应的页面

MaterialApp(
  title: 'Flutter Demo',
  initialRoute:"/", //名为"/"的路由作为运用的home(主页)
  //注册路由表
  routes:{
   "/":(context) => MyHomePage(title: 'Flutter Demo Home Page'), //注册主页路由
    "new_page":(context) => NewRoute(),
  } 
);

咱们需求界说一个办法经过页面路由称号来移除指定页面,像这样void removeName(String name)

但实际上Flutter中并没有这样的办法,只要这样一个办法Navigator.of(context).removeRoute(route)去移除页面。

咱们看一下Route这个类,

abstract class Route<T> {
  /// Initialize the [Route].
  ///
  /// If the [settings] are not provided, an empty [RouteSettings] object is
  /// used instead.
  Route({ RouteSettings? settings }) : _settings = settings ?? const RouteSettings();
}

RouteSettings特点,其中的name便是咱们界说的路由称号

class RouteSettings {
  /// Creates data used to construct routes.
  const RouteSettings({
    this.name,
    this.arguments,
  });

但咱们并不能获取到这个route,所以咱们需求记载一个路由栈,然后经过路由名获取对应的route,再调用Navigator.of(context).removeRoute(route)去移除页面

代码完成

路由装备

这里是运用GetX来进行路由办理,并经过navigatorObservers记载路由。如果你不喜欢GetX,运用Flutter默认的方式也是可以的

class MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      title: 'Flutter Demo',
      initialRoute: RouteConfig.home,
      getPages: RouteConfig.getPages,
      navigatorObservers: [routeHistoryObserver],//记载路由栈
    );
  }

装备路由表

///路由装备类
class RouteConfig {
  static const String home = "/home";
  static const String a = "/a";
  static const String b = "/b";
  static const String c = "/c";
  static final List<GetPage> getPages = [
    GetPage(name: home, page: () => const MyHomePage()),
    GetPage(name: a, page: () => const APage()),
    GetPage(name: b, page: () => const BPage()),
    GetPage(name: c, page: () => const CPage()),
  ];
}

记载路由栈

这里咱们运用RouteObserver记载路由栈,经过一个列表history记载页面翻开和封闭

HistoryRouteObserver routeHistoryObserver = HistoryRouteObserver();
///记载路由历史
class HistoryRouteObserver extends RouteObserver<PageRoute> {
  List<Route<dynamic>> history = <Route<dynamic>>[];
  @override
  void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {
    super.didPop(route, previousRoute);
    history.remove(route);
    //调用Navigator.of(context).pop() 出栈时回调
  }
  @override
  void didPush(Route route, Route? previousRoute) {
    super.didPush(route, previousRoute);
    history.add(route);
    //调用Navigator.of(context).push(Route()) 进栈时回调
  }
  @override
  void didRemove(Route route, Route? previousRoute) {
    super.didRemove(route, previousRoute);
    history.remove(route);
    //调用Navigator.of(context).removeRoute(Route()) 移除某个路由回调
  }
  @override
  void didReplace({Route? newRoute, Route? oldRoute}) {
    super.didReplace(newRoute: newRoute, oldRoute: oldRoute);
    if (oldRoute != null) {
      history.remove(oldRoute);
    }
    if (newRoute != null) {
      history.add(newRoute);
    }
    //调用Navigator.of(context).replace( oldRoute:Route("old"),newRoute:Route("new")) 替换路由时回调
  }
}

删去指定页面

这里咱们对GetInterface写一个拓宽类,经过name找到指定的Route进行删去

extension GetExtension on GetInterface {
  ///路由历史
  List<Route<dynamic>> get history => routeHistoryObserver.history;
  ///是否已翻开该页面
  bool containName(String name) {
    return getRouteByName(name) != null;
  }
  ///经过name获取route,从栈顶开始查找
  Route? getRouteByName(String name) {
    var index = history.lastIndexWhere((element) => element.settings.name == name);
    if (index != -1) {
      return history[index];
    }
    return null;
  }
  ///经过name获取route
  List<Route> getRoutesByName(String name) {
    return history.where((element) => element.settings.name == name).toList();
  }
  ///移除指定的页面,一次
  void removeName(String name) {
    var route = getRouteByName(name);
    if (route != null) {
      Get.removeRoute(route);
    }
  }
  ///移除一切指定的页面
  void removeAllName(String name) {
    var routes = getRoutesByName(name);
    for (var o in routes) {
      Get.removeRoute(o);
    }
  }
}

运用

运用拓宽办法Get.removeName(RouteConfig.b);即可移除指定页面

class CPage extends StatelessWidget {
  const CPage({super.key});
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('CPage'),
      ),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            ElevatedButton(
              onPressed: () {
                Get.removeName(RouteConfig.a);
              },
              child: const Text("Remove A"),
            ),
            ElevatedButton(
              onPressed: () {
                Get.removeName(RouteConfig.b);
              },
              child: const Text("Remove B"),
            ),
          ],
        ),
      ),
    );
  }
}