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

发布于:2025-02-28 ⋅ 阅读:(13) ⋅ 点赞:(0)

可以用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()
        ],
      ),
    );
  }
}