Align a flutter PageView to the screen left
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:
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.
Rolando Urquiza
Experienced developer with quick learning abilities. Motivated Pythonist and Go enthusiast. A git and linux fanatic.
Updated on December 12, 2022Comments
-
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
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 over 4 yearsUPVOTE THIS DUDE. Verry nice sollution. I would like to see this be implemented in the PageView
-
Rolando Urquiza over 4 yearsThanks @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 over 4 yearsIndeed maybe you could try to implement this in the pageview itself and merge it to the main branch?
-
Rolando Urquiza over 4 yearsYou'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 theScrollPhysics
-
Koen Van Looveren over 4 yearsThat 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 over 4 yearsThe snippet code is not valid:
itemExtent
is not defined. Had to use code from the gist. -
Rolando Urquiza over 4 years@AndreyGordeev sorry, it was a naming error,
itemExtent
should beitemDimension
I edited the code snippet, thanks for noticing. -
Juliano over 4 yearsI would rename CustomScrollPhysics to SnapScrollPhysics or something like that.
-
ZhangLei almost 4 yearsOn iOS devices, where the default physics is BouncingScrollPhysics, the condition of returning
super.createBallisticSimulation(position, velocity)
should containposition.outOfRange
, or the methodapplyBoundaryConditions
should be overridden to avoid unexpected bug caused by the iOS bouncing effect. -
sta over 2 yearsPlagiarism of above answer stackoverflow.com/a/69151759/4575350
-
Admin over 2 yearsYour 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 over 2 yearsDude, you save my day