You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter 保存恢复ListView最后滚动位置功能失效如何解决

问题根因

你当前的实现存在3个核心问题导致滚动位置无法跨重启恢复:

  1. 偏移量仅存储在内存的DoubleHolder对象中,应用重启后内存数据会被完全清空,你提到使用了SharedPreferences但现有代码没有对应的持久化写入、启动读取逻辑
  2. initState阶段初始化ScrollController时,列表数据还未加载完成、ListView未完成渲染,此时设置的initialScrollOffset会因为列表可滚动高度不足被系统忽略,默认回到顶部
  3. 滚动监听仅更新了内存中的偏移值,没有同步写入持久化存储,退出应用后数据就丢失了

修复方案

你可以按照以下步骤调整代码:

  • 封装SharedPreferences读写滚动偏移的工具方法,处理异步操作
  • 等待列表数据加载完成、页面第一帧渲染结束后,再主动调用滚动方法恢复位置,不要依赖initialScrollOffset
  • 滚动偏移更新时同步写入SharedPreferences持久化存储
  • 页面销毁时释放ScrollController避免内存泄漏

完整修改后代码

import 'package:shared_preferences/shared_preferences.dart';

class ListViewScreen3 extends StatefulWidget {
  String itemHolder;
  ListViewScreen3({Key key, @required this.itemHolder}) : super(key: key);

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

class ListViewScreenState3 extends State<ListViewScreen3>  {
  ScrollController _controller;
  // 存储滚动偏移量的自定义key
  static const String _scrollOffsetKey = "list_scroll_offset";
  List surahcountlist = []; // 替换为你自己的列表数据源定义

  @override
  void initState() {
    super.initState();
    _controller = ScrollController();
    // 先加载列表数据
    getAllSavedData().then((_) {
      // 数据加载完成后,等页面第一帧渲染结束再恢复滚动位置
      WidgetsBinding.instance.addPostFrameCallback((_) async {
        final prefs = await SharedPreferences.getInstance();
        final savedOffset = prefs.getDouble(_scrollOffsetKey) ?? 0.0;
        // 校验偏移量合法性,避免超出列表可滚动范围
        if (_controller.hasClients && savedOffset <= _controller.position.maxScrollExtent) {
          _controller.jumpTo(savedOffset);
        }
      });
    });
  }

  // 你原有加载列表数据的方法,确保是异步返回
  Future<void> getAllSavedData() async {
    // 原有加载数据的逻辑,给surahcountlist赋值
  }

  // 持久化存储滚动偏移
  Future<void> _saveScrollOffset(double offset) async {
    final prefs = await SharedPreferences.getInstance();
    await prefs.setDouble(_scrollOffsetKey, offset);
  }

  @override
  void dispose() {
    _controller.dispose(); // 释放控制器避免内存泄漏
    super.dispose();
  }

  @override
  Widget build(BuildContext context)  {
    final makeBody = Container(
      decoration: BoxDecoration(
        color: light_mode ? const Color(0xFFFFFFFF) : const Color(0xFF6D6D6D)
      ),
      child: returnListView()
    );
    // 补充你原有外层页面结构,比如Scaffold
    return Scaffold(body: makeBody);
  }

  Widget returnListView() {
    return Column(
      textDirection: TextDirection.rtl,
      mainAxisAlignment: MainAxisAlignment.start,
      crossAxisAlignment: CrossAxisAlignment.stretch,
      children: [
        Expanded(
          child: SizedBox(
            height: 200.0,
            child: NotificationListener<ScrollNotification>(
              child: ListView.builder(
                controller: _controller,
                itemCount: (surahcountlist.length/2).toInt(),
                itemBuilder: (context, index) {
                  return GestureDetector(
                    child: Card(
                      color: light_mode ? Colors.white : const Color(0xFF6D6D6D),
                      child: Column(
                        textDirection: TextDirection.rtl,
                        mainAxisAlignment: MainAxisAlignment.start,
                        crossAxisAlignment: CrossAxisAlignment.stretch,
                        children: <Widget>[
                          showBSMLSV(index),
                          Wrap(
                            direction: Axis.horizontal,
                            alignment: WrapAlignment.start,
                            runAlignment: WrapAlignment.center,
                            textDirection: TextDirection.rtl,
                            spacing: 2.0,
                            runSpacing: 5.0,
                            children: makeSurahListview(
                              surahcountlist[index].surah_no,
                              surahcountlist[index].ayah_no,
                              surahcountlist[index].count,
                              index
                            )
                          ),
                        ]
                      )
                    )
                  );
                }
              ),
              onNotification: (notification) {
                // 仅在滚动位置更新时存储,减少不必要的IO操作
                if (notification is ScrollUpdateNotification) {
                  _saveScrollOffset(notification.metrics.pixels);
                }
                return true;
              }
            )
          )
        )
      ],
    );
  }
}

额外优化建议

如果你的列表item高度不固定,或者数据量极大,建议改为存储可见第一个item的下标,而非滚动像素偏移,避免item高度变化导致恢复位置不准;如果需要滚动恢复动画,可以把_controller.jumpTo替换为_controller.animateTo自行配置动画参数。

内容的提问来源于stack exchange,提问作者Noor

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.04 17:57:04