2019/12/5 早
Flutter - 分页滑动的ListView
在类似图片查看的应用场景中,通常需要让ListView在滑动结束后停留在指定的分页位置,本篇就是使用Flutter ListView来实现这一需求
1.思路
- 创建一个
ListView
,且指定ScrollController
:页面的移动停留都需要ScrollController
来操作. - 对
ListView
的滑动进行监听:在ListView
外部包裹一个Listener
控件,同过其提供的api进行触摸监听. - 在滑动监听的同时,对触摸释放进行判断,并设置对应的事件。
2.创建ListView
创建一个基本的ListView
,并对其设置指定的滑动方向(这很重要,因为后续的滑动计算和滑动方向是有着直接关联的),指定的ScrollController
:
ScrollController _scrollController = new ScrollController();
List data = buildDefaultList();
ListView.builder(
physics: BouncingScrollPhysics(),
controller: _scrollController,
scrollDirection:Axis.horizontal,
itemCount: data.length,
itemBuilder: (BuildContext context, int index) {
return buildImageItemView(index);
},
),
3.创建触摸监听部件Listener
3.1 创建Listener
:
Offset pointerStart;
Offset pointerEnd;
Listener(
onPointerDown: (event) {
//保存触摸按下的位置信息
pointerStart = event.position;
},
onPointerUp: getPonitUpListenerInHorizontal()
child: child,
);
这里举例仅仅使用横向正向滑动,所以我们只需要关心触摸按下事件与触摸离开事件,其实Listener
也就提供了如下参数:
const Listener({
Key key,
this.onPointerDown,//触摸按下
this.onPointerMove,//触摸滑动
this.onPointerUp,//触摸抬起
this.onPointerCancel,//触摸取消
this.behavior = HitTestBehavior.deferToChild,
Widget child
}) : assert(behavior != null),
super(key: key, child: child);
3.2实现触摸抬起监听
onPointerUp
的类型为PointerUpEventListener
,暴露出一个PointerUpEvent
,通过读取PointerUpEvent
的position
来获取当前的位置,进而判断并执行需要处理的操作。
代码如下:
//获取屏幕宽度
Size screenSize = MediaQuery.of(context).size;
screenWidth = screenSize.width;
/**
* 构造横向滑动时候的触摸抬起监听
*/
PointerUpEventListener getPonitUpListenerInHorizontal() {
return (event) {
pointerEnd = event.position;
touchRangeX = pointerStart.dx - pointerEnd.dx;
touchRangeY =pointerStart.dy - pointerEnd.dy;
//所有的操作必须要满足滑动距离>10才算是滑动
if (touchRangeX.abs() < 10) {
nextOffset = screenWidth * lastPage;
scrollAnimToOffset(_scrollController, nextOffset, () {
if (lastPage < 0) {
lastPage = 0;
}
});
return;
}
//纵向操作大于横向操作三倍视为纵向操作
//这个判断拦截只有在纵向操作距离大于20.0的时候才生效
if (touchRangeX.abs() < touchRangeY.abs() && touchRangeY > 20) {
nextOffset = screenWidth * lastPage;
scrollAnimToOffset(_scrollController, nextOffset, () {
if (lastPage < 0) {
lastPage = 0;
}
});
return;
}
//如果滑动小于当前屏幕1/8,那么就回弹复原,超过则移动到下一页
//跳转到下一页或者上一页或者不动
if (touchRangeX > screenWidth / 8) {
nextOffset = screenWidth * (lastPage + 1);
print("animate to ${nextOffset}");
scrollAnimToOffset(_scrollController, nextOffset, () {
lastPage++;
if (lastPage >= _chapterDetail.data.length - 1) {
lastPage = _chapterDetail.data.length - 1;
}
});
} else if (touchRangeX < -1 * screenWidth / 8) {
nextOffset = screenWidth * (lastPage - 1);
print("animate to ${nextOffset}");
scrollAnimToOffset(_scrollController, nextOffset, () {
lastPage--;
if (lastPage < 0) {
lastPage = 0;
}
});
} else {
scrollAnimToOffset(_scrollController, screenWidth * lastPage, null);
}
};
}
列表滑动方法scrollAnimToOffset
:
/**
* 滑动到指定位置
*/
void scrollAnimToOffset(ScrollController controller, double offset,
void Function() onScrollCompleted) {
controller
.animateTo(offset,
duration: Duration(
milliseconds: 200,
),
curve: Curves.easeIn)
.then((v) {
if (onScrollCompleted != null) {
onScrollCompleted();
}
}).catchError((e) {
print(e);
});
}