Flutter笔记:滚动之-无限滚动与动态加载的实现(GetX简单状态管理版)

Flutter笔记 无限滚动与动态加载的实现
(GeX简单状态管理版)


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

GetX简单状态管理提供了一种更高效的且用于取代Flutter有状态组件(StatefullWidget)的方式。本文是《无限滚动与动态加载的实现》(地址:https://jclee95.blog.csdn.net/article/details/133340592)的另外一个版本,抛弃了Flutter有状态组件,取而代之的是GetX简单状态管理。以GetX简单状态管理的方式实现的。基本过程和思路一样,仅仅是状态管理方式上不一样。另外对于部分效果进行了简单的改进。


目 录


  • [1. 无限滚动列表](#1. 无限滚动列表)
  • [2. 模拟滚动列表的基本实现举例(ListView.builder)](#2. 模拟滚动列表的基本实现举例(ListView.builder))
  • [3. 改造1:仿淘宝无线滚动网格基本实现举例(GridView.builder)](#3. 改造1:仿淘宝无线滚动网格基本实现举例(GridView.builder))

[1. 无限滚动列表](#1. 无限滚动列表)

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

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

2.1 实现思路与步骤介绍

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

准备数据源

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

创建滚动控制器

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

构建列表视图

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

设置滚动监听

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

加载更多数据

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

更新列表视图

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

2.2 一个简单例子

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

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

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: InfiniteScrollList(),
    );
  }
}

class Controller extends GetxController {
  ScrollController scrollController = ScrollController();
  List<String> items = <String>[];
  var isLoading = false;

  void loadMore() {
    if (scrollController.position.pixels ==
            scrollController.position.maxScrollExtent &&
        !isLoading) {
      isLoading = true;
      update();
      // 模拟加载1秒延时
      Future.delayed(const Duration(seconds: 1), () {
        // 生成3项假数据插入
        items.addAll(
            List.generate(3, (index) => 'Item ${index + items.length}'));
        isLoading = false;
        update();
      });
    }
  }

  static Controller get to => Get.find();

  @override
  void onInit() {
    // 初始化一些数据
    items = List.generate(20, (index) => 'Item $index');
    scrollController = ScrollController();
    isLoading = false;
    // 添加滚动监听器
    scrollController.addListener(loadMore);
    super.onInit();
  }

  @override
  void onClose() {
    scrollController.dispose();
    super.onClose();
  }
}

class InfiniteScrollList extends StatelessWidget {
  const InfiniteScrollList({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('无尽滚动列表'),
      ),
      body: GetBuilder<Controller>(
        init: Controller(),
        builder: (controller) {
          return Column(
            children: [
              Expanded(
                child: ListView.builder(
                  controller: controller.scrollController,
                  itemCount:
                      controller.items.length + (controller.isLoading ? 1 : 0),
                  itemBuilder: (context, index) {
                    if (index < controller.items.length) {
                      return Card(
                        elevation: 3,
                        margin: const EdgeInsets.all(8),
                        child: ListTile(
                          title: Text(controller.items[index]),
                          // 在这里添加商品卡片的内容
                          // 例如:商品图片、描述、价格等
                        ),
                      );
                    } else {
                      return const Padding(
                        padding: EdgeInsets.all(12.0),
                        child: Center(
                          child: SizedBox(
                            width: 18.0,
                            height: 18.0,
                            child: CircularProgressIndicator(
                              valueColor: AlwaysStoppedAnimation<Color>(
                                Colors.grey,
                              ), // 颜色为灰色
                              strokeWidth: 3, // 线宽为3
                            ),
                          ),
                        ),
                      );
                    }
                  },
                ),
              ),
            ],
          );
        },
      ),
    );
  }
}

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

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

这个代码实现了一个无限滚动的列表,其中使用了GetX来进行简单的状态管理。以下是对这个代码实现无限滚动的解释:

  1. 创建一个Controller类,该类继承自GetxController,用于管理状态和滚动。
dart 复制代码
class Controller extends GetxController {
  ScrollController scrollController = ScrollController();
  List<String> items = <String>[];
  var isLoading = false;

  // 省略了其它方法
}
  • scrollController 用于管理列表的滚动。
  • items 用于存储列表中的数据项。
  • isLoading 用于标识是否正在加载更多数据。
  1. Controller类中定义了一个名为loadMore的方法,该方法用于检测是否需要加载更多数据。
dart 复制代码
void loadMore() {
  if (scrollController.position.pixels ==
          scrollController.position.maxScrollExtent &&
      !isLoading) {
    isLoading = true;
    update();
    // 模拟加载1秒延时
    Future.delayed(const Duration(seconds: 1), () {
      // 生成3项假数据插入
      items.addAll(
          List.generate(3, (index) => 'Item ${index + items.length}'));
      isLoading = false;
      update();
    });
  }
}
  • loadMore 方法会在滚动到列表底部且不处于加载状态时触发。
  • 在加载数据时,它模拟了1秒的延时,然后生成3个假数据项,将它们添加到 items 列表中。
  • 加载完成后,将 isLoading 设置为 false 并调用 update 方法通知界面更新。
  1. Controller类的onInit方法中初始化了一些数据,并为scrollController添加了滚动监听器。
dart 复制代码
@override
void onInit() {
  // 初始化一些数据
  items = List.generate(20, (index) => 'Item $index');
  scrollController = ScrollController();
  isLoading = false;
  // 添加滚动监听器
  scrollController.addListener(loadMore);
  super.onInit();
}
  • 在初始化时,生成了20个假数据项并将它们存储在 items 列表中。
  • 创建了 scrollController 并将滚动监听器 loadMore 添加到它上面。
  1. InfiniteScrollList小部件中使用了 GetBuilder,它监听 Controller 的状态变化并更新UI。
dart 复制代码
body: GetBuilder<Controller>(
  init: Controller(),
  builder: (controller) {
    return Column(
      children: [
        Expanded(
          child: ListView.builder(
            controller: controller.scrollController,
            itemCount: controller.items.length + (controller.isLoading ? 1 : 0),
            itemBuilder: (context, index) {
              if (index < controller.items.length) {
                // 渲染数据项
              } else {
                // 渲染加载指示器
              }
            },
          ),
        ),
      ],
    );
  },
),
  • GetBuilder 会监听 Controller 的状态变化,包括 itemsisLoading,以便在数据加载时更新UI。
  • ListView.builder 用于构建列表,它的 itemCount 根据数据项的数量和加载状态动态确定。
  • itemBuilder 中,根据索引渲染数据项或加载指示器。

总结:这个代码通过GetX库实现了一个无限滚动的列表,可以动态加载数据。滚动到列表底部时,它会触发加载更多数据的操作,加载完成后更新UI以显示新的数据项。GetX的简单状态管理使得管理状态变得更加容易。

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

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

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

具体的实现代码如下:

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

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: InfiniteScrollGrid(),
    );
  }
}

