Flutter 滾動(dòng)監(jiān)聽(tīng)及實(shí)戰(zhàn)appBar滾動(dòng)漸變的實(shí)現(xiàn)
介紹
在 Flutter 中滾動(dòng)監(jiān)聽(tīng)一般可以采用兩種方式來(lái)實(shí)現(xiàn),分別是ScrollController
和NotificationListener
這兩種方式。
ScrollController介紹
ScrollController
介紹一下ScrollController
常用的屬性和方法:
offset
:可滾動(dòng)組件當(dāng)前的滾動(dòng)位置。jumpTo(double offset)
跳轉(zhuǎn)到指定位置,offset
為滾動(dòng)偏移量。animateTo(double offset,@required Duration duration,@required Curve curve)
同jumpTo(double offset)
一樣,不同的是animateTo
跳轉(zhuǎn)時(shí)會(huì)執(zhí)行一個(gè)動(dòng)畫(huà),需要傳入執(zhí)行動(dòng)畫(huà)需要的時(shí)間和動(dòng)畫(huà)曲線。
ScrollPosition
ScrollPosition是用來(lái)保存可滾動(dòng)組件的滾動(dòng)位置的。一個(gè) ScrollController 對(duì)象可能會(huì)被多個(gè)可滾動(dòng)的組件使用,
ScrollController 會(huì)為每一個(gè)滾動(dòng)組件創(chuàng)建一個(gè) ScrollPosition 對(duì)象來(lái)存儲(chǔ)位置信息。ScrollPosition 中存儲(chǔ)的是在 ScrollController 的 positions 屬性里面,他是一個(gè)List<ScrollPosition>
數(shù)組,在 ScrollController 中真正保存位置信息的就是 ScrollPosition,而 offset 只是一個(gè)便捷使用的屬性。查看源碼中可以發(fā)現(xiàn) offset 獲取就是從 ScrollPosition 中獲取的。
/// 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;
一個(gè)ScrollController
雖然可以對(duì)應(yīng)多個(gè)可滾動(dòng)組件,但是讀取滾動(dòng)位置offset
,則需要一對(duì)一讀取。在一對(duì)多的情況下,我們可以使用其他方法來(lái)實(shí)現(xiàn)讀取滾動(dòng)位置。假設(shè)現(xiàn)在一個(gè)ScrollController
對(duì)應(yīng)了兩個(gè)可以滾動(dòng)的組件,那么可以通過(guò)position.elementAt(index)
來(lái)獲取ScrollPosition
,從而獲得offset
:
controller.positions.elementAt(0).pixels controller.positions.elementAt(1).pixels
ScrollPosition的方法
ScrollPosition
有兩個(gè)常用方法:分別是animateTo()
和jumpTo()
,他們才是真正控制跳轉(zhuǎn)到滾動(dòng)位置的方法,在 ScrollController 中這兩個(gè)同名方法,內(nèi)部最終都會(huì)調(diào)用 ScrollPosition 這兩個(gè)方法。
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) // 調(diào)用 ScrollPosition 中的 animateTo 方法 animations[i] = _positions[i].animateTo(offset, duration: duration, curve: curve); return Future.wait<void>(animations).then<void>((List<void> _) => null); }
ScrollController控制原理
ScrollController
還有其他比較重要的三個(gè)方法:
1、createScrollPosition
:當(dāng)ScrollController
和可滾動(dòng)組件關(guān)聯(lián)時(shí),可滾動(dòng)組件首先會(huì)調(diào)ScrollController
的createScrollPosition
方法來(lái)創(chuàng)建一個(gè)ScrollPosition
來(lái)存儲(chǔ)滾動(dòng)位置信息。
ScrollPosition createScrollPosition( ScrollPhysics physics, ScrollContext context, ScrollPosition oldPosition);
2、在滾動(dòng)組件調(diào)用createScrollPosition
方法之后,接著會(huì)調(diào)用attach
方法來(lái)將創(chuàng)建號(hào)的ScrollPosition
信息添加到positions
屬性中,這一步稱為“注冊(cè)位置”,只有注冊(cè)后animateTo()
和jumpTo()
才可以被調(diào)用。
void attach(ScrollPosition position);
3、最后當(dāng)可滾動(dòng)組件被銷毀時(shí),會(huì)調(diào)用detach()
方法,將其ScrollPosition
對(duì)象從ScrollController
的positions
屬性中移除,這一步稱為“注銷位置”,注銷后animateTo()
和jumpTo()
將不能再被調(diào)用。
void detach(ScrollPosition position);
NotificationListener介紹
通知冒泡
Flutter Widget 樹(shù)中子 Widge t可以通過(guò)發(fā)送通知(Notification)與父(包括祖先) Widget 進(jìn)行通信,父級(jí)組件可以通過(guò)NotificationListener
組件來(lái)監(jiān)聽(tīng)自己關(guān)注的通知,這種通信方式類似于 Web 開(kāi)發(fā)中瀏覽器的事件冒泡,在 Flutter 中就沿用了“冒泡”這個(gè)術(shù)語(yǔ),稱為通知冒泡
通知冒泡和用戶觸摸事件冒泡是相似的,但有一點(diǎn)不同:通知冒泡可以中止,但用戶觸摸事件不行。
滾動(dòng)通知
Flutter 中很多地方使用了通知,如可滾動(dòng)組件(Scrollable Widget)滑動(dòng)時(shí)就會(huì)分發(fā)滾動(dòng)通知(ScrollNotification),而Scrollbar
正是通過(guò)監(jiān)聽(tīng)ScrollNotification
來(lái)確定滾動(dòng)條位置的。
switch (notification.runtimeType){ case ScrollStartNotification: print("開(kāi)始滾動(dòng)"); break; case ScrollUpdateNotification: print("正在滾動(dòng)"); break; case ScrollEndNotification: print("滾動(dòng)停止"); break; case OverscrollNotification: print("滾動(dòng)到邊界"); break; }
其中ScrollStartNotification
和ScrollUpdateNotification
等都是繼承ScrollNotification
類的,不同類型的通知子類會(huì)包含不同的信息,ScrollUpdateNotification
有一個(gè)scrollDelta
屬性,它記錄了移動(dòng)的位移。
NotificationListener
時(shí)繼承StatelessWidget
類的額,左右我們可以直接在放置在Widget 數(shù)中,通過(guò)里面的onNotification
可以指定一個(gè)模板參數(shù),該模板參數(shù)類型必須是繼承自Notification
,可以顯式指定模板參數(shù)時(shí),比如通知的類型為滾動(dòng)結(jié)束通知:
NotificationListener<ScrollEndNotification>
這個(gè)時(shí)候NotificationListener
便只會(huì)接收該參數(shù)類型的通知。
onNotification
回調(diào)為通知處理回調(diào),他的返回值時(shí)布爾類型(bool),當(dāng)返回值為true
時(shí),阻止冒泡,其父級(jí) Widget 將再也收不到該通知;當(dāng)返回值為false
時(shí)繼續(xù)向上冒泡通知。
兩者區(qū)別
首先這兩種方式都可以實(shí)現(xiàn)對(duì)滾動(dòng)的監(jiān)聽(tīng),但是他們還是有一些區(qū)別:
ScrollController
可以控制滾動(dòng)控件的滾動(dòng),而NotificationListener
是不可以的。- 通過(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)位置和ViewPort的一些信息,而ScrollController
只能獲取當(dāng)前滾動(dòng)位置。ScrollController實(shí)例效果圖
代碼實(shí)現(xiàn)步驟
創(chuàng)建滾動(dòng)所需的界面,一個(gè)Scaffold
組件body
里面方式一個(gè)Stack
的層疊小部件,里面放置一個(gè)listview
,和自定義的appBar
;floatingActionButton
放置一個(gè)返回頂部的懸浮按鈕。
Scaffold( body: Stack( children: <Widget>[ MediaQuery.removePadding( removeTop: true, context: context, child: ListView.builder( // ScrollController 關(guān)聯(lián)滾動(dòng)組件 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); }, ), )
創(chuàng)建ScrollController
對(duì)象,在初始化中添加對(duì)滾動(dòng)的監(jiān)聽(tīng),并和ListView
這個(gè)可滾動(dòng)小部件進(jìn)行關(guān)聯(lián):
double t = _controller.offset / DEFAULT_SCROLLER; if (t < 0.0) { t = 0.0; } else if (t > 1.0) { t = 1.0; } setState(() { toolbarOpacity = t; });
在 _controller.addListener 中添加相關(guān)業(yè)務(wù)代碼,根據(jù)滾動(dòng)的偏移量計(jì)算出透明度,實(shí)現(xiàn)appBar滾動(dòng)漸變:
if(_controller.offset < DEFAULT_SHOW_TOP_BTN && showToTopBtn){ setState(() { showToTopBtn = false; }); }else if(_controller.offset >= DEFAULT_SHOW_TOP_BTN && !showToTopBtn){ setState(() { showToTopBtn = true; }); }
更具滾動(dòng)的高度和當(dāng)前floatingActionButton
的現(xiàn)實(shí)狀態(tài),判斷floatingActionButton
是否需要展示:
if(_controller.offset < DEFAULT_SHOW_TOP_BTN && showToTopBtn){ setState(() { showToTopBtn = false; }); }else if(_controller.offset >= DEFAULT_SHOW_TOP_BTN && !showToTopBtn){ setState(() { showToTopBtn = true; }); }
點(diǎn)擊floatingActionButton
返回到頂部:
_controller.animateTo(0.0, duration: Duration(milliseconds: 500), curve: Curves.ease);
完整代碼請(qǐng)參考下方GitHub項(xiàng)目中/demo/scroller_demo.dart
文件。
NotificationListener實(shí)例
效果圖
代碼實(shí)現(xiàn)步驟
在 NotificationListener 實(shí)例中布局基本上和 ScrollController 一致,不同的地方在于 ListView 需要包裹在 NotificationListener 中作為 child,然后 NotificationListener 在 onNotification 中判斷滾動(dòng)偏移量:
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); //打印滾動(dòng)位置 }
完整代碼請(qǐng)參考下方GitHub項(xiàng)目中/demo/notification_listener_demo.dart
文件
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android中自定義ContentProvider實(shí)例
應(yīng)用A(TestBaidu)調(diào)用另外一個(gè)應(yīng)用(TestContentProvider)即自定義ContentProvider的使用,其它應(yīng)用調(diào)用該ContentProvider,具體如下,感興趣的朋友可以參考下哈2013-06-06Android使用BottomTabBar實(shí)現(xiàn)底部導(dǎo)航頁(yè)效果
這篇文章主要介紹了Android使用BottomTabBar實(shí)現(xiàn)底部導(dǎo)航頁(yè)效果,本文通過(guò)實(shí)例代碼結(jié)合文字說(shuō)明的形式給大家介紹的非常詳細(xì),需要的朋友參考下吧2018-03-03Android 超詳細(xì)講解fitsSystemWindows屬性的使用
fitsSystemWindows屬性可以讓view根據(jù)系統(tǒng)窗口來(lái)調(diào)整自己的布局;簡(jiǎn)單點(diǎn)說(shuō)就是我們?cè)谠O(shè)置應(yīng)用布局時(shí)是否考慮系統(tǒng)窗口布局,這里系統(tǒng)窗口包括系統(tǒng)狀態(tài)欄、導(dǎo)航欄、輸入法等,包括一些手機(jī)系統(tǒng)帶有的底部虛擬按鍵2022-03-03Android中的Handler與多線程應(yīng)用實(shí)例
這篇文章主要介紹了Android中的Handler與多線程應(yīng)用實(shí)例,本文首先解釋一下handler是用來(lái)干嘛的,然后通過(guò)例子介紹其在多線程中的應(yīng)用,需要的朋友可以參考下2015-03-03Android?Studio實(shí)現(xiàn)智能聊天
這篇文章主要為大家詳細(xì)介紹了Android?Studio實(shí)現(xiàn)智能聊天,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-07-07Android開(kāi)發(fā)中Flutter組件實(shí)用技巧
這篇文章主要為大家介紹了Android開(kāi)發(fā)中Flutter組件實(shí)用技巧,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05Android筆記設(shè)計(jì)范例之日記APP實(shí)現(xiàn)全流程
這篇文章主要介紹了Android筆記設(shè)計(jì)范例之日記APP實(shí)現(xiàn)全流程,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧2023-01-01MTK Android平臺(tái)開(kāi)發(fā)流程
這篇文章主要介紹了MTK在Android平臺(tái)開(kāi)發(fā)的流程,一共分析了44個(gè)步驟,需要的朋友學(xué)習(xí)下吧。2017-12-12去掉RecycleView或者ListView上下滑動(dòng)陰影的方法
下面小編就為大家分享一篇去掉RecycleView或者ListView上下滑動(dòng)陰影的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-01-01