flutter 之真手势冲突处理

284 阅读2分钟

看过好多说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));
  }

}