Flutter_学习记录_实现列表上拉加载更多的功能

可以用ScrollController组件来实现这样列表上拉加载更多的功能:
请添加图片描述

1. 定义变量

StatefulWidget 的组件内,添加三个属性:

// 滚动视图的控制器
  final ScrollController _scrollController = ScrollController();
  // 是否已显示了上拉加载中
  bool _isShowMore = false;
  // 是否有更多的数据
  bool _isHaveMoreData = true;

2. 在初始化的方法中,添加对ScrollController的监听

重写initState的方法,并在initState方法中,添加ScrollController的监听,代码如下:


  void initState() {
    super.initState();
	
	// 对 _scrollController 添加监听
    _scrollController.addListener((){
      // 内容视图最大的高度
      double maxScrollExtent = _scrollController.position.maxScrollExtent;
      // 视图滚动的大小
      double scrollContentOffY = _scrollController.position.pixels;

      if (scrollContentOffY > maxScrollExtent + Screenadapter.height(40)) {
        // 超出了内容视图高度的20,就重新上拉加载更多
        if (_isShowMore == false && _isHaveMoreData == true) {
          // 未显示, 
          print("上拉加载更多");
          // 添加网络请求
          _getProductListDataRequest();
        }
        
      }
    });

3. 在ListView中,关联ScrollController

代码如下:

ListView.builder(
	// 关联 ScrollController
	controller: _scrollController,
	itemBuilder: (BuildContext context, int index) {
		return Column();
	}
)

4. 写一个简易的Loading的加载视图

代码如下:

import 'package:fangjd/Services/ScreenAdapter.dart';
import 'package:flutter/material.dart';

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

  
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        padding: EdgeInsets.all(Screenadapter.width(20)),
        child: Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            CircularProgressIndicator(strokeWidth: 1.0),
            SizedBox(width: Screenadapter.width(20)),
            Text("加载中....")
          ],
        ),
      ),
    );
  }
}

5. 在列表的底部视图,添加加载中的视图或者添加我是我底线的视图

代码如下:

// 商品列表底部的视图
  Widget _productBottomWiget(int index) {
    if (_isHaveMoreData == true) {
      // 如果有更多的数据,上拉,就显示“加载中”, 其中_productList 是数据源
      if (_isShowMore && (index == _productList.length -1)) {
      	// 只有最后一个数据,才添加 “加载中”的loading视图
        return Loadingwidget();
      } else {
      	// 其他数据 就什么都不显示
        return SizedBox(height: 1);
      }
    } else {
    	// 如果没有更多的数据,
      if (index == _productList.length -1) {
      	 // 在最后一个数据,添加 “--我也是有底线的--”的视图
        return Padding(padding:EdgeInsets.only(top: Screenadapter.height(30)), child: Text("--我也是有底线的--", textAlign: TextAlign.center,));
      } else {
      	// 其他数据 就什么都不显示
        return SizedBox(height: 1);
      }
    }
  }

6. 模拟网络数据的请求

代码如下:

// -----网络请求--------
  void _getProductListDataRequest() async {
    setState(() {
     // 当调用网络请求的时候,设置_isShowMore 为 true, 表示需要展示“加载中”的底部视图
      _isShowMore = true;
    });
    // 模拟网络延迟
    await Future.delayed(Duration(seconds: 2)); 
    List<ProductItemModel> mockDataList = _mockData();
    setState(() {
      _productList.addAll(mockDataList);
      // 等网路请求成功后,设置 _isShowMore 为 false, 表示 隐藏 “加载中”的底部视图
      _isShowMore = false;
      if (_productList.length > 20) {
        // 模拟没有更多数据了,就展示“我是有底线的”的视图 
        _isHaveMoreData = false;
      }
    });
  }

  // 模拟数据
  List<ProductItemModel> _mockData() {
    // return
    List<ProductItemModel> mockList = [];
    for (var index in [1,2,3,4,5,6,7,8,9,0]) {
      var model = ProductItemModel(iId: 1, title: '笔记本电脑$index', price: "¥3980", oldPrice: "¥4999", pic: "https://www.itying.com/images/flutter/hot${index+1}.jpg");
      mockList.add(model);
    }
    return mockList;
  }

