Flutter?ScrollController滾動(dòng)監(jiān)聽及控制示例詳解
ScrollController
ScrollController
構(gòu)造函數(shù)如下:
ScrollController({ double initialScrollOffset = 0.0, //初始滾動(dòng)位置 this.keepScrollOffset = true,//是否保存滾動(dòng)位置 ... })
我們介紹一下ScrollController常用的屬性和方法:
- offset:可滾動(dòng)組件當(dāng)前的滾動(dòng)位置。
- jumpTo(double offset)、animateTo(double offset,...):這兩個(gè)方法用于跳轉(zhuǎn)到指定的位置,它們不同之處在于,后者在跳轉(zhuǎn)時(shí)會(huì)執(zhí)行一個(gè)動(dòng)畫,而前者不會(huì)。
ScrollController還有一些屬性和方法,我們將在后面原理部分解釋。
滾動(dòng)監(jiān)聽
ScrollController間接繼承自Listenable,我們可以根據(jù)ScrollController來監(jiān)聽滾動(dòng)事件,如:
controller.addListener(()=>print(controller.offset))
滾動(dòng)監(jiān)聽示例
我們創(chuàng)建一個(gè)ListView
,當(dāng)滾動(dòng)位置發(fā)生變化時(shí),我們先打印出當(dāng)前滾動(dòng)位置,然后判斷當(dāng)前位置是否超過1000像素,如果超過則在屏幕右下角顯示一個(gè)“返回頂部”的按鈕,該按鈕點(diǎn)擊后可以使ListView恢復(fù)到初始位置;如果沒有超過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(); //監(jiān)聽滾動(dòng)事件,打印滾動(dòng)位置 _controller.addListener(() { //打印滾動(dòng)位置 print(_controller.offset); if (_controller.offset < 1000 && showToTopBtn) { setState(() { showToTopBtn = false; }); } else if (_controller.offset >= 1000 && showToTopBtn == false) { setState(() { showToTopBtn = true; }); } }); } @override void dispose() { //為了避免內(nèi)存泄露,需要調(diào)用_controller.dispose _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: getAppBar('滾動(dòng)監(jiān)聽以及控制'), body: _buildScollbar(), floatingActionButton: showToTopBtn==false?null:FloatingActionButton( onPressed: (){ //返回到頂部時(shí)候執(zhí)行動(dòng)畫 _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'),); }) ); } }
運(yùn)行效果:
由于列表項(xiàng)高度為 44 像素,當(dāng)滑動(dòng)到第 20+ 個(gè)列表項(xiàng)后,右下角 “返回頂部” 按鈕會(huì)顯示,點(diǎn)擊該按鈕,ListView 會(huì)在返回頂部的過程中執(zhí)行一個(gè)滾動(dòng)動(dòng)畫,動(dòng)畫時(shí)間是 200 毫秒,動(dòng)畫曲線是 Curves.ease
。
滾動(dòng)位置恢復(fù)
PageStorage
是一個(gè)用于保存頁面(路由)相關(guān)數(shù)據(jù)的組件,它并不會(huì)影響子樹的UI外觀,其實(shí),PageStorage
是一個(gè)功能型組件,它擁有一個(gè)存儲(chǔ)桶(bucket),子樹中的Widget可以通過指定不同的PageStorageKey
來存儲(chǔ)各自的數(shù)據(jù)或狀態(tài)。
每次滾動(dòng)結(jié)束,可滾動(dòng)組件都會(huì)將滾動(dòng)位置offset
存儲(chǔ)到PageStorage
中,當(dāng)可滾動(dòng)組件重新創(chuàng)建時(shí)再恢復(fù)。如果ScrollController.keepScrollOffset
為false,則滾動(dòng)位置將不會(huì)被存儲(chǔ),可滾動(dòng)組件重新創(chuàng)建時(shí)會(huì)使用ScrollController.initialScrollOffset
;ScrollController.keepScrollOffset
為true時(shí),可滾動(dòng)組件在第一次創(chuàng)建時(shí),會(huì)滾動(dòng)到initialScrollOffset
處,因?yàn)檫@時(shí)還沒有存儲(chǔ)過滾動(dòng)位置。在接下來的滾動(dòng)中就會(huì)存儲(chǔ)、恢復(fù)滾動(dòng)位置,而initialScrollOffset
會(huì)被忽略。
當(dāng)一個(gè)路由中包含多個(gè)可滾動(dòng)組件時(shí),如果你發(fā)現(xiàn)在進(jìn)行一些跳轉(zhuǎn)或切換操作后,滾動(dòng)位置不能正確恢復(fù),這時(shí)你可以通過顯式指定PageStorageKey
來分別跟蹤不同的可滾動(dòng)組件的位置,如:
ListView(key: PageStorageKey(1), ... ); ... ListView(key: PageStorageKey(2), ... );
不同的PageStorageKey
,需要不同的值,這樣才可以為不同可滾動(dòng)組件保存其滾動(dòng)位置。
注意:一個(gè)路由中包含多個(gè)可滾動(dòng)組件時(shí),如果要分別跟蹤它們的滾動(dòng)位置,并非一定就得給他們分別提供PageStorageKey。這是因?yàn)镾crollable本身是一個(gè)StatefulWidget,它的狀態(tài)中也會(huì)保存當(dāng)前滾動(dòng)位置,所以,只要可滾動(dòng)組件本身沒有被從樹上detach掉,那么其State就不會(huì)銷毀(dispose),滾動(dòng)位置就不會(huì)丟失。只有當(dāng)Widget發(fā)生結(jié)構(gòu)變化,導(dǎo)致可滾動(dòng)組件的State銷毀或重新構(gòu)建時(shí)才會(huì)丟失狀態(tài),這種情況就需要顯式指定PageStorageKey,通過PageStorage來存儲(chǔ)滾動(dòng)位置,一個(gè)典型的場景是在使用TabBarView時(shí),在Tab發(fā)生切換時(shí),Tab頁中的可滾動(dòng)組件的State就會(huì)銷毀,這時(shí)如果想恢復(fù)滾動(dòng)位置就需要指定
ScrollPosition
ScrollPosition
是用來保存可滾動(dòng)組件的滾動(dòng)位置的。一個(gè)ScrollController
對象可以同時(shí)被多個(gè)可滾動(dòng)組件使用,ScrollController
會(huì)為每一個(gè)可滾動(dòng)組件創(chuàng)建一個(gè)ScrollPosition
對象,這些ScrollPosition
保存在ScrollController
的positions
屬性中(List<ScrollPosition>)。ScrollPosition
是真正保存滑動(dòng)位置信息的對象,offset
只是一個(gè)便捷屬性:
double get offset => position.pixels;
一個(gè)ScrollController
雖然可以對應(yīng)多個(gè)可滾動(dòng)組件,但是有一些操作,如讀取滾動(dòng)位置offset,則需要一對一!但是我們?nèi)匀豢梢栽谝粚Χ嗟那闆r下,通過其它方法讀取滾動(dòng)位置,舉個(gè)例子,假設(shè)一個(gè)ScrollController同時(shí)被兩個(gè)可滾動(dòng)組件使用,那么我們可以通過如下方式分別讀取他們的滾動(dòng)位置:
... controller.positions.elementAt(0).pixels controller.positions.elementAt(1).pixels ...
我們可以通過controller.positions.length
來確定controller
被幾個(gè)可滾動(dòng)組件使用。
ScrollPosition的方法
ScrollPosition
有兩個(gè)常用方法:animateTo()
和 jumpTo()
,它們是真正來控制跳轉(zhuǎn)滾動(dòng)位置的方法,ScrollController
的這兩個(gè)同名方法,內(nèi)部最終都會(huì)調(diào)用ScrollPosition
的。
ScrollController控制原理
我們來介紹一下ScrollController
的另外三個(gè)方法:
ScrollPosition createScrollPosition( ScrollPhysics physics, ScrollContext context, ScrollPosition oldPosition); void attach(ScrollPosition position) ; void detach(ScrollPosition position) ;
當(dāng)ScrollController
和可滾動(dòng)組件關(guān)聯(lián)時(shí),可滾動(dòng)組件首先會(huì)調(diào)用ScrollController
的createScrollPosition()
方法來創(chuàng)建一個(gè)ScrollPosition
來存儲(chǔ)滾動(dòng)位置信息,接著,可滾動(dòng)組件會(huì)調(diào)用attach()方法,將創(chuàng)建的ScrollPosition
添加到ScrollController
的positions
屬性中,這一步稱為“注冊位置”,只有注冊后animateTo()
和 jumpTo()
才可以被調(diào)用。
當(dāng)可滾動(dòng)組件銷毀時(shí),會(huì)調(diào)用ScrollController
的detach()
方法,將其ScrollPosition
對象從ScrollController
的positions
屬性中移除,這一步稱為“注銷位置”,注銷后animateTo()
和 jumpTo()
將不能再被調(diào)用。
需要注意的是,ScrollController
的animateTo()
和 jumpTo()
內(nèi)部會(huì)調(diào)用所有ScrollPosition
的animateTo()
和 jumpTo()
,以實(shí)現(xiàn)所有和該ScrollController
關(guān)聯(lián)的可滾動(dòng)組件都滾動(dòng)到指定的位置。
滾動(dòng)監(jiān)聽
下面,我們監(jiān)聽ListView
的滾動(dòng)通知,然后顯示當(dāng)前滾動(dòng)進(jìn)度百分比:
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("滾動(dòng)監(jiān)聽"), body: NotificationListener<ScrollNotification>( onNotification: (ScrollNotification notification){ double progress = notification.metrics.pixels/notification.metrics.maxScrollExtent; //重新構(gòu)建 setState(() { _progress ='${(progress*100).toInt()}%'; }); print("BottomEdge: ${notification.metrics.extentAfter == 0}"); return false; //return true; //放開此行注釋后,進(jìn)度條將失效 }, 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, ) ], ), ), ); } }
運(yùn)行結(jié)果:
在接收到滾動(dòng)事件時(shí),參數(shù)類型為ScrollNotification
,它包括一個(gè)metrics
屬性,它的類型是ScrollMetrics
,該屬性包含當(dāng)前ViewPort
及滾動(dòng)位置等信息:
pixels
:當(dāng)前滾動(dòng)位置。maxScrollExtent
:最大可滾動(dòng)長度。extentBefore
:滑出ViewPort
頂部的長度;此示例中相當(dāng)于頂部滑出屏幕上方的列表長度。extentInside
:ViewPort
內(nèi)部長度;此示例中屏幕顯示的列表部分的長度。extentAfter
:列表中未滑入ViewPort部分的長度;此示例中列表底部未顯示到屏幕范圍部分的長度。atEdge
:是否滑到了可滾動(dòng)組件的邊界(此示例中相當(dāng)于列表頂或底部)。
ScrollMetrics
還有一些其它屬性,可以自行查閱API文檔。
詳細(xì)的官方文檔地址:api.flutter.dev/flutter/wid…
官方文檔解釋 控制可滾動(dòng)小部件。
滾動(dòng)控制器通常作為成員變量存儲(chǔ)在State對象中,并在每個(gè)State.build中重用。單個(gè)滾動(dòng)控制器可用于控制多個(gè)可滾動(dòng)小部件,但有些操作(如讀取滾動(dòng)偏移量)要求控制器與單個(gè)可滾動(dòng)小部件一起使用。
滾動(dòng)控制器創(chuàng)建一個(gè)ScrollPosition來管理特定于單個(gè)可滾動(dòng)小部件的狀態(tài)。要使用自定義的ScrollPosition,子類化ScrollController并重寫createScrollPosition。
ScrollController是一個(gè)Listenable。當(dāng)附加的任何scrollposition通知它們的偵聽器時(shí)(即當(dāng)它們中的任何一個(gè)滾動(dòng)時(shí)),它會(huì)通知它的偵聽器。當(dāng)附加的scrollposition列表發(fā)生變化時(shí),它不會(huì)通知偵聽器。
通常與ListView, GridView, CustomScrollView一起使用。
參見: ListView, GridView, CustomScrollView,它們可以由ScrollController控制。 Scrollable,它是較低層的小部件,用于創(chuàng)建ScrollPosition對象和ScrollController對象并將它們關(guān)聯(lián)起來。 PageController,它是控制PageView的一個(gè)類似對象。 ScrollPosition,用于管理單個(gè)滾動(dòng)小部件的滾動(dòng)偏移量。 ScrollNotification和NotificationListener,它們可用于監(jiān)視滾動(dòng)位置,而無需使用ScrollController。
以上就是Flutter ScrollController滾動(dòng)監(jiān)聽及控制示例詳解的詳細(xì)內(nèi)容,更多關(guān)于Flutter ScrollController滾動(dòng)監(jiān)聽的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Swift和Objective-C 混編注意事項(xiàng)
這篇文章主要介紹了Swift和Objective-C 混編注意事項(xiàng)的相關(guān)資料,需要的朋友可以參考下2016-10-10簡單好用的iOS導(dǎo)航欄封裝.runtime屬性控制實(shí)例代碼
這篇文章主要給大家介紹了簡單好用的iOS導(dǎo)航欄封裝.runtime屬性控制的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-10-10iOS 水波紋動(dòng)畫的實(shí)現(xiàn)效果
本篇文章主要介紹了iOS 水波紋的實(shí)現(xiàn)的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-01-01