Flutter?ScrollController滾動監(jiān)聽及控制示例詳解
ScrollController
ScrollController構(gòu)造函數(shù)如下:
ScrollController({
double initialScrollOffset = 0.0, //初始滾動位置
this.keepScrollOffset = true,//是否保存滾動位置
...
})
我們介紹一下ScrollController常用的屬性和方法:
- offset:可滾動組件當(dāng)前的滾動位置。
- jumpTo(double offset)、animateTo(double offset,...):這兩個方法用于跳轉(zhuǎn)到指定的位置,它們不同之處在于,后者在跳轉(zhuǎn)時會執(zhí)行一個動畫,而前者不會。
ScrollController還有一些屬性和方法,我們將在后面原理部分解釋。
滾動監(jiān)聽
ScrollController間接繼承自Listenable,我們可以根據(jù)ScrollController來監(jiān)聽滾動事件,如:
controller.addListener(()=>print(controller.offset))
滾動監(jiān)聽示例
我們創(chuàng)建一個ListView,當(dāng)滾動位置發(fā)生變化時,我們先打印出當(dāng)前滾動位置,然后判斷當(dāng)前位置是否超過1000像素,如果超過則在屏幕右下角顯示一個“返回頂部”的按鈕,該按鈕點擊后可以使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)聽滾動事件,打印滾動位置
_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() {
//為了避免內(nèi)存泄露,需要調(diào)用_controller.dispose
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: getAppBar('滾動監(jiān)聽以及控制'),
body: _buildScollbar(),
floatingActionButton: showToTopBtn==false?null:FloatingActionButton(
onPressed: (){
//返回到頂部時候執(zhí)行動畫
_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)行效果:

由于列表項高度為 44 像素,當(dāng)滑動到第 20+ 個列表項后,右下角 “返回頂部” 按鈕會顯示,點擊該按鈕,ListView 會在返回頂部的過程中執(zhí)行一個滾動動畫,動畫時間是 200 毫秒,動畫曲線是 Curves.ease。
滾動位置恢復(fù)
PageStorage是一個用于保存頁面(路由)相關(guān)數(shù)據(jù)的組件,它并不會影響子樹的UI外觀,其實,PageStorage是一個功能型組件,它擁有一個存儲桶(bucket),子樹中的Widget可以通過指定不同的PageStorageKey來存儲各自的數(shù)據(jù)或狀態(tài)。
每次滾動結(jié)束,可滾動組件都會將滾動位置offset存儲到PageStorage中,當(dāng)可滾動組件重新創(chuàng)建時再恢復(fù)。如果ScrollController.keepScrollOffset為false,則滾動位置將不會被存儲,可滾動組件重新創(chuàng)建時會使用ScrollController.initialScrollOffset;ScrollController.keepScrollOffset為true時,可滾動組件在第一次創(chuàng)建時,會滾動到initialScrollOffset處,因為這時還沒有存儲過滾動位置。在接下來的滾動中就會存儲、恢復(fù)滾動位置,而initialScrollOffset會被忽略。
當(dāng)一個路由中包含多個可滾動組件時,如果你發(fā)現(xiàn)在進(jìn)行一些跳轉(zhuǎn)或切換操作后,滾動位置不能正確恢復(fù),這時你可以通過顯式指定PageStorageKey來分別跟蹤不同的可滾動組件的位置,如:
ListView(key: PageStorageKey(1), ... ); ... ListView(key: PageStorageKey(2), ... );
不同的PageStorageKey,需要不同的值,這樣才可以為不同可滾動組件保存其滾動位置。
注意:一個路由中包含多個可滾動組件時,如果要分別跟蹤它們的滾動位置,并非一定就得給他們分別提供PageStorageKey。這是因為Scrollable本身是一個StatefulWidget,它的狀態(tài)中也會保存當(dāng)前滾動位置,所以,只要可滾動組件本身沒有被從樹上detach掉,那么其State就不會銷毀(dispose),滾動位置就不會丟失。只有當(dāng)Widget發(fā)生結(jié)構(gòu)變化,導(dǎo)致可滾動組件的State銷毀或重新構(gòu)建時才會丟失狀態(tài),這種情況就需要顯式指定PageStorageKey,通過PageStorage來存儲滾動位置,一個典型的場景是在使用TabBarView時,在Tab發(fā)生切換時,Tab頁中的可滾動組件的State就會銷毀,這時如果想恢復(fù)滾動位置就需要指定
ScrollPosition
ScrollPosition是用來保存可滾動組件的滾動位置的。一個ScrollController對象可以同時被多個可滾動組件使用,ScrollController會為每一個可滾動組件創(chuàng)建一個ScrollPosition對象,這些ScrollPosition保存在ScrollController的positions屬性中(List<ScrollPosition>)。ScrollPosition是真正保存滑動位置信息的對象,offset只是一個便捷屬性:
double get offset => position.pixels;
一個ScrollController雖然可以對應(yīng)多個可滾動組件,但是有一些操作,如讀取滾動位置offset,則需要一對一!但是我們?nèi)匀豢梢栽谝粚Χ嗟那闆r下,通過其它方法讀取滾動位置,舉個例子,假設(shè)一個ScrollController同時被兩個可滾動組件使用,那么我們可以通過如下方式分別讀取他們的滾動位置:
... controller.positions.elementAt(0).pixels controller.positions.elementAt(1).pixels ...
我們可以通過controller.positions.length來確定controller被幾個可滾動組件使用。
ScrollPosition的方法
ScrollPosition有兩個常用方法:animateTo() 和 jumpTo(),它們是真正來控制跳轉(zhuǎn)滾動位置的方法,ScrollController的這兩個同名方法,內(nèi)部最終都會調(diào)用ScrollPosition的。
ScrollController控制原理
我們來介紹一下ScrollController的另外三個方法:
ScrollPosition createScrollPosition(
ScrollPhysics physics,
ScrollContext context,
ScrollPosition oldPosition);
void attach(ScrollPosition position) ;
void detach(ScrollPosition position) ;
當(dāng)ScrollController和可滾動組件關(guān)聯(lián)時,可滾動組件首先會調(diào)用ScrollController的createScrollPosition()方法來創(chuàng)建一個ScrollPosition來存儲滾動位置信息,接著,可滾動組件會調(diào)用attach()方法,將創(chuàng)建的ScrollPosition添加到ScrollController的positions屬性中,這一步稱為“注冊位置”,只有注冊后animateTo() 和 jumpTo()才可以被調(diào)用。
當(dāng)可滾動組件銷毀時,會調(diào)用ScrollController的detach()方法,將其ScrollPosition對象從ScrollController的positions屬性中移除,這一步稱為“注銷位置”,注銷后animateTo() 和 jumpTo() 將不能再被調(diào)用。
需要注意的是,ScrollController的animateTo() 和 jumpTo()內(nèi)部會調(diào)用所有ScrollPosition的animateTo() 和 jumpTo(),以實現(xiàn)所有和該ScrollController關(guān)聯(lián)的可滾動組件都滾動到指定的位置。
滾動監(jiān)聽
下面,我們監(jiān)聽ListView的滾動通知,然后顯示當(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("滾動監(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é)果:

在接收到滾動事件時,參數(shù)類型為ScrollNotification,它包括一個metrics屬性,它的類型是ScrollMetrics,該屬性包含當(dāng)前ViewPort及滾動位置等信息:
pixels:當(dāng)前滾動位置。maxScrollExtent:最大可滾動長度。extentBefore:滑出ViewPort頂部的長度;此示例中相當(dāng)于頂部滑出屏幕上方的列表長度。extentInside:ViewPort內(nèi)部長度;此示例中屏幕顯示的列表部分的長度。extentAfter:列表中未滑入ViewPort部分的長度;此示例中列表底部未顯示到屏幕范圍部分的長度。atEdge:是否滑到了可滾動組件的邊界(此示例中相當(dāng)于列表頂或底部)。
ScrollMetrics還有一些其它屬性,可以自行查閱API文檔。
詳細(xì)的官方文檔地址:api.flutter.dev/flutter/wid…

官方文檔解釋 控制可滾動小部件。
滾動控制器通常作為成員變量存儲在State對象中,并在每個State.build中重用。單個滾動控制器可用于控制多個可滾動小部件,但有些操作(如讀取滾動偏移量)要求控制器與單個可滾動小部件一起使用。
滾動控制器創(chuàng)建一個ScrollPosition來管理特定于單個可滾動小部件的狀態(tài)。要使用自定義的ScrollPosition,子類化ScrollController并重寫createScrollPosition。
ScrollController是一個Listenable。當(dāng)附加的任何scrollposition通知它們的偵聽器時(即當(dāng)它們中的任何一個滾動時),它會通知它的偵聽器。當(dāng)附加的scrollposition列表發(fā)生變化時,它不會通知偵聽器。
通常與ListView, GridView, CustomScrollView一起使用。
參見: ListView, GridView, CustomScrollView,它們可以由ScrollController控制。 Scrollable,它是較低層的小部件,用于創(chuàng)建ScrollPosition對象和ScrollController對象并將它們關(guān)聯(lián)起來。 PageController,它是控制PageView的一個類似對象。 ScrollPosition,用于管理單個滾動小部件的滾動偏移量。 ScrollNotification和NotificationListener,它們可用于監(jiān)視滾動位置,而無需使用ScrollController。
以上就是Flutter ScrollController滾動監(jiān)聽及控制示例詳解的詳細(xì)內(nèi)容,更多關(guān)于Flutter ScrollController滾動監(jiān)聽的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
IOS開發(fā) UIAlertController詳解及實例代碼
這篇文章主要介紹了 IOS開發(fā) UIAlertController詳解及實例代碼的相關(guān)資料,需要的朋友可以參考下2016-12-12
iOS實現(xiàn)調(diào)用QQ客戶端發(fā)起臨時會話
本篇文章主要給大家詳細(xì)分析了用IOS實現(xiàn)調(diào)用QQ客戶端發(fā)起臨時會話的功能,對此有需要的朋友收藏分享下。2018-02-02
iOS開發(fā)之tableView實現(xiàn)左滑刪除功能
我們在使用一些應(yīng)用的時候,在滑動一些聯(lián)系人的某一行的時候,會出現(xiàn)刪除、置頂、更多等等的按鈕,下面這篇文章主要就介紹了iOS用tableView實現(xiàn)左劃刪除功能的方法,有需要的朋友們可以參考借鑒,下面來一起看看吧。2017-01-01
iOS基于UITableView實現(xiàn)多層展開與收起
這篇文章主要為大家詳細(xì)介紹了iOS基于UITableView實現(xiàn)多層展開與收起的相關(guān)資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-03-03
詳解iOS應(yīng)用開發(fā)中Core Data數(shù)據(jù)存儲的使用
這篇文章主要介紹了iOS應(yīng)用開發(fā)中Core Data數(shù)據(jù)存儲的使用,Core Data可以看作是一個內(nèi)嵌型數(shù)據(jù)庫SQLite的iOS專用版本,需要的朋友可以參考下2016-02-02
解析iOS應(yīng)用的UI開發(fā)中懶加載和xib的簡單使用方法
這篇文章主要介紹了解析iOS應(yīng)用的UI開發(fā)中懶加載和xib的簡單使用方法,代碼基于傳統(tǒng)的Objective-C,需要的朋友可以參考下2016-01-01
iOS開發(fā)中文件的上傳和下載功能的基本實現(xiàn)
這篇文章主要介紹了iOS開發(fā)中文件的上傳和下載功能的基本實現(xiàn),并且下載方面講到了大文件的多線程斷點下載,需要的朋友可以參考下2015-11-11
iOS的CoreAnimation開發(fā)框架中的Layer層動畫制作解析
在iOS中UIView層的屬性會映射到CoreAnimation框架的CALayer,這里我們來看一下iOS的CoreAnimation開發(fā)框架中的Layer層動畫制作解析,需要的朋友可以參考下2016-07-07

