【问题标题】:Navigation Drawer does not work for Screen within Screen导航抽屉不适用于屏幕内的屏幕
【发布时间】:2019-02-05 18:04:01
【问题描述】:

我想在所有屏幕上保留 Navigation Drawer。在这个问题上一直存在问题,但我的问题与 e.g. 略有不同。 Persisting AppBar Drawer across all Pages Flutter

我有导航抽屉,其中包含名为 A、B 和 C 的项目列表。在导航抽屉中单击 A 时,屏幕 A 将打开,并且 B 和 C 的内容分别相同。现在 C 屏幕有一个按钮,点击该按钮按钮 我要去屏幕 D,现在虽然屏幕 D 显示导航抽屉图标,但抽屉永远不会打开。我尝试在调用抽屉的方法中打印一条语句,打印语句确实打印但抽屉永远不会打开。以下是我的代码

我有一个基类,其抽屉如下

 class BaseScreen extends StatefulWidget {
  final List<Menu> menuList;
  final String userType;
  final String userId;

  const BaseScreen({Key key, this.menuList, this.userType, this.userId})
      : super(key: key);

  @override
  BaseScreenState createState() {
    return new BaseScreenState();
  }
}

class BaseScreenState extends State<BaseScreen> {
  String screenNameSelected = "A";

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Stack(
        children: <Widget>[
          _getDrawerItemWidget(screenNameSelected),

        ],
      ),
      drawer: SizedBox(
        width: 100,
        child: Drawer(
          child: ListView.separated(
            separatorBuilder: (context, index) => Material(
                  elevation: 2,
                  shadowColor: shadow,
                  child: Divider(
                    color: white,
                  ),
                ),
            itemBuilder: (BuildContext context, int index) {
              return ListTile(
                onTap: () {
                  openScreen(
                      widget.menuList[index].title,
                      widget.userId,
                      MenuList.returnLoginType(widget.userType).toString(),
                      context);
                  Navigator.pop(context);
                },
                title: Padding(
                  padding: const EdgeInsets.only(top: 8),
                  child: Image.asset(
                    widget.menuList[index].image,
                    width: 35,
                    height: 35,
                  ),
                ),

              );
            },
            itemCount: widget.menuList?.length ?? 0,
            shrinkWrap: true,
            physics: BouncingScrollPhysics(),
          ),
        ),
      ),
    );
  }

  _getDrawerItemWidget(String selectedScreenName) {
    switch (selectedScreenName) {
      case A:
        return A();
      case B:
        return B();
      case C:
        return C();

      default:
        return Container();
    }
  }

  void openScreen(String screenName, String userId, String loginType,
      BuildContext context) {
    if (screenName.toLowerCase() == "A".toLowerCase()) {
      setState(() {
        screenNameSelected = "A";
      });
    } else if (screenName.toLowerCase() == "B".toLowerCase()) {
      setState(() {
        screenNameSelected = "B";
      });
    } else if (screenName.toLowerCase() == "C".toLowerCase()) {
      setState(() {
        screenNameSelected = "C";
      });
    }

  }

}

我为应用栏创建了一个自定义类,因为我的应用栏是高度自定义的

class CustomAppBar extends StatelessWidget {
  final String subTitleText;
  final String subTitleImage;

  const CustomAppBar(
      {Key key, @required this.subTitleText, @required this.subTitleImage})
      : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: <Widget>[
        SafeArea(
          child: Column(
            children: <Widget>[
              Builder(builder: (context) => customAppBar(context)),
              SizedBox(
                height: 5.0,
              ),
              subTitleRow(
                subTitleText,
                subTitleImage,
              )
            ],
          ),
        ),

      ],
    );
  }
}

Widget subTitleRow(String subtitleText, String subtitleImage) {
.........
}


Widget customAppBar(BuildContext context) {
  return Material(
    elevation: 5.0,
    shadowColor: shadow,
    child: SafeArea(
      child: Stack(
        children: <Widget>[
          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            mainAxisSize: MainAxisSize.max,
            children: <Widget>[
              Image.asset(
                "images/toolbar_logo.webp",
                width: 80,
                height: 50,
              ),
            ],
          ),
          IconButton(
            icon: Image.asset("images/menu.webp"),
            onPressed: () {
              Scaffold.of(context).openDrawer();
            },
            iconSize: 20,
          ),
        ],
      ),
    ),
  );
}

现在我的 A、B 和 C 班没有脚手架,但我为 D 班提供了脚手架,我认为这是导致问题的原因。不为 D 类提供脚手架并不能提供正确的布局

A、B、C类代码如下

