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

news2024/11/25 21:19:55
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. 无限滚动列表

在 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';
import 'package:get/get.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(),
    );
  }
}

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();

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

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

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

  
  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,用于管理状态和滚动。
class Controller extends GetxController {
  ScrollController scrollController = ScrollController();
  List<String> items = <String>[];
  var isLoading = false;

  // 省略了其它方法
}
  • scrollController 用于管理列表的滚动。
  • items 用于存储列表中的数据项。
  • isLoading 用于标识是否正在加载更多数据。
  1. Controller类中定义了一个名为loadMore的方法,该方法用于检测是否需要加载更多数据。
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添加了滚动监听器。

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。
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)

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

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

具体的实现代码如下:

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);

  
  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();

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

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

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

  
  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
                      ),
                    ),
                  ),
                ),
            ],
          );
        },
      ),
    );
  }
}

这段代码的实现效果为:

在这里插入图片描述

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);

  
  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();

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

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

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

  
  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
                      ),
                    ),
                  ),
                ),
            ],
          );
        },
      ),
    );
  }
}

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1053561.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Spring Framework 学习笔记5:事务

Spring Framework 学习笔记5&#xff1a;事务 1.快速入门 1.1.准备工作 这里提供一个示例项目 transaction-demo&#xff0c;这个项目包含 Spring 框架、MyBatis 以及 JUnit。 对应的表结构见 bank.sql。 服务层有一个方法可以用于在不同的账户间进行转账&#xff1a; Se…

云原生Kubernetes:对外服务之 Ingress

目录 一、理论 1.Ingress 2.部署 nginx-ingress-controller(第一种方式) 3.部署 nginx-ingress-controller(第二种方式) 二、实验 1.部署 nginx-ingress-controller(第一种方式) 2.部署 nginx-ingress-controller(第二种方式) 三、问题 1.启动 nginx-ingress-controll…

Python海洋专题五之水深地形图海岸填充

Python海洋专题五之水深地形图海岸填充 海洋与大气科学 上期读取nc水深文件&#xff0c;并出图 但是存在一些不完美&#xff0c;本期修饰 本期内容 障眼法&#xff1a;把大于零的数据填充为陆地的灰色&#xff1b; 把等于零的数据画等深线为陆地和海洋的分界线&#xff01;…

怒刷LeetCode的第21天(Java版)

目录 第一题 题目来源 题目内容 解决方法 方法一&#xff1a;哈希表 方法二&#xff1a;计数器数组 第二题 题目来源 题目内容 解决方法 方法一&#xff1a;分治法 方法二&#xff1a;快速幂 迭代 方法三&#xff1a;快速幂 递归 第三题 题目来源 题目内容 …

JUC第十二讲:JUC锁: 锁核心类AQS详解

JUC第十二讲&#xff1a;JUC锁: 锁核心类AQS详解 本文是JUC第十二讲&#xff0c;JUC锁: 锁核心类AQS详解。AbstractQueuedSynchronizer抽象类是核心&#xff0c;需要重点掌握。它提供了一个基于FIFO队列&#xff0c;可以用于构建锁或者其他相关同步装置的基础框架。 文章目录 J…

aarch64 平台 musl gcc 工具链手动编译方法

目标 手动编译一个 aarch64 平台的 musl gcc 工具链 musl libc 与 glibc、uclibc 等,都是 标准C 库, musl libc 是基于系统调用之上的 标准C 库,也就是用户态的 标准C 库。 musl libc 轻量、开源、免费,是一些 操作系统的选择,当前 Lite-OS 与 RT-Smart 等均采用自制的 mu…

【算法训练-贪心算法】一 买卖股票的最佳时机II

废话不多说&#xff0c;喊一句号子鼓励自己&#xff1a;程序员永不失业&#xff0c;程序员走向架构&#xff01;本篇Blog的主题是【贪心算法】&#xff0c;使用【数组】这个基本的数据结构来实现&#xff0c;这个高频题的站点是&#xff1a;CodeTop&#xff0c;筛选条件为&…

开源校园服务小程序源码 校园综合服务小程序源码 包含快递代取 打印服务 校园跑腿【带详细部署教程】

校园综合服务小程序开源源码是一款功能强大的小程序&#xff0c;可用于搭建校园综合服务平台。共有6个选项可供选择&#xff0c;包括快递代取、打印服务、校园跑腿、代替服务、上门维修和其他帮助。 使用该源码需要自备服务器和备案过的域名&#xff0c;推荐使用2核4G服务器。最…

【python海洋专题一】查看数据nc文件的属性并输出属性到txt文件

