序言
Android原生里一般會使用ViewPager來實現(xiàn)Banner區(qū)域腮鞍,當(dāng)然Flutter中的PageView也可以實現(xiàn)類似的效果,今天就來擼一把循環(huán)滑動的PageView。
在Android中想要實現(xiàn)循環(huán)滑動的ViewPager,最常用的方法是,在原數(shù)據(jù)源的基礎(chǔ)上鳄哭,通過前后補(bǔ)位來操作:即準(zhǔn)備新的數(shù)據(jù)集合list , 第一個位置插入原數(shù)據(jù)中的最后一個元素、最后一個位置插入原數(shù)據(jù)中的第一個元素翔怎,F(xiàn)lutter中PageView實現(xiàn)循環(huán)滑動的方法如出一轍,如下圖所示:
在用戶滑動過程中杨耙,當(dāng)(2)被選中后赤套,無動畫切換到2的位置;當(dāng)(0)被選中后珊膜,此時無動畫切換到0的位置容握。即可實現(xiàn)循環(huán)滑動的PageView。
準(zhǔn)備新的數(shù)據(jù)源
這里需要解釋下车柠,如果只有一個數(shù)據(jù)的話剔氏,不考慮循環(huán)滑動
/// 初始化Page
/// 準(zhǔn)備一個新的數(shù)據(jù)源list
/// 在原數(shù)據(jù)data的基礎(chǔ)上,前后各添加一個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)用戶在滑動到新位置的Page后谈跛,會觸發(fā)PageView的回調(diào)監(jiān)聽onPageChanged(int index),參數(shù)即為新選中的Page索引,此時我們需要及時將頁面切換到正確的位置
/// Page切換后的回調(diào)塑陵,及時修復(fù)索引
_onPageChanged(int index) async {
if (index == 0) {
// 當(dāng)前選中的是第一個位置感憾,自動選中倒數(shù)第二個位置
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ù)第一個位置,自動選中第二個索引
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(() {});
}
你可能會發(fā)現(xiàn)在調(diào)用jumpToPage之前為什么延遲了400毫秒令花,這里做一個短暫的延遲是因為PageView在切換頁面后如果立即jumpToPage會出現(xiàn)卡頓的現(xiàn)象阻桅,做短暫延遲可以規(guī)避這個問題。
定時切換
目前已經(jīng)實現(xiàn)了PageView的循環(huán)滑動兼都,那么現(xiàn)在我們加一個定時器嫂沉,每隔2s自動切換下一個頁面。
/// 創(chuàng)建定時器
void createTimer() {
if (widget.isTimer) {
cancelTimer();
_timer = Timer.periodic(widget.delay, (timer) => _scrollPage());
}
}
/// 定時切換PageView的頁面
void _scrollPage() {
++currentIndex;
var next = currentIndex % _children?.length;
widget.controller?.get()?.animateToPage(
next,
duration: widget.duration,
curve: Curves.ease,
);
}
/// 開始定時滑動
void _start() {
if (!widget.isTimer) return;
if (!isActive) return;
if (_children.length <= 1) return;
createTimer();
}
/// 停止定時滑動
void _stop() {
if (!widget.isTimer) return;
cancelTimer();
}
/// 取消定時器
void cancelTimer() {
_timer?.cancel();
}
滑動沖突
到這里就實現(xiàn)了可以定時自動循環(huán)滑動的PageView扮碧,但是看下實際效果你會發(fā)現(xiàn)趟章,當(dāng)用戶在滑動過程中,定時器還在進(jìn)行,此時就需要取消定時器尤揣,當(dāng)用戶手指離開后再開啟定時器自動輪播搔啊。
所以這里你可以給PageView包裹一層NotificationListener來監(jiān)聽用戶滑動
@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滑動監(jiān)聽
_onNotification(notification) {
if (notification is ScrollStartNotification) {
isEnd = false;
} else if (notification is UserScrollNotification) {
// 用戶滑動時回調(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();
}
}
}
值得注意的是,自動滑動和用戶滑動都會觸發(fā)start北戏、end事件负芋,但是用戶滑動時會觸發(fā)user事件,滑動時回調(diào)順序:start - user 嗜愈、 end - user旧蛾,所以只需要在user事件回調(diào)中判斷是否手指離開了,即可區(qū)分用戶滑動和頁面滑動蠕嫁,實現(xiàn)用戶滑動狀態(tài)下暫停定時器锨天,用戶手指離開后啟動定時器。
看下最終的實現(xiàn)效果剃毒,代碼里時加了頁面圓點指示器的病袄,可以參考代碼自定義配置。
插件地址: