18

Is there a way to have an infinite loop using PageView in Flutter? For example if my PageView has 5 pages, after swiping to page 5, I would be able to swipe again in the same direction to get to Page 1.

fadisdh
  • 552
  • 1
  • 4
  • 9

9 Answers9

63

By default, PageView.builder is infinite in flutter. Unless you provide an itemCount.

The following will print page from 0 to 4 infinitely

final controller = new PageController(initialPage: 999);
 ...

new PageView.builder(
      controller: controller,
      itemBuilder: (context, index) {
        return new Center(
          child: new Text('${index % 5}'),
        );
      },
)
Nuqo
  • 3,793
  • 1
  • 25
  • 37
Rémi Rousselet
  • 256,336
  • 79
  • 519
  • 432
  • 2
    Ohh I thought itemCount is required, thank you for the clarification. – fadisdh Mar 07 '18 at 22:32
  • 1
    Thanks Remi, I was searching for a solution to stop the infinite scroll of my pageview. – Shyju M May 04 '18 at 05:16
  • 12
    But isn't this a hack? Ok, maybe noone will scroll 999 pages to the left, but IF he does: there is an end. So it's more like a "half-line" as far as I know. Still, good to know there is a way to give an initial page to start with. – Dominikus K. Jun 03 '18 at 21:22
  • 4
    will this continuously eat up memory as the pages go on? or will previous pages be released from memory? – MobileMon Sep 15 '20 at 14:26
  • 2
    @MobileMon they will be released from memory unless you specifically tell the framework to keep them. – Rémi Rousselet Sep 15 '20 at 16:06
  • The initial page starts at 999, it's not how many pages exist just where to start. Meaning that you can go all the way to 80000+ without any issue. @DominikusK. – Matthew Oct 13 '20 at 18:59
  • That is the direction I am with you @Matthew. But what with users scrolling back to the pages 998, 997, .., 1, 0, -1?? – Dominikus K. Oct 20 '20 at 19:26
  • This solution allows scrolling only in one direction, but If for example I have 3 images and start from image 1 I want to scroll backwards and get to image 3 instantly – kashlo Jan 27 '21 at 01:12
  • @RémiRousselet I want to set `initialPage` to 1. how can I do it? – BIS Tech Jun 13 '21 at 11:19
  • The builder is called only for those children that are actually visible. So it's efficient – zex_rectooor Jul 19 '22 at 09:26
  • This hack has an effect on performance, you can't set any value for initialPage, for example if you replace 999 with 999999999999999 the app becomes unusable. – Patrick Aug 23 '22 at 13:14
21

If you have a list of pre-defined Widgets, you can achieve continuous scrolling using:

return PageView.builder(
  itemBuilder: (context, index) {
    return _children[index % _children.length];
  },
  controller: pageController,
);
Tiaan Gerber
  • 219
  • 2
  • 4
7

I've found a good solution using this lib https://pub.dev/packages/infinity_page_view

Just import the lib and use InfinityPageView instead of PageView

InfinityPageView(
  controller: infinityPageController,
  itemCount: colorList.length,
  itemBuilder: (context, index) {
    return Container(
      color: colorList[index];
    );
  },
)
3

You can achieve infinite scrolling using PageView builder, without giving value to itemCount there will be infinite pages, you just have to maintain the page index which will be painted on screen.

PageView.builder(
  controller: _pageController,
  scrollDirection: Axis.horizontal,
  onPageChanged: (index) {
    setState(() {
      _currentIndex = index % _walkthroughSlides.length;
    });
  },
  itemBuilder: (context, index) {
    return _walkthroughSlides[index % _walkthroughSlides.length];
  },
)
Aman Ansari
  • 121
  • 1
  • 5
1

The answer mentioned above will take more memory and probably that is redundancy and when you are on the first child you can't go the last one by scrolling in reverse direction

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

class InfiniteScroll extends StatefulWidget{
  final List<Widget> children;
  InfiniteScroll({this.children});
  @override
  State<StatefulWidget> createState() {
    // TODO: implement createState
    return _InfiniteScrollState();
  }
}

class _InfiniteScrollState extends State<InfiniteScroll>{
  List<Widget> children;
  ScrollController s;
  int _pos;

  @override
  void initState() {
    _pos=1;
    children = [widget.children[widget.children.length - 1]];
    for (int i = 0; i < widget.children.length; ++i) {
      children.add(widget.children[i]);
    }
    if (widget.children.length > 1) {
      children.add(children[1]);
    }
    s = PageController();
    super.initState();
     WidgetsBinding.instance
        .addPostFrameCallback((_){
          s.jumpTo(MediaQuery.of(context).size.width);
        });
 
  }
  


  @override
  Widget build(BuildContext context) {
    
    return PageView.builder(
      scrollDirection: Axis.horizontal,
      controller: s,
      onPageChanged: (int i) {
        setState(() {
          _pos=i+1;
        });
        if (i == children.length - 1) {
          Timer(Duration(milliseconds: 400), () {
            s.jumpTo(MediaQuery.of(context).size.width);
          });
          setState(() {
          _pos=1;
        });

        } else if (i == 0) {
          Timer(Duration(milliseconds: 400), () {
            s.jumpTo(MediaQuery.of(context).size.width * (children.length - 2));
          });
          setState(() {
            _pos=children.length-2;
          });
        }
        
      },
      itemBuilder: (BuildContext context, int index) {
        return children[index];
      },
      itemCount: children.length,
    );
  }
}

