How to handle scrollview gestures inside a PageView?

  1. Summary of my problem
  • Details about my goal
    My goal is to present a carousel with long pages.
    So I use a PageView with scrollviews.
    The PageView scrolls horizontally.
    The scrollviews (children) scroll vertically.

  • Expected Results
    Swipe horizontally and scroll vertically smoothly.

  • Actual Results
    If I swipe horizontally to the next page, I can’t scroll it vertically right away.
    I need to wait for 1 second.
    It seems the user must wait the animation completion to be able to interact with the new current page.

  1. What have I tried so far :
    I tried gesture recognizer to pass the dragging event but I didn’t get it working.
    I tried different widgets to replace the PageView but same effect.
    I tried AutomaticKeepAliveClientMixin with wantKeepAlive = true
    I tried PageView.physics = AlwaysScrollableScrollPhysics()

  2. Here’s the minimum code you would need to reproduce the problem -

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(),
    );
  }
}

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

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: carousel(),
    );
  }

  Widget carousel() {
    return PageView(
      children: <Widget>[
        page(Colors.pinkAccent),
        page(Colors.blueAccent),
        page(Colors.orangeAccent)
      ],
    );
  }

  Widget page(Color color) {
    return Container(
        decoration: BoxDecoration(
          color: color,
        ),
        child: SingleChildScrollView(
          child: Column(
              children: pageContent()),
        ));
  }

  List<Widget> pageContent() {
    return <Widget>[
      Row(children: <Widget>[Text("Hop", textScaleFactor: 5,)]),
      Row(children: <Widget>[Text("Hop", textScaleFactor: 5,)]),
      Row(children: <Widget>[Text("Hop", textScaleFactor: 5,)]),
      Row(children: <Widget>[Text("Hop", textScaleFactor: 5,)]),
      Row(children: <Widget>[Text("Hop", textScaleFactor: 5,)]),
      Row(children: <Widget>[Text("Hop", textScaleFactor: 5,)]),
      Row(children: <Widget>[Text("Hop", textScaleFactor: 5,)]),
      Row(children: <Widget>[Text("Hop", textScaleFactor: 5,)]),
      Row(children: <Widget>[Text("Hop", textScaleFactor: 5,)]),
      Row(children: <Widget>[Text("Hop", textScaleFactor: 5,)]),
      Row(children: <Widget>[Text("Hop", textScaleFactor: 5,)]),
      Row(children: <Widget>[Text("Hop", textScaleFactor: 5,)]),
      Row(children: <Widget>[Text("Hop", textScaleFactor: 5,)]),
      Row(children: <Widget>[Text("Hop", textScaleFactor: 5,)]),
      Row(children: <Widget>[Text("Hop", textScaleFactor: 5,)]),
      Row(children: <Widget>[Text("Hop", textScaleFactor: 5,)]),
    ];
  }
}

2 Likes

Hi,
I have the same problem.
are you able to fix this issue, if yes, then it would be great if you can share the solution?

Thanks
Satish.

I succeeded to find a solution to make my PageView really smooth horizontally and vertically.

I disable PageView.pageSnapping to make the carousel very smooth. However I lost the magnetic effect, so I use a NotificationListener to catch the ScrollEndNotification event. When the scrolling ends, I calculate the most visible page to the user and call PageController.animateToPage() to finish the job.

The code looks like this:

Widget getCarousel() {

return NotificationListener<ScrollNotification>(
onNotification: (scrollNotification) {

    if (scrollNotification is ScrollEndNotification) {
      print("ScrollEndNotification");

      Future.delayed(const Duration(milliseconds: 0), () {
        int newPage = pageController.page.round();
        print("end at $newPage");
        pageController.animateToPage(newPage, duration: Duration(milliseconds: 400), curve: Curves.fastOutSlowIn);
      });
  }
  return true;
  },
    child: PageView.builder(
    scrollDirection: Axis.horizontal,
    pageSnapping: false,
    controller: pageController,
    itemBuilder: (BuildContext context, int index) {
      return myPages[index];
    },
  )
);
}
2 Likes

Thank you for the quick reply, @Mickael_West, I hope it could save my life.

save my life :smiley: @satish_kumar