對(duì)于滾動(dòng)的視圖义矛,我們經(jīng)常需要監(jiān)聽(tīng)它的一些滾動(dòng)事件,
在Flutter中監(jiān)聽(tīng)滾動(dòng)相關(guān)的內(nèi)容由兩部分組成:ScrollController和ScrollNotification意系。
ScrollController
在Flutter中拱燃,Widget并不是最終渲染到屏幕上的元素(真正渲染的是RenderObject)碎浇,因此通常這種監(jiān)聽(tīng)事件以及相關(guān)的信息并不能直接從Widget中獲取腾仅,而是必須通過(guò)對(duì)應(yīng)的Widget的Controller來(lái)實(shí)現(xiàn)乒裆。
ListView、GridView的組件控制器是ScrollController推励,我們可以通過(guò)它來(lái)獲取視圖的滾動(dòng)信息鹤耍,并且可以調(diào)用里面的方法來(lái)更新視圖的滾動(dòng)位置肉迫。通常情況下,我們會(huì)根據(jù)滾動(dòng)的位置來(lái)改變一些Widget的狀態(tài)信息惰蜜,所以ScrollController通常會(huì)和StatefulWidget一起來(lái)使用昂拂,并且會(huì)在其中控制它的初始化受神、監(jiān)聽(tīng)抛猖、銷(xiāo)毀等事件。
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à)撑教,而前者不會(huì)。
ScrollController間接繼承自Listenable醉拓,我們可以根據(jù)ScrollController來(lái)監(jiān)聽(tīng)滾動(dòng)事件伟姐。
當(dāng)滾動(dòng)到1000位置的時(shí)候,顯示一個(gè)回到頂部的按鈕亿卤。代碼示例:
class ScrollControllerDemo extends StatefulWidget {
@override
_ScrollControllerDemoState createState() => _ScrollControllerDemoState();
}
class _ScrollControllerDemoState extends State<ScrollControllerDemo> {
ScrollController _controller;
// 是否顯示“返回到頂部”按鈕
bool _isShowTopBtn = false;
@override
void initState() {
super.initState();
_controller = ScrollController();
_controller.addListener(() {
// 打印滾動(dòng)位置
print(_controller.offset);
if (_controller.offset < 1000 && _isShowTopBtn) {
setState(() {
_isShowTopBtn = false;
});
} else if (_controller.offset >= 1000 && !_isShowTopBtn) {
setState(() {
_isShowTopBtn = true;
});
}
});
}
@override
void dispose() {
// 為了避免內(nèi)存泄露愤兵,需要調(diào)用_controller.dispose
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('ScrollController Demo')),
body: Scrollbar(
child: ListView.builder(
itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text('$index'));
},
itemCount: 100,
itemExtent: 50.0,
controller: _controller,
),
),
floatingActionButton: !_isShowTopBtn
? null
: FloatingActionButton(
child: Icon(Icons.arrow_upward),
onPressed: () {
_controller.animateTo(0,
duration: Duration(milliseconds: 200), curve: Curves.ease);
},
),
);
}
}
代碼運(yùn)行效果圖如下:NotificationListener
如果希望監(jiān)聽(tīng)什么時(shí)候開(kāi)始滾動(dòng),什么時(shí)候結(jié)束滾動(dòng)排吴,這個(gè)時(shí)候可以通過(guò)NotificationListener監(jiān)聽(tīng)秆乳。
NotificationListener是一個(gè)Widget,模板參數(shù)T是想監(jiān)聽(tīng)的通知類(lèi)型钻哩,如果省略屹堰,則所有類(lèi)型通知都會(huì)被監(jiān)聽(tīng),如果指定特定類(lèi)型街氢,則只有該類(lèi)型的通知會(huì)被監(jiān)聽(tīng)扯键。
NotificationListener需要一個(gè)onNotification回調(diào)函數(shù),用于實(shí)現(xiàn)監(jiān)聽(tīng)處理邏輯珊肃。該回調(diào)可以返回一個(gè)布爾值荣刑,代表是否阻止該事件繼續(xù)向上冒泡,如果為true時(shí)近范,則冒泡終止嘶摊,事件停止向上傳播,如果不返回或者返回值為false 時(shí)评矩,則冒泡繼續(xù)叶堆。
通過(guò)NotificationListener監(jiān)聽(tīng)滾動(dòng)事件和通過(guò)ScrollController有兩個(gè)主要的不同:
通過(guò)NotificationListener可以在從可滾動(dòng)組件到widget樹(shù)根之間任意位置都能監(jiān)聽(tīng)。而ScrollController只能和具體的可滾動(dòng)組件關(guān)聯(lián)后才可以斥杜。
收到滾動(dòng)事件后獲得的信息不同虱颗;NotificationListener在收到滾動(dòng)事件時(shí)沥匈,通知中會(huì)攜帶當(dāng)前滾動(dòng)位置和視圖的一些信息,而ScrollController只能獲取當(dāng)前滾動(dòng)位置忘渔。
列表滾動(dòng), 并且在中間顯示滾動(dòng)進(jìn)度高帖。代碼示例:
class ScrollNotificationDemo extends StatefulWidget {
@override
_ScrollNotificationDemoState createState() => _ScrollNotificationDemoState();
}
class _ScrollNotificationDemoState extends State<ScrollNotificationDemo> {
// 保存進(jìn)度百分比
String _progress = '0%';
@override
Widget build(BuildContext context) {
return Scrollbar(
child: NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification notification) {
// 判斷監(jiān)聽(tīng)事件的類(lèi)型
if (notification is ScrollStartNotification) {
print('開(kāi)始滾動(dòng)');
} else if (notification is ScrollUpdateNotification) {
// 當(dāng)前滾動(dòng)的位置和總長(zhǎng)度
double currentPixel = notification.metrics.pixels;
double totalPixel = notification.metrics.maxScrollExtent;
double progress = currentPixel / totalPixel;
setState(() {
_progress = '${(progress * 100).toInt()}%';
});
print(
"正在滾動(dòng):${notification.metrics.pixels} - ${notification.metrics.maxScrollExtent}");
} else if (notification is ScrollEndNotification) {
print("結(jié)束滾動(dòng)....");
}
return false;
},
child: Stack(
alignment: Alignment.center,
children: [
ListView.builder(
itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text("$index"));
},
itemExtent: 50.0,
itemCount: 100,
),
CircleAvatar(
radius: 30.0,
child: Text(_progress),
backgroundColor: Colors.black54,
),
],
),
),
);
}
}
代碼運(yùn)行效果圖如下:在接收到滾動(dòng)事件時(shí),參數(shù)類(lèi)型為ScrollNotification畦粮,它包括一個(gè)metrics屬性散址,它的類(lèi)型是ScrollMetrics,該屬性包含當(dāng)前視圖及滾動(dòng)位置等信息:
- pixels
當(dāng)前滾動(dòng)位置宣赔。 - maxScrollExtent
最大可滾動(dòng)長(zhǎng)度预麸。 - extentBefore
滑出頂部的長(zhǎng)度;此示例中相當(dāng)于頂部滑出屏幕上方的列表長(zhǎng)度儒将。 - extentInside
內(nèi)部長(zhǎng)度吏祸;此示例中屏幕顯示的列表部分的長(zhǎng)度。 - extentAfter:
列表中未滑入V部分的長(zhǎng)度钩蚊;此示例中列表底部未顯示到屏幕范圍部分的長(zhǎng)度贡翘。 - atEdge
是否滑到了可滾動(dòng)組件的邊界;此示例中相當(dāng)于列表頂或底部砰逻。