flutter 专题 六十一 支持上拉加载更多的自定义横向滑动表格

在股票软件中,经常会看到如下所示的效果(ps:由于公司数据敏感,所以使用另一个朋友的一个图)。
在这里插入图片描述
分析需要后,我先在网上找了下支持横向滑动的组件,最后找到了这个:flutter_horizontal_data_table,看了下示例,也满足我的开发需要,并且我使用2000条数据进行测试,也没有卡顿的问题。

不过,这个组件有一个问题是不支持下拉,因为很多场景中,对于这种数据比较多的情况,我们需要对数据进行分页加载,给予此,我们需要对flutter_horizontal_data_table进行改造,增加支持上拉加载更多和下拉刷新的功能。于是,改造后的代码如下所示。

/** https://github.com/MayLau-CbL/flutter_horizontal_data_table*/
class HorizontalDataTable extends StatefulWidget {final VoidCallback loadMore;final bool enablePullUp;final double leftHandSideColumnWidth;final double rightHandSideColumnWidth;final bool isFixedHeader;final List<Widget> headerWidgets;final List<Widget> leftSideChildren;final List<Widget> rightSideChildren;final int itemCount;final IndexedWidgetBuilder leftSideItemBuilder;final IndexedWidgetBuilder rightSideItemBuilder;final Widget rowSeparatorWidget;final double elevation;const HorizontalDataTable({@required this.leftHandSideColumnWidth,@required this.rightHandSideColumnWidth,this.isFixedHeader = false,this.headerWidgets,this.leftSideItemBuilder,this.rightSideItemBuilder,this.itemCount = 0,this.leftSideChildren,this.rightSideChildren,this.enablePullUp = false,this.loadMore,this.rowSeparatorWidget = const Divider(color: Colors.transparent,height: 0.0,thickness: 0.0,),this.elevation = 5.0,Key key}): assert((leftSideChildren == null && leftSideItemBuilder != null) ||(leftSideChildren == null),'Either using itemBuilder or children to assign left side widgets'),assert((rightSideChildren == null && rightSideItemBuilder != null) ||(rightSideChildren == null),'Either using itemBuilder or children to assign right side widgets'),assert((isFixedHeader && headerWidgets != null) || !isFixedHeader,'If use fixed top row header, isFixedHeader==true, headerWidgets must not be null'),assert(itemCount >= 0, 'itemCount must >= 0'),assert(elevation >= 0.0, 'elevation must >= 0.0'),super(key: key);@overrideState<StatefulWidget> createState() {return HorizontalDataTableState();}
}class HorizontalDataTableState extends State<HorizontalDataTable> {ScrollController _leftHandSideListViewScrollController = ScrollController(keepScrollOffset: false);ScrollController _rightHandSideListViewScrollController = ScrollController(keepScrollOffset: false);ScrollController _rightHorizontalScrollController = ScrollController(keepScrollOffset: false);_SyncScrollControllerManager _syncScroller = _SyncScrollControllerManager();ScrollShadowModel _scrollShadowModel = ScrollShadowModel();RefreshController refreshController = RefreshController();ScrollController refreshScrollController = ScrollController(keepScrollOffset: false);bool finishLoading=true;scrollToTop() {_leftHandSideListViewScrollController.jumpTo(0);}finishLoad() {finishLoading = true;}@overridevoid initState() {super.initState();_syncScroller.registerScrollController(_leftHandSideListViewScrollController);_syncScroller.registerScrollController(_rightHandSideListViewScrollController);_leftHandSideListViewScrollController.addListener(() {_scrollShadowModel.verticalOffset =_leftHandSideListViewScrollController.offset;if(_leftHandSideListViewScrollController.position.pixels + 90 >=_leftHandSideListViewScrollController.position.maxScrollExtent) {if(finishLoading) {this.widget.loadMore();finishLoading = false;print('HorizontalDataTableState>>>>>>>');}}else {setState(() {});}});_rightHorizontalScrollController.addListener(() {_scrollShadowModel.horizontalOffset =_rightHorizontalScrollController.offset;if(_rightHorizontalScrollController.position.pixels ==  _rightHorizontalScrollController.position.maxScrollExtent) {}else {setState(() {});}});}@overridevoid dispose() {_syncScroller.unregisterScrollController(_leftHandSideListViewScrollController);_syncScroller.unregisterScrollController(_rightHandSideListViewScrollController);_leftHandSideListViewScrollController.dispose();_rightHandSideListViewScrollController.dispose();_rightHorizontalScrollController.dispose();refreshScrollController.dispose();super.dispose();}@overrideWidget build(BuildContext context) {return _buildContent();}Widget buildClassicFooter() {return CustomFooter(height: 0,builder: (BuildContext context,LoadStatus mode){return Container();},);}Widget _buildContent() {return ChangeNotifierProvider<ScrollShadowModel>.value(value: _scrollShadowModel, child: SafeArea(child: _getParallelListView()));}Widget _getParallelListView() {return Row(children: <Widget>[Consumer<ScrollShadowModel>(child: Container(width: widget.leftHandSideColumnWidth,child: _getLeftSideFixedHeaderScrollColumn(),),builder: (context, scrollShadowModel, child) {return Material(child: child,elevation: _getElevation(scrollShadowModel.horizontalOffset),);},),Expanded(child: SingleChildScrollView(controller: _rightHorizontalScrollController,child: Container(child: _getRightSideHeaderScrollColumn(),width: widget.rightHandSideColumnWidth,),scrollDirection: Axis.horizontal,),)],);}Widget _getLeftSideFixedHeaderScrollColumn() {if (widget.isFixedHeader) {return Column(children: <Widget>[Consumer<ScrollShadowModel>(child: widget.headerWidgets[0],builder: (context, scrollShadowModel, child) {return Material(child: child,elevation: _getElevation(scrollShadowModel.verticalOffset),);},),widget.rowSeparatorWidget,Expanded(child: _getScrollColumn(_getLeftHandSideListView(),this._leftHandSideListViewScrollController)),],);} else {return _getScrollColumn(_getLeftHandSideListView(),this._leftHandSideListViewScrollController);}}Widget _getRightSideHeaderScrollColumn() {if (widget.isFixedHeader) {List<Widget> widgetList = List<Widget>();//headerswidgetList.add(Consumer<ScrollShadowModel>(builder: (context, scrollShadowModel, child) {return Material(child: child,elevation: _getElevation(scrollShadowModel.verticalOffset));},child: Row(children: widget.headerWidgets.sublist(1))));widgetList.add(widget.rowSeparatorWidget,);//ListViewwidgetList.add(Expanded(child: _getScrollColumn(_getRightHandSideListView(),this._rightHandSideListViewScrollController),));return Column(children: widgetList,);} else {return _getScrollColumn(_getRightHandSideListView(),this._rightHandSideListViewScrollController);}}Widget _getScrollColumn(Widget child, ScrollController scrollController) {return NotificationListener<ScrollNotification>(child: child,onNotification: (ScrollNotification scrollInfo) {_syncScroller.processNotification(scrollInfo, scrollController);return false;},);}Widget _getRightHandSideListView() {return _getListView(_rightHandSideListViewScrollController,widget.rightSideItemBuilder,widget.itemCount,widget.rightSideChildren);}Widget _getLeftHandSideListView() {return _getListView(_leftHandSideListViewScrollController,widget.leftSideItemBuilder, widget.itemCount, widget.leftSideChildren);}Widget _getListView(ScrollController scrollController,IndexedWidgetBuilder indexedWidgetBuilder, int itemCount,[List<Widget> children]) {if (indexedWidgetBuilder != null) {return ListView.separated(controller: scrollController,itemBuilder: indexedWidgetBuilder,itemCount: itemCount,separatorBuilder: (context, index) {return widget.rowSeparatorWidget;},);} else {return ListView(controller: scrollController,children: children,);}}double _getElevation(double offset) {return 0.0;}
}class _SyncScrollControllerManager {List<ScrollController> _registeredScrollControllers =new List<ScrollController>();ScrollController _scrollingController;bool _scrollingActive = false;void registerScrollController(ScrollController controller) {_registeredScrollControllers.add(controller);}void unregisterScrollController(ScrollController controller) {_registeredScrollControllers.remove(controller);}void processNotification(ScrollNotification notification, ScrollController sender) {if (notification is ScrollStartNotification && !_scrollingActive) {_scrollingController = sender;_scrollingActive = true;return;}if (identical(sender, _scrollingController) && _scrollingActive) {if (notification is ScrollEndNotification) {_scrollingController = null;_scrollingActive = false;return;}if (notification is ScrollUpdateNotification) {_registeredScrollControllers.forEach((controller) {if (!identical(_scrollingController, controller)) {if (controller.hasClients) {controller.jumpTo(_scrollingController.offset);} else {}}});return;}}}
}

在Flutter中,为了支持下拉和上拉功能,我们可以使用SmartRefresher组件或者RefreshIndicator来实现,不过,我试了下,效果并不好,至于为什么,大家可以自己试一下。最后,为了满足需求,我使用可一个比较投机的方式,即使用ScrollController。我们知道,任何滚动监听都可以使用ScrollController来实现。如果要获取ScrollController距离坐标原点的位置可以使用如下的方式进行获取。

scrollController.position.pixels

由于ScrollController一般都会配合一个列表组件来使用,所以,我们可以使用下面的方法来获取列表底部距离坐标原点的值。

scrollController.position.maxScrollExtent

基于这个原理,我们可以在列表滚动到列表底部之前,请求下一页的数据,即我们可以进行如下的判断。

if(_leftHandSideListViewScrollController.position.pixels -_leftHandSideListViewScrollController.position.maxScrollExtent>= 90 ) {if(finishLoading) {this.widget.loadMore();finishLoading = false;}}

上面的数值90是可以修改的。并且,如果当前是正在上拉状态,是不可以再请求的,因此我们需要设置一个状态标志。然后,在需要使用时,传入对应的参数即可。

double length = Strings.totalItemName.length * width;return Container(color: Colors.white,child: HorizontalDataTable(key: this.widget.globalKey,enablePullUp: true,      loadMore: () {this.widget.loadMore();              //关键代码,上拉加载更多},leftHandSideColumnWidth: 130,rightHandSideColumnWidth: length,isFixedHeader: true,headerWidgets: _getTitleWidget(),leftSideItemBuilder: _generateFirstColumnRow,rightSideItemBuilder: _generateRightHandSideColumnRow,itemCount: widget.datas.length,rowSeparatorWidget: Divider(color: Colors.black54,height: 1.0,thickness: 0.0,),),height: MediaQuery.of(context).size.height,);

参考:仿同花顺自选股列表

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

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

相关文章

0-1背包问题基础概念

一、问题描述 给定一个容量为 W 的背包和 n 个物品。每个物品有一个重量 w[i] 和价值 v[i]。每个物品只能选或不选&#xff08;即“0-1”&#xff09;&#xff0c;求在不超过背包容量的前提下&#xff0c;所能获得的最大总价值。 输入&#xff1a; 背包容量 W&#xff08;in…

使用 Semantic Kernel 快速对接国产大模型实战指南(DeepSeek/Qwen/GLM)

文章目录 使用 Semantic Kernel 快速对接国产大模型实战指南&#xff08;DeepSeek/Qwen/GLM&#xff09;一、引言二、环境准备2.1 开发环境2.2 模型服务配置 三、核心代码实现3.1 会话代码封装3.2 CurModelContext封装3.3 DeepSeek对接示例3.4 Qwen对接示例3.5 GLM对接示例 四、…

Ai时代,运维人如何转型

在AI时代,传统运维向智能运维(AIOps)的转型需要系统性重塑,以下是深度拆解的转型路线图和关键实施要素: 一、认知升级范式转变 1. 演进路线模型(三阶段) 被动响应阶段:人工巡检(→监控覆盖率<30%)主动防御阶段:规则引擎(→告警准确率70%~85%)预测自治阶段:深…

windows鼠标按键自定义任意设置

因为用惯了Linux的鼠标中键的复制黏贴&#xff0c;发现windows下有完全可以实现类似自定义功能的软件&#xff0c;推荐一下&#xff1a; X Mouse Button Control。 免费版足够好用。 软件简介&#xff1a; X Mouse Button Control是一款专业的重新映射鼠标按钮的软件工具&…

怎么看户型好不好?

看房型好不好可从以下方面判断&#xff1a; 空间布局 方正性&#xff1a;户型方正为佳 &#xff0c;此时进深与开间比例在1:1.5左右。方正户型空间利用率高&#xff0c;无采光死角。如手枪型、锯齿型等异形户型&#xff0c;易有拐角、长过道&#xff0c;空间浪费大。动静分区…

基于WOA鲸鱼优化TCN-BiGRU注意力机制网络模型的时间序列预测算法matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 (完整程序运行后无水印) 2.算法运行软件版本 matlab2022a/matlab2024b 3.部分核心程序 &#xff08;完整版代码包含详细中文注释和操作步骤视频…

JAVA简单走进AI世界~Spring AI

1、背景 现代 AI 正以前所未有的速度改变着世界。它是基于复杂算法和强大计算能力的技术体系,涵盖了机器学习、深度学习、自然语言处理等多个领域。 在日常生活中,AI 广泛应用于智能语音助手、图像识别、推荐系统等。比如,智能音箱能理解并回应语音指令,为人们提供信息查…

stm32wb55rg (4) 启用usart串口

code repo: 访问gitee 上节课成功点亮了LED&#xff0c;这次来把usart 用起来&#xff0c;毕竟有交互才是系统。 技术准备 首先查看手册&#xff0c;发现mcu有1个usart和1个 lpuart。 usart 的使用需要两个pin&#xff0c;一个接收一个发送。继续查看pin and ball definition…

Python生活手册-NumPy数组创建:从快递分拣到智能家居的数据容器

一、快递分拣系统&#xff08;列表/元组转换&#xff09; 1. 快递单号录入&#xff08;np.array()&#xff09; import numpy as np快递单号入库系统 快递单列表 ["SF123", "JD456", "EMS789"] 快递数组 np.array(快递单列表) print(f"…

数据库-数据类型,表的约束和基本查询操作

一、数值类型 1. 整数类型 类型字节有符号范围无符号范围操作注意事项TINYINT1-128 ~ 1270 ~ 255默认有符号&#xff0c;UNSIGNED定义无符号SMALLINT2-32768 ~ 327670 ~ 65535无符号需显式声明INT4-2^31 ~ 2^31-10 ~ 2^32-1推荐优先使用INTBIGINT8-2^63 ~ 2^63-10 ~ 2^64-1存…

【C语言编译】编译原理和详细过程

文章目录 1. C 语言编译原理和详细过程1.1 预处理阶段1.2 编译阶段1.3 汇编阶段1.4 链接阶段 2. 疑问点解析2.1 三地址码是什么&#xff1f;有什么作用2.2 符号表是什么&#xff1f;有何作用2.3 重定位的含义与作用2.3 符号表和重定位在整个编译过程中的作用2.4 动态链接库.so和…

游戏引擎学习第251天:完成调试层级结构

运行游戏&#xff0c;查看当前调试层级的状态。 我们正在直播中开发一个完整的游戏&#xff0c;目前正进行调试代码的整理和清理工作。现在我们直接进入正题&#xff0c;虽然还不完全确定今天要完成哪些具体内容&#xff0c;但有几个明确的目标&#xff1a; 首先&#xff0c;…

关于Python:9. 深入理解Python运行机制

一、Python内存管理&#xff08;引用计数、垃圾回收&#xff09; Python&#xff08;CPython&#xff09;采用的是&#xff1a; “引用计数为主&#xff0c;垃圾回收为辅” 的内存管理机制。 也就是说&#xff1a; 引用计数机制&#xff1a;负责大部分内存释放&#xff0c;简…

【STM32单片机】#13 RTC实时时钟

主要参考学习资料&#xff1a; B站江协科技 STM32入门教程-2023版 细致讲解 中文字幕 开发资料下载链接&#xff1a;https://pan.baidu.com/s/1h_UjuQKDX9IpP-U1Effbsw?pwddspb 单片机套装&#xff1a;STM32F103C8T6开发板单片机C6T6核心板 实验板最小系统板套件科协 目录 Uni…

SecureCRT 使用指南:安装、设置与高效操作

目录 一、SecureCRT 简介 1.1 什么是 SecureCRT&#xff1f; 1.2 核心功能亮点 1.3 软件特点 二、SecureCRT 安装与激活 2.1 安装步骤&#xff08;Windows 系统&#xff09; 2.2 激活与破解&#xff08;仅供学习参考&#xff09; 三、基础配置与优化 3.1 界面与编码设…

3.5/Q1,GBD数据库最新一区文章解读

文章题目&#xff1a;Global burden of low vision and blindness due to age-related macular degeneration from 1990 to 2021 and projections for 2050 DOI&#xff1a;10.1186/s12889-024-21047-x 中文标题&#xff1a;1990年至2021年因年龄相关性黄斑变性导致的低视力和失…

【Hive入门】Hive安全管理与权限控制:基于SQL标准的授权GRANT REVOKE深度解析

目录 引言 1 Hive权限模型概述 2 SQL标准授权基础 2.1 核心概念解析 2.2 授权模型工作流程 3 GRANT/REVOKE语法详解 3.1 基础授权语法 3.2 权限回收语法 3.3 参数说明 4 授权场景 4.1 基础授权示例 4.2 列级权限控制 4.3 视图权限管理 5 权限查询与验证 5.1 查看…

无缝监控:利用 AWS X-Ray 增强 S3 跨账户复制的可见性

您准备好提升您的云和 DevOps 技能了吗? 🐥《云原生devops》专门为您打造,我们精心打造的 30 篇文章库,这些文章涵盖了 Azure、AWS 和 DevOps 方法论的众多重要主题。无论您是希望精进专业知识的资深专业人士,还是渴望学习相关知识的新手,这套资源库都能满足您的需求。 …

Python Cookbook-7.2 使用 pickle 和 cPickle 模块序列化数据

任务 你想以某种可以接受的速度序列化和重建Python 数据结构&#xff0c;这些数据既包括基本Python 对象也包括类和实例。 解决方案 如果你不想假设你的数据完全由基本 Python 对象组成&#xff0c;或者需要在不同的 Python 版本之间移植&#xff0c;再或者需要将序列化后的…

2025.5.5总结

今日感悟&#xff1a;这假期就这样结束了&#xff0c;玩了一次滑板&#xff0c;打扫了一次租房&#xff0c;出去逛了一次街&#xff0c;看完了一本书&#xff0c;追了一部剧。既没有家人&#xff0c;也没有能一同畅饮的同学&#xff0c;更没有对象&#xff0c;显得确实有些孤独…