Flutter | 在ListView中使用Listview.builder - 使整个页面可滚动

4
我想建立一个类似Instagram的个人资料页面,在该页面中,当前用户的所有帖子将在动态列表中列出。此外,应显示有关用户的一些信息(头像、粉丝数、发帖数、个人简介等)。用户应能够滑动整个页面。 目前,该页面只能滚动到ListView.builder中的sizedBox-widget的高度。就像这样:

enter image description here

ProfilePage类的构建方法如下:
Widget build(BuildContext context) {

    return SafeArea(
      child: Scaffold(    
        appBar: AppBar(),
        body: ListView(    
          children: <Widget>[
           Padding(
                    padding: const EdgeInsets.only(left: 25.0, top: 30.0),
                    child: Text(_user.displayName,
                        style: TextStyle(
                            color: Colors.black,
                            fontWeight: FontWeight.bold,
                            fontSize: 20.0)),
                  ),
                  ...(all the other information for the "header-part")

                  postImagesWidget(),

            ])));}

postImagesWidget 中,我使用 ListView.builder 来构建动态内容:
Widget postImagesWidget(){
return FutureBuilder(
            future: _future,
            builder:
                ((context, AsyncSnapshot<List<DocumentSnapshot>> snapshot) {
              if (snapshot.hasData) {
                if (snapshot.connectionState == ConnectionState.done) {
                  return SizedBox(
                    height: 600,
                      child: ListView.builder(
                          physics: const NeverScrollableScrollPhysics(),

                          //shrinkWrap: true,
                          itemCount: snapshot.data.length,
                          itemBuilder: ((context, index) => ListItem(
                              list: snapshot.data,
                              index: index,
                              user: _user))));
                } else {
                  return Center(
                    child: CircularProgressIndicator(),
                  );
                }
              } else {
                return Center(
                  child: CircularProgressIndicator(),
                );
              }
            }),
          );

}

为了避免这个错误,我不得不添加一个SizedBox-Widget:

Another exception was thrown: RenderBox was not laid out: RenderViewport#50ccf NEEDS-PAINT

这个错误的描述说我应该给我的ListView.builder添加shrinkwrap,但是当我滚动一段时间后,这会导致整个应用程序崩溃并出现以下错误:

Failed assertion: line 470 pos 12: 'child.hasSize': is not true.

如何使整个页面可滚动而不是独立滚动两个部分(标题和内容)?

我知道这与我在ListView内部使用ListView.builder有关...

最好的问候。

2个回答

6
我个人不会在Flutter中使用有固定尺寸的组件来创建列表,除非您想要特定区域的布局可滚动。

但是从您的情况来看,我认为您希望整个页面都可以滚动,因此您需要一个起始点,可以从那里设计页面的其余部分(整个页面应该是可滚动的,所以我理解这个页面的顶部小部件应该是一个可滚动小部件,如SingleChildScrollView、CustomScrollView、ListView等)。

现在您的方法并不错误,但是需要您知道SizedBox的最终高度,这可以根据您的列表进行计算(过于复杂)。

为了解决您的问题,我使用了一个包含SliverList小部件的CustomScrollView。

return SafeArea(
        child: Scaffold(
            appBar: AppBar(),
            body: CustomScrollView(
              slivers: <Widget>[
                SliverList(
                  delegate: SliverChildListDelegate(
                    [
                      Padding(
                        padding: const EdgeInsets.only(left: 25.0, top: 30.0),
                        child: Text(displayName,
                            style: TextStyle(
                                color: Colors.black, fontWeight: FontWeight.bold, fontSize: 20.0)),
                      ),
                    ],
                  ),
                ),
                FutureBuilder(
                  future: ApiProvider.getPictures(),
                  builder: ((context, AsyncSnapshot snapshot) {
                    if (snapshot.hasData) {
                      if (snapshot.connectionState == ConnectionState.done) {
                        return SliverList(
                            delegate: SliverChildBuilderDelegate(
                                (context, index) => Container(
                                      margin: EdgeInsets.symmetric(vertical: 10),
                                      child: Column(
                                        crossAxisAlignment: CrossAxisAlignment.start,
                                        children: <Widget>[
                                          Text(
                                            displayName,
                                            style: TextStyle(
                                                fontSize: 17, fontWeight: FontWeight.bold),
                                          ),
                                          Text(snapshot.data[index].location),
                                          Image(
                                            image: NetworkImage(snapshot.data[index].pictureUrl),
                                            width: MediaQuery.of(context).size.width,
                                          )
                                        ],
                                      ),
                                    ),
                                childCount: snapshot.data.length));
                      } else {
                        return SliverFillRemaining(
                          child: Center(
                            child: CircularProgressIndicator(),
                          ),
                        );
                      }
                    } else {
                      return SliverFillRemaining(
                        child: Center(
                          child: CircularProgressIndicator(),
                        ),
                      );
                    }
                  }),
                ),
              ],
            )));

您的问题还有其他解决方案,但就性能而言,我认为这是最佳解决方案。

如果需要帮助,请随时提出。


谢谢你的建议!我选择了Marcel的解决方案,但是你能否解释一下,为什么你认为你的解决方案会更高效? - Martin Seubert
2
在第二个 SliverList 中,你可以看到我使用了 SliverChildBuilderDelegate,它会在滚动屏幕时创建项目,类似于 ListView.builder。 我这样做是因为可能需要显示大量的图片,如果您不需要它,则无需创建图像小部件。 我个人在我的项目中使用了类似 Marcel 解决方案的代码,但将列表映射并在列中显示可能不是从内存和性能角度来讲最有效的方法。 - Andrei

1

内部的 ListView 并不是处理当前任务的正确工具。您想要做的是在彼此上方显示多个小部件,而Column 就是为此而设计的。 因此,不要使用内部的ListView,请考虑使用以下方式之一的Column

Column(
  children: snapshot.data.map((data) {
    return ListItem(
      data: data,
      user: _user,
    );
  }).toList(),
)

请注意,我已更新 ListItem 小部件,以接受具体数据部分而不是列表和索引。
现在你可能会想知道 Column 是否比 ListView.builder 性能差,但实际上并不是这样。这是因为 ListView.builder 本身没有滚动,所以它没有懒惰地布局其子项。相反,外部的 ListView 将决定是否显示所有内部的 ListView
从长远来看,您可能希望延迟初始化小部件(特别是如果您有数千张图片的用户)。这里是StackOverflow答案,介绍如何通过从BLoC提供多个 Future 来实现延迟初始化。

1
非常好的解释,谢谢!我尝试了你的解决方案,它有效,直到我向上滚动并再次到达页面头部。它抛出以下错误:“indexOf(child) > index”:不是真的。有什么想法吗?我在谷歌上搜索了一下,但没有找到任何东西。 - Martin Seubert
问题已解决。它与使用3个Streambuilder获取帖子、粉丝和关注者数量有关。 - Martin Seubert

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接