flutter 使用Beamer的选项卡+无选项卡导航:多个小部件使用相同的GlobalKey

b5buobof  于 2023-08-07  发布在  Flutter
关注(0)|答案(1)|浏览(147)

我试图实现一个导航模式,其中一些路线必须显示在选项卡中,但其余的是显示没有选项卡。我肯定错过了什么。
这个想法是隐藏底部导航时,“无制表符”路线显示。这种方法似乎有效,除非使用Android后退按钮。

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

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

final appRouterDelegate = BeamerDelegate(
  initialPath: '/tab1',
  locationBuilder: RoutesLocationBuilder(
    routes: {
      '*': (context, state, data) => AppPage(),
    },
  ),
);

final routerDelegate = BeamerDelegate(
  initialPath: appRouterDelegate.initialPath,
  //
  locationBuilder: (routeInformation, _) {
    if (routeInformation.location!.startsWith('/tabless')) {
      return TabLessLocation(routeInformation);
    }
    return TabbedLocation(routeInformation);
  },
);

class TabbedLocation extends BeamLocation<BeamState> {
  TabbedLocation(RouteInformation routeInformation) : super(routeInformation);

  @override
  List<BeamPage> buildPages(BuildContext context, BeamState state) {
    return [
      if (state.uri.path == '/tab1')
        BeamPage(
          key: ValueKey('/tab1'),
          child: Tab1Page(),
        ),
      if (state.uri.path == '/tab2')
        BeamPage(
          key: ValueKey('/tab2'),
          child: Tab2Page(),
        ),
    ];
  }

  @override
  List<Pattern> get pathPatterns => ['/tab1', '/tab2'];
}

class TabLessLocation extends BeamLocation<BeamState> {
  TabLessLocation(RouteInformation routeInformation) : super(routeInformation);

  @override
  List<BeamPage> buildPages(BuildContext context, BeamState state) {
    return [
      if (state.uri.path.startsWith('/tabless'))
        BeamPage(
          key: ValueKey('/tabless'),
          child: TabLessPage(),
        ),
      if (state.uri.path == '/tabless/subpage')
        BeamPage(
          key: ValueKey('/tabless/subpage'),
          child: TabLessSubPage(),
        ),
    ];
  }

  @override
  List<Pattern> get pathPatterns => ['/tabless', '/tabless/subpage'];
}

class TestPage extends StatelessWidget {
  TestPage({required this.title, required this.links});

  final String title;
  final List<String> links;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Column(
        children: links
            .map(
              (link) => OutlinedButton(
                child: Text("Go To " + link),
                onPressed: () {
                  routerDelegate.beamToNamed(link);
                },
              ),
            )
            .toList(),
      ),
    );
  }
}

class Tab1Page extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return TestPage(title: 'Tab1', links: ['/tab2', '/tabless', '/tabless/subpage', ]);
  }
}

class Tab2Page extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return TestPage(title: 'Tab2', links: ['/tab1', '/tabless', '/tabless/subpage']);
  }
}

class TabLessPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return TestPage(title: 'Tabless', links: ['/tab1', '/tab2', '/tabless/subpage']);
  }
}

class TabLessSubPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return TestPage(title: 'TabLessSubpage', links: ['/tab1', '/tab2', '/tabless']);
  }
}

class AppPage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _AppPageState();
}

class _AppPageState extends State<AppPage> {
  int _currentIndex = 0;
  bool _showTabs = true;

  @override
  void initState() {
    routerDelegate.addListener(() {
      print("Route changed");
      print("appRouterDelegate location: ${appRouterDelegate.currentBeamLocation.state.routeInformation.location!}" ?? '');
      String? location = routerDelegate.currentBeamLocation.state.routeInformation.location;
      print("routerDelegate location: ${location!}" ?? '');

      if (location == '/tab1') {
        return setState(() {
          _showTabs = true;
          _currentIndex = 0;
        });
      }
      if (location == '/tab2') {
        return setState(() {
          _showTabs = true;
          _currentIndex = 1;
        });
      }
      setState(() {
        _showTabs = false;
      });

    });
    super.initState();
  }