class Controller extends GetxController {
  ScrollController scrollController = ScrollController();
  List<String> items = <String>[];
  var isLoading = false;

  void loadMore() {
    if (scrollController.position.pixels ==
            scrollController.position.maxScrollExtent &&
        !isLoading) {
      isLoading = true;
      update();
      // 模拟加载1秒延时
      Future.delayed(const Duration(seconds: 1), () {
        // 生成3项假数据插入
        items.addAll(
            List.generate(3, (index) => 'Item ${index + items.length}'));
        isLoading = false;
        update();
      });
    }
  }

  static Controller get to => Get.find();

  @override
  void onInit() {
    // 初始化一些数据
    items = List.generate(20, (index) => 'Item $index');
    scrollController = ScrollController();
    isLoading = false;
    // 添加滚动监听器
    scrollController.addListener(loadMore);
    super.onInit();
  }

  @override
  void onClose() {
    scrollController.dispose();
    super.onClose();
  }
}

class InfiniteScrollGrid extends StatelessWidget {
  const InfiniteScrollGrid({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('无尽滚动网格'),
      ),
      body: GetBuilder<Controller>(
        init: Controller(),
        builder: (controller) {
          return Column(
            children: [
              Expanded(
                child: GridView.builder(
                  controller: controller.scrollController,
                  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                    crossAxisCount: 2, // 列数
                    childAspectRatio: 0.7, // 网格项的宽高比
                  ),
                  itemCount: controller.items.length,
                  itemBuilder: (context, index) {
                    return Card(
                      elevation: 3,
                      margin: const EdgeInsets.all(8),
                      child: Text(controller.items[index]),
                    );
                  },
                ),
              ),
              if (controller.isLoading)
                const Padding(
                  padding: EdgeInsets.all(12.0),
                  child: Center(
                    child: SizedBox(
                      width: 18.0,
                      height: 18.0,
                      child: CircularProgressIndicator(
                        valueColor: AlwaysStoppedAnimation<Color>(
                          Colors.grey,
                        ), // 颜色为灰色
                        strokeWidth: 3, // 线宽为3
                      ),
                    ),
                  ),
                ),
            ],
          );
        },
      ),
    );
  }
}

