繁体   English   中英

Flutter ListView 在每次 setState() 调用时跳转到顶部

[英]Flutter ListView jumps to top on each setState() call

我有一个客户列表视图,用户可以在其中选中/取消选中每个客户以将他们标记为“收藏夹”。 但是,每次点击 ListItem 时,ListView 都会跳回到屏幕顶部。

我如何防止这种情况发生? 我只希望 Checkbox 刷新,而不是整个屏幕,并防止屏幕每次都跳到顶部。

@override
Widget build(BuildContext context) {
  customers = getAllCustomers();

  return Scaffold(
     appBar: _getAppBar(),
     body: customers != null
          ? FutureBuilder(
              future: getFavouriteCustomers(), // contains IDs of favourite customers
              builder: (context, snapshot) {
                if (snapshot.connectionState == ConnectionState.done) {
                  if (snapshot.hasData) {
                    List<String> favouriteCustomersList = snapshot.data;

                    return ListView.builder(
                        itemCount: customers?.length ?? 0,
                        itemBuilder: (BuildContext context, int index) {
                          customer c = customers?.elementAt(index);

                          if (favouriteCustomersList.contains(c.id)) {
                            c.isSelected = true;
                          }

                          return ListTile(
                            title: Text(c.name),
                            trailing: Checkbox(
                                value: c.isFavourite,
                                onChanged: (newValue) {}),
                            onTap: () {
                              if (c.isSelected) {
                                setState(() {
                                  c.setFavourite(false);
                                });
                              } else {
                                setState(() {
                                  c.setFavourite(true);
                                }
                              }
                            },
                          );
                        });
                  }
                } else {
                  return CircularProgressIndicator();
                }
              })
          : Center(
              child: CircularProgressIndicator(),
            );
);

}

  1. 确保您在 StatefulWidget 中执行此操作。

  2. 在状态类(不是小部件类)中创建一个ScrollController作为字段:

  _controller = ScrollController(keepScrollOffset: true);

这样,控制器将保留在状态中,而不是在重建时重新创建。

  1. 将它传递给构建器作为
  controller: _controller,
  1. 记得在状态销毁时处置控制器:
  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
  1. 正如 Benno 所提到的,确保在小部件树上一直没有小部件在重建时重新创建键:如何在调用 setState 后防止 Flutter 应用程序滚动到顶部?

不确定您是否已经找到答案,但我通常通过使用两个 StatefulWidgets 拆分构建方法来解决问题。 这里发生的事情是,每次调用setState() ,都会重建整个小部件,因为整个 State 类的状态都发生了变化。 这意味着 build 方法被调用,强制重新创建脚手架及其中的所有小部件树。 由于小部件已重建,它将假定它的原始位置偏移量为 0。我建议创建另一个有状态小部件来返回ListTile对象。 这将破坏构建方法,并且在每个ListTile上执行的操作是在它自己的build方法中执行的。 这不会强制您重新创建 Scaffold。

我没有您的完整代码,因此无法建议完整的工作解决方案,但以下内容可能会有所帮助。


class SomeStatefulWidget extends StatefulWidget {

  @override
  _SomeStatefulWidgetState createState() => _SomeStatefulWidgetState();
}

class SomeStatefulWidgetState extends State<SomeStatefulWidget> {
@override
Widget build(BuildContext context) {
  customers = getAllCustomers();

  return Scaffold(
     appBar: _getAppBar(),
     body: customers != null
          ? FutureBuilder(
              future: getFavouriteCustomers(), // contains IDs of favourite customers
              builder: (context, snapshot) {
                if (snapshot.connectionState == ConnectionState.done) {
                  if (snapshot.hasData) {
                    List<String> favouriteCustomersList = snapshot.data;

                    return ListView.builder(
                        itemCount: customers?.length ?? 0,
                        itemBuilder: (BuildContext context, int index) {
                          customer c = customers?.elementAt(index);

                          if (favouriteCustomersList.contains(c.id)) {
                            c.isSelected = true;
                          }

                          return MyListTile(c);

                        });
                  }
                } else {
                  return CircularProgressIndicator();
                }
              })
          : Center(
              child: CircularProgressIndicator(),
            );
);
}


class MyListTile extends StatefulWidget {

  customer c;

  @override
  _MyListTileState createState(this.c) => _MyListTileState();

}

class _MyListTileState extends State<MyListTile> {

  @override
  Widget build(BuildContext context) {

    var c = widget.c;

    return ListTile(
      title: Text(c.name),
      trailing: Checkbox(
          value: c.isFavourite,
          onChanged: (newValue) {}),
      onTap: () {
        if (c.isSelected) {
          setState(() {
            c.setFavourite(false);
          });
        } else {
          setState(() {
            c.setFavourite(true);
          }
        }
      },
    );
  }
}

通过将小部件一分为二, setState()现在将仅通过调用MyListTileState类的build方法来构建MyListTile对象。 您有效地将ListTile包装到您自己的名为MyListTile的有状态类中。

我希望这对你有用。 如前所述,我没有你的完整代码,因此制作了一些包装类来使这个例子清晰。 此外,我还没有编译这段代码并在此处输入所有内容,因此可能存在一些编译时错误。 但这解释了跳到顶部的概念、原因和解决方案。

您可以尝试将 listTile 包装在 StreamBuilder 中,而不是使用 setState() 来更新 UI,并为其提供每次点击复选框时都会发送数据的流。 一旦 StreamBuilder 从流中接收到数据,它将只更新单元格,而不是整个列表,最终您将能够避免列表的顶部滚动。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM