【问题标题】:How can i do Navigator.push in a modal bottom sheet only (not the parent page)我怎样才能只在模态底部工作表中执行 Navigator.push(不是父页面)
【发布时间】:2025-12-10 20:35:02
【问题描述】:

在我的应用程序中,我正在尝试创建一个 cmets 部分。在本节中,我希望用户能够以类似于 YouTube 应用的方式回复其他用户的评论。我已经设法创建了一个显示* cmets 的模态底部表,并且在几个流构建器的帮助下它运行良好。但是,我面临的问题是二级cmets(回复人们的cmets。)。我想让它让用户点击评论并被带到一个页面(仍然在模态底部表中),在那里他可以看到对评论的所有回复并且也可以自己发表评论,如果他回击,他会去立即回到他所在的位置,并且可以继续滚动浏览* cmets。

类似于 YouTube 应用的外观。 我有在底部表中使用 Navigator.push 的想法,在模态底部表中当前现有页面的顶部覆盖一个新页面,在这个新页面中,特定评论的第二级 cmets(回复)是被显示。但是,当我调用 Navigator.push 时,整个页面都会发生变化。整个父页面转移到新页面,而不仅仅是底部表格中的内容。所以,这是我的问题。我对想法持开放态度。

我的评论底页背后的代码:

  showModalBottomSheet(
  context: context,
  builder: (context) {
    return 
    Container(
      child: StatefulBuilder(
        builder: (BuildContext context, setTheModalState) {
          setModalState = setTheModalState;
          return Container(
            child: Column(
              children: <Widget>[
                Expanded(
                  child: StreamBuilder(listview))])}))}

这是我迄今为止设计的。现在棘手的一点是在按下回复按钮(消息图标)时在模式表中创建一个新页面

这就是 YouTube 评论系统的样子。

评论区代码:

showModalBottomSheet(
  context: context,
  builder: (context) {
    return 
    Container(
      child: StatefulBuilder(
        builder: (BuildContext context, setTheModalState) {
          setModalState = setTheModalState;
          return Container(
            child: Column(
              children: <Widget>[
                Expanded(
                  child: StreamBuilder(
                    stream: Firestore.instance
                        .collection("Replies")
                        .document(dream.documentID)
                        .collection(dream.documentID)
                        .orderBy('time', descending: true)
                        .snapshots(),
                    builder: (context, snapshot) {
                      if (!snapshot.hasData) {
                        return Center(
                          child: SpinKitDoubleBounce(
                            color: Colors.blue,
                          ),
                        );
                      } else {
                        dynamic replies = snapshot.data.documents;

                        return ListView.builder(
                          itemCount: replies.length,
                          reverse: true,
                          itemBuilder: (context, index) {
                            if (replies[index]["text"] != "" &&
                                replies[index]["images"] == null) {
                              return _buildStringCommment(
                                  replies[index], true);
                            } else {
                              if (replies[index]["images"].length == 1) {
                                return _buildCommentFromOneImage(
                                    replies[index], true);
                              } else {
                                if (replies[index]["images"].length == 2) {
                                  return _buildCommentFromTwoImages(
                                      replies[index], true);
                                } else {
                                  if (replies[index]["images"].length ==
                                      3) {
                                    return _buildCommentFromThreeImages(
                                        replies[index], true);
                                  } else {
                                    if (replies[index]["images"].length ==
                                        4) {
                                      return _buildCommentFromFourImages(
                                          replies[index], true);
                                    } else {
                                      if (replies[index]["images"].length ==
                                          5) {
                                        return _buildCommmentFromFiveImages(
                                            replies[index], true);
                                      } else {
                                        return _buildMessageFromMoreThanFiveImages(
                                            replies[index], true);
                                      }
                                    }
                                  }
                                }
                              }
                            }
                          },
                        );
                      }
                    },
                  ),
                ),
                theTypeReplyThingie(dream, context, true)
              ],
            ),
          );
        },
      ),
    );
  },
);

“type reply thingie”是具有文本输入表单和发送按钮的小部件。

【问题讨论】:

  • 让我再澄清一下这个问题。我已经设法展示了底页和第一级 cmets。但我希望当用户点击评论时,他会被带到一个新页面(在底部表格内),其中显示了对评论的回复。有关我的意思的示例,请查看 youtube 应用程序。他们的评论系统与我正在尝试制作的类似。我已经创建了底部工作表并进行了*评论。我想不通的是如何使用 Navigator.push 在模态表中创建一个新页面,其中显示了对 cme​​ts 的回复。

标签: flutter flutter-layout flutter-navigation


【解决方案1】:

您好,我认为您需要在底页内导航,因为它发生在正常页面中,您可以继续使用下面的代码,只要我希望它能够工作,就测试一下这个代码,谢谢。 每当您需要打开工作表时调用 openbottomsheet

  void opensheet() async {
    showModalBottomSheet(
        context: (context),
        enableDrag: true,
        isDismissible: true,
        builder: (context) {
          return Page1();
        });
    }
  }

  class Page1 extends StatefulWidget {
    @override
  _Page1State createState() => _Page1State();
  }

  class _Page1State extends State<Page1> {
  int currentview = 0;
  List<Widget> pages;

  @override
  void initState() {
    pages = [
      page1(),
      page2(),
    ];
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return pages[currentview];
  }

  Widget page1() {
    return Container(
      decoration: BoxDecoration(
          color: Colors.white,
          borderRadius: BorderRadius.only(
              topRight: Radius.circular(60), topLeft: Radius.circular(60))),
      height: 400,
      width: double.maxFinite,
      child: Center(
        child: InkWell(
          onTap: () {
            setState(() {
              currentview = 1;
            });
          },
          child: RaisedButton(
            child: Text("click to navigate"),
          ),
        ),
      ),
    );
  }

  Widget page2() {
    return Container(
      decoration: BoxDecoration(
          color: Colors.white,
          borderRadius: BorderRadius.only(
              topRight: Radius.circular(60), topLeft: Radius.circular(60))),
      height: 400,
      width: double.maxFinite,
      child: Center(
        child: InkWell(
          onTap: () {
            setState(() {
              currentview = 0;
            });
          },
          child: Text("click to move back"),
        ),
      ),
    );
    }
  }

控制父页面上的后退按钮,底部工作表位于其中,用 willpopscope 包围脚手架

   willpopscope(
onwillpopscope:(){
//check if bottomsheet is open or not this may be hard to listen the state of bottomsheet let me try it 

if(bottomsheetisopen){
//set the bottomsheet currentindex to 0 for initial page 
return Future.value(false);
}else{
return Future.value(true);
}

}
child:Scaffold()
)

尝试bottomsheet和modalbottom sheet其中之一提供一个监听器来监听工作表的状态,它是打开或关闭的

【讨论】:

  • 这看起来很有希望。我会尝试一下,让你知道
  • 我已经对此进行了测试,它确实有效。但是,它存在一些问题。当用户在第二级 cmets 页面上并且他按下返回按钮时,底部工作表关闭,而不是将他带回第一个页面,即使我放置了一个按钮将用户带回* cmets 页面,它一直滚动到底部,这可能不是令人愉快的用户体验。
  • 因此,如果我们不想在用户按下后退按钮时关闭底页,请在父窗口小部件中使用 willpopscope 属性,如果底页打开,您将返回 false 并将当前值更改为 0,或者您可以说初始页面的索引,我将用更多控制后退按钮的代码来更新答案
  • Soo,通过稍微修改您的答案,我设法完成了我想要的。我将包含* cmets(在有状态构建器内部)的容器包装在一个堆栈中,并使用名为“weAreOnTheTopLevelComments”的布尔值,我将其设置为在我们位于* cmets 时显示高度为 0 的大小框或包含对给定评论的所有回复的容器。因此,本质上,当用户点击回复评论按钮时,他只需将此布尔值设置为 true,并设置一些变量将由流构建器和回复页面上的其他小部件使用。跨度>
【解决方案2】:

有两种方法可以实现。

  1. 如果您使用的是modalSheet,那么为什么需要使用Navigator.push?可以说,您有一个评论文本字段/按钮。对于 onPressed 回调,您需要调用 showModalSheet。你不需要路由。

    `Inkwell(child: Text("comment"),
            onTap: () => showModalSheet() //your sheet here
         ) ` 
    
  2. 您可以在 MaterialPageRoute 中使用 fullScreenDailog 属性。这将更符合您的要求。

       Navigator.of(context).push(
          MaterialPageRoute(
                fullscreenDialog: true,
                builder: (context) => XYZ(),)
    

【讨论】:

  • 我认为您没有很好地理解我的问题。我已经设法展示了底页和第一级 cmets。但我希望当用户点击评论时,他会被带到一个新页面(在底部表格内),其中显示了对评论的回复。有关我的意思的示例,请查看 youtube 应用程序。他们的评论系统与我正在尝试制作的类似。我已经创建了底部工作表并进行了*评论。我想不通的是如何使用 Navigator.push 在模态表中创建一个新页面,其中显示了对 cme​​ts 的回复。
  • 对不起。我意识到我没有很好地表达这个问题。请检查。我已经对其进行了编辑,更清楚地说明了我要做什么。
【解决方案3】:

Soooo,为了其他可能有同样疑问的人的利益,我已经设法使用 Azad Prajapat 先生的答案的修改版本来做到这一点。我在下面附上相关代码。

基本上,我使用堆栈来显示高度为 0 的大小框或显示具有流生成器、文本表单和 reply_to_a_comment 页面的其他相关小部件的容器。使用布尔值,我可以轻松地在用户看到的内容之间切换,而无需重新加载整个评论页面,或者用户丢失当前滚动位置。 PS:我是flutter的初学者,所以,有些代码可能看起来太样板了。请耐心等待。

新的评论区代码:

 wereOnTopLevel = true;
showModalBottomSheet(
  context: context,
  builder: (context) {
    return Container(
      child: StatefulBuilder(
        builder: (BuildContext context, setTheModalState) {
          setModalState = setTheModalState;
          return Stack(
            children: <Widget>[
              Container(
                child: Column(
                  children: <Widget>[
                    Expanded(
                      child: StreamBuilder(
                        stream: Firestore.instance
                            .collection("Replies")
                            .document(post.documentID)
                            .collection(post.documentID)
                            .orderBy('time', descending: true)
                            .snapshots(),
                        builder: (context, snapshot) {
                          if (!snapshot.hasData) {
                            return Center(
                              child: SpinKitDoubleBounce(
                                color: Colors.blue,
                              ),
                            );
                          } else {
                            dynamic replies = snapshot.data.documents;

                            return ListView.builder(
                              itemCount: replies.length,
                              reverse: true,
                              itemBuilder: (context, index) {
                                if (replies[index]["text"] != "" &&
                                    replies[index]["images"] == null) {
                                  return _buildStringReply(
                                      replies[index], true, true);
                                } else {
                                  if (replies[index]["images"].length ==
                                      1) {
                                    return _buildMessageFromOneImage(
                                        replies[index], true, true);
                                  } else {
                                    if (replies[index]["images"].length ==
                                        2) {
                                      return _buildMessageFromTwoImages(
                                          replies[index], true, true);
                                    } else {
                                      if (replies[index]["images"].length ==
                                          3) {
                                        return _buildMessageFromThreeImages(
                                            replies[index], true, true);
                                      } else {
                                        if (replies[index]["images"]
                                                .length ==
                                            4) {
                                          return _buildMessageFromFourImages(
                                              replies[index], true, true);
                                        } else {
                                          if (replies[index]["images"]
                                                  .length ==
                                              5) {
                                            return _buildMessageFromFiveImages(
                                                replies[index], true, true);
                                          } else {
                                            return _buildMessageFromMoreThanFiveImages(
                                                replies[index], true, true);
                                          }
                                        }
                                      }
                                    }
                                  }
                                }
                              },
                            );
                          }
                        },
                      ),
                    ),
                    theTypeReplyThingie(dream, context, true)
                  ],
                ),
              ),
              wereOnTopLevel
                  ? SizedBox(
                      height: 0,
                    )
                  : showTheRepliesToCommentsThing(
                      dream, context, idYaComment, commentInfo)
            ],
          );
        },
      ),
    );
  },
);

wereOnTopLevel 是一个布尔值,它告诉底部工作表用户是否点击了某个评论上的“replyToAComment”按钮。

theTypeReplyThing 是包含 textFormField、表情符号网格、用于显示所选图像网格的容器、发送按钮和用于将图像附加到回复/评论的按钮的小部件。

showTheRepliesToTheCommentsThing 是包含回复的小部件。它与评论页面或多或少相同,除了它,我在顶部放了一个正在回复的评论的小预览,我不得不将容器的颜色设置为白色,因为它是透明的并且这看起来很混乱。

让我附上一张reply_to_a_comment 页面的图片以显示差异。 (我稍后在顶部栏下方添加了一个分隔线。让它看起来更好) 将其与我附加到问题的评论部分的图像进行对比。

【讨论】:

    【解决方案4】:

    你必须使用 showModalBottomSheet() 方法,它会返回你想要的评论部分。

    示例代码是:

    import 'package:flutter/material.dart';
    
    void main() => runApp(MyApp());
    
    /// This Widget is the main application widget.
    class MyApp extends StatelessWidget {
      static const String _title = 'Flutter Code Sample';
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: _title,
          home: Scaffold(
            appBar: AppBar(title: const Text(_title)),
            body: MyStatelessWidget(),
          ),
        );
      }
    }
    
    /// This is the stateless widget that the main application instantiates.
    class MyStatelessWidget extends StatelessWidget {
      MyStatelessWidget({Key key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return Center(
          child: RaisedButton(
            child: const Text('showModalBottomSheet'),
            onPressed: () {
              showModalBottomSheet<void>(
                context: context,
                builder: (BuildContext context) {
                  return Container(
                    height: 200,
                    color: Colors.amber,
                    child: Center(
                      child: Column(
                        mainAxisAlignment: MainAxisAlignment.center,
                        mainAxisSize: MainAxisSize.min,
                        children: <Widget>[
                          const Text('Modal BottomSheet'),
                          RaisedButton(
                            child: const Text('Close BottomSheet'),
                            onPressed: () => Navigator.pop(context),
                          )
                        ],
                      ),
                    ),
                  );
                },
              );
            },
          ),
        );
      }
    }
    

    欲了解更多信息,请访问:https://api.flutter.dev/flutter/material/showModalBottomSheet.html

    【讨论】: