5

I'd like to create a progression PageView with custom ScrollPhysics, so the user can only scroll to completed tabs. See following image for reference, top right is the progression (green = accessible, red = non accessible pages):

1]

In the screenshot I completed page 1 and 2 and I don't want to allow the user to swipe to page 3 which is happening right now. I read the examples in scroll_physics.dart for the iOS and Android implementations. But I'm still stuck.

I tried this here but it's bugged. You can prevent the user from going right but if the last accessible page isn't fully visible just like in the screenshot the scrolling is already blocked and you can't scroll further to the right.

Here's my code right now:

Called from:

PageView(
          controller: PageController(
            initialPage: model.initallPage,
          ),
          children: pages,
          physics: model.currentPage >= model.lastAccessiblePage ? CustomScrollPhysics(CustomScrollStatus()..rightEnd = true) : ScrollPhysics(),
          onPageChanged: (value) {
            model.currentPage = value;
          },
        ),

Custom ScrollPhysics:

class CustomScrollStatus {
  bool leftEnd = false;
  bool rightEnd = false;
  bool isGoingLeft = false;
  bool isGoingRight = false;
}

class CustomScrollPhysics extends ScrollPhysics {
  final CustomScrollStatus status;

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

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

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

  @override
  double applyBoundaryConditions(ScrollMetrics position, double value) {
    if (value < position.pixels && position.pixels <= position.minScrollExtent) {
      print('underscroll');
      return value - position.pixels;
    }
    if (position.maxScrollExtent <= position.pixels && position.pixels < value) {
      print('overscroll');
      return value - position.pixels;
    }
    if (value < position.minScrollExtent && position.minScrollExtent < position.pixels) {
      print('hit top edge');
      return value - position.minScrollExtent;
    }
    if (position.pixels < position.maxScrollExtent && position.maxScrollExtent < value) {
      print('hit bottom edge');
      return value - position.maxScrollExtent;
    }

    if (status.leftEnd) print("leftEnd");
    if (status.rightEnd) print("rightEnd");
    if (status.isGoingLeft) print("isGoingLeft");
    if (status.isGoingRight) print("isGoingRight");

    // do something to block movement > accesssible page

    print('default');
    return 0.0;
  }
}

Edit:

I thought about a completely different solution. Dynamically changing the children of the PageView. I'm still interested in overriding the `ScrollPhysics´ solution as it's cleaner in my opinion.

children: pages

julian.a
  • 1,163
  • 2
  • 9
  • 21

1 Answers1

1

If you're using PageView.builder you can return null to indicate that other pages are inaccessible. You can check my answer here that's sort of similar to this issue.

PageView.builder(
  controller: pageController,
  onPageChanged: getCurrentPage,
  itemBuilder: (context, position) {
    // stop at 5
    if (position == 5) return null;

    // else, create a page
    return createPage(position + 1);
  },
),

As for disabling the scroll animation at the end of the PageView, a workaround for this is best explained in this post.

Omatt
  • 8,564
  • 2
  • 42
  • 144