class A extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ShortBioProvider(
      child:  Column(
            children: <Widget>[
              CustomAppBar(
                subTitleImage: "images/settings.webp",
                subTitleText: SETTINGS.toUpperCase(),
              ),
              SizedBox(
                height: 20,
              ),
              SettingsList(),
            ],
          ),

    );
  }
}

B 和 C 与 A 非常相似。

现在是 D 类代码

class D extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(body: Column(children: <Widget>[
      CustomAppBar(
        subTitleImage: "images/settings.webp",
        subTitleText: D.toUpperCase(),
      ),
      SizedBox(
        height: 20,
      ),
      ........
    ],),);
  }
}

我已经对上述类进行了很多编辑,因此这里或那里可能缺少括号或分号

【问题讨论】:

  • 看起来您正在使用函数而不是它们自己的类在同一个类中构建小部件。这可能会导致与 BuildContext 相关的问题,但如果没有您的所有代码,就很难判断。我首先建议将基本上是封闭小部件(即应用栏)的东西移动到它们自己的有状态或无状态小部件类中,然后如果这无助于发布所有相关代码(即特别是屏幕 D 可能是有趣的)。如果那里有专有的东西(即使没有),如果你从代码中删除任何不相关的东西会很有帮助。
  • @rmtmckenzie 我正在使用 A、B、C 和 D 屏幕的类。自定义应用栏也在单独的类中。我将很快发布 D 类的代码
  • @rmtmckenzie 请看一下我已经为所有类添加了代码

标签: dart flutter navigation-drawer


【解决方案1】:

啊,现在您已经添加了代码,这很有意义。

发生的情况是,在 D 的情况下,您有两个不同的脚手架:

BaseScreenState
--> Scaffold 1
    -->  drawer
         body
           --> stack
             --> D
               --> Scaffold 2
                 -->CustomAppBar ....

当您执行 Scaffold.of(context) 时,它会在树中查找最近的脚手架,在这种情况下恰好是 Scaffold 2,它没有抽屉。您的 D 类中的脚手架似乎没有做任何事情,因此删除它应该可以解决您的问题。

但是

您的代码中存在一些更严重的问题。您几乎不应该使用堆栈在页面之间切换。一方面,flutter 不能以这种方式缓存小部件,但它也消除了屏幕转换等内容。

您应该做的是为应用中的每个页面创建一个包含支架、抽屉、应用栏等的页面,然后使用导航器在这些页面之间切换。 (请注意,仅仅因为您为每个页面制作了一个小部件,并不意味着无法共享组件。

在 Flutter 中有意义的架构如下:

  • 顶级有状态小部件(有状态更适合热重载,无状态小部件可能会导致问题),例如 MyApp(但将 My 更改为实际应用的名称)。
  • 在构建函数中,这将创建一个 MaterialApp。您可以将构建各种页面的逻辑放入 materialApp 的onGenerateRoute 中 - 我发现这可以比即时构建更清晰、更容易判断正在发生的事情。
  • 如果您是按名称(字符串)在页面之间转换,则应将名称设置为全局常量。
  • 每个页面的有状态或无状态小部件(除非页面足够相似,可以通过一些简单的参数进行区分),在构建函数中将制作脚手架、应用栏、导航抽屉等
  • 脚手架也可以是它自己的小部件
  • 应用栏的小部件(即 CustomAppBar)
  • 导航抽屉小部件

还有几点需要注意:

  1. 通过构建函数而不是类来拆分内容似乎可能更快,但事实并非如此。在一个构建函数中包含两个许多小部件(不要被愚弄,调用从构建函数返回小部件的函数与将代码直接放入函数中以提高性能相同)实际上可能意味着更多的计算颤动,因为每次需要重建小部件时,都需要构建和检查整个类集,而如果您在小部件中使用适当的封装,那么如果小部件树的那部分没有任何变化,则可以跳过很多工作。
  2. 在每个页面中构建 Scaffold、AppBar 和 Drawer 似乎是一种浪费。但是,由于 Flutter 缓存内容并智能扫描小部件树的方式,它实际上非常高效,这也是 Flutter 团队推荐的方式。
  3. 始终确定您从哪个构建上下文调用事物,然后您可以跟进构建树以确定调用 .of(context) 时实际调用的是哪个小部件。

希望对你有帮助!

【讨论】:

    猜你喜欢
    • 2020-09-12
    • 1970-01-01
    • 1970-01-01
    • 2020-09-04
    • 1970-01-01
    • 1970-01-01
    • 2021-06-15
    • 1970-01-01
    • 2021-06-06
    相关资源
    最近更新 更多