Flutter-ListView组件下拉刷新+滚动底部加载+缓存封装

2023-12-11 20:50:17 浏览数 (2)

Flutter的ListView组件,虽然很好用,但是数据量大的时候,在低配置的机器上会奇卡无比,所以我封装了一个ListView组件,对数据进行截断,并使用 keframe 插件进行流畅性优化,提升低配置设备的用户体验。

具体可直接查看代码示例,配有相关注释。

Sorry, your browser does not support the video tag.

大家也可以使用adb命令对设备进行录屏:adb shell screenrecord /sdcard/test.mp4,再转换为 gif 文件,推荐使用https://ezgif.com/video-to-gif。

组件代码

代码语言:javascript复制
/*
 * @Author: hxb
 */
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';//后续去除
import 'package:keframe/size_cache_widget.dart';

/// 可缓存 下拉刷新 滚动到底部自动加载的ListView组件(返回数据定义复杂是方便局部刷新)
class CacheTableList extends StatefulWidget {
  List tableList;
  double height;
  ScrollController controller;
  String emptyMessage;
  String noMoreMessage;
  Function onRefresh; //需返回完整的待渲染tablelist,方便组件局部刷新。
  Function createList; //自定义的item建立事件
  Function getMoreData; //需返回完整的待渲染tablelist,返回null表示无更多数据,方便组件局部刷新。
  int pageCount;

  CacheTableList(
      {Key key,
      @required this.createList,
      this.tableList,
      this.onRefresh,
      this.controller,
      this.height,
      this.emptyMessage,
      this.noMoreMessage,
      this.pageCount,
      this.getMoreData})
      /// pageCount为静态数据懒加载,需配合tableList使用。getMoreData为动态加载数据,他与前者只能存在一种模式。
      : assert(getMoreData != null || pageCount != null),
        assert(pageCount == null || tableList != null),
        assert(pageCount != null || tableList == null), //tableList与pageCount同在
        super(key: key);
  @override
  _CacheTableListState createState() => _CacheTableListState();
}

class _CacheTableListState extends State<CacheTableList> {
  List orginalTableList = [];
  List _tableList = [];
  int _pageCount = 0;
  bool _hasMore = true;
  double _height;
  ScrollController _controller;

  @override
  void initState() {
    super.initState();
    orginalTableList = widget.tableList ?? [];
    _controller = widget.controller ?? new ScrollController();
    if (widget.getMoreData != null || widget.pageCount != 0) {
      _controller.addListener(() {
        if (_controller.position.pixels == _controller.position.maxScrollExtent) {
          _getMoreData();
        }
      });
    }
    _height = widget.height ?? 300; //double.infinity,
  }

