• Flutter笔记:滚动之-无限滚动与动态加载的实现


    Flutter笔记
    无限滚动与动态加载的实现

    作者李俊才 (jcLee95)https://blog.csdn.net/qq_28550263
    邮箱 :291148484@163.com
    本文地址https://blog.csdn.net/qq_28550263/article/details/133342307


    本文还有另外一个版本,基于GetX简单状态管理状态。地址为:https://jclee95.blog.csdn.net/article/details/133365040


    1. 无限滚动列表

    Flutter 中,实现一个无尽滚动列表通常涉及使用 ListView、ListView.builder 或 ListView.separated 组件,并结合数据源和滚动控制器。这使得您可以加载和显示大量数据,只有在需要时才会动态加载更多数据,以实现无尽滚动效果。

    2. 模拟滚动列表的基本实现举例(ListView.builder)

    2.1 实现思路与步骤介绍

    以下是实现 Flutter 无尽滚动列表的一般步骤:

    准备数据源

    首先需要有一个数据源。比如一个列表或一个数据库查询结果,或者是网络请求的数据,以供列表渲染。通常,这些数据应该是 按需加载 的,而不是一次性加载所有数据。

    创建滚动控制器

    通过 ScrollController 创建一个滚动控制器,以便监听列表的滚动事件。这将帮助您确定何时加载更多数据。

    构建列表视图

    使用 ListView.builder 构建一个列表视图,该构造函数会创建一个只渲染可见项的列表。通过指定 itemBuilder 参数来定义如何渲染每个列表项。

    设置滚动监听

    将滚动控制器添加到列表视图,并使用 addListener 监听滚动事件。当用户滚动列表时,可以在适当的时候触发加载更多数据的操作。

    加载更多数据

    在需要加载更多数据时,您可以调用数据源的方法或请求数据。这可以是从网络获取数据、从本地数据库查询数据或其他方式。一旦数据准备好,将其添加到数据源中,然后通知列表视图重新构建。

    更新列表视图

    当有新数据可用时,调用 setState 方法以通知 Flutter 重新构建列表视图。这将导致列表视图加载和显示新数据。

    2.2 一个简单例子

    依据 2.1 小节的步骤,实现一个模拟无线滚动的例子如下:

    import 'package:flutter/material.dart';
    
    void main() {
      runApp(const MyApp());
    }
    
    class MyApp extends StatelessWidget {
      const MyApp({Key? key}) : super(key: key);
    
      
      Widget build(BuildContext context) {
        return const MaterialApp(
          home: InfiniteScrollList(),
        );
      }
    }
    
    /// 一个带有无尽滚动列表的 StatefulWidget。
    class InfiniteScrollList extends StatefulWidget {
      const InfiniteScrollList({Key? key}) : super(key: key);
    
      
      State<InfiniteScrollList> createState() => _InfiniteScrollListState();
    }
    
    /// [InfiniteScrollList] 的状态类,包含滚动逻辑和数据管理。
    class _InfiniteScrollListState extends State<InfiniteScrollList> {
      List<int> items = List.generate(20, (index) => index); // 初始数据
      final ScrollController _scrollController = ScrollController();
      bool isLoading = false;
    
      
      void initState() {
        super.initState();
        _scrollController.addListener(_loadMore);
      }
    
      /// 处理滚动事件以加载更多数据。
      void _loadMore() {
        if (_scrollController.position.pixels ==
                _scrollController.position.maxScrollExtent &&
            !isLoading) {
          // 模拟加载更多数据
          setState(() {
            isLoading = true;
          });
    
          // 模拟加载数据的延迟
          Future.delayed(const Duration(seconds: 2), () {
            setState(() {
              // 添加新数据到现有列表中
              items.addAll(List.generate(10, (index) => index + items.length));
              isLoading = false;
            });
          });
        }
      }
    
      
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: const Text('无尽滚动列表'),
          ),
          body: ListView.builder(
            controller: _scrollController,
            itemCount: items.length + (isLoading ? 1 : 0),
            itemBuilder: (context, index) {
              if (index < items.length) {
                return ListTile(
                  title: Text('Item ${items[index]}'),
                );
              } else {
                // 显示加载指示器
                return const Padding(
                  padding: EdgeInsets.all(16.0),
                  child: Center(child: CircularProgressIndicator()),
                );
              }
            },
          ),
        );
      }
    
      
      void dispose() {
        // 释放滚动控制器
        _scrollController.dispose();
        super.dispose();
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91

    上面的代码中,InfiniteScrollList 是一个 StatefulWidget,它包含了一个可无限滚动的列表视图,可以自动加载更多数据。首先,初始状态下,列表包含20个整数项。当用户滚动到列表的底部时,它会模拟加载更多数据。当加载更多数据时,会显示一个加载指示器。效果如图所示:

    在这里插入图片描述
    现在我们归纳以下实现的思路。首先我们在 _InfiniteScrollListState 类中,使用 List.generate 创建一个包含初始数据的列表,这些数据将用于初始显示。接着创建一个 ScrollController 对象 _scrollController,它将监听列表的滚动事件。

    • initState 方法中,将滚动监听器添加到 _scrollController,以便在用户滚动到底部时触发加载更多数据的操作。
    • _loadMore 方法处理滚动事件,检查是否滚动到了列表底部,如果是并且没有在加载中,就模拟加载更多数据的过程。
    • build 方法中,使用 ListView.builder 构建列表视图。如果用户滚动到列表底部,会显示加载指示器。
    • dispose 方法中,释放 _scrollController 以防止内存泄漏。

    具体的实现步骤包括:

    以下是实现无限滚动列表的步骤:

    1. 创建一个 Flutter 应用程序。

    2. 创建一个 StatefulWidget,作为无限滚动列表的容器。

    3. 在状态类中,初始化数据源,包括初始数据列表。

    4. 创建一个滚动控制器(ScrollController)并在 initState 方法中将滚动监听器添加到它。

    5. 在滚动监听器中,检查是否滚动到了列表底部,并根据需要加载更多数据。

    6. 使用 ListView.builder 构建列表视图,根据数据源动态生成列表项。

    7. 在列表的底部显示加载指示器,以指示正在加载更多数据。

    8. dispose 方法中释放滚动控制器。

    通过这些步骤,您可以实现一个无限滚动列表,用户可以滚动并加载更多数据,从而创建无限滚动的体验。这对于需要显示大量数据的应用程序非常有用,例如社交媒体新闻源或产品列表。

    3. 改造1:仿淘宝无线滚动网格基本实现举例(GridView.builder)

    基本原理与无线滚动的列表类似,要改造为模拟无限滚动的 GridView需要进行的步骤包括:

    1. 创建数据源:首先,您需要准备一个数据源,这可以是一个包含商品信息的列表。
    2. 创建滚动视图:替换 ListView.builder 为 GridView.builder,以创建网格视图。设置 gridDelegate 来指定列数和布局。
    3. 滚动监听:使用 ScrollController 监听滚动事件,类似于之前的示例,以确定何时触发加载更多数据的操作。
    4. 动态加载触发:在滚动监听器中,检查滚动位置是否接近底部,如果是,触发加载更多数据的操作。
    5. 更新数据源:当触发加载更多数据时,更新数据源,通常是从网络或其他数据源获取新数据,并将其添加到数据源中。
    6. 重新构建UI:使用 setState() 来通知 Flutter 重新构建 UI,以显示新加载的数据。

    具体的实现代码如下:

    import 'package:flutter/material.dart';
    
    void main() {
      runApp(const MyApp());
    }
    
    class MyApp extends StatelessWidget {
      const MyApp({Key? key}) : super(key: key);
    
      
      Widget build(BuildContext context) {
        return const MaterialApp(
          home: InfiniteScrollGrid(),
        );
      }
    }
    
    class InfiniteScrollGrid extends StatefulWidget {
      const InfiniteScrollGrid({Key? key}) : super(key: key);
    
      
      State<InfiniteScrollGrid> createState() => _InfiniteScrollGridState();
    }
    
    class _InfiniteScrollGridState extends State<InfiniteScrollGrid> {
      List<String> items = List.generate(20, (index) => 'Item $index'); // 初始数据
      final ScrollController _scrollController = ScrollController();
      bool isLoading = false;
    
      
      void initState() {
        super.initState();
        _scrollController.addListener(_loadMore);
      }
    
      void _loadMore() {
        if (_scrollController.position.pixels ==
                _scrollController.position.maxScrollExtent &&
            !isLoading) {
          setState(() {
            isLoading = true;
          });
    
          Future.delayed(const Duration(seconds: 1), () {
            setState(() {
              items.addAll(
                  List.generate(10, (index) => 'Item ${index + items.length}'));
              isLoading = false;
            });
          });
        }
      }
    
      
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: const Text('无尽滚动网格'),
          ),
          body: GridView.builder(
            controller: _scrollController,
            gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
              crossAxisCount: 2, // 列数
              childAspectRatio: 0.7, // 网格项的宽高比
            ),
            itemCount: items.length + (isLoading ? 1 : 0),
            itemBuilder: (context, index) {
              if (index < items.length) {
                return Card(
                  elevation: 3,
                  margin: const EdgeInsets.all(8),
                  child: Text(items[index]),
                );
              } else {
                return const Padding(
                  padding: EdgeInsets.all(16.0),
                  child: Center(child: CircularProgressIndicator()),
                );
              }
            },
          ),
        );
      }
    
      
      void dispose() {
        _scrollController.dispose();
        super.dispose();
      }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91

    这段代码的实现效果为:
    在这里插入图片描述

  • 相关阅读:
    Redis事务失效的三种场景
    Vite探索:构建、启程、原理、CSS艺术与插件魔法
    基于uni-app与图鸟UI打造的各领域移动端模板大赏
    手把手APP抓包检测实战 - 某汽车APP
    LQ0139 油漆面积【枚举】
    【Go】slice
    QT-day2
    TinyWebServer学习笔记-
    LeetCode301:删除无效的括号
    HuTool 使用教程
  • 原文地址:https://blog.csdn.net/qq_28550263/article/details/133342307