使用自定义ScrollPhysics调整具有动态边界的PageView

时间:2019-05-02 06:58:37

标签: flutter

我正在尝试动态设置PageView边界。 这是一个简化的示例,从第10页开始,由随机数生成器(leftEnd,rightEnd)设置左右边界。

import 'package:flutter/material.dart';
import 'dart:math';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      home: new MyTabbedPage(),
    );
  }
}

class MyTabbedPage extends StatefulWidget {
  const MyTabbedPage({Key key}) : super(key: key);

  @override
  _MyTabbedPageState createState() => new _MyTabbedPageState();
}

class _MyTabbedPageState extends State<MyTabbedPage> with SingleTickerProviderStateMixin {
  final leftEnd = Random().nextInt(5);
  final rightEnd = 10 + Random().nextInt(5);
  CustomScrollPhysics scrollPhysics = CustomScrollPhysics();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(),
        body: PageView.builder(
          controller: PageController(initialPage: 10),
          physics: scrollPhysics,
          itemBuilder: (context, index) {
            scrollPhysics.leftEnd = (index <= leftEnd);
            scrollPhysics.rightEnd = (index >= rightEnd);

            // --------- print (1) ----------
            print("is leftEnd: ${index <= leftEnd}");
            print("is rightEnd: ${index >= rightEnd}");
            print("scrollphysics.leftEnd: ${scrollPhysics.leftEnd}");
            print("scrollphysics.rightEnd: ${scrollPhysics.rightEnd}");
            return Center(
              child: Text("Item $index"),
            );
          }
        ));
  }
}

class CustomScrollPhysics extends ScrollPhysics {
  CustomScrollPhysics({ScrollPhysics parent}) : super(parent: parent);

  bool leftEnd = false;
  bool rightEnd = false;
  bool isGoingLeft = false;

  @override
  CustomScrollPhysics applyTo(ScrollPhysics ancestor) {
    return CustomScrollPhysics(parent: buildParent(ancestor));
  }

  @override
  double applyPhysicsToUserOffset(ScrollMetrics position, double offset) {
    isGoingLeft = offset.sign < 0;
    return offset;
  }

  @override
  double applyBoundaryConditions(ScrollMetrics position, double value) {
    //print("applyBoundaryConditions");
    assert(() {
      if (value == position.pixels) {
        throw FlutterError(
            '$runtimeType.applyBoundaryConditions() was called redundantly.\n'
                'The proposed new position, $value, is exactly equal to the current position of the '
                'given ${position.runtimeType}, ${position.pixels}.\n'
                'The applyBoundaryConditions method should only be called when the value is '
                'going to actually change the pixels, otherwise it is redundant.\n'
                'The physics object in question was:\n'
                '  $this\n'
                'The position object in question was:\n'
                '  $position\n');
      }
      return true;
    }());
    if (value < position.pixels && position.pixels <= position.minScrollExtent)
      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;

    // --------- print (2) ----------
    if (leftEnd) print("leftEnd");
    if (rightEnd) print("rightEnd");
    if (isGoingLeft) print("isGoingLeft");

    if (leftEnd && !isGoingLeft) {
      return value - position.pixels;
    } else if (rightEnd && isGoingLeft) {
      return value - position.pixels;
    }
    return 0.0;
  }
}

scrollphysics.leftEnd / rightEnd在PageView.builder内部更改(基于打印结果(1)),但在CustomScrollPhysics中没有更改(无打印结果(2))。

有人可以解释一下这里发生了什么吗? 这是为PageView设置动态边界的正确方法吗?

1 个答案:

答案 0 :(得分:1)

ScrollPhysics所延伸的

CustomScrollPhysics被标记为不可变。即使您在itemBuilder内更改其布尔值,实际的布尔值也不会更改(如未在leftEnd中打印rightEndapplyBoundaryConditions所示)。我没有官方解释,为什么您的scrollPhysics.leftEnd中的itemBuilder在类本身未更改的情况下显示所需的已更改布尔值,但我想这是因为您没有设置那些变量应为final,应有尽有,并且没有任何阻碍您进行更改,因此scrollPhysics中的本地_MyTabbedPageState会显示这些更改,即使它们在内部未更改。 isGoingLeft之所以要打印,是因为它是在CustomScrollPhysics本身内部而不是从外部进行更改,如itemBuilder所示。

作为快速解决方案,我创建了另一个类:

class Status {
  bool leftEnd = false;
  bool rightEnd = false;
  bool isGoingLeft = false;
}

稍微更改了您的CustomScrollPhysics

class CustomScrollPhysics extends ScrollPhysics {
  final Status status;

  CustomScrollPhysics(this.status, {ScrollPhysics parent})
      : super(parent: parent);

  @override
  CustomScrollPhysics applyTo(ScrollPhysics ancestor) {
    return CustomScrollPhysics(this.status, parent: buildParent(ancestor));
  }

  ...

并在您处于状态的构造函数调用中添加了Status实例:

CustomScrollPhysics scrollPhysics = CustomScrollPhysics(Status());

this.status中的CustomScrollPhysics进行所有更改。现在它应该可以按预期工作了。