看过好多说flutter手势冲突的文章,但大多都是说的都是官方组件之间的联动。但是作为一个Android 开发者,我理解的手势冲突更应该是去处理父父子子的关系。可以动态的决定这个事件是父处理还是子处理。
flutter 滑动组件一般都可以设置ScrollPhysics,我们通过重写ScrollPhysics的applyPhysicsToUserOffset方法来进行事件的分发。当组件有手势滑动的时候,会先回调这个方法,这个方法的返回值决定这次手势真正处理的数值,比如返回0,这个组件就不会滑动。
class _ChildScrollPhysics extends ScrollPhysics {
final ScrollController controller;
const _ChildScrollPhysics(this.controller, {super.parent});
@override
double applyPhysicsToUserOffset(ScrollMetrics position, double offset) {
var isReach = (
(position.pixels <= position.minScrollExtent && offset > 0 ) || // 到达顶部
(position.pixels >= position.maxScrollExtent && offset < 0) // 到达底部
);
if(isReach){
controller.jumpTo(controller.position.pixels - offset);
return 0;
}else {
return a;
}
}
@override
_ChildScrollPhysics applyTo(ScrollPhysics? ancestor) {
return _ChildScrollPhysics(controller,parent: buildParent(ancestor));
}
}
var contoller = ScrollController();
class ScrollPhysicsTestPage extends StatelessWidget {
const ScrollPhysicsTestPage({super.key});
@override
Widget build(BuildContext context) {
return Material(
color: Colors.white,
child: ListView.builder(
controller: contoller,
physics: ClampingScrollPhysics(),
itemBuilder: (context,index){
if(index == 3){
return SizedBox(
height: 300,
child: ListView.builder(
padding: EdgeInsets.zero,
physics: _ChildScrollPhysics(contoller),
itemBuilder: (context,index){
return Container(height: 100,color: Colors.blue,
margin: const EdgeInsets.only(bottom: 20));
},
itemCount: 10,
),
);
}
return Container(height: 100,color: Colors.red,
margin: const EdgeInsets.only(bottom: 20)
);
}),
);
}
}
_ChildScrollPhysics 中的controll 是父节点的controller。
var isReach = (
(position.pixels <= position.minScrollExtent && offset > 0 ) || // 到达顶部
(position.pixels >= position.maxScrollExtent && offset < 0) // 到达底部
);
这部分代码是判断子节点是否滑动到边界。
if(isReach){
controller.jumpTo(controller.position.pixels - offset);
return 0;
}else {
return a;
}
如果到了边界交给父节点的controller 去处理。没有到边界自己全权处理。
这里增加 applyBoundaryConditions 也就是边界处理。上面的方式父节点设置的ScrollPhysics的边界处理不会生效,比如ClampingScrollPhysics 夹紧的效果。
class _ChildScrollPhysics extends ScrollPhysics {
final ScrollController parentController;
const _ChildScrollPhysics(this.parentController, {super.parent});
@override
double applyPhysicsToUserOffset(ScrollMetrics position, double offset) {
// 子滚动先处理自身偏移
final childProcessedOffset = super.applyPhysicsToUserOffset(position, offset);
// 检查子滚动是否到达边界
final atTop = position.pixels <= position.minScrollExtent && childProcessedOffset > 0;
final atBottom = position.pixels >= position.maxScrollExtent && childProcessedOffset < 0;
if ((atTop || atBottom) && parentController.hasClients) {
// 动态获取父滚动当前的物理效果(核心)
final parentPosition = parentController.position;
final parentPhysics = parentPosition.physics;
// 计算传递给父滚动的偏移(方向反转)
final parentOffset = childProcessedOffset;
// 应用父滚动物理效果处理偏移
final parentProcessedOffset = parentPhysics.applyPhysicsToUserOffset(
parentPosition,
parentOffset,
);
var oldPixels = parentPosition.pixels;
var nPixels = oldPixels - parentProcessedOffset;
final value = parentPhysics.applyBoundaryConditions(parentPosition, nPixels);
// 计算父滚动新位置并应用(使用animateTo确保物理效果生效)
final newParentPixels = nPixels - value;
parentController.jumpTo(
newParentPixels
);
return 0; // 子滚动不再消耗偏移
}
return childProcessedOffset;
}
@override
_ChildScrollPhysics applyTo(ScrollPhysics? ancestor) {
return _ChildScrollPhysics(parentController,parent: buildParent(ancestor));
}
}