Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Adding new pages to a PageView while swiping

Tags:

flutter

I am currently making a calendar app that I would like to swipe right or left to go to the next or previous month. I am using a PageView by initially setting up an array with 3 items in it and the initial page being the second one. I would like to swipe right and add a page to the end. I would like to swipe left and add a page to the beginning. Currently, if you go to the right (adding pages to the end) it works great. But if you go to the left (adding pages to the beginning) there is some odd behavior and it doesn't work at all.

I have pasted a simple example below with counters. I am not sure if I am doing it right or if my logic is off. Can anyone let me know the correct way to do this?

import 'package:flutter/material.dart';

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

class LimeApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Pageview Test',
      home: MainPage(),
    );
  }
}

int _lowerCount = -1;
int _upperCount = 1;

class MainPage extends StatelessWidget {
  final List<Widget> _pages = <Widget>[
    new Center(child: new Text("-1", style: new TextStyle(fontSize: 60.0))),
    new Center(child: new Text("0", style: new TextStyle(fontSize: 60.0))),
    new Center(child: new Text("1", style: new TextStyle(fontSize: 60.0)))
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        margin: EdgeInsets.symmetric(
          vertical: 50.0,
        ),
        child: PageView(
          onPageChanged: (pageId){
            if(pageId == _pages.length - 1){
              print("Last page, add page to end");
              _upperCount = _upperCount + 1;
              _pages.add(new Center(child: new Text(_upperCount.toString(), style: new TextStyle(fontSize: 60.0))));
            }
            if(pageId == 0){
              print("First page, add page to start");
              _lowerCount = _lowerCount - 1;
              _pages.insert(0, new Center(child: new Text(_lowerCount.toString(), style: new TextStyle(fontSize: 60.0))));
            }
          },
          controller: PageController(
            initialPage: 1,
          ),
          children: _pages,
        ),
      ),
    );
  }
}

Swiping Example:

like image 413
casmang Avatar asked Sep 30 '18 15:09

casmang


2 Answers

I think I got it working. I did not use the onPageChange event. Instead I listened to changes in the controller.


import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  static const Widget _home = HomePage();
  @override
  Widget build(BuildContext context) => MaterialApp(
        home: _home,
      );
}

class HomePage extends StatefulWidget {
  const HomePage();

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

class _HomePageState extends State<HomePage> {
  late PageController _controller;

  // This points always to the mid-element in _list
  late int _initialIndex;

  // This should work with 3, 7, 11, ... odd elements. Mind the pattern!!!
  List<int> _list = [-2, -1, 0, 1, 2];
  

  @override
  void initState() {
    super.initState();
    // Calculate mid.
    _initialIndex = (_list.length / 2).floor();
    
    _controller = PageController(initialPage: _initialIndex, viewportFraction: 0.8);

    // This is where we listen to changes.
    _controller.addListener(() {
      // Get index according to the direction
      // _controller.page! > _initialIndex => swiping to the right, going to the left / previous element
      // _controller.page! < _initialIndex => swiping to the left, going to the right / next element
      final index = _controller.page! > _initialIndex ? _controller.page!.floor() : _controller.page!.ceil();
      

      if (index == _initialIndex) return;
      if (index == _initialIndex - 1) {
        _prev();
      } else if (index == _initialIndex + 1) {
        _next();
      }

    });
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  // Update list and jump to the middle element
  void _next() {
    setState(() {
      _list
        ..removeAt(0)
        ..insert(_list.length, _list.last + 1);
      // Update current DateTime here
    });
    _controller.jumpToPage(_initialIndex);
  }

  // Update list and jump to the middle element
  void _prev() {
    setState(() {
      _list
        ..insert(0, _list.first - 1)
        ..removeLast();
      // Update current DateTime here
    });
    _controller.jumpToPage(_initialIndex);
  }

  @override
  Widget build(BuildContext context) => Scaffold(
    appBar: AppBar(
          actions: <Widget>[
            Padding(
              padding: const EdgeInsets.only(right: 20.0),
              child: IconButton(
                icon: const Icon(Icons.arrow_back),
                onPressed: () {
                  _prev();
                },
              ),
            ),
            Padding(
              padding: const EdgeInsets.only(right: 20.0),
              child: IconButton(
                icon: const Icon(Icons.arrow_forward),
                onPressed: () {
                  _next();
                },
              ),
            ),
          ],
        ),
    body: PageView.builder(
      controller: _controller,
      itemCount: _list.length,
      itemBuilder: (context, i) {
        // This is where you should put your widget that generates
        // the view of the month.
        // Calculate DateTime like so 'DateTime(initialDate.year, initialDate.month + _list[index], initialDate.day)'
        return Padding(
          padding: const EdgeInsets.all(32.0),
          child: Container(
            color: Colors.blueAccent[100],
            alignment: Alignment.center,
            child: Text(
              '${_list[i]}',
              style: const TextStyle(fontSize: 32),
            ),
          ),
        );
      }
    ),
  );
}




like image 158
krjw Avatar answered Oct 19 '22 14:10

krjw


I propose you the code below that works.

  • It must use a stateful widget to be able to use the setState() methods.
  • When inserting the first page you need to create a new list and assign it to the previous list. Testing just inserting them without creating a new list resulted in the pages being added at the end. You need to make the list non final to be able to change it.

But it has the following caveat.

  • When you change the page backwards the onPageChanged() keeps the page 0 so just after inserting it you go to the newly added page because it is page 0. And since you already are on page 0, you can only add new pages if you go forward and back again.

Hope it helps

import 'package:flutter/material.dart';

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

class LimeApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Pageview Test',
      home: MainPage(),
    );
  }
}

int _lowerCount = -1;
int _upperCount = 1;

class MainPage extends StatefulWidget {
  @override
  MainPageState createState() {
    return new MainPageState();
  }
}

class MainPageState extends State<MainPage> {
  List<Widget> _pages = <Widget>[
    new Center(child: new Text("-1", style: new TextStyle(fontSize: 60.0))),
    new Center(child: new Text("0", style: new TextStyle(fontSize: 60.0))),
    new Center(child: new Text("1", style: new TextStyle(fontSize: 60.0)))
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        margin: EdgeInsets.symmetric(
          vertical: 50.0,
        ),
        child: PageView(
          onPageChanged: (pageId) {
            if (pageId == _pages.length - 1) {
              print("Last page, add page to end");
              _upperCount = _upperCount + 1;
              _pages.add(new Center(child: new Text(_upperCount.toString(), style: new TextStyle(fontSize: 60.0))));
              setState(() {});
            }
            if (pageId == 0) {
              print("First page, add page to start");
              _lowerCount = _lowerCount - 1;
              Widget w = new Center(child: new Text(_lowerCount.toString(), style: new TextStyle(fontSize: 60.0)));
              _pages = [w]..addAll(_pages);
              setState(() {});
            }
          },
          controller: PageController(
            initialPage: 1,
          ),
          children: _pages,
        ),
      ),
    );
  }
}
like image 45
chemamolins Avatar answered Oct 19 '22 15:10

chemamolins