7. 完整的代码实例

import 'package:fangjd/CommonWidget/LoadingWidget.dart';
import 'package:fangjd/Models/ProductModel.dart';
import 'package:fangjd/Services/ScreenAdapter.dart';
import 'package:flutter/material.dart';

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

  
  State<ProductlistPage> createState() => _ProductlistPageState();
}

class _ProductlistPageState extends State<ProductlistPage> {
  // ScaffoldState, 控制侧边栏的显示
  final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
  // 筛选导航栏的索引
  int _selectIndex = 1;
  // 商品列表的数据
  List<ProductItemModel> _productList = [];
  // 滚动视图的控制器
  final ScrollController _scrollController = ScrollController();
  // 是否已显示了上拉加载中
  bool _isShowMore = false;
  // 是否有更多的数据
  bool _isHaveMoreData = true;

  
  void initState() {
    super.initState();

    _scrollController.addListener((){
      // 内容视图最大的高度
      double maxScrollExtent = _scrollController.position.maxScrollExtent;
      // 视图滚动的大小
      double scrollContentOffY = _scrollController.position.pixels;

      if (scrollContentOffY > maxScrollExtent + Screenadapter.height(40)) {
        // 超出了内容视图高度的20,就重新上拉加载更多
        if (_isShowMore == false && _isHaveMoreData == true) {
          // 未显示
          _getProductListDataRequest();
        }
        
      }
    });

    // -----网络请求--------
    _getProductListDataRequest();
  }

  // -----网络请求--------
  void _getProductListDataRequest() async {
    setState(() {
      _isShowMore = true;
    });
    // 模拟网络延迟
    await Future.delayed(Duration(seconds: 2)); 
    List<ProductItemModel> mockDataList = _mockData();
    setState(() {
      _productList.addAll(mockDataList);
      _isShowMore = false;
      if (_productList.length > 20) {
        _isHaveMoreData = false;
      }
    });
  }

  // 模拟数据
  List<ProductItemModel> _mockData() {
    // return
    List<ProductItemModel> mockList = [];
    for (var index in [1,2,3,4,5,6,7,8,9,0]) {
      var model = ProductItemModel(iId: 1, title: '笔记本电脑$index', price: "¥3980", oldPrice: "¥4999", pic: "https://www.itying.com/images/flutter/hot${index+1}.jpg");
      mockList.add(model);
    }
    return mockList;
  }

  // -----视图设置--------
  // 设置 筛选透视图的底部选中的线
  Border _selectHeaderBoder() {
    return Border(
      bottom: BorderSide(
        width: 1,
        color: Colors.red
      )
    );
  }

  // 筛选导航栏
  Widget _subHeaderWidget() {
    return Positioned(
      top: 0.0,
      child: Container(
        height: Screenadapter.height(80),
        width: Screenadapter.screenWidth(),
        decoration: BoxDecoration(
          border: Border(
            bottom: BorderSide(
              width: 1,
              color: Colors.black12
            ),
          ),
        ),
        child: Row(
          children: [
            Expanded(
              child: InkWell(
                onTap: () {
                  setState(() {
                    _selectIndex = 1;
                  });
                },
                child: Container(
                  decoration: BoxDecoration(
                    border: _selectIndex == 1 ? _selectHeaderBoder() : null
                  ),
                  child: Center(child: Text("综合", style: TextStyle(color: _selectIndex == 1 ? Colors.red : Colors.black))),
                ),
              )
            ),
            Expanded(
              child: InkWell(
                onTap: () {
                  setState(() {
                    _selectIndex = 2;
                  });
                },
                child: Container(
                  decoration: BoxDecoration(
                    border: _selectIndex == 2 ? _selectHeaderBoder() : null
                  ),
                  child: Center(child: Text("销量", style: TextStyle(color: _selectIndex == 2 ? Colors.red : Colors.black))),
                ),
              )
            ),
            Expanded(
              child: InkWell(
                onTap: () {
                  setState(() {
                    _selectIndex = 3;
                  });
                },
                child: Container(
                  decoration: BoxDecoration(
                    border: _selectIndex == 3 ? _selectHeaderBoder() : null
                  ),
                  child: Center(child: Text("价格", style: TextStyle(color: _selectIndex == 3 ? Colors.red : Colors.black))),
                ),
              )
            ),
            Expanded(
              child: InkWell(
                onTap: () {
                  setState(() {
                    _selectIndex = 4;
                  });
                  _scaffoldKey.currentState?.openEndDrawer();
                },
                child: Container(
                  decoration: BoxDecoration(
                    border: _selectIndex == 4 ? _selectHeaderBoder() : null
                  ),
                  child: Center(child: Text("筛选", style: TextStyle(color: _selectIndex == 4 ? Colors.red : Colors.black))),
                ),
              )
            ),
          ],
        ),
      )
    );
  }

