Flutter ScrollController滾動監聽及控制示例詳解
ScrollController
ScrollController
構造函數如下:
ScrollController({ double initialScrollOffset = 0.0, //初始滾動位置 this.keepScrollOffset = true,//是否保存滾動位置 ... })
我們介紹一下ScrollController常用的屬性和方法:
- offset:可滾動組件當前的滾動位置。
- jumpTo(double offset)、animateTo(double offset,…):這兩個方法用於跳轉到指定的位置,它們不同之處在於,後者在跳轉時會執行一個動畫,而前者不會。
ScrollController還有一些屬性和方法,我們將在後面原理部分解釋。
滾動監聽
ScrollController間接繼承自Listenable,我們可以根據ScrollController來監聽滾動事件,如:
controller.addListener(()=>print(controller.offset))
滾動監聽示例
我們創建一個ListView
,當滾動位置發生變化時,我們先打印出當前滾動位置,然後判斷當前位置是否超過1000像素,如果超過則在屏幕右下角顯示一個“返回頂部”的按鈕,該按鈕點擊後可以使ListView恢復到初始位置;如果沒有超過1000像素,則隱藏“返回頂部”按鈕。代碼如下:
import 'package:demo202112/utils/common_appbar.dart'; import 'package:flutter/material.dart'; /// @Author wywinstonwy /// @Date 2022/1/19 10:46 下午 /// @Description: class MyScrollController extends StatefulWidget { const MyScrollController({Key? key}) : super(key: key); @override _MyScrollControllerState createState() => _MyScrollControllerState(); } class _MyScrollControllerState extends State<MyScrollController> { final ScrollController _controller = ScrollController(); bool showToTopBtn = false; //是否顯示“返回到頂部”按鈕 @override void initState() { // TODO: implement initState super.initState(); //監聽滾動事件,打印滾動位置 _controller.addListener(() { //打印滾動位置 print(_controller.offset); if (_controller.offset < 1000 && showToTopBtn) { setState(() { showToTopBtn = false; }); } else if (_controller.offset >= 1000 && showToTopBtn == false) { setState(() { showToTopBtn = true; }); } }); } @override void dispose() { //為瞭避免內存泄露,需要調用_controller.dispose _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: getAppBar('滾動監聽以及控制'), body: _buildScollbar(), floatingActionButton: showToTopBtn==false?null:FloatingActionButton( onPressed: (){ //返回到頂部時候執行動畫 _controller.animateTo(0, duration: const Duration(milliseconds: 200), curve: Curves.easeIn); }, child: const Icon(Icons.arrow_upward),), ); } _buildScollbar(){ return Scrollbar( child: ListView.builder( controller: _controller, itemCount: 100, itemExtent: 44, itemBuilder: (context,index){ return ListTile(title: Text('$index'),); }) ); } }
運行效果:
由於列表項高度為 44 像素,當滑動到第 20+ 個列表項後,右下角 “返回頂部” 按鈕會顯示,點擊該按鈕,ListView 會在返回頂部的過程中執行一個滾動動畫,動畫時間是 200 毫秒,動畫曲線是 Curves.ease
。
滾動位置恢復
PageStorage
是一個用於保存頁面(路由)相關數據的組件,它並不會影響子樹的UI外觀,其實,PageStorage
是一個功能型組件,它擁有一個存儲桶(bucket),子樹中的Widget可以通過指定不同的PageStorageKey
來存儲各自的數據或狀態。
每次滾動結束,可滾動組件都會將滾動位置offset
存儲到PageStorage
中,當可滾動組件重新創建時再恢復。如果ScrollController.keepScrollOffset
為false,則滾動位置將不會被存儲,可滾動組件重新創建時會使用ScrollController.initialScrollOffset
;ScrollController.keepScrollOffset
為true時,可滾動組件在第一次創建時,會滾動到initialScrollOffset
處,因為這時還沒有存儲過滾動位置。在接下來的滾動中就會存儲、恢復滾動位置,而initialScrollOffset
會被忽略。
當一個路由中包含多個可滾動組件時,如果你發現在進行一些跳轉或切換操作後,滾動位置不能正確恢復,這時你可以通過顯式指定PageStorageKey
來分別跟蹤不同的可滾動組件的位置,如:
ListView(key: PageStorageKey(1), ... ); ... ListView(key: PageStorageKey(2), ... );
不同的PageStorageKey
,需要不同的值,這樣才可以為不同可滾動組件保存其滾動位置。
註意:一個路由中包含多個可滾動組件時,如果要分別跟蹤它們的滾動位置,並非一定就得給他們分別提供PageStorageKey。這是因為Scrollable本身是一個StatefulWidget,它的狀態中也會保存當前滾動位置,所以,隻要可滾動組件本身沒有被從樹上detach掉,那麼其State就不會銷毀(dispose),滾動位置就不會丟失。隻有當Widget發生結構變化,導致可滾動組件的State銷毀或重新構建時才會丟失狀態,這種情況就需要顯式指定PageStorageKey,通過PageStorage來存儲滾動位置,一個典型的場景是在使用TabBarView時,在Tab發生切換時,Tab頁中的可滾動組件的State就會銷毀,這時如果想恢復滾動位置就需要指定
ScrollPosition
ScrollPosition
是用來保存可滾動組件的滾動位置的。一個ScrollController
對象可以同時被多個可滾動組件使用,ScrollController
會為每一個可滾動組件創建一個ScrollPosition
對象,這些ScrollPosition
保存在ScrollController
的positions
屬性中(List<ScrollPosition>)。ScrollPosition
是真正保存滑動位置信息的對象,offset
隻是一個便捷屬性:
double get offset => position.pixels;
一個ScrollController
雖然可以對應多個可滾動組件,但是有一些操作,如讀取滾動位置offset,則需要一對一!但是我們仍然可以在一對多的情況下,通過其它方法讀取滾動位置,舉個例子,假設一個ScrollController同時被兩個可滾動組件使用,那麼我們可以通過如下方式分別讀取他們的滾動位置:
... controller.positions.elementAt(0).pixels controller.positions.elementAt(1).pixels ...
我們可以通過controller.positions.length
來確定controller
被幾個可滾動組件使用。
ScrollPosition的方法
ScrollPosition
有兩個常用方法:animateTo()
和 jumpTo()
,它們是真正來控制跳轉滾動位置的方法,ScrollController
的這兩個同名方法,內部最終都會調用ScrollPosition
的。
ScrollController控制原理
我們來介紹一下ScrollController
的另外三個方法:
ScrollPosition createScrollPosition( ScrollPhysics physics, ScrollContext context, ScrollPosition oldPosition); void attach(ScrollPosition position) ; void detach(ScrollPosition position) ;
當ScrollController
和可滾動組件關聯時,可滾動組件首先會調用ScrollController
的createScrollPosition()
方法來創建一個ScrollPosition
來存儲滾動位置信息,接著,可滾動組件會調用attach()方法,將創建的ScrollPosition
添加到ScrollController
的positions
屬性中,這一步稱為“註冊位置”,隻有註冊後animateTo()
和 jumpTo()
才可以被調用。
當可滾動組件銷毀時,會調用ScrollController
的detach()
方法,將其ScrollPosition
對象從ScrollController
的positions
屬性中移除,這一步稱為“註銷位置”,註銷後animateTo()
和 jumpTo()
將不能再被調用。
需要註意的是,ScrollController
的animateTo()
和 jumpTo()
內部會調用所有ScrollPosition
的animateTo()
和 jumpTo()
,以實現所有和該ScrollController
關聯的可滾動組件都滾動到指定的位置。
滾動監聽
下面,我們監聽ListView
的滾動通知,然後顯示當前滾動進度百分比:
import 'package:demo202112/utils/common_appbar.dart'; import 'package:flutter/material.dart'; /// @Author wywinstonwy /// @Date 2022/1/19 11:21 下午 /// @Description: class MyScrollcontroller2 extends StatefulWidget { const MyScrollcontroller2({Key? key}) : super(key: key); @override _MyScrollcontroller2State createState() => _MyScrollcontroller2State(); } class _MyScrollcontroller2State extends State<MyScrollcontroller2> { String _progress ='0%'; @override Widget build(BuildContext context) { return Scaffold( appBar: getAppBar("滾動監聽"), body: NotificationListener<ScrollNotification>( onNotification: (ScrollNotification notification){ double progress = notification.metrics.pixels/notification.metrics.maxScrollExtent; //重新構建 setState(() { _progress ='${(progress*100).toInt()}%'; }); print("BottomEdge: ${notification.metrics.extentAfter == 0}"); return false; //return true; //放開此行註釋後,進度條將失效 }, child: Stack( alignment: Alignment.center, children: [ ListView.builder( itemCount: 100, itemExtent: 50, itemBuilder: (context,index){ return ListTile(title: Text('$index'),); }), CircleAvatar( radius: 30, child: Text(_progress), backgroundColor: Colors.black54, ) ], ), ), ); } }
運行結果:
在接收到滾動事件時,參數類型為ScrollNotification
,它包括一個metrics
屬性,它的類型是ScrollMetrics
,該屬性包含當前ViewPort
及滾動位置等信息:
pixels
:當前滾動位置。maxScrollExtent
:最大可滾動長度。extentBefore
:滑出ViewPort
頂部的長度;此示例中相當於頂部滑出屏幕上方的列表長度。extentInside
:ViewPort
內部長度;此示例中屏幕顯示的列表部分的長度。extentAfter
:列表中未滑入ViewPort部分的長度;此示例中列表底部未顯示到屏幕范圍部分的長度。atEdge
:是否滑到瞭可滾動組件的邊界(此示例中相當於列表頂或底部)。
ScrollMetrics
還有一些其它屬性,可以自行查閱API文檔。
詳細的官方文檔地址:api.flutter.dev/flutter/wid…
官方文檔解釋 控制可滾動小部件。
滾動控制器通常作為成員變量存儲在State對象中,並在每個State.build中重用。單個滾動控制器可用於控制多個可滾動小部件,但有些操作(如讀取滾動偏移量)要求控制器與單個可滾動小部件一起使用。
滾動控制器創建一個ScrollPosition來管理特定於單個可滾動小部件的狀態。要使用自定義的ScrollPosition,子類化ScrollController並重寫createScrollPosition。
ScrollController是一個Listenable。當附加的任何scrollposition通知它們的偵聽器時(即當它們中的任何一個滾動時),它會通知它的偵聽器。當附加的scrollposition列表發生變化時,它不會通知偵聽器。
通常與ListView, GridView, CustomScrollView一起使用。
參見: ListView, GridView, CustomScrollView,它們可以由ScrollController控制。 Scrollable,它是較低層的小部件,用於創建ScrollPosition對象和ScrollController對象並將它們關聯起來。 PageController,它是控制PageView的一個類似對象。 ScrollPosition,用於管理單個滾動小部件的滾動偏移量。 ScrollNotification和NotificationListener,它們可用於監視滾動位置,而無需使用ScrollController。
以上就是Flutter ScrollController滾動監聽及控制示例詳解的詳細內容,更多關於Flutter ScrollController滾動監聽的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- Flutter listview如何實現下拉刷新上拉加載更多功能
- Flutter之可滾動組件實例詳解
- flutter 動手擼一個城市選擇citypicker功能
- flutter實現一個列表下拉抽屜的示例代碼
- Flutter折疊控件使用方法詳解