  @override
  void didUpdateWidget(covariant AppPage oldWidget) {
    super.didUpdateWidget(oldWidget);
  }

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Scaffold(
        body: Beamer(
          routerDelegate: routerDelegate,
          backButtonDispatcher: BeamerBackButtonDispatcher(
            delegate: routerDelegate,
            fallbackToBeamBack: false,
          ),
        ),
        bottomNavigationBar: _showTabs
            ? BottomNavigationBar(
                currentIndex: _currentIndex,
                items: const [
                  BottomNavigationBarItem(icon: Icon(Icons.tab), label: 'Tab1'),
                  BottomNavigationBarItem(icon: Icon(Icons.tab), label: 'Tab2'),
                ],
                onTap: (index) {
                  // if (index == _currentIndex) return;
                  if (index == 0) {
                    routerDelegate.beamToNamed('/tab1');
                  }
                  if (index == 1) {
                    routerDelegate.beamToNamed('/tab2');
                  }
                },
              )
            : null,
      ),
    );
  }
}

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

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      title: 'Tabs + TabLess',
      routeInformationParser: BeamerParser(),
      routerDelegate: appRouterDelegate,
      backButtonDispatcher: BeamerBackButtonDispatcher(
        delegate: appRouterDelegate,
        fallbackToBeamBack: false,
      ),
    );
  }
}

字符串
结果,我收到一个错误:

E/flutter (11913): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: 'package:flutter/src/widgets/focus_manager.dart': Failed assertion: line 1252 pos 12: '_focusedChildren.isEmpty || _focusedChildren.last.enclosingScope == this': Focused child does not have the same idea of its enclosing scope as the scope does.
E/flutter (11913): #0      _AssertionError._doThrowNew (dart:core-patch/errors_patch.dart:51:61)
E/flutter (11913): #1      _AssertionError._throwNew (dart:core-patch/errors_patch.dart:40:5)
E/flutter (11913): #2      FocusScopeNode.focusedChild (package:flutter/src/widgets/focus_manager.dart:1252:12)
E/flutter (11913): #3      _Autofocus.applyIfValid (package:flutter/src/widgets/focus_manager.dart:139:37)
E/flutter (11913): #4      FocusManager._applyFocusChange (package:flutter/src/widgets/focus_manager.dart:1610:17)
E/flutter (11913): #5      _microtaskLoop (dart:async/schedule_microtask.dart:40:21)
E/flutter (11913): #6      _startMicrotaskLoop (dart:async/schedule_microtask.dart:49:5)
E/flutter (11913): 

======== Exception caught by widgets library =======================================================
The following assertion was thrown while finalizing the widget tree:
Multiple widgets used the same GlobalKey.