  // 商品列表底部的视图
  Widget _productBottomWiget(int index) {
    if (_isHaveMoreData == true) {
      if (_isShowMore && (index == _productList.length -1)) {
        return Loadingwidget();
      } else {
        return Text("");
      }
    } else {
      if (index == _productList.length -1) {
        return Padding(padding:EdgeInsets.only(top: Screenadapter.height(30)), child: Text("--我也是有底线的--", textAlign: TextAlign.center,));
      } else {
        return SizedBox(height: 1);
      }
    }
  }

  // 商品列表
  Widget _productListWidget() {
    if (_productList.isNotEmpty) {
      return  Container(
        padding: EdgeInsets.all(Screenadapter.width(10)),
        margin: EdgeInsets.only(top: Screenadapter.height(80)),
        child: ListView.builder(
          controller: _scrollController,
          itemBuilder: (BuildContext context, int index) {
            ProductItemModel itemModel = _productList[index];
            return Column(
              children: [
                Row(
                  children: [
                    Container(
                      padding: EdgeInsets.all(Screenadapter.width(20)),
                      width: Screenadapter.width(220),
                      height: Screenadapter.height(220),
                      child: AspectRatio(
                        aspectRatio: 1,
                        child: Image.network(itemModel.pic, fit: BoxFit.cover)
                      ),
                    ),
                    Expanded(child: Container(
                      height: Screenadapter.height(220),
                      padding: EdgeInsets.only(top: Screenadapter.height(10)),
                      child: Column(
                        mainAxisAlignment: MainAxisAlignment.start,
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          Text(
                            itemModel.title,
                            maxLines: 2,
                            overflow: TextOverflow.ellipsis,
                          ),
                          SizedBox(height: Screenadapter.height(20)),
                          Container(
                            height: Screenadapter.height(36),
                            width: double.infinity,
                            child: Row(
                              children: [
                                Container(
                                  height: Screenadapter.height(40),
                                  width: Screenadapter.width(80),
                                  decoration: BoxDecoration(
                                    borderRadius: BorderRadius.circular(Screenadapter.height(18)),
                                    color: Colors.black12
                                  ),
                                  child: Center(child: Text(' 4G ', style: TextStyle(color: Colors.black87))),
                                ),
                                SizedBox(width: Screenadapter.width(20)),
                                Container(
                                  height: Screenadapter.height(40),
                                  width: Screenadapter.width(80),
                                  decoration: BoxDecoration(
                                    borderRadius: BorderRadius.circular(Screenadapter.height(18)),
                                    color: Colors.black12
                                  ),
                                  child: Center(child: Text(' 126 ', style: TextStyle(color: Colors.black87))),
                                ),
                              ],
                            ),
                          ),
                          SizedBox(height: Screenadapter.width(20)),
                          Text(itemModel.price, style: TextStyle(color: Colors.red, fontSize: 18))
                        ]
                      )
                    ))
                  ],
                ),
                Divider(height: Screenadapter.height(2)),
                // 添加列表底部视图:显示“加载中”还是显示“我是有底线的”
                _productBottomWiget(index)
              ],
            );
          },
          itemCount: _productList.length
        ),
      );
    } else {
      return Loadingwidget();
    }
  }
  
  
  Widget build(BuildContext context) {
    return Scaffold(
      key: _scaffoldKey,
      appBar: AppBar(
        title: Text("商品分类"),
        actions: [
          // 去掉’endDrawer‘默认设置的图标
          Text("")
        ],
      ),
      endDrawer: Drawer(
        child: Center(child: Text("我是侧边栏")),
      ),
      body: Stack(
        children: [
          // 筛选导航栏
          _subHeaderWidget(),
          // 商品列表
          _productListWidget()
        ],
      ),
    );
  }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用DeepSeek+KIMI生成高质量PPT

