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

如何在Flutter中使用PageView实现双向分页?

周数据PageView界面截图

解决PageView反向分页加载问题

核心问题分析

反向分页加载失败大多是因为:

  • 往列表开头插入新数据后,没有同步调整PageController的page值,导致页面位置偏移
  • 未正确监听反向滚动的触发时机(比如滚动到第一个页面时触发加载)

实现方案

以下是完整的可运行示例,基于Flutter的PageView实现周数据的双向分页加载:

1. 定义数据模型和状态管理

import 'package:flutter/material.dart';
import 'package:intl/intl.dart';

// 周数据模型
class WeekData {
  final DateTime startDate;
  final List<String> days;

  WeekData(this.startDate)
      : days = List.generate(7, (index) {
          final date = startDate.add(Duration(days: index));
          return DateFormat('MM-dd').format(date);
        });

  // 获取上一周的起始日期
  WeekData getPreviousWeek() {
    return WeekData(startDate.subtract(const Duration(days: 7)));
  }

  // 获取下一周的起始日期
  WeekData getNextWeek() {
    return WeekData(startDate.add(const Duration(days: 7)));
  }
}

2. 实现带双向分页的PageView

class WeekPageView extends StatefulWidget {
  const WeekPageView({super.key});

  @override
  State<WeekPageView> createState() => _WeekPageViewState();
}

class _WeekPageViewState extends State<WeekPageView> {
  late PageController _pageController;
  List<WeekData> _weekList = [];
  bool _isLoading = false;

  @override
  void initState() {
    super.initState();
    // 初始化当前周数据
    final currentWeek = _getCurrentWeek();
    _weekList = [currentWeek];
    // 初始page值设为0,对应第一个页面
    _pageController = PageController(initialPage: 0);
    // 添加滚动监听
    _pageController.addListener(_onScroll);
  }

  // 获取当前周的起始日期(周一)
  WeekData _getCurrentWeek() {
    final now = DateTime.now();
    final monday = now.subtract(Duration(days: now.weekday - 1));
    return WeekData(monday);
  }

  void _onScroll() {
    // 滚动到第一个页面时,加载上一周数据
    if (_pageController.page! <= 0.5 && !_isLoading) {
      _loadPreviousWeek();
    }
    // 滚动到最后一个页面时,加载下一周数据
    else if (_pageController.page! >= _weekList.length - 1.5 && !_isLoading) {
      _loadNextWeek();
    }
  }

  Future<void> _loadPreviousWeek() async {
    setState(() => _isLoading = true);
    // 模拟接口请求延迟
    await Future.delayed(const Duration(milliseconds: 500));
    final previousWeek = _weekList.first.getPreviousWeek();
    setState(() {
      // 在列表开头插入新数据
      _weekList.insert(0, previousWeek);
      // 关键:插入后page值+1,保持当前显示的页面不变
      _pageController.jumpToPage(_pageController.page!.toInt() + 1);
    });
    setState(() => _isLoading = false);
  }

  Future<void> _loadNextWeek() async {
    setState(() => _isLoading = true);
    await Future.delayed(const Duration(milliseconds: 500));
    final nextWeek = _weekList.last.getNextWeek();
    setState(() {
      _weekList.add(nextWeek);
    });
    setState(() => _isLoading = false);
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('周数据分页')),
      body: Stack(
        children: [
          PageView.builder(
            controller: _pageController,
            itemCount: _weekList.length,
            itemBuilder: (context, index) {
              final week = _weekList[index];
              return Container(
                padding: const EdgeInsets.all(16),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      '${DateFormat('yyyy-MM-dd').format(week.startDate)} 至 ${DateFormat('yyyy-MM-dd').format(week.startDate.add(const Duration(days: 6)))}',
                      style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
                    ),
                    const SizedBox(height: 16),
                    Wrap(
                      spacing: 8,
                      children: week.days.map((day) {
                        return Chip(label: Text(day));
                      }).toList(),
                    ),
                  ],
                ),
              );
            },
          ),
          if (_isLoading)
            const Center(child: CircularProgressIndicator()),
        ],
      ),
    );
  }
}

关键注意点

  • 反向加载后的page调整:往列表开头插入新数据后,必须调用_pageController.jumpToPage(_pageController.page!.toInt() + 1),否则原来的页面会变成第二个,显示内容会跳变。
  • 滚动触发时机:判断page值时加0.5的偏移,避免因为滚动中的微小波动重复触发加载。
  • 加载状态控制:用_isLoading防止同时发起多个请求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 18:35:14