and now you can use it as

import 'package:flutter/material.dart';
import 'package:test1/Widgets/infinite_scroll.dart';


class CoursesPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return 
        Container(
          height: MediaQuery.of(context).size.height,
          child: 
              InfiniteScroll(
                children: <Widget>[
                  Container(
                    color: Colors.red,
                    child: Center(child: Text("1",style: TextStyle(color: Colors.white,fontSize: 50),),),
                  ),
                  Container(
                    color: Colors.black,
                    child: Center(child: Text("2",style: TextStyle(color: Colors.white,fontSize: 50),),),
                  ),
                  Container(
                    color: Colors.green,
                    child: Center(child: Text("3",style: TextStyle(color: Colors.white,fontSize: 50),),),
                  ),
                  Container(
                    color: Colors.blue,
                    child: Center(child: Text("4",style: TextStyle(color: Colors.white,fontSize: 50),),),
                  ),
                ],
              )
        );
  }
}
  • 2
    ugly solution, setState calls twice per page swipe, magic numbers in code –  Jul 01 '19 at 05:06
  • Did you run the code.and I think init state will run only first time when you initialize the state.so no way init state will be called two time per swipe build will be called and it going to call it only one time – Avinash katariya Jul 05 '19 at 10:45
  • did you run your code? I don't think so, because in this case you should know setState executes twice every time when you swipe to last or first page moreover if you try to swipe fast you will get "lag" and no swipe every time when last or first page is on screen –  Jul 05 '19 at 12:39
  • It's a little ugly, but I think it's a good idea. I will try to catch the touch-end event and trigger the `s.jumpTo` – mutoe Jun 25 '20 at 10:53
0

It's work without controller. You need comment prop itemCount & repce index like bellow.

PageView.builder(
  onPageChanged: (v) {
    setState(() => _currentImage = v % images.length);
  },
  scrollDirection: Axis.horizontal,
  // itemCount: images.length,
  itemBuilder: (ctx, i) {
    return CustomImageNetwork(
      width: imageSize,
      height: imageSize,
      image: images[i % images.length] ?? '',
    );
  },
)
ziqq
  • 523
  • 5
  • 11
0

Used extention beside remi solution

extension ListX<E> on List {
  E loop(int index) => this[index % length];
}

class _CarouselWidgetState extends State<CarouselWidget> {
  late final List<Widget> children;
  late final PageController controller;
  @override
  initState() {
    super.initState();
    children = widget.children;
    controller = PageController(initialPage: 100);
  }

  @override
  Widget build(BuildContext context) {
    return PageView.builder(
      controller: controller,
      // itemCount: children.length,
      itemBuilder: (context, pagePosition) {
        return children.loop(pagePosition)
      },
    );
  }
}
zex_rectooor
  • 692
  • 7
  • 26
0

You can use an "OverScroll notifier" and PageController controller.jumpToPage() to change the current page to the last or the first page depending on the edge we "overscrolled".

class CarousselView extends StatefulWidget {
const CarousselView({super.key});

@override
State<CarousselView> createState() => _CarousselViewState();
}

class _CarousselViewState extends State<CarousselView>
with SingleTickerProviderStateMixin {
 int _index = 1;

 TabController? controller;
 late PageController _controller;
 String message = "";

  @override
 void initState() {
 controller = TabController(
  length: 6,
  vsync: this,
   );
  _controller = PageController();
 }

@override
Widget build(BuildContext context) {
 return Stack(
   children: [
    NotificationListener<OverscrollIndicatorNotification>(
      onNotification: ((notification) {
        print("Oversrolled");
        setState(() {
          _controller.jumpToPage(_index == 0 ? 5 : 0);
        });
        return true;
      }),
      child: PageView.builder(
        scrollDirection: Axis.horizontal,
        itemCount: 6,
        controller: _controller,
        onPageChanged: (int index) => setState(() {
          controller!.index = index;
          _index = index;
        }),
        itemBuilder: (_, i) {
          return Card(
            elevation: 6,
            shape: RoundedRectangleBorder(
                borderRadius: BorderRadius.circular(20)),
            child: Center(
              child: Text(
                "Card ${i}",
                style: TextStyle(fontSize: 32),
              ),
            ),
          );
        },
      ),
    ),
    Container(
      alignment: Alignment.bottomCenter,
      child: TabPageSelector(
        indicatorSize: 6.0,
        controller: controller,
         ),
        )
       ],
     );
   }
 }
-1
final controller = new PageController(initialPage: children.length*999);

PageView.builder(
  controller: controller,
  itemBuilder: (context, index) {
    return new Center(
      child: new Text('${index % children.length}'),
    );
  },

)

Ahmed Raafat
  • 162
  • 4
  • 6