通过 Listener 解决 Slider 滑动冲突

问题背景

Flutter 中,我们经常使用 ScrollView + Slider 这样的场景。

但是在这样的场景下,存在用户体验并不好的问题:

列表滑动的过程中 Slider 不能响应

举例:

1. 滑动未完成,Slider 不能响应

SingleChildScrollView 在我们手指抬起的过程中,还是会有一定的惯性, 列表不会立刻停止。它这样做是为了用户体验,让用户感觉到丝滑。

当遇到 Slider 会发生什么情况?

不能滑动可以滑动
以下是未处理的效果:以下是优化后的效果:
不能滑动1.gif可以滑动2.gif

以上是开启了 FlutterDisable Slow Animations 功能展示的效果

慢动画展示的效果

根据视频观察到:

当手指抬起时,列表会根据惯性滑动一段距离,如果此时点击列表,列表会停止滑动

意味着,如果用户想在这个时候想滑动 Slider 的话,需要等 SingleChildScrollView 完全停止的情况下,才可以滑动 Slider

2. 滑动溢出,Slider 不能响应

SingleChildScrollView 中设置了 physicsBouncingScrollPhysics()

这样设置后 ScrollView 就会有滑动溢出的效果,对于用户来说能获得较好的用户体验

但是不巧的是 Slider 在这种情况下不能响应滑动事件

不能滑动可以滑动
以下是未处理的效果:以下是优化后的效果:
不能滑动.gif可以滑动.gif
3. 解决方案

一句话总结:ListeneronPointerMove 中计算 value 值。

具体方案:

  • 通过 Listener 监听原始指针事件,拿到 PointerEvent 对象。

  • 再通过 GlobalKey 拿到 SliderSingleChildScrollView 中的位置。

  • 通过他们可以在 onPointerMove 中计算 Slidervalue 的值应该滑动的位置。

class SliderPage extends StatefulWidget {
  @override
  _SliderPageState createState() => _SliderPageState();
}

Rect rect = Rect.zero;

class _SliderPageState extends State<SliderPage> {
  String? text;
  double value = 0;
  GlobalKey key = GlobalKey();
  ScrollController controller = ScrollController();

  Widget _buildBody() {
    return SizedBox(
      height: MediaQuery.of(context).size.height,
      child: Listener(
        onPointerMove: (p) {
          final box = key.currentContext!.findRenderObject()! as RenderBox;
          final offset = box.localToGlobal(Offset.zero);
          final size = box.size;
          rect = Rect.fromPoints(offset, offset.translate(size.width, size.height));
          setState(() {});

          if (rect.contains(p.position)) {
            value = ((p.position.dx) / (rect.width - 2 * 24 )).clamp(0, 1);
            setState(() {});
          }
        },
        child: Column(
          children: [
            SizedBox(
              height: 555,
              child: SingleChildScrollView(
                controller: controller,
                physics: BouncingScrollPhysics(),
                dragStartBehavior: DragStartBehavior.down,
                child: Column(
                  children: [
                    Container(
                      height: 444,
                      color: Colors.blue,
                    ),
                    Listener(
                      onPointerDown: (p) {},
                      onPointerMove: (p) {
                        // print('Slider ${p}');
                      },
                      child: Slider(
                        key: key,
                        value: value,
                        autofocus: true,
                        onChanged: (v) {
                          value = v;
                          setState(() {});
                        },
                      ),
                    ),
                    Container(
                      height: 333,
                      color: Colors.blue,
                    ),
                  ],
                ),
              ),
            ),
            Expanded(child: Container(color: Colors.blue))
          ],
        ),
      ),
    );
  }

4. 总结:
  1. Slider 只有当 SingleChildScrollView 完全停止的情况下才可以其他事件。

原因: 当指针按下时,Flutter 会对应用程序执行命中测试(Hit Test) ,以确定指针与屏幕接触的位置存在哪些组件(widget), 指针按下事件(以及该指针的后续事件)然后被分发到由命中测试发现的最内部的组件

  1. Listener 中计算 value 值,可能是不准确。因为拿到 Sinder 的长度还需要拿到 SinderPadding,才能保证计算的准确性。

  2. 这篇文章提供一个解决滑动冲突的思路,如果有其他思路欢迎沟通交流。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值