如何用Flutter+auto_route实现动态加载的类TikTok无限滚动信息流?
解决方案:自定义PageView结合AutoRoute实现无限滚动信息流
AutoTabsRouter.pageView的设计初衷是适配固定数量的标签页场景,无法直接支持动态懒加载的无限滚动需求。我们可以通过自定义PageView + 手动管理AutoRoute路由栈的方式实现TikTok式的垂直滑动信息流,同时保证路由状态同步。
核心思路
- 用
PageView.builder实现垂直方向的无限懒加载,动态构建帖子页面 - 监听PageView的滑动事件,每次切换到新页面时,手动更新AutoRoute的路由栈
- 同步路由状态与PageView索引,确保回退、跳转等操作的一致性
具体实现步骤
1. 定义路由结构
首先在AutoRoute的路由配置中,定义首页和单个帖子页面:
@MaterialAutoRouter( routes: [ AutoRoute(page: HomeFeedScreen, initial: true), // 信息流首页 AutoRoute(page: PostDetailScreen), // 单个帖子页面 ], ) class $AppRouter {}
2. 实现首页的无限滚动PageView
在首页中使用PageView.builder构建垂直滑动列表,同时处理路由同步和懒加载逻辑:
class HomeFeedScreen extends StatefulWidget { const HomeFeedScreen({super.key}); @override State<HomeFeedScreen> createState() => _HomeFeedScreenState(); } class _HomeFeedScreenState extends State<HomeFeedScreen> { final PageController _pageController = PageController(); final List<PostModel> _posts = []; int _currentPageIndex = 0; bool _isLoading = false; @override void initState() { super.initState(); _loadInitialPosts(); _pageController.addListener(_handlePageChange); } // 模拟加载初始帖子数据 Future<void> _loadInitialPosts() async { setState(() => _isLoading = true); final fetchedPosts = await PostApi.fetchPosts(offset: 0); setState(() { _posts.addAll(fetchedPosts); _isLoading = false; }); } // 加载更多帖子 Future<void> _loadMorePosts() async { if (_isLoading) return; setState(() => _isLoading = true); final fetchedPosts = await PostApi.fetchPosts(offset: _posts.length); setState(() { _posts.addAll(fetchedPosts); _isLoading = false; }); } // 处理页面切换,同步路由状态 void _handlePageChange() { final newIndex = _pageController.page?.round() ?? 0; if (newIndex == _currentPageIndex) return; setState(() => _currentPageIndex = newIndex); final router = AutoRouter.of(context); // 清理路由栈中多余的帖子页面,避免栈过深 while (router.stack.length > 1) { router.pop(); } // 推送当前帖子的路由 router.push(PostDetailRoute(postId: _posts[newIndex].id)); // 滑动到接近列表末尾时触发懒加载 if (newIndex >= _posts.length - 2) { _loadMorePosts(); } } // 监听路由变化,同步PageView索引(比如从帖子页回退时) @override void didChangeDependencies() { super.didChangeDependencies(); final currentRoute = context.topRoute; if (currentRoute is PostDetailRoute) { final postId = currentRoute.postId; final index = _posts.indexWhere((p) => p.id == postId); if (index != -1 && index != _currentPageIndex) { _pageController.jumpToPage(index); setState(() => _currentPageIndex = index); } } } @override void dispose() { _pageController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( body: PageView.builder( controller: _pageController, scrollDirection: Axis.vertical, itemCount: _posts.length + (_isLoading ? 1 : 0), itemBuilder: (context, index) { if (index == _posts.length) { // 加载更多的占位组件 return const Center(child: CircularProgressIndicator()); } final post = _posts[index]; return PostDetailScreen(post: post); }, ), ); } }
3. 实现单个帖子页面
class PostDetailScreen extends StatelessWidget { final PostModel post; const PostDetailScreen({super.key, required this.post}); @override Widget build(BuildContext context) { return Scaffold( body: Stack( children: [ // 帖子内容区域(视频/图片+文案等) Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text('帖子ID: ${post.id}'), const SizedBox(height: 16), Text(post.content), ], ), ), // 返回按钮 Positioned( top: 40, left: 16, child: IconButton( icon: const Icon(Icons.arrow_back), onPressed: () => AutoRouter.of(context).pop(), ), ), ], ), ); } }
关键注意点
- 路由栈优化:每次切换帖子时清理之前的帖子路由,避免路由栈无限增长,保证回退操作的合理性
- 状态同步:通过
didChangeDependencies监听路由变化,确保从其他页面返回时,PageView能自动跳转到对应的帖子位置 - 懒加载时机:提前2个页面触发加载,避免用户滑动到空白区域等待
- 数据状态管理:如果需要全局共享帖子数据,可结合Riverpod/Provider/Bloc等状态管理工具,简化页面间的数据传递
内容的提问来源于stack exchange,提问作者Martin Reindl
相关产品推荐
相关产品推荐