【python海洋专题一】查看数据nc文件的属性并输出属性到txt文件 海洋与大气科学 软件 选择此软件是因为习惯了&#xff0c;matlab能看得到的界面。 新建文本 导入相关库 import netCDF4,numpy netCDF4:该包作用&#xff1a;读、写netCDF files. numpy:该包作用&#xff1a;…

【JavaScript】读取本地json文件并绘制表格

本文为避免跨域问题&#xff0c;使用了改造过的本地json文件的方法实现读取json数据并绘制表格。 如果发起http请求获取本地 json文件中数据&#xff0c;需要架设本地服务器&#xff0c;本文不做阐述。 概述 1、json在本地&#xff0c;并不需要从服务器下载。 2、采用jquery…

八、垃圾收集高级

JVM由浅入深系列一、关于Java性能的误解二、Java性能概述三、了解JVM概述四、探索JVM架构五、垃圾收集基础六、HotSpot中的垃圾收集七、垃圾收集中级八、垃圾收集高级👋垃圾收集高级 ⚽️1. CMS CMS 收集器是专为老年代空间设计的一个延迟极低的收集器,它通常会与一个稍微…

【每日一题】1498. 满足条件的子序列数目

1498. 满足条件的子序列数目 - 力扣&#xff08;LeetCode&#xff09; 给你一个整数数组 nums 和一个整数 target 。 请你统计并返回 nums 中能满足其最小元素与最大元素的 和 小于或等于 target 的 非空 子序列的数目。 由于答案可能很大&#xff0c;请将结果对 109 7 取余后…

buuctf-[WUSTCTF2020]CV Maker

打开环境 随便登录注册一下 进入到了profile.php 其他没有什么页面&#xff0c;只能更换头像上传文件&#xff0c;所以猜测是文件上传漏洞 上传一句话木马看看 <?php eval($_POST[a]);?>回显 搜索一下 添加文件头GIF89a。上传php文件 查看页面源代码&#xff0c;看…

设计模式7、桥接模式 Bridge

解释说明&#xff1a;将抽象部分与它的实现部分解耦&#xff0c;使得两者都能够独立变化 桥接模式将两个独立变化的维度设计成两个独立的继承等级结构&#xff08;而不会将两者耦合在一起形成多层继承结构&#xff09;&#xff0c;在抽象层将二者建立起一个抽象关联&#xff0c…

安卓 kuaishou 设备did和egid 学习分析

did和egid注册 接口 https://gdfp.ksapisrv.com/rest/infra/gdfp/report/kuaishou/android did 是本地生成的16进制 或者 获取的 android_id public static final Random f16237a new Random(System.currentTimeMillis()); public static long m19668a() { return f1623…

c#设计模式-结构型模式 之装饰者模式

&#x1f680;介绍 在装饰者模式中&#xff0c;装饰者类通常对原始类的功能进行增强或减弱。这种模式是在不必改变原始类的情况下&#xff0c;动态地扩展一个对象的功能。这种类型的设计模式属于结构型模式&#xff0c;因为这种模式涉及到两个类型之间的关系&#xff0c;这两个…

优化用户体验:解决element中el-tabs组件切换闪屏问题

前言 在现代 web 应用中&#xff0c;用户体验是至关重要的。然而&#xff0c;在使用 element 中的 el-tabs 组件时&#xff0c;相信有不少开发者都会遇到切换时的闪屏问题。这个问题可能导致用户在切换标签页时感到不适&#xff0c;降低了用户体验&#xff0c;本文将探讨这个问…

LeetCode面向运气之Javascript—第58题-最后一个单词的长度-99.83%

LeetCode第58题-最后一个单词的长度 题目要求 给你一个字符串 s&#xff0c;由若干单词组成&#xff0c;单词前后用一些空格字符隔开。返回字符串中 最后一个 单词的长度。 举例 输入&#xff1a;s “Hello World” 输出&#xff1a;5 输入&#xff1a;s " fly me to …

力扣 -- 97. 交错字符串

解题步骤&#xff1a; 参考代码&#xff1a; class Solution { public:bool isInterleave(string s1, string s2, string s3) {int ms1.size();int ns2.size();//先判断s1的长度s2的长度是否等于s3的长度&#xff0c;如果不等&#xff0c;则s1和s2不可能拼接成s3if(mn!s3.size…

C++11(列表初始化,声明,范围for)

目录 一、列表初始化 1、一般的列表初始化 2、容器的列表初始化 二、声明 1、 auto 2、decltype 3、nullptr 三、 范围for 一、列表初始化 1、一般的列表初始化 在C98中&#xff0c;标准允许使用花括号{}对数组或者结构体元素进行统一的列表初始值设定。 int main() {…