Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make PageView with different page size only for the current page

The reason I want to use PageView because it can stick to the page when dragging is released. But I can't figure out how to achieve the screen like below:

enter image description here

I want to make the page currently selected page show a larger size, while others show smaller. The most straightforward method is to set the viewportFraction (to 0.15). But after that I find out that the pages need the different values of viewportFraction:

enter image description here

You can see that I want the space between pages equally but only the middle one shows larger. Is this possible to do with the PageView Widget? Or does anyone have other workarounds that can achieve the same result?

If possible, I also want to add GestureDetector to each page (which can animate to the target page)

like image 341
yellowgray Avatar asked May 17 '21 04:05

yellowgray


2 Answers

Check this dartpad out:

enter image description here

You can check out all the code there but the gist is that inside every page view you have an animated container in which you animate its padding to control its height :

The value is hardcoded but you could use a mediaquery to get a dynamic top padding.

class PageviewGallery extends StatefulWidget {
  @override
  _PageviewGalleryState createState() => _PageviewGalleryState();
}

class _PageviewGalleryState extends State<PageviewGallery> {
  final PageController ctrl = PageController(
    viewportFraction: 0.75,
  );

  int currentPage = 0;
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        home: Scaffold(
      body: PageView.builder(
          controller: ctrl,
          itemCount: 8,
          physics: const BouncingScrollPhysics(),
          itemBuilder: (context, int index) {
            // Active page
            bool active = index == currentPage;
            return _buildStoryPage(active);
          }),
    ));
  }

  @override
  void initState() {
    super.initState();
    ctrl.addListener(() {
      int pos = ctrl.page!.round();
      if (currentPage != pos) {
        {
          setState(() {
            currentPage = pos;
          });
        }
      }
    });
  }
  
    @override
void dispose(){
ctrl.dispose();
super.dispose();

}
}

_buildStoryPage( bool active) {
  // Animated Properties
  final double blur = active ? 30 : 0;
  final double offset = active ? 20 : 0;
  final double top = active ? 100 : 200;

  return AnimatedContainer(
    duration: Duration(milliseconds: 500),
    curve: Curves.easeOutQuint,
    margin: EdgeInsets.only(top: top, bottom: 50, right: 30),
    decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(20),
        color :Colors.red,
        boxShadow: [BoxShadow(color: Colors.black87, blurRadius: blur, offset: Offset(offset, offset))]),
  );
}
like image 116
croxx5f Avatar answered Oct 29 '22 00:10

croxx5f


From the Flutter docs: "Each child of a page view is forced to be the same size as the viewport."

With that in mind, it is possible to achieve the effect you are looking for to some degree using a PageView. I riffed off of @croxx5f's answer, but here's how you can achieve it: https://dartpad.dev/e3fe965b19d36bc6f58bac8029a89251?null_safety=true.

Note that it is using padding (or lack thereof) to make the active element larger.

In short, the very nature of PageView required the elements to be the same width. If you want to use PageView, you will have to use padding or a similar technique to make the non active components smaller than the active one.

like image 32
Spencer Stolworthy Avatar answered Oct 29 '22 02:10

Spencer Stolworthy