Flutter应用开发:返回列表刷新并保持原始操作位置

背景

用户在频繁操作中,无论是进入详情页还是操作页,再次返回列表后,表格恢复到了初始状态。用户连贯操作不顺畅,用户提出要优化,有些操作需要刷新并保持原始操作位置,有些操作需要刷新重置。

需求梳理

用户诉求合情合理,操作其它APP(eg:掘金)也是如此。区分场景:

需要保持原始位置的操作,梳理来看是本身业务的操作,需要进行连续性操作的页面。连续在A数据上进行操作操作完A数据立即对下一个B数据进行操作

  • 进入详情
  • 进入编辑页
  • 进入揽收页
  • ...

刷新重置的操作,进入其它业务页面,连续性操作必须打断的场景。

  • 订单操作完从订单入口生成运费进行操作
  • ...

搜索方案

在 Flutter 应用中,实现从分页列表点进详情页后返回列表并保持之前点击的位置,可以通过以下几种方法实现:

方法一:使用 AutomaticKeepAliveClientMixin

AutomaticKeepAliveClientMixin 可以帮助你在返回页面时保持页面的状态。这在某些情况下非常有用,特别是当列表数据不需要重新获取时。

  1. 在详情页中使用 AutomaticKeepAliveClientMixin

  2. 确保在返回列表页时数据不会刷新

    如果列表数据在每次构建时都重新获取,那么即便使用了 AutomaticKeepAliveClientMixin,用户返回时看到的也可能还是初始状态。确保你的列表数据在不需要时不会重新获取。

方法二:保存滚动位置

另一种方法是在进入详情页前保存列表的滚动位置,并在返回时恢复该位置。

  1. 在列表页中保存滚动位置

    dart 复制代码
    	  @override
    	  void initState() {
    	    super.initState();
    	    _scrollController.addListener(() {
    	      setState(() {
    	        _scrollPosition = _scrollController.position.pixels;
    	      });
    	    });
    	  }
  2. 详情页保持不变

    详情页可以像之前一样实现,不需要特别处理。

方法三:使用状态管理(如 Provider、Riverpod 或 BLoC)

对于更复杂的应用,可以使用状态管理库来保存和恢复状态。例如,使用 Riverpod 或 Provider 来管理滚动位置的状态。

  1. 设置状态管理

    less 复制代码
    	import 'package:flutter/material.dart';
    	import 'package:flutter_riverpod/flutter_riverpod.dart';
    	final listScrollPositionProvider = StateProvider<double>((ref) => 0.0);
    
    	class ListPage extends ConsumerWidget {
    	  @override
    	  Widget build(BuildContext context, WidgetRef ref) {
    	    final scrollPosition = ref.watch(listScrollPositionProvider);
    	    final scrollController = ScrollController();
    	    useEffect(() {
    	      scrollController.addListener(() {
    	        ref.update(listScrollPositionProvider, scrollController.position.pixels);
    	      });
    	      return () {
    	        scrollController.dispose();
    	      };
    	    }, []);
    
    	    return Scaffold(
    	      appBar: AppBar(
    	        title: Text('List Page'),
    	      ),
    	      body: ListView.builder(
    	        controller: scrollController,
    	        itemCount: 100,
    	        itemBuilder: (context, index) {
    	          return ListTile(
    	            title: Text('Item $index'),
    	            onTap: () {
    	              Navigator.push(
    	                context,
    	                MaterialPageRoute(builder: (context) => DetailPage()),
    	              ).then((_) {
    	                scrollController.animateTo(
    	                  scrollPosition,
    	                  duration: Duration(milliseconds: 300),
    	                  curve: Curves.easeInOut,
    	                );
    	              });
    	            },
    	          );
    	        },
    	      ),
    	    );
    	  }
    	}
    
    	class DetailPage extends StatelessWidget {
    	  @override
    	  Widget build(BuildContext context) {
    	    return Scaffold(
    	      appBar: AppBar(
    	        title: Text('Detail Page'),
    	      ),
    	      body: Center(
    	        child: Text('Detail Page Content'),
    	      ),
    	    );
    	  }
    	}
    	
    	void main() {
    	  runApp(
    	    ProviderScope(
    	      child: MaterialApp(
    	        home: ListPage(),
    	      ),
    	    ),
    	  );
    	}

这些方法可以根据你的应用需求和复杂度选择使用。对于简单应用,方法一和方法二通常已经足够;对于更复杂的应用,使用状态管理库(如 Riverpod 或 Provider)可能更合适。

应用方案

根据搜索到的方案结合应用实际场景,发现搜索方案只能提供一种思路方向,不能完全落地到实际应用中。

  1. 采用页面缓存方式,虽然能保持原始位置,但是不能刷新。很多操作页处理完返回时数据展示需要更新。
js 复制代码
  // with AutomaticKeepAliveClientMixin

  @override
  bool get wantKeepAlive => true;
  1. 缓存当前页信息,返回时请求目标页数据,虽然能返回刷新并保持原始位置,但是对于列表插件来说,下拉操作只是刷新当前页,不能page-- 去补齐前面数据。

应用方案落地

缓存当前页信息,返回时计算一次性还原多少条数据。当用户操作到第2页第4条数据时,记录prevPage = 2,记录第4条的滚动条位置_scrollPosition = 89.0,返回列表时进行page = 1;limit = 2 * 10的数据请求,请求完成后滚动条定位到89.0

js 复制代码
// 根据之前分页情况,计算出一次性要还原多少条数据
limit = prevPage * 10
// 每次返回请求第一页
page = 1;


// 记录滚动条位置
_controller.addListener(() {
  setState(() {
    _scrollPosition = _controller.position.pixels;
  });
});

// 加载完数据后定位到记录的滚动条为止
if (_controller.hasClients) _controller.animateTo(_scrollPosition, duration: Duration(milliseconds: 100), curve: Curves.decelerate);

信心满满,一进行测试傻眼(⊙_⊙)?

第一次操作返回效果很完美继续操作就失效了,一次操作后page还原成1了,再次操作后返回数据请求变成了page = 1;limit = 1 * 10

继续尝试,缓存page不行,直接缓存请求到的条数list.length(不是总条数totalCount)。list.length存在可能不是10的倍数,因为操作最后一页时可能不满10条。返回列表时进行page = 1;limit = Utils.roundUpTen(list.length)的数据请求,向上补齐10的倍数。

再次测试,实现完成。

调用执行链

调用执行链包含如下六步走:

  1. 跳转详情页(用户行为)
  2. 跳转详情页,返回触发回调处理
  3. 根据之前分页情况,计算出一次性要还原多少条数据
  4. 请求之前加载到的所有数据
  5. 数据完成加载
  6. 滚动条定位到之前位置
js 复制代码
// 1.跳转详情页
NavigatorUtils.push(context, OrderRouter.orderInfoPage, params: {'orderNo': item.orderNo}).then((value) {
    // 2.跳转详情页,返回时回调处理
    doRefreshList(true);
}),

void doRefreshList(isRefresh) {
    if (isRefresh) {
      // 3. 根据之前分页情况,计算出一次性要还原多少条数据
      _page = 1;
      _getOrderList(limit: Utils.roundUpTen(_list.length));
    }
}

// 刷新重置
Future<void> _onRefresh() async {
    _page = 1;
    _scrollPosition = 0.0;
    _getList();
}

  _getList({int? limit, bool? isMore}) async {
    _isLoading = isMore != true;
    4. 请求数据
    OrderPageResp? resp = await G.req.order.fetchOrderList(page: _page, limit:limit ?? 10);
    if (mounted) {
      setState(() {
        var records = resp.orderList;
        this._totalPage = resp.totalPage!;

        if (_page == 1) {
         // 5. 数据加载完成
          this._list = records;
        } else {
          this._list.addAll(records);
        }

        // 6.滚动条定位到之前位置
        if (_controller.hasClients) _controller.animateTo(_scrollPosition, duration: Duration(milliseconds: 100), curve: Curves.decelerate);

        _isLoading = false;
      });
    }
  }

后记

当前方案存在一个性能问题,在操作数据很靠后时,返回再次进入时请求数据量会比较大(因为要还原之前所有数据)。不过上线后用户用的很顺畅,可见实际应用场景并没有程序员预想的那么极端。

相关推荐
sunly_7 小时前
Flutter:打包apk,详细图文介绍(一)
flutter
哥谭居民00017 小时前
primevue的<Menu>组件
flutter
LuiChun9 小时前
flutter在windows平台中运行报错
flutter
通域17 小时前
Mac 安装 Flutter 提示 A network error occurred while checking
flutter·macos
AdSet聚合广告21 小时前
解锁节日季应用广告变现潜力,提升应用广告收入
flutter·搜索引擎·uni-app·个人开发·节日
程序员老刘·1 天前
我在成都教人用Flutter写TDD(补充)——关于敏捷教练
flutter·敏捷开发·tdd
lichong9512 天前
【Flutter&Dart】构建布局(1/100)
android·flutter·api·postman·smartapi·postapi·foxapi
HH思️️无邪2 天前
Flutter-插件 scroll-to-index 实现 listView 滚动到指定索引位置
android·flutter·ios
Time@traveler2 天前
Flutter中添加全局防护水印的实现
flutter·flutter添加全局水印·flutter水印防伪·flutter水印·flutter飞书水印效果·flutter企微水印效果
lichong9512 天前
【Flutter&Dart】交互~创建一个有状态的widget &StatefulWidget(2/100)
flutter·yapi·交互·api·postman·dart·smartapi