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.pswp.cn/web/78589.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/78589.shtml
英文地址,請注明出處:http://en.pswp.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;顯得確實有些孤獨…