Flutter树形导航插件tree_navigation的使用

发布于 1周前 作者 phonegap100 来自 Flutter

Flutter树形导航插件tree_navigation的使用

tree_navigation 是一个基于 GoRouter 的导航包。本文将详细介绍如何使用该插件来实现树形导航。

初始化

首先,你需要在 MyApp 中初始化 tree_navigation。为此,你需要创建两个全局键(GlobalKey),用于导航状态。这些全局键应该按照层级顺序排序,顶层的键应放在列表前面。

GlobalKey<NavigatorState> topKey = GlobalKey<NavigatorState>();
GlobalKey<NavigatorState> shellKey = GlobalKey<NavigatorState>();

abstract class Routes {
  static const RouteInfo home = RouteInfo(
    path: '/',
    name: 'home',
    isShellRoute: false,
  );
  static const RouteInfo newPage = RouteInfo(
    path: '/newPage',
    name: 'newPage',
    isShellRoute: false,
  );
  
  static const List<RouteInfo> allRoutes = [home, newPage];
}

void main() async {
  await init();
  runApp(RouteProvider(child: const MyApp()));
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  [@override](/user/override)
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  [@override](/user/override)
  void initState() {
    super.initState();
    TreeNavigation.init(
      globalKeyList: [topKey, shellKey],
      routeInfoList: Routes.allRoutes,
      routeTreeDefaultPageBuilder: (_, state, child) => MyCustomTransitionPage(
        key: state.pageKey,
        child: child,
        transitionsBuilder: (_, animation, ___, widget) {
          return FadeTransition(
            opacity: animation,
            child: widget,
          );
        },
      ),
      routeTreeDefaultShellPageBuilder: (_, state, parent, child) => MyCustomTransitionPage(
        key: state.pageKey,
        child: parent(child),
        transitionsBuilder: (_, animation, ___, widget) {
          const begin = Offset(0.0, 1.0);
          const end = Offset.zero;
          final tween = Tween(begin: begin, end: end);
          final offsetAnimation = animation.drive(tween);

          return SlideTransition(
            position: offsetAnimation,
            child: widget,
          );
        },
      ),
    );
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return TreeNavigation.makeMaterialApp(
      navigatorKey: topKey,
      globalKeyList: [topKey, shellKey],
      routeInfoList: Routes.allRoutes,
      routes: [
        TreeRoute(
          routeInfo: Routes.home,
          pageWidget: const MyHomePage(
            title: 'Home',
            color: Colors.white,
          ),
        ),
        TreeShellRoute(
          navigatorKey: shellKey,
          pageWidget: (child) => MyHomePage(title: 'Shell Route', color: Colors.blue, child: child,),
          routes: [
            TreeRoute(
              routeInfo: Routes.newPage,
              pageWidget: const MyHomePage(
                title: 'Sub Shell',
                color: Colors.pink,
              ),
            ),
          ],
        ),
      ],
    );
  }
}

使用方法

你可以通过以下方式访问当前路由:

final navigation = GetIt.instance<NavigationInterface>();
print(navigation.of(context)?.name);

导航到屏幕

navigation.goNamed(Routes.home);

打开对话框

await navigation.openDialog<String>(dialog: const MyDialog());

打开底部弹出框

await navigation.openBottomSheet<String>(bottomSheet: const MyBottomSheet());

显示文本提示框

navigation.showTextToast(text: 'this is a toast');

弹出对话框、底部弹出框或屏幕

navigation.pop();

弹出所有对话框

navigation.popAllDialogs();

弹出所有底部弹出框

navigation.popAllBottomSheets();

弹出所有对话框和底部弹出框

navigation.popAllPopUps();

弹出直到满足条件

navigation.popUntilRoute(verifyCondition: (currentRoute) {
  return currentRoute == Routes.home;
});

弹出直到到达特定弹出框

navigation.popUntilPopUp(verifyCondition: (currentPopUpName) => popUpName == currentPopUpName);

显示覆盖视图

final handler = navigation.showOverlay(
  child: Container(
    color: Colors.brown,
    child: const Text('Other overlay'),
  ),
  alignment: Alignment.topLeft,
);

处理覆盖视图

handler.remove();
handler.dispose();

获取上下文

navigation.context;

检查是否有打开的对话框

navigation.isDialogOpen();

检查是否有打开的底部弹出框

navigation.isBottomSheetOpen();

检查是否有打开的对话框或底部弹出框

navigation.isDialogOrBottomSheetOpen();

完整示例代码

以下是完整的示例代码,展示了如何使用 tree_navigation 插件实现树形导航。

import 'package:example/DotButton.dart';
import 'package:flutter/material.dart';
import 'package:tree_navigation/tree_navigation.dart';