  @override
  void dispose() {
    super.dispose();
    _controller.removeListener(() {});
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      height: _height,
      decoration: new BoxDecoration(color: Colors.white),
      child: SizeCacheWidget(
        estimateCount: _tableList.length,
        child: 
        widget.onRefresh != null
        ? RefreshIndicator(
            onRefresh: _onRefresh, //下拉刷新回调
            displacement: 10, //指示器显示时距顶部位置
            color: Colors.white, //指示器颜色,默认ThemeData.accentColor
            backgroundColor: Colors.blueAccent, //指示器背景颜色,默认ThemeData.canvasColor
            notificationPredicate: defaultScrollNotificationPredicate, //是否应处理滚动通知的检查(是否通知下拉刷新动作)
            child: Scrollbar(
              child: _createList(),
            ),
          )
        : Scrollbar(
            child: _createList(),
          ),
      ),
    );
  }

  /// 创建list
  Widget _createList() {
    if (_tableList.isEmpty && _pageCount == 0) {
      _getMoreData();
      return CupertinoActivityIndicator();
    }
    return _tableList.isEmpty
        ? Container(
            height: _height - 20,
            alignment: Alignment.topCenter,
            margin: EdgeInsets.only(top: 20),
            child: Text(
              widget.emptyMessage ?? '暂无数据',
              style: TextStyle(fontSize: 15, color: Colors.black38, fontWeight: FontWeight.bold),
            ),
          )
        : ListView.builder(
            controller: _controller,
            cacheExtent: 1000,
            itemCount: _tableList.length,
            itemBuilder: (_, i) {
              if (i == _tableList.length - 1 && _hasMore) {
                return Padding(
                  padding: EdgeInsets.all(10.0),
                  child: Center(
                    child: CupertinoActivityIndicator(),
                  ),
                );
              }
              return widget.createList(i);//自定义创建item事件
            },
          );
  }

  /// 加载更多数据
  void _getMoreData() async {
    if (!_hasMore) {
      EasyLoading.showToast(widget.noMoreMessage ?? '已经到底了');
      // ScaffoldMessenger.of(context).showSnackBar(
      //   SnackBar(
      //     content: Text(widget.noMoreMessage ?? '已经到底了'),
      //   ),
      // );
      return;
    }
    if (widget.getMoreData != null) {
      _pageCount = -1; //使用自定义方法加载数据,除第一次加载数据后则无需pageCount。
      var tempData = await widget.getMoreData();
      if (tempData == null) {
        _hasMore = false;
      } else {
        _tableList = tempData;
      }
      setState(() {});
      return;
    }
    int nowLength = orginalTableList.length;
    if (_pageCount <= nowLength) {
      _pageCount  = widget.pageCount;
      Future.delayed(Duration(microseconds: 500)).then((e) {
        setState(() {
          if (_pageCount <= nowLength) {
            _tableList = orginalTableList.sublist(0, _pageCount);
          } else {
            _tableList = orginalTableList;
            _hasMore = false;
          }
        });
      });
    }
  }

  /// 刷新时候的事件,需返回Future。
  Future _onRefresh() {
    return Future.sync(() async {
      _tableList = await widget.onRefresh();
      _pageCount = 0;
      _hasMore = true;
      setState(() {});
    });
  }
}

使用示例

静态加载数据模式-简单示例
代码语言:javascript复制
CacheTableList(
  tableList: ["列表内容1","列表内容2","列表内容3","列表内容4","列表内容5","列表内容6"],
  pageCount: 3,//上面6笔静态数据,会以每三笔的方式加载。注意高度设定需符合要求
  createList: (index) {
    return FrameSeparateWidget(//使用keframe进行单帧动画优化,提升低配置设备流畅性。
      index: index,
      placeHolder: Container(//占位组件,尽量简单。
        height: 36,
        width: double.infinity,
        margin: EdgeInsets.only(bottom: 5, top: 5),
        child: Text("......"),
        alignment: Alignment.center,
      ),
      child: ListTile(title: Text("列表第$index项"))
    );
  },
  onRefresh: () {
    //下拉事件->重新渲染列表
    return Future.delayed(Duration(milliseconds: 1000), () {
      return ["列表内容1","列表内容2","列表内容3","列表内容4","列表内容5","列表内容6"];
    });
  }
);
动态加载数据模式-简单示例
代码语言:javascript复制
// _tableList = ["列表内容1","列表内容2","列表内容3","列表内容4","列表内容5","列表内容6"];
CacheTableList(
  createList: (index) {
    return FrameSeparateWidget(//使用 keframe 进行单帧动画优化,提升低配置设备流畅性。
      index: index,
      placeHolder: Container(//占位组件,尽量简单。
        height: 36,
        width: double.infinity,
        margin: EdgeInsets.only(bottom: 5, top: 5),
        child: Text("......"),
        alignment: Alignment.center,
      ),
      child: ListTile(title: Text(_tableList[index]))
    );
  },
  getMoreData: () {
    _tableList.addAll(_tableList);
    return Future.delayed(Duration(milliseconds: 1000), () {
      return _tableList.length > 100 ? null : _tableList; //为null表示数据到极限不再加载
    });
  }
  /// onRefresh不配置时则不会有下拉事件
  //  onRefresh: () {
  //    //下拉事件->重新渲染列表
  //    return Future.delayed(Duration(milliseconds: 1000), () {
  //      return ["列表内容1","列表内容2","列表内容3","列表内容4","列表内容5","列表内容6"];
  //    });
  //  }
);

0 人点赞