gpt4 book ai didi

Flutter web 底部导航栏,支持 url 更新和超链接

转载 作者:行者123 更新时间:2023-12-04 01:12:19 31 4
gpt4 key购买 nike

我已经在网上搜索了很长时间,以寻找支持以下功能的 flutter 应用程序示例:

  • 持久化底部导航栏
  • 导航器支持每个保持状态的底部导航栏项目
  • 导航器在导航上更新 flutter web 中的 URL
  • 导航器支持超链接到 flutter web 中的特定页面。例如,如果用户键入 www.myappdomain.com/#/profile他们被导航到个人资料页面。或者他们输入 www.myappdomain.com/#/profile?id=123他们被导航到用户 123 的个人资料页面。

  • 我已经能够使用以下代码实现第 1 点和第 2 点:
    import 'package:flutter/material.dart';

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

    class BaseApp extends StatefulWidget {
    @override
    _BaseAppState createState() => _BaseAppState();
    }

    class _BaseAppState extends State<BaseApp> {
    // define all navigation tabs for user
    static List<NavigationTabModel> _navigationTabs = [
    NavigationTabModel(
    title: 'Home',
    icon: Icons.home,
    url: '/home',
    navigatorKey: GlobalKey<NavigatorState>(),
    ),
    NavigationTabModel(
    title: 'Search',
    icon: Icons.search,
    url: '/search',
    navigatorKey: GlobalKey<NavigatorState>(),
    ),
    NavigationTabModel(
    title: 'Profile',
    icon: Icons.person,
    url: '/profile',
    navigatorKey: GlobalKey<NavigatorState>(),
    ),
    ];

    // route generator used to build all material page routes
    RouteGenerator _routeGenerator = RouteGenerator();

    // set the current tab to the home page
    int _currentIndex = 0;

    void _select(int index) {
    if (index == _currentIndex) {
    // case 1 - if user presses on currently selected tab
    // pop to first route - i.e. ensure no routes are over laid on top of the current route
    _navigationTabs[_currentIndex]
    .navigatorKey
    .currentState
    .popUntil((route) {
    return route.isFirst;
    });
    } else {
    // case 2 - user selects any other tab
    // rebuild application state with the newly selected navigation tab
    setState(() {
    _currentIndex = index;
    });
    }
    }

    /// generate a list of navigators that will have their state persisted in an
    /// indexed stack.
    List<Widget> _getPersistantStack() {
    return _navigationTabs.map((tab) {
    return WillPopScope(
    onWillPop: () async {
    return !await tab.navigatorKey.currentState.maybePop();
    },
    child: Navigator(
    key: tab.navigatorKey,
    initialRoute: tab.url,
    onGenerateRoute: _routeGenerator.generateRoute,
    ),
    );
    }).toList();
    }

    @override
    Widget build(BuildContext context) {
    /// ********************* HOLD POINT *********************
    /// MaterialApp contains our top-level Navigator. Top level navigator is
    /// required to enable navigation via urls in flutter web. Likely that this
    /// section requires refractoring in some way to enable url updates from the
    /// nested navigators and hyperlinking from web browsers to specific pages
    return MaterialApp(
    debugShowCheckedModeBanner: false,
    home: Scaffold(
    body: SafeArea(
    child: IndexedStack(
    children: _getPersistantStack(),
    index: _currentIndex,
    ),
    ),
    bottomNavigationBar: BottomNavigationBar(
    items: _navigationTabs.map((tab) {
    return BottomNavigationBarItem(
    label: tab.title,
    icon: Icon(tab.icon),
    );
    }).toList(),
    onTap: (int index) {
    _select(index);
    },
    currentIndex: _currentIndex,
    type: BottomNavigationBarType.fixed,
    // hide titles on navigation bar
    showSelectedLabels: false,
    showUnselectedLabels: false,
    ),
    ),
    );
    }
    }

    class NavigationTabModel {
    final String title;
    final IconData icon;
    final String url;
    final GlobalKey<NavigatorState> navigatorKey;

    NavigationTabModel({
    this.title,
    this.icon,
    this.url,
    this.navigatorKey,
    });
    }

    class RouteGenerator {
    Route<dynamic> generateRoute(RouteSettings settings) {
    // Widget builder (function that returns a widget) to construct the route page
    WidgetBuilder builder;

    // build different route (page) based on the route passed to the navigator
    switch (settings.name) {
    case '/home':
    builder = (BuildContext context) {
    return SamplePage(name: 'home');
    };
    break;
    case '/search':
    builder = (BuildContext context) {
    return SamplePage(name: 'search');
    };
    break;
    case '/profile':
    builder = (BuildContext context) {
    return SamplePage(name: 'profile');
    };
    break;
    case '/':
    builder = null;
    break;
    default:
    // If there is no such named route in the switch statement
    builder = (BuildContext context) {
    return SamplePage();
    };
    }
    // prevent page being added to default '/' route
    if (builder == null) {
    return null;
    }
    return MaterialPageRoute(
    builder: builder,
    settings: settings,
    );
    }
    }

    class SamplePage extends StatelessWidget {
    final String name;

    SamplePage({
    this.name,
    });

    @override
    Widget build(BuildContext context) {
    return Scaffold(
    appBar: AppBar(
    title: Text('$name'),
    ),
    body: Padding(
    padding: const EdgeInsets.all(16.0),
    child: Column(
    children: [
    RaisedButton(
    child: Text('push new route'),
    onPressed: () {
    Navigator.of(context).pushNamed('/$name');
    },
    ),
    SizedBox(
    height: 16,
    ),
    Expanded(
    child: ListView.builder(
    itemCount: 100,
    itemBuilder: (context, index) {
    return Card(
    child: Text(
    index.toString(),
    ),
    );
    },
    ),
    ),
    ],
    ),
    ),
    );
    }
    }
    但是,我无法弄清楚如何更新此应用程序以实现第 3 点和第 4 点。有谁知道如何实现?

    最佳答案

    虽然 Navigator 2.0 一开始有点吓人,但当您(主要)针对 Web 应用程序时,它是非常值得的,因为您有很多选项可以从深层链接恢复状态。
    official introduction @Lulupointu 已经链接到了,有一个例子说明你正在寻找什么(但在文章的最底部有点隐藏)。
    In this gist是对不同选项卡使用不同导航器堆栈的工作示例,为了完整起见,我在下面发布。它适用于 channel 测试版,1.23.0-18.1.pre

    import 'package:flutter/material.dart';

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

    class Book {
    final String title;
    final String author;

    Book(this.title, this.author);
    }

    class NestedRouterDemo extends StatefulWidget {
    @override
    _NestedRouterDemoState createState() => _NestedRouterDemoState();
    }

    class _NestedRouterDemoState extends State<NestedRouterDemo> {
    BookRouterDelegate _routerDelegate = BookRouterDelegate();
    BookRouteInformationParser _routeInformationParser =
    BookRouteInformationParser();

    @override
    Widget build(BuildContext context) {
    return MaterialApp.router(
    title: 'Books App',
    routerDelegate: _routerDelegate,
    routeInformationParser: _routeInformationParser,
    );
    }
    }

    class BooksAppState extends ChangeNotifier {
    int _selectedIndex;

    Book _selectedBook;

    final List<Book> books = [
    Book('Stranger in a Strange Land', 'Robert A. Heinlein'),
    Book('Foundation', 'Isaac Asimov'),
    Book('Fahrenheit 451', 'Ray Bradbury'),
    ];

    BooksAppState() : _selectedIndex = 0;

    int get selectedIndex => _selectedIndex;

    set selectedIndex(int idx) {
    _selectedIndex = idx;
    notifyListeners();
    }

    Book get selectedBook => _selectedBook;

    set selectedBook(Book book) {
    _selectedBook = book;
    notifyListeners();
    }

    int getSelectedBookById() {
    if (!books.contains(_selectedBook)) return 0;
    return books.indexOf(_selectedBook);
    }

    void setSelectedBookById(int id) {
    if (id < 0 || id > books.length - 1) {
    return;
    }

    _selectedBook = books[id];
    notifyListeners();
    }
    }

    class BookRouteInformationParser extends RouteInformationParser<BookRoutePath> {
    @override
    Future<BookRoutePath> parseRouteInformation(
    RouteInformation routeInformation) async {
    final uri = Uri.parse(routeInformation.location);

    if (uri.pathSegments.isNotEmpty && uri.pathSegments.first == 'settings') {
    return BooksSettingsPath();
    } else {
    if (uri.pathSegments.length >= 2) {
    if (uri.pathSegments[0] == 'book') {
    return BooksDetailsPath(int.tryParse(uri.pathSegments[1]));
    }
    }
    return BooksListPath();
    }
    }

    @override
    RouteInformation restoreRouteInformation(BookRoutePath configuration) {
    if (configuration is BooksListPath) {
    return RouteInformation(location: '/home');
    }
    if (configuration is BooksSettingsPath) {
    return RouteInformation(location: '/settings');
    }
    if (configuration is BooksDetailsPath) {
    return RouteInformation(location: '/book/${configuration.id}');
    }
    return null;
    }
    }

    class BookRouterDelegate extends RouterDelegate<BookRoutePath>
    with ChangeNotifier, PopNavigatorRouterDelegateMixin<BookRoutePath> {
    final GlobalKey<NavigatorState> navigatorKey;

    BooksAppState appState = BooksAppState();

    BookRouterDelegate() : navigatorKey = GlobalKey<NavigatorState>() {
    appState.addListener(notifyListeners);
    }

    @override
    BookRoutePath get currentConfiguration {
    if (appState.selectedIndex == 1) {
    return BooksSettingsPath();
    } else {
    if (appState.selectedBook == null) {
    return BooksListPath();
    } else {
    return BooksDetailsPath(appState.getSelectedBookById());
    }
    }
    }

    @override
    Widget build(BuildContext context) {
    return Navigator(
    key: navigatorKey,
    pages: [
    MaterialPage(
    child: AppShell(appState: appState),
    ),
    ],
    onPopPage: (route, result) {
    if (!route.didPop(result)) {
    return false;
    }

    if (appState.selectedBook != null) {
    appState.selectedBook = null;
    }
    notifyListeners();
    return true;
    },
    );
    }

    @override
    Future<void> setNewRoutePath(BookRoutePath path) async {
    if (path is BooksListPath) {
    appState.selectedIndex = 0;
    appState.selectedBook = null;
    } else if (path is BooksSettingsPath) {
    appState.selectedIndex = 1;
    } else if (path is BooksDetailsPath) {
    appState.selectedIndex = 0;
    appState.setSelectedBookById(path.id);
    }
    }
    }

    // Routes
    abstract class BookRoutePath {}

    class BooksListPath extends BookRoutePath {}

    class BooksSettingsPath extends BookRoutePath {}

    class BooksDetailsPath extends BookRoutePath {
    final int id;

    BooksDetailsPath(this.id);
    }

    // Widget that contains the AdaptiveNavigationScaffold
    class AppShell extends StatefulWidget {
    final BooksAppState appState;

    AppShell({
    @required this.appState,
    });

    @override
    _AppShellState createState() => _AppShellState();
    }

    class _AppShellState extends State<AppShell> {
    InnerRouterDelegate _routerDelegate;
    ChildBackButtonDispatcher _backButtonDispatcher;

    void initState() {
    super.initState();
    _routerDelegate = InnerRouterDelegate(widget.appState);
    }

    @override
    void didUpdateWidget(covariant AppShell oldWidget) {
    super.didUpdateWidget(oldWidget);
    _routerDelegate.appState = widget.appState;
    }

    @override
    void didChangeDependencies() {
    super.didChangeDependencies();
    // Defer back button dispatching to the child router
    _backButtonDispatcher = Router.of(context)
    .backButtonDispatcher
    .createChildBackButtonDispatcher();
    }

    @override
    Widget build(BuildContext context) {
    var appState = widget.appState;

    // Claim priority, If there are parallel sub router, you will need
    // to pick which one should take priority;
    _backButtonDispatcher.takePriority();

    return Scaffold(
    appBar: AppBar(),
    body: Router(
    routerDelegate: _routerDelegate,
    backButtonDispatcher: _backButtonDispatcher,
    ),
    bottomNavigationBar: BottomNavigationBar(
    items: [
    BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
    BottomNavigationBarItem(
    icon: Icon(Icons.settings), label: 'Settings'),
    ],
    currentIndex: appState.selectedIndex,
    onTap: (newIndex) {
    appState.selectedIndex = newIndex;
    },
    ),
    );
    }
    }

    class InnerRouterDelegate extends RouterDelegate<BookRoutePath>
    with ChangeNotifier, PopNavigatorRouterDelegateMixin<BookRoutePath> {
    final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
    BooksAppState get appState => _appState;
    BooksAppState _appState;
    set appState(BooksAppState value) {
    if (value == _appState) {
    return;
    }
    _appState = value;
    notifyListeners();
    }

    InnerRouterDelegate(this._appState);

    @override
    Widget build(BuildContext context) {
    return Navigator(
    key: navigatorKey,
    pages: [
    if (appState.selectedIndex == 0) ...[
    FadeAnimationPage(
    child: BooksListScreen(
    books: appState.books,
    onTapped: _handleBookTapped,
    ),
    key: ValueKey('BooksListPage'),
    ),
    if (appState.selectedBook != null)
    MaterialPage(
    key: ValueKey(appState.selectedBook),
    child: BookDetailsScreen(book: appState.selectedBook),
    ),
    ] else
    FadeAnimationPage(
    child: SettingsScreen(),
    key: ValueKey('SettingsPage'),
    ),
    ],
    onPopPage: (route, result) {
    appState.selectedBook = null;
    notifyListeners();
    return route.didPop(result);
    },
    );
    }

    @override
    Future<void> setNewRoutePath(BookRoutePath path) async {
    // This is not required for inner router delegate because it does not
    // parse route
    assert(false);
    }

    void _handleBookTapped(Book book) {
    appState.selectedBook = book;
    notifyListeners();
    }
    }

    class FadeAnimationPage extends Page {
    final Widget child;

    FadeAnimationPage({Key key, this.child}) : super(key: key);

    Route createRoute(BuildContext context) {
    return PageRouteBuilder(
    settings: this,
    pageBuilder: (context, animation, animation2) {
    var curveTween = CurveTween(curve: Curves.easeIn);
    return FadeTransition(
    opacity: animation.drive(curveTween),
    child: child,
    );
    },
    );
    }
    }

    // Screens
    class BooksListScreen extends StatelessWidget {
    final List<Book> books;
    final ValueChanged<Book> onTapped;

    BooksListScreen({
    @required this.books,
    @required this.onTapped,
    });

    @override
    Widget build(BuildContext context) {
    return Scaffold(
    body: ListView(
    children: [
    for (var book in books)
    ListTile(
    title: Text(book.title),
    subtitle: Text(book.author),
    onTap: () => onTapped(book),
    )
    ],
    ),
    );
    }
    }

    class BookDetailsScreen extends StatelessWidget {
    final Book book;

    BookDetailsScreen({
    @required this.book,
    });

    @override
    Widget build(BuildContext context) {
    return Scaffold(
    body: Padding(
    padding: const EdgeInsets.all(8.0),
    child: Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
    FlatButton(
    onPressed: () {
    Navigator.of(context).pop();
    },
    child: Text('Back'),
    ),
    if (book != null) ...[
    Text(book.title, style: Theme.of(context).textTheme.headline6),
    Text(book.author, style: Theme.of(context).textTheme.subtitle1),
    ],
    ],
    ),
    ),
    );
    }
    }

    class SettingsScreen extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
    return Scaffold(
    body: Center(
    child: Text('Settings screen'),
    ),
    );
    }
    }
    如果您希望主页导航按钮链接到主页而不保持状态,则必须将 set selectedIndex 替换为:
      set selectedIndex(int idx) {
    _selectedIndex = idx;
    if (_selectedIndex == 1) {
    // Remove this line if you want to keep the selected book when navigating
    // between "settings" and "home" which book was selected when Settings is
    // tapped.
    selectedBook = null;
    }
    notifyListeners();
    }
    在编写自己的 RouteInformationParser 时,您可能想看看如何提取查询参数:
    Parsing a URI to extract query parameters, with Dart
    这是您可能想查看的另一个示例以了解 Navigator 2.0: https://github.com/flutter/flutter/pull/63424

    关于Flutter web 底部导航栏,支持 url 更新和超链接,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64457071/

    31 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com