GlobalKey<NavigatorState> topKey = GlobalKey<NavigatorState>();
GlobalKey<NavigatorState> shellKey = GlobalKey<NavigatorState>();

void main() async {
  runApp(
    RouteProvider(child: const MyApp()),
  );
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  [@override](/user/override)
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  [@override](/user/override)
  void initState() {
    super.initState();
    TreeNavigation.init(
      useNavigationOne: true,
      globalKeyList: [topKey, shellKey],
      routeInfoList: Routes.allRoutes,
      routeTreeDefaultPageBuilder: (_, state, child, routeName) => MyCustomTransitionPage(
        key: state.pageKey,
        child: child,
        name: routeName,
        transitionsBuilder: (_, animation, ___, widget) {
          return FadeTransition(
            opacity: animation,
            child: widget,
          );
        },
      ),
      routeTreeDefaultShellPageBuilder: (_, state, parent, child) => MyCustomTransitionPage(
        key: state.pageKey,
        child: parent(child),
        transitionsBuilder: (_, animation, ___, widget) {
          const begin = Offset(0.0, 1.0);
          const end = Offset.zero;
          final tween = Tween(begin: begin, end: end);
          final offsetAnimation = animation.drive(tween);

          return SlideTransition(
            position: offsetAnimation,
            child: widget,
          );
        },
      ),
    );
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return TreeNavigation.makeMaterialApp(
      navigatorKey: topKey,
      globalKeyList: [topKey, shellKey],
      routeInfoList: Routes.allRoutes,
      routes: [
        TreeRoute(
          routeInfo: Routes.home1,
          pageWidget: MyHomePage(
            title: 'Home1',
            color: Colors.indigo,
            onPressedButton: () => TreeNavigation.navigator.goNamed(Routes.newPage2).then(
                  (res) => print('Page1 Home Result is : $res'),
                ),
          ),
          routes: [
            TreeRoute(
              routeInfo: Routes.newPage2,
              pageWidget: MyHomePage(
                title: 'Sub2',
                color: Colors.orange,
                onPressedButton: () {
                  TreeNavigation.navigator
                      .openDialog(
                    dialog: Dialog(
                      child: Column(
                        mainAxisSize: MainAxisSize.min,
                        children: [
                          Text('Pop Me'),
                          TextButton(
                            onPressed: () => TreeNavigation.navigator.pop(result: 'sub2 dialog'),
                            child: Text('POP'),
                          ),
                        ],
                      ),
                    ),
                  )
                      .then(
                    (value) {
                      print('After dialog pop: $value');
                    },
                  );
                },
                hasPopButton: true,
              ),
            )
          ],
        ),
        TreeRoute(
          routeInfo: Routes.home,
          pageWidget: MyHomePage(
            title: 'Home',
            color: Colors.white,
            onPressedButton: () async =>
                TreeNavigation.navigator.goNamed(Routes.newPage).then((res) => print('Page Home Result is : $res')),
          ),
        ),
        TreeRoute(
          routeInfo: Routes.newPage,
          pageWidget: MyHomePage(
            title: 'Sub Shell',
            color: Colors.pink,
            hasPopButton: true,
            onPressedButton: () {
              TreeNavigation.navigator.goNamed(Routes.newPage2).then((res) => print('Page New Page Result is : $res'));
            },
          ),
        ),
      ],
    );
  }
}

abstract class Routes {
  static const RouteInfo home1 = RouteInfo(
    path: '/h1',
    name: 'home1',
    isShellRoute: false,
  );
  static const RouteInfo home = RouteInfo(
    path: '/',
    name: 'home',
    isShellRoute: false,
  );
  static const RouteInfo newPage = RouteInfo(
    path: '/newPage',
    name: 'newPage',
    isShellRoute: false,
  );
  static const RouteInfo newPage2 = RouteInfo(
    path: '/newPage2',
    name: 'newPage2',
    isShellRoute: false,
  );

  static const List<RouteInfo> allRoutes = [home1, home, newPage, newPage2];
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({
    super.key,
    required this.title,
    this.child,
    required this.color,
    required this.onPressedButton,
    this.hasPopButton = false,
  });

  final String title;
  final Widget? child;
  final Color color;
  final Function onPressedButton;
  final bool hasPopButton;

