本文主要介紹了 Flutter 監聽滾動控件的滾動事件和一些滾動控制器的用法,最後實現 appBar 滾動漸變,若有不當之處敬請指正。git
閱讀本文大約須要 5 分鐘github
在 Flutter 中滾動監聽通常能夠採用兩種方式來實現,分別是 ScrollController
和 NotificationListener
這兩種方式。數組
介紹一下ScrollController
經常使用的屬性和方法:瀏覽器
offset
:可滾動組件當前的滾動位置。jumpTo(double offset)
跳轉到指定位置,offset
爲滾動偏移量。animateTo(double offset,@required Duration duration,@required Curve curve)
同 jumpTo(double offset)
同樣,不一樣的是 animateTo
跳轉時會執行一個動畫,須要傳入執行動畫須要的時間和動畫曲線。ScrollPosition是用來保存可滾動組件的滾動位置的。一個 ScrollController 對象可能會被多個可滾動的組件使用,markdown
ScrollController 會爲每個滾動組件建立一個 ScrollPosition 對象來存儲位置信息。ScrollPosition 中存儲的是在 ScrollController 的 positions 屬性裏面,他是一個 List<ScrollPosition>
數組,在 ScrollController 中真正保存位置信息的就是 ScrollPosition,而 offset 只是一個便捷使用的屬性。查看源碼中能夠發現 offset 獲取就是從 ScrollPosition 中獲取的。app
/// Returns the attached [ScrollPosition], from which the actual scroll offset
/// of the [ScrollView] can be obtained.
/// Calling this is only valid when only a single position is attached.
ScrollPosition get position {
assert(_positions.isNotEmpty, 'ScrollController not attached to any scroll views.');
assert(_positions.length == 1, 'ScrollController attached to multiple scroll views.');
return _positions.single;
}
/// The current scroll offset of the scrollable widget.
/// Requires the controller to be controlling exactly one scrollable widget.
double get offset => position.pixels;
複製代碼
一個 ScrollController
雖然能夠對應多個可滾動組件,可是讀取滾動位置 offset
,則須要一對一讀取。在一對多的狀況下,咱們可使用其餘方法來實現讀取滾動位置。假設如今一個 ScrollController
對應了兩個能夠滾動的組件,那麼能夠經過 position.elementAt(index)
來獲取 ScrollPosition
,從而得到 offset
:less
controller.positions.elementAt(0).pixels
controller.positions.elementAt(1).pixels
複製代碼
ScrollPosition
有兩個經常使用方法:分別是 animateTo()
和 jumpTo()
,他們纔是真正控制跳轉到滾動位置的方法,在 ScrollController 中這兩個同名方法,內部最終都會調用 ScrollPosition 這兩個方法。ide
Future<void> animateTo(
double offset, {
@required Duration duration,
@required Curve curve,
}) {
assert(_positions.isNotEmpty, 'ScrollController not attached to any scroll views.');
final List<Future<void>> animations = List<Future<void>>(_positions.length);
for (int i = 0; i < _positions.length; i += 1)
// 調用 ScrollPosition 中的 animateTo 方法
animations[i] = _positions[i].animateTo(offset, duration: duration, curve: curve);
return Future.wait<void>(animations).then<void>((List<void> _) => null);
}
複製代碼
ScrollController
還有其餘比較重要的三個方法:佈局
createScrollPosition
:當 ScrollController
和可滾動組件關聯時,可滾動組件首先會調 ScrollController
的 createScrollPosition
方法來建立一個ScrollPosition
來存儲滾動位置信息。ScrollPosition createScrollPosition(
ScrollPhysics physics,
ScrollContext context,
ScrollPosition oldPosition);
複製代碼
createScrollPosition
方法以後,接着會調用 attach
方法來將建立號的 ScrollPosition
信息添加到 positions
屬性中,這一步稱爲「註冊位置」,只有註冊後animateTo()
和 jumpTo()
才能夠被調用。void attach(ScrollPosition position);
複製代碼
detach()
方法,將其 ScrollPosition
對象從 ScrollController
的positions
屬性中移除,這一步稱爲「註銷位置」,註銷後 animateTo()
和 jumpTo()
將不能再被調用。void detach(ScrollPosition position);
複製代碼
Flutter Widget 樹中子 Widge t能夠經過發送通知(Notification)與父(包括祖先) Widget 進行通訊,父級組件能夠經過 NotificationListener
組件來監聽本身關注的通知,這種通訊方式相似於 Web 開發中瀏覽器的事件冒泡,在 Flutter 中就沿用了「冒泡」這個術語,稱爲通知冒泡學習
通知冒泡和用戶觸摸事件冒泡是類似的,但有一點不一樣:通知冒泡能夠停止,但用戶觸摸事件不行。
Flutter 中不少地方使用了通知,如可滾動組件(Scrollable Widget)滑動時就會分發滾動通知(ScrollNotification),而 Scrollbar
正是經過監聽 ScrollNotification
來肯定滾動條位置的。
switch (notification.runtimeType){
case ScrollStartNotification: print("開始滾動"); break;
case ScrollUpdateNotification: print("正在滾動"); break;
case ScrollEndNotification: print("滾動中止"); break;
case OverscrollNotification: print("滾動到邊界"); break;
}
複製代碼
其中 ScrollStartNotification
和 ScrollUpdateNotification
等都是繼承 ScrollNotification
類的,不一樣類型的通知子類會包含不一樣的信息,ScrollUpdateNotification
有一個 scrollDelta
屬性,它記錄了移動的位移。
NotificationListener
時繼承 StatelessWidget
類的額,左右咱們能夠直接在放置在Widget 數中,經過裏面的 onNotification
能夠指定一個模板參數,該模板參數類型必須是繼承自Notification
,能夠顯式指定模板參數時,好比通知的類型爲滾動結束通知:
NotificationListener<ScrollEndNotification>
複製代碼
這個時候 NotificationListener
便只會接收該參數類型的通知。
onNotification
回調爲通知處理回調,他的返回值時布爾類型(bool),當返回值爲 true
時,阻止冒泡,其父級 Widget 將再也收不到該通知;當返回值爲 false
時繼續向上冒泡通知。
首先這兩種方式均可以實現對滾動的監聽,可是他們仍是有一些區別:
ScrollController
能夠控制滾動控件的滾動,而 NotificationListener
是不能夠的。NotificationListener
能夠在從可滾動組件到widget樹根之間任意位置都能監聽,而ScrollController
只能和具體的可滾動組件關聯後才能夠。NotificationListener
在收到滾動事件時,通知中會攜帶當前滾動位置和ViewPort的一些信息,而ScrollController
只能獲取當前滾動位置。建立滾動所需的界面,一個 Scaffold
組件 body
裏面方式一個 Stack
的層疊小部件,裏面放置一個listview
,和自定義的 appBar
;floatingActionButton
放置一個返回頂部的懸浮按鈕。
Scaffold(
body: Stack(
children: <Widget>[
MediaQuery.removePadding(
removeTop: true,
context: context,
child: ListView.builder(
// ScrollController 關聯滾動組件
controller: _controller,
itemCount: 100,
itemBuilder: (context, index) {
if (index == 0) {
return Container(
height: 200,
child: Swiper(
itemBuilder: (BuildContext context, int index) {
return new Image.network(
"http://via.placeholder.com/350x150",
fit: BoxFit.fill,
);
},
itemCount: 3,
autoplay: true,
pagination: new SwiperPagination(),
),
);
}
return ListTile(
title: Text("ListTile:$index"),
);
},
),
),
Opacity(
opacity: toolbarOpacity,
child: Container(
height: 98,
color: Colors.blue,
child: Padding(
padding: const EdgeInsets.only(top: 30.0),
child: Center(
child: Text(
"ScrollerDemo",
style: TextStyle(color: Colors.white, fontSize: 20.0),
),
),
),
),
)
],
),
floatingActionButton: !showToTopBtn
? null
: FloatingActionButton(
child: Icon(Icons.keyboard_arrow_up),
onPressed: () {
_controller.animateTo(0.0, duration: Duration(milliseconds: 500), curve: Curves.ease);
},
),
)
複製代碼
建立 ScrollController
對象,在初始化中添加對滾動的監聽,並和 ListView
這個可滾動小部件進行關聯:
ScrollController _controller = new ScrollController();
@override
void initState() {
_controller.addListener(() {
print(_controller.offset); //打印滾動位置
})
}
複製代碼
double t = _controller.offset / DEFAULT_SCROLLER;
if (t < 0.0) {
t = 0.0;
} else if (t > 1.0) {
t = 1.0;
}
setState(() {
toolbarOpacity = t;
});
複製代碼
更具滾動的高度和當前 floatingActionButton
的現實狀態,判斷 floatingActionButton
是否須要展現:
if(_controller.offset < DEFAULT_SHOW_TOP_BTN && showToTopBtn){
setState(() {
showToTopBtn = false;
});
}else if(_controller.offset >= DEFAULT_SHOW_TOP_BTN && !showToTopBtn){
setState(() {
showToTopBtn = true;
});
}
複製代碼
點擊 floatingActionButton
返回到頂部:
_controller.animateTo(0.0, duration: Duration(milliseconds: 500), curve: Curves.ease);
複製代碼
完整代碼請參考下方GitHub項目中
/demo/scroller_demo.dart
文件。
在 NotificationListener 實例中佈局基本上和 ScrollController 一致,不一樣的地方在於 ListView 須要包裹在 NotificationListener 中做爲 child,而後 NotificationListener 在 onNotification 中判斷滾動偏移量:
if (notification is ScrollUpdateNotification && notification.depth == 0) {
double t = notification.metrics.pixels / DEFAULT_SCROLLER;
if (t < 0.0) {
t = 0.0;
} else if (t > 1.0) {
t = 1.0;
}
setState(() {
toolbarOpacity = t;
});
print(notification.metrics.pixels); //打印滾動位置
}
複製代碼
完整代碼請參考下方GitHub項目中
/demo/notification_listener_demo.dart
文件
完整代碼奉上GitHub地址:fluter_demo ,歡迎star和fork。
到此,本文就結束了,若有不當之處敬請指正,一塊兒學習探討,謝謝🙏。