0

I'm trying to set PageView boundary dynamically. Here's a simplified example, which starts page 10, and left and right boundary is set by random number generator (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 changes inside PageView.builder (based on the print (1)), but it doesn't change in CustomScrollPhysics (no print (2)).

Could anyone explain what's happening here? Is this the right way to set a dynamic boundary for PageView?

user987654
  • 5,461
  • 5
  • 23
  • 26

1 Answers1

1

ScrollPhysics which your CustomScrollPhysics is extending from is marked as immutable. Even though you are changing its booleans inside your itemBuilder the actual booleans are not changed (as seen by not printing leftEnd and rightEnd in applyBoundaryConditions). I don't have an official explanation as why scrollPhysics.leftEnd in your itemBuilder shows the desired changed boolean while it didn't change in it's class itself but i would guess it is because you didn't set those variables as final as they should be and nothing is holding you back to change them so your local scrollPhysics in _MyTabbedPageState is showing those changes even though they are not changed internally. isGoingLeft is only getting printed because it is being changed inside the CustomScrollPhysics itself rather then from the outside as seen in itemBuilder.

As a quick fix i created another class:

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

changed your CustomScrollPhysics a bit:

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

  ...

and added a Status instance inside the constructor call in your state:

CustomScrollPhysics scrollPhysics = CustomScrollPhysics(Status());

Apply every change to this.status inside CustomScrollPhysics. Now it should work as you intended.

kounex
  • 1,555
  • 4
  • 12