Flutter---RefreshIndicator案例

效果图

核心架构

Dart 复制代码
上拉使用ScrollController

下拉使用RefreshIndicator

核心代码

Dart 复制代码
body: RefreshIndicator(

        onRefresh: _refreshData,//下拉刷新

        color: Colors.blue,//指示器箭头颜色

        backgroundColor: Colors.white,//指示器背景颜色

        displacement: 40.0,//触发下拉距离

        child: ListView.builder(
          controller: _scrollController,
          itemCount: items.length + 1, // 始终显示加载更多组件
          itemBuilder: (context, index) {
            if (index == items.length) {
              return _buildLoadMoreWidget();
            }
            return _buildListItem(items[index]);
          },
        ),
      ),

数据流程图

Dart 复制代码
┌─────────────────────────────────────────────────────────────────┐
│                         页面生命周期                            │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│ 1️⃣ initState() - 页面初始化                                   │
│    ├── super.initState()                                       │
│    ├── _loadData()  ← 加载第一页数据                           │
│    └── _scrollController.addListener()  ← 监听滚动             │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│ 2️⃣ build() - 构建UI                                           │
│    ├── Scaffold                                                │
│    ├── AppBar                                                   │
│    └── RefreshIndicator                                         │
│        └── ListView.builder                                     │
│            ├── itemCount: items.length + 1                      │
│            └── itemBuilder:                                     │
│                ├── 最后一项 → _buildLoadMoreWidget()            │
│                └── 其他项 → _buildListItem()                    │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│ 3️⃣ 等待用户交互                                                │
│    ├── 下拉刷新 → _refreshData()                               │
│    ├── 上拉加载 → _loadMoreData()                              │
│    └── 点击列表项 → onTap()                                    │
└─────────────────────────────────────────────────────────────────┘

数据流

1.首次进入页面

Dart 复制代码
1. DemoPage 构造函数
   ↓
2. createState() → _DemoPageState()
   ↓
3. _DemoPageState 构造函数 (初始化变量)
   • items = []
   • _page = 1
   • _isLoadingMore = false
   • _hasMore = true
   ↓
4. initState()
   ├── super.initState()
   ├── _loadData()  ← 调用加载方法
   └── _scrollController.addListener()
   ↓
5. _loadData() 执行
   ├── await Future.delayed(1秒)  ← 模拟网络请求
   ├── 生成20条数据
   └── setState() {
         items = newData  ← 更新数据
       }
   ↓
6. build() 执行 (由setState触发)
   ├── 构建RefreshIndicator
   └── ListView.builder
       ├── itemCount = 20 + 1 = 21
       ├── 索引0-19: _buildListItem()
       └── 索引20: _buildLoadMoreWidget()
   ↓
7. 页面显示完成

2.上拉加载更多

Dart 复制代码
1. 用户滚动到列表底部
   ↓
2. _scrollController.addListener() 触发
   ↓
3. 判断条件: 滚动位置 ≥ 最大滚动位置 - 100
   ↓
4. _loadMoreData() 调用
   ├── 检查: _isLoadingMore? _hasMore?
   ├── _isLoadingMore = true
   ├── setState(() {})  ← 显示加载状态
   ├── _page++  (1→2)
   ├── await Future.delayed(1秒)
   ├── 生成新数据 (第2页)
   ├── setState() {
   │     items.addAll(newData)  ← 追加数据
   │   }
   └── finally: _isLoadingMore = false
   ↓
5. build() 重新执行
   ├── itemCount = 40 + 1 = 41
   ├── 索引0-39: 显示所有数据
   └── 索引40: 显示"上拉加载更多" (第2次加载后)
   ↓
6. 用户再次滚动到底部 (第3页)
   ↓
7. _loadMoreData() 再次执行
   ├── _page++  (2→3)
   ├── 判断 _page >= 3 → _hasMore = false
   └── setState(() {})  ← 更新状态
   ↓