The key [LabeledGlobalKey<NavigatorState>#0d783] was used by multiple widgets. The parents of those widgets were different widgets that both had the following description:
  Builder
A GlobalKey can only be specified on one widget at a time in the widget tree.
When the exception was thrown, this was the stack: 
#0      BuildOwner._debugVerifyGlobalKeyReservation.<anonymous closure>.<anonymous closure>.<anonymous closure> (package:flutter/src/widgets/framework.dart:2988:13)
#1      _LinkedHashMapMixin.forEach (dart:collection-patch/compact_hash.dart:625:13)
#2      BuildOwner._debugVerifyGlobalKeyReservation.<anonymous closure>.<anonymous closure> (package:flutter/src/widgets/framework.dart:2932:20)
#3      _LinkedHashMapMixin.forEach (dart:collection-patch/compact_hash.dart:625:13)
#4      BuildOwner._debugVerifyGlobalKeyReservation.<anonymous closure> (package:flutter/src/widgets/framework.dart:2927:36)
#5      BuildOwner._debugVerifyGlobalKeyReservation (package:flutter/src/widgets/framework.dart:2996:6)
#6      BuildOwner.finalizeTree.<anonymous closure> (package:flutter/src/widgets/framework.dart:3053:11)
#7      BuildOwner.finalizeTree (package:flutter/src/widgets/framework.dart:3135:8)
#8      WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:906:19)
#9      RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:358:5)
#10     SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1284:15)
#11     SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1214:9)
#12     SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:1072:5)
#13     _invoke (dart:ui/hooks.dart:142:13)
#14     PlatformDispatcher._drawFrame (dart:ui/platform_dispatcher.dart:359:5)
#15     _drawFrame (dart:ui/hooks.dart:112:31)
====================================================================================================

======== Exception caught by scheduler library =====================================================
The following assertion was thrown during a scheduler callback:
Focused child does not have the same idea of its enclosing scope as the scope does.
'package:flutter/src/widgets/focus_manager.dart':
Failed assertion: line 1252 pos 12: '_focusedChildren.isEmpty || _focusedChildren.last.enclosingScope == this'

Either the assertion indicates an error in the framework itself, or we should provide substantially more information in this error message to help you determine and fix the underlying cause.
In either case, please report this assertion by filing a bug on GitHub:
  https://github.com/flutter/flutter/issues/new?template=2_bug.md

When the exception was thrown, this was the stack: 
#2      FocusScopeNode.focusedChild (package:flutter/src/widgets/focus_manager.dart:1252:12)
#3      FocusScopeNode._doRequestFocus (package:flutter/src/widgets/focus_manager.dart:1337:17)
#4      FocusScopeNode.setFirstFocus (package:flutter/src/widgets/focus_manager.dart:1304:13)
#5      _ModalScopeState._routeSetState (package:flutter/src/widgets/routes.dart:886:57)
#6      ModalRoute.setState (package:flutter/src/widgets/routes.dart:1043:31)
#7      ModalRoute.offstage= (package:flutter/src/widgets/routes.dart:1458:5)
#8      HeroController._startHeroTransition (package:flutter/src/widgets/heroes.dart:899:8)
#9      HeroController._maybeStartHeroTransition.<anonymous closure> (package:flutter/src/widgets/heroes.dart:883:11)
#10     SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1284:15)
#11     SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1223:9)
#12     SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:1072:5)
#13     _invoke (dart:ui/hooks.dart:142:13)
#14     PlatformDispatcher._drawFrame (dart:ui/platform_dispatcher.dart:359:5)
#15     _drawFrame (dart:ui/hooks.dart:112:31)
(elided 2 frames from class _AssertionError)
====================================================================================================


pubspec.yaml:

name: navig
description: A new Flutter project.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 1.0.0+1

environment:
  sdk: '>=3.0.6 <4.0.0'

dependencies:
  flutter:
    sdk: flutter

  cupertino_icons: ^1.0.2
  beamer: ^1.5.6

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^2.0.0

flutter:
  uses-material-design: true


要点如下:https://gist.github.com/andr2k/27918aec82296f45c9d8ac41450b559a


的数据

ndh0cuux

ndh0cuux1#

显然,当一个激光器在另一个激光器下面时,问题就出现了。作为临时解决方法,切换到一个具有参数化BackButtonDispatcher的Beamer,而不是使用多个Beamer:

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      title: 'Tabs + TabLess',
      routeInformationParser: BeamerParser(),
      routerDelegate: routerDelegate,
      backButtonDispatcher: BeamerBackButtonDispatcher(
          delegate: routerDelegate,
          fallbackToBeamBack: false,
          onBack: (BeamerDelegate delegate) async {
            if (await delegate.popRoute()) {
              return true;
            }

            if (delegate.currentBeamLocation is TabbedLocation) {
              return false;
            }

            if (delegate.canPopBeamLocation) {
              delegate.popBeamLocation();
              return true;
            }

            return false;
          }),
    );
  }
}

字符串

相关问题