Flutter笔记无限滚动与动态加载的实现
(GeX简单状态管理版)
作者:李俊才 (jcLee95):https://blog.csdn.net/qq_28550263
本文地址: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); @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来进行简单的状态管理。以下是对这个代码实现无限滚动的解释:
- 创建一个
Controller
类,该类继承自GetxController
,用于管理状态和滚动。
class Controller extends GetxController { ScrollController scrollController = ScrollController(); List<String> items = <String>[]; var isLoading = false; // 省略了其它方法 }
scrollController
用于管理列表的滚动。items
用于存储列表中的数据项。isLoading
用于标识是否正在加载更多数据。
- 在
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
方法通知界面更新。
- 在
Controller
类的onInit
方法中初始化了一些数据,并为scrollController
添加了滚动监听器。
@override void onInit() { // 初始化一些数据 items = List.generate(20, (index) => 'Item $index'); scrollController = ScrollController(); isLoading = false; // 添加滚动监听器 scrollController.addListener(loadMore); super.onInit(); }
- 在初始化时,生成了20个假数据项并将它们存储在
items
列表中。 - 创建了
scrollController
并将滚动监听器loadMore
添加到它上面。
- 在
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
的状态变化,包括items
和isLoading
,以便在数据加载时更新UI。ListView.builder
用于构建列表,它的itemCount
根据数据项的数量和加载状态动态确定。- 在
itemBuilder
中,根据索引渲染数据项或加载指示器。
总结:这个代码通过GetX库实现了一个无限滚动的列表,可以动态加载数据。滚动到列表底部时,它会触发加载更多数据的操作,加载完成后更新UI以显示新的数据项。GetX的简单状态管理使得管理状态变得更加容易。
3. 改造1:仿淘宝无线滚动网格基本实现举例(GridView.builder)
基本原理与无线滚动的列表类似,要改造为模拟无限滚动的 GridView需要进行的步骤包括:
- 创建数据源:首先,您需要准备一个数据源,这可以是一个包含商品信息的列表。
- 创建滚动视图:替换 ListView.builder 为 GridView.builder,以创建网格视图。设置 gridDelegate 来指定列数和布局。
- 滚动监听:使用 ScrollController 监听滚动事件,类似于之前的示例,以确定何时触发加载更多数据的操作。
- 动态加载触发:在滚动监听器中,检查滚动位置是否接近底部,如果是,触发加载更多数据的操作。
- 更新数据源:当触发加载更多数据时,更新数据源,通常是从网络或其他数据源获取新数据,并将其添加到数据源中。
- 重新构建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); @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 ), ), ), ), ], ); }, ), ); } }
这段代码的实现效果为:
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 ), ), ), ), ], ); }, ), ); } }