23

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.

11 Answers 11

68

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}'),
        );
      },
)
Sign up to request clarification or add additional context in comments.

11 Comments

Ohh I thought itemCount is required, thank you for the clarification.
Thanks Remi, I was searching for a solution to stop the infinite scroll of my pageview.
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.
will this continuously eat up memory as the pages go on? or will previous pages be released from memory?
@MobileMon they will be released from memory unless you specifically tell the framework to keep them.
|
20

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

3 Comments

Wondering how this is on memory. Will it continuously create memory and never release it?
this won't work, cause pageController won't go lower than zero without any hacks.
It did work for me. It's not the best way to go about it, but it works :))
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];
    );
  },
)

Comments

4

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

2 Comments

This should be the correct accepted answer, Works like a charm!
You can't go from position 0 to position -1.
1

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

Comments

1

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

2 Comments

Good approach if you want to maintain a clear first and last slide, except: - "setState" isn't needed in onNotification - I would recommend using "animateToPage" rather than "jumpToPage" and wrapping that call with "unawaited"
Unfortunately, it doesn't work on iOS, due to this bug: github.com/flutter/flutter/issues/17649
0

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

4 Comments

ugly solution, setState calls twice per page swipe, magic numbers in code
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
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
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
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] ?? '',
    );
  },
)

Comments

0

You should try out the loop page view https://pub.dev/packages/loop_page_view it's a pretty straightforward plugin

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

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Loop Page View Demo'),
        ),
        body: Center(
          child: LoopPageView.builder(
            itemCount: 2,
            itemBuilder: (_, index) {
              return Card(
                child: Center(
                  child: Text('$index'),
                ),
              );
            },
          ),
        ),
      ),
    );
  }
}

Comments

0

Just Simple Alternative

In my case, i am using infinite builder by default. no itemCount. and my list is widget.images .

PageView.builder(
    controller: _pageController,
    allowImplicitScrolling: true,
    itemBuilder: (context, index) {
      final url = widget.images[index % widget.images.length];
      return InteractiveViewer(maxScale: 3, child: Image.asset(url));
    },
),

But also, i want to view initial page, so i manage using _pageController . To initialize _pageController , i sent initialIndex for example index 2.
When i snap to right, infinite looping work. after i reach last page, the page show first page again and keep going.

But !!, when i star over again. after pageview show initial page then i snap to left, infinite loop not work. from this i learn that is pageview default infinite only work for upcoming index.

To fix this, i create schema to handle range page/index e.g. from 0-1000. and create function to calculateInitialIndexRepresentation. this index will be center of range page. if range 0-1000 and listCount is 10, i create _infiniteScrollFactor as a multiplier then initialIndex will be 500 as center. This will work to infinite loop for left direction until indexValue is 0.

and here to initialize _pageCntroller and calculate initial:

  late final PageController _pageController;
  // index/page
  // min: 0, max: 100 * widget.images.length
  static const int _infiniteScrollFactor = 100;

  int _calculateInitialPage() {
    return (_infiniteScrollFactor ~/ 2) * widget.images.length +
        widget.initialIndex;
  }

enter image description here

Comments

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

)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.