0
点赞
收藏
分享

微信扫一扫

通过 Listener 解决 Slider 滑动冲突

问题背景 

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

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

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

举例:

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

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

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

不能滑动

可以滑动

以下是未处理的效果:

以下是优化后的效果:

通过 Listener 解决 Slider 滑动冲突_ci

通过 Listener 解决 Slider 滑动冲突_ide_02

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

慢动画展示的效果

根据视频观察到:

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

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

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

SingleChildScrollView 中设置了 physicsBouncingScrollPhysics()

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

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

不能滑动

可以滑动

以下是未处理的效果:

以下是优化后的效果:

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. 这篇文章提供一个解决滑动冲突的思路,如果有其他思路欢迎沟通交流。

想要了解更多Anrloid相关知识可以点击下方课堂链接                  https://edu.51cto.com/course/32703.html Android课


举报

相关推荐

0 条评论