在PageView中滚动多个页面更容易

时间:2019-04-04 19:12:25

标签: dart flutter

我正在使用PageView在flutter中构建水平可滚动列表,但是,我希望能够同时滚动多个页面。如果我滚动得真快,目前是可能的,但这远非理想。另一个选择是将pageSnapping设置为false,但是它根本不会对齐,这不是我想要的。

我认为,如果滚动速度处于某个特定的阈值之下,则可以将pageSnapping从false更改为true,但是我不知道如何获得所述速度。

我正在构建的应用看起来像like this

感谢所有帮助!

2 个答案:

答案 0 :(得分:1)

有趣的问题!

要获得滑动的速度,您可以使用GestureDetector,不幸的是,当尝试同时使用GestureDetector和PageView时,PageView会从PageView窃取焦点,因此不能一起使用。

GestureDetector(
  onPanEnd: (details) {
    Velocity velocity = details.velocity;
    print("onPanEnd - velocity: $velocity");
  },
)

但是,另一种方法是在PageView的onPageChanged中使用DateTime来测量时间变化,而不是速度变化。但是,这并不理想,我编写的代码就像黑客一样。它具有一个错误(或功能),即页面停顿后的第一次滑动只会移动一页,但是连续的滑动将能够移动多个页面。

bool pageSnapping = true;
List<int> intervals = [330, 800, 1200, 1600]; // Could probably be optimised better
DateTime t0;

Widget timeBasedPageView(){
  return PageView(
    onPageChanged: (item) {

      // Obtain a measure of change in time.
      DateTime t1 = t0 ?? DateTime.now();
      t0 = DateTime.now();
      int millisSincePageChange = t0.difference(t1).inMilliseconds;
      print("Millis: $millisSincePageChange");

      // Loop through the intervals, they only affect how much time is 
      // allocated before pageSnapping is enabled again. 
      for (int i = 1; i < intervals.length; i++) {
        bool lwrBnd = millisSincePageChange > intervals[i - 1];
        bool uprBnd = millisSincePageChange < intervals[i];
        bool withinBounds = lwrBnd && uprBnd;

        if (withinBounds) {
          print("Index triggered: $i , lwr: $lwrBnd, upr: $uprBnd");

          // The two setState calls ensures that pageSnapping will 
          // always return to being true.
          setState(() {
            pageSnapping = false;
          });

          // Allows some time for the fast pageChanges to proceed 
          // without being pageSnapped.
          Future.delayed(Duration(milliseconds: i * 100)).then((val){
            setState(() {
              pageSnapping = true;
            });
          });
        }
      }
    },
    pageSnapping: pageSnapping,
    children: widgets,
  );
}

我希望这会有所帮助。

编辑:基于汉尼斯答案的另一个答案。

class PageCarousel extends StatefulWidget {
  @override
  _PageCarouselState createState() => _PageCarouselState();
}

class _PageCarouselState extends State<PageCarousel> {
  int _currentPage = 0;
  PageController _pageController;
  int timePrev; //Tid
  double posPrev; //Position
  List<Widget> widgets = List.generate(
      10,
          (item) =>
          Container(
            padding: EdgeInsets.all(8),
            child: Card(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  Text("Index $item"),
                ],
              ),
            ),
          ));

  @override
  void initState() {
    super.initState();
    _pageController = PageController(
      viewportFraction: 0.75,
      initialPage: 0,
    );
  }

  int boundedPage(int newPage){
    if(newPage < 0){
      return 0;
    }
    if(newPage >= widgets.length){
      return widgets.length - 1;
    }
    return newPage;
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Listener(
        onPointerDown: (pos){
          posPrev = pos.position.dx;
          timePrev = DateTime.now().millisecondsSinceEpoch;
          print("Down");
          print("Time: $timePrev");
        },
        onPointerUp: (pos){
          int newTime = DateTime.now().millisecondsSinceEpoch;
          int timeDx = newTime - timePrev;
          double v = (posPrev - pos.position.dx) / (timeDx);
          int newPage = _currentPage + (v * 1.3).round();

          print("Velocity: $v");
          print("New Page: $newPage, Old Page: $_currentPage");

          if (v < 0 && newPage < _currentPage || v >= 0 && newPage > _currentPage) {
            _currentPage = boundedPage(newPage);
          }

          _pageController.animateToPage(_currentPage,
              duration: Duration(milliseconds: 800), curve: Curves.easeOutCubic);
        },
        child: PageView(
          controller: _pageController,
          physics: ClampingScrollPhysics(), //Will scroll to far with BouncingScrollPhysics
          scrollDirection: Axis.horizontal,
          children: widgets,
        ),
      ),
    );
  }
}

这应该确保一个不错的多页导航。

答案 1 :(得分:1)

对于以后来这里的任何人,我最终使用由Listener插入的GestureDetector来解决此问题,以手动计算代码。

以下是相关代码:

class HomeWidget extends StatefulWidget {
  @override
  _HomeWidgetState createState() => _HomeWidgetState();
}

class _HomeWidgetState extends State<HomeWidget> {
  int _currentPage = 0;
  PageController _pageController;
  int t; //Tid
  double p; //Position

  @override
  initState() {
    super.initState();
    _pageController = PageController(
      viewportFraction: 0.75,
      initialPage: 0,
    );
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Listener(
        onPointerMove: (pos) { //Get pointer position when pointer moves
          //If time since last scroll is undefined or over 100 milliseconds
          if (t == null || DateTime.now().millisecondsSinceEpoch - t > 100) {
            t = DateTime.now().millisecondsSinceEpoch;
            p = pos.position.dx; //x position
          } else {
            //Calculate velocity
            double v = (p - pos.position.dx) / (DateTime.now().millisecondsSinceEpoch - t);
            if (v < -2 || v > 2) { //Don't run if velocity is to low
              //Move to page based on velocity (increase velocity multiplier to scroll further)
              _pageController.animateToPage(_currentPage + (v * 1.2).round(),
                  duration: Duration(milliseconds: 800), curve: Curves.easeOutCubic);
            }
          }
        },
        child: PageView(
          controller: _pageController,
          physics: ClampingScrollPhysics(), //Will scroll to far with BouncingScrollPhysics
          scrollDirection: Axis.horizontal,
          children: <Widget>[
            //Pages
          ],
        ),
      ),
    );
  }
}