Flutter的滑动冲突 - 右滑父响应,左滑子响应

2,606 阅读2分钟

实现效果

右滑「父组件PageView」响应,左滑「子组件PageView的Item」响应

scroll_conflict.gif

滑动基础组件

ScrollController - 滑动控制器

控制可滚动组件的滚动位置以及获取滚动状态属性(真正控制滚动行为的是ScrollPosition

ScrollPhysics - 滑动物理模拟

滑动模拟器,将用户的交互处理成更接近真实世界的行为,例如,手指快速滑动之后,产生的惯性滑动效果。

  • BouncingScrollPhysics : 允许滚动超出边界,但之后内容会反弹回来
  • ClampingScrollPhysics : 防止滚动超出边界,夹住
  • AlwaysScrollableScrollPhysics :默认,始终响应用户的滚动
  • NeverScrollableScrollPhysics : 不响应用户的滚动

RawGestureDetector - 原始手势识别器

获取发生的所有「指针事件」,检测手势类型, 并将其发送到注册的识别器。

Drag - 滑动手势更新

当识别为滑动手势,控制器会调用「Drag.update」触发组件更新。同理,手势结束/取消,会调用「Drag.end/cancel」表示滑动结束。

代码实现

自定义手势识别器

默认情况下,子组件获得手势事件并消费事件,父组件是无法收到手势事件,因此自定义「RawGestureDetector」重写「rejectGesture」函数,在事件被拒绝时,手动处理为接收。

// 仅针对与横向滑动手势处理。
class CustomHorizontalDragGestureRecognizer
    extends HorizontalDragGestureRecognizer {
  @override
  void rejectGesture(int pointer) {
    acceptGesture(pointer);
  }
}

滑动处理(滑动冲突核心)

  • 滑动开始(可以理解为Down事件),默认父组件获取滑动事件。并获取父组件的「Drag」实例
  • 滑动中,根据滑动方向来判断此滑动事件处理组件,设置Drag和ScrollPhysics滑动行为来控制
  • 滑动结束(可以理解为Up事件)
// 初始化,定义滑动手势处理规则
initGestureRecognizer() {
    _gestureRecognizers = <Type, GestureRecognizerFactory>{
      CustomHorizontalDragGestureRecognizer:
          GestureRecognizerFactoryWithHandlers<
                  CustomHorizontalDragGestureRecognizer>(
              () => CustomHorizontalDragGestureRecognizer(),
              (CustomHorizontalDragGestureRecognizer instance) {
        instance
          ..onStart = _handleDragStart
          ..onUpdate = _handleDragUpdate
          ..onEnd = _handleDragEnd
          ..onCancel = _handleDragCancel;
      }),
    };
}

// 滑动开始
_handleDragStart(details) {
    _drag = _pageController.position.drag(details, _disposeDrag);
}

// 滑动中
_handleDragUpdate(details) {
 // 右滑pageview获取滑动事件
 if ((details.delta.dx > 0 || details.delta.dx == 0) && !_isOpened) {
      _refreshPhysics(false);
      _drag?.update(details);
    } else {
      _refreshPhysics(true);
      _drag?.cancel();
    }
 }

  // 滑动未停止不处理滑动事件
_refreshPhysics(bool isSlideLeft) {
  if (isSlideLeft) {
      if (_physics.parent is BouncingScrollPhysics) {
        _physics.applyTo(NeverScrollableScrollPhysics());
      }
  } else {
      if (_physics.parent is NeverScrollableScrollPhysics) {
        _physics.applyTo(BouncingScrollPhysics());
      }
  }
}
// 滑动结束
_handleDragEnd(details) {
    _drag?.end(details);
  }

_handleDragCancel() {
    assert(_drag == null);
    _drag?.cancel();
  }

最终使用

RawGestureDetector(
      gestures: _gestureRecognizers,
      child: PageView.builder(
          controller: _pageController,
          itemCount: 5,
          physics: _physics,
          itemBuilder: (BuildContext context, int index) {
            return itemWidget(index);
          }),
);

源码地址

总结

此功能的实现,除了钻源码,还很感谢广大Flutter开发者的分享。 Flutter的生态相比于Android和iOS,还有很大差距。 不过,我相信参与分享的人会越来越多。 从我做起。我会把开发过程中遇到的问题和难点,尽可能通俗详细的分享。

原文链接

参考链接

How to "merge" scrolls on a TabBarView inside a PageView?

深入进阶-从一次点击探寻Flutter事件分发原理