Flutter 滾動(dòng)監(jiān)聽及實(shí)戰(zhàn)appBar滾動(dòng)漸變的實(shí)現(xiàn)
介紹
在 Flutter 中滾動(dòng)監(jiān)聽一般可以采用兩種方式來實(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)畫,需要傳入執(zhí)行動(dòng)畫需要的時(shí)間和動(dòng)畫曲線。
ScrollPosition
ScrollPosition是用來保存可滾動(dòng)組件的滾動(dòng)位置的。一個(gè) ScrollController 對象可能會(huì)被多個(gè)可滾動(dòng)的組件使用,
ScrollController 會(huì)為每一個(gè)滾動(dòng)組件創(chuàng)建一個(gè) ScrollPosition 對象來存儲(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雖然可以對應(yīng)多個(gè)可滾動(dòng)組件,但是讀取滾動(dòng)位置offset,則需要一對一讀取。在一對多的情況下,我們可以使用其他方法來實(shí)現(xiàn)讀取滾動(dòng)位置。假設(shè)現(xiàn)在一個(gè)ScrollController對應(yīng)了兩個(gè)可以滾動(dòng)的組件,那么可以通過position.elementAt(index)來獲取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方法來創(chuàng)建一個(gè)ScrollPosition來存儲(chǔ)滾動(dòng)位置信息。
ScrollPosition createScrollPosition( ScrollPhysics physics, ScrollContext context, ScrollPosition oldPosition);
2、在滾動(dòng)組件調(diào)用createScrollPosition方法之后,接著會(huì)調(diào)用attach方法來將創(chuàng)建號(hào)的ScrollPosition信息添加到positions屬性中,這一步稱為“注冊位置”,只有注冊后animateTo()和jumpTo()才可以被調(diào)用。
void attach(ScrollPosition position);
3、最后當(dāng)可滾動(dòng)組件被銷毀時(shí),會(huì)調(diào)用detach()方法,將其ScrollPosition對象從ScrollController的positions屬性中移除,這一步稱為“注銷位置”,注銷后animateTo()和jumpTo()將不能再被調(diào)用。
void detach(ScrollPosition position);
NotificationListener介紹
通知冒泡
Flutter Widget 樹中子 Widge t可以通過發(fā)送通知(Notification)與父(包括祖先) Widget 進(jìn)行通信,父級組件可以通過NotificationListener組件來監(jiān)聽自己關(guān)注的通知,這種通信方式類似于 Web 開發(fā)中瀏覽器的事件冒泡,在 Flutter 中就沿用了“冒泡”這個(gè)術(shù)語,稱為通知冒泡
通知冒泡和用戶觸摸事件冒泡是相似的,但有一點(diǎn)不同:通知冒泡可以中止,但用戶觸摸事件不行。
滾動(dòng)通知
Flutter 中很多地方使用了通知,如可滾動(dòng)組件(Scrollable Widget)滑動(dòng)時(shí)就會(huì)分發(fā)滾動(dòng)通知(ScrollNotification),而Scrollbar正是通過監(jiān)聽ScrollNotification來確定滾動(dòng)條位置的。
switch (notification.runtimeType){
case ScrollStartNotification: print("開始滾動(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ù)中,通過里面的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í),阻止冒泡,其父級 Widget 將再也收不到該通知;當(dāng)返回值為false時(shí)繼續(xù)向上冒泡通知。
兩者區(qū)別
首先這兩種方式都可以實(shí)現(xiàn)對滾動(dòng)的監(jiān)聽,但是他們還是有一些區(qū)別:
ScrollController可以控制滾動(dòng)控件的滾動(dòng),而NotificationListener是不可以的。- 通過
NotificationListener可以在從可滾動(dòng)組件到widget樹根之間任意位置都能監(jiān)聽,而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對象,在初始化中添加對滾動(dòng)的監(jiān)聽,并和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);
完整代碼請參考下方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)位置
}
完整代碼請參考下方GitHub項(xiàng)目中/demo/notification_listener_demo.dart文件
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持我們。
欄 目:Android
下一篇:Android使用MediaCodec將攝像頭采集的視頻編碼為h264
本文標(biāo)題:Flutter 滾動(dòng)監(jiān)聽及實(shí)戰(zhàn)appBar滾動(dòng)漸變的實(shí)現(xiàn)
本文地址:http://www.jygsgssxh.com/a1/Android/9166.html
您可能感興趣的文章
- 01-10如何給Flutter界面切換實(shí)現(xiàn)點(diǎn)特效
- 01-10Flutter適配深色模式的方法(DarkMode)
- 01-10Flutter里面錯(cuò)誤捕獲的正確方法
- 01-10Android DSelectorBryant 單選滾動(dòng)選擇器的實(shí)例代碼
- 01-10如何使用Flutter實(shí)現(xiàn)58同城中的加載動(dòng)畫詳解
- 01-10android監(jiān)聽器實(shí)例代碼
- 01-10Flutter 假異步的實(shí)現(xiàn)示例
- 01-10使用Flutter實(shí)現(xiàn)一個(gè)走馬燈布局的示例代碼
- 01-10Flutter中如何加載并預(yù)覽本地的html文件的方法
- 01-10SurfaceView播放視頻發(fā)送彈幕并實(shí)現(xiàn)滾動(dòng)歌詞


閱讀排行
本欄相關(guān)
- 01-10Android自定義View之繪制圓形頭像功能
- 01-10Android實(shí)現(xiàn)雙擊返回鍵退出應(yīng)用實(shí)現(xiàn)方
- 01-10android實(shí)現(xiàn)簡單計(jì)算器功能
- 01-10android實(shí)現(xiàn)記住用戶名和密碼以及自動(dòng)
- 01-10C++自定義API函數(shù)實(shí)現(xiàn)大數(shù)相乘算法
- 01-10Android 友盟第三方登錄與分享的實(shí)現(xiàn)代
- 01-10android實(shí)現(xiàn)指紋識(shí)別功能
- 01-10如何給Flutter界面切換實(shí)現(xiàn)點(diǎn)特效
- 01-10Android實(shí)現(xiàn)圓形漸變加載進(jìn)度條
- 01-10Emoji表情在Android JNI中的兼容性問題詳
隨機(jī)閱讀
- 01-10SublimeText編譯C開發(fā)環(huán)境設(shè)置
- 08-05DEDE織夢data目錄下的sessions文件夾有什
- 04-02jquery與jsp,用jquery
- 08-05織夢dedecms什么時(shí)候用欄目交叉功能?
- 01-11Mac OSX 打開原生自帶讀寫NTFS功能(圖文
- 01-10C#中split用法實(shí)例總結(jié)
- 01-11ajax實(shí)現(xiàn)頁面的局部加載
- 01-10delphi制作wav文件的方法
- 08-05dedecms(織夢)副欄目數(shù)量限制代碼修改
- 01-10使用C語言求解撲克牌的順子及n個(gè)骰子


