1 PageView禁止左滑或右滑效果图
2 PageView
PageView可实现Widget的整页滑动切换,可用于轮播图、App左右切换TAB页面、引导页切换页面等场景。
2.1 PageView示例
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),
),
),
);
}
}
画面效果
2.2 ScrollPhysics
ScrollPhysics 用于确定可滑动的Widget的滑动物理特效,常用几种的ScrollPhysics如下:
- NeverScrollableScrollPhysics
不允许左右滑动。 - BouncingScrollPhysics
iOS风格的滑动效果,有物理弹性,和上图的滑动效果一致。 - ClampingScrollPhysics
Android风格的滑动效果,无弹性效果,滑到底部后不允许继续滑。
PageView的physics 属性的默认值是PageScrollPhysics,若不自己设置,最终的滑动效果会根据系统的特性来展示。
3 自定义ScrollPhysics实现PageView禁止左滑或右滑
需求是禁止左滑或右滑,和ClampingScrollPhysics 的滑到底就禁止滑动有共通之处,所以自定义时可参考ClampingScrollPhysics
3.1 ClampingScrollPhysics源码
查看源码ClampingScrollPhysics源码
class ClampingScrollPhysics extends ScrollPhysics {
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)
return value - position.pixels;
if (position.maxScrollExtent <= position.pixels && position.pixels < value)
return value - position.pixels;
if (value < position.minScrollExtent && position.minScrollExtent < position.pixels)
return value - position.minScrollExtent;
if (position.pixels < position.maxScrollExtent && position.maxScrollExtent < value)
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个方法:
- applyTo 自定义ScrollPhysics必须重写的方法
- applyBoundaryConditions 通过返回值来确定是否已到达边界,即确定是否可滑动的方法
- createBallisticSimulation 确定滑动的效果
判断能否滑动的核心在于applyBoundaryConditions方法,在该方法中添加日志打印。
处于第一页,往右滑动无法滑动的日志。
处于最后一页,往左滑无法滑动的日志
现象
- 往左滑value会变大,往右滑value会变小
- 无法左滑时,position.pixels 等于 minScrollExtent
- 无法右滑时,position.pixels 等于 maxScrollExtent
- value 是本次滑动将要滑到的位置
- 假定position.pixels所处的位置不允许滑动(即已经是边界),value - position.pixels就是滑动将要超出边界的值
结论 在applyBoundaryConditions中只需返回(value - position.pixels)即可禁止滑动,返回0即可以滑动。
3.2 自定义ScrollPhysics
ScrollPhysics 由@immutable 标记,是不可变的,所以需要新建一个Controller来存储或改变需要的变量。
新建 CustomScrollPhysicsController
class CustomScrollPhysicsController {
late bool banSwipeRight;
late bool banSwipeLeft;
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) {
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(
physics: CustomScrollPhysics(controller: _physicsController),
children: List.generate(3, _buildChildPage),
),
_physicsController.banSwipeLeft = true;
_physicsController.banSwipeRight = true;
最终效果
|