42

How can i create an Carousel of Containers Like the example below?

I looked at Pageview class but this only displays one Container and hides the others. But i want to see the Container partly on the left and right too. Is there anyway to do this in Flutter and how?

Note: The current Container should always stay in the center

Example

Edit: Darky gave an very good Example but i have one problem with his given code:

The following ArgumentError was thrown building AnimatedBuilder(animation: PageController#fc5f0(one client, offset 0.0), dirty, state: _AnimatedState#1ea5c): Invalid argument (lowerLimit): not a number: null –

This gets thrown at the Position where he calls controller.page. Does anyone know how i can fix this?

DolDurma
  • 15,753
  • 51
  • 198
  • 377
Lukas Kirner
  • 3,989
  • 6
  • 23
  • 30

3 Answers3

66

Actually what you want is PageView.

PageView accept a PageController as argument. And that controller possess a viewportFraction property (default to 1.0) which represent in percent the main-size of displayed pages.

Which means that with a viewportFraction of 0.5, the main page will be centered. And you'll see half a page on both left and right (if there's one)

Example :

example_gif

class Carroussel extends StatefulWidget {
  @override
  _CarrousselState createState() => new _CarrousselState();
}

class _CarrousselState extends State<Carroussel> {
  PageController controller;
  int currentpage = 0;

  @override
  initState() {
    super.initState();
    controller = PageController(
      initialPage: currentpage,
      keepPage: false,
      viewportFraction: 0.5,
    );
  }

  @override
  dispose() {
    controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Container(
          child: PageView.builder(
              onPageChanged: (value) {
                setState(() {
                  currentpage = value;
                });
              },
              controller: controller,
              itemBuilder: (context, index) => builder(index)),
        ),
      ),
    );
  }

  builder(int index) {
    return AnimatedBuilder(
      animation: controller,
      builder: (context, child) {
        double value = 1.0;
        if (controller.position.haveDimensions) {
          value = controller.page - index;
          value = (1 - (value.abs() * .5)).clamp(0.0, 1.0);
        }

        return Center(
          child: SizedBox(
            height: Curves.easeOut.transform(value) * 300,
            width: Curves.easeOut.transform(value) * 250,
            child: child,
          ),
        );
      },
      child: Container(
        margin: const EdgeInsets.all(8.0),
        color: index % 2 == 0 ? Colors.blue : Colors.red,
      ),
    );
  }
}
BIS Tech
  • 17,000
  • 12
  • 99
  • 148
