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;
      });
    }
  }

后记

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

相关推荐
行者9638 分钟前
Flutter与OpenHarmony深度集成:数据导出组件的实战优化与性能提升
flutter·harmonyos·鸿蒙
小雨下雨的雨40 分钟前
Flutter 框架跨平台鸿蒙开发 —— Row & Column 布局之轴线控制艺术
flutter·华为·交互·harmonyos·鸿蒙系统
小雨下雨的雨1 小时前
Flutter 框架跨平台鸿蒙开发 —— Center 控件之完美居中之道
flutter·ui·华为·harmonyos·鸿蒙
小雨下雨的雨2 小时前
Flutter 框架跨平台鸿蒙开发 —— Icon 控件之图标交互美学
flutter·华为·交互·harmonyos·鸿蒙系统
小雨下雨的雨2 小时前
Flutter 框架跨平台鸿蒙开发 —— Placeholder 控件之布局雏形美学
flutter·ui·华为·harmonyos·鸿蒙系统
行者963 小时前
OpenHarmony Flutter弹出菜单组件深度实践:从基础到高级的完整指南
flutter·harmonyos·鸿蒙
前端不太难3 小时前
Flutter / RN / iOS,在长期维护下的性能差异本质
flutter·ios
小雨下雨的雨3 小时前
Flutter 框架跨平台鸿蒙开发 —— Padding 控件之空间呼吸艺术
flutter·ui·华为·harmonyos·鸿蒙系统
行者964 小时前
Flutter到OpenHarmony:横竖屏自适应布局深度实践
flutter·harmonyos·鸿蒙
小雨下雨的雨4 小时前
Flutter 框架跨平台鸿蒙开发 —— Align 控件之精准定位美学
flutter·ui·华为·harmonyos·鸿蒙