这段代码的实现效果为:

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

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: InfiniteScrollGrid(),
    );
  }
}

class Controller extends GetxController {
  ScrollController scrollController = ScrollController();
  List<String> items = <String>[];
  var isLoading = false;

  void loadMore() {
    if (scrollController.position.pixels ==
            scrollController.position.maxScrollExtent &&
        !isLoading) {
      isLoading = true;
      update();
      // 模拟加载1秒延时
      Future.delayed(const Duration(seconds: 1), () {
        // 生成3项假数据插入
        items.addAll(
            List.generate(3, (index) => 'Item ${index + items.length}'));
        isLoading = false;
        update();
      });
    }
  }

  static Controller get to => Get.find();

  @override
  void onInit() {
    // 初始化一些数据
    items = List.generate(20, (index) => 'Item $index');
    scrollController = ScrollController();
    isLoading = false;
    // 添加滚动监听器
    scrollController.addListener(loadMore);
    super.onInit();
  }

  @override
  void onClose() {
    scrollController.dispose();
    super.onClose();
  }
}

class InfiniteScrollGrid extends StatelessWidget {
  const InfiniteScrollGrid({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('无尽滚动网格'),
      ),
      body: GetBuilder<Controller>(
        init: Controller(),
        builder: (controller) {
          return Column(
            children: [
              Expanded(
                child: GridView.builder(
                  controller: controller.scrollController,
                  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                    crossAxisCount: 2, // 列数
                    childAspectRatio: 0.7, // 网格项的宽高比
                  ),
                  itemCount: controller.items.length,
                  itemBuilder: (context, index) {
                    return Card(
                      elevation: 3,
                      margin: const EdgeInsets.all(8),
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          // 商品图片
                          Image.network(
                            'https://csdn-ebook-resources.oss-cn-beijing.aliyuncs.com/images/4e05b89fedf043f1964e73aa729d21fb/cover.jpg',
                            width: double.infinity, // 图片宽度占满卡片宽度
                            height: 200, // 图片高度
                            fit: BoxFit.cover, // 图片填充方式
                          ),
                          // 商品名称
                          const Padding(
                            padding: EdgeInsets.all(8.0),
                            child: Text(
                              '商品名称',
                              style: TextStyle(
                                  fontSize: 18, fontWeight: FontWeight.bold),
                            ),
                          ),
                          // 商品描述
                          const Padding(
                            padding: EdgeInsets.all(8.0),
                            child: Text(
                              '商品描述',
                              style: TextStyle(fontSize: 16),
                            ),
                          ),
                          // 商品价格
                          const Padding(
                            padding: EdgeInsets.all(8.0),
                            child: Text(
                              '¥ 99.99',
                              style: TextStyle(fontSize: 18, color: Colors.red),
                            ),
                          ),
                          // 在这里添加其他商品信息
                        ],
                      ),
                    );
                  },
                ),
              ),
              if (controller.isLoading)
                const Padding(
                  padding: EdgeInsets.all(12.0),
                  child: Center(
                    child: SizedBox(
                      width: 18.0,
                      height: 18.0,
                      child: CircularProgressIndicator(
                        valueColor: AlwaysStoppedAnimation<Color>(
                          Colors.grey,
                        ), // 颜色为灰色
                        strokeWidth: 3, // 线宽为3
                      ),
                    ),
                  ),
                ),
            ],
          );
        },
      ),
    );
  }
}
相关推荐
孤鸿玉17 小时前
Fluter InteractiveViewer 与ScrollView滑动冲突问题解决
flutter
叽哥1 天前
Flutter Riverpod上手指南
android·flutter·ios
BG2 天前
Flutter 简仿Excel表格组件介绍
flutter
zhangmeng2 天前
FlutterBoost在iOS26真机运行崩溃问题
flutter·app·swift
恋猫de小郭2 天前
对于普通程序员来说 AI 是什么?AI 究竟用的是什么?
前端·flutter·ai编程
卡尔特斯2 天前
Flutter A GlobalKey was used multipletimes inside one widget'schild list.The ...
flutter
w_y_fan2 天前
Flutter 滚动组件总结
前端·flutter
醉过才知酒浓2 天前
Flutter Getx 的页面传参
flutter
火柴就是我3 天前
flutter 之真手势冲突处理
android·flutter
Speed1233 天前
`mockito` 的核心“打桩”规则
flutter·dart