Android原生里一般会使用ViewPager来实现Banner区域,当然Flutter中的PageView也可以实现类似的效果,今天就来撸一把循环滑动的PageView。
在Android中想要实现循环滑动的ViewPager,最常用的方法是,在原数据源的基础上,通过前后补位来操作:即准备新的数据集合list , 第一个位置插入原数据中的最后一个元素、最后一个位置插入原数据中的第一个元素,Flutter中PageView实现循环滑动的方法如出一辙,如下图所示:
在用户滑动过程中,当(2)被选中后,无动画切换到2的位置;当(0)被选中后,此时无动画切换到0的位置。即可实现循环滑动的PageView。
这里需要解释下,如果只有一个数据的话,不考虑循环滑动
/// 初始化Page
/// 准备一个新的数据源list
/// 在原数据data的基础上,前后各添加一个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]);
}
}
当用户在滑动到新位置的Page后,会触发PageView的回调监听onPageChanged(int index),参数即为新选中的Page索引,此时我们需要及时将页面切换到正确的位置
/// Page切换后的回调,及时修复索引
void _onPageChanged(int index) async {
if (index == 0) {
//当前选中的是第一个位置,自动选中倒数第二个位置
currentIndex = _children.length - 2;
await Future.delayed(widget.duration);
widget.controller?.get()?.jumpToPage(currentIndex);
realPosition = currentIndex - 1;
} else if (index == _children.length - 1) {
//当前选中的是倒数第一个位置,自动选中第二个索引
currentIndex = 1;
await Future.delayed(widget.duration);
widget.controller?.get()?.jumpToPage(currentIndex);
realPosition = 0;
} else {
currentIndex = index;
realPosition = index - 1;
if (realPosition < 0) realPosition = 0;
}
setState(() {});
}
目前已经实现了PageView的循环滑动,那么现在我们加一个定时器,每隔2s自动切换下一个页面。
/// 创建定时器
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();
}
到这里就实现了可以定时自动循环滑动的PageView,但是看下实际效果你会发现,当用户在滑动过程中,定时器还在进行,此时就需要取消定时器,当用户手指离开后再开启定时器自动轮播。
所以这里你可以给PageView包裹一层NotificationListener来监听用户滑动
@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: _onPageChanged,
children: _children,
dragStartBehavior: widget.dragStartBehavior,
allowImplicitScrolling: widget.allowImplicitScrolling,
restorationId: widget.restorationId,
clipBehavior: widget.clipBehavior,
),
);
/// Page滑动监听
_onNotification(notification) {
if (notification is ScrollStartNotification) {
isEnd = false;
} else if (notification is UserScrollNotification) {
//用户滑动时回调顺序:start - user , end - user
if (isEnd) {
isUserGesture = false;
_start();
return;
}
isUserGesture = true;
_stop();
} else if (notification is ScrollEndNotification) {
isEnd = true;
if (isUserGesture) {
_start();
}
}
}
值得注意的是,自动滑动和用户滑动都会触发start、end事件,但是用户滑动时会触发user事件,滑动时回调顺序:start - user 、 end - user,所以只需要在user事件回调中判断是否手指离开了,即可区分用户滑动和页面滑动,实现用户滑动状态下暂停定时器,用户手指离开后启动定时器。
看下最终的实现效果,代码里时加了页面圆点指示器的,可以参考代码自定义配置。
插件地址:
Github
pub.dev