gpt4 book ai didi

flutter - 从抽屉中导航部分屏幕

转载 作者:行者123 更新时间:2023-12-02 18:51:32 25 4
gpt4 key购买 nike

假设我有一个具有以下设置的应用程序:

  @override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Container(
color: Colors.grey[200],
child: Row(
children: [
MainMenu(),
Expanded(child: MainLoginScreen()),
],
),
));
}
}

我想知道如何使用任何 .push() 方法仅从 MainMenu 导航 MainLoginScreen 小部件。

(我找到了一种从主登录屏幕内的上下文进行导航的方法,方法是用 MaterialApp 小部件包装它,但如果我想使用 MainMenu 小部件,它有另一个上下文,该怎么办)

最佳答案

人们普遍认为“屏幕”是 route 最顶层的小部件。 'screen' 的实例是您传递给 Navigator.of(context).push(MaterialPageRoute(builder: (context) => HereGoesTheScreen()) 的内容。因此,如果它位于 Scaffold 下,则它是不是屏幕。也就是说,以下是选项:

1。如果您想使用带有“后退”按钮的导航

使用不同的屏幕。为了避免代码重复,请创建 MenuAndContentScreen 类:

class MenuAndContentScreen extends StatelessWidget {
final Widget child;

MenuAndContentScreen({
required this.child,
});

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Container(
color: Colors.grey[200],
child: Row(
children: [
MainMenu(),
Expanded(child: child),
],
),
),
);
}
}

然后为每个屏幕创建一对屏幕和一个嵌套小部件:

class MainLoginScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MenuAndContentScreen(
child: MainLoginWidget(),
);
}
}

class MainLoginWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Here goes the screen content.
}
}

2。如果您不需要使用“后退”按钮进行导航

您可以使用IndexedStack小部件。它可以包含多个小部件,一次只能看到一个小部件。

class MenuAndContentScreen extends StatefulWidget {
@override
_MenuAndContentScreenState createState() => _MenuAndContentScreenState(
initialContentIndex: 0,
);
}

class _MenuAndContentScreenState extends State<MenuAndContentScreen> {
int _index;

_MainMenuAndContentScreenState({
required int initialContentIndex,
}) : _contentIndex = initialContentIndex;

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Container(
color: Colors.grey[200],
child: Row(
children: [
MainMenu(
// A callback that will be triggered somewhere down the menu
// when an item is tapped.
setContentIndex: _setContentIndex,
),
Expanded(
child: IndexedStack(
index: _contentIndex,
children: [
MainLoginWidget(),
SomeOtherContentWidget(),
],
),
),
],
),
),
);
}

void _setContentIndex(int index) {
setState(() {
_contentIndex = index;
});
}
}

第一种方式通常是首选,因为它是声明式的,这是 Flutter 中的一个主要思想。当您静态声明整个小部件树时,出错和需要跟踪的事情就会减少。一旦你感受到了,那真的是一种享受。如果您想避免后退导航,请按照 ahmetakil 在评论中建议的那样使用替换:Navigator.of(context).pushReplacement(...)

当 MainMenu 需要保持某些需要在 View 之间保留的状态时,通常会使用第二种方法,因此我们选择在一个屏幕上显示可互换的内容。

3。使用嵌套导航器小部件

当您特别询问嵌套 Navigator 小部件时,您可以使用它而不是 IndexedStack:

class MenuAndContentScreen extends StatefulWidget {
@override
_MenuAndContentScreenState createState() => _MenuAndContentScreenState();
}

class _MenuAndContentScreenState extends State<MenuAndContentScreen> {
final _navigatorKey = GlobalKey();

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Container(
color: Colors.grey[200],
child: Row(
children: [
MainMenu(
navigatorKey: _navigatorKey,
),
Expanded(
child: Navigator(
key: _navigatorKey,
onGenerateRoute: ...
),
),
],
),
),
);
}
}

// Then somewhere in MainMenu:
final anotherContext = navigatorKey.currentContext;
Navigator.of(anotherContext).push(...);

这应该可以解决问题,但这是一个不好的做法,因为:

  1. MainMenu 知道特定的导航器存在并且应该与其交互。最好像 (2) 中那样使用回调来抽象这些知识,或者像 (1) 中那样不使用特定的导航器。 Flutter 实际上是沿着树向下而不是向上传递信息。
  2. 有时您想突出显示 MainMenu 中的事件项目,但 MainMenu 很难知道导航器中当前有哪个小部件。这将添加另一个非向下交互。

对于此类交互,存在 BLoC 模式

在 Flutter 中,BLoC 代表业务逻辑组件。在最简单的形式中,它是一个在父窗口小部件中创建的普通对象,然后传递到 MainMenu 和 Navigator,然后这些窗口小部件可以通过它发送事件并监听它。

class CurrentPageBloc {
// int is an example. You may use String, enum or whatever
// to identify pages.
final _outCurrentPageController = BehaviorSubject<int>();
Stream<int> _outCurrentPage => _outCurrentPageController.stream;

void setCurrentPage(int page) {
_outCurrentPageController.sink.add(page);
}

void dispose() {
_outCurrentPageController.close();
}
}

class MenuAndContentScreen extends StatefulWidget {
@override
_MenuAndContentScreenState createState() => _MenuAndContentScreenState();
}

class _MenuAndContentScreenState extends State<MenuAndContentScreen> {
final _currentPageBloc = CurrentPageBloc();

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Container(
color: Colors.grey[200],
child: Row(
children: [
MainMenu(
currentPageBloc: _currentPageBloc,
),
Expanded(
child: ContentWidget(
currentPageBloc: _currentPageBloc,
onGenerateRoute: ...
),
),
],
),
),
);
}

@override
void dispose() {
_currentPageBloc.dispose();
}
}

// Then in MainMenu:
currentPageBloc.setCurrentPage(1);

// Then in ContentWidget's state:
final _navigatorKey = GlobalKey();
late final StreamSubscription _subscription;

@override
void initState() {
super.initState();
_subscription = widget.currentPageBloc.outCurrentPage.listen(_setCurrentPage);
}

@override
Widget build(BuildContext context) {
return Navigator(
key: _navigatorKey,
// Everything else.
);
}

void _setCurrentPage(int currentPage) {
// Can't use this.context, because the Navigator's context is down the tree.
final anotherContext = navigatorKey?.currentContext;
if (anotherContext != null) { // null if the event is emitted before the first build.
Navigator.of(anotherContext).push(...); // Use currentPage
}
}

@override
void dispose() {
_subscription.cancel();
}

这有优点:

  • MainMenu 不知道谁会收到该事件(如果有的话)。
  • 任意数量的监听器都可以监听此类事件。

但是,Navigator 仍然存在一个根本性缺陷。无需了解主菜单,即可使用“后退”按钮或其内部小部件对其进行导航。因此没有一个变量知道现在显示的是哪个页面。要突出显示事件菜单项,您需要查询导航器堆栈,这消除了 BLoC 的优势。

出于所有这些原因,我仍然建议前两个解决方案之一。

关于flutter - 从抽屉中导航部分屏幕,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66690100/

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