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

文章目录

  • 1 PageView禁止左滑或右滑效果图
  • 2 PageView
    • 2.1 PageView示例
    • 2.2 ScrollPhysics
  • 3 自定义ScrollPhysics实现PageView禁止左滑或右滑
    • 3.1 ClampingScrollPhysics源码
    • 3.2 自定义ScrollPhysics

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),
        ),
      ),
    );
  }
}

画面效果
Flutter 自定义ScrollPhysics实现PageView禁止左滑或右滑_第1张图片


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,
    );
  }
}

发现只重写了3个方法:

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

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

处于第一页,往右滑动无法滑动的日志。
Flutter 自定义ScrollPhysics实现PageView禁止左滑或右滑_第2张图片

处于最后一页,往左滑无法滑动的日志
Flutter 自定义ScrollPhysics实现PageView禁止左滑或右滑_第3张图片

现象

  • 往左滑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,
  });
}

因为需求与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);
  }
}

使用方式

final _physicsController = CustomScrollPhysicsController();

// 构建PageView
 PageView(
   physics: CustomScrollPhysics(controller: _physicsController),
   children: List.generate(3, _buildChildPage),
 ),

// 设置是否禁用滑动
_physicsController.banSwipeLeft = true;
_physicsController.banSwipeRight = true;

最终效果

你可能感兴趣的:(Flutter,flutter,PageView,ScrollPhysics,禁止PageView左滑,禁止PageView右滑)