序言
Android原生里一般會(huì)使用ViewPager來(lái)實(shí)現(xiàn)Banner區(qū)域,當(dāng)然Flutter中的PageView也可以實(shí)現(xiàn)類似的效果,今天就來(lái)擼一把循環(huán)滑動(dòng)的PageView。
在Android中想要實(shí)現(xiàn)循環(huán)滑動(dòng)的ViewPager,最常用的方法是,在原數(shù)據(jù)源的基礎(chǔ)上,通過(guò)前后補(bǔ)位來(lái)操作:即準(zhǔn)備新的數(shù)據(jù)集合list , 第一個(gè)位置插入原數(shù)據(jù)中的最后一個(gè)元素、最后一個(gè)位置插入原數(shù)據(jù)中的第一個(gè)元素,F(xiàn)lutter中PageView實(shí)現(xiàn)循環(huán)滑動(dòng)的方法如出一轍,如下圖所示:
在用戶滑動(dòng)過(guò)程中,當(dāng)(2)被選中后,無(wú)動(dòng)畫切換到2的位置;當(dāng)(0)被選中后,此時(shí)無(wú)動(dòng)畫切換到0的位置。即可實(shí)現(xiàn)循環(huán)滑動(dòng)的PageView。
準(zhǔn)備新的數(shù)據(jù)源
這里需要解釋下,如果只有一個(gè)數(shù)據(jù)的話,不考慮循環(huán)滑動(dòng)
/// 初始化Page
/// 準(zhǔn)備一個(gè)新的數(shù)據(jù)源list
/// 在原數(shù)據(jù)data的基礎(chǔ)上,前后各添加一個(gè)view data[data.length-1]、data[0]
void _initWidget() {
currentIndex = widget.controller.initialPage;
if (widget.children == null || widget.children.isEmpty) return;
if (widget.children.length == 1) {
_children.addAll(widget.children);
} else {
_children.add(widget.children[widget.children.length - 1]);
_children.addAll(widget.children);
_children.add(widget.children[0]);
}
}
當(dāng)用戶在滑動(dòng)到新位置的Page后,會(huì)觸發(fā)PageView的回調(diào)監(jiān)聽onPageChanged(int index),參數(shù)即為新選中的Page索引,此時(shí)我們需要及時(shí)將頁(yè)面切換到正確的位置
/// Page切換后的回調(diào),及時(shí)修復(fù)索引
_onPageChanged(int index) async {
if (index == 0) {
// 當(dāng)前選中的是第一個(gè)位置,自動(dòng)選中倒數(shù)第二個(gè)位置
currentIndex = _children.length - 2;
await Future.delayed(Duration(milliseconds: 400));
widget.controller?.get()?.jumpToPage(currentIndex);
realPosition = currentIndex - 1;
} else if (index == _children.length - 1) {
// 當(dāng)前選中的是倒數(shù)第一個(gè)位置,自動(dòng)選中第二個(gè)索引
currentIndex = 1;
await Future.delayed(Duration(milliseconds: 400));
widget.controller?.get()?.jumpToPage(currentIndex);
realPosition = 0;
} else {
currentIndex = index;
realPosition = index - 1;
if (realPosition < 0) realPosition = 0;
}
setState(() {});
}
你可能會(huì)發(fā)現(xiàn)在調(diào)用jumpToPage之前為什么延遲了400毫秒,這里做一個(gè)短暫的延遲是因?yàn)镻ageView在切換頁(yè)面后如果立即jumpToPage會(huì)出現(xiàn)卡頓的現(xiàn)象,做短暫延遲可以規(guī)避這個(gè)問(wèn)題。
定時(shí)切換
目前已經(jīng)實(shí)現(xiàn)了PageView的循環(huán)滑動(dòng),那么現(xiàn)在我們加一個(gè)定時(shí)器,每隔2s自動(dòng)切換下一個(gè)頁(yè)面。
/// 創(chuàng)建定時(shí)器
void createTimer() {
if (widget.isTimer) {
cancelTimer();
_timer = Timer.periodic(widget.delay, (timer) => _scrollPage());
}
}
/// 定時(shí)切換PageView的頁(yè)面
void _scrollPage() {
++currentIndex;
var next = currentIndex % _children?.length;
widget.controller?.get()?.animateToPage(
next,
duration: widget.duration,
curve: Curves.ease,
);
}
/// 開始定時(shí)滑動(dòng)
void _start() {
if (!widget.isTimer) return;
if (!isActive) return;
if (_children.length <= 1) return;
createTimer();
}
/// 停止定時(shí)滑動(dòng)
void _stop() {
if (!widget.isTimer) return;
cancelTimer();
}
/// 取消定時(shí)器
void cancelTimer() {
_timer?.cancel();
}
滑動(dòng)沖突
到這里就實(shí)現(xiàn)了可以定時(shí)自動(dòng)循環(huán)滑動(dòng)的PageView,但是看下實(shí)際效果你會(huì)發(fā)現(xiàn),當(dāng)用戶在滑動(dòng)過(guò)程中,定時(shí)器還在進(jìn)行,此時(shí)就需要取消定時(shí)器,當(dāng)用戶手指離開后再開啟定時(shí)器自動(dòng)輪播。
所以這里你可以給PageView包裹一層NotificationListener來(lái)監(jiān)聽用戶滑動(dòng)
@override
Widget build(BuildContext context) => NotificationListener(
onNotification: (notification) => _onNotification(notification),
child: PageView(
scrollDirection: widget.scrollDirection,
reverse: widget.reverse,
controller: widget.controller?.get(),
physics: widget.physics,
pageSnapping: widget.pageSnapping,
onPageChanged: (index) => _onPageChanged(index),
children: _children,
dragStartBehavior: widget.dragStartBehavior,
allowImplicitScrolling: widget.allowImplicitScrolling,
restorationId: widget.restorationId,
clipBehavior: widget.clipBehavior,
),
);
/// Page滑動(dòng)監(jiān)聽
_onNotification(notification) {
if (notification is ScrollStartNotification) {
isEnd = false;
} else if (notification is UserScrollNotification) {
// 用戶滑動(dòng)時(shí)回調(diào)順序:start - user , end - user
if (isEnd) {
isUserGesture = false;
_start();
return;
}
isUserGesture = true;
_stop();
} else if (notification is ScrollEndNotification) {
isEnd = true;
if (isUserGesture) {
_start();
}
}
}
值得注意的是,自動(dòng)滑動(dòng)和用戶滑動(dòng)都會(huì)觸發(fā)start、end事件,但是用戶滑動(dòng)時(shí)會(huì)觸發(fā)user事件,滑動(dòng)時(shí)回調(diào)順序:start - user 、 end - user,所以只需要在user事件回調(diào)中判斷是否手指離開了,即可區(qū)分用戶滑動(dòng)和頁(yè)面滑動(dòng),實(shí)現(xiàn)用戶滑動(dòng)狀態(tài)下暫停定時(shí)器,用戶手指離開后啟動(dòng)定時(shí)器。
看下最終的實(shí)現(xiàn)效果,代碼里時(shí)加了頁(yè)面圓點(diǎn)指示器的,可以參考代碼自定義配置。
插件地址:
