Align a flutter PageView to the screen left

8,827

Solution 1

After making a deeper analysis on my own needs and checking the source code for the PageView widget, I realized that that I needed a scrolling widget that works in a item by item basis, but at the same time I needed that the space given to every item was the same as a normal scroll, so I needed to change the ScrollPhysics of a normal scroller. In found this post which describes scroll physics in flutter at some extent and was close to my needs, the difference was I needed to add space at bith sides of the current visible widget, not only to the right.

So I took the CustomScrollPhysics in the post and modified it in this way (the changed parts from the post code are sourrounded withh <-- and --> comments:

class CustomScrollPhysics extends ScrollPhysics {
  final double itemDimension;

  const CustomScrollPhysics(
      {required this.itemDimension, ScrollPhysics? parent})
      : super(parent: parent);

  @override
  CustomScrollPhysics applyTo(ScrollPhysics? ancestor) {
    return CustomScrollPhysics(
        itemDimension: itemDimension, parent: buildParent(ancestor));
  }

  double _getPage(ScrollMetrics position, double portion) {
    // <--
    return (position.pixels + portion) / itemDimension;
    // -->
  }

  double _getPixels(double page, double portion) {
    // <--
    return (page * itemDimension) - portion;
    // -->
  }

  double _getTargetPixels(
    ScrollMetrics position,
    Tolerance tolerance,
    double velocity,
    double portion,
  ) {
    // <--
    double page = _getPage(position, portion);
    // -->
    if (velocity < -tolerance.velocity) {
      page -= 0.5;
    } else if (velocity > tolerance.velocity) {
      page += 0.5;
    }
    // <--
    return _getPixels(page.roundToDouble(), portion);
    // -->
  }

  @override
  Simulation? createBallisticSimulation(
      ScrollMetrics position, double velocity) {
    // If we're out of range and not headed back in range, defer to the parent
    // ballistics, which should put us back in range at a page boundary.
    if ((velocity <= 0.0 && position.pixels <= position.minScrollExtent) ||
        (velocity >= 0.0 && position.pixels >= position.maxScrollExtent)) {
      return super.createBallisticSimulation(position, velocity);
    }

    final Tolerance tolerance = this.tolerance;
    // <--
    final portion = (position.extentInside - itemDimension) / 2;
    final double target =
        _getTargetPixels(position, tolerance, velocity, portion);
    // -->
    if (target != position.pixels) {
      return ScrollSpringSimulation(spring, position.pixels, target, velocity,
          tolerance: tolerance);
    }
    return null;
  }

  @override
  bool get allowImplicitScrolling => false;
}

In summary, what I did is to take half of the extra space left by the current visible widget (i.e (position.extentInside - itemDimension) / 2) and add it to the page calculation based on the scroll position, allowing the widget to be smaller that the visible scroll size but considering the whole extent as a single page, and subtract it to the scroll pixels calculation based on the page, preventing a "page" to be placed past or before the half visible part of the widgets at their sides.

The other change is that itemDimension is not the scroll extent divided by the element amount, I needed this value to be the size of each widget in the scroll direction.

This is what I end up with:

final result

Of course, this implementation has some limitations:

  • The size of each element in the scroll direction must be fixed, if a single element has a different size, then the whole scroll behaves erratically
  • The size must include the padding in case there is some, otherwise it will have the same effect that having widgets of different sizes

I didn't focus on solving this limitations and having a more complete widget because this limitations are ensured in the case I need this widget for. Here is the complete code of the above example.

https://gist.github.com/rolurq/5db4c0cb7db66cf8f5a59396faeec7fa

Solution 2

Setting the PageView's padEnds property to false should do the trick 👌🏽.

...the property probably didn't exist in the past.

Share:
8,827
Rolando Urquiza
Author by

Rolando Urquiza

Experienced developer with quick learning abilities. Motivated Pythonist and Go enthusiast. A git and linux fanatic.

Updated on December 12, 2022

Comments

  • Rolando Urquiza
    Rolando Urquiza over 1 year

    I want to render cards with a horizontal paged scroll and be able to see the borders of the previous and next card every time one is visible. The flutter PageView widget produces almost the result I want, but it doesn't show the pages aligned the way I want, this is my code

    import 'package:flutter/material.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      // This widget is the root of your application.
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'PageView Demo',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: MyHomePage(title: 'PageView Alignment'),
        );
      }
    }
    
    class MyHomePage extends StatelessWidget {
      const MyHomePage({Key key, this.title}) : super(key: key);
    
      final String title;
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: Text(title)),
          body: PageView.builder(
            itemCount: 5,
            itemBuilder: (context, i) => Container(
                  color: Colors.blue,
                  margin: const EdgeInsets.only(right: 10),
                  child: Center(child: Text("Page $i")),
                ),
            controller: PageController(viewportFraction: .7),
          ),
        );
      }
    }
    

    this is the result the above code produces enter image description here

    I want the PageView to be aligned to the left of the screen, or at least that first page, i.e to remove that blank space at the left of Page 0. I s there any PageView parameter I'm missing? Or does some other component exists that produces the result I'm looking for?

  • Koen Van Looveren
    Koen Van Looveren over 4 years
    UPVOTE THIS DUDE. Verry nice sollution. I would like to see this be implemented in the PageView
  • Rolando Urquiza
    Rolando Urquiza over 4 years
    Thanks @KoenVanLooveren !! I agree with you, this should be implemented in PageView, I don't know why it isn't, I have seen this behavior in many apps
  • Koen Van Looveren
    Koen Van Looveren over 4 years
    Indeed maybe you could try to implement this in the pageview itself and merge it to the main branch?
  • Rolando Urquiza
    Rolando Urquiza over 4 years
    You're right, and I will, I should clean it just a bit and work in the limitations. Also, I think that changing the sliver used when viewportFraction is set can work without having to deal with the ScrollPhysics
  • Koen Van Looveren
    Koen Van Looveren over 4 years
    That is what I tried before. I could not figure it out. The problem with the scrollphysics for me was navigating to a specific page. This is possible with the PageViewController
  • Andrey Gordeev
    Andrey Gordeev over 4 years
    The snippet code is not valid: itemExtent is not defined. Had to use code from the gist.
  • Rolando Urquiza
    Rolando Urquiza over 4 years
    @AndreyGordeev sorry, it was a naming error, itemExtent should be itemDimension I edited the code snippet, thanks for noticing.
  • Juliano
    Juliano over 4 years
    I would rename CustomScrollPhysics to SnapScrollPhysics or something like that.
  • ZhangLei
    ZhangLei almost 4 years
    On iOS devices, where the default physics is BouncingScrollPhysics, the condition of returning super.createBallisticSimulation(position, velocity) should contain position.outOfRange, or the method applyBoundaryConditions should be overridden to avoid unexpected bug caused by the iOS bouncing effect.
  • sta
    sta over 2 years
    Plagiarism of above answer stackoverflow.com/a/69151759/4575350
  • Admin
    Admin over 2 years
    Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.
  • Nantaphop
    Nantaphop over 2 years
    Dude, you save my day