• Flutter 自定义ScrollPhysics实现PageView禁止左滑或右滑


    1 PageView禁止左滑或右滑效果图

    在这里插入图片描述


    2 PageView

    PageView可实现Widget的整页滑动切换,可用于轮播图、App左右切换TAB页面、引导页切换页面等场景。

    2.1 PageView示例

    class PageViewPage extends StatefulWidget {
      const PageViewPage({Key? key}) : super(key: key);
    
      @override
      State<StatefulWidget> createState() => _PageViewPageState();
    }
    
    class _PageViewPageState extends State<PageViewPage> {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: PageView(
            children: List.generate(3, _buildChildPage),
          ),
        );
      }
    
      Widget _buildChildPage(int index) {
        return Container(
          color: Colors.primaries[index * 2],
          child: Center(
            child: Text(
              'Page  $index',
              style: const TextStyle(color: Colors.white, fontSize: 24),
            ),
          ),
        );
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    画面效果
    在这里插入图片描述


    2.2 ScrollPhysics

    ScrollPhysics 用于确定可滑动的Widget的滑动物理特效,常用几种的ScrollPhysics如下:

    • NeverScrollableScrollPhysics
      不允许左右滑动。
    • BouncingScrollPhysics
      iOS风格的滑动效果,有物理弹性,和上图的滑动效果一致。
    • ClampingScrollPhysics
      Android风格的滑动效果,无弹性效果,滑到底部后不允许继续滑。

    PageView的physics属性的默认值是PageScrollPhysics,若不自己设置,最终的滑动效果会根据系统的特性来展示。

    3 自定义ScrollPhysics实现PageView禁止左滑或右滑

    需求是禁止左滑或右滑,和ClampingScrollPhysics的滑到底就禁止滑动有共通之处,所以自定义时可参考ClampingScrollPhysics

    3.1 ClampingScrollPhysics源码

    查看源码ClampingScrollPhysics源码

    class ClampingScrollPhysics extends ScrollPhysics {
      /// Creates scroll physics that prevent the scroll offset from exceeding the
      /// bounds of the content.
      const ClampingScrollPhysics({ ScrollPhysics? parent }) : super(parent: parent);
    
      @override
      ClampingScrollPhysics applyTo(ScrollPhysics? ancestor) {
        return ClampingScrollPhysics(parent: buildParent(ancestor));
      }
    
      @override
      double applyBoundaryConditions(ScrollMetrics position, double value) {
        print(
            'value:$value  maxScrollExtent:${position.maxScrollExtent}  minScrollExtent:${position.minScrollExtent}');
        print('position.pixels:${position.pixels}');
        print('value - position.pixels:${value - position.pixels}');
    
        if (value < position.pixels && position.pixels <= position.minScrollExtent) // underscroll
          return value - position.pixels;
        if (position.maxScrollExtent <= position.pixels && position.pixels < value) // overscroll
          return value - position.pixels;
        if (value < position.minScrollExtent && position.minScrollExtent < position.pixels) // hit top edge
          return value - position.minScrollExtent;
        if (position.pixels < position.maxScrollExtent && position.maxScrollExtent < value) // hit bottom edge
          return value - position.maxScrollExtent;
        return 0.0;
      }
    
      @override
      Simulation? createBallisticSimulation(ScrollMetrics position, double velocity) {
        final Tolerance tolerance = this.tolerance;
        if (position.outOfRange) {
          double? end;
          if (position.pixels > position.maxScrollExtent)
            end = position.maxScrollExtent;
          if (position.pixels < position.minScrollExtent)
            end = position.minScrollExtent;
          assert(end != null);
          return ScrollSpringSimulation(
            spring,
            position.pixels,
            end!,
            math.min(0.0, velocity),
            tolerance: tolerance,
          );
        }
        if (velocity.abs() < tolerance.velocity)
          return null;
        if (velocity > 0.0 && position.pixels >= position.maxScrollExtent)
          return null;
        if (velocity < 0.0 && position.pixels <= position.minScrollExtent)
          return null;
        return ClampingScrollSimulation(
          position: position.pixels,
          velocity: velocity,
          tolerance: tolerance,
        );
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    发现只重写了3个方法:

    • applyTo 自定义ScrollPhysics必须重写的方法
    • applyBoundaryConditions 通过返回值来确定是否已到达边界,即确定是否可滑动的方法
    • createBallisticSimulation 确定滑动的效果

    判断能否滑动的核心在于applyBoundaryConditions方法,在该方法中添加日志打印。

    处于第一页,往右滑动无法滑动的日志。
    在这里插入图片描述

    处于最后一页,往左滑无法滑动的日志
    在这里插入图片描述

    现象

    • 往左滑value会变大,往右滑value会变小
    • 无法左滑时,position.pixels 等于 minScrollExtent
    • 无法右滑时,position.pixels 等于 maxScrollExtent
    • value 是本次滑动将要滑到的位置
    • 假定position.pixels所处的位置不允许滑动(即已经是边界),value - position.pixels就是滑动将要超出边界的值

    结论
    在applyBoundaryConditions中只需返回(value - position.pixels)即可禁止滑动,返回0即可以滑动。

    3.2 自定义ScrollPhysics

    ScrollPhysics 由@immutable标记,是不可变的,所以需要新建一个Controller来存储或改变需要的变量。

    新建 CustomScrollPhysicsController

    class CustomScrollPhysicsController {
      // 禁止左滑或右滑
      late bool banSwipeRight;
      late bool banSwipeLeft;
    
      // 记录 CustomScrollPhysics 滑动的值
      double? _lastSwipePosition;
    
      CustomScrollPhysicsController({
        this.banSwipeRight = false,
        this.banSwipeLeft = false,
      });
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    因为需求与ClampingScrollPhysics很相似,所以直接继承于ClampingScrollPhysics

    class CustomScrollPhysics extends ClampingScrollPhysics {
      final CustomScrollPhysicsController controller;
    
      const CustomScrollPhysics({
        super.parent,
        required this.controller,
      });
    
      /// 必须实现的方法
      @override
      CustomScrollPhysics applyTo(ScrollPhysics? ancestor) {
        return CustomScrollPhysics(
          parent: buildParent(ancestor),
          controller: controller,
        );
      }
    
      /// 返回值,确定要限制滑动的距离
      @override
      double applyBoundaryConditions(ScrollMetrics position, double value) {
        // 处理 禁止左滑或右滑
        final lastSwipePosition = controller._lastSwipePosition;
        if (lastSwipePosition != null) {
          // 手势往左滑 value值会越来越大,往右滑 value会越来越小
          // 此时将要往左滑   但禁止往左滑
          if (value > lastSwipePosition && controller.banSwipeLeft) {
            // 返回要限制的滑动距离 抵消滑动
            return value - position.pixels;
          }
          // 此时将要往右滑 但禁止往右滑
          if (value < lastSwipePosition && controller.banSwipeRight) {
            // 返回要限制的滑动距离 抵消滑动
            return value - position.pixels;
          }
        }
        controller._lastSwipePosition = value;
    
        return super.applyBoundaryConditions(position, value);
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    使用方式

    final _physicsController = CustomScrollPhysicsController();
    
    // 构建PageView
     PageView(
       physics: CustomScrollPhysics(controller: _physicsController),
       children: List.generate(3, _buildChildPage),
     ),
    
    // 设置是否禁用滑动
    _physicsController.banSwipeLeft = true;
    _physicsController.banSwipeRight = true;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    最终效果

    在这里插入图片描述

  • 相关阅读:
    关于c++中和java中 集合添加元素的区别
    YOLO V5、SAM、RESNET50模型在GPU环境下搭建过程
    数据结构 2.1 单链表
    如何快速看懂复杂代码
    K-近邻算法
    基础知识集合
    Java JVM相关
    SpringMVC 06: 日期类型的变量的注入和显示
    TensorFlow新文档发布:新增CLP、DTensor...最先进的模型已就绪
    [SpringBoot]配置文件②(多环境配置,配置文件分类)
  • 原文地址:https://blog.csdn.net/ww897532167/article/details/125520964