Rémi Rousselet
  • 256,336
  • 79
  • 519
  • 432
  • 4
    The following ArgumentError was thrown building AnimatedBuilder(animation: PageController#fc5f0(one client, offset 0.0), dirty, state: _AnimatedState#1ea5c): Invalid argument (lowerLimit): not a number: null – Lukas Kirner Nov 19 '17 at 12:48
  • I got these error if i perfom controller.page i dont understand why – Lukas Kirner Nov 19 '17 at 13:41
  • I avoided the error by catching ArgumentError and setting the value as 1.0. – ryanafrish7 May 06 '18 at 15:53
  • You should check if pageController.position.haveDimensions is true before calling pageController.page, otherwise it may throw NPE. – raver Jun 20 '18 at 08:21
  • 1
    I don't understand how the animation works. I see that the builder function is called when the PageView is scrolling. But how is this possible? Thanks to the AnimatedBuilder Widget? Or for the controller? :// – Gabbr Issimo Nov 05 '18 at 22:47
  • This is working great for me except when I am using it in conjunction with StreamBuilders. When I initially draw my page widget, it does not re-size the left/right, but the moment I interact with it it works perfectly until I re-init the page. Is there any tricks you know of to get the animation builder to trigger without interaction? – calebisstupid Dec 03 '18 at 22:55
  • @GabbrIssimo The animation is triggered constantly during the animation process. If you debug through it you should see the value variable constantly changing each frame of the animation, which is used to shrink/grow each of the pages. – calebisstupid Dec 03 '18 at 22:57
  • solved my own issue: added "max: 1.0, period: Duration(milliseconds: 1)" to the repeat parameters to force the animation to happen once instantly – calebisstupid Dec 03 '18 at 23:04
  • @calebisstupid: Please let us know where to add these values.. coz I am stuck with this now for hours. Thanks. – Raja Yogan Dec 14 '18 at 22:24
  • @calebisstupid: I would like to see your solution as well, I have tried triggering the animations in several ways but none have worked. – Lloyd Jan 24 '19 at 06:06
  • Hey! Thanks a lot for your explanation and code it worked perfectly for me. I'm just stuck on making the container to dismiss once clicked on it. I have created a new question regarding the issue: https://stackoverflow.com/questions/54910418/how-to-remove-a-page-from-page-view-with-a-smooth-animation Hopefully you can help me:) – Lambasoft Feb 27 '19 at 16:54
  • Hey just having the initial issue too. The repeat method is located on the AnimationController, but as in the sample code I'm also using a PageController. This one does not have a Repeat method. – Rasmus Christensen Apr 12 '19 at 10:29
  • Corrected "pageController" to "controller" --- variable name. Then WORKS like charm – byJeevan Jul 18 '19 at 13:30
  • I really don't understand the `if (pageController.position.haveDimensions)` part in the `AnimatedBuilder`. Could you please explain to me why did you do that and what did you trying to achieve? – Richie Permana Sep 24 '19 at 09:17
  • @RémiRousselet hi, use 0.25 viewportFraction, so i display 5page in viewport, how to animate only small size for 1st left and right page from current page and normal size page for 3 other (0(normal), 1(small), 2/currentpage(normal), 3(small), 4(normal)) – RIFAL Nov 13 '19 at 03:26
  • @RémiRousselet if I widgets to each box, you can I scale the content of each page to fit and to prevent them from overflowing? thanks in advance for your help! – Gabe Mar 03 '20 at 16:13
  • Thank you for this solution! Is it possible to shrink the font size of the inactive pages? – Andreas Mar 29 '20 at 20:56
  • @RémiRousselet can you please help me here https://stackoverflow.com/q/61073304/8112541 – Goku Apr 07 '20 at 10:44
  • @RémiRousselet is there a way to animate fontSize? – Alex.Marynovskyi Jan 06 '21 at 16:01
  • Why is `value` being computed twice in the same block? – Matt Jun 16 '21 at 11:30
  • Hi how do you change the padding between the cards ? – Aristidios Dec 27 '21 at 13:58
4

This solved my problem:

Future.delayed(const Duration(milliseconds: 20), () {

  setState(() {
    _pageController.animateToPage(0, duration: Duration(milliseconds: 1), curve: Curves.bounceIn);
  });

});
adiga
  • 34,372
  • 9
  • 61
  • 83
onkar dhanlobhe
  • 231
  • 2
  • 13
  • 2
    Solved it for me too. I think the above solution using the AnimationController is good, but not sure how to integrate it into the sample as it is using a PageController – Rasmus Christensen Apr 12 '19 at 10:31
0

Didn't had much time to investigate, but the AnimatedBuilder is called X times equal to number of visible children. Dimensions are not available during first frame, so controller.page will throw Exception. Correct formula to compute animation offsets during first frame is (caution: it just worked for me flawlessly, keep your implementation in mind):

        builder: (context, child) {
          double value = _controller.position.haveDimensions
              ? _controller.page! - index
              : index.toDouble();
          value =  Curves.easeInOut.transform(
              (1 - (value.abs() * .4)).clamp(0.0, 1.0)
          );

          return Center(
            child: Container(
              height: value * widget.height,
              width: value * widget.width,
              padding: EdgeInsets.symmetric(horizontal: 15),
              child: child,
            ),
          );
        },
uptoyou
  • 1,427
  • 19
  • 24