8. build() 重新执行
   ├── itemCount = 40 + 1 = 41
   ├── 索引40: _buildLoadMoreWidget()
   └── 显示"--- 已加载全部 ---" (不再加载)

3.下拉刷新

Dart 复制代码
1. 用户在列表顶部下拉
   ↓
2. RefreshIndicator 检测到手势
   ↓
3. 下拉距离 ≥ displacement (40.0)
   ↓
4. onRefresh: _refreshData() 调用
   ├── _page = 1  ← 重置页码
   ├── _hasMore = true  ← 重置标志
   └── await _loadData(isRefresh: true)
   ↓
5. _loadData(isRefresh: true) 执行
   ├── await Future.delayed(1秒)
   ├── 生成20条新数据 (第1页)
   └── setState() {
         items = newData  ← 替换所有数据
       }
   ↓
6. build() 重新执行
   ├── itemCount = 20 + 1 = 21
   └── 显示刷新后的数据
   ↓
7. RefreshIndicator 自动隐藏

代码实例

Dart 复制代码
import 'package:flutter/material.dart';

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

  @override
  State<DemoPage> createState() => _DemoPageState();
}

class _DemoPageState extends State<DemoPage> {

  List<String> items = []; //存储列表数据

  int _page = 1; //当前页码

  bool _isLoadingMore = false; //是否正在加载更多

  bool _hasMore = true; //是否还有更多数据

  final int _pageSize = 20; //每页数据量

  //滚动控制器,实现上拉加载
  final ScrollController _scrollController = ScrollController();