一、使用DeepSeek DeepSeek官网&#xff1a;DeepSeek 点击“开始对话”&#xff0c;进入交互页面。 在上图中&#xff0c;输入问题&#xff0c;即可获取AI生成的结果。 基础模型&#xff08;V3&#xff09;&#xff1a;通用模型&#xff08;2024.12&#xff09;&#xff0c;高…

TCP和UDP比较

以下是 TCP&#xff08;传输控制协议&#xff09; 和 UDP&#xff08;用户数据报协议&#xff09; 的详细对比&#xff0c;涵盖核心特性、应用场景及技术差异&#xff1a; 1. 核心特性对比 特性TCPUDP连接方式面向连接&#xff08;需三次握手建立连接&#xff09;无连接&#…

Spring Boot 3.x 基于 Redis 实现邮箱验证码认证

文章目录 依赖配置开启 QQ 邮箱 SMTP 服务配置文件代码实现验证码服务邮件服务接口实现执行流程 依赖配置 <dependencies> <!-- Spring Boot Starter Web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spr…

(七)消息队列-Kafka 序列化avro(传递)

&#xff08;七&#xff09;消息队列-Kafka 序列化avro&#xff08;传递&#xff09; 客从远方来&#xff0c;遗我双鲤鱼。呼儿烹鲤鱼&#xff0c;中有尺素书。 ——佚名《饮马长城窟行》 本文已同步CSDN、掘金平台、知乎等多个平台&#xff0c;图片依然保持最初发布的水印&…

Docker 学习(一)

一、Docker 核心概念 Docker 是一个开源的容器化平台&#xff0c;允许开发者将应用及其所有依赖&#xff08;代码、运行时、系统工具、库等&#xff09;打包成一个轻量级、可移植的“容器”&#xff0c;实现 “一次构建&#xff0c;随处运行”。 1、容器&#xff08;Container…

使用mermaid查看cursor程序生成的流程图

一、得到cursor生成的流程图文本 cursor写的程序正常运行后&#xff0c;在对话框输入框中输入诸如“请生成扫雷的代码流程图”&#xff0c;然后cursor就把流程图给生成了&#xff0c;但是看到的还是文本的样子&#xff0c;保留这部分内容待用 二、注册一个Mermaid绘图账号 …

蜂鸣器使用

1、蜂鸣器原理 无源蜂鸣器模块根据输入的 不同方波信号&#xff08;作为震荡源&#xff09;可以发出不同的声音。驱动电路中三极管电阻一般为1K-4K都行&#xff0c;能够让三极管导通即可。&#xff08;三极管即带箭头的部分&#xff0c;基极和发射机&#xff08;PNP&#xff09…

15. LangChain实战项目2——易速鲜花海报文案生成

你已经制作好了一批鲜花的推广海报&#xff0c;想为每一个海报的内容&#xff0c;写一两句话&#xff0c;然后 post 到社交平台上&#xff0c;以期图文并茂。 下载 Salesforce/blip-image-captioning-large 图生文模型 通过以下几个命令下载该模型 pip install -U huggingfa…

支持IPD项目管理的9大系统,哪款工具能有效提高项目控制能力

本文介绍了以下9大系统: 1.Worktile&#xff1b; 2. 腾讯敏捷开发平台&#xff08;TAPD&#xff09;&#xff1b; 3. 简道云&#xff08;Jiandaoyun&#xff09;&#xff1b; 4. 蓝鲸智云&#xff08;BlueWhale&#xff09;&#xff1b; 5. 轻流&#xff08;Qingflow&#xff0…

创建一个MCP服务器,并在Cline中使用,增强自定义功能。

