likes
comments
collection
share

Flutter 路由移除指定页面

作者站长头像
站长
· 阅读数 27

完整代码

需求

在开发中,对于页面路由,我们通常会遇到这样的一个需求:依次打开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"),
            ),
          ],
        ),
      ),
    );
  }
}