  @override
  void initState() {
    super.initState();

    _loadData();//加载第一页数据

    //添加滚动监听
    _scrollController.addListener(() {
      //当滚动到距离底部100像素时触发更多
      if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 100) {
        _loadMoreData(); //加载更多数据
      }
    });
  }

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


  //下拉刷新
  Future<void> _refreshData() async {
    print("🔄 下拉刷新");
    _page = 1; //重置页码
    _hasMore = true; //重置标志位
    await _loadData(isRefresh: true);//重新加载数据
  }


  //加载数据
  Future<void> _loadData({bool isRefresh = false}) async {
    try {
      await Future.delayed(Duration(seconds: 1));

      //生成模拟数据
      List<String> newData = List.generate(
          _pageSize,
              (i) => "Item ${(_page - 1) * _pageSize + i + 1}"
      );

      //更新UI
      setState(() {
        if (isRefresh) {
          items = newData; //刷新替换所有数据
        } else {
          items.addAll(newData);
        }
      });

    } catch (e) {
      print("加载数据失败: $e");
    }
  }


  //加载更多数据
  Future<void> _loadMoreData() async {

    //防止重复加载
    if (_isLoadingMore || !_hasMore) return;

    //设置加载状态
    _isLoadingMore = true;
    setState(() {}); // 立即显示加载状态

    _page++; //页码加1

    try {
      await Future.delayed(Duration(seconds: 1));

      // 模拟:第3页后没有更多数据
      if (_page >= 3) {
        _hasMore = false;
        setState(() {});
      } else {
        //生成数据
        List<String> newData = List.generate(
            _pageSize,
                (i) => "Item ${(_page - 1) * _pageSize + i + 1}"
        );

        //添加数据
        setState(() {
          items.addAll(newData);
        });
      }

    } catch (e) {
      print("加载更多失败: $e");
      _page--;
    } finally {
      _isLoadingMore = false;
      setState(() {});
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        leading: IconButton(
          onPressed: () => Navigator.pop(context),
          icon: Icon(Icons.arrow_back_ios),
        ),
        title: Column(
          children: [
            Text("RefreshIndicator", style: TextStyle(color: Colors.black, fontSize: 18)),
            Text("上拉加载,下拉刷新", style: TextStyle(color: Colors.black.withOpacity(0.7), fontSize: 12)),
          ],
        ),
        centerTitle: true,
        backgroundColor: Colors.blue,
      ),
      body: RefreshIndicator(

        onRefresh: _refreshData,//下拉刷新

        color: Colors.blue,//指示器箭头颜色

        backgroundColor: Colors.white,//指示器背景颜色

        displacement: 40.0,//触发下拉距离

        child: ListView.builder(
          controller: _scrollController,
          itemCount: items.length + 1, // 始终显示加载更多组件
          itemBuilder: (context, index) {
            if (index == items.length) {
              return _buildLoadMoreWidget();
            }
            return _buildListItem(items[index]);
          },
        ),
      ),
    );
  }


  //列表子项
  Widget _buildListItem(String text) {
    return Card(
      margin: EdgeInsets.symmetric(horizontal: 16, vertical: 6),
      elevation: 2,
      child: ListTile(
        leading: CircleAvatar(
          backgroundColor: Colors.blue[100],
          child: Text(
            text.split(' ').last,
            style: TextStyle(color: Colors.blue[700]),
          ),
        ),
        //标题
        title: Text(
          text,
          style: TextStyle(fontWeight: FontWeight.w500),
        ),
        //副标题
        subtitle: Text("这是第${text.split(' ').last}项"),
        //尾部按钮
        trailing: Icon(Icons.arrow_forward_ios, size: 16, color: Colors.grey),
        //点击事件
        onTap: () {
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(content: Text("点击了 $text")),
          );
        },
      ),
    );
  }

  // 加载更多组件
  Widget _buildLoadMoreWidget() {
    if (_isLoadingMore) {
      return Container(
        padding: EdgeInsets.symmetric(vertical: 20),
        child: Center(
          child: Column(
            children: [
              SizedBox(
                width: 24,
                height: 24,
                child: CircularProgressIndicator(
                  strokeWidth: 2,
                  color: Colors.blue,
                ),
              ),
              SizedBox(height: 8),
              Text(
                "加载中...",
                style: TextStyle(color: Colors.grey[600], fontSize: 14),
              ),
            ],
          ),
        ),
      );
    } else if (!_hasMore) {
      return Container(
        padding: EdgeInsets.symmetric(vertical: 20),
        child: Center(
          child: Text(
            "--- 已加载全部 ---",
            style: TextStyle(color: Colors.grey[400], fontSize: 14),
          ),
        ),
      );
    } else {
      // 显示"上拉加载更多"提示
      return Container(
        padding: EdgeInsets.symmetric(vertical: 20),
        child: Center(
          child: Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text(
                "上拉加载更多",
                style: TextStyle(color: Colors.grey[500], fontSize: 13),
              ),
              SizedBox(width: 8),
              Icon(
                Icons.keyboard_arrow_up,
                size: 20,
                color: Colors.grey[500],
              ),
            ],
          ),
        ),
      );
    }
  }
}
相关推荐
西西学代码2 小时前
Flutter---RefreshIndicator
flutter
末代iOS程序员华仔2 小时前
从 Figma 到上架:使用 Codex 与 MCP 全流程实现 AI 聊天 App
flutter·ios·swift
西西学代码3 小时前
Flutter---StreamBuilder案例
javascript·windows·flutter
子非鱼94275 小时前
03-Flutter 鸿蒙实战 03:登录注册与本地会话持久化
网络·flutter·华为·harmonyos
AM艾玛5 小时前
Flutter 环境搭建实录
vscode·flutter
子非鱼94276 小时前
01-Flutter 鸿蒙实战 01:会说话的相册项目架构拆解
flutter·华为·harmonyos
子非鱼94276 小时前
02-Flutter 鸿蒙实战 02:Web 移植到 Flutter HarmonyOS 的页面结构
学习·flutter·华为·harmonyos
dragon7251 天前
一个 Flutter Loading 关不掉的诡异 Bug:从 showDialog 的竞态说起
flutter
一枚菜鸟_1 天前
在 Flutter 项目里装 Skills:从 0 搭建 AI 辅助开发体系
flutter·ai编程