MCP介绍 MCP 是一个开放协议&#xff0c;它标准化了应用程序如何向LLMs提供上下文。可以将 MCP 视为 AI 应用程序的 USB-C 端口。正如 USB-C 提供了一种标准化的方法来将您的设备连接到各种外围设备和配件一样&#xff0c;MCP 提供了一种标准化的方法来将 AI 模型连接到不同的…

Linux之yum详解

—— 小 峰 编 程 目录 1、Linux软件的安装方式 2、什么是yum 3、配置网络yum源 4、yum命令 【语法】 【yum常用命令】 1、Linux软件的安装方式 在CentOS系统中&#xff0c;软件管理方式通常有三种方式&#xff1a; rpm安装 、 yum安装 以及 编译安装 。 2、什么是yum…

2025国家护网HVV高频面试题总结来了01(题目+回答)

网络安全领域各种资源&#xff0c;学习文档&#xff0c;以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具&#xff0c;欢迎关注。 目录 0x1 高频面试题第一套 0x2 高频面试题第二套 0x3 高频面试题第三套 0x4 高频面试题第四套 0x5 高频面…

leetcode 59. 螺旋矩阵 II 中等

给你一个正整数 n &#xff0c;生成一个包含 1 到 n2 所有元素&#xff0c;且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;[[1,2,3],[8,9,4],[7,6,5]]示例 2&#xff1a; 输入&#xff1a;n 1 输出&am…

基于Rook的Ceph云原生存储部署与实践指南(下)

#作者&#xff1a;任少近 文章目录 6Ceph资源对像管理6.1查看services6.2查看Jobs6.3 查看deployments.apps6.4查看daemonsets.apps6.5查看configmaps6.6查看clusterroles.rbac.authorization.k8s.io6.7查看clusterrolebindings.rbac.authorization.k8s.io6.8通过cephclusters…

深入浅出 Go 语言:协程(Goroutine)详解

深入浅出 Go 语言&#xff1a;协程(Goroutine)详解 引言 Go 语言的协程&#xff08;goroutine&#xff09;是其并发模型的核心特性之一。协程允许你轻松地编写并发代码&#xff0c;而不需要复杂的线程管理和锁机制。通过协程&#xff0c;你可以同时执行多个任务&#xff0c;并…

常用的api测试软件

我们在写完后端API接口的时候&#xff0c;前端工程师可能还没有写完前端的页面&#xff0c;这时候后端工程师需要测试接口&#xff0c;因此后端开发通常需要api测试软件来测试接口&#xff0c;同时通过测试软件把定义好的接口格式分享文档。 这里推荐两款api测试软件软件&…

深入理解并实现自定义 unordered_map 和 unordered_set

亲爱的读者朋友们&#x1f603;&#xff0c;此文开启知识盛宴与思想碰撞&#x1f389;。 快来参与讨论&#x1f4ac;&#xff0c;点赞&#x1f44d;、收藏⭐、分享&#x1f4e4;&#xff0c;共创活力社区。 在 C 的标准模板库&#xff08;STL&#xff09;中&#xff0c;unorder…

可商用街头文化艺术海报封面手写涂鸦标题LOGO排版英文字体 FS163 TYPE FACE

Freestyle 163 &#xff08;FS163&#xff09;是一个受街头文化和城市艺术启发的视觉宣言。该字体旨在突出我们的文化和创意根源&#xff0c;反映了街头运动、城市艺术以及来自社会和边缘的故事。 FS163与面临挑战、质疑规范、放大被忽视声音的品牌和个人联系在一起&#xff0c…

【教程】可视化配置多台主机通过交换机实现互联通信

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ 目录 场景定义 配置步骤 1. 确认网络接口名称 2. 配置静态 IP 地址 3. 验证连接 主机间速率测试 场景定义 有两台主机通过网线与交换机相连。主…

HTTP/2 服务器端推送:FastAPI实现与前端集成指南

HTTP/2 服务器端推送&#xff1a;FastAPI实现与前端集成指南 注意&#xff1a;本文末尾附有完整示例代码&#xff0c;文中仅展示核心关键代码。完整代码可在GitHub仓库获取。 本文将会讲解HTTP2协议和相关配置实践。但是不要混淆&#xff0c;SSE的实现完全基于HTTP/1.1的持久连…