PageView可实现Widget的整页滑动切换,可用于轮播图、App左右切换TAB页面、引导页切换页面等场景。
class PageViewPage extends StatefulWidget {
const PageViewPage({Key? key}) : super(key: key);
@override
State<StatefulWidget> createState() => _PageViewPageState();
}
class _PageViewPageState extends State<PageViewPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: PageView(
children: List.generate(3, _buildChildPage),
),
);
}
Widget _buildChildPage(int index) {
return Container(
color: Colors.primaries[index * 2],
child: Center(
child: Text(
'Page $index',
style: const TextStyle(color: Colors.white, fontSize: 24),
),
),
);
}
}
ScrollPhysics 用于确定可滑动的Widget的滑动物理特效,常用几种的ScrollPhysics如下:
PageView的physics
属性的默认值是PageScrollPhysics,若不自己设置,最终的滑动效果会根据系统的特性来展示。
需求是禁止左滑或右滑,和ClampingScrollPhysics
的滑到底就禁止滑动有共通之处,所以自定义时可参考ClampingScrollPhysics
查看源码ClampingScrollPhysics源码
class ClampingScrollPhysics extends ScrollPhysics {
/// Creates scroll physics that prevent the scroll offset from exceeding the
/// bounds of the content.
const ClampingScrollPhysics({ ScrollPhysics? parent }) : super(parent: parent);
@override
ClampingScrollPhysics applyTo(ScrollPhysics? ancestor) {
return ClampingScrollPhysics(parent: buildParent(ancestor));
}
@override
double applyBoundaryConditions(ScrollMetrics position, double value) {
print(
'value:$value maxScrollExtent:${position.maxScrollExtent} minScrollExtent:${position.minScrollExtent}');
print('position.pixels:${position.pixels}');
print('value - position.pixels:${value - position.pixels}');
if (value < position.pixels && position.pixels <= position.minScrollExtent) // underscroll
return value - position.pixels;
if (position.maxScrollExtent <= position.pixels && position.pixels < value) // overscroll
return value - position.pixels;
if (value < position.minScrollExtent && position.minScrollExtent < position.pixels) // hit top edge
return value - position.minScrollExtent;
if (position.pixels < position.maxScrollExtent && position.maxScrollExtent < value) // hit bottom edge
return value - position.maxScrollExtent;
return 0.0;
}
@override
Simulation? createBallisticSimulation(ScrollMetrics position, double velocity) {
final Tolerance tolerance = this.tolerance;
if (position.outOfRange) {
double? end;
if (position.pixels > position.maxScrollExtent)
end = position.maxScrollExtent;
if (position.pixels < position.minScrollExtent)
end = position.minScrollExtent;
assert(end != null);
return ScrollSpringSimulation(
spring,
position.pixels,
end!,
math.min(0.0, velocity),
tolerance: tolerance,
);
}
if (velocity.abs() < tolerance.velocity)
return null;
if (velocity > 0.0 && position.pixels >= position.maxScrollExtent)
return null;
if (velocity < 0.0 && position.pixels <= position.minScrollExtent)
return null;
return ClampingScrollSimulation(
position: position.pixels,
velocity: velocity,
tolerance: tolerance,
);
}
}
发现只重写了3个方法:
判断能否滑动的核心在于applyBoundaryConditions方法,在该方法中添加日志打印。
现象
结论
在applyBoundaryConditions中只需返回(value - position.pixels)即可禁止滑动,返回0即可以滑动。
ScrollPhysics 由@immutable
标记,是不可变的,所以需要新建一个Controller来存储或改变需要的变量。
新建 CustomScrollPhysicsController
class CustomScrollPhysicsController {
// 禁止左滑或右滑
late bool banSwipeRight;
late bool banSwipeLeft;
// 记录 CustomScrollPhysics 滑动的值
double? _lastSwipePosition;
CustomScrollPhysicsController({
this.banSwipeRight = false,
this.banSwipeLeft = false,
});
}
因为需求与ClampingScrollPhysics很相似,所以直接继承于ClampingScrollPhysics
class CustomScrollPhysics extends ClampingScrollPhysics {
final CustomScrollPhysicsController controller;
const CustomScrollPhysics({
super.parent,
required this.controller,
});
/// 必须实现的方法
@override
CustomScrollPhysics applyTo(ScrollPhysics? ancestor) {
return CustomScrollPhysics(
parent: buildParent(ancestor),
controller: controller,
);
}
/// 返回值,确定要限制滑动的距离
@override
double applyBoundaryConditions(ScrollMetrics position, double value) {
// 处理 禁止左滑或右滑
final lastSwipePosition = controller._lastSwipePosition;
if (lastSwipePosition != null) {
// 手势往左滑 value值会越来越大,往右滑 value会越来越小
// 此时将要往左滑 但禁止往左滑
if (value > lastSwipePosition && controller.banSwipeLeft) {
// 返回要限制的滑动距离 抵消滑动
return value - position.pixels;
}
// 此时将要往右滑 但禁止往右滑
if (value < lastSwipePosition && controller.banSwipeRight) {
// 返回要限制的滑动距离 抵消滑动
return value - position.pixels;
}
}
controller._lastSwipePosition = value;
return super.applyBoundaryConditions(position, value);
}
}
使用方式
final _physicsController = CustomScrollPhysicsController();
// 构建PageView
PageView(
physics: CustomScrollPhysics(controller: _physicsController),
children: List.generate(3, _buildChildPage),
),
// 设置是否禁用滑动
_physicsController.banSwipeLeft = true;
_physicsController.banSwipeRight = true;
最终效果