對于滾動的視圖,我們經(jīng)常需要監(jiān)聽它的一些滾動事件,
在Flutter中監(jiān)聽滾動相關(guān)的內(nèi)容由兩部分組成:ScrollController和ScrollNotification。
ScrollController
在Flutter中,Widget并不是最終渲染到屏幕上的元素(真正渲染的是RenderObject),因此通常這種監(jiān)聽事件以及相關(guān)的信息并不能直接從Widget中獲取,而是必須通過對應(yīng)的Widget的Controller來實現(xiàn)。
ListView、GridView的組件控制器是ScrollController,我們可以通過它來獲取視圖的滾動信息,并且可以調(diào)用里面的方法來更新視圖的滾動位置。通常情況下,我們會根據(jù)滾動的位置來改變一些Widget的狀態(tài)信息,所以ScrollController通常會和StatefulWidget一起來使用,并且會在其中控制它的初始化、監(jiān)聽、銷毀等事件。
ScrollController常用的屬性和方法:
- offset
可滾動組件當(dāng)前的滾動位置。 - jumpTo(double offset)、animateTo(double offset,...)
這兩個方法用于跳轉(zhuǎn)到指定的位置,它們不同之處在于,后者在跳轉(zhuǎn)時會執(zhí)行一個動畫,而前者不會。
ScrollController間接繼承自Listenable,我們可以根據(jù)ScrollController來監(jiān)聽滾動事件。
當(dāng)滾動到1000位置的時候,顯示一個回到頂部的按鈕。代碼示例:
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(() {
// 打印滾動位置
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);
},
),
);
}
}
代碼運行效果圖如下:NotificationListener
如果希望監(jiān)聽什么時候開始滾動,什么時候結(jié)束滾動,這個時候可以通過NotificationListener監(jiān)聽。
NotificationListener是一個Widget,模板參數(shù)T是想監(jiān)聽的通知類型,如果省略,則所有類型通知都會被監(jiān)聽,如果指定特定類型,則只有該類型的通知會被監(jiān)聽。
NotificationListener需要一個onNotification回調(diào)函數(shù),用于實現(xiàn)監(jiān)聽處理邏輯。該回調(diào)可以返回一個布爾值,代表是否阻止該事件繼續(xù)向上冒泡,如果為true時,則冒泡終止,事件停止向上傳播,如果不返回或者返回值為false 時,則冒泡繼續(xù)。
通過NotificationListener監(jiān)聽滾動事件和通過ScrollController有兩個主要的不同:
通過NotificationListener可以在從可滾動組件到widget樹根之間任意位置都能監(jiān)聽。而ScrollController只能和具體的可滾動組件關(guān)聯(lián)后才可以。
收到滾動事件后獲得的信息不同;NotificationListener在收到滾動事件時,通知中會攜帶當(dāng)前滾動位置和視圖的一些信息,而ScrollController只能獲取當(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)聽事件的類型
if (notification is ScrollStartNotification) {
print('開始滾動');
} else if (notification is ScrollUpdateNotification) {
// 當(dāng)前滾動的位置和總長度
double currentPixel = notification.metrics.pixels;
double totalPixel = notification.metrics.maxScrollExtent;
double progress = currentPixel / totalPixel;
setState(() {
_progress = '${(progress * 100).toInt()}%';
});
print(
"正在滾動:${notification.metrics.pixels} - ${notification.metrics.maxScrollExtent}");
} else if (notification is ScrollEndNotification) {
print("結(jié)束滾動....");
}
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,
),
],
),
),
);
}
}
代碼運行效果圖如下:在接收到滾動事件時,參數(shù)類型為ScrollNotification,它包括一個metrics屬性,它的類型是ScrollMetrics,該屬性包含當(dāng)前視圖及滾動位置等信息:
- pixels
當(dāng)前滾動位置。 - maxScrollExtent
最大可滾動長度。 - extentBefore
滑出頂部的長度;此示例中相當(dāng)于頂部滑出屏幕上方的列表長度。 - extentInside
內(nèi)部長度;此示例中屏幕顯示的列表部分的長度。 - extentAfter:
列表中未滑入V部分的長度;此示例中列表底部未顯示到屏幕范圍部分的長度。 - atEdge
是否滑到了可滾動組件的邊界;此示例中相當(dāng)于列表頂或底部。