📌  相关文章
📜  禁用列表视图颤动中的滚动 - Dart (1)

📅  最后修改于: 2023-12-03 14:56:32.660000             🧑  作者: Mango

禁用列表视图颤动中的滚动 - Dart

在开发Flutter应用程序时,在列表视图中滚动时会出现颤动的情况,这是因为Flutter框架在每次滚动时都要重新构建列表中的所有控件,并将它们放置在新的位置。这个过程会导致屏幕上的所有控件都需要重新绘制和布局。为了解决这个问题,我们需要使用一种称为“禁用列表视图颤动中的滚动”的方法,这种方法可以使Flutter框架能够在滚动列表时更加平滑。

解决方案
禁用列表项的默认动画效果

当我们在ListView中滚动时,每个列表项都会默认显示一个动画效果。为了消除这种抖动,我们可以通过在列表项上设置removeClippedSubviews属性为false来禁用此默认动画效果。

ListView.builder(
  itemCount: itemCount,
  itemBuilder: (BuildContext context, int index) {
    return Container(
      height: 50, // 列表项高度
      child: Text(data[index]),
      clipBehavior: Clip.antiAlias, // 设置抗锯齿
      removeClippedSubviews: false, // 禁用默认动画效果
    );
  },
)
使用AutomaticKeepAliveClientMixin保持列表项状态

使用AutomaticKeepAliveClientMixin可以帮助我们在滚动列表时保持列表项状态不变。这个Mixin可以帮助我们减少Flutter框架从新布局和重绘的次数。

我们需要在需要保持状态的列表项中添加此Mixin,并重写wantKeepAlive方法来返回true来告知Flutter框架需要保持状态。

class CustomListItem extends StatefulWidget {
  const CustomListItem({
    Key? key,
    required this.text,
    required this.index,
  }) : super(key: key);

  final String text;
  final int index;

  @override
  State<StatefulWidget> createState() => _CustomListItemState();
}

class _CustomListItemState extends State<CustomListItem>
    with AutomaticKeepAliveClientMixin {
  @override
  bool get wantKeepAlive => true;

  @override
  Widget build(BuildContext context) {
    super.build(context);
    return Container(
      height: 50,
      child: Text(widget.text),
    );
  }
}
预构建下一页

在滚动列表时,可能需要等待一段时间才能加载下一页数据并更新UI。为了解决这个问题,我们可以使用FutureBuilderPageStorage来预构建下一页,以便在需要时立即切换并使更新更加平滑。

我们可以在当前页面上使用一个FutureBuilder来预构建下一页,并将其存储在PageStorage中以便在需要时立即将其展示出来。

class NextPageData {
  // 下一页数据
}

class CustomListView extends StatefulWidget {
  @override
  _CustomListViewState createState() => _CustomListViewState();
}

class _CustomListViewState extends State<CustomListView>
    with AutomaticKeepAliveClientMixin {
  List<String> _data = [];
  bool _isLoading = false;
  bool _hasMoreItems = true;
  int _currentPage = 0;

  final PageStorageBucket _bucket = PageStorageBucket();
  final ScrollController _scrollController = ScrollController();

  Future<NextPageData> _getNextPageData() async {
    // 加载下一页数据
    _currentPage++;
    await Future.delayed(const Duration(seconds: 2));
    return NextPageData();
  }

  @override
  bool get wantKeepAlive => true;

  @override
  void initState() {
    _loadData();
    _scrollController.addListener(() {
      if (_scrollController.position.pixels ==
              _scrollController.position.maxScrollExtent &&
          _hasMoreItems &&
          !_isLoading) {
        _loadData();
      }
    });
    super.initState();
  }

  @override
  void dispose() {
    _scrollController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    super.build(context);
    return Scaffold(
      body: FutureBuilder(
        future: _getNextPageData(),
        builder: (context, AsyncSnapshot<NextPageData> snapshot) {
          if (snapshot.hasData) {
            final Widget listView = ListView.builder(
              addAutomaticKeepAlives: true,
              controller: _scrollController,
              itemCount: _data.length + 1,
              itemBuilder: (BuildContext context, int index) {
                if (index == _data.length) {
                  return snapshot.data!;
                } else {
                  return CustomListItem(
                    key: ValueKey(_data[index]),
                    text: _data[index],
                    index: index,
                  );
                }
              },
            );
            return PageStorage(
              child: listView,
              bucket: _bucket,
            );
          } else {
            return const Center(child: CircularProgressIndicator());
          }
        },
      ),
    );
  }

  void _loadData() async {
    setState(() => _isLoading = true);
    // 加载数据
    await Future.delayed(const Duration(seconds: 2));
    _data.addAll(List<String>.generate(20, (index) {
      return 'Item $index';
    }));
    setState(() => _isLoading = false);
    if (_currentPage == 5) {
      setState(() => _hasMoreItems = false);
    }
  }
}
结论

禁用列表视图颤动中的滚动可以使我们的Flutter应用程序更加流畅。我们可以通过禁用列表项的默认动画效果、使用AutomaticKeepAliveClientMixin保持列表项状态和预构建下一页来实现这些目标。这些方法通过减少Flutter框架的布局和绘制次数,使列表中的滚动更加平滑和高效。