  [@override](/user/override)
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      backgroundColor: widget.color,
      body: Padding(
        padding: const EdgeInsets.all(100),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            BackButton(),
            if (widget.child != null) widget.child! else Text(widget.title),
            if (widget.hasPopButton)
              TextButton(
                onPressed: () => TreeNavigation.navigator.pop(result: widget.title),
                child: const Text('Pop'),
              ),
            TextButton(
              onPressed: () {
                // print(RouteProvider.of(context)?.name);
              },
              child: Text('My Route'),
            ),
            TextButton(
              onPressed: () {
                TreeNavigation.navigator.showTextToast(text: 'text');
              },
              child: Text('text toast'),
            ),
            TextButton(
              onPressed: () => TreeNavigation.navigator.showToast(
                attachedBuilder: (_) => Transform.scale(
                  scale: 0.9,
                  child: Material(
                    child: GestureDetector(
                      onTap: () {
                        TreeNavigation.navigator.removeToasts();
                      },
                      child: AbsorbPointer(
                        child: Material(
                          child: Text('yo'),
                        ),
                      ),
                    ),
                  ),
                ),
                duration: Duration(seconds: (5)),
                target: const Offset(500, 20),
              ),
              child: Text('Toast'),
            ),
            if (widget.title == 'Home')
              TextButton(
                onPressed: () {
                  TreeNavigation.navigator
                      .openDialog(
                          dialog: Dialog(
                        child: Column(
                          mainAxisSize: MainAxisSize.min,
                          children: [
                            Text('Pop Me'),
                            TextButton(
                              onPressed: () => TreeNavigation.navigator.pop(result: 'home dialog'),
                              child: Text('POP'),
                            ),
                            TextButton(
                              onPressed: () {
                                TreeNavigation.navigator
                                    .goNamed(Routes.newPage)
                                    .then((v) => print('In home dialog, sub pop result is: $v'));
                              },
                              child: Text('To Sub'),
                            ),
                          ],
                        ),
                      ))
                      .then((value) => print('dialog result: $value'));
                },
                child: Text('Dialog'),
              ),
          ],
        ),
      ),
      floatingActionButton: DotButton(
        onPressed: () async => await widget.onPressedButton(),
        child: const Icon(Icons.change_circle),
      ),
    );
  }
}

更多关于Flutter树形导航插件tree_navigation的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter树形导航插件tree_navigation的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是一个关于如何使用Flutter中的tree_navigation插件来实现树形导航的示例代码。这个插件允许你以树状结构管理导航,非常适合那些需要复杂导航结构的应用。

首先,确保你已经在pubspec.yaml文件中添加了tree_navigation依赖:

dependencies:
  flutter:
    sdk: flutter
  tree_navigation: ^最新版本号  # 请替换为当前最新版本号

然后运行flutter pub get来安装依赖。

接下来是一个简单的示例,展示如何使用tree_navigation插件来设置和导航树形结构:

import 'package:flutter/material.dart';
import 'package:tree_navigation/tree_navigation.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Tree Navigation Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: NavigatorTree(
        routes: {
          '/': (context) => HomeScreen(),
          '/home': (context) => HomeScreen(),
          '/settings': (context) => SettingsScreen(),
          '/settings/profile': (context) => ProfileScreen(),
          '/settings/preferences': (context) => PreferencesScreen(),
        },
        initialRoute: '/',
        onGenerateRoute: (settings) {
          // Handle dynamic routes if needed
          return null;
        },
      ),
    );
  }
}

class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final TreeNavigator treeNavigator = TreeNavigator.of(context);

    return Scaffold(
      appBar: AppBar(
        title: Text('Home Screen'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            ElevatedButton(
              onPressed: () => treeNavigator.navigateTo('/settings/profile'),
              child: Text('Go to Profile'),
            ),
            ElevatedButton(
              onPressed: () => treeNavigator.navigateTo('/settings/preferences'),
              child: Text('Go to Preferences'),
            ),
          ],
        ),
      ),
    );
  }
}

class SettingsScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Settings Screen'),
      ),
      body: Center(
        child: Text('This is the Settings Screen'),
      ),
    );
  }
}

class ProfileScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Profile Screen'),
      ),
      body: Center(
        child: Text('This is the Profile Screen'),
      ),
    );
  }
}

class PreferencesScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Preferences Screen'),
      ),
      body: Center(
        child: Text('This is the Preferences Screen'),
      ),
    );
  }
}

在这个示例中,我们做了以下几件事:

  1. 定义路由:在NavigatorTreeroutes参数中,我们定义了应用的路由结构。每个路由都映射到一个Widget构建函数。

  2. 初始路由initialRoute参数设置了应用的初始路由。

  3. 使用TreeNavigator:在需要导航的地方,我们通过TreeNavigator.of(context)获取TreeNavigator实例,并使用它的navigateTo方法导航到指定的路由。

  4. 创建屏幕Widget:我们创建了HomeScreenSettingsScreenProfileScreenPreferencesScreen这四个屏幕Widget,每个屏幕都有一个简单的界面。

这个示例展示了如何使用tree_navigation插件来组织复杂的导航结构。你可以根据需要扩展这个示例,添加更多的屏幕和路由,以实现更复杂的应用结构。

回到顶部